diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/mod.rs b/crates/pumpkin-world/src/generation/feature/features/tree/mod.rs index d148a8961..29cea523e 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/mod.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/mod.rs @@ -1,6 +1,8 @@ use decorator::TreeDecorator; use foliage::FoliagePlacer; use pumpkin_data::BlockState; +use pumpkin_data::block_properties::{BlockProperties, OakLeavesLikeProperties}; +use pumpkin_data::tag::Taggable; use pumpkin_data::{BlockId, tag}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use root::RootPlacer; @@ -56,6 +58,10 @@ impl TreeFeature { pos, ); + if log_positions.is_empty() && foliage_positions.is_empty() { + return false; + } + for decorator in &self.decorators { decorator.generate( chunk, @@ -66,9 +72,127 @@ impl TreeFeature { &foliage_positions, ); } + + Self::update_leaves(chunk, &log_positions, &root_positions, &foliage_positions); + true } + pub fn update_leaves( + 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 { + 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; 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 { Self::can_replace(state, id) || id.has_tag(tag::Block::MINECRAFT_LOGS) } diff --git a/crates/pumpkin/src/block/blocks/brushable_block.rs b/crates/pumpkin/src/block/blocks/brushable_block.rs index 748ea3ac6..38f9be609 100644 --- a/crates/pumpkin/src/block/blocks/brushable_block.rs +++ b/crates/pumpkin/src/block/blocks/brushable_block.rs @@ -17,7 +17,7 @@ impl BlockMetadata for BrushableBlock { } impl BrushableBlock { - pub async fn brush( + pub fn brush( world: &Arc, pos: &pumpkin_util::math::position::BlockPos, block: &Block, diff --git a/crates/pumpkin/src/block/blocks/leaves.rs b/crates/pumpkin/src/block/blocks/leaves.rs index cbedce1e1..0dca67cef 100644 --- a/crates/pumpkin/src/block/blocks/leaves.rs +++ b/crates/pumpkin/src/block/blocks/leaves.rs @@ -65,10 +65,19 @@ impl BlockBehaviour for LeavesBlock { &self, args: GetStateForNeighborUpdateArgs<'_>, ) -> 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 distance_from_neighbor = 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 { args.world diff --git a/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs b/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs index 236782ba4..2fd295c00 100644 --- a/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs +++ b/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs @@ -27,7 +27,7 @@ impl BlockMetadata for SculkShriekerBlock { } impl SculkShriekerBlock { - pub async fn try_activate(world: &Arc, pos: &BlockPos) -> bool { + pub fn try_activate(world: &Arc, pos: &BlockPos) -> bool { let block = world.get_block(pos); if block.id != BlockId::SCULK_SHRIEKER { return false; diff --git a/crates/pumpkin/src/block/blocks/signs.rs b/crates/pumpkin/src/block/blocks/signs.rs index 0deb7ce0d..32acba43a 100644 --- a/crates/pumpkin/src/block/blocks/signs.rs +++ b/crates/pumpkin/src/block/blocks/signs.rs @@ -32,8 +32,8 @@ use crate::item::items::dye::DyeItem; use crate::item::items::glowing_ink_sac::GlowingInkSacItem; use crate::item::items::honeycomb::HoneyCombItem; use crate::item::items::ink_sac::InkSacItem; -use crate::net::ClientPlatform; use crate::world::World; +use pumpkin_protocol::java::client::play::COpenSignEditor; #[pumpkin_block_from_tag("minecraft:all_signs")] pub struct SignBlock; @@ -315,13 +315,8 @@ impl BlockBehaviour for SignBlock { } fn player_placed(&self, args: PlayerPlacedArgs<'_>) { - let client = args.player.client.clone(); - let pos = *args.position; - tokio::spawn(async move { - if let crate::net::ClientPlatform::Java(java) = client.as_ref() { - java.send_sign_packet(pos, true).await; - } - }); + args.player + .try_send_client_packet(&COpenSignEditor::new(*args.position, true)); } fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { @@ -454,13 +449,8 @@ impl BlockBehaviour for SignBlock { let is_facing_front_text = is_facing_front_text(args.world, args.position, args.block, args.player); - let client = args.player.client.clone(); - let pos = *args.position; - tokio::spawn(async move { - if let ClientPlatform::Java(java) = client.as_ref() { - java.send_sign_packet(pos, is_facing_front_text).await; - } - }); + args.player + .try_send_client_packet(&COpenSignEditor::new(*args.position, is_facing_front_text)); BlockActionResult::SuccessServer } diff --git a/crates/pumpkin/src/block/entities/calibrated_sculk_sensor.rs b/crates/pumpkin/src/block/entities/calibrated_sculk_sensor.rs index e41801652..5e225531a 100644 --- a/crates/pumpkin/src/block/entities/calibrated_sculk_sensor.rs +++ b/crates/pumpkin/src/block/entities/calibrated_sculk_sensor.rs @@ -51,7 +51,7 @@ impl BlockEntity for CalibratedSculkSensorBlockEntity { impl CalibratedSculkSensorBlockEntity { pub const ID: &'static str = "minecraft:calibrated_sculk_sensor"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, last_vibration_frequency: Mutex::new(0), diff --git a/crates/pumpkin/src/block/entities/conduit.rs b/crates/pumpkin/src/block/entities/conduit.rs index fe9a4a1a7..8b073834a 100644 --- a/crates/pumpkin/src/block/entities/conduit.rs +++ b/crates/pumpkin/src/block/entities/conduit.rs @@ -62,7 +62,7 @@ impl BlockEntity for ConduitBlockEntity { impl ConduitBlockEntity { pub const ID: &'static str = "minecraft:conduit"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, active: Mutex::new(false), diff --git a/crates/pumpkin/src/block/entities/end_gateway.rs b/crates/pumpkin/src/block/entities/end_gateway.rs index 4ad875ce1..79ef68814 100644 --- a/crates/pumpkin/src/block/entities/end_gateway.rs +++ b/crates/pumpkin/src/block/entities/end_gateway.rs @@ -82,7 +82,7 @@ impl BlockEntity for EndGatewayBlockEntity { impl EndGatewayBlockEntity { pub const ID: &'static str = "minecraft:end_gateway"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, age: Mutex::new(0), diff --git a/crates/pumpkin/src/block/entities/hopper.rs b/crates/pumpkin/src/block/entities/hopper.rs index 56f6f2934..1da4c45af 100644 --- a/crates/pumpkin/src/block/entities/hopper.rs +++ b/crates/pumpkin/src/block/entities/hopper.rs @@ -177,6 +177,7 @@ impl HopperBlockEntity { true } + #[allow(clippy::too_many_lines)] fn suck_in_items(&self, world: &Arc) -> bool { // TODO getEntityContainer let pos_up = &self.position.up(); diff --git a/crates/pumpkin/src/block/entities/jigsaw_block.rs b/crates/pumpkin/src/block/entities/jigsaw_block.rs index 054d7cc2a..bad49a25d 100644 --- a/crates/pumpkin/src/block/entities/jigsaw_block.rs +++ b/crates/pumpkin/src/block/entities/jigsaw_block.rs @@ -74,7 +74,7 @@ impl JigsawBlockEntity { } } - pub async fn generate(&self, world: &Arc, levels: i32, keep_jigsaws: bool) { + pub fn generate(&self, world: &Arc, levels: i32, keep_jigsaws: bool) { let pool = self .pool .lock() diff --git a/crates/pumpkin/src/block/entities/sculk_catalyst.rs b/crates/pumpkin/src/block/entities/sculk_catalyst.rs index fb5357128..305813683 100644 --- a/crates/pumpkin/src/block/entities/sculk_catalyst.rs +++ b/crates/pumpkin/src/block/entities/sculk_catalyst.rs @@ -48,7 +48,7 @@ impl BlockEntity for SculkCatalystBlockEntity { impl SculkCatalystBlockEntity { pub const ID: &'static str = "minecraft:sculk_catalyst"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, decay_delay: Mutex::new(0), diff --git a/crates/pumpkin/src/block/entities/sculk_sensor.rs b/crates/pumpkin/src/block/entities/sculk_sensor.rs index 6e7f54035..bc253b1f5 100644 --- a/crates/pumpkin/src/block/entities/sculk_sensor.rs +++ b/crates/pumpkin/src/block/entities/sculk_sensor.rs @@ -51,7 +51,7 @@ impl BlockEntity for SculkSensorBlockEntity { impl SculkSensorBlockEntity { pub const ID: &'static str = "minecraft:sculk_sensor"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, last_vibration_frequency: Mutex::new(0), diff --git a/crates/pumpkin/src/block/entities/sculk_shrieker.rs b/crates/pumpkin/src/block/entities/sculk_shrieker.rs index 1f7edbe3d..cae510028 100644 --- a/crates/pumpkin/src/block/entities/sculk_shrieker.rs +++ b/crates/pumpkin/src/block/entities/sculk_shrieker.rs @@ -48,7 +48,7 @@ impl BlockEntity for SculkShriekerBlockEntity { impl SculkShriekerBlockEntity { pub const ID: &'static str = "minecraft:sculk_shrieker"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, warning_level: Mutex::new(0), diff --git a/crates/pumpkin/src/block/entities/trial_spawner.rs b/crates/pumpkin/src/block/entities/trial_spawner.rs index 499be1a68..fa60fdde8 100644 --- a/crates/pumpkin/src/block/entities/trial_spawner.rs +++ b/crates/pumpkin/src/block/entities/trial_spawner.rs @@ -77,7 +77,7 @@ impl BlockEntity for TrialSpawnerBlockEntity { impl TrialSpawnerBlockEntity { pub const ID: &'static str = "minecraft:trial_spawner"; #[must_use] - pub fn new(position: BlockPos) -> Self { + pub const fn new(position: BlockPos) -> Self { Self { position, normal_config: Mutex::new(None), diff --git a/crates/pumpkin/src/command/commands/advancement.rs b/crates/pumpkin/src/command/commands/advancement.rs index 303e35b60..7f496d25c 100644 --- a/crates/pumpkin/src/command/commands/advancement.rs +++ b/crates/pumpkin/src/command/commands/advancement.rs @@ -146,7 +146,10 @@ impl Action { advancements: &[&'static Advancement], show_advancement: bool, ) -> 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 { guard.flush_dirty(player, true); } @@ -166,7 +169,10 @@ impl Action { advancement: &'static Advancement, criterion: &str, ) -> bool { - let mut guard = player.advancements.blocking_lock(); + let mut guard = player + .advancements + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); match self { Self::Grant => guard.award(advancement, criterion), Self::Revoke => guard.revoke(advancement, criterion), diff --git a/crates/pumpkin/src/data/advancement_data.rs b/crates/pumpkin/src/data/advancement_data.rs index c51b37c75..a01e63a87 100644 --- a/crates/pumpkin/src/data/advancement_data.rs +++ b/crates/pumpkin/src/data/advancement_data.rs @@ -57,7 +57,10 @@ impl AdvancementManager { } let mut to_write = Vec::with_capacity(players.len()); 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)?; to_write.push((guard.path.clone(), json)); } diff --git a/crates/pumpkin/src/entity/living.rs b/crates/pumpkin/src/entity/living.rs index e0b5f8e33..0185ea366 100644 --- a/crates/pumpkin/src/entity/living.rs +++ b/crates/pumpkin/src/entity/living.rs @@ -2041,7 +2041,11 @@ impl LivingEntity { self.last_damage_taken.store(0f32); 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 self.fall_distance.store(0f32); diff --git a/crates/pumpkin/src/entity/mod.rs b/crates/pumpkin/src/entity/mod.rs index 2d61ba9d9..218a9a7ef 100644 --- a/crates/pumpkin/src/entity/mod.rs +++ b/crates/pumpkin/src/entity/mod.rs @@ -79,7 +79,6 @@ use std::sync::{ Ordering::{self, Relaxed}, }, }; -use tokio::sync::Mutex; use uuid::Uuid; pub mod ageable; @@ -846,7 +845,7 @@ pub struct Entity { pub portal_cooldown: AtomicU32, - pub portal_manager: Mutex>>, + pub portal_manager: std::sync::Mutex>, /// Custom name for the entity pub custom_name: ArcSwap>, /// Indicates whether the entity's custom name is visible @@ -994,7 +993,7 @@ impl Entity { current_biome: ArcSwap::new(Arc::new(current_biome)), last_biome_update_pos: AtomicCell::new(BlockPos::new(floor_x, floor_y, floor_z)), 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_visible: AtomicBool::new(false), silent: AtomicBool::new(false), @@ -2331,10 +2330,7 @@ impl Entity { return; }; let mut should_remove = false; - if let Some(pmanager_mutex) = manager_guard.as_ref() { - let Ok(mut portal_processor) = pmanager_mutex.try_lock() else { - return; - }; + if let Some(portal_processor) = manager_guard.as_mut() { if portal_processor.process_portal_teleportation( &self.world.load(), caller.as_ref(), @@ -2495,7 +2491,10 @@ impl Entity { 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(); if manager.is_none() { let portal_type = if portal_world.dimension == Dimension::THE_END @@ -2534,9 +2533,8 @@ impl Entity { }); } - *manager = Some(Mutex::new(new_manager)); - } else if let Some(manager) = manager.as_ref() { - let mut manager = manager.blocking_lock(); + *manager = Some(new_manager); + } else if let Some(manager) = manager.as_mut() { manager.entry_position = pos; manager.inside_portal_this_tick = true; } @@ -3561,7 +3559,11 @@ impl Entity { // Use fallback position as placeholder — updated below with real position let placeholder = 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) } else { None @@ -3774,7 +3776,11 @@ impl Entity { if let Some(id) = teleport_id { player.get_entity().set_pos(dismount_pos); // 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 // the same packet queue as CSetPassengers, preserving send order. // Vanilla uses DELTA | ROT flags: position absolute, delta/rotation relative. diff --git a/crates/pumpkin/src/entity/passive/villager/mod.rs b/crates/pumpkin/src/entity/passive/villager/mod.rs index df87c9853..626cb8c68 100644 --- a/crates/pumpkin/src/entity/passive/villager/mod.rs +++ b/crates/pumpkin/src/entity/passive/villager/mod.rs @@ -610,7 +610,8 @@ impl VillagerEntity { target.0.z, 2, ); - map.blocking_lock() + map.lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) .decorations .push(crate::world::map::MapDecoration { icon_type, @@ -811,7 +812,7 @@ impl VillagerEntity { < range * range } - async fn complete_trade(&self, offer_index: usize, world: &Arc, player_uuid: Uuid) { + fn complete_trade(&self, offer_index: usize, world: &Arc, player_uuid: Uuid) { let (xp_gain, reward_exp) = { let mut offers = self .offers @@ -855,11 +856,11 @@ impl VillagerEntity { .or_default(); *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) { + fn resend_offers_to_player(&self, player: &Arc) { let trading_player = *self .trading_player .lock() @@ -904,11 +905,10 @@ impl VillagerEntity { if !ok { return; } - self.send_trade_offers(player, sync_id, offers, villager_data) - .await; + self.send_trade_offers(player, sync_id, &offers, villager_data); } - async fn resend_offers_to_trading_player(&self) { + fn resend_offers_to_trading_player(&self) { let trading_player = *self .trading_player .lock() @@ -954,8 +954,7 @@ impl VillagerEntity { if !ok { return; } - self.send_trade_offers(&player, sync_id, offers, villager_data) - .await; + self.send_trade_offers(&player, sync_id, &offers, villager_data); } fn decay_gossips(&self, game_time: i64) { @@ -997,9 +996,7 @@ impl VillagerEntity { .as_ref() .and_then(Weak::upgrade) { - tokio::spawn(async move { - villager.resend_offers_to_trading_player().await; - }); + villager.resend_offers_to_trading_player(); } } @@ -1298,7 +1295,7 @@ impl VillagerEntity { entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo); } - pub async fn open_trading_screen(&self, player: &Arc) { + pub fn open_trading_screen(&self, player: &Arc) { // Open the merchant screen and then send the current offers packet if let Some(sync_id) = player.open_handled_screen(self, None) { let offers = self @@ -1310,8 +1307,7 @@ impl VillagerEntity { .villager_data .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); - self.send_trade_offers(player, sync_id, offers, villager_data) - .await; + self.send_trade_offers(player, sync_id, &offers, villager_data); } } @@ -1421,18 +1417,18 @@ impl VillagerEntity { data } - async fn send_trade_offers( + fn send_trade_offers( &self, player: &Player, sync_id: u8, - offers: Vec, + offers: &[pumpkin_protocol::java::client::play::MerchantOffer], villager_data: VillagerData, ) { use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong}; let java = CMerchantOffers::new( VarInt(i32::from(sync_id)), - offers.clone(), + offers.to_owned(), villager_data.level, VarInt(self.xp.load(Ordering::Relaxed)), true, @@ -1448,12 +1444,9 @@ impl VillagerEntity { display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(), use_new_trade_screen: 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 - .client - .enqueue_packet_editioned(&java, &bedrock) - .await; + player.client.try_enqueue_packet_editioned(&java, &bedrock); } } @@ -1531,12 +1524,7 @@ impl ScreenHandlerFactory for VillagerEntity { handler.on_trade = Some(Box::new(move |offer_index| { if let Some(villager) = self_weak.upgrade() { - let world = world.clone(); - tokio::spawn(async move { - villager - .complete_trade(offer_index, &world, player_uuid) - .await; - }); + villager.complete_trade(offer_index, &world, player_uuid); } })); @@ -2357,10 +2345,7 @@ impl Mob for VillagerEntity { .as_ref() .and_then(Weak::upgrade); if let Some(villager) = villager { - let player = player.clone(); - tokio::spawn(async move { - villager.open_trading_screen(&player).await; - }); + villager.open_trading_screen(player); } true diff --git a/crates/pumpkin/src/entity/passive/wandering_trader.rs b/crates/pumpkin/src/entity/passive/wandering_trader.rs index 2dd468fb2..a9c6bb323 100644 --- a/crates/pumpkin/src/entity/passive/wandering_trader.rs +++ b/crates/pumpkin/src/entity/passive/wandering_trader.rs @@ -297,14 +297,14 @@ impl WanderingTraderEntity { add_offers_from_trade_set(&mut offers, TRADES_WANDERING_TRADER_COMMON, 5, &mut rng); } - pub async fn open_trading_screen(&self, player: &Arc) { + pub fn open_trading_screen(&self, player: &Arc) { if let Some(sync_id) = player.open_handled_screen(self, None) { let offers = self .offers .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .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 } - async fn send_trade_offers( + fn send_trade_offers( &self, player: &Player, sync_id: u8, - offers: Vec, + offers: &[pumpkin_protocol::java::client::play::MerchantOffer], ) { use pumpkin_protocol::{bedrock::client::CUpdateTrade, codec::var_long::VarLong}; let java = CMerchantOffers::new( VarInt(i32::from(sync_id)), - offers.clone(), + offers.to_owned(), VarInt(1), VarInt(0), false, @@ -421,12 +421,9 @@ impl WanderingTraderEntity { display_name: ScreenHandlerFactory::get_display_name(self).to_pretty_console(), use_new_trade_screen: true, using_economy_trade: true, - data: Self::bedrock_trade_data(&offers), + data: Self::bedrock_trade_data(offers), }; - player - .client - .enqueue_packet_editioned(&java, &bedrock) - .await; + player.client.try_enqueue_packet_editioned(&java, &bedrock); } fn can_continue_trading( @@ -782,10 +779,7 @@ impl Mob for WanderingTraderEntity { .as_ref() .and_then(Weak::upgrade); if let Some(trader) = trader { - let player = player.clone(); - tokio::spawn(async move { - trader.open_trading_screen(&player).await; - }); + trader.open_trading_screen(player); } true } diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 7f91d25ea..886ad1950 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -7,7 +7,7 @@ use std::f64::consts::TAU; use std::num::NonZero; use std::str::FromStr; 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 crate::plugin::api::events::enchantment::{EnchantItemEvent, PrepareItemEnchantEvent}; @@ -41,7 +41,6 @@ use pumpkin_util::translation::Locale; use pumpkin_util::version::JavaMinecraftVersion; use pumpkin_world::chunk::{ChunkData, ChunkEntityData}; use pumpkin_world::inventory::Inventory; -use tokio::sync::Mutex; use tokio::task::JoinHandle; use tracing::{debug, warn}; use uuid::Uuid; @@ -1136,8 +1135,14 @@ impl Player { } pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) { - *self.tab_list_header.lock().await = header.clone(); - *self.tab_list_footer.lock().await = footer.clone(); + *self + .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)) .await; } @@ -1204,18 +1209,25 @@ impl Player { )); } - pub async fn get_tab_list_name(&self) -> Option { - self.tab_list_name.lock().await.clone() + pub fn get_tab_list_name(&self) -> Option { + self.tab_list_name + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() } - pub async fn set_tab_list_name(&self, name: Option) { - *self.tab_list_name.lock().await = name.clone(); + pub fn set_tab_list_name(&self, name: Option) { + let mut guard = self + .tab_list_name + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + *guard = name; let world = self.world(); world.broadcast_packet_all(&CPlayerInfoUpdate::new( PlayerInfoFlags::UPDATE_DISPLAY_NAME.bits(), &[pumpkin_protocol::java::client::play::Player { 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; 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. // Given enough time, all of these chunks will be in memory. @@ -2551,12 +2566,15 @@ impl Player { let world_clone = p.world(); let server_clone = world_clone.server.upgrade(); 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 state = world.get_block_state(&pos); // Is the block broken? if state.is_air() { - p.stop_mining().await; + p.stop_mining(); } else { let finished = p.continue_mining( pos, @@ -2565,7 +2583,7 @@ impl Player { p.start_mining_time.load(Ordering::Relaxed), ); 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 can_harvest = p.can_harvest(state, block); @@ -2677,14 +2695,17 @@ impl Player { 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 stage = self.current_block_destroy_stage.swap(-1, Ordering::Relaxed); self.current_block_breaking_speed .store(0, Ordering::Relaxed); 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.living_entity.entity, pos, @@ -3355,20 +3376,30 @@ impl Player { self.respawn_location.load() } - pub async fn hide_player(&self, other_id: uuid::Uuid) { - self.hidden_players.lock().await.insert(other_id); + pub fn hide_player(&self, other_id: uuid::Uuid) { + 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) { - self.hidden_players.lock().await.remove(&other_id); + pub fn show_player(&self, other_id: uuid::Uuid) { + 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 { - !self.hidden_players.lock().await.contains(other_id) + pub fn can_see(&self, other_id: &uuid::Uuid) -> bool { + !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 { - self.can_see(other_id).await + pub fn can_see_player(&self, other_id: &uuid::Uuid) -> bool { + self.can_see(other_id) } // --- Experience & Leveling API --- @@ -3537,7 +3568,10 @@ impl Player { }); 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()); if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER { @@ -3651,7 +3685,10 @@ impl Player { entity.set_rotation(yaw, pitch); match self.client.as_ref() { 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)); let packet = CPlayerPosition::new( teleport_id.into(), @@ -6747,16 +6784,14 @@ impl AsRef<[Box<[u8]>]> for LastSeen { impl LastSeen { /// 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) - pub async fn indexed_for(&self, recipient: &Arc) -> Box<[PreviousMessage]> { + pub fn indexed_for(&self, recipient: &Arc) -> Box<[PreviousMessage]> { let mut indexed = Vec::new(); + let cache = recipient + .signature_cache + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); for signature in &self.0 { - let index = recipient - .signature_cache - .lock() - .await - .full_cache - .iter() - .position(|s| s == signature); + let index = cache.full_cache.iter().position(|s| s == signature); if let Some(index) = index { indexed.push(PreviousMessage { // Send ID reference to recipient's cache (index + 1 because 0 is reserved for full signature) diff --git a/crates/pumpkin/src/entity/player/advancement.rs b/crates/pumpkin/src/entity/player/advancement.rs index f9d80985d..0d376e0fb 100644 --- a/crates/pumpkin/src/entity/player/advancement.rs +++ b/crates/pumpkin/src/entity/player/advancement.rs @@ -254,7 +254,7 @@ impl PlayerAdvancement { } ///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.progress.clear(); self.visible.clear(); @@ -262,7 +262,7 @@ impl PlayerAdvancement { self.progress_changed.clear(); self.is_first_packet = true; self.last_selected_tab = None; - self.load().await + self.load() } /// Saves the player's advancement progress to disk as JSON. @@ -287,17 +287,12 @@ impl PlayerAdvancement { } /// 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() { return Ok(()); } - let path = self.path.clone(); - 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 json = read(&self.path).map_err(AdvancementDataError::Io)?; let loaded_data: HashMap = serde_json::from_slice(&json).map_err(AdvancementDataError::Json)?; @@ -391,18 +386,13 @@ impl PlayerAdvancement { .collect(), }) .collect(); - let first_packet = self.is_first_packet; - tokio::spawn(async move { - player - .send_client_packet(&CUpdateAdvancements::new( - first_packet, - added, - parsed_progress, - removed, - show_advancement, - )) - .await; - }); + player.try_send_client_packet(&CUpdateAdvancements::new( + self.is_first_packet, + added, + parsed_progress, + removed, + show_advancement, + )); } } self.is_first_packet = false; @@ -487,7 +477,7 @@ impl PlayerAdvancement { } /// 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; if let Some(value) = advancement && value.is_root() @@ -500,11 +490,8 @@ impl PlayerAdvancement { if old != self.last_selected_tab && let Some(player) = self.player.upgrade() { - player - .send_client_packet(&CSelectAdvancementsTab::new( - self.last_selected_tab.map(|adv| adv.id.clone()), - )) - .await; + let tab_id = self.last_selected_tab.map(|adv| adv.id.clone()); + player.try_send_client_packet(&CSelectAdvancementsTab::new(tab_id)); } } } @@ -665,7 +652,7 @@ mod tests { // Load from nonexistent file should return Ok (not error) assert!( - pa.load().await.is_ok(), + pa.load().is_ok(), "Loading from nonexistent file should return Ok" ); assert!(pa.progress.is_empty(), "Advancements should remain empty"); @@ -687,7 +674,7 @@ mod tests { std::fs::write(&pa.path, data.to_string()).unwrap(); // 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 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 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 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 assert!( - pa.load().await.is_ok(), + pa.load().is_ok(), "Load should succeed even with invalid IDs" ); assert!( @@ -807,7 +794,7 @@ mod tests { std::fs::write(&pa.path, data.to_string()).unwrap(); //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 assert!( diff --git a/crates/pumpkin/src/net/bedrock/play/player_action.rs b/crates/pumpkin/src/net/bedrock/play/player_action.rs index 64d273824..2e4be1435 100644 --- a/crates/pumpkin/src/net/bedrock/play/player_action.rs +++ b/crates/pumpkin/src/net/bedrock/play/player_action.rs @@ -33,9 +33,13 @@ impl BedrockClient { let (block, state) = world.get_block_and_state(&location); 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 { @@ -52,7 +56,7 @@ impl BedrockClient { } else if !state.is_air() { let speed = crate::block::calc_block_breaking(player, state, block); if speed >= 1.0 { - player.stop_mining().await; + player.stop_mining(); let broken_state = world.get_block_state(&location); let can_harvest = player.can_harvest(broken_state, block); let new_state = world.break_block( @@ -75,7 +79,10 @@ impl BedrockClient { } } } 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 = !player.mining.load(Ordering::Relaxed) || *mining_pos != location; let progress = if starts_breaking { @@ -133,12 +140,16 @@ impl BedrockClient { let elapsed = player.tick_counter.load(Ordering::Relaxed) - player.start_mining_time.load(Ordering::Relaxed) + 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) && same_block && speed * elapsed as f32 >= MIN_PREDICTED_BREAK_PROGRESS { - player.stop_mining().await; + player.stop_mining(); let can_harvest = player.can_harvest(state, block); let flags = if can_harvest { @@ -163,7 +174,7 @@ impl BedrockClient { self.enqueue_client_packet(&CUpdateBlock::new(location, runtime_id as u32)) .await; if matches!(action, PlayerAction::StopDestroyBlock) { - player.stop_mining().await; + player.stop_mining(); } else { world.set_block_breaking( entity, @@ -178,7 +189,7 @@ impl BedrockClient { } } } else if matches!(action, PlayerAction::StopDestroyBlock) { - player.stop_mining().await; + player.stop_mining(); } } PlayerAction::CrackBlock => { @@ -186,7 +197,7 @@ impl BedrockClient { // cracking is done fully server-side. } PlayerAction::AbortDestroyBlock => { - player.stop_mining().await; + player.stop_mining(); } PlayerAction::DropItem => { player.drop_held_item(false).await; diff --git a/crates/pumpkin/src/net/java/mod.rs b/crates/pumpkin/src/net/java/mod.rs index e97d124c9..d11f84d43 100644 --- a/crates/pumpkin/src/net/java/mod.rs +++ b/crates/pumpkin/src/net/java/mod.rs @@ -929,13 +929,14 @@ impl JavaClient { id if id == SSetJigsawBlock::to_id(version) => { self.handle_set_jigsaw_block( player, - SSetJigsawBlock::read(&mut payload, &version)?, - ) - .await; + &SSetJigsawBlock::read(&mut payload, &version)?, + ); } id if id == SJigsawGenerate::to_id(version) => { - self.handle_jigsaw_generate(player, SJigsawGenerate::read(&mut payload, &version)?) - .await; + self.handle_jigsaw_generate( + player, + &SJigsawGenerate::read(&mut payload, &version)?, + ); } id if id == SPlayerCommand::to_id(version) => { self.handle_player_command( @@ -1016,8 +1017,7 @@ impl JavaClient { ); } id if id == SChunkBatch::to_id(version) => { - self.handle_chunk_batch(player, SChunkBatch::read(&mut payload, &version)?) - .await; + self.handle_chunk_batch(player, &SChunkBatch::read(&mut payload, &version)?); } id if id == SPlayerSession::to_id(version) => { self.handle_chat_session_update( @@ -1083,8 +1083,7 @@ impl JavaClient { self.handle_seen_advancement( player, SSeenAdvancement::read(&mut payload, &version)?, - ) - .await; + ); } id if id == SPlayResourcePack::to_id(version) => { self.handle_play_resource_pack_response( diff --git a/crates/pumpkin/src/net/java/play/chat_ack.rs b/crates/pumpkin/src/net/java/play/chat_ack.rs index add8b78df..ffb7f3bc2 100644 --- a/crates/pumpkin/src/net/java/play/chat_ack.rs +++ b/crates/pumpkin/src/net/java/play/chat_ack.rs @@ -19,13 +19,22 @@ impl JavaClient { return; } - let mut cache = player.signature_cache.lock().await; - if let Err(err) = cache.last_seen_validator.apply_offset(offset as usize) { + let validation_err = { + 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!( "Failed to validate message acknowledgement offset from {}: {}", player.gameprofile.name, err ); - drop(cache); self.kick(TextComponent::translate_cross( translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED, translation::java::MULTIPLAYER_DISCONNECT_CHAT_VALIDATION_FAILED, diff --git a/crates/pumpkin/src/net/java/play/chat_message.rs b/crates/pumpkin/src/net/java/play/chat_message.rs index e48ca5979..a55ed81e2 100644 --- a/crates/pumpkin/src/net/java/play/chat_message.rs +++ b/crates/pumpkin/src/net/java/play/chat_message.rs @@ -19,10 +19,7 @@ impl JavaClient { let gameprofile = &player.gameprofile; - if let Err(err) = self - .validate_chat_message(server, player, &chat_message) - .await - { + if let Err(err) = self.validate_chat_message(server, player, &chat_message) { log_at_level!( err.severity(), "{} (uuid {}) {}", @@ -82,7 +79,7 @@ impl JavaClient { } /// Runs all vanilla checks for a valid chat message - pub async fn validate_chat_message( + pub fn validate_chat_message( &self, server: &Server, player: &Arc, @@ -124,7 +121,13 @@ impl JavaClient { } // 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); } @@ -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 cache .last_seen_validator @@ -159,8 +165,14 @@ impl JavaClient { // Validate previous signature checksum (new in 1.21.5) // The client can bypass this check by sending 0 if chat_message.checksum != 0 { - let checksum = - polynomial_rolling_hash(player.signature_cache.lock().await.last_seen.as_ref()); + let checksum = polynomial_rolling_hash( + player + .signature_cache + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .last_seen + .as_ref(), + ); if checksum != chat_message.checksum { return Err(ChatError::ChatValidationFailed); } @@ -197,7 +209,10 @@ impl JavaClient { } // 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.expires_at, session.public_key.clone(), diff --git a/crates/pumpkin/src/net/java/play/chunk_batch.rs b/crates/pumpkin/src/net/java/play/chunk_batch.rs index 56caad8bb..5c6dd91f0 100644 --- a/crates/pumpkin/src/net/java/play/chunk_batch.rs +++ b/crates/pumpkin/src/net/java/play/chunk_batch.rs @@ -2,11 +2,11 @@ use super::*; impl JavaClient { - pub async fn handle_chunk_batch(&self, player: &Player, packet: SChunkBatch) { + pub fn handle_chunk_batch(&self, player: &Player, packet: &SChunkBatch) { player .chunk_manager .lock() - .await + .unwrap_or_else(std::sync::PoisonError::into_inner) .handle_acknowledge(packet.chunks_per_tick); trace!( "Client requested {} chunks per tick", diff --git a/crates/pumpkin/src/net/java/play/confirm_teleport.rs b/crates/pumpkin/src/net/java/play/confirm_teleport.rs index 6f043375c..f60c0b7bc 100644 --- a/crates/pumpkin/src/net/java/play/confirm_teleport.rs +++ b/crates/pumpkin/src/net/java/play/confirm_teleport.rs @@ -7,25 +7,43 @@ impl JavaClient { player: &Player, confirm_teleport: SConfirmTeleport, ) { - let mut awaiting_teleport = player.awaiting_teleport.lock().await; - if let Some((id, position)) = awaiting_teleport.as_ref() { - if id == &confirm_teleport.teleport_id { - // 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. - player.get_entity().set_pos(*position); + enum TeleportResult { + Success, + WrongId, + NotTeleporting, + } - *awaiting_teleport = None; - drop(awaiting_teleport); + 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 id == &confirm_teleport.teleport_id { + // 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. + player.get_entity().set_pos(*position); + *awaiting_teleport = None; + TeleportResult::Success + } else { + TeleportResult::WrongId + } } else { - drop(awaiting_teleport); + TeleportResult::NotTeleporting + } + }; + + match result { + TeleportResult::Success => {} + TeleportResult::WrongId => { self.kick(TextComponent::text("Wrong teleport id")).await; } - } else { - drop(awaiting_teleport); - self.kick(TextComponent::text( - "Send Teleport confirm, but we did not teleport", - )) - .await; + TeleportResult::NotTeleporting => { + self.kick(TextComponent::text( + "Send Teleport confirm, but we did not teleport", + )) + .await; + } } } } diff --git a/crates/pumpkin/src/net/java/play/jigsaw_generate.rs b/crates/pumpkin/src/net/java/play/jigsaw_generate.rs index 066614700..2a6190572 100644 --- a/crates/pumpkin/src/net/java/play/jigsaw_generate.rs +++ b/crates/pumpkin/src/net/java/play/jigsaw_generate.rs @@ -2,7 +2,7 @@ use super::*; impl JavaClient { - pub async fn handle_jigsaw_generate(&self, player: &Arc, generate: SJigsawGenerate) { + pub fn handle_jigsaw_generate(&self, player: &Arc, generate: &SJigsawGenerate) { if !player.is_creative() { return; } @@ -13,9 +13,7 @@ impl JavaClient { if let Some(block_entity) = player.world().get_block_entity(&pos) && let Some(jigsaw_block) = block_entity.as_any().downcast_ref::() { - jigsaw_block - .generate(&player.world(), generate.levels.0, generate.keep_jigsaws) - .await; + jigsaw_block.generate(&player.world(), generate.levels.0, generate.keep_jigsaws); } } } diff --git a/crates/pumpkin/src/net/java/play/mod.rs b/crates/pumpkin/src/net/java/play/mod.rs index 4dd112c9e..235718fb8 100644 --- a/crates/pumpkin/src/net/java/play/mod.rs +++ b/crates/pumpkin/src/net/java/play/mod.rs @@ -57,10 +57,10 @@ use pumpkin_protocol::bedrock::client::CMovePlayer; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_protocol::java::client::play::{ - CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor, - CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetCamera, CSetSelectedSlot, - CSystemChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, - PlayerAction, PlayerInfoFlags, + CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, CPingResponse, + CPlayerInfoUpdate, CPlayerPosition, CSetCamera, CSetSelectedSlot, CSystemChatMessage, + CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction, + PlayerInfoFlags, }; use pumpkin_protocol::java::server::play::{ Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected, diff --git a/crates/pumpkin/src/net/java/play/player_action.rs b/crates/pumpkin/src/net/java/play/player_action.rs index b22e18db7..7b719ceb2 100644 --- a/crates/pumpkin/src/net/java/play/player_action.rs +++ b/crates/pumpkin/src/net/java/play/player_action.rs @@ -129,7 +129,10 @@ impl JavaClient { self.sync_block_state_to_client(&world, position).await; } else { 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; player .current_block_breaking_speed diff --git a/crates/pumpkin/src/net/java/play/player_position.rs b/crates/pumpkin/src/net/java/play/player_position.rs index ddc6b8479..74260fe60 100644 --- a/crates/pumpkin/src/net/java/play/player_position.rs +++ b/crates/pumpkin/src/net/java/play/player_position.rs @@ -56,7 +56,12 @@ impl JavaClient { return; } // 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; } // y = feet Y @@ -185,7 +190,12 @@ impl JavaClient { return; } // 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; } // y = feet Y @@ -325,7 +335,11 @@ impl JavaClient { pub async fn force_tp(&self, player: &Arc, position: Vector3) { 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( teleport_id.into(), player.get_entity().pos.load(), diff --git a/crates/pumpkin/src/net/java/play/seen_advancement.rs b/crates/pumpkin/src/net/java/play/seen_advancement.rs index b186c2d0d..a9763323a 100644 --- a/crates/pumpkin/src/net/java/play/seen_advancement.rs +++ b/crates/pumpkin/src/net/java/play/seen_advancement.rs @@ -2,16 +2,15 @@ use super::*; impl JavaClient { - pub async fn handle_seen_advancement(&self, player: &Arc, packet: SSeenAdvancement) { + pub fn handle_seen_advancement(&self, player: &Arc, packet: SSeenAdvancement) { if let SSeenAdvancement::OpenTab(tab) = packet { let advancement = Advancement::from_minecraft_name(&tab.to_string()); if advancement.is_some() { player .advancements .lock() - .await - .set_selected_tab(advancement) - .await; + .unwrap_or_else(std::sync::PoisonError::into_inner) + .set_selected_tab(advancement); } } } diff --git a/crates/pumpkin/src/net/java/play/set_jigsaw_block.rs b/crates/pumpkin/src/net/java/play/set_jigsaw_block.rs index 076a3c2c3..affadbb0c 100644 --- a/crates/pumpkin/src/net/java/play/set_jigsaw_block.rs +++ b/crates/pumpkin/src/net/java/play/set_jigsaw_block.rs @@ -2,7 +2,7 @@ use super::*; impl JavaClient { - pub async fn handle_set_jigsaw_block(&self, player: &Arc, jigsaw: SSetJigsawBlock<'_>) { + pub fn handle_set_jigsaw_block(&self, player: &Arc, jigsaw: &SSetJigsawBlock<'_>) { if !player.is_creative() { return; } diff --git a/crates/pumpkin/src/net/java/play/use_item_on.rs b/crates/pumpkin/src/net/java/play/use_item_on.rs index 9db0ce41f..e7c92c5e3 100644 --- a/crates/pumpkin/src/net/java/play/use_item_on.rs +++ b/crates/pumpkin/src/net/java/play/use_item_on.rs @@ -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; - } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index aa54e1ae7..19c5ce6c0 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -1450,7 +1450,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { player: Resource, ) -> wasmtime::Result>> { 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( || Ok(None), |name| { @@ -1468,7 +1468,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result<()> { let name = name.map(|n| text_component_from_resource(self, &n)); let player = player_from_resource(self, &player)?; - player.set_tab_list_name(name).await; + player.set_tab_list_name(name); Ok(()) } @@ -1879,7 +1879,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result<()> { let player = player_from_resource(self, &player)?; let other = player_from_resource(self, &other)?; - player.hide_player(other.gameprofile.id).await; + player.hide_player(other.gameprofile.id); Ok(()) } @@ -1890,7 +1890,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result<()> { let player = player_from_resource(self, &player)?; let other = player_from_resource(self, &other)?; - player.show_player(other.gameprofile.id).await; + player.show_player(other.gameprofile.id); Ok(()) } @@ -1901,7 +1901,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result { let player = player_from_resource(self, &player)?; 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( @@ -1911,7 +1911,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result { let player = player_from_resource(self, &player)?; 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( @@ -2625,7 +2625,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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( || pumpkin::plugin::advancement::AdvancementProgress { advancement_id: advancement.id.to_string(), @@ -2667,7 +2670,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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); if awarded { guard.flush_dirty(&player, true); @@ -2689,7 +2695,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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); if revoked { guard.flush_dirty(&player, true); @@ -2710,7 +2719,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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); if progress.is_done() { return Ok(false); @@ -2741,7 +2753,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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); if !progress.has_progress() { return Ok(false); @@ -2772,7 +2787,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { else { 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 .progress .map @@ -2786,7 +2804,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { player: Resource, ) -> wasmtime::Result> { 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 .progress .map @@ -2802,7 +2823,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { player: Resource, ) -> wasmtime::Result> { 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())) } @@ -2815,8 +2839,11 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { let target_adv = tab_id.as_deref().and_then( crate::plugin::loader::wasm::wasm_host::wit::v0_1::advancement::find_advancement, ); - let mut guard = player.advancements.lock().await; - guard.set_selected_tab(target_adv).await; + let mut guard = player + .advancements + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + guard.set_selected_tab(target_adv); Ok(()) } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs index 907e5a13b..ccbdd40dc 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs @@ -315,9 +315,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { .server .as_ref() .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; - server - .broadcast_tab_list_header_footer(&header, &footer) - .await; + server.broadcast_tab_list_header_footer(&header, &footer); Ok(()) } diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index 83c06548e..666ebf525 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -705,8 +705,11 @@ impl Server { // Wrap in Arc after data is loaded let player = Arc::new(player); { - let mut advancements = player.advancements.lock().await; - if let Err(e) = advancements.load().await { + let mut advancements = player + .advancements + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Err(e) = advancements.load() { warn!("Error loading player {}: {e}", player.gameprofile.id); } advancements.player = Arc::downgrade(&player); @@ -795,16 +798,18 @@ impl Server { } } - pub async fn broadcast_tab_list_header_footer( - &self, - header: &TextComponent, - footer: &TextComponent, - ) { + pub fn broadcast_tab_list_header_footer(&self, header: &TextComponent, footer: &TextComponent) { let packet = CTabList::new(header, footer); for world in self.worlds.load().iter() { for player in world.players.load().iter() { - *player.tab_list_header.lock().await = header.clone(); - *player.tab_list_footer.lock().await = footer.clone(); + *player + .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); } diff --git a/crates/pumpkin/src/world/chunker.rs b/crates/pumpkin/src/world/chunker.rs index 4c6ad6fe1..7f77861dd 100644 --- a/crates/pumpkin/src/world/chunker.rs +++ b/crates/pumpkin/src/world/chunker.rs @@ -83,7 +83,10 @@ pub async fn update_position(player: &Arc) { // 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. 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(); chunk_manager.update_center_and_view_distance( new_chunk_center, diff --git a/crates/pumpkin/src/world/map.rs b/crates/pumpkin/src/world/map.rs index 4e0c8abf1..bc118161b 100644 --- a/crates/pumpkin/src/world/map.rs +++ b/crates/pumpkin/src/world/map.rs @@ -2,8 +2,7 @@ use crate::entity::player::Player; use dashmap::DashMap; use pumpkin_data::dimension::Dimension; use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; -use std::sync::Arc; -use tokio::sync::Mutex; +use std::sync::{Arc, Mutex}; pub struct MapManager { pub maps: DashMap>>, diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index 4b3cd2099..c03da2b27 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -481,7 +481,7 @@ impl World { .map(|chunk_block_entities| *chunk_block_entities.key()) .collect(); for chunk_pos in chunks { - self.save_block_entities(&chunk_pos); + self.save_block_entities(chunk_pos); } // 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 /// entity up - so this has to run before the chunk is dropped, or everything /// the entity did since it was loaded is lost. - fn save_block_entities(&self, chunk_pos: &Vector2) { + fn save_block_entities(&self, chunk_pos: Vector2) { let Some(block_entities) = self .block_entities - .get(chunk_pos) + .get(&chunk_pos) .map(|chunk_block_entities| chunk_block_entities.values().cloned().collect::>()) else { return; @@ -851,14 +851,25 @@ impl World { chat_message: &SChatMessage<'_>, 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 cache = sender.signature_cache.lock().await; + let cache = sender + .signature_cache + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); cache.last_seen.clone() }; 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( VarInt(messages_received), sender.gameprofile.id, @@ -867,7 +878,7 @@ impl World { chat_message.message.into(), chat_message.timestamp, chat_message.salt, - sender_last_seen.indexed_for(recipient).await, + sender_last_seen.indexed_for(recipient), Some(decorated_message.clone()), FilterType::PassThrough, (RAW + 1).into(), // Custom registry chat_type with no sender name @@ -888,7 +899,10 @@ impl World { } 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.last_seen_validator.add_pending(signature); let tracked_count = cache.last_seen_validator.tracked_messages_count(); @@ -911,13 +925,21 @@ impl World { recipient .signature_cache .lock() - .await + .unwrap_or_else(std::sync::PoisonError::into_inner) .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( @@ -1739,7 +1761,7 @@ impl World { spawning_chunks.shuffle(&mut rng()); let world = self.clone(); - let spawn_handle = handle.clone(); + let spawn_handle = handle; spawning_chunks.par_chunks(8).for_each(|batch| { let _guard = spawn_handle.enter(); let world = world.clone(); @@ -3249,7 +3271,7 @@ impl World { self.broadcast_editioned(&player_info_update, &bedrock_player_list); // 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 java_player = [pumpkin_protocol::java::client::play::Player { uuid: gameprofile.id, @@ -3276,8 +3298,11 @@ impl World { let mut current_player_data = Vec::new(); for (properties, player) in &data_to_process { - let chat_session = player.chat_session.lock().await; - let tab_list_name = player.get_tab_list_name().await; + let chat_session = player + .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 { name: &player.gameprofile.name, @@ -4088,7 +4113,7 @@ impl World { player .chunk_manager .lock() - .await + .unwrap_or_else(std::sync::PoisonError::into_inner) .change_world(&self.level, destination.clone()); player.living_entity.entity.set_world(destination.clone()); destination.players.rcu(|current_list| { @@ -4934,7 +4959,7 @@ impl World { } for chunk_pos in &chunks_set { - self.save_block_entities(chunk_pos); + self.save_block_entities(*chunk_pos); self.block_entities.remove(chunk_pos); } } @@ -6427,7 +6452,7 @@ impl World { .map(|chunk_block_entities| *chunk_block_entities.key()) .collect(); 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() { @@ -6793,32 +6818,26 @@ impl WorldPortalExt for WorldPortal { } fn spawn_structure_entities(&self, entities: Vec) { - let world = self.0.clone(); - let Some(server) = world.server.upgrade() else { - return; - }; - server.spawn_task(async move { - for nbt in entities { - let Some(id) = nbt.get_string("id") else { - continue; - }; - let Some(entity_type) = - EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id)) - else { - warn!("Unknown structure entity type: {id}"); - continue; - }; - let entity = from_type( - entity_type, - Vector3::new(0.0, 0.0, 0.0), - &world, - Uuid::new_v4(), - ); - entity.get_entity().read_nbt_non_mut(&nbt); - entity.read_nbt_non_mut(&nbt); - world.spawn_entity(entity); - } - }); + for nbt in entities { + let Some(id) = nbt.get_string("id") else { + continue; + }; + let Some(entity_type) = + EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id)) + else { + warn!("Unknown structure entity type: {id}"); + continue; + }; + let entity = from_type( + entity_type, + Vector3::new(0.0, 0.0, 0.0), + &self.0, + Uuid::new_v4(), + ); + entity.get_entity().read_nbt_non_mut(&nbt); + entity.read_nbt_non_mut(&nbt); + self.0.spawn_entity(entity); + } } }