feat(bedrock): implement Bedrock status effect packet and translation

This commit is contained in:
Alexander Medvedev 2026-08-12 21:31:31 +02:00
parent be279fa274
commit f49e29aa3f
6 changed files with 135 additions and 8 deletions

View File

@ -419,6 +419,25 @@ impl StatusEffect {
attribute_modifiers: &[], attribute_modifiers: &[],
}; };
#[must_use] #[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> { pub fn from_name(name: &str) -> Option<&'static Self> {
match name { match name {
"absorption" => Some(&Self::ABSORPTION), "absorption" => Some(&Self::ABSORPTION),

View File

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

View File

@ -20,6 +20,7 @@ pub mod item_registry;
pub mod item_stack_response; pub mod item_stack_response;
pub mod level_chunk; pub mod level_chunk;
pub mod level_event; pub mod level_event;
pub mod mob_effect;
pub mod mob_equipment; pub mod mob_equipment;
pub mod modal_form_request; pub mod modal_form_request;
pub mod move_actor_absolute; pub mod move_actor_absolute;
@ -76,6 +77,7 @@ pub use item_stack_response::*;
pub use level_chunk::*; pub use level_chunk::*;
pub use level_event::*; pub use level_event::*;
pub use level_sound_event::*; pub use level_sound_event::*;
pub use mob_effect::*;
pub use mob_equipment::*; pub use mob_equipment::*;
pub use modal_form_request::*; pub use modal_form_request::*;
pub use move_actor_absolute::*; pub use move_actor_absolute::*;

View File

@ -527,6 +527,7 @@ impl LivingEntity {
self.entity.entity_id self.entity.entity_id
} }
#[expect(clippy::too_many_lines)]
pub async fn add_effect(&self, effect: Effect) { pub async fn add_effect(&self, effect: Effect) {
// Apply instant effects immediately before storing // Apply instant effects immediately before storing
if effect.effect_type == &StatusEffect::INSTANT_HEALTH { if effect.effect_type == &StatusEffect::INSTANT_HEALTH {
@ -625,7 +626,7 @@ impl LivingEntity {
flag |= 8; flag |= 8;
} }
let packet = CUpdateMobEffect::new( let je_packet = CUpdateMobEffect::new(
self.entity.entity_id.into(), self.entity.entity_id.into(),
VarInt(i32::from(effect.effect_type.id)), VarInt(i32::from(effect.effect_type.id)),
effect.amplifier.into(), effect.amplifier.into(),
@ -633,7 +634,22 @@ impl LivingEntity {
flag, 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 { pub async fn remove_effect(&self, effect_type: &'static StatusEffect) -> bool {

View File

@ -466,14 +466,23 @@ impl World {
pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) { pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) {
let chunk_pos = entity.chunk_pos.load(); let chunk_pos = entity.chunk_pos.load();
self.broadcast_to_chunk( let je_packet =
chunk_pos, CRemoveMobEffect::new(entity.entity_id.into(), VarInt(i32::from(effect_type.id)));
&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) { 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; let mut flags: i8 = 0;
if effect.ambient { if effect.ambient {
flags |= 0x01; flags |= 0x01;
@ -485,13 +494,25 @@ impl World {
flags |= 0x04; flags |= 0x04;
} }
self.broadcast_packet_all(&CUpdateMobEffect::new( let je_packet = CUpdateMobEffect::new(
VarInt(entity.entity_id), VarInt(entity.entity_id),
VarInt(i32::from(effect.effect_type.id)), VarInt(i32::from(effect.effect_type.id)),
VarInt(i32::from(effect.amplifier)), VarInt(i32::from(effect.amplifier)),
VarInt(effect.duration), VarInt(effect.duration),
flags, 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) { pub fn set_difficulty(&self, difficulty: Difficulty) {

View File

@ -157,6 +157,26 @@ pub fn build() -> TokenStream {
impl StatusEffect { impl StatusEffect {
#variants #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] #[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> { pub fn from_name(name: &str) -> Option<&'static Self> {
match name { match name {