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 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<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 {
Self::can_replace(state, id) || id.has_tag(tag::Block::MINECRAFT_LOGS)
}

View File

@@ -17,7 +17,7 @@ impl BlockMetadata for BrushableBlock {
}
impl BrushableBlock {
pub async fn brush(
pub fn brush(
world: &Arc<crate::world::World>,
pos: &pumpkin_util::math::position::BlockPos,
block: &Block,

View File

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

View File

@@ -27,7 +27,7 @@ impl BlockMetadata for 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);
if block.id != BlockId::SCULK_SHRIEKER {
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::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
}

View File

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

View File

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

View File

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

View File

@@ -177,6 +177,7 @@ impl HopperBlockEntity {
true
}
#[allow(clippy::too_many_lines)]
fn suck_in_items(&self, world: &Arc<World>) -> bool {
// TODO getEntityContainer
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
.pool
.lock()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Option<Mutex<PortalProcessor>>>,
pub portal_manager: std::sync::Mutex<Option<PortalProcessor>>,
/// Custom name for the entity
pub custom_name: ArcSwap<Option<TextComponent>>,
/// 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.

View File

@@ -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<World>, player_uuid: Uuid) {
fn complete_trade(&self, offer_index: usize, world: &Arc<World>, 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<Player>) {
fn resend_offers_to_player(&self, player: &Arc<Player>) {
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<Player>) {
pub fn open_trading_screen(&self, player: &Arc<Player>) {
// 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<pumpkin_protocol::java::client::play::MerchantOffer>,
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

View File

@@ -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<Player>) {
pub fn open_trading_screen(&self, player: &Arc<Player>) {
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<pumpkin_protocol::java::client::play::MerchantOffer>,
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
}

View File

@@ -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<TextComponent> {
self.tab_list_name.lock().await.clone()
pub fn get_tab_list_name(&self) -> Option<TextComponent> {
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>) {
*self.tab_list_name.lock().await = name.clone();
pub fn set_tab_list_name(&self, name: Option<TextComponent>) {
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(&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());
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<Player>) -> Box<[PreviousMessage]> {
pub fn indexed_for(&self, recipient: &Arc<Player>) -> 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)

View File

@@ -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<String, AdvancementProgress> =
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!(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
use super::*;
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() {
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::<JigsawBlockEntity>()
{
jigsaw_block
.generate(&player.world(), generate.levels.0, generate.keep_jigsaws)
.await;
jigsaw_block.generate(&player.world(), generate.levels.0, generate.keep_jigsaws);
}
}
}

View File

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

View File

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

View File

@@ -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<Player>, position: Vector3<f64>) {
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(),

View File

@@ -2,16 +2,15 @@
use super::*;
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 {
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);
}
}
}

View File

@@ -2,7 +2,7 @@
use super::*;
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() {
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>,
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::text::TextComponent>>> {
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<bool> {
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<bool> {
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<Player>,
) -> wasmtime::Result<Vec<String>> {
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<Player>,
) -> wasmtime::Result<Option<String>> {
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(())
}

View File

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

View File

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

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.
// 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,

View File

@@ -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<i32, Arc<Mutex<MapData>>>,

View File

@@ -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<i32>) {
fn save_block_entities(&self, chunk_pos: Vector2<i32>) {
let Some(block_entities) = self
.block_entities
.get(chunk_pos)
.get(&chunk_pos)
.map(|chunk_block_entities| chunk_block_entities.values().cloned().collect::<Vec<_>>())
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<J: ClientPacket, B: BClientPacket>(
@@ -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<NbtCompound>) {
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);
}
}
}