From ee32c60427115f53dfd1dd27a8c1ecae6ad3def1 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 17 May 2025 10:35:10 +0200 Subject: [PATCH] Implement fire/lava damage Entities entering fire or lava now take damage and are extinguished upon entering water. --- pumpkin/src/block/blocks/fire/fire.rs | 28 ++++++++ .../flowing_fluid.rs => fluid/flowing.rs} | 0 pumpkin/src/block/{fluids => fluid}/lava.rs | 11 +++- pumpkin/src/block/{fluids => fluid}/mod.rs | 2 +- pumpkin/src/block/{fluids => fluid}/water.rs | 8 ++- pumpkin/src/block/mod.rs | 6 +- pumpkin/src/block/pumpkin_fluid.rs | 4 +- pumpkin/src/block/registry.rs | 7 ++ pumpkin/src/entity/mod.rs | 66 ++++++++++++++++++- pumpkin/src/server/mod.rs | 1 - 10 files changed, 122 insertions(+), 11 deletions(-) rename pumpkin/src/block/{fluids/flowing_fluid.rs => fluid/flowing.rs} (100%) rename pumpkin/src/block/{fluids => fluid}/lava.rs (80%) rename pumpkin/src/block/{fluids => fluid}/mod.rs (58%) rename pumpkin/src/block/{fluids => fluid}/water.rs (86%) diff --git a/pumpkin/src/block/blocks/fire/fire.rs b/pumpkin/src/block/blocks/fire/fire.rs index 302810342..22e491ce7 100644 --- a/pumpkin/src/block/blocks/fire/fire.rs +++ b/pumpkin/src/block/blocks/fire/fire.rs @@ -1,7 +1,9 @@ use pumpkin_data::block_properties::HorizontalAxis; +use pumpkin_data::entity::EntityType; use pumpkin_registry::DimensionType; use rand::Rng; use std::sync::Arc; +use std::sync::atomic::Ordering; use async_trait::async_trait; use pumpkin_data::{Block, BlockState}; @@ -13,6 +15,7 @@ use pumpkin_world::block::BlockDirection; use pumpkin_world::chunk::TickPriority; use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::EntityBase; use crate::entity::player::Player; use crate::server::Server; use crate::world::World; @@ -65,6 +68,31 @@ impl PumpkinBlock for FireBlock { .await; } + async fn on_entity_collision( + &self, + _world: &Arc, + entity: &dyn EntityBase, + _pos: BlockPos, + _block: Block, + _state: BlockState, + ) { + let base_entity = entity.get_entity(); + if !base_entity.entity_type.fire_immune { + let ticks = base_entity.fire_ticks.load(Ordering::Relaxed); + if ticks < 0 { + base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed); + } else if base_entity.entity_type == EntityType::PLAYER { + let rnd_ticks = rand::thread_rng().gen_range(1..3); + base_entity + .fire_ticks + .store(ticks + rnd_ticks, Ordering::Relaxed); + } + if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 { + base_entity.set_on_fire_for(8.0); + } + } + } + async fn get_state_for_neighbor_update( &self, world: &World, diff --git a/pumpkin/src/block/fluids/flowing_fluid.rs b/pumpkin/src/block/fluid/flowing.rs similarity index 100% rename from pumpkin/src/block/fluids/flowing_fluid.rs rename to pumpkin/src/block/fluid/flowing.rs diff --git a/pumpkin/src/block/fluids/lava.rs b/pumpkin/src/block/fluid/lava.rs similarity index 80% rename from pumpkin/src/block/fluids/lava.rs rename to pumpkin/src/block/fluid/lava.rs index 3a56a0bb8..badda0c36 100644 --- a/pumpkin/src/block/fluids/lava.rs +++ b/pumpkin/src/block/fluid/lava.rs @@ -6,9 +6,9 @@ use pumpkin_macros::pumpkin_block; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; -use crate::{block::pumpkin_fluid::PumpkinFluid, world::World}; +use crate::{block::pumpkin_fluid::PumpkinFluid, entity::EntityBase, world::World}; -use super::flowing_fluid::FlowingFluid; +use super::flowing::FlowingFluid; #[pumpkin_block("minecraft:flowing_lava")] pub struct FlowingLava; @@ -46,6 +46,13 @@ impl PumpkinFluid for FlowingLava { .schedule_fluid_tick(fluid.id, *block_pos, LAVA_FLOW_SPEED) .await; } + + async fn on_entity_collision(&self, entity: &dyn EntityBase) { + let base_entity = entity.get_entity(); + if !base_entity.entity_type.fire_immune { + base_entity.set_on_fire_for(15.0); + } + } } #[async_trait] diff --git a/pumpkin/src/block/fluids/mod.rs b/pumpkin/src/block/fluid/mod.rs similarity index 58% rename from pumpkin/src/block/fluids/mod.rs rename to pumpkin/src/block/fluid/mod.rs index 5fab3e233..4ce14c898 100644 --- a/pumpkin/src/block/fluids/mod.rs +++ b/pumpkin/src/block/fluid/mod.rs @@ -1,3 +1,3 @@ -pub(crate) mod flowing_fluid; +pub(crate) mod flowing; pub(crate) mod lava; pub(crate) mod water; diff --git a/pumpkin/src/block/fluids/water.rs b/pumpkin/src/block/fluid/water.rs similarity index 86% rename from pumpkin/src/block/fluids/water.rs rename to pumpkin/src/block/fluid/water.rs index faa3e9b75..86e89c758 100644 --- a/pumpkin/src/block/fluids/water.rs +++ b/pumpkin/src/block/fluid/water.rs @@ -6,9 +6,9 @@ use pumpkin_macros::pumpkin_block; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; -use crate::{block::pumpkin_fluid::PumpkinFluid, world::World}; +use crate::{block::pumpkin_fluid::PumpkinFluid, entity::EntityBase, world::World}; -use super::flowing_fluid::FlowingFluid; +use super::flowing::FlowingFluid; #[pumpkin_block("minecraft:flowing_water")] pub struct FlowingWater; @@ -46,6 +46,10 @@ impl PumpkinFluid for FlowingWater { .schedule_fluid_tick(fluid.id, *block_pos, WATER_FLOW_SPEED) .await; } + + async fn on_entity_collision(&self, entity: &dyn EntityBase) { + entity.get_entity().extinguish(); + } } #[async_trait] diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 15c4487e9..ec29e2eae 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -42,8 +42,8 @@ use blocks::walls::WallBlock; use blocks::{ chest::ChestBlock, furnace::FurnaceBlock, redstone::lever::LeverBlock, tnt::TNTBlock, }; -use fluids::lava::FlowingLava; -use fluids::water::FlowingWater; +use fluid::lava::FlowingLava; +use fluid::water::FlowingWater; use loot::LootTableExt; use pumpkin_data::block_properties::Integer0To15; use pumpkin_data::entity::EntityType; @@ -63,7 +63,7 @@ use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::Experi use std::sync::Arc; pub(crate) mod blocks; -mod fluids; +mod fluid; mod loot; pub mod pumpkin_block; pub mod pumpkin_fluid; diff --git a/pumpkin/src/block/pumpkin_fluid.rs b/pumpkin/src/block/pumpkin_fluid.rs index 1157f167d..01f7289af 100644 --- a/pumpkin/src/block/pumpkin_fluid.rs +++ b/pumpkin/src/block/pumpkin_fluid.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::entity::player::Player; +use crate::entity::{EntityBase, player::Player}; use async_trait::async_trait; use pumpkin_data::{fluid::Fluid, item::Item}; use pumpkin_protocol::server::play::SUseItemOn; @@ -78,6 +78,8 @@ pub trait PumpkinFluid: Send + Sync { ) { } + async fn on_entity_collision(&self, _entity: &dyn EntityBase) {} + async fn on_scheduled_tick(&self, _world: &Arc, _fluid: &Fluid, _block_pos: &BlockPos) {} async fn create_legacy_block(&self, _world: &World, _block_pos: &BlockPos) {} diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index 85ad20ce4..121c7fa88 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -72,6 +72,13 @@ impl BlockRegistry { } } + pub async fn on_entity_collision_fluid(&self, fluid: &Fluid, entity: &dyn EntityBase) { + let pumpkin_fluid = self.get_pumpkin_fluid(fluid); + if let Some(pumpkin_fluid) = pumpkin_fluid { + pumpkin_fluid.on_entity_collision(entity).await; + } + } + pub async fn on_use( &self, block: &Block, diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index aeadca5d1..a56e9afb4 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -138,6 +138,8 @@ pub struct Entity { pub invulnerable: AtomicBool, /// List of damage types this entity is immune to pub damage_immunities: Vec, + pub fire_ticks: AtomicI32, + pub has_visual_fire: AtomicBool, pub portal_cooldown: AtomicU32, @@ -189,6 +191,8 @@ impl Entity { bounding_box_size: AtomicCell::new(bounding_box_size), invulnerable: AtomicBool::new(invulnerable), damage_immunities: Vec::new(), + fire_ticks: AtomicI32::new(-1), + has_visual_fire: AtomicBool::new(false), portal_cooldown: AtomicU32::new(0), portal_manager: Mutex::new(None), } @@ -353,6 +357,22 @@ impl Entity { } } + /// Extinguishes this entity. + pub fn extinguish(&self) { + self.fire_ticks.store(0, Ordering::Relaxed); + } + + pub fn set_on_fire_for(&self, seconds: f32) { + self.set_on_fire_for_ticks((seconds * 20.0).floor() as u32); + } + + pub fn set_on_fire_for_ticks(&self, ticks: u32) { + if self.fire_ticks.load(Ordering::Relaxed) < ticks as i32 { + self.fire_ticks.store(ticks as i32, Ordering::Relaxed); + } + // TODO: defrost + } + /// Sets the `Entity` yaw & pitch rotation pub fn set_rotation(&self, yaw: f32, pitch: f32) { // TODO @@ -424,6 +444,13 @@ impl Entity { } } + pub async fn set_on_fire(&self, on_fire: bool) { + if self.has_visual_fire.load(Ordering::Relaxed) != on_fire { + self.has_visual_fire.store(on_fire, Ordering::Relaxed); + self.set_flag(Flag::OnFire, on_fire).await; + } + } + pub fn get_horizontal_facing(&self) -> HorizontalFacing { let adjusted_yaw = (self.yaw.load() % 360.0 + 360.0) % 360.0; // Normalize yaw to [0, 360) @@ -572,6 +599,12 @@ impl Entity { .on_entity_collision(block, &world, entity, pos, state) .await; } + if let Ok(fluid) = world.get_fluid(&pos).await { + world + .block_registry + .on_entity_collision_fluid(&fluid, entity) + .await; + } } } } @@ -584,8 +617,25 @@ impl EntityBase for Entity { false } - async fn tick(&self, _caller: &dyn EntityBase, _: &Server) { + async fn tick(&self, caller: &dyn EntityBase, _: &Server) { self.tick_portal().await; + let fire_ticks = self.fire_ticks.load(Ordering::Relaxed); + if fire_ticks > 0 { + if self.entity_type.fire_immune { + self.fire_ticks.store(fire_ticks - 4, Ordering::Relaxed); + if self.fire_ticks.load(Ordering::Relaxed) < 0 { + self.extinguish(); + } + } else { + if fire_ticks % 20 == 0 { + caller.damage(1.0, DamageType::ON_FIRE).await; + } + + self.fire_ticks.store(fire_ticks - 1, Ordering::Relaxed); + } + } + self.set_on_fire(self.fire_ticks.load(Ordering::Relaxed) > 0) + .await; // TODO: Tick } @@ -619,7 +669,13 @@ impl NBTStorage for Entity { "Rotation", NbtTag::List(vec![self.yaw.load().into(), self.pitch.load().into()].into_boxed_slice()), ); + nbt.put_short("Fire", self.fire_ticks.load(Relaxed) as i16); nbt.put_bool("OnGround", self.on_ground.load(Relaxed)); + nbt.put_bool("Invulnerable", self.invulnerable.load(Relaxed)); + nbt.put_int("PortalCooldown", self.portal_cooldown.load(Relaxed) as i32); + if self.has_visual_fire.load(Relaxed) { + nbt.put_bool("HasVisualFire", true); + } // todo more... } @@ -640,8 +696,16 @@ impl NBTStorage for Entity { let pitch = rotation[1].extract_float().unwrap_or(0.0); self.set_rotation(yaw, pitch); self.head_yaw.store(yaw); + self.fire_ticks + .store(i32::from(nbt.get_short("Fire").unwrap_or(0)), Relaxed); self.on_ground .store(nbt.get_bool("OnGround").unwrap_or(false), Relaxed); + self.invulnerable + .store(nbt.get_bool("Invulnerable").unwrap_or(false), Relaxed); + self.portal_cooldown + .store(nbt.get_int("PortalCooldown").unwrap_or(0) as u32, Relaxed); + self.has_visual_fire + .store(nbt.get_bool("HasVisualFire").unwrap_or(false), Relaxed); // todo more... } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 6e35f03e8..d825e7854 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -277,7 +277,6 @@ impl Server { } } else { // Player data exists but doesn't have a "Dimension" key. - log::warn!("Player data missing 'Dimension' key, defaulting to Overworld."); let default_world_guard = self.worlds.read().await; let default_world = default_world_guard .first()