Fix damage & death msg & portal delay (#1095)

* fix: damage sound & death message

* fix

* fix

* fix rustfmt

* fix: reset_state

* fix
This commit is contained in:
Liyan Zhao
2025-08-01 16:04:59 +08:00
committed by GitHub
parent 0c1f0b9a33
commit 5ea2f70dba
7 changed files with 109 additions and 69 deletions

View File

@@ -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<DeathMessageType>,
pub death_message_type: DeathMessageType,
pub exhaustion: f32,
pub effects: Option<DamageEffects>,
pub message_id: &'static str,

View File

@@ -20,14 +20,16 @@ impl NetherPortalBlock {
/// Gets the portal delay time based on entity type and gamemode
async fn get_portal_time(world: &Arc<World>, 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,
}

View File

@@ -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

View File

@@ -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<dyn EntityBase>, _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 {

View File

@@ -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

View File

@@ -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<dyn ItemBehaviour>> {
self.items.get(item)
pub fn get_pumpkin_item(&self, item: &Item) -> Option<&dyn ItemBehaviour> {
self.items.get(item).map(|value| &**value)
}
}

View File

@@ -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;