mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
parent
e0bcab53b8
commit
ba4cfaafae
17 changed files with 465 additions and 223 deletions
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -72,9 +72,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.56"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -103,8 +103,12 @@ name = "azalea"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"azalea-client",
|
||||
"azalea-protocol",
|
||||
"env_logger",
|
||||
"parking_lot 0.12.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -183,6 +187,7 @@ dependencies = [
|
|||
"azalea-protocol",
|
||||
"azalea-world",
|
||||
"log",
|
||||
"parking_lot 0.12.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"uuid",
|
||||
|
@ -338,11 +343,9 @@ name = "bot"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"azalea-client",
|
||||
"azalea-core",
|
||||
"azalea-physics",
|
||||
"azalea-protocol",
|
||||
"azalea",
|
||||
"env_logger",
|
||||
"parking_lot 0.12.1",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -1029,7 +1032,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
|||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
"parking_lot_core 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1046,6 +1059,19 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
@ -1393,18 +1419,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.34"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.34"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1517,7 +1543,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"log",
|
||||
"lru-cache",
|
||||
"parking_lot",
|
||||
"parking_lot 0.11.2",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
|
|
|
@ -23,7 +23,8 @@ I wanted a fun excuse to do something cool with Rust, and I also felt like I cou
|
|||
|
||||
- Do everything a vanilla client can do.
|
||||
- Be intuitive and easy to use.
|
||||
- Bypass most/all anticheats.
|
||||
- Make it easy to have many bots working at the same time.
|
||||
- Don't trigger anticheats.
|
||||
- Support the latest Minecraft version.
|
||||
- Be fast and memory efficient.
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ azalea-physics = {path = "../azalea-physics"}
|
|||
azalea-protocol = {path = "../azalea-protocol"}
|
||||
azalea-world = {path = "../azalea-world"}
|
||||
log = "0.4.17"
|
||||
parking_lot = "0.12.1"
|
||||
thiserror = "^1.0.34"
|
||||
tokio = {version = "^1.19.2", features = ["sync"]}
|
||||
uuid = "^1.1.2"
|
||||
|
|
|
@ -25,14 +25,13 @@ use azalea_protocol::{
|
|||
read::ReadPacketError,
|
||||
resolver, ServerAddress,
|
||||
};
|
||||
use azalea_world::entity::EntityData;
|
||||
use azalea_world::Dimension;
|
||||
use log::{debug, error, warn};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
io,
|
||||
sync::{Arc, Mutex},
|
||||
use azalea_world::{
|
||||
entity::{EntityData, EntityMut, EntityRef},
|
||||
Dimension,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use parking_lot::Mutex;
|
||||
use std::{fmt::Debug, io, sync::Arc};
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
|
@ -41,12 +40,14 @@ use tokio::{
|
|||
time::{self},
|
||||
};
|
||||
|
||||
/// Events are sent before they're processed, so for example game ticks happen
|
||||
/// at the beginning of a tick before anything has happened.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
Login,
|
||||
Chat(ChatPacket),
|
||||
/// A game tick, happens 20 times per second.
|
||||
GameTick,
|
||||
Tick,
|
||||
Packet(Box<ClientboundGamePacket>),
|
||||
}
|
||||
|
||||
|
@ -219,7 +220,7 @@ impl Client {
|
|||
// read the error to see where the issue is
|
||||
// you might be able to just drop the lock or put it in its own scope to fix
|
||||
{
|
||||
let mut tasks = client.tasks.lock().unwrap();
|
||||
let mut tasks = client.tasks.lock();
|
||||
tasks.push(tokio::spawn(Self::protocol_loop(
|
||||
client.clone(),
|
||||
tx.clone(),
|
||||
|
@ -238,7 +239,7 @@ impl Client {
|
|||
/// Disconnect from the server, ending all tasks.
|
||||
pub async fn shutdown(self) -> Result<(), std::io::Error> {
|
||||
self.write_conn.lock().await.write_stream.shutdown().await?;
|
||||
let tasks = self.tasks.lock().unwrap();
|
||||
let tasks = self.tasks.lock();
|
||||
for task in tasks.iter() {
|
||||
task.abort();
|
||||
}
|
||||
|
@ -346,7 +347,7 @@ impl Client {
|
|||
.as_int()
|
||||
.expect("min_y tag is not an int");
|
||||
|
||||
let mut dimension_lock = client.dimension.lock().unwrap();
|
||||
let mut dimension_lock = client.dimension.lock();
|
||||
// the 16 here is our render distance
|
||||
// i'll make this an actual setting later
|
||||
*dimension_lock = Dimension::new(16, height, min_y);
|
||||
|
@ -354,7 +355,7 @@ impl Client {
|
|||
let entity = EntityData::new(client.game_profile.uuid, Vec3::default());
|
||||
dimension_lock.add_entity(p.player_id, entity);
|
||||
|
||||
let mut player_lock = client.player.lock().unwrap();
|
||||
let mut player_lock = client.player.lock();
|
||||
|
||||
player_lock.set_entity_id(p.player_id);
|
||||
}
|
||||
|
@ -411,11 +412,11 @@ impl Client {
|
|||
|
||||
let (new_pos, y_rot, x_rot) = {
|
||||
let player_entity_id = {
|
||||
let player_lock = client.player.lock().unwrap();
|
||||
let player_lock = client.player.lock();
|
||||
player_lock.entity_id
|
||||
};
|
||||
|
||||
let mut dimension_lock = client.dimension.lock().unwrap();
|
||||
let mut dimension_lock = client.dimension.lock();
|
||||
|
||||
let mut player_entity = dimension_lock
|
||||
.entity_mut(player_entity_id)
|
||||
|
@ -503,7 +504,7 @@ impl Client {
|
|||
debug!("Got chunk cache center packet {:?}", p);
|
||||
client
|
||||
.dimension
|
||||
.lock()?
|
||||
.lock()
|
||||
.update_view_center(&ChunkPos::new(p.x, p.z));
|
||||
}
|
||||
ClientboundGamePacket::LevelChunkWithLight(p) => {
|
||||
|
@ -513,7 +514,7 @@ impl Client {
|
|||
// debug("chunk {:?}")
|
||||
client
|
||||
.dimension
|
||||
.lock()?
|
||||
.lock()
|
||||
.replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -523,7 +524,7 @@ impl Client {
|
|||
ClientboundGamePacket::AddEntity(p) => {
|
||||
debug!("Got add entity packet {:?}", p);
|
||||
let entity = EntityData::from(p);
|
||||
client.dimension.lock()?.add_entity(p.id, entity);
|
||||
client.dimension.lock().add_entity(p.id, entity);
|
||||
}
|
||||
ClientboundGamePacket::SetEntityData(_p) => {
|
||||
// debug!("Got set entity data packet {:?}", p);
|
||||
|
@ -540,7 +541,7 @@ impl Client {
|
|||
ClientboundGamePacket::AddPlayer(p) => {
|
||||
debug!("Got add player packet {:?}", p);
|
||||
let entity = EntityData::from(p);
|
||||
client.dimension.lock()?.add_entity(p.id, entity);
|
||||
client.dimension.lock().add_entity(p.id, entity);
|
||||
}
|
||||
ClientboundGamePacket::InitializeBorder(p) => {
|
||||
debug!("Got initialize border packet {:?}", p);
|
||||
|
@ -561,7 +562,7 @@ impl Client {
|
|||
debug!("Got set experience packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::TeleportEntity(p) => {
|
||||
let mut dimension_lock = client.dimension.lock()?;
|
||||
let mut dimension_lock = client.dimension.lock();
|
||||
|
||||
dimension_lock
|
||||
.set_entity_pos(
|
||||
|
@ -581,14 +582,14 @@ impl Client {
|
|||
// debug!("Got rotate head packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPos(p) => {
|
||||
let mut dimension_lock = client.dimension.lock()?;
|
||||
let mut dimension_lock = client.dimension.lock();
|
||||
|
||||
dimension_lock
|
||||
.move_entity_with_delta(p.entity_id, &p.delta)
|
||||
.map_err(|e| HandleError::Other(e.into()))?;
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPosRot(p) => {
|
||||
let mut dimension_lock = client.dimension.lock()?;
|
||||
let mut dimension_lock = client.dimension.lock();
|
||||
|
||||
dimension_lock
|
||||
.move_entity_with_delta(p.entity_id, &p.delta)
|
||||
|
@ -623,7 +624,7 @@ impl Client {
|
|||
}
|
||||
ClientboundGamePacket::BlockUpdate(p) => {
|
||||
debug!("Got block update packet {:?}", p);
|
||||
let mut dimension = client.dimension.lock()?;
|
||||
let mut dimension = client.dimension.lock();
|
||||
dimension.set_block_state(&p.pos, p.block_state);
|
||||
}
|
||||
ClientboundGamePacket::Animate(p) => {
|
||||
|
@ -725,10 +726,12 @@ impl Client {
|
|||
|
||||
/// Runs every 50 milliseconds.
|
||||
async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) {
|
||||
tx.send(Event::Tick).unwrap();
|
||||
|
||||
// return if there's no chunk at the player's position
|
||||
{
|
||||
let dimension_lock = client.dimension.lock().unwrap();
|
||||
let player_lock = client.player.lock().unwrap();
|
||||
let dimension_lock = client.dimension.lock();
|
||||
let player_lock = client.player.lock();
|
||||
let player_entity = player_lock.entity(&dimension_lock);
|
||||
let player_entity = if let Some(player_entity) = player_entity {
|
||||
player_entity
|
||||
|
@ -749,8 +752,27 @@ impl Client {
|
|||
client.ai_step();
|
||||
|
||||
// TODO: minecraft does ambient sounds here
|
||||
}
|
||||
|
||||
tx.send(Event::GameTick).unwrap();
|
||||
/// Returns the entity associated to the player.
|
||||
pub fn entity_mut<'d>(&self, dimension: &'d mut Dimension) -> EntityMut<'d> {
|
||||
let entity_id = {
|
||||
let player_lock = self.player.lock();
|
||||
player_lock.entity_id
|
||||
};
|
||||
dimension
|
||||
.entity_mut(entity_id)
|
||||
.expect("Player entity should be in the given dimension")
|
||||
}
|
||||
/// Returns the entity associated to the player.
|
||||
pub fn entity<'d>(&self, dimension: &'d Dimension) -> EntityRef<'d> {
|
||||
let entity_id = {
|
||||
let player_lock = self.player.lock();
|
||||
player_lock.entity_id
|
||||
};
|
||||
dimension
|
||||
.entity(entity_id)
|
||||
.expect("Player entity should be in the given dimension")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,9 +31,9 @@ impl Client {
|
|||
/// This gets called every tick.
|
||||
pub async fn send_position(&mut self) -> Result<(), MovePlayerError> {
|
||||
let packet = {
|
||||
let player_lock = self.player.lock().unwrap();
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
let player_lock = self.player.lock();
|
||||
let mut physics_state = self.physics_state.lock();
|
||||
let mut dimension_lock = self.dimension.lock();
|
||||
|
||||
let mut player_entity = player_lock
|
||||
.entity_mut(&mut dimension_lock)
|
||||
|
@ -129,8 +129,8 @@ impl Client {
|
|||
|
||||
// Set our current position to the provided Vec3, potentially clipping through blocks.
|
||||
pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> {
|
||||
let player_lock = self.player.lock().unwrap();
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
let player_lock = self.player.lock();
|
||||
let mut dimension_lock = self.dimension.lock();
|
||||
|
||||
dimension_lock.set_entity_pos(player_lock.entity_id, new_pos)?;
|
||||
|
||||
|
@ -138,8 +138,8 @@ impl Client {
|
|||
}
|
||||
|
||||
pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> {
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
let player = self.player.lock().unwrap();
|
||||
let mut dimension_lock = self.dimension.lock();
|
||||
let player = self.player.lock();
|
||||
|
||||
let mut entity = player
|
||||
.entity_mut(&mut dimension_lock)
|
||||
|
@ -157,25 +157,17 @@ impl Client {
|
|||
pub fn ai_step(&mut self) {
|
||||
self.tick_controls(None);
|
||||
|
||||
let player_lock = self.player.lock().unwrap();
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
|
||||
let player_lock = self.player.lock();
|
||||
let mut dimension_lock = self.dimension.lock();
|
||||
let mut player_entity = player_lock
|
||||
.entity_mut(&mut dimension_lock)
|
||||
.expect("Player must exist");
|
||||
|
||||
// server ai step
|
||||
{
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
let physics_state = self.physics_state.lock();
|
||||
player_entity.xxa = physics_state.left_impulse;
|
||||
player_entity.zza = physics_state.forward_impulse;
|
||||
|
||||
// handle jumping_once
|
||||
if physics_state.jumping_once {
|
||||
player_entity.jumping = true;
|
||||
} else if player_entity.jumping {
|
||||
physics_state.jumping_once = false;
|
||||
}
|
||||
}
|
||||
|
||||
player_entity.ai_step();
|
||||
|
@ -183,7 +175,7 @@ impl Client {
|
|||
|
||||
/// Update the impulse from self.move_direction. The multipler is used for sneaking.
|
||||
pub(crate) fn tick_controls(&mut self, multiplier: Option<f32>) {
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
let mut physics_state = self.physics_state.lock();
|
||||
|
||||
let mut forward_impulse: f32 = 0.;
|
||||
let mut left_impulse: f32 = 0.;
|
||||
|
@ -219,31 +211,29 @@ impl Client {
|
|||
|
||||
/// Start walking in the given direction.
|
||||
pub fn walk(&mut self, direction: MoveDirection) {
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
let mut physics_state = self.physics_state.lock();
|
||||
physics_state.move_direction = direction;
|
||||
}
|
||||
|
||||
/// Jump once next tick. This acts as if you pressed space for one tick in
|
||||
/// vanilla. If you want to jump continuously, use `set_jumping`.
|
||||
pub fn jump(&mut self) {
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
physics_state.jumping_once = true;
|
||||
}
|
||||
|
||||
/// Toggle whether we're jumping. This acts as if you held space in
|
||||
/// vanilla. If you want to jump once, use the `jump` function.
|
||||
///
|
||||
/// If you're making a realistic client, calling this function every tick is
|
||||
/// recommended.
|
||||
pub fn set_jumping(&mut self, jumping: bool) {
|
||||
let player_lock = self.player.lock().unwrap();
|
||||
let mut dimension_lock = self.dimension.lock().unwrap();
|
||||
let mut player_entity = player_lock
|
||||
.entity_mut(&mut dimension_lock)
|
||||
.expect("Player must exist");
|
||||
let mut dimension = self.dimension.lock();
|
||||
let mut player_entity = self.entity_mut(&mut dimension);
|
||||
|
||||
player_entity.jumping = jumping;
|
||||
}
|
||||
|
||||
/// Returns whether the player will try to jump next tick.
|
||||
pub fn jumping(&self) -> bool {
|
||||
let dimension = self.dimension.lock();
|
||||
let player_entity = self.entity(&dimension);
|
||||
|
||||
player_entity.jumping
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
|
|
@ -9,7 +9,13 @@ version = "0.1.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "^1.0.65"
|
||||
async-trait = "^0.1.57"
|
||||
azalea-client = {version = "0.1.0", path = "../azalea-client"}
|
||||
azalea-protocol = {version = "0.1.0", path = "../azalea-protocol"}
|
||||
parking_lot = "^0.12.1"
|
||||
thiserror = "^1.0.37"
|
||||
tokio = "^1.21.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "^1.0.65"
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
A wrapper over azalea-client, adding useful functions for making bots.
|
||||
A framework for creating Minecraft bots.
|
||||
|
||||
Interally, it's just a wrapper over azalea-client, adding useful functions for making bots.
|
||||
|
|
|
@ -1,45 +1,66 @@
|
|||
use azalea::{Bot, Event};
|
||||
use azalea::{pathfinder, Account};
|
||||
use azalea::{Bot, Client, Event};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct Context {
|
||||
pub started: bool
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
pub started: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let bot = Bot::offline("bot");
|
||||
let account = Account::offline("bot");
|
||||
// or let bot = azalea::Bot::microsoft("access token").await;
|
||||
|
||||
bot.join("localhost".try_into().unwrap()).await.unwrap();
|
||||
|
||||
let ctx = Arc::new(Mutex::new(Context { started: false }));
|
||||
|
||||
loop {
|
||||
tokio::spawn(handle_event(bot.next().await, bot, ctx.clone()));
|
||||
}
|
||||
azalea::start(azalea::Options {
|
||||
account,
|
||||
address: "localhost",
|
||||
state: Arc::new(Mutex::new(State::default())),
|
||||
plugins: vec![],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
||||
async fn handle_event(event: &Event, bot: &Bot, ctx: Arc<Context>) {
|
||||
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) {
|
||||
match event {
|
||||
Event::Message(m) {
|
||||
if m.username == bot.player.username { return };
|
||||
Event::Message(m) => {
|
||||
if m.username == bot.player.username {
|
||||
return;
|
||||
};
|
||||
if m.message = "go" {
|
||||
// make sure we only start once
|
||||
let ctx_lock = ctx.lock().unwrap();
|
||||
if ctx_lock.started { return };
|
||||
if ctx_lock.started {
|
||||
return;
|
||||
};
|
||||
ctx_lock.started = true;
|
||||
drop(ctx_lock);
|
||||
|
||||
bot.goto(
|
||||
pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))
|
||||
).await;
|
||||
let chest = bot.open_container(&bot.world.find_one_block(|b| b.id == "minecraft:chest")).await.unwrap();
|
||||
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks").await;
|
||||
bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)))
|
||||
.await;
|
||||
let chest = bot
|
||||
.open_container(&bot.world.find_one_block(|b| b.id == "minecraft:chest"))
|
||||
.await
|
||||
.unwrap();
|
||||
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks")
|
||||
.await;
|
||||
chest.close().await;
|
||||
|
||||
let crafting_table = bot.open_crafting_table(&bot.world.find_one_block(|b| b.id == "minecraft:crafting_table")).await.unwrap();
|
||||
bot.craft(&crafting_table, &bot.recipe_for("minecraft:sticks")).await?;
|
||||
let pickaxe = bot.craft(&crafting_table, &bot.recipe_for("minecraft:wooden_pickaxe")).await?;
|
||||
let crafting_table = bot
|
||||
.open_crafting_table(
|
||||
&bot.world
|
||||
.find_one_block(|b| b.id == "minecraft:crafting_table"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
bot.craft(&crafting_table, &bot.recipe_for("minecraft:sticks"))
|
||||
.await?;
|
||||
let pickaxe = bot
|
||||
.craft(&crafting_table, &bot.recipe_for("minecraft:wooden_pickaxe"))
|
||||
.await?;
|
||||
crafting_table.close().await;
|
||||
|
||||
bot.hold(&pickaxe);
|
||||
|
@ -50,7 +71,7 @@ async fn handle_event(event: &Event, bot: &Bot, ctx: Arc<Context>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
|
@ -1,38 +1,46 @@
|
|||
use azalea::{Account, Event};
|
||||
use std::sync::Arc;
|
||||
|
||||
let account = Account::offline("bot");
|
||||
// or let account = azalea::Account::microsoft("access token").await;
|
||||
use azalea::{Account, Client, Event};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
let bot = account.join("localhost".try_into().unwrap()).await.unwrap();
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let account = Account::offline("bot");
|
||||
// or let account = azalea::Account::microsoft("access token").await;
|
||||
|
||||
loop {
|
||||
match bot.next().await {
|
||||
Event::Message(m) {
|
||||
if m.username == bot.username { return };
|
||||
azalea::start(azalea::Options {
|
||||
account,
|
||||
address: "localhost",
|
||||
state: Arc::new(Mutex::new(State::default())),
|
||||
plugins: vec![],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub struct State {}
|
||||
|
||||
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Chat(m) => {
|
||||
if m.username == bot.username {
|
||||
return Ok(()); // ignore our own messages
|
||||
};
|
||||
bot.chat(m.message).await;
|
||||
},
|
||||
Event::Kicked(m) {
|
||||
}
|
||||
Event::Kick(m) => {
|
||||
println!(m);
|
||||
bot.reconnect().await.unwrap();
|
||||
},
|
||||
Event::Hunger(h) {
|
||||
}
|
||||
Event::HungerUpdate(h) => {
|
||||
if !h.using_held_item() && h.hunger <= 17 {
|
||||
match bot.hold(azalea::ItemGroup::Food).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
match bot.use_held_item().await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
bot.hold(azalea::ItemGroup::Food).await?;
|
||||
bot.use_held_item().await?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use azalea::{Account, Accounts, Event, pathfinder};
|
||||
|
||||
// You can use the `azalea::Bots` struct to control many bots as one unit.
|
||||
use azalea::{pathfinder, Account, Accounts, Client, Event};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
@ -10,18 +10,60 @@ async fn main() {
|
|||
accounts.add(Account::offline(format!("bot{}", i)));
|
||||
}
|
||||
|
||||
let bots = accounts.join("localhost".try_into().unwrap()).await.unwrap();
|
||||
azalea::start_group(azalea::GroupOptions {
|
||||
accounts,
|
||||
address: "localhost",
|
||||
|
||||
bots.goto(azalea::BlockPos::new(0, 70, 0)).await;
|
||||
// or bots.goto_goal(pathfinder::Goals::Goto(azalea::BlockPos(0, 70, 0))).await;
|
||||
group_state: Arc::new(Mutex::new(State::default())),
|
||||
state: State::default(),
|
||||
|
||||
// destroy the blocks in this area and then leave
|
||||
group_plugins: vec![Arc::new(pathfinder::Plugin::default())],
|
||||
plugins: vec![],
|
||||
|
||||
bots.fill(
|
||||
azalea::Selection::Range(
|
||||
azalea::BlockPos::new(0, 0, 0),
|
||||
azalea::BlockPos::new(16, 255, 16)
|
||||
),
|
||||
azalea::block::Air
|
||||
).await;
|
||||
handle: Box::new(handle),
|
||||
group_handle: Box::new(handle),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GroupState {}
|
||||
|
||||
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
match event {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn group_handle(
|
||||
bots: Swarm,
|
||||
event: Arc<Event>,
|
||||
state: Arc<Mutex<GroupState>>,
|
||||
) -> anyhow::Result<()> {
|
||||
match *event {
|
||||
Event::Login => {
|
||||
bots.goto(azalea::BlockPos::new(0, 70, 0)).await;
|
||||
// or bots.goto_goal(pathfinder::Goals::Goto(azalea::BlockPos(0, 70, 0))).await;
|
||||
|
||||
// destroy the blocks in this area and then leave
|
||||
|
||||
bots.fill(
|
||||
azalea::Selection::Range(
|
||||
azalea::BlockPos::new(0, 0, 0),
|
||||
azalea::BlockPos::new(16, 255, 16),
|
||||
),
|
||||
azalea::block::Air,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
//! Automatically eat when we get hungry.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use azalea::{Client, Event};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Plugin {
|
||||
pub state: Arc<Mutex<State>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {}
|
||||
|
||||
pub async fn handle(bot: &mut Client, event: Event, state: Arc<Mutex<State>>) {
|
||||
match event {
|
||||
Event::UpdateHunger => {
|
||||
if !bot.using_held_item() && bot.food_level() <= 17 {
|
||||
if bot.hold(azalea::ItemGroup::Food).await {
|
||||
bot.use_held_item().await;
|
||||
#[async_trait]
|
||||
impl azalea::Plugin for Plugin {
|
||||
async fn handle(self: Arc<Self>, bot: Client, event: Arc<Event>) {
|
||||
match event {
|
||||
Event::UpdateHunger => {
|
||||
if !bot.using_held_item() && bot.food_level() <= 17 {
|
||||
if bot.hold(azalea::ItemGroup::Food).await {
|
||||
bot.use_held_item().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +1,34 @@
|
|||
mod autoeat;
|
||||
|
||||
use azalea::{pathfinder, Account, BlockPos, Client, Event, ItemKind, MoveDirection, Vec3};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use azalea::prelude::*;
|
||||
use azalea::{pathfinder, Account, BlockPos, Client, Event, ItemKind, MoveDirection, Plugin, Vec3};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
pub eating: bool,
|
||||
}
|
||||
struct State {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let account = Account::offline("bot");
|
||||
let (bot, mut rx) = account
|
||||
.join(&"localhost".try_into().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Maybe all this could be turned into a macro in the future?
|
||||
let state = Arc::new(Mutex::new(State::default()));
|
||||
let autoeat_state = Arc::new(Mutex::new(autoeat::State::default()));
|
||||
let pathfinder_state = Arc::new(Mutex::new(pathfinder::State::default()));
|
||||
while let Some(event) = rx.recv().await {
|
||||
// we put it into an Arc so it's cheaper to clone
|
||||
let event = Arc::new(event);
|
||||
|
||||
tokio::spawn(autoeat::handle(
|
||||
bot.clone(),
|
||||
event.clone(),
|
||||
autoeat_state.clone(),
|
||||
));
|
||||
tokio::spawn(pathfinder::handle(
|
||||
bot.clone(),
|
||||
event.clone(),
|
||||
pathfinder_state.clone(),
|
||||
));
|
||||
tokio::spawn(handle(bot.clone(), event.clone(), state.clone()));
|
||||
}
|
||||
azalea::start(azalea::Options {
|
||||
account,
|
||||
address: "localhost",
|
||||
state: Arc::new(Mutex::new(State::default())),
|
||||
plugins: vec![
|
||||
Arc::new(autoeat::Plugin::default()),
|
||||
Arc::new(pathfinder::Plugin::default()),
|
||||
],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handle(bot: Client, event: Event, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
async fn handle(bot: Client, event: Arc<Event>, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Login => {
|
||||
goto_farm(bot, state).await?;
|
||||
|
@ -58,8 +43,7 @@ async fn handle(bot: Client, event: Event, state: Arc<Mutex<State>>) -> anyhow::
|
|||
|
||||
// go to the place where we start farming
|
||||
async fn goto_farm(bot: Client, state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
bot.state
|
||||
.goto(pathfinder::Goals::Near(5, BlockPos::new(0, 70, 0)))
|
||||
bot.goto(pathfinder::Goals::Near(5, BlockPos::new(0, 70, 0)))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -69,7 +53,7 @@ async fn deposit(bot: &mut Client, state: &mut Arc<Mutex<State>>) -> anyhow::Res
|
|||
// first throw away any garbage we might have
|
||||
bot.toss(|item| item.kind != ItemKind::Potato && item.kind != ItemKind::DiamondHoe);
|
||||
|
||||
bot.state.goto(Vec3::new(0, 70, 0)).await?;
|
||||
bot.goto(Vec3::new(0, 70, 0)).await?;
|
||||
let chest = bot
|
||||
.open_container(&bot.dimension.block_at(BlockPos::new(0, 70, 0)))
|
||||
.await
|
||||
|
|
|
@ -1,22 +1,46 @@
|
|||
use azalea::{Account, Accounts, Event, pathfinder};
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea::{pathfinder, Account, Accounts, Client, Event};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let accounts = Accounts::new();
|
||||
|
||||
for i in 0..10 {
|
||||
accounts.add(Account::offline(format!("bot{}", i)));
|
||||
}
|
||||
|
||||
let bots = accounts.join("localhost".try_into().unwrap()).await.unwrap();
|
||||
azalea::start_swarm(azalea::SwarmOptions {
|
||||
accounts,
|
||||
address: "localhost",
|
||||
|
||||
match bots.next().await {
|
||||
Event::Tick {
|
||||
swarm_state: Arc::new(Mutex::new(State::default())),
|
||||
state: State::default(),
|
||||
|
||||
swarm_plugins: vec![Arc::new(pathfinder::Plugin::default())],
|
||||
plugins: vec![],
|
||||
|
||||
handle: Box::new(handle),
|
||||
swarm_handle: Box::new(handle),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct State {}
|
||||
struct SwarmState {}
|
||||
|
||||
async fn handle(bots: Client, event: Arc<Event>, state: Arc<Mutex<State>>) {
|
||||
match *event {
|
||||
Event::Tick => {
|
||||
// choose an arbitrary player within render distance to target
|
||||
if let Some(target) = bots.world.find_one_entity(|e| e.id == "minecraft:player") {
|
||||
if let Some(target) = bots
|
||||
.dimension()
|
||||
.find_one_entity(|e| e.id == "minecraft:player")
|
||||
{
|
||||
for bot in bots {
|
||||
bot.tick_goto_goal(
|
||||
pathfinder::Goals::Reach(target.bounding_box)
|
||||
);
|
||||
bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box));
|
||||
// if target.bounding_box.distance(bot.eyes) < bot.reach_distance() {
|
||||
if bot.entity.can_reach(target.bounding_box) {
|
||||
bot.swing();
|
||||
|
@ -27,7 +51,7 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,46 @@
|
|||
pub struct BotState {
|
||||
use crate::{Client, Event};
|
||||
use async_trait::async_trait;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Plugin {
|
||||
pub state: Arc<Mutex<State>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
jumping_once: bool,
|
||||
}
|
||||
|
||||
pub trait BotTrait {
|
||||
fn jump(&mut self);
|
||||
fn jump(&self);
|
||||
}
|
||||
|
||||
impl BotTrait for azalea_client::Client {
|
||||
fn jump(&mut self) {
|
||||
let mut physics_state = self.physics_state.lock().unwrap();
|
||||
physics_state.jumping_once = true;
|
||||
/// Try to jump next tick.
|
||||
fn jump(&self) {
|
||||
let player_lock = self.player.lock();
|
||||
let mut dimension_lock = self.dimension.lock();
|
||||
|
||||
let mut player_entity = player_lock
|
||||
.entity_mut(&mut dimension_lock)
|
||||
.expect("Player must exist");
|
||||
|
||||
player_entity.jumping = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Plugin for Plugin {
|
||||
async fn handle(self: Arc<Self>, mut bot: Client, event: Arc<Event>) {
|
||||
if let Event::Tick = *event {
|
||||
let mut state = self.state.lock();
|
||||
if bot.jumping() {
|
||||
state.jumping_once = false;
|
||||
} else if state.jumping_once {
|
||||
bot.set_jumping(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,84 @@
|
|||
mod bot;
|
||||
pub mod prelude;
|
||||
|
||||
pub use azalea_client::Client;
|
||||
use async_trait::async_trait;
|
||||
pub use azalea_client::*;
|
||||
use azalea_protocol::ServerAddress;
|
||||
use parking_lot::Mutex;
|
||||
use std::{future::Future, sync::Arc};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Plugins can keep their own personal state, listen to events, and add new functions to Client.
|
||||
#[async_trait]
|
||||
pub trait Plugin: Send + Sync {
|
||||
async fn handle(self: Arc<Self>, bot: Client, event: Arc<Event>);
|
||||
}
|
||||
|
||||
// pub type HeuristicFn<N, W> = fn(start: &Vertex<N, W>, current: &Vertex<N, W>) -> W;
|
||||
pub type HandleFn<Fut, S> = fn(Client, Arc<Event>, Arc<Mutex<S>>) -> Fut;
|
||||
|
||||
pub struct Options<S, A, Fut>
|
||||
where
|
||||
A: TryInto<ServerAddress>,
|
||||
Fut: Future<Output = Result<(), anyhow::Error>>,
|
||||
{
|
||||
pub address: A,
|
||||
pub account: Account,
|
||||
pub plugins: Vec<Arc<dyn Plugin>>,
|
||||
pub state: Arc<Mutex<S>>,
|
||||
pub handle: HandleFn<Fut, S>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Invalid address")]
|
||||
InvalidAddress,
|
||||
}
|
||||
|
||||
/// Join a Minecraft server.
|
||||
///
|
||||
/// ```no_run
|
||||
/// azalea::start(azalea::Options {
|
||||
/// account,
|
||||
/// address: "localhost",
|
||||
/// state: Arc::new(Mutex::new(State::default())),
|
||||
/// plugins: vec![&autoeat::Plugin::default()],
|
||||
/// handle: Box::new(handle),
|
||||
/// }).await.unwrap();
|
||||
/// ```
|
||||
pub async fn start<
|
||||
S: Send + 'static,
|
||||
A: Send + TryInto<ServerAddress>,
|
||||
Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
|
||||
>(
|
||||
options: Options<S, A, Fut>,
|
||||
) -> Result<(), Error> {
|
||||
let address = match options.address.try_into() {
|
||||
Ok(address) => address,
|
||||
Err(_) => return Err(Error::InvalidAddress),
|
||||
};
|
||||
|
||||
let (bot, mut rx) = options.account.join(&address).await.unwrap();
|
||||
|
||||
let state = options.state;
|
||||
let bot_plugin = Arc::new(bot::Plugin::default());
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
// we put it into an Arc so it's cheaper to clone
|
||||
let event = Arc::new(event);
|
||||
|
||||
for plugin in &options.plugins {
|
||||
tokio::spawn(plugin.clone().handle(bot.clone(), event.clone()));
|
||||
}
|
||||
|
||||
{
|
||||
let bot_plugin = bot_plugin.clone();
|
||||
let bot = bot.clone();
|
||||
let event = event.clone();
|
||||
tokio::spawn(bot::Plugin::handle(bot_plugin, bot, event));
|
||||
};
|
||||
tokio::spawn((options.handle)(bot.clone(), event.clone(), state.clone()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,10 +7,8 @@ version = "0.1.0"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.65"
|
||||
azalea-client = {path = "../azalea-client"}
|
||||
azalea-core = {path = "../azalea-core"}
|
||||
azalea-physics = {path = "../azalea-physics"}
|
||||
azalea-protocol = {path = "../azalea-protocol"}
|
||||
azalea = { path = "../azalea" }
|
||||
env_logger = "0.9.1"
|
||||
tokio = "1.19.2"
|
||||
uuid = "1.1.2"
|
||||
parking_lot = "^0.12.1"
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
use azalea_client::{Account, Client, Event, MoveDirection};
|
||||
use std::convert::TryInto;
|
||||
use azalea::prelude::*;
|
||||
use azalea::{Account, Client, Event};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let bot = Account::offline("bot");
|
||||
let account = Account::offline("bot");
|
||||
|
||||
let (bot, mut rx) = bot.join(&"localhost".try_into().unwrap()).await.unwrap();
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
tokio::spawn(handle_event(event, bot.clone()));
|
||||
}
|
||||
azalea::start(azalea::Options {
|
||||
account,
|
||||
address: "localhost",
|
||||
state: Arc::new(Mutex::new(State::default())),
|
||||
plugins: vec![],
|
||||
handle,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handle_event(event: Event, mut bot: Client) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Login => {
|
||||
// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
// bot.walk(MoveDirection::Forward);
|
||||
|
||||
// loop {
|
||||
// tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
// }
|
||||
// bot.walk(MoveDirection::None);
|
||||
}
|
||||
Event::GameTick => {
|
||||
bot.set_jumping(true);
|
||||
}
|
||||
Event::Packet(_packet) => {}
|
||||
_ => {}
|
||||
async fn handle(bot: Client, event: Arc<Event>, _state: Arc<Mutex<State>>) -> anyhow::Result<()> {
|
||||
if let Event::Tick = *event {
|
||||
bot.jump();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Reference in a new issue