diff --git a/Cargo.toml b/Cargo.toml index cca8bc8e0..b27a9ebb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/assets/entity_statuses.json b/assets/entity_statuses.json new file mode 100644 index 000000000..1609b8328 --- /dev/null +++ b/assets/entity_statuses.json @@ -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 +} \ No newline at end of file diff --git a/assets/world_event.json b/assets/world_event.json index 9e43ac24b..d4f4df763 100644 --- a/assets/world_event.json +++ b/assets/world_event.json @@ -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, diff --git a/pumpkin-data/build/build.rs b/pumpkin-data/build/build.rs index 5a4ef2bca..c1420d04d 100644 --- a/pumpkin-data/build/build.rs +++ b/pumpkin-data/build/build.rs @@ -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 { diff --git a/pumpkin-data/build/entity_status.rs b/pumpkin-data/build/entity_status.rs new file mode 100644 index 000000000..0b33797a9 --- /dev/null +++ b/pumpkin-data/build/entity_status.rs @@ -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 = + 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 + } + } +} diff --git a/pumpkin-data/src/lib.rs b/pumpkin-data/src/lib.rs index efec08899..a14580a1d 100644 --- a/pumpkin-data/src/lib.rs +++ b/pumpkin-data/src/lib.rs @@ -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")); diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index d831f9691..586393c9e 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -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" diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 827db43a2..bb5ba54e2 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -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, /// The current health level of the entity. pub health: AtomicCell, + pub death_time: AtomicU8, /// The distance the entity has been falling pub fall_distance: AtomicCell, pub active_effects: Mutex>, @@ -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; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 1fd8e50d5..90c1c1356 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -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; } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index a5cdbc5d4..9ecf31b29 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -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.