171 lines
5.7 KiB
Rust
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;
|
|
}
|
|
}
|