diff --git a/pumpkin-data/build/entity_type.rs b/pumpkin-data/build/entity_type.rs index bdb9747cd..016db2760 100644 --- a/pumpkin-data/build/entity_type.rs +++ b/pumpkin-data/build/entity_type.rs @@ -230,7 +230,7 @@ pub(crate) fn build() -> TokenStream { impl Taggable for EntityType { #[inline] fn tag_key() -> RegistryKey { - RegistryKey::Block + RegistryKey::EntityType } #[inline] diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index d6b715ba8..e45dc759e 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -15,6 +15,7 @@ use pumpkin_data::block_properties::{EnumVariants, Integer0To15}; use pumpkin_data::dimension::Dimension; use pumpkin_data::fluid::Fluid; use pumpkin_data::meta_data_type::MetaDataType; +use pumpkin_data::tag::Taggable; use pumpkin_data::tracked_data::TrackedData; use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::{ @@ -365,6 +366,9 @@ pub struct Entity { pub damage_immunities: Vec, pub fire_ticks: AtomicI32, pub has_visual_fire: AtomicBool, + /// The number of ticks the entity has been frozen (in powder snow) + /// Max is 140 ticks (7 seconds). Increases by 1/tick in powder snow, decreases by 2/tick outside. + pub frozen_ticks: AtomicI32, pub removal_reason: AtomicCell>, // The passengers that entity has pub passengers: Mutex>>, @@ -461,6 +465,7 @@ impl Entity { data: AtomicI32::new(0), fire_ticks: AtomicI32::new(-1), has_visual_fire: AtomicBool::new(false), + frozen_ticks: AtomicI32::new(0), removal_reason: AtomicCell::new(None), passengers: Mutex::new(Vec::new()), vehicle: Mutex::new(None), @@ -1578,6 +1583,66 @@ impl Entity { // TODO: defrost } + /// Maximum freeze ticks (7 seconds at 20 tps) + pub const MAX_FROZEN_TICKS: i32 = 140; + + /// Freeze damage is dealt every 40 ticks when fully frozen + const FREEZE_DAMAGE_INTERVAL: i32 = 40; + + /// Check if the entity is currently in powder snow + pub async fn is_in_powder_snow(&self) -> bool { + let block_pos = self.block_pos.load(); + self.world.load().get_block(&block_pos).await == &Block::POWDER_SNOW + } + + /// Check if this entity type is immune to freezing + /// Uses the `minecraft:freeze_immune_entity_types` tag + pub fn is_freeze_immune(&self) -> bool { + self.entity_type + .is_tagged_with("minecraft:freeze_immune_entity_types") + .unwrap_or(false) + } + + /// Ticks the frozen state of the entity. + /// In powder snow: `frozen_ticks` increases by 1 (up to `MAX_FROZEN_TICKS`) + /// Outside powder snow: `frozen_ticks` decreases by 2 (down to 0) + /// When fully frozen, deals 1 damage every 40 ticks + pub async fn tick_frozen(&self, caller: &dyn EntityBase) { + // Freeze-immune entities don't accumulate freeze ticks + if self.is_freeze_immune() { + return; + } + + let in_powder_snow = self.is_in_powder_snow().await; + let old_frozen_ticks = self.frozen_ticks.load(Ordering::Relaxed); + + let new_frozen_ticks = if in_powder_snow { + // Increase frozen ticks when in powder snow + (old_frozen_ticks + 1).min(Self::MAX_FROZEN_TICKS) + } else { + // Decrease frozen ticks when not in powder snow (2x faster thaw rate) + (old_frozen_ticks - 2).max(0) + }; + + // Only update and send metadata if the value changed + if new_frozen_ticks != old_frozen_ticks { + self.frozen_ticks.store(new_frozen_ticks, Ordering::Relaxed); + self.send_meta_data(&[Metadata::new( + TrackedData::DATA_FROZEN_TICKS, + MetaDataType::Integer, + VarInt(new_frozen_ticks), + )]) + .await; + } + + // Deal freeze damage when fully frozen (every 40 ticks) + if new_frozen_ticks >= Self::MAX_FROZEN_TICKS + && self.age.load(Ordering::Relaxed) % Self::FREEZE_DAMAGE_INTERVAL == 0 + { + caller.damage(caller, 1.0, DamageType::FREEZE).await; + } + } + /// Sets the `Entity` yaw & pitch rotation pub fn set_rotation(&self, yaw: f32, pitch: f32) { // TODO @@ -2102,7 +2167,9 @@ impl EntityBase for Entity { } self.set_on_fire(self.fire_ticks.load(Ordering::Relaxed) > 0) .await; - // TODO: Tick + + // Tick freeze state (powder snow) + self.tick_frozen(&*caller).await; }) }