use std::sync::Arc; use pumpkin_data::dimension::Dimension; use pumpkin_data::tag::Taggable; use pumpkin_data::world::WorldEvent; use pumpkin_data::{Block, BlockDirection, tag}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::random::RandomGenerator; use pumpkin_util::random::xoroshiro128::Xoroshiro; use rand::RngExt; use soul_fire::SoulFireBlock; use crate::block::blocks::fire::fire::FireBlock; use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, OnEntityCollisionArgs}; use crate::entity::EntityBase; use crate::world::World; use crate::world::portal::nether::NetherPortal; use pumpkin_data::damage::DamageType; use pumpkin_data::entity::EntityType; use std::sync::atomic::Ordering; #[expect(clippy::module_inception)] pub mod fire; pub mod soul_fire; pub struct FireBlockBase; impl FireBlockBase { pub async fn get_fire_type(world: &World, pos: &BlockPos) -> Block { let block = world.get_block(&pos.down()).await; if SoulFireBlock::is_soul_base(block) { return Block::SOUL_FIRE; } Block::FIRE } #[must_use] pub fn can_place_on(block: &Block) -> bool { // Make sure the block below is not a fire block or fluid block block != &Block::SOUL_FIRE && block != &Block::FIRE && block != &Block::WATER && block != &Block::LAVA } pub async fn is_soul_fire(world: &Arc, block_pos: &BlockPos) -> bool { let block = world.get_block(&block_pos.down()).await; block.has_tag(&tag::Block::MINECRAFT_SOUL_FIRE_BASE_BLOCKS) } pub async fn can_place_at(world: &Arc, block_pos: &BlockPos) -> bool { let block_state = world.get_block_state(block_pos).await; if !block_state.is_air() { return false; } if Self::is_soul_fire(world, block_pos).await { SoulFireBlock .can_place_at(CanPlaceAtArgs { server: None, world: Some(world), block_accessor: world.as_ref(), block: &Block::SOUL_FIRE, state: Block::SOUL_FIRE.default_state, position: block_pos, direction: None, player: None, use_item_on: None, }) .await } else { FireBlock .can_place_at(CanPlaceAtArgs { server: None, world: Some(world), block_accessor: world.as_ref(), block: &Block::FIRE, state: Block::FIRE.default_state, position: block_pos, direction: None, player: None, use_item_on: None, }) .await || Self::should_light_portal_at(world, block_pos, BlockDirection::Up).await } } pub async fn should_light_portal_at( world: &Arc, block_pos: &BlockPos, direction: BlockDirection, ) -> bool { let dimension = &world.dimension; if dimension != &Dimension::OVERWORLD && dimension != &Dimension::THE_NETHER { return false; } let mut found = false; for dir in BlockDirection::all() { if world.get_block(&block_pos.offset(dir.to_offset())).await == &Block::OBSIDIAN { found = true; break; } } if !found { return false; } let dir = if direction.is_horizontal() { direction.rotate_counter_clockwise() } else { BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro( Xoroshiro::from_seed(rand::rng().random()), )) }; return NetherPortal::get_new_portal(world, block_pos, dir.to_horizontal_axis().unwrap()) .await .is_some(); } /// Shared fire collision behavior used by `fire` and `soul_fire`. #[must_use] #[allow(clippy::needless_pass_by_value)] pub fn apply_fire_collision( args: OnEntityCollisionArgs<'_>, extra_damage_for_living: bool, ) -> BlockFuture<'_, ()> { Box::pin(async move { let base_entity = args.entity.get_entity(); if !base_entity.entity_type.fire_immune && !base_entity.fire_immune.load(Ordering::Relaxed) { let ticks = base_entity.fire_ticks.load(Ordering::Relaxed); // Timer logic if ticks < 0 { base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed); } else if base_entity.entity_type == &EntityType::PLAYER { let rnd_ticks = rand::rng().random_range(1..3); base_entity .fire_ticks .store(ticks + rnd_ticks, Ordering::Relaxed); } // Apply fire ticks if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 { base_entity.set_on_fire_for(8.0); } // Regular fire vs soul fire damage if extra_damage_for_living { base_entity .damage(args.entity, 2.0, DamageType::IN_FIRE) .await; } else { base_entity .damage(args.entity, 1.0, DamageType::IN_FIRE) .await; } } }) } async fn broken(world: &World, block_pos: BlockPos) { world .sync_world_event(WorldEvent::SoundExtinguishFire, block_pos, 0) .await; } }