mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(bedrock): death animation
This commit is contained in:
@@ -15,7 +15,7 @@ pub struct SActorEvent {
|
||||
pub fire_at_position: Option<Vector3<f32>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum ActorEventType {
|
||||
Jump = 1,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -279,7 +279,23 @@ impl LivingEntity {
|
||||
&& item.get_data_component::<BlocksAttacksImpl>().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::<crate::entity::player::Player>()
|
||||
&& 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Self>, gamemode: GameMode) -> bool {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<ActorEventType>,
|
||||
) {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user