From 5ea2f70dba90db5b74db667ca4b6a445ebc8bb5c Mon Sep 17 00:00:00 2001 From: Liyan Zhao Date: Fri, 1 Aug 2025 16:04:59 +0800 Subject: [PATCH] Fix damage & death msg & portal delay (#1095) * fix: damage sound & death message * fix * fix * fix rustfmt * fix: reset_state * fix --- pumpkin-data/build/damage_type.rs | 6 +- pumpkin/src/block/blocks/nether_portal.rs | 8 +- pumpkin/src/entity/living.rs | 118 ++++++++++++++-------- pumpkin/src/entity/mod.rs | 11 +- pumpkin/src/entity/player.rs | 27 +++-- pumpkin/src/item/registry.rs | 6 +- pumpkin/src/world/mod.rs | 2 + 7 files changed, 109 insertions(+), 69 deletions(-) diff --git a/pumpkin-data/build/damage_type.rs b/pumpkin-data/build/damage_type.rs index 49ca40f06..6fb664ed2 100644 --- a/pumpkin-data/build/damage_type.rs +++ b/pumpkin-data/build/damage_type.rs @@ -69,9 +69,9 @@ pub(crate) fn build() -> TokenStream { let death_message_type = match &data.death_message_type { Some(msg) => { let msg_ident = Ident::new(&format!("{msg:?}"), proc_macro2::Span::call_site()); - quote! { Some(DeathMessageType::#msg_ident) } + quote! { DeathMessageType::#msg_ident } } - None => quote! { None }, + None => quote! { DeathMessageType::Default }, }; let effects = match &data.effects { @@ -106,7 +106,7 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Clone, Copy, Debug, PartialEq)] pub struct DamageType { - pub death_message_type: Option, + pub death_message_type: DeathMessageType, pub exhaustion: f32, pub effects: Option, pub message_id: &'static str, diff --git a/pumpkin/src/block/blocks/nether_portal.rs b/pumpkin/src/block/blocks/nether_portal.rs index 65325c0c6..3cdcb483a 100644 --- a/pumpkin/src/block/blocks/nether_portal.rs +++ b/pumpkin/src/block/blocks/nether_portal.rs @@ -20,14 +20,16 @@ impl NetherPortalBlock { /// Gets the portal delay time based on entity type and gamemode async fn get_portal_time(world: &Arc, entity: &dyn EntityBase) -> u32 { let entity_type = entity.get_entity().entity_type; - + let level_info = world.level_info.read().await; match entity_type.id { id if id == EntityType::PLAYER.id => (world .get_player_by_id(entity.get_entity().entity_id) .await) .map_or(80, |player| match player.gamemode.load() { - GameMode::Creative => 0, - _ => 80, + GameMode::Creative => { + level_info.game_rules.players_nether_portal_creative_delay as u32 + } + _ => level_info.game_rules.players_nether_portal_default_delay as u32, }), _ => 0, } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 768693147..1243d201c 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -1,7 +1,6 @@ -use core::f32; use pumpkin_data::potion::Effect; use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::AtomicBool; use std::sync::atomic::{AtomicU8, Ordering::Relaxed}; use std::{collections::HashMap, sync::atomic::AtomicI32}; @@ -17,6 +16,7 @@ use pumpkin_data::damage::DeathMessageType; use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType}; +use pumpkin_data::sound::SoundCategory; use pumpkin_data::{damage::DamageType, sound::Sound}; use pumpkin_inventory::entity_equipment::EntityEquipment; use pumpkin_nbt::tag::NbtTag; @@ -231,7 +231,46 @@ impl LivingEntity { /// Kills the Entity pub async fn kill(&self) { - self.damage(f32::MAX, DamageType::OUT_OF_WORLD).await; + self.damage(f32::MAX, DamageType::GENERIC_KILL).await; + } + + pub async fn get_death_message( + dyn_self: &dyn EntityBase, + damage_type: DamageType, + source: Option<&dyn EntityBase>, + cause: Option<&dyn EntityBase>, + ) -> TextComponent { + match damage_type.death_message_type { + DeathMessageType::Default => { + if cause.is_some() && source.is_some() { + TextComponent::translate( + format!("death.attack.{}.player", damage_type.message_id), + [ + dyn_self.get_display_name().await, + cause.unwrap().get_display_name().await, + ], + ) + } else { + TextComponent::translate( + format!("death.attack.{}", damage_type.message_id), + [dyn_self.get_display_name().await], + ) + } + } + DeathMessageType::FallVariants => { + //TODO + TextComponent::translate( + "death.fell.accident.generic", + [dyn_self.get_display_name().await], + ) + } + DeathMessageType::IntentionalGameDesign => TextComponent::text("[") + .add_child(TextComponent::translate( + format!("death.attack.{}.message", damage_type.message_id), + [dyn_self.get_display_name().await], + )) + .add_child(TextComponent::text("]")), + } } pub async fn on_death( @@ -269,45 +308,8 @@ impl LivingEntity { let game_rules = &level_info.game_rules; if self.entity.entity_type == &EntityType::PLAYER && game_rules.show_death_messages { //TODO: KillCredit - let death_message = if let Some(death_message_type) = damage_type.death_message_type - { - match death_message_type { - DeathMessageType::Default => { - if cause.is_some() && source.is_some() { - TextComponent::translate( - format!("death.attack.{}.player", damage_type.message_id), - [ - dyn_self.get_display_name().await, - cause.unwrap().get_display_name().await, - ], - ) - } else { - TextComponent::translate( - format!("death.attack.{}", damage_type.message_id), - [dyn_self.get_display_name().await], - ) - } - } - DeathMessageType::FallVariants => { - //TODO - TextComponent::translate( - "death.fell.accident.generic", - [dyn_self.get_display_name().await], - ) - } - DeathMessageType::IntentionalGameDesign => TextComponent::text("[") - .add_child(TextComponent::translate( - format!("death.attack.{}.message", damage_type.message_id), - [dyn_self.get_display_name().await], - )) - .add_child(TextComponent::text("]")), - } - } else { - TextComponent::translate( - "death.attack.generic", - [dyn_self.get_display_name().await], - ) - }; + let death_message = + Self::get_death_message(&*dyn_self, damage_type, source, cause).await; if let Some(server) = world.server.upgrade() { for player in server.get_all_players().await { player.send_system_message(&death_message).await; @@ -361,6 +363,16 @@ impl LivingEntity { pub fn is_part_of_game(&self) -> bool { self.is_spectator() && self.entity.is_alive() } + + pub async fn reset_state(&self) { + self.entity.reset_state().await; + self.hurt_cooldown.store(0, Relaxed); + self.last_damage_taken.store(0f32); + self.entity.portal_cooldown.store(0, Relaxed); + *self.entity.portal_manager.lock().await = None; + self.fall_distance.store(0f32); + self.dead.store(false, Relaxed); + } } impl LivingEntityTrait for LivingEntity {} @@ -397,13 +409,16 @@ impl EntityBase for LivingEntity { let world = self.entity.world.read().await; let last_damage = self.last_damage_taken.load(); + let play_sound; let mut damage_amount = if self.hurt_cooldown.load(Relaxed) > 10 { if amount <= last_damage { return false; } + play_sound = false; amount - self.last_damage_taken.load() } else { self.hurt_cooldown.store(20, Relaxed); + play_sound = true; amount }; self.last_damage_taken.store(amount); @@ -431,11 +446,26 @@ impl EntityBase for LivingEntity { )) .await; + if play_sound { + self.entity + .world + .read() + .await + .play_sound( + // Sound::EntityPlayerHurt, + Sound::EntityGenericHurt, + SoundCategory::Players, + &self.entity.pos.load(), + ) + .await; + // todo: calculate knockback + } + let new_health = self.health.load() - damage_amount; if damage_amount > 0.0 { self.on_actually_hurt(damage_amount, damage_type).await; + self.set_health(new_health).await; } - self.set_health(new_health).await; if new_health <= 0.0 { self.on_death(damage_type, source, cause).await; @@ -452,7 +482,7 @@ impl EntityBase for LivingEntity { self.hurt_cooldown.fetch_sub(1, Relaxed); } if self.health.load() <= 0.0 { - let time = self.death_time.fetch_add(1, Ordering::Relaxed); + let time = self.death_time.fetch_add(1, Relaxed); if time == 20 { // Spawn Death particles self.entity diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index e323abcf0..0a3d711e3 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -946,14 +946,20 @@ impl Entity { vehicle.is_some() } - pub async fn check_out_of_world(&self) { + pub async fn check_out_of_world(&self, dyn_self: &dyn EntityBase) { if self.pos.load().y < f64::from(self.world.read().await.generation_settings().shape.min_y) - 64.0 { // Tick out of world damage - self.damage(4.0, DamageType::OUT_OF_WORLD).await; + dyn_self.damage(4.0, DamageType::OUT_OF_WORLD).await; } } + + #[allow(clippy::unused_async)] + pub async fn reset_state(&self) { + self.pose.store(EntityPose::Standing); + self.fall_flying.store(false, Relaxed); + } } #[async_trait] @@ -971,6 +977,7 @@ impl EntityBase for Entity { async fn tick(&self, caller: Arc, _server: &Server) { self.tick_portal(&caller).await; + self.check_out_of_world(&*caller).await; let fire_ticks = self.fire_ticks.load(Ordering::Relaxed); if fire_ticks > 0 { if self.entity_type.fire_immune { diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 5bcf25bcb..f29bc86ed 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1288,16 +1288,12 @@ impl Player { pub async fn kill(&self) { self.living_entity.kill().await; - self.handle_killed().await; } - async fn handle_killed(&self) { + async fn handle_killed(&self, death_msg: TextComponent) { self.set_client_loaded(false); self.client - .send_packet_now(&CCombatDeath::new( - self.entity_id().into(), - &TextComponent::text("noob"), - )) + .send_packet_now(&CCombatDeath::new(self.entity_id().into(), &death_msg)) .await; } @@ -1901,6 +1897,10 @@ impl Player { .await; } } + + pub async fn reset_state(&self) { + self.living_entity.reset_state().await; + } } #[async_trait] @@ -2037,14 +2037,11 @@ impl EntityBase for Player { if self.abilities.lock().await.invulnerable { return false; } - self.world() + let world = self.living_entity.entity.world.read().await; + let dyn_self = world + .get_entity_by_id(self.living_entity.entity.entity_id) .await - .play_sound( - Sound::EntityPlayerHurt, - SoundCategory::Players, - &self.living_entity.entity.pos.load(), - ) - .await; + .expect("Entity not found in world"); let result = self .living_entity .damage_with_context(amount, damage_type, position, source, cause) @@ -2052,7 +2049,9 @@ impl EntityBase for Player { if result { let health = self.living_entity.health.load(); if health <= 0.0 { - self.handle_killed().await; + let death_message = + LivingEntity::get_death_message(&*dyn_self, damage_type, source, cause).await; + self.handle_killed(death_message).await; } } result diff --git a/pumpkin/src/item/registry.rs b/pumpkin/src/item/registry.rs index 76732eba8..16c031341 100644 --- a/pumpkin/src/item/registry.rs +++ b/pumpkin/src/item/registry.rs @@ -41,7 +41,7 @@ impl ItemRegistry { ) { let pumpkin_item = self.get_pumpkin_item(item); if let Some(pumpkin_item) = pumpkin_item { - return pumpkin_item + pumpkin_item .use_on_block(item, player, location, face, block, server) .await; } @@ -56,7 +56,7 @@ impl ItemRegistry { } #[must_use] - pub fn get_pumpkin_item(&self, item: &Item) -> Option<&Arc> { - self.items.get(item) + pub fn get_pumpkin_item(&self, item: &Item) -> Option<&dyn ItemBehaviour> { + self.items.get(item).map(|value| &**value) } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 55b6c9a36..4e8f8e1f0 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1464,6 +1464,8 @@ impl World { )) .await; + player.reset_state().await; + log::debug!("Sending player abilities to {}", player.gameprofile.name); player.send_abilities_update().await;