From 018f0c03d439d9ff6267b574a9bafb8a6331d7ab Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Mon, 19 May 2025 11:16:11 -0600 Subject: [PATCH] Add code --- .gitignore | 5 + Cargo.toml | 11 +++ src/main.rs | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 6985cf1..196e176 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..328a52b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rustbot" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.98" +azalea = "0.12.0" +lazy_static = "1.5.0" +parking_lot = "0.12.3" +tokio = "1.45.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..479d811 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,257 @@ +use std::{collections::HashSet, sync::Arc, thread::sleep, time::Duration}; + +use azalea::{ + BlockPos, Bot, GameProfileComponent, + blocks::{BlockState, properties::Type}, + ecs::{entity::Entity, query::With}, + entity::{Position, metadata::Player}, + pathfinder::{GotoEvent, Pathfinder, astar::PathfinderTimeout, goals, moves::default_move}, + prelude::*, + registry::{Block, Item}, + world::find_blocks::FindBlocks, +}; + +use lazy_static::lazy_static; +use parking_lot::Mutex; +// use parking_lot::Mutex; + +#[tokio::main] +async fn main() { + let account = Account::offline("bot"); + // or Account::microsoft("example@example.com").await.unwrap(); + + ClientBuilder::new() + .set_handler(handle) + .start(account, "localhost") + .await + .unwrap(); +} + +#[derive(Clone)] +pub enum BotAction { + Nothing, + Goto(Position), + PathfindMine(PathfindMineAction), +} + +#[derive(Clone)] +pub enum PathfindMineAction { + Start, + Goto(HashSet, BlockPos), + Mine(HashSet, BlockPos), +} + +impl Default for BotAction { + fn default() -> Self { + Self::Nothing + } +} + +pub static MINE_DISTANCE: i32 = 7; + +lazy_static! { + pub static ref MINE_BLOCKS: HashSet = { + let mut b = HashSet::new(); + + b.insert(Block::OakLog.into()); + + b + }; +} + +#[derive(Default, Clone, Component)] +pub struct State { + pub action: Arc>, + // pub messages_received: Arc>, +} + +async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { + match event { + Event::Chat(m) => { + let (sender, message) = m.split_sender_and_content(); + println!("<{:?}> {}", sender, message); + + if sender.is_some() && message.starts_with("!") { + match message.as_str() { + "!follow" => { + let uuid = m.sender_uuid().unwrap(); + let entity = bot.entity_by_uuid(uuid).unwrap(); + let position = bot.get_entity_component::(entity).unwrap(); + + bot.chat(format!("Following player {:?}", position).as_str()); + + bot.ecs.lock().send_event(GotoEvent { + entity: bot.entity, + goal: Arc::new(goals::BlockPosGoal(position.to_block_pos_ceil())), + successors_fn: default_move, + allow_mining: true, + min_timeout: PathfinderTimeout::Time(Duration::from_secs(2)), + max_timeout: PathfinderTimeout::Time(Duration::from_secs(10)), + }); + } + "!mine" => { + *state.action.lock() = BotAction::PathfindMine(PathfindMineAction::Start); + std::mem::drop(state.action); + + // blocks. + // + } + "!stop" => { + bot.stop_pathfinding(); + *state.action.lock() = BotAction::Nothing; + std::mem::drop(state.action); + } + _ => { + bot.chat(format!("Invalid command: {}", message).as_str()); + } + } + } + + // *state.messages_received.lock() += 1; + } + Event::Tick => { + let action = state.action.lock().clone(); + match action { + BotAction::PathfindMine(action) => match action { + PathfindMineAction::Start => { + let block = bot + .world() + .read() + .find_blocks( + bot.position(), + &azalea::blocks::BlockStates { + set: MINE_BLOCKS.clone(), + }, + ) + .next() + .unwrap(); + + bot.ecs.lock().send_event(GotoEvent { + entity: bot.entity, + goal: Arc::new(goals::RadiusGoal { + pos: block.center(), + radius: MINE_DISTANCE as f32, + }), + successors_fn: default_move, + allow_mining: true, + min_timeout: PathfinderTimeout::Time(Duration::from_secs(2)), + max_timeout: PathfinderTimeout::Time(Duration::from_secs(10)), + }); + std::mem::drop(bot.ecs); + + // println!("Starting goto start..."); + + *state.action.lock() = BotAction::PathfindMine(PathfindMineAction::Goto( + MINE_BLOCKS.clone(), + block, + )); + std::mem::drop(state.action); + // println!("Finished lock"); + } + PathfindMineAction::Goto(blocks, block) => { + if is_goto_target_reached(&bot) { + bot.start_mining(block); + + // println!("Starting mine..."); + + *state.action.lock() = BotAction::PathfindMine( + PathfindMineAction::Mine(MINE_BLOCKS.clone(), block.clone()), + ); + std::mem::drop(state.action); + // println!("Droped mine..."); + } + } + PathfindMineAction::Mine(blocks, block) => { + println!("Stall 1"); + if let Some(blockstate) = bot.world().read().get_block_state(&block) { + println!("Stall 2"); + if blockstate.is_air() { + println!("Stall 3"); + if let Some(block) = bot + .world() + .read() + .find_blocks( + bot.position(), + &azalea::blocks::BlockStates { + set: MINE_BLOCKS.clone(), + }, + ) + .next() + { + println!("Stall 4"); + // { + // println!("Stall 5"); + // bot.ecs.lock() + // } + // .send_event(GotoEvent { + // entity: { + // println!("Stall 6"); + // bot.entity + // }, + // goal: Arc::new(goals::RadiusGoal { + // pos: block.center(), + // radius: MINE_DISTANCE as f32, + // }), + // successors_fn: default_move, + // allow_mining: true, + // min_timeout: PathfinderTimeout::Time(Duration::from_secs( + // 2, + // )), + // max_timeout: PathfinderTimeout::Time(Duration::from_secs( + // 10, + // )), + // }); + // println!("Stall 7"); + // + // std::mem::drop(bot.ecs); + + sleep(Duration::from_millis(100)); + + bot.goto(goals::RadiusGoal { + pos: block.center(), + radius: MINE_DISTANCE as f32, + }); + + // bot.is_go().await; + + println!("Stall 82"); + + println!("Starting Goto..."); + *state.action.lock() = BotAction::PathfindMine( + PathfindMineAction::Goto(MINE_BLOCKS.clone(), block), + ); + std::mem::drop(state.action); + println!("Dropped Goto..."); + } else { + *state.action.lock() = BotAction::Nothing; + std::mem::drop(state.action); + } + } + } + } + }, + + _ => {} + } + } + _ => {} + } + + Ok(()) +} + +fn is_goto_target_reached(bot: &Client) -> bool { + bot.map_get_component::(|p| { + p.map(|p| p.goal.is_none() && !p.is_calculating) + .unwrap_or(true) + }) +} + +fn player_by_name(bot: &Client, name: String) -> Option { + bot.entity_by::, (&GameProfileComponent,)>( + |(profile,): &(&GameProfileComponent,)| { + // return sender.unwrap() == profile.name; + profile.name == name + }, + ) +}