feat: leaves

This commit is contained in:
Alexander Medvedev 2026-08-26 22:58:01 +02:00
parent e2a138cfa6
commit 3d69190a37
41 changed files with 556 additions and 301 deletions

View File

@ -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)
} }

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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
} }

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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();

View File

@ -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()

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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));
} }

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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
} }

View File

@ -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(&current_world).await; self.unload_watched_chunks(&current_world).await;
self.chunk_manager.lock().await.change_world(&current_world.level, new_world.clone()); self.chunk_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.change_world(&current_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)

View File

@ -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!(

View File

@ -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;

View File

@ -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(

View File

@ -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,

View File

@ -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(),

View File

@ -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",

View File

@ -7,21 +7,38 @@ 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",
)) ))
@ -29,3 +46,4 @@ impl JavaClient {
} }
} }
} }
}

View File

@ -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;
} }
} }
} }

View File

@ -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,

View File

@ -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

View File

@ -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(),

View File

@ -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;
} }
} }
} }

View File

@ -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;
} }

View File

@ -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;
}
} }

View File

@ -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(())
} }

View File

@ -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(())
} }

View File

@ -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);
} }

View File

@ -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,

View File

@ -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>>>,

View File

@ -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);
} }
});
} }
} }