mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix(entity): consolidate hurt sound parity for slime and shared dispatch families (#1935)
* fix(entity): derive hurt sound parity from generated mapping Keep slime hurt sounds in runtime because they depend on entity size.\n\nMove enderman and undead-family hurt sound mappings into generated data so living.rs dispatches through a shared lookup instead of owning a hardcoded table. * refactor(entity): derive hurt sound lookup from entities data The Extractor companion now emits hurt_sound through entities.json. Keep Pumpkin's hurt_sound_for_entity_type helper narrow and generated while moving its source of truth to entity data. Slime remains a runtime special case because its hurt sound depends on size rather than static entity metadata. * refactor(entity): fold hurt sound helper into entity codegen Move hurt-sound helper generation into the existing entity codegen path. Remove the standalone helper builder and generated file now that hurt_sound is sourced from entities.json. Runtime dispatch stays unchanged and slime remains a runtime size-based exception. * refactor(entity): inline hurt sound into EntityType Remove the standalone entity_hurt_sound feature and helper path. Hurt sounds now live directly on generated EntityType values, while runtime behavior stays the same and slime remains the size-based runtime exception.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
use std::{collections::BTreeMap, fs};
|
||||
|
||||
use heck::ToPascalCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use pumpkin_util::HeightMap;
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
@@ -14,6 +15,8 @@ pub struct EntityType {
|
||||
/// Numeric registry ID for this entity type.
|
||||
pub id: u16,
|
||||
pub attributes: Option<Vec<BTreeMap<String, f64>>>,
|
||||
/// Static hurt sound event name when it is safely derivable from extracted entity data.
|
||||
pub hurt_sound: Option<String>,
|
||||
/// Whether this entity can be attacked by players or other entities.
|
||||
pub attackable: Option<bool>,
|
||||
/// Whether this entity is classified as a mob (affects spawning mechanics).
|
||||
@@ -110,6 +113,13 @@ impl ToTokens for NamedEntityType<'_> {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
let hurt_sound = if let Some(sound_name) = entity.hurt_sound.as_ref() {
|
||||
let sound_ident = format_ident!("{}", sound_name.to_pascal_case());
|
||||
quote! { Some(Sound::#sound_ident) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
let spawn_restriction_location = match entity.spawn_restriction.location {
|
||||
SpawnLocation::InLava => quote! {SpawnLocation::InLava},
|
||||
SpawnLocation::InWater => quote! {SpawnLocation::InWater},
|
||||
@@ -174,6 +184,7 @@ impl ToTokens for NamedEntityType<'_> {
|
||||
EntityType {
|
||||
id: #id,
|
||||
attributes: #attributes_field,
|
||||
hurt_sound: #hurt_sound,
|
||||
attackable: #attackable,
|
||||
mob: #mob,
|
||||
saveable: #saveable,
|
||||
@@ -227,6 +238,7 @@ pub fn build() -> TokenStream {
|
||||
use crate::tag::Taggable;
|
||||
use crate::tag::RegistryKey;
|
||||
use crate::attributes::Attributes;
|
||||
use crate::sound::Sound;
|
||||
use pumpkin_util::loot_table::*;
|
||||
use pumpkin_util::HeightMap;
|
||||
use std::hash::Hash;
|
||||
@@ -235,6 +247,7 @@ pub fn build() -> TokenStream {
|
||||
pub struct EntityType {
|
||||
pub id: u16,
|
||||
pub attributes: &'static [(Attributes, f64)],
|
||||
pub hurt_sound: Option<Sound>,
|
||||
pub attackable: Option<bool>,
|
||||
pub mob: bool,
|
||||
pub saveable: bool,
|
||||
@@ -399,6 +412,7 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IDSetContent for EntityType {
|
||||
fn registry_id(&self) -> u16 {
|
||||
Taggable::registry_id(self)
|
||||
|
||||
@@ -82,7 +82,7 @@ biome = []
|
||||
chunk_status = []
|
||||
entity_pose = []
|
||||
entity_status = []
|
||||
entity_type = []
|
||||
entity_type = ["sound"]
|
||||
spawn_egg = []
|
||||
world_event = []
|
||||
message_type = []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ use crate::block::OnLandedUponArgs;
|
||||
use crate::entity::attributes::AttributeInstance;
|
||||
use crate::entity::attributes::Modifier;
|
||||
use crate::entity::attributes::ModifierOperation;
|
||||
use crate::entity::mob::slime::SlimeEntity;
|
||||
use crate::entity::{EntityBaseFuture, NbtFuture};
|
||||
use crate::server::Server;
|
||||
use crate::world::loot::{LootContextParameters, LootTableExt};
|
||||
@@ -126,6 +127,10 @@ impl LivingEntity {
|
||||
&Block::SLIME_BLOCK,
|
||||
];
|
||||
|
||||
fn hurt_sound_for_entity(entity_type: &'static EntityType) -> Sound {
|
||||
entity_type.hurt_sound.unwrap_or(Sound::EntityGenericHurt)
|
||||
}
|
||||
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
let water_movement_speed_multiplier = if entity.entity_type == &EntityType::POLAR_BEAR {
|
||||
0.98
|
||||
@@ -1739,6 +1744,14 @@ impl LivingEntity {
|
||||
pub fn get_movement(&self) -> Vector3<f64> {
|
||||
self.entity.movement.load()
|
||||
}
|
||||
|
||||
fn hurt_sound(&self) -> Sound {
|
||||
if self.entity.entity_type == &EntityType::SLIME {
|
||||
SlimeEntity::hurt_sound_for_size(self.entity.data.load(Relaxed))
|
||||
} else {
|
||||
Self::hurt_sound_for_entity(self.entity.entity_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for LivingEntity {
|
||||
@@ -1938,7 +1951,7 @@ impl EntityBase for LivingEntity {
|
||||
if play_sound {
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityGenericHurt,
|
||||
self.hurt_sound(),
|
||||
SoundCategory::Players,
|
||||
&self.entity.pos.load(),
|
||||
)
|
||||
@@ -2343,4 +2356,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hurt_sound_for_entity_uses_zombie_family_sounds() {
|
||||
let cases = [
|
||||
(&EntityType::ZOMBIE, Sound::EntityZombieHurt),
|
||||
(&EntityType::DROWNED, Sound::EntityDrownedHurt),
|
||||
(&EntityType::HUSK, Sound::EntityHuskHurt),
|
||||
(
|
||||
&EntityType::ZOMBIE_VILLAGER,
|
||||
Sound::EntityZombieVillagerHurt,
|
||||
),
|
||||
];
|
||||
|
||||
for (entity_type, expected) in cases {
|
||||
assert_eq!(LivingEntity::hurt_sound_for_entity(entity_type), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hurt_sound_for_entity_uses_enderman_hurt_sound() {
|
||||
assert_eq!(
|
||||
LivingEntity::hurt_sound_for_entity(&EntityType::ENDERMAN),
|
||||
Sound::EntityEndermanHurt
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hurt_sound_for_entity_uses_skeleton_family_sounds() {
|
||||
let cases = [
|
||||
(&EntityType::SKELETON, Sound::EntitySkeletonHurt),
|
||||
(&EntityType::BOGGED, Sound::EntityBoggedHurt),
|
||||
(&EntityType::PARCHED, Sound::EntityParchedHurt),
|
||||
(
|
||||
&EntityType::WITHER_SKELETON,
|
||||
Sound::EntityWitherSkeletonHurt,
|
||||
),
|
||||
(&EntityType::STRAY, Sound::EntityStrayHurt),
|
||||
];
|
||||
|
||||
for (entity_type, expected) in cases {
|
||||
assert_eq!(LivingEntity::hurt_sound_for_entity(entity_type), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hurt_sound_for_entity_defaults_to_generic_hurt() {
|
||||
assert_eq!(
|
||||
LivingEntity::hurt_sound_for_entity(&EntityType::CREEPER),
|
||||
Sound::EntityGenericHurt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub mod creeper;
|
||||
pub mod enderman;
|
||||
pub mod silverfish;
|
||||
pub mod skeleton;
|
||||
pub mod slime;
|
||||
pub mod zombie;
|
||||
|
||||
pub struct MobEntity {
|
||||
|
||||
64
pumpkin/src/entity/mob/slime.rs
Normal file
64
pumpkin/src/entity/mob/slime.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use pumpkin_data::sound::Sound;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
|
||||
use crate::entity::{
|
||||
Entity, NBTStorage, NbtFuture,
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
|
||||
pub struct SlimeEntity {
|
||||
entity: Arc<MobEntity>,
|
||||
}
|
||||
|
||||
impl SlimeEntity {
|
||||
pub fn new(entity: Entity) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
entity: Arc::new(MobEntity::new(entity)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) const fn hurt_sound_for_size(size: i32) -> Sound {
|
||||
if size == 1 {
|
||||
Sound::EntitySlimeHurtSmall
|
||||
} else {
|
||||
Sound::EntitySlimeHurt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for SlimeEntity {
|
||||
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.entity.living_entity.entity.read_nbt_non_mut(nbt).await;
|
||||
self.entity
|
||||
.living_entity
|
||||
.entity
|
||||
.data
|
||||
.store(nbt.get_int("Size").unwrap_or(0), Relaxed);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Mob for SlimeEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.entity
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn uses_small_hurt_sound_only_for_smallest_slimes() {
|
||||
assert_eq!(
|
||||
SlimeEntity::hurt_sound_for_size(1),
|
||||
Sound::EntitySlimeHurtSmall
|
||||
);
|
||||
assert_eq!(SlimeEntity::hurt_sound_for_size(0), Sound::EntitySlimeHurt);
|
||||
assert_eq!(SlimeEntity::hurt_sound_for_size(2), Sound::EntitySlimeHurt);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use crate::{
|
||||
bogged::BoggedSkeletonEntity, parched::ParchedSkeletonEntity,
|
||||
skeleton::SkeletonEntity, stray::StraySkeletonEntity, wither::WitherSkeletonEntity,
|
||||
},
|
||||
slime::SlimeEntity,
|
||||
zombie::{drowned::DrownedEntity, husk::HuskEntity, zombie::ZombieEntity},
|
||||
},
|
||||
passive::{
|
||||
@@ -80,6 +81,7 @@ pub async fn from_type(
|
||||
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),
|
||||
id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)),
|
||||
id if id == EntityType::SILVERFISH.id => SilverfishEntity::new(entity).await,
|
||||
id if id == EntityType::SLIME.id => SlimeEntity::new(entity),
|
||||
// Fallback Entity
|
||||
_ => {
|
||||
if entity_type.attributes.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user