Living Entity: Proper death

Now we wait until despawning the entity so the death animation is played
This commit is contained in:
Alexander Medvedev
2025-02-27 22:51:49 +01:00
parent d827f68826
commit 27f239a612
10 changed files with 139 additions and 29 deletions

View File

@@ -51,7 +51,7 @@ rayon = "1.10"
parking_lot = { version = "0.12", features = ["send_guard"] }
crossbeam = "0.8"
uuid = { version = "1.14", features = ["serde", "v3", "v4"] }
uuid = { version = "1.15", features = ["serde", "v3", "v4"] }
derive_more = { version = "2.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@@ -0,0 +1,60 @@
{
"ADD_SPRINTING_PARTICLES_OR_RESET_SPAWNER_MINECART_SPAWN_DELAY": 1,
"PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES": 3,
"PLAY_ATTACK_SOUND": 4,
"ADD_NEGATIVE_PLAYER_REACTION_PARTICLES": 6,
"ADD_POSITIVE_PLAYER_REACTION_PARTICLES": 7,
"SHAKE_OFF_WATER": 8,
"CONSUME_ITEM": 9,
"SET_SHEEP_EAT_GRASS_TIMER_OR_PRIME_TNT_MINECART": 10,
"LOOK_AT_VILLAGER": 11,
"ADD_VILLAGER_HEART_PARTICLES": 12,
"ADD_VILLAGER_ANGRY_PARTICLES": 13,
"ADD_VILLAGER_HAPPY_PARTICLES": 14,
"ADD_WITCH_PARTICLES": 15,
"PLAY_CURE_ZOMBIE_VILLAGER_SOUND": 16,
"EXPLODE_FIREWORK_CLIENT": 17,
"ADD_BREEDING_PARTICLES": 18,
"RESET_SQUID_THRUST_TIMER": 19,
"PLAY_SPAWN_EFFECTS": 20,
"PLAY_GUARDIAN_ATTACK_SOUND": 21,
"USE_REDUCED_DEBUG_INFO": 22,
"USE_FULL_DEBUG_INFO": 23,
"SET_OP_LEVEL_0": 24,
"SET_OP_LEVEL_1": 25,
"SET_OP_LEVEL_2": 26,
"SET_OP_LEVEL_3": 27,
"SET_OP_LEVEL_4": 28,
"BLOCK_WITH_SHIELD": 29,
"BREAK_SHIELD": 30,
"PULL_HOOKED_ENTITY": 31,
"HIT_ARMOR_STAND": 32,
"STOP_LOOKING_AT_VILLAGER": 34,
"USE_TOTEM_OF_UNDYING": 35,
"ADD_DOLPHIN_HAPPY_VILLAGER_PARTICLES": 38,
"STUN_RAVAGER": 39,
"TAME_OCELOT_FAILED": 40,
"TAME_OCELOT_SUCCESS": 41,
"ADD_SPLASH_PARTICLES": 42,
"CREATE_EATING_PARTICLES": 45,
"ADD_PORTAL_PARTICLES": 46,
"BREAK_MAINHAND": 47,
"BREAK_OFFHAND": 48,
"BREAK_HEAD": 49,
"BREAK_CHEST": 50,
"BREAK_LEGS": 51,
"BREAK_FEET": 52,
"DRIP_HONEY": 53,
"DRIP_RICH_HONEY": 54,
"SWAP_HANDS": 55,
"RESET_WOLF_SHAKE": 56,
"PREPARE_RAM": 58,
"FINISH_RAM": 59,
"ADD_DEATH_PARTICLES": 60,
"EARS_TWITCH": 61,
"SONIC_BOOM": 62,
"START_DIGGING": 63,
"PEEKING": 64,
"BREAK_BODY": 65,
"INVULNERABLE_CREAKING_HIT": 66
}

View File

@@ -40,7 +40,6 @@
"SKELETON_CONVERTS_TO_STRAY": 1048,
"CRAFTER_CRAFTS": 1049,
"CRAFTER_FAILS": 1050,
"field_51787": 1051,
"COMPOSTER_USED": 1500,
"LAVA_EXTINGUISHED": 1501,
"REDSTONE_TORCH_BURNS_OUT": 1502,

View File

@@ -8,6 +8,7 @@ mod biome;
mod chunk_status;
mod damage_type;
mod entity_pose;
mod entity_status;
mod entity_type;
mod fluid;
mod game_event;
@@ -44,6 +45,7 @@ pub fn main() {
write_generated_file(item::build(), "item.rs");
write_generated_file(fluid::build(), "fluid.rs");
write_generated_file(status_effect::build(), "status_effect.rs");
write_generated_file(entity_status::build(), "entity_status.rs");
}
pub fn array_to_tokenstream(array: &[String]) -> TokenStream {

View File

@@ -0,0 +1,27 @@
use std::collections::HashMap;
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/entity_statuses.json");
let events: HashMap<String, u8> =
serde_json::from_str(include_str!("../../assets/entity_statuses.json"))
.expect("Failed to parse entity_statuses.json");
let mut variants = TokenStream::new();
for (event, id) in events.iter() {
let name = format_ident!("{}", event.to_pascal_case());
variants.extend([quote! {
#name = #id,
}]);
}
quote! {
#[repr(u8)]
pub enum EntityStatus {
#variants
}
}
}

View File

@@ -32,6 +32,7 @@ pub mod game_event {
}
pub mod entity {
include!(concat!(env!("OUT_DIR"), "/entity_status.rs"));
include!(concat!(env!("OUT_DIR"), "/status_effect.rs"));
include!(concat!(env!("OUT_DIR"), "/spawn_egg.rs"));
include!(concat!(env!("OUT_DIR"), "/entity_type.rs"));

View File

@@ -28,7 +28,7 @@ dashmap = "6.1"
num-traits = "0.2"
# Compression
flate2 = "1.0"
flate2 = "1.1"
lz4 = "1.28"
zstd = "0.13.3"

View File

@@ -1,18 +1,17 @@
use std::sync::atomic::AtomicU8;
use std::{collections::HashMap, sync::atomic::AtomicI32};
use crate::server::Server;
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_data::entity::EffectType;
use pumpkin_data::entity::{EffectType, EntityStatus};
use pumpkin_data::{damage::DamageType, sound::Sound};
use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::client::play::CHurtAnimation;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::{
client::play::{
CDamageEvent, CEntityStatus, CSetEquipment, EquipmentSlot, MetaDataType, Metadata,
},
client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata},
codec::slot::Slot,
};
use pumpkin_util::math::vector3::Vector3;
@@ -36,6 +35,7 @@ pub struct LivingEntity {
pub last_damage_taken: AtomicCell<f32>,
/// The current health level of the entity.
pub health: AtomicCell<f32>,
pub death_time: AtomicU8,
/// The distance the entity has been falling
pub fall_distance: AtomicCell<f32>,
pub active_effects: Mutex<HashMap<EffectType, Effect>>,
@@ -49,6 +49,7 @@ impl LivingEntity {
last_damage_taken: AtomicCell::new(0.0),
health: AtomicCell::new(20.0),
fall_distance: AtomicCell::new(0.0),
death_time: AtomicU8::new(0),
active_effects: Mutex::new(HashMap::new()),
}
}
@@ -211,22 +212,16 @@ impl LivingEntity {
pub async fn kill(&self) {
self.set_health(0.0).await;
// Spawns death smoke particles
// Plays the death sound
self.entity
.world
.read()
.await
.broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 60))
.send_entity_status(
&self.entity,
EntityStatus::PlayDeathSoundOrAddProjectileHitParticles,
)
.await;
// Plays the death sound and death animation
self.entity
.world
.read()
.await
.broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 3))
.await;
// TODO: wait
self.entity.remove().await;
}
}
@@ -241,6 +236,21 @@ impl EntityBase for LivingEntity {
self.time_until_regen
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
if self.health.load() <= 0.0 {
let time = self
.death_time
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if time >= 20 {
// Spawn Death particles
self.entity
.world
.read()
.await
.send_entity_status(&self.entity, EntityStatus::AddDeathParticles)
.await;
self.entity.remove().await;
}
}
}
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
let world = self.entity.world.read().await;

View File

@@ -13,7 +13,7 @@ use crossbeam::atomic::AtomicCell;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_data::{
damage::DamageType,
entity::{EffectType, EntityType},
entity::{EffectType, EntityStatus, EntityType},
item::Operation,
particle::Particle,
sound::{Sound, SoundCategory},
@@ -24,8 +24,8 @@ use pumpkin_protocol::{
RawPacket, ServerPacket,
bytebuf::packet::Packet,
client::play::{
CAcknowledgeBlockChange, CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus,
CGameEvent, CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate,
CAcknowledgeBlockChange, CActionBar, CCombatDeath, CDisguisedChatMessage, CGameEvent,
CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate,
CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage,
CTitleText, CUnloadChunk, CUpdateMobEffect, GameEvent, MetaDataType, PlayerAction,
},
@@ -613,11 +613,16 @@ impl Player {
/// syncs the players permission level with the client
pub async fn send_permission_lvl_update(&self) {
self.client
.send_packet(&CEntityStatus::new(
self.entity_id(),
24 + self.permission_lvl.load() as i8,
))
let status = match self.permission_lvl.load() {
PermissionLvl::Zero => EntityStatus::SetOpLevel0,
PermissionLvl::One => EntityStatus::SetOpLevel1,
PermissionLvl::Two => EntityStatus::SetOpLevel2,
PermissionLvl::Three => EntityStatus::SetOpLevel3,
PermissionLvl::Four => EntityStatus::SetOpLevel4,
};
self.world()
.await
.send_entity_status(&self.living_entity.entity, status)
.await;
}

View File

@@ -23,7 +23,7 @@ use border::Worldborder;
use explosion::Explosion;
use pumpkin_config::BasicConfiguration;
use pumpkin_data::{
entity::EntityType,
entity::{EntityStatus, EntityType},
particle::Particle,
sound::{Sound, SoundCategory},
world::WorldEvent,
@@ -32,8 +32,8 @@ use pumpkin_macros::send_cancellable;
use pumpkin_protocol::{
ClientPacket,
client::play::{
CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
CSpawnEntity, GameEvent, PlayerAction,
CChunkData, CEntityStatus, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities,
CRemovePlayerInfo, CSpawnEntity, GameEvent, PlayerAction,
},
};
use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier};
@@ -149,6 +149,12 @@ impl World {
self.level.save().await;
}
pub async fn send_entity_status(&self, entity: &Entity, status: EntityStatus) {
// TODO: only nearby
self.broadcast_packet_all(&CEntityStatus::new(entity.entity_id, status as i8))
.await;
}
/// Broadcasts a packet to all connected players within the world.
///
/// Sends the specified packet to every player currently logged in to the world.