pub mod basic;
pub mod parkour;
pub mod uncommon;
use std::{
fmt::{self, Debug},
sync::Arc,
};
use azalea_block::BlockState;
use azalea_client::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
inventory::SetSelectedHotbarSlotEvent, mining::StartMiningBlockEvent,
};
use azalea_core::position::{BlockPos, Vec3};
use azalea_inventory::Menu;
use azalea_registry::builtin::BlockKind;
use azalea_world::World;
use bevy_ecs::{entity::Entity, message::MessageWriter, system::Commands, world::EntityWorldMut};
use parking_lot::RwLock;
use tracing::debug;
use super::{
astar,
custom_state::CustomPathfinderStateRef,
mining::MiningCache,
positions::RelBlockPos,
world::{CachedWorld, is_block_state_passable},
};
use crate::{
auto_tool::best_tool_in_hotbar_for_block,
bot::{JumpEvent, LookAtEvent},
pathfinder::player_pos_to_block_pos,
};
type Edge = astar::Edge<RelBlockPos, MoveData>;
pub type SuccessorsFn = fn(&mut MovesCtx, RelBlockPos);
pub const BARITONE_COMPAT: bool = false;
pub fn default_move(ctx: &mut MovesCtx, node: RelBlockPos) {
basic::basic_move(ctx, node);
parkour::parkour_move(ctx, node);
uncommon::uncommon_move(ctx, node);
}
#[derive(Clone)]
pub struct MoveData {
pub execute: &'static (dyn Fn(ExecuteCtx) + Send + Sync),
pub is_reached: &'static (dyn Fn(IsReachedCtx) -> bool + Send + Sync),
}
impl Debug for MoveData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MoveData")
.finish()
}
}
pub struct ExecuteCtx<'s, 'w1, 'w2, 'w3, 'w4, 'w5, 'w6, 'a> {
pub entity: Entity,
pub target: BlockPos,
pub start: BlockPos,
pub position: Vec3,
pub physics: &'a azalea_entity::Physics,
pub is_currently_mining: bool,
pub can_mine: bool,
pub world: Arc<RwLock<World>>,
pub menu: Menu,
pub commands: &'a mut Commands<'w1, 's>,
pub look_at_events: &'a mut MessageWriter<'w2, LookAtEvent>,
pub sprint_events: &'a mut MessageWriter<'w3, StartSprintEvent>,
pub walk_events: &'a mut MessageWriter<'w4, StartWalkEvent>,
pub jump_events: &'a mut MessageWriter<'w5, JumpEvent>,
pub start_mining_events: &'a mut MessageWriter<'w6, StartMiningBlockEvent>,
}
impl ExecuteCtx<'_, '_, '_, '_, '_, '_, '_, '_> {
pub fn on_tick_start(&mut self) {
self.set_sneaking(false);
}
pub fn look_at(&mut self, position: Vec3) {
self.look_at_events.write(LookAtEvent {
entity: self.entity,
position: Vec3 {
x: position.x,
y: self.position.up(1.53).y,
z: position.z,
},
});
}
pub fn look_at_exact(&mut self, position: Vec3) {
self.look_at_events.write(LookAtEvent {
entity: self.entity,
position,
});
}
pub fn sprint(&mut self, direction: SprintDirection) {
self.sprint_events.write(StartSprintEvent {
entity: self.entity,
direction,
});
}
pub fn walk(&mut self, direction: WalkDirection) {
self.walk_events.write(StartWalkEvent {
entity: self.entity,
direction,
});
}
pub fn jump(&mut self) {
self.jump_events.write(JumpEvent {
entity: self.entity,
});
}
fn set_sneaking(&mut self, sneaking: bool) {
self.commands
.entity(self.entity)
.queue(move |mut entity: EntityWorldMut<'_>| {
if let Some(mut physics_state) = entity.get_mut::<PhysicsState>() {
physics_state.trying_to_crouch = sneaking;
}
});
}
pub fn sneak(&mut self) {
self.set_sneaking(true);
}
pub fn jump_if_in_water(&mut self) {
if self.physics.is_in_water() {
self.jump();
}
}
pub fn should_mine(&mut self, block: BlockPos) -> bool {
let block_state = self.world.read().get_block_state(block).unwrap_or_default();
should_mine_block_state(block_state)
}
pub fn mine(&mut self, block: BlockPos) -> bool {
if !self.can_mine {
return false;
}
let block_state = self.world.read().get_block_state(block).unwrap_or_default();
if is_block_state_passable(block_state) {
return false;
}
let best_tool_result = best_tool_in_hotbar_for_block(block_state, &self.menu);
debug!("best tool for {block_state:?}: {best_tool_result:?}");
self.commands.trigger(SetSelectedHotbarSlotEvent {
entity: self.entity,
slot: best_tool_result.index as u8,
});
self.is_currently_mining = true;
self.walk(WalkDirection::None);
self.look_at_exact(block.center());
self.start_mining_events.write(StartMiningBlockEvent {
entity: self.entity,
position: block,
force: true,
});
true
}
pub fn mine_while_at_start(&mut self, block: BlockPos) -> bool {
let horizontal_distance_from_start = (self.start.center() - self.position)
.horizontal_distance_squared()
.sqrt();
let at_start_position = player_pos_to_block_pos(self.position) == self.start
&& horizontal_distance_from_start < 0.25;
if self.should_mine(block) {
if at_start_position {
self.look_at(block.center());
self.mine(block);
} else {
self.look_at(self.start.center());
self.walk(WalkDirection::Forward);
}
true
} else {
false
}
}
pub fn get_block_state(&self, block: BlockPos) -> BlockState {
self.world.read().get_block_state(block).unwrap_or_default()
}
}
pub fn should_mine_block_state(block_state: BlockState) -> bool {
if is_block_state_passable(block_state) || BlockKind::from(block_state) == BlockKind::Water {
return false;
}
true
}
pub struct IsReachedCtx<'a> {
pub target: BlockPos,
pub start: BlockPos,
pub position: Vec3,
pub physics: &'a azalea_entity::Physics,
}
#[must_use]
pub fn default_is_reached(
IsReachedCtx {
position,
target,
physics,
..
}: IsReachedCtx,
) -> bool {
let block_pos = player_pos_to_block_pos(position);
if block_pos == target {
return true;
}
if physics.is_in_water() && block_pos.down(1) == target {
return true;
}
false
}
pub struct MovesCtx<'a> {
pub edges: &'a mut Vec<Edge>,
pub world: &'a CachedWorld,
pub mining_cache: &'a MiningCache,
pub custom_state: &'a CustomPathfinderStateRef,
}