mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: leaves
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(¤t_world).await;
|
||||
|
||||
self.chunk_manager.lock().await.change_world(¤t_world.level, new_world.clone());
|
||||
self.chunk_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.change_world(¤t_world.level, new_world.clone());
|
||||
self.living_entity.entity.set_world(new_world.clone());
|
||||
|
||||
if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
||||
@@ -3651,7 +3685,10 @@ impl Player {
|
||||
entity.set_rotation(yaw, pitch);
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
*self.awaiting_teleport.lock().await =
|
||||
*self
|
||||
.awaiting_teleport
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) =
|
||||
Some((teleport_id.into(), position));
|
||||
let packet = CPlayerPosition::new(
|
||||
teleport_id.into(),
|
||||
@@ -6747,16 +6784,14 @@ impl AsRef<[Box<[u8]>]> for LastSeen {
|
||||
impl LastSeen {
|
||||
/// The sender's `last_seen` signatures are sent as ID's if the recipient has them in their cache.
|
||||
/// Otherwise, the full signature is sent. (ID:0 indicates full signature is being sent)
|
||||
pub async fn indexed_for(&self, recipient: &Arc<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)
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -247,10 +247,4 @@ impl JavaClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the block placed was a sign, then opens a dialog.
|
||||
pub async fn send_sign_packet(&self, block_position: BlockPos, is_front_text: bool) {
|
||||
self.enqueue_client_packet(&COpenSignEditor::new(block_position, is_front_text))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1450,7 +1450,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
player: Resource<Player>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>>>,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user