From f49e29aa3f1f07321fcb6718eb821da6ac64e5af Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 12 Aug 2026 21:31:31 +0200 Subject: [PATCH] feat(bedrock): implement Bedrock status effect packet and translation --- crates/pumpkin-data/src/generated/effect.rs | 19 +++++++ .../src/bedrock/client/mob_effect.rs | 49 +++++++++++++++++++ .../src/bedrock/client/mod.rs | 2 + crates/pumpkin/src/entity/living.rs | 20 +++++++- crates/pumpkin/src/world/mod.rs | 33 ++++++++++--- tools/pumpkin-codegen/src/effect.rs | 20 ++++++++ 6 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 crates/pumpkin-protocol/src/bedrock/client/mob_effect.rs diff --git a/crates/pumpkin-data/src/generated/effect.rs b/crates/pumpkin-data/src/generated/effect.rs index 12ff372b0..1974210d0 100644 --- a/crates/pumpkin-data/src/generated/effect.rs +++ b/crates/pumpkin-data/src/generated/effect.rs @@ -419,6 +419,25 @@ impl StatusEffect { attribute_modifiers: &[], }; #[must_use] + pub const fn to_bedrock_id(&self) -> i32 { + match self.id { + 0..=22 => self.id as i32 + 1, + 24 => 24, + 27 => 27, + 28 => 26, + 30 => 28, + 31 => 29, + 32 => 30, + 33 => 31, + 34 => 32, + 35 => 33, + 36 => 34, + 37 => 35, + 38 => 36, + _ => self.id as i32 + 1, + } + } + #[must_use] pub fn from_name(name: &str) -> Option<&'static Self> { match name { "absorption" => Some(&Self::ABSORPTION), diff --git a/crates/pumpkin-protocol/src/bedrock/client/mob_effect.rs b/crates/pumpkin-protocol/src/bedrock/client/mob_effect.rs new file mode 100644 index 000000000..97e664c65 --- /dev/null +++ b/crates/pumpkin-protocol/src/bedrock/client/mob_effect.rs @@ -0,0 +1,49 @@ +use pumpkin_macros::packet; + +use crate::{ + codec::{var_int::VarInt, var_ulong::VarULong}, + serial::PacketWrite, +}; + +#[derive(PacketWrite)] +#[packet(28)] +pub struct CMobEffect { + pub runtime_entity_id: VarULong, + pub event_id: u8, + pub effect_id: VarInt, + pub amplifier: VarInt, + pub particles: bool, + pub duration: VarInt, + pub tick: VarULong, + pub ambient: bool, +} + +impl CMobEffect { + pub const EVENT_ADD: u8 = 1; + pub const EVENT_MODIFY: u8 = 2; + pub const EVENT_REMOVE: u8 = 3; + + #[expect(clippy::too_many_arguments)] + #[must_use] + pub const fn new( + runtime_entity_id: VarULong, + event_id: u8, + effect_id: VarInt, + amplifier: VarInt, + particles: bool, + duration: VarInt, + tick: VarULong, + ambient: bool, + ) -> Self { + Self { + runtime_entity_id, + event_id, + effect_id, + amplifier, + particles, + duration, + tick, + ambient, + } + } +} diff --git a/crates/pumpkin-protocol/src/bedrock/client/mod.rs b/crates/pumpkin-protocol/src/bedrock/client/mod.rs index 6b2116d23..511d77251 100644 --- a/crates/pumpkin-protocol/src/bedrock/client/mod.rs +++ b/crates/pumpkin-protocol/src/bedrock/client/mod.rs @@ -20,6 +20,7 @@ pub mod item_registry; pub mod item_stack_response; pub mod level_chunk; pub mod level_event; +pub mod mob_effect; pub mod mob_equipment; pub mod modal_form_request; pub mod move_actor_absolute; @@ -76,6 +77,7 @@ pub use item_stack_response::*; pub use level_chunk::*; pub use level_event::*; pub use level_sound_event::*; +pub use mob_effect::*; pub use mob_equipment::*; pub use modal_form_request::*; pub use move_actor_absolute::*; diff --git a/crates/pumpkin/src/entity/living.rs b/crates/pumpkin/src/entity/living.rs index 261684dd0..cf7a7edaa 100644 --- a/crates/pumpkin/src/entity/living.rs +++ b/crates/pumpkin/src/entity/living.rs @@ -527,6 +527,7 @@ impl LivingEntity { self.entity.entity_id } + #[expect(clippy::too_many_lines)] pub async fn add_effect(&self, effect: Effect) { // Apply instant effects immediately before storing if effect.effect_type == &StatusEffect::INSTANT_HEALTH { @@ -625,7 +626,7 @@ impl LivingEntity { flag |= 8; } - let packet = CUpdateMobEffect::new( + let je_packet = CUpdateMobEffect::new( self.entity.entity_id.into(), VarInt(i32::from(effect.effect_type.id)), effect.amplifier.into(), @@ -633,7 +634,22 @@ impl LivingEntity { flag, ); - self.entity.world.load().broadcast_packet_all(&packet); + let be_packet = pumpkin_protocol::bedrock::client::CMobEffect::new( + VarULong(self.entity.entity_id as u64), + pumpkin_protocol::bedrock::client::CMobEffect::EVENT_ADD, + VarInt(effect.effect_type.to_bedrock_id()), + VarInt(i32::from(effect.amplifier)), + effect.show_particles, + VarInt(effect.duration), + VarULong(0), + effect.ambient, + ); + + let chunk_pos = self.entity.chunk_pos.load(); + self.entity + .world + .load() + .broadcast_to_chunk_editioned_sync(chunk_pos, &je_packet, &be_packet); } pub async fn remove_effect(&self, effect_type: &'static StatusEffect) -> bool { diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index b31d84b5a..538625633 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -466,14 +466,23 @@ impl World { pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) { let chunk_pos = entity.chunk_pos.load(); - self.broadcast_to_chunk( - chunk_pos, - &CRemoveMobEffect::new(entity.entity_id.into(), VarInt(i32::from(effect_type.id))), + let je_packet = + CRemoveMobEffect::new(entity.entity_id.into(), VarInt(i32::from(effect_type.id))); + let be_packet = pumpkin_protocol::bedrock::client::CMobEffect::new( + VarULong(entity.entity_id as u64), + pumpkin_protocol::bedrock::client::CMobEffect::EVENT_REMOVE, + VarInt(effect_type.to_bedrock_id()), + VarInt(0), + false, + VarInt(0), + VarULong(0), + false, ); + self.broadcast_to_chunk_editioned_sync(chunk_pos, &je_packet, &be_packet); } pub fn send_add_mob_effect(&self, entity: &Entity, effect: &pumpkin_data::potion::Effect) { - // TODO: only nearby + let chunk_pos = entity.chunk_pos.load(); let mut flags: i8 = 0; if effect.ambient { flags |= 0x01; @@ -485,13 +494,25 @@ impl World { flags |= 0x04; } - self.broadcast_packet_all(&CUpdateMobEffect::new( + let je_packet = CUpdateMobEffect::new( VarInt(entity.entity_id), VarInt(i32::from(effect.effect_type.id)), VarInt(i32::from(effect.amplifier)), VarInt(effect.duration), flags, - )); + ); + let be_packet = pumpkin_protocol::bedrock::client::CMobEffect::new( + VarULong(entity.entity_id as u64), + pumpkin_protocol::bedrock::client::CMobEffect::EVENT_ADD, + VarInt(effect.effect_type.to_bedrock_id()), + VarInt(i32::from(effect.amplifier)), + effect.show_particles, + VarInt(effect.duration), + VarULong(0), + effect.ambient, + ); + + self.broadcast_to_chunk_editioned_sync(chunk_pos, &je_packet, &be_packet); } pub fn set_difficulty(&self, difficulty: Difficulty) { diff --git a/tools/pumpkin-codegen/src/effect.rs b/tools/pumpkin-codegen/src/effect.rs index 1caafcd8c..be2db5308 100644 --- a/tools/pumpkin-codegen/src/effect.rs +++ b/tools/pumpkin-codegen/src/effect.rs @@ -157,6 +157,26 @@ pub fn build() -> TokenStream { impl StatusEffect { #variants + #[must_use] + pub const fn to_bedrock_id(&self) -> i32 { + match self.id { + 0..=22 => self.id as i32 + 1, + 24 => 24, + 27 => 27, + 28 => 26, + 30 => 28, + 31 => 29, + 32 => 30, + 33 => 31, + 34 => 32, + 35 => 33, + 36 => 34, + 37 => 35, + 38 => 36, + _ => self.id as i32 + 1, + } + } + #[must_use] pub fn from_name(name: &str) -> Option<&'static Self> { match name {