diff --git a/crates/pumpkin-protocol/src/bedrock/server/actor_event.rs b/crates/pumpkin-protocol/src/bedrock/server/actor_event.rs index 96b4905c0..0afcbb007 100644 --- a/crates/pumpkin-protocol/src/bedrock/server/actor_event.rs +++ b/crates/pumpkin-protocol/src/bedrock/server/actor_event.rs @@ -15,7 +15,7 @@ pub struct SActorEvent { pub fire_at_position: Option>, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum ActorEventType { Jump = 1, diff --git a/crates/pumpkin/src/entity/combat.rs b/crates/pumpkin/src/entity/combat.rs index 9e64777f8..f7df0f07a 100644 --- a/crates/pumpkin/src/entity/combat.rs +++ b/crates/pumpkin/src/entity/combat.rs @@ -13,6 +13,8 @@ use crate::{ world::World, }; +use crate::net::ClientPlatform; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AttackType { Knockback, @@ -38,6 +40,7 @@ impl AttackType { } let sword = held_item.is_sword(); + let is_bedrock = matches!(player.client.as_ref(), ClientPlatform::Bedrock(_)); let is_strong = attack_cooldown_progress > 0.9; if sprinting && is_strong { @@ -48,7 +51,7 @@ impl AttackType { return Self::Critical; } - if sword && is_strong { + if sword && is_strong && !is_bedrock { return Self::Sweeping; } diff --git a/crates/pumpkin/src/entity/decoration/armor_stand.rs b/crates/pumpkin/src/entity/decoration/armor_stand.rs index f51c8e5d7..02feaf041 100644 --- a/crates/pumpkin/src/entity/decoration/armor_stand.rs +++ b/crates/pumpkin/src/entity/decoration/armor_stand.rs @@ -411,7 +411,7 @@ impl EntityBase for ArmorStandEntity { let time = world.level_time.lock().await.query_gametime(); if time - self.last_hit_time.load(Ordering::Relaxed) > 5 && !always_kills { - world.send_entity_status(entity, EntityStatus::ArmorstandWobble); + world.send_entity_status(entity, EntityStatus::ArmorstandWobble, None); world.play_sound( Sound::EntityArmorStandHit, SoundCategory::Neutral, diff --git a/crates/pumpkin/src/entity/living.rs b/crates/pumpkin/src/entity/living.rs index f91ee007a..edbaccfbe 100644 --- a/crates/pumpkin/src/entity/living.rs +++ b/crates/pumpkin/src/entity/living.rs @@ -279,7 +279,23 @@ impl LivingEntity { && item.get_data_component::().is_some() { let use_time = self.item_use_time.load(Ordering::Relaxed); - return item.get_max_use_time() - use_time >= 5; + let required_time = if let Some(dyn_self) = self + .entity + .world + .load() + .get_entity_by_id(self.entity.entity_id) + && let Some(player) = dyn_self + .cast_any() + .downcast_ref::() + && matches!( + player.client.as_ref(), + crate::net::ClientPlatform::Bedrock(_) + ) { + 0 + } else { + 5 + }; + return item.get_max_use_time() - use_time >= required_time; } false } @@ -1355,7 +1371,11 @@ impl LivingEntity { self.update_death_stats(&*dyn_self, cause).await; // Plays the death sound - world.send_entity_status(&self.entity, EntityStatus::Death); + world.send_entity_status( + &self.entity, + EntityStatus::Death, + Some(ActorEventType::Death), + ); let looting_level; let tool = if let Some(cause_ent) = cause { if let Some(player) = cause_ent @@ -1747,10 +1767,11 @@ impl LivingEntity { .insert(slot, stack); } self.set_health(1.0); - self.entity - .world - .load() - .send_entity_status(&self.entity, EntityStatus::ProtectedFromDeath); + self.entity.world.load().send_entity_status( + &self.entity, + EntityStatus::ProtectedFromDeath, + Some(ActorEventType::InstantDeath), + ); // Set Absorption, Regeneration, and Fire Resistance effects self.add_effect(Effect { @@ -1828,8 +1849,11 @@ impl LivingEntity { if slot_result != pumpkin_data::item_stack::DamageResult::Untouched { if slot_result == pumpkin_data::item_stack::DamageResult::Broken { let world = self.entity.world.load(); - world - .send_entity_status(&self.entity, super::equipment_break_status(&slot)); + world.send_entity_status( + &self.entity, + super::equipment_break_status(&slot), + None, + ); } equipment_updates.push((slot.clone(), stack.clone())); if let Some(player) = caller.get_player() { @@ -2358,6 +2382,7 @@ impl EntityBase for LivingEntity { world.send_entity_status( &self.entity, crate::entity::equipment_break_status(&slot), + None, ); *stack = ItemStack::EMPTY.clone(); let broken_stack = stack.clone(); @@ -2759,10 +2784,11 @@ impl EntityBase for LivingEntity { // Only send death particles once (on the exact tick death_time reaches 20) // and then remove the entity, preventing entity_event spam. if time == 20 && !self.entity.removed.swap(true, Ordering::Relaxed) { - self.entity - .world - .load() - .send_entity_status(&self.entity, EntityStatus::Death); + self.entity.world.load().send_entity_status( + &self.entity, + EntityStatus::Death, + Some(ActorEventType::Death), + ); self.entity.remove().await; } } diff --git a/crates/pumpkin/src/entity/passive/animal.rs b/crates/pumpkin/src/entity/passive/animal.rs index c884823fa..455595813 100644 --- a/crates/pumpkin/src/entity/passive/animal.rs +++ b/crates/pumpkin/src/entity/passive/animal.rs @@ -5,6 +5,7 @@ use pumpkin_data::particle::Particle; use pumpkin_data::sound::{Sound, SoundCategory}; use crate::entity::{EntityBaseFuture, mob::Mob, player::Player}; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_util::math::vector3::Vector3; pub trait Animal: Mob { @@ -61,6 +62,7 @@ pub trait Animal: Mob { world.send_entity_status( entity, pumpkin_data::entity::EntityStatus::InLoveHearts, + Some(ActorEventType::InLoveHearts), ); world.spawn_particle( diff --git a/crates/pumpkin/src/entity/passive/villager/mod.rs b/crates/pumpkin/src/entity/passive/villager/mod.rs index 83fc17e69..51910bf5f 100644 --- a/crates/pumpkin/src/entity/passive/villager/mod.rs +++ b/crates/pumpkin/src/entity/passive/villager/mod.rs @@ -18,6 +18,7 @@ use pumpkin_inventory::screen_handler::{ BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, }; use pumpkin_nbt::compound::NbtCompound; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::{CMerchantOffers, Metadata}; use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3}; @@ -282,10 +283,11 @@ impl VillagerEntity { pub fn set_unhappy(&self) { let entity = self.get_entity(); - entity - .world - .load() - .send_entity_status(entity, pumpkin_data::entity::EntityStatus::VillagerAngry); + entity.world.load().send_entity_status( + entity, + pumpkin_data::entity::EntityStatus::VillagerAngry, + Some(ActorEventType::VillagerAngry), + ); entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo); } @@ -379,6 +381,7 @@ impl ScreenHandlerFactory for VillagerEntity { entity.world.load().send_entity_status( entity, pumpkin_data::entity::EntityStatus::VillagerHappy, + Some(ActorEventType::VillagerHappy), ); entity.play_sound( pumpkin_data::sound::Sound::EntityVillagerCelebrate, diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 8418ca1a0..5e690f41f 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -59,8 +59,10 @@ use pumpkin_nbt::tag::NbtTag; use pumpkin_protocol::IdOr; use pumpkin_protocol::SoundEvent; use pumpkin_protocol::bedrock::client::container_open::CContainerOpen; +use pumpkin_protocol::bedrock::server::actor_event::{ActorEventType, SActorEvent}; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::codec::var_long::VarLong; +use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_protocol::java::client::play::{ Animation, CActionBar, CAwardStats, CChangeDifficulty, CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent, @@ -1085,11 +1087,12 @@ impl Player { let attack_speed = base_attack_speed + add_speed; - let attack_cooldown_progress = self.get_attack_cooldown_progress( - f64::from(server.basic_config.tps), - 0.5, - attack_speed, - ); + let is_bedrock = matches!(self.client.as_ref(), ClientPlatform::Bedrock(_)); + let attack_cooldown_progress = if is_bedrock { + 1.0 + } else { + self.get_attack_cooldown_progress(f64::from(server.basic_config.tps), 0.5, attack_speed) + }; self.last_attacked_ticks.store(0, Ordering::Relaxed); // Only reduce attack damage if in cooldown @@ -1349,6 +1352,7 @@ impl Player { self.world().send_entity_status( &self.living_entity.entity, super::equipment_break_status(slot), + None, ); } @@ -2589,7 +2593,7 @@ impl Player { PermissionLvl::Four => EntityStatus::PermissionLevelOwners, }; self.world() - .send_entity_status(&self.living_entity.entity, status); + .send_entity_status(&self.living_entity.entity, status, None); } /// Sets the player's difficulty level. @@ -3250,8 +3254,17 @@ impl Player { self.breath_manager.reset(self); self.client - .send_packet_now(&CCombatDeath::new(self.entity_id().into(), &death_msg)) + .send_packet_now_editioned( + &CCombatDeath::new(self.entity_id().into(), &death_msg), + &SActorEvent { + entity_runtime_id: VarULong(self.entity_id() as u64), + event_type: ActorEventType::Death, + event_data: VarInt(0), + fire_at_position: None, + }, + ) .await; + self.send_health().await; } pub async fn set_gamemode(self: &Arc, gamemode: GameMode) -> bool { diff --git a/crates/pumpkin/src/entity/projectile/egg.rs b/crates/pumpkin/src/entity/projectile/egg.rs index 159b38339..a2155f4f8 100644 --- a/crates/pumpkin/src/entity/projectile/egg.rs +++ b/crates/pumpkin/src/entity/projectile/egg.rs @@ -14,6 +14,7 @@ use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::meta_data_type::MetaDataType; use pumpkin_data::tracked_data::TrackedData; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::java::client::play::Metadata; use pumpkin_util::math::vector3::Vector3; @@ -114,7 +115,11 @@ impl EntityBase for EggEntity { let spawn_pos = hit_pos.add(&normal.multiply(0.5, 0.5, 0.5)); // Play egg break particles - world.send_entity_status(self.get_entity(), EntityStatus::Death); + world.send_entity_status( + self.get_entity(), + EntityStatus::Death, + Some(ActorEventType::Death), + ); // Decide spawn count per probabilities: // r == 0 -> spawn 4 (1/256) diff --git a/crates/pumpkin/src/entity/projectile/ender_pearl.rs b/crates/pumpkin/src/entity/projectile/ender_pearl.rs index 3f564d23f..e1255be4f 100644 --- a/crates/pumpkin/src/entity/projectile/ender_pearl.rs +++ b/crates/pumpkin/src/entity/projectile/ender_pearl.rs @@ -13,6 +13,7 @@ use pumpkin_data::damage::DamageType; use pumpkin_data::entity::{EntityPose, EntityStatus}; use pumpkin_data::particle::Particle; use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_util::math::vector3::Vector3; const GRAVITY: f64 = 0.03; @@ -177,7 +178,7 @@ impl EntityBase for EnderPearlEntity { .await; } - world.send_entity_status(entity, EntityStatus::Death); + world.send_entity_status(entity, EntityStatus::Death, Some(ActorEventType::Death)); }) } } diff --git a/crates/pumpkin/src/entity/projectile/firework_rocket.rs b/crates/pumpkin/src/entity/projectile/firework_rocket.rs index 33e40aaf3..7cf55bb1f 100644 --- a/crates/pumpkin/src/entity/projectile/firework_rocket.rs +++ b/crates/pumpkin/src/entity/projectile/firework_rocket.rs @@ -4,6 +4,7 @@ use crate::{ world::World, }; use pumpkin_data::{entity::EntityStatus, meta_data_type::MetaDataType, tracked_data::TrackedData}; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_protocol::{codec::optional_int::OptionalInt, java::client::play::Metadata}; use pumpkin_util::{ math::vector3::Vector3, @@ -82,7 +83,11 @@ impl FireworkRocketEntity { pub async fn explode_and_remove(&self, world: &World) { let entity = self.get_entity(); - world.send_entity_status(entity, EntityStatus::FireworksExplode); + world.send_entity_status( + entity, + EntityStatus::FireworksExplode, + Some(ActorEventType::FireworksExplode), + ); // TODO: Explode/colors diff --git a/crates/pumpkin/src/entity/projectile/lingering_potion.rs b/crates/pumpkin/src/entity/projectile/lingering_potion.rs index d34d1e4a5..d7854273a 100644 --- a/crates/pumpkin/src/entity/projectile/lingering_potion.rs +++ b/crates/pumpkin/src/entity/projectile/lingering_potion.rs @@ -8,6 +8,7 @@ use crate::{ }; use pumpkin_data::entity::EntityStatus; use pumpkin_data::item_stack::ItemStack; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_protocol::java::client::play::CWorldEvent; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::{Vector2, to_chunk_pos}; @@ -116,7 +117,11 @@ impl EntityBase for LingeringPotionEntity { extinguish_fire_if_water_potion(&world, hit_pos, &stack).await; // Play impact particles - world.send_entity_status(self.get_entity(), EntityStatus::Death); + world.send_entity_status( + self.get_entity(), + EntityStatus::Death, + Some(ActorEventType::Death), + ); // Read stored item stack and compute potion effects let stack = self.item_stack.read().await.clone(); diff --git a/crates/pumpkin/src/entity/projectile/snowball.rs b/crates/pumpkin/src/entity/projectile/snowball.rs index 7070a1c36..cbee6e60c 100644 --- a/crates/pumpkin/src/entity/projectile/snowball.rs +++ b/crates/pumpkin/src/entity/projectile/snowball.rs @@ -8,6 +8,7 @@ use crate::{ }; use pumpkin_data::damage::DamageType; use pumpkin_data::entity::{EntityStatus, EntityType}; +use pumpkin_protocol::bedrock::server::actor_event::ActorEventType; use pumpkin_util::math::vector3::Vector3; const GRAVITY: f64 = 0.03; @@ -72,7 +73,11 @@ impl EntityBase for SnowballEntity { let world = self.get_entity().world.load(); // Always send particle status regardless of what was hit - world.send_entity_status(self.get_entity(), EntityStatus::Death); + world.send_entity_status( + self.get_entity(), + EntityStatus::Death, + Some(ActorEventType::Death), + ); // Handle entity-specific damage if let ProjectileHit::Entity { ref entity, .. } = hit { diff --git a/crates/pumpkin/src/net/java/play.rs b/crates/pumpkin/src/net/java/play.rs index 8f44e48b7..c3b67a1df 100644 --- a/crates/pumpkin/src/net/java/play.rs +++ b/crates/pumpkin/src/net/java/play.rs @@ -2411,9 +2411,11 @@ impl JavaClient { } else { &EquipmentSlot::OFF_HAND }; - player - .world() - .send_entity_status(player.get_entity(), equipment_break_status(slot)); + player.world().send_entity_status( + player.get_entity(), + equipment_break_status(slot), + None, + ); } if !after.are_equal(&before) { diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index 3ac89448f..55c0db89e 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -98,7 +98,10 @@ use pumpkin_protocol::{ start_game::{Experiments, GamePublishSetting, LevelSettings}, update_attributes::{Attribute, CUpdateAttributes}, }, - server::text::SText, + server::{ + actor_event::{ActorEventType, SActorEvent}, + text::SText, + }, }, codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong}, java::{ @@ -406,12 +409,25 @@ impl World { } /// Sends an entity status update to all players tracking the specified entity. - pub fn send_entity_status(&self, entity: &Entity, status: EntityStatus) { + pub fn send_entity_status( + &self, + entity: &Entity, + java_status: EntityStatus, + bedrock_status: Option, + ) { let chunk_pos = entity.chunk_pos.load(); - self.broadcast_to_chunk( - chunk_pos, - &CEntityStatus::new(entity.entity_id, status as i8), - ); + let je_packet = CEntityStatus::new(entity.entity_id, java_status as i8); + if let Some(be_event) = bedrock_status { + let be_packet = SActorEvent { + entity_runtime_id: VarULong(entity.entity_id as u64), + event_type: be_event, + event_data: VarInt(0), + fire_at_position: None, + }; + self.broadcast_to_chunk_editioned_sync(chunk_pos, &je_packet, &be_packet); + } else { + self.broadcast_to_chunk(chunk_pos, &je_packet); + } } pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) {