mirror of
https://github.com/Astatin3/rustbot.git
synced 2026-06-09 00:28:00 -06:00
Add stuff
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use azalea::{Client, Event};
|
||||
|
||||
use crate::command_controler::BotTask;
|
||||
|
||||
pub struct Chat {
|
||||
last_update: Instant,
|
||||
messages: Vec<String>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
static DELAY: Duration = Duration::from_secs(1);
|
||||
|
||||
impl Chat {
|
||||
pub fn init(args: Vec<String>) -> Self {
|
||||
Self {
|
||||
last_update: Instant::now() - DELAY,
|
||||
messages: args,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BotTask for Chat {
|
||||
fn on_event(&mut self, bot: &Client, _event: &Event) {
|
||||
if self.last_update.elapsed() >= DELAY {
|
||||
bot.chat(self.messages[self.index].as_str());
|
||||
self.index += 1;
|
||||
self.last_update = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
fn end(&self) -> bool {
|
||||
self.index == self.messages.len()
|
||||
// Clean up
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use azalea::brigadier::prelude::*;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{CommandControler::Ctx, CommandSource};
|
||||
|
||||
pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||
commands.register(
|
||||
literal("killaura").then(argument("enabled", bool()).executes(|ctx: &Ctx| {
|
||||
let enabled = get_bool(ctx, "enabled").unwrap();
|
||||
let source = ctx.source.lock();
|
||||
let bot = source.bot.clone();
|
||||
{
|
||||
let mut ecs = bot.ecs.lock();
|
||||
let mut entity = ecs.entity_mut(bot.entity);
|
||||
let mut state = entity.get_mut::<crate::State>().unwrap();
|
||||
// state.killaura = enabled
|
||||
}
|
||||
source.reply(if enabled {
|
||||
"Enabled killaura"
|
||||
} else {
|
||||
"Disabled killaura"
|
||||
});
|
||||
1
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
//! Commands for debugging and getting the current state of the bot.
|
||||
|
||||
use std::{env, fs::File, io::Write, thread, time::Duration};
|
||||
|
||||
use azalea::{
|
||||
BlockPos,
|
||||
brigadier::prelude::*,
|
||||
chunks::ReceiveChunkEvent,
|
||||
entity::{LookDirection, Position},
|
||||
interact::HitResultComponent,
|
||||
packet::game,
|
||||
pathfinder::{ExecutingPath, Pathfinder},
|
||||
world::{InstanceContainer, MinecraftEntityId},
|
||||
};
|
||||
use bevy_ecs::event::Events;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{CommandControler::Ctx, CommandSource};
|
||||
|
||||
pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||
commands.register(literal("ping").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.reply("pong!");
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("disconnect").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.disconnect();
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("whereami").executes(|ctx: &Ctx| {
|
||||
let mut source = ctx.source.lock();
|
||||
let Some(entity) = source.entity() else {
|
||||
source.reply("You aren't in render distance!");
|
||||
return 0;
|
||||
};
|
||||
let position = source.bot.entity_component::<Position>(entity);
|
||||
source.reply(&format!(
|
||||
"You are at {}, {}, {}",
|
||||
position.x, position.y, position.z
|
||||
));
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("entityid").executes(|ctx: &Ctx| {
|
||||
let mut source = ctx.source.lock();
|
||||
let Some(entity) = source.entity() else {
|
||||
source.reply("You aren't in render distance!");
|
||||
return 0;
|
||||
};
|
||||
let entity_id = source.bot.entity_component::<MinecraftEntityId>(entity);
|
||||
source.reply(&format!(
|
||||
"Your Minecraft ID is {} and your ECS id is {entity:?}",
|
||||
*entity_id
|
||||
));
|
||||
1
|
||||
}));
|
||||
|
||||
let whereareyou = |ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let position = source.bot.position();
|
||||
source.reply(&format!(
|
||||
"I'm at {}, {}, {}",
|
||||
position.x, position.y, position.z
|
||||
));
|
||||
1
|
||||
};
|
||||
commands.register(literal("whereareyou").executes(whereareyou));
|
||||
commands.register(literal("pos").executes(whereareyou));
|
||||
|
||||
commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.reply(&format!(
|
||||
"I am {} ({})",
|
||||
source.bot.username(),
|
||||
source.bot.uuid()
|
||||
));
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("getdirection").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let direction = source.bot.component::<LookDirection>();
|
||||
source.reply(&format!(
|
||||
"I'm looking at {}, {}",
|
||||
direction.y_rot, direction.x_rot
|
||||
));
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("health").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
|
||||
let health = source.bot.health();
|
||||
source.reply(&format!("I have {health} health"));
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("lookingat").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
|
||||
let hit_result = source.bot.component::<HitResultComponent>();
|
||||
|
||||
if hit_result.is_miss() {
|
||||
source.reply("I'm not looking at anything");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let block_pos = &hit_result
|
||||
.as_block_hit_result_if_not_miss()
|
||||
.unwrap()
|
||||
.block_pos;
|
||||
let block = source.bot.world().read().get_block_state(block_pos);
|
||||
|
||||
source.reply(&format!("I'm looking at {block:?} at {block_pos:?}"));
|
||||
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("getblock").then(argument("x", integer()).then(
|
||||
argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let x = get_integer(ctx, "x").unwrap();
|
||||
let y = get_integer(ctx, "y").unwrap();
|
||||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("getblock xyz {x} {y} {z}");
|
||||
let block_pos = BlockPos::new(x, y, z);
|
||||
let block = source.bot.world().read().get_block_state(&block_pos);
|
||||
source.reply(&format!("Block at {block_pos} is {block:?}"));
|
||||
1
|
||||
})),
|
||||
)));
|
||||
commands.register(literal("getfluid").then(argument("x", integer()).then(
|
||||
argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let x = get_integer(ctx, "x").unwrap();
|
||||
let y = get_integer(ctx, "y").unwrap();
|
||||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("getfluid xyz {x} {y} {z}");
|
||||
let block_pos = BlockPos::new(x, y, z);
|
||||
let block = source.bot.world().read().get_fluid_state(&block_pos);
|
||||
source.reply(&format!("Fluid at {block_pos} is {block:?}"));
|
||||
1
|
||||
})),
|
||||
)));
|
||||
|
||||
commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let pathfinder = source.bot.get_component::<Pathfinder>();
|
||||
let Some(pathfinder) = pathfinder else {
|
||||
source.reply("I don't have the Pathfinder ocmponent");
|
||||
return 1;
|
||||
};
|
||||
source.reply(&format!(
|
||||
"pathfinder.is_calculating: {}",
|
||||
pathfinder.is_calculating
|
||||
));
|
||||
|
||||
let executing_path = source.bot.get_component::<ExecutingPath>();
|
||||
let Some(executing_path) = executing_path else {
|
||||
source.reply("I'm not executing a path");
|
||||
return 1;
|
||||
};
|
||||
source.reply(&format!(
|
||||
"is_path_partial: {}, path.len: {}, queued_path.len: {}",
|
||||
executing_path.is_path_partial,
|
||||
executing_path.path.len(),
|
||||
if let Some(queued) = executing_path.queued_path {
|
||||
queued.len().to_string()
|
||||
} else {
|
||||
"n/a".to_string()
|
||||
},
|
||||
));
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
|
||||
source.reply("Ok!");
|
||||
|
||||
|
||||
|
||||
source.bot.disconnect();
|
||||
|
||||
let ecs = source.bot.ecs.clone();
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
// dump the ecs
|
||||
|
||||
let ecs = ecs.lock();
|
||||
|
||||
|
||||
|
||||
let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
|
||||
let mut report = File::create(&report_path).unwrap();
|
||||
|
||||
for entity in ecs.iter_entities() {
|
||||
writeln!(report, "Entity: {}", entity.id()).unwrap();
|
||||
let archetype = entity.archetype();
|
||||
let component_count = archetype.component_count();
|
||||
|
||||
let component_names = archetype
|
||||
.components()
|
||||
.map(|c| ecs.components().get_info(c).unwrap().name())
|
||||
.collect::<Vec<_>>();
|
||||
writeln!(
|
||||
report,
|
||||
"- {component_count} components: {}",
|
||||
component_names.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
writeln!(report).unwrap();
|
||||
|
||||
|
||||
for (info, _) in ecs.iter_resources() {
|
||||
let name = info.name();
|
||||
writeln!(report, "Resource: {name}").unwrap();
|
||||
// writeln!(report, "- Size: {} bytes",
|
||||
// info.layout().size()).unwrap();
|
||||
|
||||
match name {
|
||||
"azalea_world::container::InstanceContainer" => {
|
||||
let instance_container = ecs.resource::<InstanceContainer>();
|
||||
|
||||
for (instance_name, instance) in &instance_container.instances {
|
||||
writeln!(report, "- Name: {}", instance_name).unwrap();
|
||||
writeln!(report, "- Reference count: {}", instance.strong_count())
|
||||
.unwrap();
|
||||
if let Some(instance) = instance.upgrade() {
|
||||
let instance = instance.read();
|
||||
let strong_chunks = instance
|
||||
.chunks
|
||||
.map
|
||||
.iter()
|
||||
.filter(|(_, v)| v.strong_count() > 0)
|
||||
.count();
|
||||
writeln!(
|
||||
report,
|
||||
"- Chunks: {} strongly referenced, {} in map",
|
||||
strong_chunks,
|
||||
instance.chunks.map.len()
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
report,
|
||||
"- Entities: {}",
|
||||
instance.entities_by_chunk.len()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
"bevy_ecs::event::collections::Events<azalea_client::packet::game::ReceivePacketEvent>" => {
|
||||
let events = ecs.resource::<Events<game::ReceiveGamePacketEvent>>();
|
||||
writeln!(report, "- Event count: {}", events.len()).unwrap();
|
||||
}
|
||||
"bevy_ecs::event::collections::Events<azalea_client::chunks::ReceiveChunkEvent>" => {
|
||||
let events = ecs.resource::<Events<ReceiveChunkEvent>>();
|
||||
writeln!(report, "- Event count: {}", events.len()).unwrap();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
|
||||
});
|
||||
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(literal("exit").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.reply("bye!");
|
||||
|
||||
source.bot.disconnect();
|
||||
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
std::process::exit(0);
|
||||
});
|
||||
|
||||
1
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use azalea::{
|
||||
BlockPos, Client, Event,
|
||||
pathfinder::{Pathfinder, goals},
|
||||
prelude::PathfinderClientExt,
|
||||
};
|
||||
|
||||
use crate::command_controler::BotTask;
|
||||
|
||||
pub struct GotoBlock {
|
||||
x: i32,
|
||||
y: Option<i32>,
|
||||
z: i32,
|
||||
started: bool,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
impl GotoBlock {
|
||||
pub fn parse(args: Vec<String>) -> Option<Self> {
|
||||
if args.len() == 3 {
|
||||
let x = args[0].parse().ok()?;
|
||||
let y = args[1].parse().ok()?;
|
||||
let z = args[2].parse().ok()?;
|
||||
Some(Self {
|
||||
x,
|
||||
y: Some(y),
|
||||
z,
|
||||
started: false,
|
||||
finished: false,
|
||||
})
|
||||
} else if args.len() == 2 {
|
||||
let x = args[0].parse().ok()?;
|
||||
let z = args[1].parse().ok()?;
|
||||
Some(Self {
|
||||
x,
|
||||
y: None,
|
||||
z,
|
||||
started: false,
|
||||
finished: false,
|
||||
})
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BotTask for GotoBlock {
|
||||
fn on_event(&mut self, bot: &Client, event: &Event) {
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
bot.chat(
|
||||
format!("Going to ({}, {}, {})", self.x, self.y.unwrap_or(0), self.z).as_str(),
|
||||
);
|
||||
if let Some(y) = self.y {
|
||||
bot.start_goto(goals::BlockPosGoal(BlockPos {
|
||||
x: self.x,
|
||||
y,
|
||||
z: self.z,
|
||||
}));
|
||||
} else {
|
||||
bot.start_goto(goals::XZGoal {
|
||||
x: self.x,
|
||||
z: self.z,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
match event {
|
||||
Event::Tick => {
|
||||
self.finished = {
|
||||
let pos = bot.position().to_block_pos_floor();
|
||||
|
||||
pos.x == self.x
|
||||
&& pos.z == self.z
|
||||
&& (self.y.is_none() || pos.y == self.y.unwrap())
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn end(&self) -> bool {
|
||||
// bot.chat("Arrived!")
|
||||
self.finished
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// pub mod combat;
|
||||
// pub mod debug;
|
||||
// pub mod movement;
|
||||
pub mod chat_task;
|
||||
pub mod goto_block;
|
||||
|
||||
pub use chat_task::Chat;
|
||||
pub use goto_block::GotoBlock;
|
||||
@@ -0,0 +1,208 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use azalea::{
|
||||
BlockPos, SprintDirection, WalkDirection,
|
||||
brigadier::prelude::*,
|
||||
entity::{EyeHeight, Position},
|
||||
pathfinder::goals::{BlockPosGoal, RadiusGoal, XZGoal},
|
||||
prelude::*,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::BotTask;
|
||||
use crate::{CommandControler::Ctx, CommandSource};
|
||||
|
||||
pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||
commands.register(
|
||||
literal("goto")
|
||||
.executes(|ctx: &Ctx| {
|
||||
let mut source = ctx.source.lock();
|
||||
println!("got goto");
|
||||
// look for the sender
|
||||
let Some(entity) = source.entity() else {
|
||||
source.reply("I can't see you!");
|
||||
return 0;
|
||||
};
|
||||
let Some(position) = source.bot.get_entity_component::<Position>(entity) else {
|
||||
source.reply("I can't see you!");
|
||||
return 0;
|
||||
};
|
||||
source.reply("ok");
|
||||
source.bot.goto(BlockPosGoal(BlockPos::from(position)));
|
||||
1
|
||||
})
|
||||
.then(literal("xz").then(argument("x", integer()).then(
|
||||
argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let x = get_integer(ctx, "x").unwrap();
|
||||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto xz {x} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(XZGoal { x, z });
|
||||
1
|
||||
}),
|
||||
)))
|
||||
.then(literal("radius").then(argument("radius", float()).then(
|
||||
argument("x", integer()).then(argument("y", integer()).then(
|
||||
argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let radius = get_float(ctx, "radius").unwrap();
|
||||
let x = get_integer(ctx, "x").unwrap();
|
||||
let y = get_integer(ctx, "y").unwrap();
|
||||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto radius {radius}, position: {x} {y} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(RadiusGoal {
|
||||
pos: BlockPos::new(x, y, z).center(),
|
||||
radius,
|
||||
});
|
||||
1
|
||||
}),
|
||||
)),
|
||||
)))
|
||||
.then(argument("x", integer()).then(argument("y", integer()).then(
|
||||
argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
let x = get_integer(ctx, "x").unwrap();
|
||||
let y = get_integer(ctx, "y").unwrap();
|
||||
let z = get_integer(ctx, "z").unwrap();
|
||||
println!("goto xyz {x} {y} {z}");
|
||||
source.reply("ok");
|
||||
source.bot.goto(BlockPosGoal(BlockPos::new(x, y, z)));
|
||||
1
|
||||
}),
|
||||
))),
|
||||
);
|
||||
|
||||
commands.register(literal("down").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.clone();
|
||||
tokio::spawn(async move {
|
||||
let bot = source.lock().bot.clone();
|
||||
let position = BlockPos::from(bot.position());
|
||||
source.lock().reply("mining...");
|
||||
bot.mine(position.down(1)).await;
|
||||
source.lock().reply("done");
|
||||
});
|
||||
1
|
||||
}));
|
||||
|
||||
commands.register(
|
||||
literal("look")
|
||||
.executes(|ctx: &Ctx| {
|
||||
// look for the sender
|
||||
let mut source = ctx.source.lock();
|
||||
let Some(entity) = source.entity() else {
|
||||
source.reply("I can't see you!");
|
||||
return 0;
|
||||
};
|
||||
let Some(position) = source.bot.get_entity_component::<Position>(entity) else {
|
||||
source.reply("I can't see you!");
|
||||
return 0;
|
||||
};
|
||||
let eye_height = source
|
||||
.bot
|
||||
.get_entity_component::<EyeHeight>(entity)
|
||||
.map(|h| *h)
|
||||
.unwrap_or_default();
|
||||
source.bot.look_at(position.up(eye_height as f64));
|
||||
1
|
||||
})
|
||||
.then(argument("x", integer()).then(argument("y", integer()).then(
|
||||
argument("z", integer()).executes(|ctx: &Ctx| {
|
||||
let pos = BlockPos::new(
|
||||
get_integer(ctx, "x").unwrap(),
|
||||
get_integer(ctx, "y").unwrap(),
|
||||
get_integer(ctx, "z").unwrap(),
|
||||
);
|
||||
println!("{:?}", pos);
|
||||
let source = ctx.source.lock();
|
||||
source.bot.look_at(pos.center());
|
||||
1
|
||||
}),
|
||||
))),
|
||||
);
|
||||
|
||||
commands.register(
|
||||
literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
|
||||
let mut seconds = get_float(ctx, "seconds").unwrap();
|
||||
let source = ctx.source.lock();
|
||||
let bot = source.bot.clone();
|
||||
|
||||
if seconds < 0. {
|
||||
bot.walk(WalkDirection::Backward);
|
||||
seconds = -seconds;
|
||||
} else {
|
||||
bot.walk(WalkDirection::Forward);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
|
||||
bot.walk(WalkDirection::None);
|
||||
});
|
||||
source.reply(&format!("ok, walking for {seconds} seconds"));
|
||||
1
|
||||
})),
|
||||
);
|
||||
commands.register(
|
||||
literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
|
||||
let seconds = get_float(ctx, "seconds").unwrap();
|
||||
let source = ctx.source.lock();
|
||||
let bot = source.bot.clone();
|
||||
bot.sprint(SprintDirection::Forward);
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
|
||||
bot.walk(WalkDirection::None);
|
||||
});
|
||||
source.reply(&format!("ok, spriting for {seconds} seconds"));
|
||||
1
|
||||
})),
|
||||
);
|
||||
|
||||
commands.register(literal("north").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.set_direction(180., 0.);
|
||||
source.reply("ok");
|
||||
1
|
||||
}));
|
||||
commands.register(literal("south").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.set_direction(0., 0.);
|
||||
source.reply("ok");
|
||||
1
|
||||
}));
|
||||
commands.register(literal("east").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.set_direction(-90., 0.);
|
||||
source.reply("ok");
|
||||
1
|
||||
}));
|
||||
commands.register(literal("west").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.set_direction(90., 0.);
|
||||
source.reply("ok");
|
||||
1
|
||||
}));
|
||||
commands.register(
|
||||
literal("jump")
|
||||
.executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.jump();
|
||||
source.reply("ok");
|
||||
1
|
||||
})
|
||||
.then(argument("enabled", bool()).executes(|ctx: &Ctx| {
|
||||
let jumping = get_bool(ctx, "enabled").unwrap();
|
||||
let source = ctx.source.lock();
|
||||
source.bot.set_jumping(jumping);
|
||||
1
|
||||
})),
|
||||
);
|
||||
|
||||
commands.register(literal("stop").executes(|ctx: &Ctx| {
|
||||
let source = ctx.source.lock();
|
||||
source.bot.stop_pathfinding();
|
||||
source.reply("ok");
|
||||
*source.state.task.lock() = BotTask::None;
|
||||
1
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use azalea::{Client, Event};
|
||||
|
||||
use crate::bot_task::*;
|
||||
|
||||
pub trait BotTask: Send {
|
||||
fn on_event(&mut self, bot: &Client, event: &Event);
|
||||
fn end(&self) -> bool;
|
||||
}
|
||||
|
||||
pub fn parse_command(command: &str) -> Option<Box<dyn BotTask>> {
|
||||
let args = command
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
match args[0].as_str() {
|
||||
"!chat" => Some(Box::new(Chat::init(args[1..].to_vec()))),
|
||||
"!goto" => {
|
||||
if let Some(task) = GotoBlock::parse(args[1..].to_vec()) {
|
||||
Some(Box::new(task))
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use azalea::{
|
||||
ecs::prelude::*,
|
||||
entity::{Dead, LocalEntity, Position, metadata::AbstractMonster},
|
||||
prelude::*,
|
||||
world::{InstanceName, MinecraftEntityId},
|
||||
};
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub fn tick(bot: Client, state: State) -> anyhow::Result<()> {
|
||||
if !state.killaura {
|
||||
return Ok(());
|
||||
}
|
||||
if bot.has_attack_cooldown() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut nearest_entity = None;
|
||||
let mut nearest_distance = f64::INFINITY;
|
||||
let bot_position = bot.eye_position();
|
||||
let bot_instance_name = bot.component::<InstanceName>();
|
||||
{
|
||||
let mut ecs = bot.ecs.lock();
|
||||
let mut query = ecs
|
||||
.query_filtered::<(&MinecraftEntityId, &Position, &InstanceName), (
|
||||
With<AbstractMonster>,
|
||||
Without<LocalEntity>,
|
||||
Without<Dead>,
|
||||
)>();
|
||||
for (&entity_id, position, instance_name) in query.iter(&ecs) {
|
||||
if instance_name != &bot_instance_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
let distance = bot_position.distance_to(position);
|
||||
if distance < 4. && distance < nearest_distance {
|
||||
nearest_entity = Some(entity_id);
|
||||
nearest_distance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(nearest_entity) = nearest_entity {
|
||||
println!("attacking {:?}", nearest_entity);
|
||||
println!("distance {:?}", nearest_distance);
|
||||
bot.attack(nearest_entity);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+111
-237
@@ -1,257 +1,131 @@
|
||||
use std::{collections::HashSet, sync::Arc, thread::sleep, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod bot_task;
|
||||
pub mod command_controler;
|
||||
|
||||
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,
|
||||
swarm::{Swarm, SwarmBuilder, SwarmEvent},
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use command_controler::BotTask;
|
||||
use parking_lot::Mutex;
|
||||
// use parking_lot::Mutex;
|
||||
|
||||
pub static BOT_COUNT: usize = 3;
|
||||
pub static BOT_PREFIX: &'static str = "bot";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let account = Account::offline("bot");
|
||||
// or Account::microsoft("example@example.com").await.unwrap();
|
||||
|
||||
ClientBuilder::new()
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
SwarmBuilder::new()
|
||||
.add_accounts(
|
||||
(0..BOT_COUNT)
|
||||
.map(|i| Account::offline(format!("{}{}", BOT_PREFIX, i).as_str()))
|
||||
.collect(),
|
||||
)
|
||||
.set_handler(handle)
|
||||
.start(account, "localhost")
|
||||
.await
|
||||
.unwrap();
|
||||
.set_swarm_handler(handle_swarm)
|
||||
.start("localhost")
|
||||
.await?
|
||||
|
||||
// ClientBuilder::new()
|
||||
// .set_handler(handle)
|
||||
// .set_state(BotState {
|
||||
// commands: Arc::new(Mutex::new(Vec::new())),
|
||||
// })
|
||||
// .start(Account::offline("bot"), "localhost")
|
||||
// .await?;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum BotAction {
|
||||
Nothing,
|
||||
Goto(Position),
|
||||
PathfindMine(PathfindMineAction),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PathfindMineAction {
|
||||
Start,
|
||||
Goto(HashSet<BlockState>, BlockPos),
|
||||
Mine(HashSet<BlockState>, BlockPos),
|
||||
}
|
||||
|
||||
impl Default for BotAction {
|
||||
fn default() -> Self {
|
||||
Self::Nothing
|
||||
}
|
||||
}
|
||||
|
||||
pub static MINE_DISTANCE: i32 = 7;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref MINE_BLOCKS: HashSet<BlockState> = {
|
||||
let mut b = HashSet::new();
|
||||
|
||||
b.insert(Block::OakLog.into());
|
||||
|
||||
b
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Component)]
|
||||
pub struct State {
|
||||
pub action: Arc<Mutex<BotAction>>,
|
||||
// pub messages_received: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
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::<Position>(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;
|
||||
async fn handle(bot: Client, event: Event, state: BotState) -> anyhow::Result<()> {
|
||||
if state.task.lock().is_some() {
|
||||
// Process commands
|
||||
if state.task.lock().as_ref().unwrap().end() {
|
||||
bot.chat(
|
||||
format!(
|
||||
"Finished {}",
|
||||
print_type_of(&state.task.lock().as_ref().unwrap())
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
*state.task.lock() = None;
|
||||
} else {
|
||||
state.task.lock().as_mut().unwrap().on_event(&bot, &event);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
let swarm_state = bot.resource::<SwarmState>();
|
||||
if !swarm_state.tasks.lock().is_empty() {
|
||||
*state.task.lock() = Some(swarm_state.tasks.lock().remove(0));
|
||||
bot.chat(
|
||||
format!(
|
||||
"Starting {}",
|
||||
print_type_of(&state.task.lock().as_ref().unwrap())
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_goto_target_reached(bot: &Client) -> bool {
|
||||
bot.map_get_component::<Pathfinder, _>(|p| {
|
||||
p.map(|p| p.goal.is_none() && !p.is_calculating)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
#[derive(Default, Clone, Component)]
|
||||
pub struct BotState {
|
||||
pub task: Arc<Mutex<Option<Box<dyn BotTask>>>>,
|
||||
// pub messages_received: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
fn player_by_name(bot: &Client, name: String) -> Option<Entity> {
|
||||
bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
||||
|(profile,): &(&GameProfileComponent,)| {
|
||||
// return sender.unwrap() == profile.name;
|
||||
profile.name == name
|
||||
},
|
||||
)
|
||||
#[derive(Resource, Default, Clone)]
|
||||
struct SwarmState {
|
||||
pub tasks: Arc<Mutex<Vec<Box<dyn BotTask>>>>,
|
||||
}
|
||||
|
||||
async fn handle_swarm(swarm: Swarm, event: SwarmEvent, state: SwarmState) -> anyhow::Result<()> {
|
||||
match &event {
|
||||
SwarmEvent::Init => {
|
||||
println!("Swarm initialized");
|
||||
}
|
||||
SwarmEvent::Login => {
|
||||
println!("All bots have logged in");
|
||||
}
|
||||
SwarmEvent::Disconnect(account, join_opts) => {
|
||||
println!("Bot {} disconnected", account.username);
|
||||
swarm
|
||||
.add_with_opts(account, BotState::default(), join_opts)
|
||||
.await?;
|
||||
}
|
||||
SwarmEvent::Chat(msg) => {
|
||||
let command = msg.content();
|
||||
println!("Chat message: {}", command);
|
||||
|
||||
println!("{}", command);
|
||||
if let Some(command) = command_controler::parse_command(command.as_str()) {
|
||||
state.tasks.lock().push(command);
|
||||
}
|
||||
|
||||
// match state.commands.execute(
|
||||
// command,
|
||||
// Mutex::new(CommandSource {
|
||||
// bot: bot.clone(),
|
||||
// chat: chat.clone(),
|
||||
// state: state.clone(),
|
||||
// }),
|
||||
// ) {
|
||||
// Ok(_) => {}
|
||||
// Err(err) => {
|
||||
// eprintln!("{err:?}");
|
||||
// let command_source = CommandSource {
|
||||
// bot,
|
||||
// chat: chat.clone(),
|
||||
// state: state.clone(),
|
||||
// };
|
||||
// command_source.reply(&format!("{err:?}"));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_type_of<T>(_: &T) -> String {
|
||||
std::any::type_name::<T>().to_string()
|
||||
}
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
//! A relatively simple bot for demonstrating some of Azalea's capabilities.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! - Modify the consts below if necessary.
|
||||
//! - Run `cargo r --example testbot -- [arguments]`. (see below)
|
||||
//! - Commands are prefixed with `!` in chat. You can send them either in public
|
||||
//! chat or as a /msg.
|
||||
//! - Some commands to try are `!goto`, `!killaura true`, `!down`. Check the
|
||||
//! `commands` directory to see all of them.
|
||||
//!
|
||||
//! ### Arguments
|
||||
//!
|
||||
//! - `--owner` or `-O`: The username of the player who owns the bot. The bot
|
||||
//! will ignore commands from other players.
|
||||
//! - `--account` or `-A`: The username or email of the bot.
|
||||
//! - `--server` or `-S`: The address of the server to join.
|
||||
//! - `--pathfinder-debug-particles` or `-P`: Whether the bot should run
|
||||
//! /particle a ton of times to show where it's pathfinding to. You should
|
||||
//! only have this on if the bot has operator permissions, otherwise it'll
|
||||
//! just spam the server console unnecessarily.
|
||||
|
||||
#![feature(trivial_bounds)]
|
||||
|
||||
mod commands;
|
||||
pub mod killaura;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{env, process};
|
||||
use std::{sync::Arc, thread};
|
||||
|
||||
use azalea::ClientInformation;
|
||||
use azalea::brigadier::command_dispatcher::CommandDispatcher;
|
||||
use azalea::ecs::prelude::*;
|
||||
use azalea::pathfinder::debug::PathfinderDebugParticles;
|
||||
use azalea::prelude::*;
|
||||
use azalea::swarm::prelude::*;
|
||||
use commands::{CommandSource, register_commands};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = parse_args();
|
||||
|
||||
thread::spawn(deadlock_detection_thread);
|
||||
|
||||
let join_address = args.server.clone();
|
||||
|
||||
let mut builder = SwarmBuilder::new()
|
||||
.set_handler(handle)
|
||||
.set_swarm_handler(swarm_handle);
|
||||
|
||||
for username_or_email in &args.accounts {
|
||||
let account = if username_or_email.contains('@') {
|
||||
Account::microsoft(username_or_email).await.unwrap()
|
||||
} else {
|
||||
Account::offline(username_or_email)
|
||||
};
|
||||
|
||||
builder = builder.add_account_with_state(account, State::new());
|
||||
}
|
||||
|
||||
let mut commands = CommandDispatcher::new();
|
||||
register_commands(&mut commands);
|
||||
|
||||
builder
|
||||
.join_delay(Duration::from_millis(100))
|
||||
.set_swarm_state(SwarmState {
|
||||
args,
|
||||
commands: Arc::new(commands),
|
||||
})
|
||||
.start(join_address)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Runs a loop that checks for deadlocks every 10 seconds.
|
||||
///
|
||||
/// Note that this requires the `deadlock_detection` parking_lot feature to be
|
||||
/// enabled, which is only enabled in azalea by default when running in debug
|
||||
/// mode.
|
||||
fn deadlock_detection_thread() {
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
let deadlocks = parking_lot::deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("{} deadlocks detected", deadlocks.len());
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
println!("Deadlock #{i}");
|
||||
for t in threads {
|
||||
println!("Thread Id {:#?}", t.thread_id());
|
||||
println!("{:#?}", t.backtrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum BotTask {
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
pub struct State {
|
||||
pub killaura: bool,
|
||||
pub task: Arc<Mutex<BotTask>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
killaura: true,
|
||||
task: Arc::new(Mutex::new(BotTask::None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Clone)]
|
||||
struct SwarmState {
|
||||
pub args: Args,
|
||||
pub commands: Arc<CommandDispatcher<Mutex<CommandSource>>>,
|
||||
}
|
||||
|
||||
async fn handle(bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> {
|
||||
let swarm = bot.resource::<SwarmState>();
|
||||
|
||||
match event {
|
||||
azalea::Event::Init => {
|
||||
bot.set_client_information(ClientInformation {
|
||||
view_distance: 32,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
if swarm.args.pathfinder_debug_particles {
|
||||
bot.ecs
|
||||
.lock()
|
||||
.entity_mut(bot.entity)
|
||||
.insert(PathfinderDebugParticles);
|
||||
}
|
||||
}
|
||||
azalea::Event::Chat(chat) => {
|
||||
let (Some(username), content) = chat.split_sender_and_content() else {
|
||||
return Ok(());
|
||||
};
|
||||
if username != swarm.args.owner_username {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("{:?}", chat.message());
|
||||
|
||||
let command = if chat.is_whisper() {
|
||||
Some(content)
|
||||
} else {
|
||||
content.strip_prefix('!').map(|s| s.to_owned())
|
||||
};
|
||||
if let Some(command) = command {
|
||||
match swarm.commands.execute(
|
||||
command,
|
||||
Mutex::new(CommandSource {
|
||||
bot: bot.clone(),
|
||||
chat: chat.clone(),
|
||||
state: state.clone(),
|
||||
}),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("{err:?}");
|
||||
let command_source = CommandSource {
|
||||
bot,
|
||||
chat: chat.clone(),
|
||||
state: state.clone(),
|
||||
};
|
||||
command_source.reply(&format!("{err:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
azalea::Event::Tick => {
|
||||
killaura::tick(bot.clone(), state.clone())?;
|
||||
|
||||
let task = *state.task.lock();
|
||||
match task {
|
||||
BotTask::None => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn swarm_handle(_swarm: Swarm, event: SwarmEvent, _state: SwarmState) -> anyhow::Result<()> {
|
||||
match &event {
|
||||
SwarmEvent::Disconnect(account, _join_opts) => {
|
||||
println!("bot got kicked! {}", account.username);
|
||||
}
|
||||
SwarmEvent::Chat(chat) => {
|
||||
if chat.message().to_string() == "The particle was not visible for anybody" {
|
||||
return Ok(());
|
||||
}
|
||||
println!("{}", chat.message().to_ansi());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Args {
|
||||
pub owner_username: String,
|
||||
pub accounts: Vec<String>,
|
||||
pub server: String,
|
||||
pub pathfinder_debug_particles: bool,
|
||||
}
|
||||
|
||||
fn parse_args() -> Args {
|
||||
let mut owner_username = "admin".to_string();
|
||||
let mut accounts = Vec::new();
|
||||
let mut server = "localhost".to_string();
|
||||
let mut pathfinder_debug_particles = false;
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--owner" | "-O" => {
|
||||
owner_username = args.next().expect("Missing owner username");
|
||||
}
|
||||
"--account" | "-A" => {
|
||||
for account in args.next().expect("Missing account").split(',') {
|
||||
accounts.push(account.to_string());
|
||||
}
|
||||
}
|
||||
"--server" | "-S" => {
|
||||
server = args.next().expect("Missing server address");
|
||||
}
|
||||
"--pathfinder-debug-particles" | "-P" => {
|
||||
pathfinder_debug_particles = true;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown argument: {}", arg);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if accounts.is_empty() {
|
||||
accounts.push("azalea".to_string());
|
||||
}
|
||||
|
||||
Args {
|
||||
owner_username,
|
||||
accounts,
|
||||
server,
|
||||
pathfinder_debug_particles,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user