Pumpkin/crates/pumpkin/src/entity/ai/goal/break_door.rs
2026-09-02 15:16:42 +02:00

181 lines
5.6 KiB
Rust

use std::sync::Arc;
use pumpkin_data::BlockStateId;
use pumpkin_data::world::WorldEvent;
use pumpkin_util::Difficulty;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use super::door_interact::DoorInteractGoal;
use super::{Controls, Goal};
use crate::block::blocks::doors::DoorBlock;
use crate::entity::mob::Mob;
const DEFAULT_DOOR_BREAK_TIME: i32 = 240;
pub type DifficultyPredicate = Arc<dyn Fn(Difficulty) -> bool + Send + Sync>;
pub struct BreakDoorGoal {
pub door_interact_goal: DoorInteractGoal,
valid_difficulties: DifficultyPredicate,
pub break_time: i32,
pub last_break_progress: i32,
pub door_break_time: i32,
}
impl BreakDoorGoal {
#[must_use]
pub fn new(valid_difficulties: DifficultyPredicate) -> Self {
Self {
door_interact_goal: DoorInteractGoal::new(),
valid_difficulties,
break_time: 0,
last_break_progress: -1,
door_break_time: -1,
}
}
#[must_use]
pub fn with_door_break_time(
door_break_time: i32,
valid_difficulties: DifficultyPredicate,
) -> Self {
Self {
door_interact_goal: DoorInteractGoal::new(),
valid_difficulties,
break_time: 0,
last_break_progress: -1,
door_break_time,
}
}
#[must_use]
pub const fn get_door_break_time(&self) -> i32 {
if self.door_break_time > DEFAULT_DOOR_BREAK_TIME {
self.door_break_time
} else {
DEFAULT_DOOR_BREAK_TIME
}
}
#[must_use]
pub fn is_valid_difficulty(&self, difficulty: Difficulty) -> bool {
(self.valid_difficulties)(difficulty)
}
}
impl Default for BreakDoorGoal {
fn default() -> Self {
Self::new(Arc::new(|d| d == Difficulty::Hard))
}
}
impl Goal for BreakDoorGoal {
fn can_start(&mut self, mob: &dyn Mob) -> bool {
if !self.door_interact_goal.can_use(mob) {
return false;
}
let world = mob.get_entity().world.load();
let level_info = world.level_info.load();
if !level_info.game_rules.mob_griefing {
return false;
}
self.is_valid_difficulty(level_info.difficulty) && !self.door_interact_goal.is_open(mob)
}
fn should_continue(&self, mob: &dyn Mob) -> bool {
let world = mob.get_entity().world.load();
let level_info = world.level_info.load();
let mob_pos = mob.get_entity().pos.load();
let door_pos = self.door_interact_goal.door_pos;
let center_x = f64::from(door_pos.0.x) + 0.5;
let center_y = f64::from(door_pos.0.y) + 0.5;
let center_z = f64::from(door_pos.0.z) + 0.5;
let dx = center_x - mob_pos.x;
let dy = center_y - mob_pos.y;
let dz = center_z - mob_pos.z;
let dist_sq = dx * dx + dy * dy + dz * dz;
self.break_time <= self.get_door_break_time()
&& !DoorBlock::is_open(&world, &door_pos)
&& dist_sq < 4.0
&& self.is_valid_difficulty(level_info.difficulty)
}
fn start(&mut self, mob: &dyn Mob) {
self.door_interact_goal.start_interaction(mob);
self.break_time = 0;
self.last_break_progress = -1;
}
fn stop(&mut self, mob: &dyn Mob) {
let world = mob.get_entity().world.load();
world.set_block_destroy_stage(
mob.get_entity().entity_id,
self.door_interact_goal.door_pos,
-1,
);
}
fn tick(&mut self, mob: &dyn Mob) {
self.door_interact_goal.tick_interaction(mob);
let world = mob.get_entity().world.load_full();
if mob.get_random().random_range(0..20) == 0 {
world.sync_world_event(
WorldEvent::SoundZombieWoodenDoor,
self.door_interact_goal.door_pos,
0,
);
mob.get_mob_entity().living_entity.swing_hand();
}
self.break_time += 1;
let progress = (self.break_time as f32 / self.get_door_break_time() as f32 * 10.0) as i32;
if progress != self.last_break_progress {
world.set_block_destroy_stage(
mob.get_entity().entity_id,
self.door_interact_goal.door_pos,
progress as i8,
);
self.last_break_progress = progress;
}
let level_info = world.level_info.load();
if self.break_time == self.get_door_break_time()
&& self.is_valid_difficulty(level_info.difficulty)
{
let (_, block_state_id) =
world.get_block_and_state_id(&self.door_interact_goal.door_pos);
let door_pos = self.door_interact_goal.door_pos;
let mut event =
crate::plugin::api::events::entity::entity_break_door::EntityBreakDoorEvent::new(
mob.get_entity().entity_id,
door_pos,
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
world.set_block_state(&door_pos, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
world.sync_world_event(
WorldEvent::SoundZombieDoorCrash,
self.door_interact_goal.door_pos,
0,
);
world.sync_world_event(
WorldEvent::ParticlesDestroyBlock,
self.door_interact_goal.door_pos,
i32::from(block_state_id.as_u16()),
);
}
}
fn should_run_every_tick(&self) -> bool {
true
}
fn controls(&self) -> Controls {
Controls::empty()
}
}