feat: leaves
This commit is contained in:
parent
e2a138cfa6
commit
3d69190a37
@ -1,6 +1,8 @@
|
|||||||
use decorator::TreeDecorator;
|
use decorator::TreeDecorator;
|
||||||
use foliage::FoliagePlacer;
|
use foliage::FoliagePlacer;
|
||||||
use pumpkin_data::BlockState;
|
use pumpkin_data::BlockState;
|
||||||
|
use pumpkin_data::block_properties::{BlockProperties, OakLeavesLikeProperties};
|
||||||
|
use pumpkin_data::tag::Taggable;
|
||||||
use pumpkin_data::{BlockId, tag};
|
use pumpkin_data::{BlockId, tag};
|
||||||
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
|
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
|
||||||
use root::RootPlacer;
|
use root::RootPlacer;
|
||||||
@ -56,6 +58,10 @@ impl TreeFeature {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if log_positions.is_empty() && foliage_positions.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for decorator in &self.decorators {
|
for decorator in &self.decorators {
|
||||||
decorator.generate(
|
decorator.generate(
|
||||||
chunk,
|
chunk,
|
||||||
@ -66,9 +72,127 @@ impl TreeFeature {
|
|||||||
&foliage_positions,
|
&foliage_positions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self::update_leaves(chunk, &log_positions, &root_positions, &foliage_positions);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_leaves<T: GenerationCache>(
|
||||||
|
chunk: &mut T,
|
||||||
|
logs: &[BlockPos],
|
||||||
|
roots: &[BlockPos],
|
||||||
|
foliage: &[BlockPos],
|
||||||
|
) {
|
||||||
|
if logs.is_empty() && foliage.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min_x = i32::MAX;
|
||||||
|
let mut min_y = i32::MAX;
|
||||||
|
let mut min_z = i32::MAX;
|
||||||
|
let mut max_x = i32::MIN;
|
||||||
|
let mut max_y = i32::MIN;
|
||||||
|
let mut max_z = i32::MIN;
|
||||||
|
|
||||||
|
for pos in logs.iter().chain(roots.iter()).chain(foliage.iter()) {
|
||||||
|
min_x = min_x.min(pos.0.x);
|
||||||
|
min_y = min_y.min(pos.0.y);
|
||||||
|
min_z = min_z.min(pos.0.z);
|
||||||
|
max_x = max_x.max(pos.0.x);
|
||||||
|
max_y = max_y.max(pos.0.y);
|
||||||
|
max_z = max_z.max(pos.0.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_span = (max_x - min_x + 1) as usize;
|
||||||
|
let y_span = (max_y - min_y + 1) as usize;
|
||||||
|
let z_span = (max_z - min_z + 1) as usize;
|
||||||
|
|
||||||
|
let total_size = x_span * y_span * z_span;
|
||||||
|
let mut visited = vec![false; total_size];
|
||||||
|
|
||||||
|
let get_index = |x: i32, y: i32, z: i32| -> Option<usize> {
|
||||||
|
if x < min_x || x > max_x || y < min_y || y > max_y || z < min_z || z > max_z {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let x_idx = (x - min_x) as usize;
|
||||||
|
let y_idx = (y - min_y) as usize;
|
||||||
|
let z_idx = (z - min_z) as usize;
|
||||||
|
Some((x_idx * y_span + y_idx) * z_span + z_idx)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for pos in roots {
|
||||||
|
if let Some(idx) = get_index(pos.0.x, pos.0.y, pos.0.z) {
|
||||||
|
visited[idx] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut to_check: [std::collections::HashSet<BlockPos>; 7] = Default::default();
|
||||||
|
for pos in logs {
|
||||||
|
to_check[0].insert(*pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut smallest_distance = 0;
|
||||||
|
|
||||||
|
while smallest_distance < 7 {
|
||||||
|
while smallest_distance < 7 && !to_check[smallest_distance].is_empty() {
|
||||||
|
let Some(pos) = to_check[smallest_distance].iter().next().copied() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
to_check[smallest_distance].remove(&pos);
|
||||||
|
|
||||||
|
let Some(idx) = get_index(pos.0.x, pos.0.y, pos.0.z) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if smallest_distance != 0 {
|
||||||
|
let (block, state) = chunk.get_block_and_state(&pos);
|
||||||
|
if OakLeavesLikeProperties::handles_block_id(block.id) {
|
||||||
|
let mut props = OakLeavesLikeProperties::from_state_id(state.id, block);
|
||||||
|
props.distance = smallest_distance as u8;
|
||||||
|
let new_state = &block.states[props.to_index() as usize];
|
||||||
|
chunk.set_block_state(&pos.0, new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visited[idx] = true;
|
||||||
|
|
||||||
|
for direction in pumpkin_data::BlockDirection::all() {
|
||||||
|
let offset = direction.to_offset();
|
||||||
|
let neighbor_pos = pos.offset(offset);
|
||||||
|
if let Some(n_idx) =
|
||||||
|
get_index(neighbor_pos.0.x, neighbor_pos.0.y, neighbor_pos.0.z)
|
||||||
|
&& !visited[n_idx]
|
||||||
|
{
|
||||||
|
let (n_block, n_state) = chunk.get_block_and_state(&neighbor_pos);
|
||||||
|
let distance =
|
||||||
|
if n_block.has_tag(&tag::Block::MINECRAFT_PREVENTS_NEARBY_LEAF_DECAY) {
|
||||||
|
Some(0)
|
||||||
|
} else if OakLeavesLikeProperties::handles_block_id(n_block.id) {
|
||||||
|
Some(
|
||||||
|
OakLeavesLikeProperties::from_state_id(n_state.id, n_block)
|
||||||
|
.distance as usize,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(dist) = distance {
|
||||||
|
let new_distance = dist.min(smallest_distance + 1);
|
||||||
|
if new_distance < 7 {
|
||||||
|
to_check[new_distance].insert(neighbor_pos);
|
||||||
|
smallest_distance = smallest_distance.min(new_distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smallest_distance += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn can_replace_or_log(state: &BlockState, id: BlockId) -> bool {
|
pub fn can_replace_or_log(state: &BlockState, id: BlockId) -> bool {
|
||||||
Self::can_replace(state, id) || id.has_tag(tag::Block::MINECRAFT_LOGS)
|
Self::can_replace(state, id) || id.has_tag(tag::Block::MINECRAFT_LOGS)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ impl BlockMetadata for BrushableBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BrushableBlock {
|
impl BrushableBlock {
|
||||||
pub async fn brush(
|
pub fn brush(
|
||||||
world: &Arc<crate::world::World>,
|
world: &Arc<crate::world::World>,
|
||||||
pos: &pumpkin_util::math::position::BlockPos,
|
pos: &pumpkin_util::math::position::BlockPos,
|
||||||
block: &Block,
|
block: &Block,
|
||||||
|
|||||||
@ -65,10 +65,19 @@ impl BlockBehaviour for LeavesBlock {
|
|||||||
&self,
|
&self,
|
||||||
args: GetStateForNeighborUpdateArgs<'_>,
|
args: GetStateForNeighborUpdateArgs<'_>,
|
||||||
) -> BlockStateId {
|
) -> BlockStateId {
|
||||||
|
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
|
||||||
|
if current_props.waterlogged {
|
||||||
|
args.world.schedule_fluid_tick(
|
||||||
|
&pumpkin_data::fluid::Fluid::WATER,
|
||||||
|
*args.position,
|
||||||
|
pumpkin_data::fluid::Fluid::WATER.flow_speed as u8,
|
||||||
|
TickPriority::Normal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||||
let distance_from_neighbor =
|
let distance_from_neighbor =
|
||||||
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
|
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
|
||||||
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
|
|
||||||
|
|
||||||
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
|
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
|
||||||
args.world
|
args.world
|
||||||
|
|||||||
@ -27,7 +27,7 @@ impl BlockMetadata for SculkShriekerBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SculkShriekerBlock {
|
impl SculkShriekerBlock {
|
||||||
pub async fn try_activate(world: &Arc<World>, pos: &BlockPos) -> bool {
|
pub fn try_activate(world: &Arc<World>, pos: &BlockPos) -> bool {
|
||||||
let block = world.get_block(pos);
|
let block = world.get_block(pos);
|
||||||
if block.id != BlockId::SCULK_SHRIEKER {
|
if block.id != BlockId::SCULK_SHRIEKER {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -32,8 +32,8 @@ use crate::item::items::dye::DyeItem;
|
|||||||
use crate::item::items::glowing_ink_sac::GlowingInkSacItem;
|
use crate::item::items::glowing_ink_sac::GlowingInkSacItem;
|
||||||
use crate::item::items::honeycomb::HoneyCombItem;
|
use crate::item::items::honeycomb::HoneyCombItem;
|
||||||
use crate::item::items::ink_sac::InkSacItem;
|
use crate::item::items::ink_sac::InkSacItem;
|
||||||
use crate::net::ClientPlatform;
|
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
|
use pumpkin_protocol::java::client::play::COpenSignEditor;
|
||||||
|
|
||||||
#[pumpkin_block_from_tag("minecraft:all_signs")]
|
#[pumpkin_block_from_tag("minecraft:all_signs")]
|
||||||
pub struct SignBlock;
|
pub struct SignBlock;
|
||||||
@ -315,13 +315,8 @@ impl BlockBehaviour for SignBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
||||||
let client = args.player.client.clone();
|
args.player
|
||||||
let pos = *args.position;
|
.try_send_client_packet(&COpenSignEditor::new(*args.position, true));
|
||||||
tokio::spawn(async move {
|
|
||||||
if let crate::net::ClientPlatform::Java(java) = client.as_ref() {
|
|
||||||
java.send_sign_packet(pos, true).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||||
@ -454,13 +449,8 @@ impl BlockBehaviour for SignBlock {
|
|||||||
|
|
||||||
let is_facing_front_text =
|
let is_facing_front_text =
|
||||||
is_facing_front_text(args.world, args.position, args.block, args.player);
|
is_facing_front_text(args.world, args.position, args.block, args.player);
|
||||||
let client = args.player.client.clone();
|
args.player
|
||||||
let pos = *args.position;
|
.try_send_client_packet(&COpenSignEditor::new(*args.position, is_facing_front_text));
|
||||||
tokio::spawn(async move {
|
|
||||||
if let ClientPlatform::Java(java) = client.as_ref() {
|
|
||||||
java.send_sign_packet(pos, is_facing_front_text).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
BlockActionResult::SuccessServer
|
BlockActionResult::SuccessServer
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,7 @@ impl BlockEntity for CalibratedSculkSensorBlockEntity {
|
|||||||
impl CalibratedSculkSensorBlockEntity {
|
impl CalibratedSculkSensorBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:calibrated_sculk_sensor";
|
pub const ID: &'static str = "minecraft:calibrated_sculk_sensor";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
last_vibration_frequency: Mutex::new(0),
|
last_vibration_frequency: Mutex::new(0),
|
||||||
|
|||||||
@ -62,7 +62,7 @@ impl BlockEntity for ConduitBlockEntity {
|
|||||||
impl ConduitBlockEntity {
|
impl ConduitBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:conduit";
|
pub const ID: &'static str = "minecraft:conduit";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
active: Mutex::new(false),
|
active: Mutex::new(false),
|
||||||
|
|||||||
@ -82,7 +82,7 @@ impl BlockEntity for EndGatewayBlockEntity {
|
|||||||
impl EndGatewayBlockEntity {
|
impl EndGatewayBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:end_gateway";
|
pub const ID: &'static str = "minecraft:end_gateway";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
age: Mutex::new(0),
|
age: Mutex::new(0),
|
||||||
|
|||||||
@ -177,6 +177,7 @@ impl HopperBlockEntity {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn suck_in_items(&self, world: &Arc<World>) -> bool {
|
fn suck_in_items(&self, world: &Arc<World>) -> bool {
|
||||||
// TODO getEntityContainer
|
// TODO getEntityContainer
|
||||||
let pos_up = &self.position.up();
|
let pos_up = &self.position.up();
|
||||||
|
|||||||
@ -74,7 +74,7 @@ impl JigsawBlockEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate(&self, world: &Arc<World>, levels: i32, keep_jigsaws: bool) {
|
pub fn generate(&self, world: &Arc<World>, levels: i32, keep_jigsaws: bool) {
|
||||||
let pool = self
|
let pool = self
|
||||||
.pool
|
.pool
|
||||||
.lock()
|
.lock()
|
||||||
|
|||||||
@ -48,7 +48,7 @@ impl BlockEntity for SculkCatalystBlockEntity {
|
|||||||
impl SculkCatalystBlockEntity {
|
impl SculkCatalystBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:sculk_catalyst";
|
pub const ID: &'static str = "minecraft:sculk_catalyst";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
decay_delay: Mutex::new(0),
|
decay_delay: Mutex::new(0),
|
||||||
|
|||||||
@ -51,7 +51,7 @@ impl BlockEntity for SculkSensorBlockEntity {
|
|||||||
impl SculkSensorBlockEntity {
|
impl SculkSensorBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:sculk_sensor";
|
pub const ID: &'static str = "minecraft:sculk_sensor";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
last_vibration_frequency: Mutex::new(0),
|
last_vibration_frequency: Mutex::new(0),
|
||||||
|
|||||||
@ -48,7 +48,7 @@ impl BlockEntity for SculkShriekerBlockEntity {
|
|||||||
impl SculkShriekerBlockEntity {
|
impl SculkShriekerBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:sculk_shrieker";
|
pub const ID: &'static str = "minecraft:sculk_shrieker";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
warning_level: Mutex::new(0),
|
warning_level: Mutex::new(0),
|
||||||
|
|||||||
@ -77,7 +77,7 @@ impl BlockEntity for TrialSpawnerBlockEntity {
|
|||||||
impl TrialSpawnerBlockEntity {
|
impl TrialSpawnerBlockEntity {
|
||||||
pub const ID: &'static str = "minecraft:trial_spawner";
|
pub const ID: &'static str = "minecraft:trial_spawner";
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(position: BlockPos) -> Self {
|
pub const fn new(position: BlockPos) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
normal_config: Mutex::new(None),
|
normal_config: Mutex::new(None),
|
||||||
|
|||||||
@ -146,7 +146,10 @@ impl Action {
|
|||||||
advancements: &[&'static Advancement],
|
advancements: &[&'static Advancement],
|
||||||
show_advancement: bool,
|
show_advancement: bool,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
let mut guard = player.advancements.blocking_lock();
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
if !show_advancement {
|
if !show_advancement {
|
||||||
guard.flush_dirty(player, true);
|
guard.flush_dirty(player, true);
|
||||||
}
|
}
|
||||||
@ -166,7 +169,10 @@ impl Action {
|
|||||||
advancement: &'static Advancement,
|
advancement: &'static Advancement,
|
||||||
criterion: &str,
|
criterion: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut guard = player.advancements.blocking_lock();
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
match self {
|
match self {
|
||||||
Self::Grant => guard.award(advancement, criterion),
|
Self::Grant => guard.award(advancement, criterion),
|
||||||
Self::Revoke => guard.revoke(advancement, criterion),
|
Self::Revoke => guard.revoke(advancement, criterion),
|
||||||
|
|||||||
@ -57,7 +57,10 @@ impl AdvancementManager {
|
|||||||
}
|
}
|
||||||
let mut to_write = Vec::with_capacity(players.len());
|
let mut to_write = Vec::with_capacity(players.len());
|
||||||
for player in players {
|
for player in players {
|
||||||
let guard = player.advancements.lock().await;
|
let guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let json = serde_json::to_string_pretty(&*guard).map_err(AdvancementDataError::Json)?;
|
let json = serde_json::to_string_pretty(&*guard).map_err(AdvancementDataError::Json)?;
|
||||||
to_write.push((guard.path.clone(), json));
|
to_write.push((guard.path.clone(), json));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2041,7 +2041,11 @@ impl LivingEntity {
|
|||||||
self.last_damage_taken.store(0f32);
|
self.last_damage_taken.store(0f32);
|
||||||
|
|
||||||
self.entity.portal_cooldown.store(0, Relaxed);
|
self.entity.portal_cooldown.store(0, Relaxed);
|
||||||
*self.entity.portal_manager.blocking_lock() = None;
|
*self
|
||||||
|
.entity
|
||||||
|
.portal_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
|
||||||
|
|
||||||
// Clear fall/fire state
|
// Clear fall/fire state
|
||||||
self.fall_distance.store(0f32);
|
self.fall_distance.store(0f32);
|
||||||
|
|||||||
@ -79,7 +79,6 @@ use std::sync::{
|
|||||||
Ordering::{self, Relaxed},
|
Ordering::{self, Relaxed},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod ageable;
|
pub mod ageable;
|
||||||
@ -846,7 +845,7 @@ pub struct Entity {
|
|||||||
|
|
||||||
pub portal_cooldown: AtomicU32,
|
pub portal_cooldown: AtomicU32,
|
||||||
|
|
||||||
pub portal_manager: Mutex<Option<Mutex<PortalProcessor>>>,
|
pub portal_manager: std::sync::Mutex<Option<PortalProcessor>>,
|
||||||
/// Custom name for the entity
|
/// Custom name for the entity
|
||||||
pub custom_name: ArcSwap<Option<TextComponent>>,
|
pub custom_name: ArcSwap<Option<TextComponent>>,
|
||||||
/// Indicates whether the entity's custom name is visible
|
/// Indicates whether the entity's custom name is visible
|
||||||
@ -994,7 +993,7 @@ impl Entity {
|
|||||||
current_biome: ArcSwap::new(Arc::new(current_biome)),
|
current_biome: ArcSwap::new(Arc::new(current_biome)),
|
||||||
last_biome_update_pos: AtomicCell::new(BlockPos::new(floor_x, floor_y, floor_z)),
|
last_biome_update_pos: AtomicCell::new(BlockPos::new(floor_x, floor_y, floor_z)),
|
||||||
portal_cooldown: AtomicU32::new(0),
|
portal_cooldown: AtomicU32::new(0),
|
||||||
portal_manager: Mutex::new(None),
|
portal_manager: std::sync::Mutex::new(None),
|
||||||
custom_name: ArcSwap::new(Arc::new(None)),
|
custom_name: ArcSwap::new(Arc::new(None)),
|
||||||
custom_name_visible: AtomicBool::new(false),
|
custom_name_visible: AtomicBool::new(false),
|
||||||
silent: AtomicBool::new(false),
|
silent: AtomicBool::new(false),
|
||||||
@ -2331,10 +2330,7 @@ impl Entity {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut should_remove = false;
|
let mut should_remove = false;
|
||||||
if let Some(pmanager_mutex) = manager_guard.as_ref() {
|
if let Some(portal_processor) = manager_guard.as_mut() {
|
||||||
let Ok(mut portal_processor) = pmanager_mutex.try_lock() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if portal_processor.process_portal_teleportation(
|
if portal_processor.process_portal_teleportation(
|
||||||
&self.world.load(),
|
&self.world.load(),
|
||||||
caller.as_ref(),
|
caller.as_ref(),
|
||||||
@ -2495,7 +2491,10 @@ impl Entity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut manager = self.portal_manager.blocking_lock();
|
let mut manager = self
|
||||||
|
.portal_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
if manager.is_none() {
|
if manager.is_none() {
|
||||||
let portal_type = if portal_world.dimension == Dimension::THE_END
|
let portal_type = if portal_world.dimension == Dimension::THE_END
|
||||||
@ -2534,9 +2533,8 @@ impl Entity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
*manager = Some(Mutex::new(new_manager));
|
*manager = Some(new_manager);
|
||||||
} else if let Some(manager) = manager.as_ref() {
|
} else if let Some(manager) = manager.as_mut() {
|
||||||
let mut manager = manager.blocking_lock();
|
|
||||||
manager.entry_position = pos;
|
manager.entry_position = pos;
|
||||||
manager.inside_portal_this_tick = true;
|
manager.inside_portal_this_tick = true;
|
||||||
}
|
}
|
||||||
@ -3561,7 +3559,11 @@ impl Entity {
|
|||||||
// Use fallback position as placeholder — updated below with real position
|
// Use fallback position as placeholder — updated below with real position
|
||||||
let placeholder =
|
let placeholder =
|
||||||
Vector3::new(self.pos.load().x, vehicle_box.max.y, self.pos.load().z);
|
Vector3::new(self.pos.load().x, vehicle_box.max.y, self.pos.load().z);
|
||||||
*player.awaiting_teleport.lock().await = Some((id.into(), placeholder));
|
*player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
||||||
|
Some((id.into(), placeholder));
|
||||||
Some(id)
|
Some(id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -3774,7 +3776,11 @@ impl Entity {
|
|||||||
if let Some(id) = teleport_id {
|
if let Some(id) = teleport_id {
|
||||||
player.get_entity().set_pos(dismount_pos);
|
player.get_entity().set_pos(dismount_pos);
|
||||||
// Update awaiting_teleport with the real dismount position
|
// Update awaiting_teleport with the real dismount position
|
||||||
*player.awaiting_teleport.lock().await = Some((id.into(), dismount_pos));
|
*player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
||||||
|
Some((id.into(), dismount_pos));
|
||||||
// Use send_client_packet so the teleport goes through
|
// Use send_client_packet so the teleport goes through
|
||||||
// the same packet queue as CSetPassengers, preserving send order.
|
// the same packet queue as CSetPassengers, preserving send order.
|
||||||
// Vanilla uses DELTA | ROT flags: position absolute, delta/rotation relative.
|
// Vanilla uses DELTA | ROT flags: position absolute, delta/rotation relative.
|
||||||
|
|||||||
@ -610,7 +610,8 @@ impl VillagerEntity {
|
|||||||
target.0.z,
|
target.0.z,
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
map.blocking_lock()
|
map.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.decorations
|
.decorations
|
||||||
.push(crate::world::map::MapDecoration {
|
.push(crate::world::map::MapDecoration {
|
||||||
icon_type,
|
icon_type,
|
||||||
@ -811,7 +812,7 @@ impl VillagerEntity {
|
|||||||
< range * range
|
< range * range
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn complete_trade(&self, offer_index: usize, world: &Arc<World>, player_uuid: Uuid) {
|
fn complete_trade(&self, offer_index: usize, world: &Arc<World>, player_uuid: Uuid) {
|
||||||
let (xp_gain, reward_exp) = {
|
let (xp_gain, reward_exp) = {
|
||||||
let mut offers = self
|
let mut offers = self
|
||||||
.offers
|
.offers
|
||||||
@ -855,11 +856,11 @@ impl VillagerEntity {
|
|||||||
.or_default();
|
.or_default();
|
||||||
*value = (*value + 2).min(GossipType::Trading.max_value());
|
*value = (*value + 2).min(GossipType::Trading.max_value());
|
||||||
};
|
};
|
||||||
self.resend_offers_to_player(&player).await;
|
self.resend_offers_to_player(&player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resend_offers_to_player(&self, player: &Arc<Player>) {
|
fn resend_offers_to_player(&self, player: &Arc<Player>) {
|
||||||
let trading_player = *self
|
let trading_player = *self
|
||||||
.trading_player
|
.trading_player
|
||||||
.lock()
|
.lock()
|
||||||
@ -904,11 +905,10 @@ impl VillagerEntity {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.send_trade_offers(player, sync_id, offers, villager_data)
|
self.send_trade_offers(player, sync_id, &offers, villager_data);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resend_offers_to_trading_player(&self) {
|
fn resend_offers_to_trading_player(&self) {
|
||||||
let trading_player = *self
|
let trading_player = *self
|
||||||
.trading_player
|
.trading_player
|
||||||
.lock()
|
.lock()
|
||||||
@ -954,8 +954,7 @@ impl VillagerEntity {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.send_trade_offers(&player, sync_id, offers, villager_data)
|
self.send_trade_offers(&player, sync_id, &offers, villager_data);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decay_gossips(&self, game_time: i64) {
|
fn decay_gossips(&self, game_time: i64) {
|
||||||
@ -997,9 +996,7 @@ impl VillagerEntity {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Weak::upgrade)
|
.and_then(Weak::upgrade)
|
||||||
{
|
{
|
||||||
tokio::spawn(async move {
|
villager.resend_offers_to_trading_player();
|
||||||
villager.resend_offers_to_trading_player().await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1298,7 +1295,7 @@ impl VillagerEntity {
|
|||||||
entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo);
|
entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn open_trading_screen(&self, player: &Arc<Player>) {
|
pub fn open_trading_screen(&self, player: &Arc<Player>) {
|
||||||
// Open the merchant screen and then send the current offers packet
|
// Open the merchant screen and then send the current offers packet
|
||||||
if let Some(sync_id) = player.open_handled_screen(self, None) {
|
if let Some(sync_id) = player.open_handled_screen(self, None) {
|
||||||
let offers = self
|
let offers = self
|
||||||
@ -1310,8 +1307,7 @@ impl VillagerEntity {
|
|||||||
.villager_data
|
.villager_data
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
self.send_trade_offers(player, sync_id, offers, villager_data)
|
self.send_trade_offers(player, sync_id, &offers, villager_data);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1421,18 +1417,18 @@ impl VillagerEntity {
|
|||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_trade_offers(
|
fn send_trade_offers(
|
||||||
&self,
|
&self,
|
||||||
player: &Player,
|
player: &Player,
|
||||||
sync_id: u8,
|
sync_id: u8,
|
||||||
offers: Vec<pumpkin_protocol::java::client::play::MerchantOffer>,
|
offers: &[pumpkin_protocol::java::client::play::MerchantOffer],
|
||||||
villager_data: VillagerData,
|
villager_data: VillagerData,
|
||||||
) {
|
) {
|
||||||
use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong};
|
use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong};
|
||||||
|
|
||||||
let java = CMerchantOffers::new(
|
let java = CMerchantOffers::new(
|
||||||
VarInt(i32::from(sync_id)),
|
VarInt(i32::from(sync_id)),
|
||||||
offers.clone(),
|
offers.to_owned(),
|
||||||
villager_data.level,
|
villager_data.level,
|
||||||
VarInt(self.xp.load(Ordering::Relaxed)),
|
VarInt(self.xp.load(Ordering::Relaxed)),
|
||||||
true,
|
true,
|
||||||
@ -1448,12 +1444,9 @@ impl VillagerEntity {
|
|||||||
display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(),
|
display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(),
|
||||||
use_new_trade_screen: true,
|
use_new_trade_screen: true,
|
||||||
using_economy_trade: true,
|
using_economy_trade: true,
|
||||||
data: Self::bedrock_trade_data(&offers, villager_data.level.0),
|
data: Self::bedrock_trade_data(offers, villager_data.level.0),
|
||||||
};
|
};
|
||||||
player
|
player.client.try_enqueue_packet_editioned(&java, &bedrock);
|
||||||
.client
|
|
||||||
.enqueue_packet_editioned(&java, &bedrock)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1531,12 +1524,7 @@ impl ScreenHandlerFactory for VillagerEntity {
|
|||||||
|
|
||||||
handler.on_trade = Some(Box::new(move |offer_index| {
|
handler.on_trade = Some(Box::new(move |offer_index| {
|
||||||
if let Some(villager) = self_weak.upgrade() {
|
if let Some(villager) = self_weak.upgrade() {
|
||||||
let world = world.clone();
|
villager.complete_trade(offer_index, &world, player_uuid);
|
||||||
tokio::spawn(async move {
|
|
||||||
villager
|
|
||||||
.complete_trade(offer_index, &world, player_uuid)
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -2357,10 +2345,7 @@ impl Mob for VillagerEntity {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Weak::upgrade);
|
.and_then(Weak::upgrade);
|
||||||
if let Some(villager) = villager {
|
if let Some(villager) = villager {
|
||||||
let player = player.clone();
|
villager.open_trading_screen(player);
|
||||||
tokio::spawn(async move {
|
|
||||||
villager.open_trading_screen(&player).await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|||||||
@ -297,14 +297,14 @@ impl WanderingTraderEntity {
|
|||||||
add_offers_from_trade_set(&mut offers, TRADES_WANDERING_TRADER_COMMON, 5, &mut rng);
|
add_offers_from_trade_set(&mut offers, TRADES_WANDERING_TRADER_COMMON, 5, &mut rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn open_trading_screen(&self, player: &Arc<Player>) {
|
pub fn open_trading_screen(&self, player: &Arc<Player>) {
|
||||||
if let Some(sync_id) = player.open_handled_screen(self, None) {
|
if let Some(sync_id) = player.open_handled_screen(self, None) {
|
||||||
let offers = self
|
let offers = self
|
||||||
.offers
|
.offers
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.clone();
|
.clone();
|
||||||
self.send_trade_offers(player, sync_id, offers).await;
|
self.send_trade_offers(player, sync_id, &offers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,17 +395,17 @@ impl WanderingTraderEntity {
|
|||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_trade_offers(
|
fn send_trade_offers(
|
||||||
&self,
|
&self,
|
||||||
player: &Player,
|
player: &Player,
|
||||||
sync_id: u8,
|
sync_id: u8,
|
||||||
offers: Vec<pumpkin_protocol::java::client::play::MerchantOffer>,
|
offers: &[pumpkin_protocol::java::client::play::MerchantOffer],
|
||||||
) {
|
) {
|
||||||
use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong};
|
use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong};
|
||||||
|
|
||||||
let java = CMerchantOffers::new(
|
let java = CMerchantOffers::new(
|
||||||
VarInt(i32::from(sync_id)),
|
VarInt(i32::from(sync_id)),
|
||||||
offers.clone(),
|
offers.to_owned(),
|
||||||
VarInt(1),
|
VarInt(1),
|
||||||
VarInt(0),
|
VarInt(0),
|
||||||
false,
|
false,
|
||||||
@ -421,12 +421,9 @@ impl WanderingTraderEntity {
|
|||||||
display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(),
|
display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(),
|
||||||
use_new_trade_screen: true,
|
use_new_trade_screen: true,
|
||||||
using_economy_trade: true,
|
using_economy_trade: true,
|
||||||
data: Self::bedrock_trade_data(&offers),
|
data: Self::bedrock_trade_data(offers),
|
||||||
};
|
};
|
||||||
player
|
player.client.try_enqueue_packet_editioned(&java, &bedrock);
|
||||||
.client
|
|
||||||
.enqueue_packet_editioned(&java, &bedrock)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_continue_trading(
|
fn can_continue_trading(
|
||||||
@ -782,10 +779,7 @@ impl Mob for WanderingTraderEntity {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Weak::upgrade);
|
.and_then(Weak::upgrade);
|
||||||
if let Some(trader) = trader {
|
if let Some(trader) = trader {
|
||||||
let player = player.clone();
|
trader.open_trading_screen(player);
|
||||||
tokio::spawn(async move {
|
|
||||||
trader.open_trading_screen(&player).await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::f64::consts::TAU;
|
|||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::plugin::api::events::enchantment::{EnchantItemEvent, PrepareItemEnchantEvent};
|
use crate::plugin::api::events::enchantment::{EnchantItemEvent, PrepareItemEnchantEvent};
|
||||||
@ -41,7 +41,6 @@ use pumpkin_util::translation::Locale;
|
|||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
||||||
use pumpkin_world::inventory::Inventory;
|
use pumpkin_world::inventory::Inventory;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -1136,8 +1135,14 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) {
|
pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) {
|
||||||
*self.tab_list_header.lock().await = header.clone();
|
*self
|
||||||
*self.tab_list_footer.lock().await = footer.clone();
|
.tab_list_header
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = header.clone();
|
||||||
|
*self
|
||||||
|
.tab_list_footer
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = footer.clone();
|
||||||
self.send_client_packet(&CTabList::new(&header, &footer))
|
self.send_client_packet(&CTabList::new(&header, &footer))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -1204,18 +1209,25 @@ impl Player {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_tab_list_name(&self) -> Option<TextComponent> {
|
pub fn get_tab_list_name(&self) -> Option<TextComponent> {
|
||||||
self.tab_list_name.lock().await.clone()
|
self.tab_list_name
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_tab_list_name(&self, name: Option<TextComponent>) {
|
pub fn set_tab_list_name(&self, name: Option<TextComponent>) {
|
||||||
*self.tab_list_name.lock().await = name.clone();
|
let mut guard = self
|
||||||
|
.tab_list_name
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
*guard = name;
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
world.broadcast_packet_all(&CPlayerInfoUpdate::new(
|
world.broadcast_packet_all(&CPlayerInfoUpdate::new(
|
||||||
PlayerInfoFlags::UPDATE_DISPLAY_NAME.bits(),
|
PlayerInfoFlags::UPDATE_DISPLAY_NAME.bits(),
|
||||||
&[pumpkin_protocol::java::client::play::Player {
|
&[pumpkin_protocol::java::client::play::Player {
|
||||||
uuid: self.gameprofile.id,
|
uuid: self.gameprofile.id,
|
||||||
actions: &[PlayerAction::UpdateDisplayName(name.as_ref())],
|
actions: &[PlayerAction::UpdateDisplayName(guard.as_ref())],
|
||||||
}],
|
}],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -1331,7 +1343,10 @@ impl Player {
|
|||||||
world.remove_player(self, true).await;
|
world.remove_player(self, true).await;
|
||||||
|
|
||||||
let cylindrical = self.watched_section.load();
|
let cylindrical = self.watched_section.load();
|
||||||
self.chunk_manager.lock().await.clean_up(&world.level);
|
self.chunk_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.clean_up(&world.level);
|
||||||
|
|
||||||
// Radial chunks are all of the chunks the player is theoretically viewing.
|
// Radial chunks are all of the chunks the player is theoretically viewing.
|
||||||
// Given enough time, all of these chunks will be in memory.
|
// Given enough time, all of these chunks will be in memory.
|
||||||
@ -2551,12 +2566,15 @@ impl Player {
|
|||||||
let world_clone = p.world();
|
let world_clone = p.world();
|
||||||
let server_clone = world_clone.server.upgrade();
|
let server_clone = world_clone.server.upgrade();
|
||||||
server.runtime.spawn(async move {
|
server.runtime.spawn(async move {
|
||||||
let pos = *p.mining_pos.lock().await;
|
let pos = *p
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let world = p.world();
|
let world = p.world();
|
||||||
let state = world.get_block_state(&pos);
|
let state = world.get_block_state(&pos);
|
||||||
// Is the block broken?
|
// Is the block broken?
|
||||||
if state.is_air() {
|
if state.is_air() {
|
||||||
p.stop_mining().await;
|
p.stop_mining();
|
||||||
} else {
|
} else {
|
||||||
let finished = p.continue_mining(
|
let finished = p.continue_mining(
|
||||||
pos,
|
pos,
|
||||||
@ -2565,7 +2583,7 @@ impl Player {
|
|||||||
p.start_mining_time.load(Ordering::Relaxed),
|
p.start_mining_time.load(Ordering::Relaxed),
|
||||||
);
|
);
|
||||||
if finished && matches!(p.client.as_ref(), ClientPlatform::Bedrock(_)) {
|
if finished && matches!(p.client.as_ref(), ClientPlatform::Bedrock(_)) {
|
||||||
p.stop_mining().await;
|
p.stop_mining();
|
||||||
|
|
||||||
let block = Block::from_state_id(state.id);
|
let block = Block::from_state_id(state.id);
|
||||||
let can_harvest = p.can_harvest(state, block);
|
let can_harvest = p.can_harvest(state, block);
|
||||||
@ -2677,14 +2695,17 @@ impl Player {
|
|||||||
total_progress >= 1.0
|
total_progress >= 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn stop_mining(&self) {
|
pub(crate) fn stop_mining(&self) {
|
||||||
let was_mining = self.mining.swap(false, Ordering::Relaxed);
|
let was_mining = self.mining.swap(false, Ordering::Relaxed);
|
||||||
let stage = self.current_block_destroy_stage.swap(-1, Ordering::Relaxed);
|
let stage = self.current_block_destroy_stage.swap(-1, Ordering::Relaxed);
|
||||||
self.current_block_breaking_speed
|
self.current_block_breaking_speed
|
||||||
.store(0, Ordering::Relaxed);
|
.store(0, Ordering::Relaxed);
|
||||||
|
|
||||||
if was_mining || stage >= 0 {
|
if was_mining || stage >= 0 {
|
||||||
let pos = *self.mining_pos.lock().await;
|
let pos = *self
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
self.world().set_block_breaking(
|
self.world().set_block_breaking(
|
||||||
&self.living_entity.entity,
|
&self.living_entity.entity,
|
||||||
pos,
|
pos,
|
||||||
@ -3355,20 +3376,30 @@ impl Player {
|
|||||||
self.respawn_location.load()
|
self.respawn_location.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn hide_player(&self, other_id: uuid::Uuid) {
|
pub fn hide_player(&self, other_id: uuid::Uuid) {
|
||||||
self.hidden_players.lock().await.insert(other_id);
|
self.hidden_players
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.insert(other_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_player(&self, other_id: uuid::Uuid) {
|
pub fn show_player(&self, other_id: uuid::Uuid) {
|
||||||
self.hidden_players.lock().await.remove(&other_id);
|
self.hidden_players
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.remove(&other_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn can_see(&self, other_id: &uuid::Uuid) -> bool {
|
pub fn can_see(&self, other_id: &uuid::Uuid) -> bool {
|
||||||
!self.hidden_players.lock().await.contains(other_id)
|
!self
|
||||||
|
.hidden_players
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.contains(other_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn can_see_player(&self, other_id: &uuid::Uuid) -> bool {
|
pub fn can_see_player(&self, other_id: &uuid::Uuid) -> bool {
|
||||||
self.can_see(other_id).await
|
self.can_see(other_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Experience & Leveling API ---
|
// --- Experience & Leveling API ---
|
||||||
@ -3537,7 +3568,10 @@ impl Player {
|
|||||||
});
|
});
|
||||||
self.unload_watched_chunks(¤t_world).await;
|
self.unload_watched_chunks(¤t_world).await;
|
||||||
|
|
||||||
self.chunk_manager.lock().await.change_world(¤t_world.level, new_world.clone());
|
self.chunk_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.change_world(¤t_world.level, new_world.clone());
|
||||||
self.living_entity.entity.set_world(new_world.clone());
|
self.living_entity.entity.set_world(new_world.clone());
|
||||||
|
|
||||||
if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
||||||
@ -3651,7 +3685,10 @@ impl Player {
|
|||||||
entity.set_rotation(yaw, pitch);
|
entity.set_rotation(yaw, pitch);
|
||||||
match self.client.as_ref() {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
*self.awaiting_teleport.lock().await =
|
*self
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
||||||
Some((teleport_id.into(), position));
|
Some((teleport_id.into(), position));
|
||||||
let packet = CPlayerPosition::new(
|
let packet = CPlayerPosition::new(
|
||||||
teleport_id.into(),
|
teleport_id.into(),
|
||||||
@ -6747,16 +6784,14 @@ impl AsRef<[Box<[u8]>]> for LastSeen {
|
|||||||
impl LastSeen {
|
impl LastSeen {
|
||||||
/// The sender's `last_seen` signatures are sent as ID's if the recipient has them in their cache.
|
/// The sender's `last_seen` signatures are sent as ID's if the recipient has them in their cache.
|
||||||
/// Otherwise, the full signature is sent. (ID:0 indicates full signature is being sent)
|
/// Otherwise, the full signature is sent. (ID:0 indicates full signature is being sent)
|
||||||
pub async fn indexed_for(&self, recipient: &Arc<Player>) -> Box<[PreviousMessage]> {
|
pub fn indexed_for(&self, recipient: &Arc<Player>) -> Box<[PreviousMessage]> {
|
||||||
let mut indexed = Vec::new();
|
let mut indexed = Vec::new();
|
||||||
for signature in &self.0 {
|
let cache = recipient
|
||||||
let index = recipient
|
|
||||||
.signature_cache
|
.signature_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
.full_cache
|
for signature in &self.0 {
|
||||||
.iter()
|
let index = cache.full_cache.iter().position(|s| s == signature);
|
||||||
.position(|s| s == signature);
|
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
indexed.push(PreviousMessage {
|
indexed.push(PreviousMessage {
|
||||||
// Send ID reference to recipient's cache (index + 1 because 0 is reserved for full signature)
|
// Send ID reference to recipient's cache (index + 1 because 0 is reserved for full signature)
|
||||||
|
|||||||
@ -254,7 +254,7 @@ impl PlayerAdvancement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///reload the advancements from the file
|
///reload the advancements from the file
|
||||||
pub async fn reload(&mut self) -> Result<(), AdvancementDataError> {
|
pub fn reload(&mut self) -> Result<(), AdvancementDataError> {
|
||||||
//self.stopListening(); TODO
|
//self.stopListening(); TODO
|
||||||
self.progress.clear();
|
self.progress.clear();
|
||||||
self.visible.clear();
|
self.visible.clear();
|
||||||
@ -262,7 +262,7 @@ impl PlayerAdvancement {
|
|||||||
self.progress_changed.clear();
|
self.progress_changed.clear();
|
||||||
self.is_first_packet = true;
|
self.is_first_packet = true;
|
||||||
self.last_selected_tab = None;
|
self.last_selected_tab = None;
|
||||||
self.load().await
|
self.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves the player's advancement progress to disk as JSON.
|
/// Saves the player's advancement progress to disk as JSON.
|
||||||
@ -287,17 +287,12 @@ impl PlayerAdvancement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the player's advancement progress from disk.
|
/// Loads the player's advancement progress from disk.
|
||||||
pub async fn load(&mut self) -> Result<(), AdvancementDataError> {
|
pub fn load(&mut self) -> Result<(), AdvancementDataError> {
|
||||||
if !self.path.exists() || !self.is_save_enabled() {
|
if !self.path.exists() || !self.is_save_enabled() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = self.path.clone();
|
let json = read(&self.path).map_err(AdvancementDataError::Io)?;
|
||||||
let json = spawn_blocking(|| read(path).map_err(AdvancementDataError::Io))
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(AdvancementDataError::Io(std::io::Error::from(
|
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
))))?;
|
|
||||||
|
|
||||||
let loaded_data: HashMap<String, AdvancementProgress> =
|
let loaded_data: HashMap<String, AdvancementProgress> =
|
||||||
serde_json::from_slice(&json).map_err(AdvancementDataError::Json)?;
|
serde_json::from_slice(&json).map_err(AdvancementDataError::Json)?;
|
||||||
@ -391,18 +386,13 @@ impl PlayerAdvancement {
|
|||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let first_packet = self.is_first_packet;
|
player.try_send_client_packet(&CUpdateAdvancements::new(
|
||||||
tokio::spawn(async move {
|
self.is_first_packet,
|
||||||
player
|
|
||||||
.send_client_packet(&CUpdateAdvancements::new(
|
|
||||||
first_packet,
|
|
||||||
added,
|
added,
|
||||||
parsed_progress,
|
parsed_progress,
|
||||||
removed,
|
removed,
|
||||||
show_advancement,
|
show_advancement,
|
||||||
))
|
));
|
||||||
.await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.is_first_packet = false;
|
self.is_first_packet = false;
|
||||||
@ -487,7 +477,7 @@ impl PlayerAdvancement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// set the selected advancement tab of the player
|
/// set the selected advancement tab of the player
|
||||||
pub async fn set_selected_tab(&mut self, advancement: Option<&'static Advancement>) {
|
pub fn set_selected_tab(&mut self, advancement: Option<&'static Advancement>) {
|
||||||
let old = self.last_selected_tab;
|
let old = self.last_selected_tab;
|
||||||
if let Some(value) = advancement
|
if let Some(value) = advancement
|
||||||
&& value.is_root()
|
&& value.is_root()
|
||||||
@ -500,11 +490,8 @@ impl PlayerAdvancement {
|
|||||||
if old != self.last_selected_tab
|
if old != self.last_selected_tab
|
||||||
&& let Some(player) = self.player.upgrade()
|
&& let Some(player) = self.player.upgrade()
|
||||||
{
|
{
|
||||||
player
|
let tab_id = self.last_selected_tab.map(|adv| adv.id.clone());
|
||||||
.send_client_packet(&CSelectAdvancementsTab::new(
|
player.try_send_client_packet(&CSelectAdvancementsTab::new(tab_id));
|
||||||
self.last_selected_tab.map(|adv| adv.id.clone()),
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,7 +652,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load from nonexistent file should return Ok (not error)
|
// Load from nonexistent file should return Ok (not error)
|
||||||
assert!(
|
assert!(
|
||||||
pa.load().await.is_ok(),
|
pa.load().is_ok(),
|
||||||
"Loading from nonexistent file should return Ok"
|
"Loading from nonexistent file should return Ok"
|
||||||
);
|
);
|
||||||
assert!(pa.progress.is_empty(), "Advancements should remain empty");
|
assert!(pa.progress.is_empty(), "Advancements should remain empty");
|
||||||
@ -687,7 +674,7 @@ mod tests {
|
|||||||
std::fs::write(&pa.path, data.to_string()).unwrap();
|
std::fs::write(&pa.path, data.to_string()).unwrap();
|
||||||
|
|
||||||
// Load the file
|
// Load the file
|
||||||
assert!(pa.load().await.is_ok(), "Load should succeed");
|
assert!(pa.load().is_ok(), "Load should succeed");
|
||||||
|
|
||||||
// Verify the advancement was loaded
|
// Verify the advancement was loaded
|
||||||
let loaded_progress = pa.progress.get_mut_or_start_progress(adv);
|
let loaded_progress = pa.progress.get_mut_or_start_progress(adv);
|
||||||
@ -716,7 +703,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load the saved advancements into a new instance
|
// Load the saved advancements into a new instance
|
||||||
let mut pa_loaded = PlayerAdvancement::new(manager, id);
|
let mut pa_loaded = PlayerAdvancement::new(manager, id);
|
||||||
assert!(pa_loaded.load().await.is_ok(), "Load should succeed");
|
assert!(pa_loaded.load().is_ok(), "Load should succeed");
|
||||||
|
|
||||||
// Verify the loaded data matches the saved data
|
// Verify the loaded data matches the saved data
|
||||||
let loaded_progress = pa_loaded.progress.get_mut_or_start_progress(adv);
|
let loaded_progress = pa_loaded.progress.get_mut_or_start_progress(adv);
|
||||||
@ -756,7 +743,7 @@ mod tests {
|
|||||||
// Load should still succeed but skip the invalid entry
|
// Load should still succeed but skip the invalid entry
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
pa.load().await.is_ok(),
|
pa.load().is_ok(),
|
||||||
"Load should succeed even with invalid IDs"
|
"Load should succeed even with invalid IDs"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
@ -807,7 +794,7 @@ mod tests {
|
|||||||
std::fs::write(&pa.path, data.to_string()).unwrap();
|
std::fs::write(&pa.path, data.to_string()).unwrap();
|
||||||
|
|
||||||
//try load the file
|
//try load the file
|
||||||
assert!(pa.load().await.is_ok(), "Load should succeed");
|
assert!(pa.load().is_ok(), "Load should succeed");
|
||||||
|
|
||||||
// Verify that the advancement was not loaded
|
// Verify that the advancement was not loaded
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@ -33,9 +33,13 @@ impl BedrockClient {
|
|||||||
let (block, state) = world.get_block_and_state(&location);
|
let (block, state) = world.get_block_and_state(&location);
|
||||||
|
|
||||||
if player.mining.load(Ordering::Relaxed)
|
if player.mining.load(Ordering::Relaxed)
|
||||||
&& *player.mining_pos.lock().await != location
|
&& *player
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
!= location
|
||||||
{
|
{
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if player.gamemode.load() == GameMode::Creative {
|
if player.gamemode.load() == GameMode::Creative {
|
||||||
@ -52,7 +56,7 @@ impl BedrockClient {
|
|||||||
} else if !state.is_air() {
|
} else if !state.is_air() {
|
||||||
let speed = crate::block::calc_block_breaking(player, state, block);
|
let speed = crate::block::calc_block_breaking(player, state, block);
|
||||||
if speed >= 1.0 {
|
if speed >= 1.0 {
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
let broken_state = world.get_block_state(&location);
|
let broken_state = world.get_block_state(&location);
|
||||||
let can_harvest = player.can_harvest(broken_state, block);
|
let can_harvest = player.can_harvest(broken_state, block);
|
||||||
let new_state = world.break_block(
|
let new_state = world.break_block(
|
||||||
@ -75,7 +79,10 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut mining_pos = player.mining_pos.lock().await;
|
let mut mining_pos = player
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let starts_breaking =
|
let starts_breaking =
|
||||||
!player.mining.load(Ordering::Relaxed) || *mining_pos != location;
|
!player.mining.load(Ordering::Relaxed) || *mining_pos != location;
|
||||||
let progress = if starts_breaking {
|
let progress = if starts_breaking {
|
||||||
@ -133,12 +140,16 @@ impl BedrockClient {
|
|||||||
let elapsed = player.tick_counter.load(Ordering::Relaxed)
|
let elapsed = player.tick_counter.load(Ordering::Relaxed)
|
||||||
- player.start_mining_time.load(Ordering::Relaxed)
|
- player.start_mining_time.load(Ordering::Relaxed)
|
||||||
+ 1;
|
+ 1;
|
||||||
let same_block = *player.mining_pos.lock().await == location;
|
let same_block = *player
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
== location;
|
||||||
if player.mining.load(Ordering::Relaxed)
|
if player.mining.load(Ordering::Relaxed)
|
||||||
&& same_block
|
&& same_block
|
||||||
&& speed * elapsed as f32 >= MIN_PREDICTED_BREAK_PROGRESS
|
&& speed * elapsed as f32 >= MIN_PREDICTED_BREAK_PROGRESS
|
||||||
{
|
{
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
|
|
||||||
let can_harvest = player.can_harvest(state, block);
|
let can_harvest = player.can_harvest(state, block);
|
||||||
let flags = if can_harvest {
|
let flags = if can_harvest {
|
||||||
@ -163,7 +174,7 @@ impl BedrockClient {
|
|||||||
self.enqueue_client_packet(&CUpdateBlock::new(location, runtime_id as u32))
|
self.enqueue_client_packet(&CUpdateBlock::new(location, runtime_id as u32))
|
||||||
.await;
|
.await;
|
||||||
if matches!(action, PlayerAction::StopDestroyBlock) {
|
if matches!(action, PlayerAction::StopDestroyBlock) {
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
} else {
|
} else {
|
||||||
world.set_block_breaking(
|
world.set_block_breaking(
|
||||||
entity,
|
entity,
|
||||||
@ -178,7 +189,7 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if matches!(action, PlayerAction::StopDestroyBlock) {
|
} else if matches!(action, PlayerAction::StopDestroyBlock) {
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayerAction::CrackBlock => {
|
PlayerAction::CrackBlock => {
|
||||||
@ -186,7 +197,7 @@ impl BedrockClient {
|
|||||||
// cracking is done fully server-side.
|
// cracking is done fully server-side.
|
||||||
}
|
}
|
||||||
PlayerAction::AbortDestroyBlock => {
|
PlayerAction::AbortDestroyBlock => {
|
||||||
player.stop_mining().await;
|
player.stop_mining();
|
||||||
}
|
}
|
||||||
PlayerAction::DropItem => {
|
PlayerAction::DropItem => {
|
||||||
player.drop_held_item(false).await;
|
player.drop_held_item(false).await;
|
||||||
|
|||||||
@ -929,13 +929,14 @@ impl JavaClient {
|
|||||||
id if id == SSetJigsawBlock::to_id(version) => {
|
id if id == SSetJigsawBlock::to_id(version) => {
|
||||||
self.handle_set_jigsaw_block(
|
self.handle_set_jigsaw_block(
|
||||||
player,
|
player,
|
||||||
SSetJigsawBlock::read(&mut payload, &version)?,
|
&SSetJigsawBlock::read(&mut payload, &version)?,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
id if id == SJigsawGenerate::to_id(version) => {
|
id if id == SJigsawGenerate::to_id(version) => {
|
||||||
self.handle_jigsaw_generate(player, SJigsawGenerate::read(&mut payload, &version)?)
|
self.handle_jigsaw_generate(
|
||||||
.await;
|
player,
|
||||||
|
&SJigsawGenerate::read(&mut payload, &version)?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
id if id == SPlayerCommand::to_id(version) => {
|
id if id == SPlayerCommand::to_id(version) => {
|
||||||
self.handle_player_command(
|
self.handle_player_command(
|
||||||
@ -1016,8 +1017,7 @@ impl JavaClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
id if id == SChunkBatch::to_id(version) => {
|
id if id == SChunkBatch::to_id(version) => {
|
||||||
self.handle_chunk_batch(player, SChunkBatch::read(&mut payload, &version)?)
|
self.handle_chunk_batch(player, &SChunkBatch::read(&mut payload, &version)?);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
id if id == SPlayerSession::to_id(version) => {
|
id if id == SPlayerSession::to_id(version) => {
|
||||||
self.handle_chat_session_update(
|
self.handle_chat_session_update(
|
||||||
@ -1083,8 +1083,7 @@ impl JavaClient {
|
|||||||
self.handle_seen_advancement(
|
self.handle_seen_advancement(
|
||||||
player,
|
player,
|
||||||
SSeenAdvancement::read(&mut payload, &version)?,
|
SSeenAdvancement::read(&mut payload, &version)?,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
id if id == SPlayResourcePack::to_id(version) => {
|
id if id == SPlayResourcePack::to_id(version) => {
|
||||||
self.handle_play_resource_pack_response(
|
self.handle_play_resource_pack_response(
|
||||||
|
|||||||
@ -19,13 +19,22 @@ impl JavaClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cache = player.signature_cache.lock().await;
|
let validation_err = {
|
||||||
if let Err(err) = cache.last_seen_validator.apply_offset(offset as usize) {
|
let mut cache = player
|
||||||
|
.signature_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
cache
|
||||||
|
.last_seen_validator
|
||||||
|
.apply_offset(offset as usize)
|
||||||
|
.err()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = validation_err {
|
||||||
warn!(
|
warn!(
|
||||||
"Failed to validate message acknowledgement offset from {}: {}",
|
"Failed to validate message acknowledgement offset from {}: {}",
|
||||||
player.gameprofile.name, err
|
player.gameprofile.name, err
|
||||||
);
|
);
|
||||||
drop(cache);
|
|
||||||
self.kick(TextComponent::translate_cross(
|
self.kick(TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED,
|
translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED,
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED,
|
translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED,
|
||||||
|
|||||||
@ -19,10 +19,7 @@ impl JavaClient {
|
|||||||
|
|
||||||
let gameprofile = &player.gameprofile;
|
let gameprofile = &player.gameprofile;
|
||||||
|
|
||||||
if let Err(err) = self
|
if let Err(err) = self.validate_chat_message(server, player, &chat_message) {
|
||||||
.validate_chat_message(server, player, &chat_message)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log_at_level!(
|
log_at_level!(
|
||||||
err.severity(),
|
err.severity(),
|
||||||
"{} (uuid {}) {}",
|
"{} (uuid {}) {}",
|
||||||
@ -82,7 +79,7 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Runs all vanilla checks for a valid chat message
|
/// Runs all vanilla checks for a valid chat message
|
||||||
pub async fn validate_chat_message(
|
pub fn validate_chat_message(
|
||||||
&self,
|
&self,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
@ -124,7 +121,13 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify session expiry
|
// Verify session expiry
|
||||||
if player.chat_session.lock().await.expires_at < now {
|
if player
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.expires_at
|
||||||
|
< now
|
||||||
|
{
|
||||||
return Err(ChatError::ExpiredPublicKey);
|
return Err(ChatError::ExpiredPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +137,10 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut cache = player.signature_cache.lock().await;
|
let mut cache = player
|
||||||
|
.signature_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
if !chat_message.acknowledged.is_empty() {
|
if !chat_message.acknowledged.is_empty() {
|
||||||
if cache
|
if cache
|
||||||
.last_seen_validator
|
.last_seen_validator
|
||||||
@ -159,8 +165,14 @@ impl JavaClient {
|
|||||||
// Validate previous signature checksum (new in 1.21.5)
|
// Validate previous signature checksum (new in 1.21.5)
|
||||||
// The client can bypass this check by sending 0
|
// The client can bypass this check by sending 0
|
||||||
if chat_message.checksum != 0 {
|
if chat_message.checksum != 0 {
|
||||||
let checksum =
|
let checksum = polynomial_rolling_hash(
|
||||||
polynomial_rolling_hash(player.signature_cache.lock().await.last_seen.as_ref());
|
player
|
||||||
|
.signature_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.last_seen
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
if checksum != chat_message.checksum {
|
if checksum != chat_message.checksum {
|
||||||
return Err(ChatError::ChatValidationFailed);
|
return Err(ChatError::ChatValidationFailed);
|
||||||
}
|
}
|
||||||
@ -197,7 +209,10 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the chat session fields
|
// Update the chat session fields
|
||||||
*player.chat_session.lock().await = ChatSession::new(
|
*player
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = ChatSession::new(
|
||||||
session.session_id,
|
session.session_id,
|
||||||
session.expires_at,
|
session.expires_at,
|
||||||
session.public_key.clone(),
|
session.public_key.clone(),
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub async fn handle_chunk_batch(&self, player: &Player, packet: SChunkBatch) {
|
pub fn handle_chunk_batch(&self, player: &Player, packet: &SChunkBatch) {
|
||||||
player
|
player
|
||||||
.chunk_manager
|
.chunk_manager
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.handle_acknowledge(packet.chunks_per_tick);
|
.handle_acknowledge(packet.chunks_per_tick);
|
||||||
trace!(
|
trace!(
|
||||||
"Client requested {} chunks per tick",
|
"Client requested {} chunks per tick",
|
||||||
|
|||||||
@ -7,25 +7,43 @@ impl JavaClient {
|
|||||||
player: &Player,
|
player: &Player,
|
||||||
confirm_teleport: SConfirmTeleport,
|
confirm_teleport: SConfirmTeleport,
|
||||||
) {
|
) {
|
||||||
let mut awaiting_teleport = player.awaiting_teleport.lock().await;
|
enum TeleportResult {
|
||||||
|
Success,
|
||||||
|
WrongId,
|
||||||
|
NotTeleporting,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
let mut awaiting_teleport = player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
if let Some((id, position)) = awaiting_teleport.as_ref() {
|
if let Some((id, position)) = awaiting_teleport.as_ref() {
|
||||||
if id == &confirm_teleport.teleport_id {
|
if id == &confirm_teleport.teleport_id {
|
||||||
// We should set the position now to what we requested in the teleport packet.
|
// We should set the position now to what we requested in the teleport packet.
|
||||||
// This may fix issues when the client sends the position while being teleported.
|
// This may fix issues when the client sends the position while being teleported.
|
||||||
player.get_entity().set_pos(*position);
|
player.get_entity().set_pos(*position);
|
||||||
|
|
||||||
*awaiting_teleport = None;
|
*awaiting_teleport = None;
|
||||||
drop(awaiting_teleport);
|
TeleportResult::Success
|
||||||
} else {
|
} else {
|
||||||
drop(awaiting_teleport);
|
TeleportResult::WrongId
|
||||||
self.kick(TextComponent::text("Wrong teleport id")).await;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
drop(awaiting_teleport);
|
TeleportResult::NotTeleporting
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
TeleportResult::Success => {}
|
||||||
|
TeleportResult::WrongId => {
|
||||||
|
self.kick(TextComponent::text("Wrong teleport id")).await;
|
||||||
|
}
|
||||||
|
TeleportResult::NotTeleporting => {
|
||||||
self.kick(TextComponent::text(
|
self.kick(TextComponent::text(
|
||||||
"Send Teleport confirm, but we did not teleport",
|
"Send Teleport confirm, but we did not teleport",
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub async fn handle_jigsaw_generate(&self, player: &Arc<Player>, generate: SJigsawGenerate) {
|
pub fn handle_jigsaw_generate(&self, player: &Arc<Player>, generate: &SJigsawGenerate) {
|
||||||
if !player.is_creative() {
|
if !player.is_creative() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -13,9 +13,7 @@ impl JavaClient {
|
|||||||
if let Some(block_entity) = player.world().get_block_entity(&pos)
|
if let Some(block_entity) = player.world().get_block_entity(&pos)
|
||||||
&& let Some(jigsaw_block) = block_entity.as_any().downcast_ref::<JigsawBlockEntity>()
|
&& let Some(jigsaw_block) = block_entity.as_any().downcast_ref::<JigsawBlockEntity>()
|
||||||
{
|
{
|
||||||
jigsaw_block
|
jigsaw_block.generate(&player.world(), generate.levels.0, generate.keep_jigsaws);
|
||||||
.generate(&player.world(), generate.levels.0, generate.keep_jigsaws)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,10 +57,10 @@ use pumpkin_protocol::bedrock::client::CMovePlayer;
|
|||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, CPingResponse,
|
||||||
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetCamera, CSetSelectedSlot,
|
CPlayerInfoUpdate, CPlayerPosition, CSetCamera, CSetSelectedSlot, CSystemChatMessage,
|
||||||
CSystemChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat,
|
CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction,
|
||||||
PlayerAction, PlayerInfoFlags,
|
PlayerInfoFlags,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::java::server::play::{
|
use pumpkin_protocol::java::server::play::{
|
||||||
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected,
|
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected,
|
||||||
|
|||||||
@ -129,7 +129,10 @@ impl JavaClient {
|
|||||||
self.sync_block_state_to_client(&world, position).await;
|
self.sync_block_state_to_client(&world, position).await;
|
||||||
} else {
|
} else {
|
||||||
player.mining.store(true, Ordering::Relaxed);
|
player.mining.store(true, Ordering::Relaxed);
|
||||||
*player.mining_pos.lock().await = position;
|
*player
|
||||||
|
.mining_pos
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = position;
|
||||||
let progress = (speed * 10.0) as i32;
|
let progress = (speed * 10.0) as i32;
|
||||||
player
|
player
|
||||||
.current_block_breaking_speed
|
.current_block_breaking_speed
|
||||||
|
|||||||
@ -56,7 +56,12 @@ impl JavaClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
||||||
if player.awaiting_teleport.lock().await.is_some() {
|
if player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// y = feet Y
|
// y = feet Y
|
||||||
@ -185,7 +190,12 @@ impl JavaClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
||||||
if player.awaiting_teleport.lock().await.is_some() {
|
if player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// y = feet Y
|
// y = feet Y
|
||||||
@ -325,7 +335,11 @@ impl JavaClient {
|
|||||||
|
|
||||||
pub async fn force_tp(&self, player: &Arc<Player>, position: Vector3<f64>) {
|
pub async fn force_tp(&self, player: &Arc<Player>, position: Vector3<f64>) {
|
||||||
let teleport_id = player.teleport_id_count.fetch_add(1, Ordering::Relaxed) + 1;
|
let teleport_id = player.teleport_id_count.fetch_add(1, Ordering::Relaxed) + 1;
|
||||||
*player.awaiting_teleport.lock().await = Some((teleport_id.into(), position));
|
*player
|
||||||
|
.awaiting_teleport
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
||||||
|
Some((teleport_id.into(), position));
|
||||||
self.enqueue_client_packet(&CPlayerPosition::new(
|
self.enqueue_client_packet(&CPlayerPosition::new(
|
||||||
teleport_id.into(),
|
teleport_id.into(),
|
||||||
player.get_entity().pos.load(),
|
player.get_entity().pos.load(),
|
||||||
|
|||||||
@ -2,16 +2,15 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub async fn handle_seen_advancement(&self, player: &Arc<Player>, packet: SSeenAdvancement) {
|
pub fn handle_seen_advancement(&self, player: &Arc<Player>, packet: SSeenAdvancement) {
|
||||||
if let SSeenAdvancement::OpenTab(tab) = packet {
|
if let SSeenAdvancement::OpenTab(tab) = packet {
|
||||||
let advancement = Advancement::from_minecraft_name(&tab.to_string());
|
let advancement = Advancement::from_minecraft_name(&tab.to_string());
|
||||||
if advancement.is_some() {
|
if advancement.is_some() {
|
||||||
player
|
player
|
||||||
.advancements
|
.advancements
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.set_selected_tab(advancement)
|
.set_selected_tab(advancement);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub async fn handle_set_jigsaw_block(&self, player: &Arc<Player>, jigsaw: SSetJigsawBlock<'_>) {
|
pub fn handle_set_jigsaw_block(&self, player: &Arc<Player>, jigsaw: &SSetJigsawBlock<'_>) {
|
||||||
if !player.is_creative() {
|
if !player.is_creative() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -247,10 +247,4 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the block placed was a sign, then opens a dialog.
|
|
||||||
pub async fn send_sign_packet(&self, block_position: BlockPos, is_front_text: bool) {
|
|
||||||
self.enqueue_client_packet(&COpenSignEditor::new(block_position, is_front_text))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1450,7 +1450,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::text::TextComponent>>> {
|
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::text::TextComponent>>> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let tab_list_name = player.get_tab_list_name().await;
|
let tab_list_name = player.get_tab_list_name();
|
||||||
tab_list_name.map_or_else(
|
tab_list_name.map_or_else(
|
||||||
|| Ok(None),
|
|| Ok(None),
|
||||||
|name| {
|
|name| {
|
||||||
@ -1468,7 +1468,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let name = name.map(|n| text_component_from_resource(self, &n));
|
let name = name.map(|n| text_component_from_resource(self, &n));
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
player.set_tab_list_name(name).await;
|
player.set_tab_list_name(name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1879,7 +1879,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let other = player_from_resource(self, &other)?;
|
let other = player_from_resource(self, &other)?;
|
||||||
player.hide_player(other.gameprofile.id).await;
|
player.hide_player(other.gameprofile.id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1890,7 +1890,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let other = player_from_resource(self, &other)?;
|
let other = player_from_resource(self, &other)?;
|
||||||
player.show_player(other.gameprofile.id).await;
|
player.show_player(other.gameprofile.id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1901,7 +1901,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<bool> {
|
) -> wasmtime::Result<bool> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let other = player_from_resource(self, &other)?;
|
let other = player_from_resource(self, &other)?;
|
||||||
Ok(player.can_see(&other.gameprofile.id).await)
|
Ok(player.can_see(&other.gameprofile.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn can_see_player(
|
async fn can_see_player(
|
||||||
@ -1911,7 +1911,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<bool> {
|
) -> wasmtime::Result<bool> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let other = player_from_resource(self, &other)?;
|
let other = player_from_resource(self, &other)?;
|
||||||
Ok(player.can_see(&other.gameprofile.id).await)
|
Ok(player.can_see(&other.gameprofile.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_tab_list_ping(
|
async fn set_tab_list_ping(
|
||||||
@ -2625,7 +2625,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let guard = player.advancements.lock().await;
|
let guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let progress = guard.progress.map.get(advancement).map_or_else(
|
let progress = guard.progress.map.get(advancement).map_or_else(
|
||||||
|| pumpkin::plugin::advancement::AdvancementProgress {
|
|| pumpkin::plugin::advancement::AdvancementProgress {
|
||||||
advancement_id: advancement.id.to_string(),
|
advancement_id: advancement.id.to_string(),
|
||||||
@ -2667,7 +2670,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let mut guard = player.advancements.lock().await;
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let awarded = guard.award(advancement, &criterion);
|
let awarded = guard.award(advancement, &criterion);
|
||||||
if awarded {
|
if awarded {
|
||||||
guard.flush_dirty(&player, true);
|
guard.flush_dirty(&player, true);
|
||||||
@ -2689,7 +2695,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let mut guard = player.advancements.lock().await;
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let revoked = guard.revoke(advancement, &criterion);
|
let revoked = guard.revoke(advancement, &criterion);
|
||||||
if revoked {
|
if revoked {
|
||||||
guard.flush_dirty(&player, true);
|
guard.flush_dirty(&player, true);
|
||||||
@ -2710,7 +2719,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let mut guard = player.advancements.lock().await;
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let progress = guard.progress.get_mut_or_start_progress(advancement);
|
let progress = guard.progress.get_mut_or_start_progress(advancement);
|
||||||
if progress.is_done() {
|
if progress.is_done() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@ -2741,7 +2753,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let mut guard = player.advancements.lock().await;
|
let mut guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let progress = guard.progress.get_mut_or_start_progress(advancement);
|
let progress = guard.progress.get_mut_or_start_progress(advancement);
|
||||||
if !progress.has_progress() {
|
if !progress.has_progress() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@ -2772,7 +2787,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
else {
|
else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let guard = player.advancements.lock().await;
|
let guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let done = guard
|
let done = guard
|
||||||
.progress
|
.progress
|
||||||
.map
|
.map
|
||||||
@ -2786,7 +2804,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
) -> wasmtime::Result<Vec<String>> {
|
) -> wasmtime::Result<Vec<String>> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let guard = player.advancements.lock().await;
|
let guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let list = guard
|
let list = guard
|
||||||
.progress
|
.progress
|
||||||
.map
|
.map
|
||||||
@ -2802,7 +2823,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
) -> wasmtime::Result<Option<String>> {
|
) -> wasmtime::Result<Option<String>> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let guard = player.advancements.lock().await;
|
let guard = player
|
||||||
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
Ok(guard.last_selected_tab.map(|adv| adv.id.to_string()))
|
Ok(guard.last_selected_tab.map(|adv| adv.id.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2815,8 +2839,11 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
let target_adv = tab_id.as_deref().and_then(
|
let target_adv = tab_id.as_deref().and_then(
|
||||||
crate::plugin::loader::wasm::wasm_host::wit::v0_1::advancement::find_advancement,
|
crate::plugin::loader::wasm::wasm_host::wit::v0_1::advancement::find_advancement,
|
||||||
);
|
);
|
||||||
let mut guard = player.advancements.lock().await;
|
let mut guard = player
|
||||||
guard.set_selected_tab(target_adv).await;
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
guard.set_selected_tab(target_adv);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -315,9 +315,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
.server
|
.server
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||||
server
|
server.broadcast_tab_list_header_footer(&header, &footer);
|
||||||
.broadcast_tab_list_header_footer(&header, &footer)
|
|
||||||
.await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -705,8 +705,11 @@ impl Server {
|
|||||||
// Wrap in Arc after data is loaded
|
// Wrap in Arc after data is loaded
|
||||||
let player = Arc::new(player);
|
let player = Arc::new(player);
|
||||||
{
|
{
|
||||||
let mut advancements = player.advancements.lock().await;
|
let mut advancements = player
|
||||||
if let Err(e) = advancements.load().await {
|
.advancements
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
if let Err(e) = advancements.load() {
|
||||||
warn!("Error loading player {}: {e}", player.gameprofile.id);
|
warn!("Error loading player {}: {e}", player.gameprofile.id);
|
||||||
}
|
}
|
||||||
advancements.player = Arc::downgrade(&player);
|
advancements.player = Arc::downgrade(&player);
|
||||||
@ -795,16 +798,18 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn broadcast_tab_list_header_footer(
|
pub fn broadcast_tab_list_header_footer(&self, header: &TextComponent, footer: &TextComponent) {
|
||||||
&self,
|
|
||||||
header: &TextComponent,
|
|
||||||
footer: &TextComponent,
|
|
||||||
) {
|
|
||||||
let packet = CTabList::new(header, footer);
|
let packet = CTabList::new(header, footer);
|
||||||
for world in self.worlds.load().iter() {
|
for world in self.worlds.load().iter() {
|
||||||
for player in world.players.load().iter() {
|
for player in world.players.load().iter() {
|
||||||
*player.tab_list_header.lock().await = header.clone();
|
*player
|
||||||
*player.tab_list_footer.lock().await = footer.clone();
|
.tab_list_header
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = header.clone();
|
||||||
|
*player
|
||||||
|
.tab_list_footer
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = footer.clone();
|
||||||
}
|
}
|
||||||
world.broadcast_packet_all(&packet);
|
world.broadcast_packet_all(&packet);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,10 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
// Use the chunk_manager's world reference, which is updated on dimension change.
|
// Use the chunk_manager's world reference, which is updated on dimension change.
|
||||||
// This ensures we load chunks from the correct world after portal teleportation.
|
// This ensures we load chunks from the correct world after portal teleportation.
|
||||||
let world = {
|
let world = {
|
||||||
let mut chunk_manager = player.chunk_manager.lock().await;
|
let mut chunk_manager = player
|
||||||
|
.chunk_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let world = chunk_manager.world().clone();
|
let world = chunk_manager.world().clone();
|
||||||
chunk_manager.update_center_and_view_distance(
|
chunk_manager.update_center_and_view_distance(
|
||||||
new_chunk_center,
|
new_chunk_center,
|
||||||
|
|||||||
@ -2,8 +2,7 @@ use crate::entity::player::Player;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use pumpkin_data::dimension::Dimension;
|
use pumpkin_data::dimension::Dimension;
|
||||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
pub struct MapManager {
|
pub struct MapManager {
|
||||||
pub maps: DashMap<i32, Arc<Mutex<MapData>>>,
|
pub maps: DashMap<i32, Arc<Mutex<MapData>>>,
|
||||||
|
|||||||
@ -481,7 +481,7 @@ impl World {
|
|||||||
.map(|chunk_block_entities| *chunk_block_entities.key())
|
.map(|chunk_block_entities| *chunk_block_entities.key())
|
||||||
.collect();
|
.collect();
|
||||||
for chunk_pos in chunks {
|
for chunk_pos in chunks {
|
||||||
self.save_block_entities(&chunk_pos);
|
self.save_block_entities(chunk_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save portal POI to disk
|
// Save portal POI to disk
|
||||||
@ -520,10 +520,10 @@ impl World {
|
|||||||
/// `get_block_entity` takes the saved NBT out of the chunk when it wakes an
|
/// `get_block_entity` takes the saved NBT out of the chunk when it wakes an
|
||||||
/// entity up - so this has to run before the chunk is dropped, or everything
|
/// entity up - so this has to run before the chunk is dropped, or everything
|
||||||
/// the entity did since it was loaded is lost.
|
/// the entity did since it was loaded is lost.
|
||||||
fn save_block_entities(&self, chunk_pos: &Vector2<i32>) {
|
fn save_block_entities(&self, chunk_pos: Vector2<i32>) {
|
||||||
let Some(block_entities) = self
|
let Some(block_entities) = self
|
||||||
.block_entities
|
.block_entities
|
||||||
.get(chunk_pos)
|
.get(&chunk_pos)
|
||||||
.map(|chunk_block_entities| chunk_block_entities.values().cloned().collect::<Vec<_>>())
|
.map(|chunk_block_entities| chunk_block_entities.values().cloned().collect::<Vec<_>>())
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
@ -851,14 +851,25 @@ impl World {
|
|||||||
chat_message: &SChatMessage<'_>,
|
chat_message: &SChatMessage<'_>,
|
||||||
decorated_message: &TextComponent,
|
decorated_message: &TextComponent,
|
||||||
) {
|
) {
|
||||||
let messages_sent: i32 = sender.chat_session.lock().await.messages_sent;
|
let messages_sent: i32 = sender
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.messages_sent;
|
||||||
let sender_last_seen = {
|
let sender_last_seen = {
|
||||||
let cache = sender.signature_cache.lock().await;
|
let cache = sender
|
||||||
|
.signature_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
cache.last_seen.clone()
|
cache.last_seen.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
for recipient in self.players.load().iter() {
|
for recipient in self.players.load().iter() {
|
||||||
let messages_received: i32 = recipient.chat_session.lock().await.messages_received;
|
let messages_received: i32 = recipient
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.messages_received;
|
||||||
let packet = &CPlayerChatMessage::new(
|
let packet = &CPlayerChatMessage::new(
|
||||||
VarInt(messages_received),
|
VarInt(messages_received),
|
||||||
sender.gameprofile.id,
|
sender.gameprofile.id,
|
||||||
@ -867,7 +878,7 @@ impl World {
|
|||||||
chat_message.message.into(),
|
chat_message.message.into(),
|
||||||
chat_message.timestamp,
|
chat_message.timestamp,
|
||||||
chat_message.salt,
|
chat_message.salt,
|
||||||
sender_last_seen.indexed_for(recipient).await,
|
sender_last_seen.indexed_for(recipient),
|
||||||
Some(decorated_message.clone()),
|
Some(decorated_message.clone()),
|
||||||
FilterType::PassThrough,
|
FilterType::PassThrough,
|
||||||
(RAW + 1).into(), // Custom registry chat_type with no sender name
|
(RAW + 1).into(), // Custom registry chat_type with no sender name
|
||||||
@ -888,7 +899,10 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(signature) = chat_message.signature {
|
if let Some(signature) = chat_message.signature {
|
||||||
let mut cache = recipient.signature_cache.lock().await;
|
let mut cache = recipient
|
||||||
|
.signature_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
cache.add_seen_signature(signature);
|
cache.add_seen_signature(signature);
|
||||||
cache.last_seen_validator.add_pending(signature);
|
cache.last_seen_validator.add_pending(signature);
|
||||||
let tracked_count = cache.last_seen_validator.tracked_messages_count();
|
let tracked_count = cache.last_seen_validator.tracked_messages_count();
|
||||||
@ -911,13 +925,21 @@ impl World {
|
|||||||
recipient
|
recipient
|
||||||
.signature_cache
|
.signature_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.cache_signatures(sender_last_seen.as_ref());
|
.cache_signatures(sender_last_seen.as_ref());
|
||||||
}
|
}
|
||||||
recipient.chat_session.lock().await.messages_received += 1;
|
recipient
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.messages_received += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.chat_session.lock().await.messages_sent += 1;
|
sender
|
||||||
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.messages_sent += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn broadcast_packet_except_editioned<J: ClientPacket, B: BClientPacket>(
|
pub fn broadcast_packet_except_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
@ -1739,7 +1761,7 @@ impl World {
|
|||||||
spawning_chunks.shuffle(&mut rng());
|
spawning_chunks.shuffle(&mut rng());
|
||||||
|
|
||||||
let world = self.clone();
|
let world = self.clone();
|
||||||
let spawn_handle = handle.clone();
|
let spawn_handle = handle;
|
||||||
spawning_chunks.par_chunks(8).for_each(|batch| {
|
spawning_chunks.par_chunks(8).for_each(|batch| {
|
||||||
let _guard = spawn_handle.enter();
|
let _guard = spawn_handle.enter();
|
||||||
let world = world.clone();
|
let world = world.clone();
|
||||||
@ -3249,7 +3271,7 @@ impl World {
|
|||||||
self.broadcast_editioned(&player_info_update, &bedrock_player_list);
|
self.broadcast_editioned(&player_info_update, &bedrock_player_list);
|
||||||
|
|
||||||
// If the player has a custom tab_list_name, send an update for it
|
// If the player has a custom tab_list_name, send an update for it
|
||||||
if let Some(tab_list_name) = player.get_tab_list_name().await {
|
if let Some(tab_list_name) = player.get_tab_list_name() {
|
||||||
let actions = [PlayerAction::UpdateDisplayName(Some(&tab_list_name))];
|
let actions = [PlayerAction::UpdateDisplayName(Some(&tab_list_name))];
|
||||||
let java_player = [pumpkin_protocol::java::client::play::Player {
|
let java_player = [pumpkin_protocol::java::client::play::Player {
|
||||||
uuid: gameprofile.id,
|
uuid: gameprofile.id,
|
||||||
@ -3276,8 +3298,11 @@ impl World {
|
|||||||
|
|
||||||
let mut current_player_data = Vec::new();
|
let mut current_player_data = Vec::new();
|
||||||
for (properties, player) in &data_to_process {
|
for (properties, player) in &data_to_process {
|
||||||
let chat_session = player.chat_session.lock().await;
|
let chat_session = player
|
||||||
let tab_list_name = player.get_tab_list_name().await;
|
.chat_session
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
let tab_list_name = player.get_tab_list_name();
|
||||||
|
|
||||||
let mut player_actions = vec![PlayerAction::AddPlayer {
|
let mut player_actions = vec![PlayerAction::AddPlayer {
|
||||||
name: &player.gameprofile.name,
|
name: &player.gameprofile.name,
|
||||||
@ -4088,7 +4113,7 @@ impl World {
|
|||||||
player
|
player
|
||||||
.chunk_manager
|
.chunk_manager
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.change_world(&self.level, destination.clone());
|
.change_world(&self.level, destination.clone());
|
||||||
player.living_entity.entity.set_world(destination.clone());
|
player.living_entity.entity.set_world(destination.clone());
|
||||||
destination.players.rcu(|current_list| {
|
destination.players.rcu(|current_list| {
|
||||||
@ -4934,7 +4959,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for chunk_pos in &chunks_set {
|
for chunk_pos in &chunks_set {
|
||||||
self.save_block_entities(chunk_pos);
|
self.save_block_entities(*chunk_pos);
|
||||||
self.block_entities.remove(chunk_pos);
|
self.block_entities.remove(chunk_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6427,7 +6452,7 @@ impl World {
|
|||||||
.map(|chunk_block_entities| *chunk_block_entities.key())
|
.map(|chunk_block_entities| *chunk_block_entities.key())
|
||||||
.collect();
|
.collect();
|
||||||
for chunk_pos in chunks {
|
for chunk_pos in chunks {
|
||||||
self.save_block_entities(&chunk_pos);
|
self.save_block_entities(chunk_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut portal_poi) = self.portal_poi.try_lock() {
|
if let Ok(mut portal_poi) = self.portal_poi.try_lock() {
|
||||||
@ -6793,11 +6818,6 @@ impl WorldPortalExt for WorldPortal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_structure_entities(&self, entities: Vec<NbtCompound>) {
|
fn spawn_structure_entities(&self, entities: Vec<NbtCompound>) {
|
||||||
let world = self.0.clone();
|
|
||||||
let Some(server) = world.server.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
server.spawn_task(async move {
|
|
||||||
for nbt in entities {
|
for nbt in entities {
|
||||||
let Some(id) = nbt.get_string("id") else {
|
let Some(id) = nbt.get_string("id") else {
|
||||||
continue;
|
continue;
|
||||||
@ -6811,14 +6831,13 @@ impl WorldPortalExt for WorldPortal {
|
|||||||
let entity = from_type(
|
let entity = from_type(
|
||||||
entity_type,
|
entity_type,
|
||||||
Vector3::new(0.0, 0.0, 0.0),
|
Vector3::new(0.0, 0.0, 0.0),
|
||||||
&world,
|
&self.0,
|
||||||
Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
);
|
);
|
||||||
entity.get_entity().read_nbt_non_mut(&nbt);
|
entity.get_entity().read_nbt_non_mut(&nbt);
|
||||||
entity.read_nbt_non_mut(&nbt);
|
entity.read_nbt_non_mut(&nbt);
|
||||||
world.spawn_entity(entity);
|
self.0.spawn_entity(entity);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user