feat(bedrock): add CMobEquipment

This commit is contained in:
Alexander Medvedev
2026-08-11 18:40:48 +02:00
parent 432f092b0f
commit 7eb49e2e4d
5 changed files with 170 additions and 43 deletions

View File

@@ -0,0 +1,45 @@
use crate::{
bedrock::network_item::NetworkItemStackDescriptor, codec::var_ulong::VarULong,
serial::PacketWrite,
};
use pumpkin_macros::packet;
#[derive(PacketWrite, Debug)]
#[packet(31)]
pub struct CMobEquipment {
pub entity_runtime_id: VarULong,
pub item: NetworkItemStackDescriptor,
pub inventory_slot: u8,
pub hotbar_slot: u8,
pub window_id: u8,
}
impl CMobEquipment {
#[must_use]
pub const fn new(
entity_runtime_id: u64,
item: NetworkItemStackDescriptor,
inventory_slot: u8,
hotbar_slot: u8,
window_id: u8,
) -> Self {
Self {
entity_runtime_id: VarULong(entity_runtime_id),
item,
inventory_slot,
hotbar_slot,
window_id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::Packet;
#[test]
fn mob_equipment_packet_id() {
assert_eq!(CMobEquipment::PACKET_ID, 31);
}
}

View File

@@ -19,6 +19,7 @@ pub mod item_registry;
pub mod item_stack_response;
pub mod level_chunk;
pub mod level_event;
pub mod mob_equipment;
pub mod modal_form_request;
pub mod move_actor_absolute;
pub mod move_actor_delta;
@@ -73,6 +74,7 @@ pub use item_stack_response::*;
pub use level_chunk::*;
pub use level_event::*;
pub use level_sound_event::*;
pub use mob_equipment::*;
pub use modal_form_request::*;
pub use move_actor_absolute::*;
pub use move_actor_delta::*;

View File

@@ -193,7 +193,7 @@ impl LivingEntity {
if equipment.is_empty() {
return;
}
let equipment: Vec<(i8, ItemStackSerializer)> = equipment
let equipment_java: Vec<(i8, ItemStackSerializer)> = equipment
.iter()
.map(|(slot, stack)| {
(
@@ -202,10 +202,43 @@ impl LivingEntity {
)
})
.collect();
self.entity.world.load().broadcast_packet_except(
&[self.entity.entity_uuid],
&CSetEquipment::new(self.entity_id().into(), equipment),
);
let je_packet = CSetEquipment::new(self.entity_id().into(), equipment_java);
let mut sent_editioned = false;
for (slot, stack) in equipment {
if *slot == EquipmentSlot::MAIN_HAND || *slot == EquipmentSlot::OFF_HAND {
let window_id = if *slot == EquipmentSlot::OFF_HAND {
120
} else {
0
};
let be_packet = pumpkin_protocol::bedrock::client::CMobEquipment::new(
self.entity_id() as u64,
pumpkin_protocol::bedrock::network_item::NetworkItemStackDescriptor::from(
stack,
),
0,
0,
window_id,
);
self.entity
.world
.load()
.broadcast_packet_except_editioned_sync(
&[self.entity.entity_uuid],
&je_packet,
&be_packet,
);
sent_editioned = true;
}
}
if !sent_editioned {
self.entity
.world
.load()
.broadcast_packet_except(&[self.entity.entity_uuid], &je_packet);
}
}
/// Picks up and Item entity or XP Orb

View File

@@ -68,11 +68,11 @@ use pumpkin_protocol::java::client::play::{
CCustomPayload, CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent,
CItemCooldown, CMapItemData, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate,
CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetCamera, CSetContainerContent,
CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment, CSetExperience,
CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle,
CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect,
CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags,
PlayerSpawnData, PreviousMessage, Statistic,
CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetExperience, CSetHealth,
CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage,
CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent,
MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags, PlayerSpawnData, PreviousMessage,
Statistic,
};
use pumpkin_protocol::java::server::play::{
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
@@ -5790,18 +5790,8 @@ impl InventoryPlayer for Player {
stack: &'a ItemStack,
) -> PlayerFuture<'a, ()> {
Box::pin(async move {
let chunk_pos = self.living_entity.entity.chunk_pos.load();
self.world().broadcast_to_chunk_except(
chunk_pos,
&[self.get_entity().entity_uuid],
&CSetEquipment::new(
self.entity_id().into(),
vec![(
slot.discriminant(),
ItemStackSerializer::from(stack.clone()),
)],
),
);
self.living_entity
.send_equipment_changes(&[(slot.clone(), stack.clone())]);
if let Some(equippable) = stack.get_data_component::<EquippableImpl>() {
self.world().play_sound_event(

View File

@@ -6,7 +6,7 @@ use pumpkin_data::item::{BedrockItem, BedrockItemVersion};
use pumpkin_protocol::bedrock::client::EntityProperties;
use pumpkin_protocol::bedrock::client::item_registry::{CItemRegistry, ItemDefinition};
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
use pumpkin_protocol::bedrock::network_item::NetworkItemDescriptor;
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;
use std::sync::atomic::Ordering::Relaxed;
@@ -2527,6 +2527,22 @@ impl World {
&bedrock_add_player,
);
let held_item = player.inventory().held_item().await;
let be_mob_equipment = pumpkin_protocol::bedrock::client::CMobEquipment::new(
runtime_id,
NetworkItemStackDescriptor::from(&held_item),
0,
0,
0,
);
self.broadcast_packet_except_editioned_sync(
&[gameprofile.id],
&CSetEquipment::new((runtime_id as i32).into(), vec![]),
&be_mob_equipment,
);
// Broadcast metadata to Java players so they can correctly interact with the new player
let skin_parts = player.config.load().skin_parts;
@@ -2606,6 +2622,18 @@ impl World {
};
client.send_game_packet(&ex_add_player).await;
let ex_held_item = existing_player.inventory().held_item().await;
let ex_be_mob_equipment = pumpkin_protocol::bedrock::client::CMobEquipment::new(
existing_player.entity_id() as u64,
NetworkItemStackDescriptor::from(&ex_held_item),
0,
0,
0,
);
client.send_game_packet(&ex_be_mob_equipment).await;
}
// 3. Trigger Join Event and Broadcast Join Message
@@ -2981,6 +3009,22 @@ impl World {
&bedrock_add_player,
);
let held_item = player.inventory().held_item().await;
let be_mob_equipment = pumpkin_protocol::bedrock::client::CMobEquipment::new(
entity_id as u64,
NetworkItemStackDescriptor::from(&held_item),
0,
0,
0,
);
self.broadcast_packet_except_editioned_sync(
&[player.gameprofile.id],
&CSetEquipment::new(entity_id.into(), vec![]),
&be_mob_equipment,
);
// Broadcast metadata to Java players so they can correctly interact with the new player
let skin_parts = player.config.load().skin_parts;
@@ -3153,12 +3197,9 @@ impl World {
};
{
let mut equipment_list = Vec::new();
equipment_list.push((
EquipmentSlot::MAIN_HAND.discriminant(),
existing_player.inventory.held_item().await,
));
let held_item = existing_player.inventory.held_item().await;
let mut equipment_list =
vec![(EquipmentSlot::MAIN_HAND.discriminant(), held_item.clone())];
let equipment_guard = existing_player.inventory.entity_equipment.lock().await;
for (slot, item_stack) in &equipment_guard.equipment {
@@ -3170,11 +3211,19 @@ impl World {
.map(|(slot, stack)| (*slot, ItemStackSerializer::from(stack.clone())))
.collect();
client
.enqueue_packet(&CSetEquipment::new(
existing_player.entity_id().into(),
equipment,
))
let je_packet = CSetEquipment::new(existing_player.entity_id().into(), equipment);
let be_mob_equipment = pumpkin_protocol::bedrock::client::CMobEquipment::new(
existing_player.entity_id() as u64,
NetworkItemStackDescriptor::from(&held_item),
0,
0,
0,
);
player
.client
.enqueue_packet_editioned(&je_packet, &be_mob_equipment)
.await;
}
}
@@ -3291,12 +3340,8 @@ impl World {
}
async fn send_player_equipment(&self, from: &Player) {
let mut equipment_list = Vec::new();
equipment_list.push((
EquipmentSlot::MAIN_HAND.discriminant(),
from.inventory.held_item().await,
));
let held_item = from.inventory.held_item().await;
let mut equipment_list = vec![(EquipmentSlot::MAIN_HAND.discriminant(), held_item.clone())];
let equipment_guard = from.inventory.entity_equipment.lock().await;
for (slot, item_stack) in &equipment_guard.equipment {
@@ -3307,12 +3352,24 @@ impl World {
.iter()
.map(|(slot, stack)| (*slot, ItemStackSerializer::from(stack.clone())))
.collect();
let je_packet = CSetEquipment::new(from.entity_id().into(), equipment);
let be_mob_equipment = pumpkin_protocol::bedrock::client::CMobEquipment::new(
from.entity_id() as u64,
NetworkItemStackDescriptor::from(&held_item),
0,
0,
0,
);
let chunk_pos = from.get_entity().chunk_pos.load();
self.broadcast_to_chunk_except(
self.broadcast_to_chunk_except_editioned(
chunk_pos,
&[from.get_entity().entity_uuid],
&CSetEquipment::new(from.entity_id().into(), equipment),
);
&je_packet,
&be_mob_equipment,
)
.await;
}
pub async fn send_world_info(