Pumpkin/pumpkin/src/block/blocks/fire/mod.rs
2026-05-10 11:09:38 +02:00

171 lines
5.7 KiB
Rust

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<World>, 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<World>, 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<World>,
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;
}
}