refactor: random fixes and usage improvements

This commit is contained in:
Ryan 2025-02-17 17:01:23 -05:00
parent 8f2fbf11da
commit dde489a8ed
Signed by: ErrorNoInternet
GPG Key ID: 2486BFB7B1E6A4A3
15 changed files with 203 additions and 90 deletions

View File

@ -12,17 +12,16 @@ opt-level = 3
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
lto = true lto = true
panic = "abort"
strip = true strip = true
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
azalea = { git = "https://github.com/azalea-rs/azalea.git" } azalea = { git = "https://github.com/azalea-rs/azalea.git" }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
futures = "0"
http-body-util = "0" http-body-util = "0"
hyper = { version = "1", features = ["server"] } hyper = { version = "1", features = ["server"] }
hyper-util = "0" hyper-util = "0"
log = { version = "0" } log = { version = "0" }
mlua = { version = "0", features = ["async", "luau", "send"] } mlua = { version = "0", features = ["async", "luau", "send"] }
tokio = { version = "1", features = ["macros"] } tokio = { version = "1", features = ["macros"] }
futures = "0"

View File

@ -6,6 +6,8 @@ for _, module in
{ {
"enum", "enum",
"events", "events",
"inventory",
"movement",
"utils", "utils",
} }
do do

View File

@ -1,5 +1,5 @@
local center = { x = 0, z = 0 } local center = { x = 0, z = 0 }
local radius = 50 local radius = 100
function on_tick() function on_tick()
local entities = client:find_entities(function(e) local entities = client:find_entities(function(e)
@ -15,6 +15,10 @@ function on_tick()
end end
function on_init() function on_init()
info("client initialized, setting client information") info("client initialized, setting information...")
client:set_client_information({ view_distance = 16 }) client:set_client_information({ view_distance = 16 })
end end
function on_login()
info("player successfully logged in!")
end

38
lib/inventory.lua Normal file
View File

@ -0,0 +1,38 @@
function steal(item_name)
for _, chest_pos in client:find_blocks(client.position, get_block_states({ "chest" })) do
client:chat(dump(chest_pos))
client:goto({ position = chest_pos, radius = 3 }, { type = RADIUS_GOAL })
while client.pathfinder.is_calculating or client.pathfinder.is_executing do
sleep(50)
end
client:look_at(chest_pos)
local container = client:open_container_at(chest_pos)
for index, item in container.contents do
if item.kind == item_name then
container:click({slot = index - 1}, THROW_ALL)
sleep(50)
end
end
container = nil
while client.open_container do
sleep(50)
end
end
end
function drop_all_hotbar()
local inventory = client:open_inventory()
for i = 0, 9 do
inventory:click({slot = 36 + i}, THROW_ALL)
end
end
function drop_all_inventory()
local inventory = client:open_inventory()
for i = 0, 45 do
inventory:click({slot = i}, THROW_ALL)
end
end

18
lib/movement.lua Normal file
View File

@ -0,0 +1,18 @@
function look_at_player(name)
local player = get_player(name)
if player then
player.position.y = player.position.y + 1
client:look_at(player.position)
else
client:chat("player not found!")
end
end
function goto_player(name)
local player = get_player(name)
if player then
client:goto(player.position)
else
client:chat("player not found!")
end
end

View File

@ -1,3 +1,17 @@
function get_player(name)
local target_uuid = nil
for uuid, player in client.tab_list do
if player.name == name then
target_uuid = uuid
break
end
end
return client:find_entities(function(e)
return e.kind == "minecraft:player" and e.uuid == target_uuid
end)[1]
end
function dump(object) function dump(object)
if type(object) == "table" then if type(object) == "table" then
local string = "{ " local string = "{ "

View File

@ -7,6 +7,7 @@ use mlua::{Function, IntoLuaMulti, Table};
use tokio::net::TcpListener; use tokio::net::TcpListener;
pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> { pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
state.lua.gc_stop();
let globals = state.lua.globals(); let globals = state.lua.globals();
match event { match event {

View File

@ -24,8 +24,8 @@ pub async fn serve(
let bytes = request.into_body().collect().await?.to_bytes(); let bytes = request.into_body().collect().await?.to_bytes();
match std::str::from_utf8(&bytes) { match std::str::from_utf8(&bytes) {
Ok(code) => Response::new(full(match path.as_str() { Ok(code) => Response::new(full(match path.as_str() {
"/eval" => format!("{:?}", eval(&state.lua, code).await), "/eval" => format!("{:#?}", eval(&state.lua, code).await),
"/exec" => format!("{:?}", exec(&state.lua, code).await), "/exec" => format!("{:#?}", exec(&state.lua, code).await),
_ => unreachable!(), _ => unreachable!(),
})), })),
Err(error) => status_code_response( Err(error) => status_code_response(

View File

@ -1,4 +1,8 @@
use mlua::{FromLua, IntoLua, Lua, Result, Value}; use azalea::blocks::{
Block as AzaleaBlock, BlockState,
properties::{ChestType, Facing, LightLevel},
};
use mlua::{FromLua, Function, IntoLua, Lua, Result, Table, Value};
#[derive(Clone)] #[derive(Clone)]
pub struct Block { pub struct Block {
@ -46,3 +50,57 @@ impl FromLua for Block {
} }
} }
} }
pub fn register_functions(lua: &Lua, globals: &Table) -> Result<()> {
globals.set(
"get_block_from_state",
lua.create_function(get_block_from_state)?,
)?;
globals.set("get_block_states", lua.create_function(get_block_states)?)?;
Ok(())
}
pub fn get_block_from_state(_lua: &Lua, state: u32) -> Result<Option<Block>> {
let Ok(state) = BlockState::try_from(state) else {
return Ok(None);
};
let block: Box<dyn AzaleaBlock> = state.into();
let behavior = block.behavior();
Ok(Some(Block {
id: block.id().to_string(),
friction: behavior.friction,
jump_factor: behavior.jump_factor,
destroy_time: behavior.destroy_time,
explosion_resistance: behavior.explosion_resistance,
requires_correct_tool_for_drops: behavior.requires_correct_tool_for_drops,
}))
}
pub fn get_block_states(
lua: &Lua,
(block_names, filter_fn): (Vec<String>, Option<Function>),
) -> Result<Vec<u16>> {
let mut matched = Vec::new();
for block_name in block_names {
for b in
(u32::MIN..u32::MAX).map_while(|possible_id| BlockState::try_from(possible_id).ok())
{
if block_name == Into::<Box<dyn AzaleaBlock>>::into(b).id()
&& (if let Some(filter_fn) = &filter_fn {
let p = lua.create_table()?;
p.set("chest_type", b.property::<ChestType>().map(|v| v as u8))?;
p.set("facing", b.property::<Facing>().map(|v| v as u8))?;
p.set("light_level", b.property::<LightLevel>().map(|v| v as u8))?;
filter_fn.call::<bool>(p.clone())?
} else {
true
})
{
matched.push(b.id);
}
}
}
Ok(matched)
}

View File

@ -6,6 +6,15 @@ use azalea::{
use log::error; use log::error;
use mlua::{Lua, Result, UserDataRef}; use mlua::{Lua, Result, UserDataRef};
pub fn container(_lua: &Lua, client: &Client) -> Result<Option<ContainerRef>> {
Ok(client
.inner
.as_ref()
.unwrap()
.get_open_container()
.map(|c| ContainerRef { inner: c }))
}
pub fn held_item(_lua: &Lua, client: &Client) -> Result<ItemStack> { pub fn held_item(_lua: &Lua, client: &Client) -> Result<ItemStack> {
Ok(ItemStack { Ok(ItemStack {
inner: client inner: client
@ -26,15 +35,6 @@ pub fn held_slot(_lua: &Lua, client: &Client) -> Result<u8> {
.selected_hotbar_slot) .selected_hotbar_slot)
} }
pub fn open_container(_lua: &Lua, client: &Client) -> Result<Option<ContainerRef>> {
Ok(client
.inner
.as_ref()
.unwrap()
.get_open_container()
.map(|c| ContainerRef { inner: c }))
}
pub async fn open_container_at( pub async fn open_container_at(
_lua: Lua, _lua: Lua,
client: UserDataRef<Client>, client: UserDataRef<Client>,

View File

@ -5,7 +5,6 @@ mod state;
mod world; mod world;
use super::{ use super::{
block::Block,
container::item_stack::ItemStack, container::item_stack::ItemStack,
container::{Container, ContainerRef}, container::{Container, ContainerRef},
direction::Direction, direction::Direction,
@ -24,6 +23,7 @@ pub struct Client {
impl UserData for Client { impl UserData for Client {
fn add_fields<F: UserDataFields<Self>>(f: &mut F) { fn add_fields<F: UserDataFields<Self>>(f: &mut F) {
f.add_field_method_get("air_supply", state::air_supply); f.add_field_method_get("air_supply", state::air_supply);
f.add_field_method_get("container", container::container);
f.add_field_method_get("direction", movement::direction); f.add_field_method_get("direction", movement::direction);
f.add_field_method_get("eye_position", movement::eye_position); f.add_field_method_get("eye_position", movement::eye_position);
f.add_field_method_get("has_attack_cooldown", interaction::has_attack_cooldown); f.add_field_method_get("has_attack_cooldown", interaction::has_attack_cooldown);
@ -32,7 +32,6 @@ impl UserData for Client {
f.add_field_method_get("held_slot", container::held_slot); f.add_field_method_get("held_slot", container::held_slot);
f.add_field_method_get("hunger", state::hunger); f.add_field_method_get("hunger", state::hunger);
f.add_field_method_get("looking_at", movement::looking_at); f.add_field_method_get("looking_at", movement::looking_at);
f.add_field_method_get("open_container", container::open_container);
f.add_field_method_get("pathfinder", movement::pathfinder); f.add_field_method_get("pathfinder", movement::pathfinder);
f.add_field_method_get("position", movement::position); f.add_field_method_get("position", movement::position);
f.add_field_method_get("score", state::score); f.add_field_method_get("score", state::score);
@ -41,16 +40,15 @@ impl UserData for Client {
} }
fn add_methods<M: UserDataMethods<Self>>(m: &mut M) { fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
m.add_async_method("goto", movement::goto);
m.add_async_method("mine", interaction::mine); m.add_async_method("mine", interaction::mine);
m.add_async_method("open_container_at", container::open_container_at); m.add_async_method("open_container_at", container::open_container_at);
m.add_async_method("set_client_information", state::set_client_information); m.add_async_method("set_client_information", state::set_client_information);
m.add_method("best_tool_for_block", world::best_tool_for_block); m.add_method("best_tool_for_block", world::best_tool_for_block);
m.add_method("block_names_to_states", world::block_names_to_states);
m.add_method("chat", chat); m.add_method("chat", chat);
m.add_method("disconnect", disconnect); m.add_method("disconnect", disconnect);
m.add_method("find_blocks", world::find_blocks); m.add_method("find_blocks", world::find_blocks);
m.add_method("find_entities", world::find_entities); m.add_method("find_entities", world::find_entities);
m.add_method("get_block_from_state", world::get_block_from_state);
m.add_method("get_block_state", world::get_block_state); m.add_method("get_block_state", world::get_block_state);
m.add_method("get_fluid_state", world::get_fluid_state); m.add_method("get_fluid_state", world::get_fluid_state);
m.add_method("set_held_slot", container::set_held_slot); m.add_method("set_held_slot", container::set_held_slot);
@ -58,7 +56,6 @@ impl UserData for Client {
m.add_method("stop_pathfinding", movement::stop_pathfinding); m.add_method("stop_pathfinding", movement::stop_pathfinding);
m.add_method_mut("attack", interaction::attack); m.add_method_mut("attack", interaction::attack);
m.add_method_mut("block_interact", interaction::block_interact); m.add_method_mut("block_interact", interaction::block_interact);
m.add_method_mut("goto", movement::goto);
m.add_method_mut("jump", movement::jump); m.add_method_mut("jump", movement::jump);
m.add_method_mut("look_at", movement::look_at); m.add_method_mut("look_at", movement::look_at);
m.add_method_mut("open_inventory", container::open_inventory); m.add_method_mut("open_inventory", container::open_inventory);

View File

@ -3,11 +3,11 @@ use azalea::{
BlockPos, BotClientExt, Client as AzaleaClient, SprintDirection, WalkDirection, BlockPos, BotClientExt, Client as AzaleaClient, SprintDirection, WalkDirection,
interact::HitResultComponent, interact::HitResultComponent,
pathfinder::{ pathfinder::{
ExecutingPath, Pathfinder, PathfinderClientExt, ExecutingPath, GotoEvent, Pathfinder, PathfinderClientExt,
goals::{BlockPosGoal, Goal, RadiusGoal, ReachBlockPosGoal, XZGoal, YGoal}, goals::{BlockPosGoal, Goal, RadiusGoal, ReachBlockPosGoal, XZGoal, YGoal},
}, },
}; };
use mlua::{FromLua, Lua, Result, Table, Value}; use mlua::{FromLua, Lua, Result, Table, UserDataRef, Value};
pub fn direction(_lua: &Lua, client: &Client) -> Result<Direction> { pub fn direction(_lua: &Lua, client: &Client) -> Result<Direction> {
let d = client.inner.as_ref().unwrap().direction(); let d = client.inner.as_ref().unwrap().direction();
@ -23,9 +23,9 @@ pub fn eye_position(_lua: &Lua, client: &Client) -> Result<Vec3> {
}) })
} }
pub fn goto( pub async fn goto(
lua: &Lua, lua: Lua,
client: &mut Client, client: UserDataRef<Client>,
(data, metadata): (Value, Option<Table>), (data, metadata): (Value, Option<Table>),
) -> Result<()> { ) -> Result<()> {
fn g(client: &AzaleaClient, without_mining: bool, goal: impl Goal + Send + Sync + 'static) { fn g(client: &AzaleaClient, without_mining: bool, goal: impl Goal + Send + Sync + 'static) {
@ -55,7 +55,7 @@ pub fn goto(
match goal_type { match goal_type {
1 => { 1 => {
let t = data.as_table().ok_or(error)?; let t = data.as_table().ok_or(error)?;
let p = Vec3::from_lua(t.get("position")?, lua)?; let p = Vec3::from_lua(t.get("position")?, &lua)?;
g( g(
client, client,
without_mining, without_mining,
@ -66,7 +66,7 @@ pub fn goto(
); );
} }
2 => { 2 => {
let p = Vec3::from_lua(data, lua)?; let p = Vec3::from_lua(data, &lua)?;
g( g(
client, client,
without_mining, without_mining,
@ -95,7 +95,7 @@ pub fn goto(
}, },
), ),
_ => { _ => {
let p = Vec3::from_lua(data, lua)?; let p = Vec3::from_lua(data, &lua)?;
g( g(
client, client,
without_mining, without_mining,
@ -104,6 +104,12 @@ pub fn goto(
} }
} }
while client.get_tick_broadcaster().recv().await.is_ok() {
if client.ecs.lock().get::<GotoEvent>(client.entity).is_none() {
break;
}
}
Ok(()) Ok(())
} }

View File

@ -1,8 +1,8 @@
use super::{Block, Client, Entity, FluidState, Vec3}; use super::{Client, Entity, FluidState, Vec3};
use azalea::{ use azalea::{
BlockPos, BlockPos,
auto_tool::AutoToolClientExt, auto_tool::AutoToolClientExt,
blocks::{Block as AzaleaBlock, BlockState, BlockStates}, blocks::{BlockState, BlockStates},
ecs::query::Without, ecs::query::Without,
entity::{Dead, EntityKind, EntityUuid, Position as AzaleaPosition, metadata::CustomName}, entity::{Dead, EntityKind, EntityUuid, Position as AzaleaPosition, metadata::CustomName},
world::MinecraftEntityId, world::MinecraftEntityId,
@ -22,22 +22,6 @@ pub fn best_tool_for_block(lua: &Lua, client: &Client, block_state: u16) -> Resu
Ok(tool_result) Ok(tool_result)
} }
pub fn block_names_to_states(
_lua: &Lua,
_client: &Client,
block_names: Vec<String>,
) -> Result<Vec<u16>> {
Ok(block_names
.iter()
.flat_map(|n| {
(u32::MIN..u32::MAX)
.map_while(|i| BlockState::try_from(i).ok())
.filter(move |&b| n == Into::<Box<dyn AzaleaBlock>>::into(b).id())
.map(|b| b.id)
})
.collect())
}
pub fn find_blocks( pub fn find_blocks(
_lua: &Lua, _lua: &Lua,
client: &Client, client: &Client,
@ -93,7 +77,7 @@ pub fn find_entities(_lua: &Lua, client: &Client, filter_fn: Function) -> Result
custom_name: custom_name.as_ref().map(ToString::to_string), custom_name: custom_name.as_ref().map(ToString::to_string),
}; };
if filter_fn.call::<bool>(entity.clone()).unwrap() { if filter_fn.call::<bool>(entity.clone())? {
matched.push(entity); matched.push(entity);
} }
} }
@ -101,23 +85,6 @@ pub fn find_entities(_lua: &Lua, client: &Client, filter_fn: Function) -> Result
Ok(matched) Ok(matched)
} }
pub fn get_block_from_state(_lua: &Lua, _client: &Client, state: u32) -> Result<Option<Block>> {
let Ok(state) = BlockState::try_from(state) else {
return Ok(None);
};
let block: Box<dyn AzaleaBlock> = state.into();
let behavior = block.behavior();
Ok(Some(Block {
id: block.id().to_string(),
friction: behavior.friction,
jump_factor: behavior.jump_factor,
destroy_time: behavior.destroy_time,
explosion_resistance: behavior.explosion_resistance,
requires_correct_tool_for_drops: behavior.requires_correct_tool_for_drops,
}))
}
pub fn get_block_state(_lua: &Lua, client: &Client, position: Vec3) -> Result<Option<u16>> { pub fn get_block_state(_lua: &Lua, client: &Client, position: Vec3) -> Result<Option<u16>> {
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
Ok(client Ok(client

View File

@ -32,10 +32,14 @@ impl UserData for Container {
} }
fn add_methods<M: UserDataMethods<Self>>(m: &mut M) { fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
m.add_method("click", |_, this, operation: Table| { m.add_method(
this.inner.click(click_operation_from_table(operation)?); "click",
Ok(()) |_, this, (operation, operation_type): (Table, Option<u8>)| {
}); this.inner
.click(click_operation_from_table(operation, operation_type)?);
Ok(())
},
);
} }
} }
@ -66,59 +70,63 @@ impl UserData for ContainerRef {
Ok(()) Ok(())
}); });
m.add_method("click", |_, this, operation: Table| { m.add_method(
this.inner.click(click_operation_from_table(operation)?); "click",
Ok(()) |_, this, (operation, operation_type): (Table, Option<u8>)| {
}); this.inner
.click(click_operation_from_table(operation, operation_type)?);
Ok(())
},
);
} }
} }
fn click_operation_from_table(o: Table) -> Result<ClickOperation> { fn click_operation_from_table(op: Table, op_type: Option<u8>) -> Result<ClickOperation> {
Ok(match o.get("type")? { Ok(match op_type.unwrap_or_default() {
0 => ClickOperation::Pickup(PickupClick::Left { 0 => ClickOperation::Pickup(PickupClick::Left {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
1 => ClickOperation::Pickup(PickupClick::Right { 1 => ClickOperation::Pickup(PickupClick::Right {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
2 => ClickOperation::Pickup(PickupClick::LeftOutside), 2 => ClickOperation::Pickup(PickupClick::LeftOutside),
3 => ClickOperation::Pickup(PickupClick::RightOutside), 3 => ClickOperation::Pickup(PickupClick::RightOutside),
5 => ClickOperation::QuickMove(QuickMoveClick::Right { 5 => ClickOperation::QuickMove(QuickMoveClick::Right {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
6 => ClickOperation::Swap(SwapClick { 6 => ClickOperation::Swap(SwapClick {
source_slot: o.get("source_slot")?, source_slot: op.get("source_slot")?,
target_slot: o.get("target_slot")?, target_slot: op.get("target_slot")?,
}), }),
7 => ClickOperation::Clone(CloneClick { 7 => ClickOperation::Clone(CloneClick {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
8 => ClickOperation::Throw(ThrowClick::Single { 8 => ClickOperation::Throw(ThrowClick::Single {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
9 => ClickOperation::Throw(ThrowClick::All { 9 => ClickOperation::Throw(ThrowClick::All {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
10 => ClickOperation::QuickCraft(QuickCraftClick { 10 => ClickOperation::QuickCraft(QuickCraftClick {
kind: match o.get("kind").unwrap_or_default() { kind: match op.get("kind").unwrap_or_default() {
1 => QuickCraftKind::Right, 1 => QuickCraftKind::Right,
2 => QuickCraftKind::Middle, 2 => QuickCraftKind::Middle,
_ => QuickCraftKind::Left, _ => QuickCraftKind::Left,
}, },
status: match o.get("status").unwrap_or_default() { status: match op.get("status").unwrap_or_default() {
1 => QuickCraftStatus::Add { 1 => QuickCraftStatus::Add {
slot: o.get("slot")?, slot: op.get("slot")?,
}, },
2 => QuickCraftStatus::End, 2 => QuickCraftStatus::End,
_ => QuickCraftStatus::Start, _ => QuickCraftStatus::Start,
}, },
}), }),
11 => ClickOperation::PickupAll(PickupAllClick { 11 => ClickOperation::PickupAll(PickupAllClick {
slot: o.get("slot")?, slot: op.get("slot")?,
reversed: o.get("reversed").unwrap_or_default(), reversed: op.get("reversed").unwrap_or_default(),
}), }),
_ => ClickOperation::QuickMove(QuickMoveClick::Left { _ => ClickOperation::QuickMove(QuickMoveClick::Left {
slot: o.get("slot")?, slot: op.get("slot")?,
}), }),
}) })
} }

View File

@ -29,7 +29,8 @@ pub fn register_functions(lua: &Lua, globals: &Table) -> mlua::Result<()> {
})?, })?,
)?; )?;
logging::register_functions(lua, globals) logging::register_functions(lua, globals)?;
block::register_functions(lua, globals)
} }
pub fn reload(lua: &Lua) -> Result<(), Error> { pub fn reload(lua: &Lua) -> Result<(), Error> {