use std::time::Instant;
use crossterm::style::Stylize;
use float_ord::FloatOrd;
use interfaces::types::BlockLocation;
use itertools::Itertools;
use crate::{
client::{
state::{global::GlobalState, local::LocalState},
tasks::{
compound::CompoundTask, eat::EatTask, fall_bucket::FallBucketTask, mine::MineTask,
navigate::BlockTravelTask, Task, TaskTrait,
},
},
protocol::{EventQueue, Face, InterfaceOut},
types::Displacement,
};
/// The current [`Task`] we are trying to achieve. If we are not aiming at
/// achieving a task, this is [`None`].
#[derive(Default)]
pub struct ActionState {
/// the task. In the future, this might change to be a priority queue of
/// tasks
task: Option<Task>,
}
impl ActionState {
/// schedule a task
pub fn schedule<T: Into<Task>>(&mut self, task: T) {
self.task = Some(task.into());
}
/// clear the task list
pub fn clear(&mut self) {
self.task = None;
}
}
/// The bot instance we are dealing with
pub struct Bot<Queue: EventQueue, Out: InterfaceOut> {
/// the [`LocalState`] of the bot (i.e., things that are part of its mind
/// but not shared by other bots)
pub state: LocalState,
/// The state of actions ([`ActionState`]) of the bot
pub actions: ActionState,
/// TODO: idk what this is
pub queue: Queue,
/// The [`InterfaceOut`] we use to communicate with the world
pub out: Out,
}
impl<Queue: EventQueue, Out: InterfaceOut> Bot<Queue, Out> {
pub fn run_sync(&mut self, global: &mut GlobalState) {
match self.actions.task.as_mut() {
None => {}
Some(task) => {
if task.tick(&mut self.out, &mut self.state, global) {
self.actions.task = None;
}
}
}
let actions = self
.state
.physics
.tick(&mut global.blocks, &self.state.inventory);
let physics = &self.state.physics;
self.out
.teleport_and_look(physics.location(), physics.direction(), physics.on_ground());
// if self.actions.task.is_none() {
// // let mut vel = self.state.physics.velocity();
// // vel.dy = 0.;
// // if vel.mag2() > 0.01 {
// // vel *= -1.;
// // self.state.physics.look(Direction::from(vel));
// // self.state.physics.speed(Speed::SPRINT);
// // self.state.physics.line(Line::Forward);
// // }
// }
//
//
if let Some(place) = actions.block_placed.as_ref() {
self.out.swing_arm();
self.out.place_block(place.location, place.face);
}
// this should be after everything else as actions depend on the previous
// location
self.state.ticks += 1;
}
}
/// Always returns None.
#[allow(clippy::many_single_char_names)]
pub fn process_command(
name: &str,
args: &[&str],
local: &mut LocalState,
global: &mut GlobalState,
actions: &mut ActionState,
out: &mut impl InterfaceOut,
) -> anyhow::Result<()> {
macro_rules! msg {
() => {{
println!();
}};
($($msg: expr),*) => {{
let to_print_raw = format!($($msg),*);
let to_print = to_print_raw.bold().to_string();
println!("{}", to_print);
}};
}
match name {
// "pillar" => {
// if let [a] = args {
// let y = a.parse()?;
// actions.schedule(PillarTask::new(y));
// }
// }
// "bridge" => {
// if let [a] = args {
// let amount = a.parse()?;
// actions.schedule(BridgeTask::new(amount, CardinalDirection::North, local));
// }
// }
// "block" => {
// local.inventory.switch_block(out);
// }
// "gotoc" => { // goto chunk
// if let [a, b] = args {
// let x = a.parse()?;
// let z = b.parse()?;
// let goal = ChunkLocation(x, z);
// actions.schedule(ChunkTravelTask::new(goal, local));
// }
// }
// "jump" => {
// local.physics.jump();
// }
"health" => {
println!("Health: {}, Food: {}", local.health, local.food);
}
"follow" => {
local.follow_closest = true;
}
// "kys" => {
// // TODO: try to kill themself by fall damage/lava/etc
// }
"eat" => {
let eat_task = EatTask::default();
actions.task = Some(eat_task.into());
}
"slot" => {
if let [number] = args {
let number: u8 = number.parse().unwrap();
local.inventory.change_slot(number, out);
}
}
"fall" => {
let below = BlockLocation::from(local.physics.location()).below();
let mine = MineTask::new(below, out, local, global);
let fall = FallBucketTask::default();
let mut compound = CompoundTask::default();
compound.add(mine).add(fall);
actions.schedule(compound);
}
"drop" => {
local.inventory.drop_hotbar(out);
}
"goto" => {
if let [a, b, c] = args {
let x = a.parse()?;
let y = b.parse()?;
let z = c.parse()?;
let dest = BlockLocation::new(x, y, z);
actions.schedule(BlockTravelTask::new(dest, local));
}
}
"stop" => {
actions.task = None;
}
"loc" => {
msg!(
"My location is {} in {}",
local.physics.location(),
local.dimension
);
}
"state" => {
if let [name] = args {
if name == &local.info.username {
msg!("location {}", local.physics.location());
msg!("on ground {}", local.physics.on_ground());
let below_loc =
BlockLocation::from(local.physics.location() - Displacement::EPSILON_Y);
msg!("below kind {:?}", global.blocks.get_block_kind(below_loc));
msg!("inventory slots {:?}", local.inventory.hotbar());
}
}
}
"get" => {
if let [a, b, c] = args {
let x = a.parse()?;
let y = b.parse()?;
let z = c.parse()?;
let location = BlockLocation::new(x, y, z);
msg!("The block is {:?}", global.blocks.get_block(location));
}
}
"place" => {
if let [a, b, c] = args {
let x = a.parse()?;
let y = b.parse()?;
let z = c.parse()?;
let origin = local.physics.location() + Displacement::EYE_HEIGHT;
let location = BlockLocation::new(x, y, z);
let faces = location.faces();
let best_loc_idx = IntoIterator::into_iter(faces)
.position_min_by_key(|loc| FloatOrd(loc.dist2(origin)))
.unwrap();
local.physics.look_at(faces[best_loc_idx]);
out.use_item();
out.place_block(location, Face::from(best_loc_idx as u8));
}
}
// "mine" => {
// let origin = local.physics.location() + Displacement::EYE_HEIGHT;
//
// let closest = global.blocks.closest_in_chunk(origin.into(), |state|
// state.kind().mineable(&global.block_data));
//
// if let Some(closest) = closest {
// let mine_task = MineTask::new(closest, local, global);
// actions.schedule(mine_task);
// }
// }
_ => {}
}
Ok(())
}
pub fn run_threaded(
_: &rayon::Scope,
local: &mut LocalState,
actions: &mut ActionState,
global: &GlobalState,
end_by: Instant,
) {
if let Some(task) = actions.task.as_mut() {
task.expensive(end_by, local, global);
}
}