fix(bedrock): synchronize colored beds (#2917)

* fix(bedrock): synchronize colored beds

* refactor(bedrock): abstract bed block actor data
This commit is contained in:
ZlordHUN
2026-08-13 18:21:35 +02:00
committed by GitHub
parent ddd98c88d9
commit 05c692985f
6 changed files with 198 additions and 13 deletions

View File

@@ -0,0 +1,58 @@
use std::io::{Error, Write};
use pumpkin_macros::packet;
use pumpkin_nbt::{Nbt, compound::NbtCompound};
use pumpkin_util::math::position::BlockPos;
use crate::serial::PacketWrite;
/// Synchronizes the complete block-actor data for a block position.
#[packet(56)]
pub struct CBlockActorData {
pub position: BlockPos,
pub data: NbtCompound,
}
impl CBlockActorData {
#[must_use]
pub const fn new(position: BlockPos, data: NbtCompound) -> Self {
Self { position, data }
}
}
impl PacketWrite for CBlockActorData {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.position.write(writer)?;
writer.write_all(&Nbt::from(self.data.clone()).write_bedrock())
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use pumpkin_nbt::deserializer::NbtReadHelperBedrock;
use super::*;
use crate::{Packet, serial::PacketWrite};
#[test]
fn block_actor_data_uses_bedrock_network_nbt() {
assert_eq!(<CBlockActorData as Packet>::PACKET_ID, 56);
let mut data = NbtCompound::new();
data.put_string("id", "Bed".to_string());
data.put_byte("color", 11);
let mut encoded = Vec::new();
CBlockActorData::new(BlockPos::new(1, 64, -2), data)
.write(&mut encoded)
.unwrap();
assert_eq!(&encoded[..4], &[2, 128, 1, 3]);
let mut reader = NbtReadHelperBedrock::new(Cursor::new(&encoded[4..]));
let parsed = Nbt::read(&mut reader).unwrap();
assert_eq!(parsed.get_string("id"), Some("Bed"));
assert_eq!(parsed.get_byte("color"), Some(11));
}
}

View File

@@ -3,6 +3,7 @@ pub mod add_item_actor;
pub mod add_player;
pub mod available_commands;
pub mod biome_definition_list;
pub mod block_actor_data;
pub mod block_event;
pub mod boss_event;
pub mod change_dimension;
@@ -61,6 +62,7 @@ pub use add_item_actor::*;
pub use add_player::*;
pub use available_commands::*;
pub use biome_definition_list::*;
pub use block_actor_data::*;
pub use block_event::*;
pub use boss_event::*;
pub use change_dimension::*;

View File

@@ -20,7 +20,7 @@ use crate::block::bounce_entity_after_fall;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, OnStateReplacedArgs,
PlacedArgs,
PlacedArgs, PlayerPlacedArgs,
};
use crate::entity::{Entity, EntityBase};
use crate::world::World;
@@ -135,6 +135,16 @@ impl BlockBehaviour for BedBlock {
})
}
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
args.world.play_bedrock_level_sound(
"place",
&args.position.to_centered_f64(),
i32::from(pumpkin_data::BlockState::to_be_network_id(args.state_id)),
);
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let bed_props = BedProperties::from_state_id(args.state.id, args.block);

View File

@@ -1,5 +1,10 @@
use std::pin::Pin;
use pumpkin_data::{
BlockState, BlockStateId,
item::JavaToBedrockItemMapping,
tag::{Block as BlockTag, Taggable},
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
@@ -32,6 +37,25 @@ impl BlockEntity for BedBlockEntity {
Box::pin(async {})
}
fn bedrock_block_actor_data(&self, state_id: BlockStateId) -> Option<NbtCompound> {
let (block, _) = BlockState::from_id_with_block(state_id);
if !block.has_tag(&BlockTag::MINECRAFT_BEDS) {
return None;
}
let color = JavaToBedrockItemMapping::from_java_item_id(block.item_id)?
.bedrock_data
.try_into()
.ok()?;
let mut nbt = NbtCompound::new();
nbt.put_string("id", "Bed".to_string());
nbt.put_int("x", self.position.0.x);
nbt.put_int("y", self.position.0.y);
nbt.put_int("z", self.position.0.z);
nbt.put_byte("color", color);
nbt.put_bool("isMovable", true);
Some(nbt)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
@@ -44,3 +68,33 @@ impl BedBlockEntity {
Self { position }
}
}
#[cfg(test)]
mod tests {
use pumpkin_data::Block;
use pumpkin_util::math::position::BlockPos;
use super::{BedBlockEntity, BlockEntity};
#[test]
fn bedrock_block_actor_uses_the_java_bed_color() {
let entity = BedBlockEntity::new(BlockPos::new(5, 64, 7));
let blue = entity
.bedrock_block_actor_data(Block::BLUE_BED.default_state.id)
.unwrap();
let red = entity
.bedrock_block_actor_data(Block::RED_BED.default_state.id)
.unwrap();
assert_eq!(blue.get_string("id"), Some("Bed"));
assert_eq!(blue.get_byte("color"), Some(11));
assert_eq!(red.get_byte("color"), Some(14));
assert_eq!(blue.get_int("x"), Some(5));
assert_eq!(blue.get_bool("isMovable"), Some(true));
assert!(
entity
.bedrock_block_actor_data(Block::STONE.default_state.id)
.is_none()
);
}
}

View File

@@ -126,6 +126,11 @@ pub trait BlockEntity: Any + Send + Sync {
None
}
/// Obtain block actor NBT for fields Bedrock does not include in its block state.
fn bedrock_block_actor_data(&self, _state_id: BlockStateId) -> Option<NbtCompound> {
None
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
None
}

View File

@@ -5,7 +5,9 @@ use pumpkin_data::chunk::Biome;
use pumpkin_data::item::{BedrockItem, BedrockItemVersion};
use pumpkin_protocol::bedrock::client::item_registry::{CItemRegistry, ItemDefinition};
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
use pumpkin_protocol::bedrock::client::{CBiomeDefinitionList, EntityProperties};
use pumpkin_protocol::bedrock::client::{
CBiomeDefinitionList, EntityProperties, block_actor_data::CBlockActorData,
};
use pumpkin_protocol::bedrock::network_item::{NetworkItemDescriptor, NetworkItemStackDescriptor};
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
use pumpkin_world::generation::proto_chunk::GenerationCache;
@@ -1289,6 +1291,12 @@ impl World {
be_block_id as u32,
),
);
if let Some(data) = self.bedrock_block_entity_data(block_state_id, block_pos) {
self.broadcast_to_chunk_bedrock(
chunk_pos,
&CBlockActorData::new(block_pos, data),
);
}
} else {
let players = self.players.load();
let mut java_recipients = Vec::new();
@@ -1311,6 +1319,13 @@ impl World {
be_block_id as u32,
),
);
if let Some(data) =
self.bedrock_block_entity_data(*block_state_id, *block_pos)
{
be_client.try_enqueue_packet(&CBlockActorData::new(
*block_pos, data,
));
}
}
}
}
@@ -5442,33 +5457,61 @@ impl World {
Some(entity)
}
fn bedrock_block_entity_data(
&self,
state_id: BlockStateId,
position: BlockPos,
) -> Option<NbtCompound> {
self.get_block_entity(&position)?
.bedrock_block_actor_data(state_id)
}
/// Builds Bedrock block actor tags that are not represented by Java block states alone.
pub fn bedrock_chunk_block_actors(&self, chunk: &ChunkData) -> Vec<NbtCompound> {
let chunk_pos = Vector2::new(chunk.x, chunk.z);
let live_positions: FxHashSet<_> = self
let live_entities: FxHashMap<_, _> = self
.block_entities
.get(&chunk_pos)
.map(|entities| entities.keys().copied().collect())
.map(|entities| {
entities
.iter()
.map(|(position, entity)| (*position, entity.clone()))
.collect()
})
.unwrap_or_default();
let pending = chunk
.pending_block_entities
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
live_positions
live_entities
.iter()
.chain(
pending
.keys()
.filter(|position| !live_positions.contains(position)),
)
.filter_map(|position| {
.filter_map(|(position, entity)| {
let relative = position.chunk_relative_position();
chunk
.section
.get_block_absolute_y(relative.x as usize, relative.y, relative.z as usize)
.and_then(|state_id| bedrock_chest_block_actor(state_id, *position))
.and_then(|state_id| {
bedrock_chest_block_actor(state_id, *position)
.or_else(|| entity.bedrock_block_actor_data(state_id))
})
})
.chain(
pending
.iter()
.filter(|(position, _)| !live_entities.contains_key(position))
.filter_map(|(position, nbt)| {
let relative = position.chunk_relative_position();
let state_id = chunk.section.get_block_absolute_y(
relative.x as usize,
relative.y,
relative.z as usize,
)?;
bedrock_chest_block_actor(state_id, *position).or_else(|| {
block_entity_from_nbt(nbt)?.bedrock_block_actor_data(state_id)
})
}),
)
.collect()
}
@@ -5805,6 +5848,19 @@ impl World {
Self::broadcast_java_grouped(packet, recipients_by_version);
}
fn broadcast_to_chunk_bedrock<P: BClientPacket>(&self, chunk_pos: Vector2<i32>, packet: &P) {
let players = self.players.load();
for player in players.iter().filter(|player| {
let center = player.get_entity().chunk_pos.load();
let view_distance = get_view_distance(player).get() as i32;
is_within_view_distance(chunk_pos, center, view_distance)
}) {
if let ClientPlatform::Bedrock(client) = player.client.as_ref() {
client.try_enqueue_packet(packet);
}
}
}
pub fn broadcast_to_chunk_editioned_sync<J: ClientPacket, B: BClientPacket>(
&self,
chunk_pos: Vector2<i32>,