From dde489a8ed8e86e66bc3859d54d9f34c5e3aab6b Mon Sep 17 00:00:00 2001
From: ErrorNoInternet <errornointernet@envs.net>
Date: Mon, 17 Feb 2025 17:01:23 -0500
Subject: [PATCH] refactor: random fixes and usage improvements

---
 Cargo.toml                  |  3 +-
 errornowatcher.lua          |  2 ++
 lib/events.lua              |  8 +++--
 lib/inventory.lua           | 38 +++++++++++++++++++++++
 lib/movement.lua            | 18 +++++++++++
 lib/utils.lua               | 14 +++++++++
 src/events.rs               |  1 +
 src/http.rs                 |  4 +--
 src/lua/block.rs            | 60 ++++++++++++++++++++++++++++++++++++-
 src/lua/client/container.rs | 18 +++++------
 src/lua/client/mod.rs       |  7 ++---
 src/lua/client/movement.rs  | 22 +++++++++-----
 src/lua/client/world.rs     | 39 ++----------------------
 src/lua/container/mod.rs    | 56 +++++++++++++++++++---------------
 src/lua/mod.rs              |  3 +-
 15 files changed, 203 insertions(+), 90 deletions(-)
 create mode 100644 lib/inventory.lua
 create mode 100644 lib/movement.lua

diff --git a/Cargo.toml b/Cargo.toml
index e17c9d7..12e2746 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,17 +12,16 @@ opt-level = 3
 [profile.release]
 codegen-units = 1
 lto = true
-panic = "abort"
 strip = true
 
 [dependencies]
 anyhow = "1"
 azalea = { git = "https://github.com/azalea-rs/azalea.git" }
 clap = { version = "4", features = ["derive"] }
+futures = "0"
 http-body-util = "0"
 hyper = { version = "1", features = ["server"] }
 hyper-util = "0"
 log = { version = "0" }
 mlua = { version = "0", features = ["async", "luau", "send"] }
 tokio = { version = "1", features = ["macros"] }
-futures = "0"
diff --git a/errornowatcher.lua b/errornowatcher.lua
index fc641ba..c55f286 100644
--- a/errornowatcher.lua
+++ b/errornowatcher.lua
@@ -6,6 +6,8 @@ for _, module in
 	{
 		"enum",
 		"events",
+		"inventory",
+		"movement",
 		"utils",
 	}
 do
diff --git a/lib/events.lua b/lib/events.lua
index a7f4c1f..8a960c4 100644
--- a/lib/events.lua
+++ b/lib/events.lua
@@ -1,5 +1,5 @@
 local center = { x = 0, z = 0 }
-local radius = 50
+local radius = 100
 
 function on_tick()
 	local entities = client:find_entities(function(e)
@@ -15,6 +15,10 @@ function on_tick()
 end
 
 function on_init()
-	info("client initialized, setting client information")
+	info("client initialized, setting information...")
 	client:set_client_information({ view_distance = 16 })
 end
+
+function on_login()
+	info("player successfully logged in!")
+end
diff --git a/lib/inventory.lua b/lib/inventory.lua
new file mode 100644
index 0000000..d99e618
--- /dev/null
+++ b/lib/inventory.lua
@@ -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
diff --git a/lib/movement.lua b/lib/movement.lua
new file mode 100644
index 0000000..e885d92
--- /dev/null
+++ b/lib/movement.lua
@@ -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
diff --git a/lib/utils.lua b/lib/utils.lua
index 4ef5ba8..cf6023b 100644
--- a/lib/utils.lua
+++ b/lib/utils.lua
@@ -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)
 	if type(object) == "table" then
 		local string = "{ "
diff --git a/src/events.rs b/src/events.rs
index 9569634..fec8620 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -7,6 +7,7 @@ use mlua::{Function, IntoLuaMulti, Table};
 use tokio::net::TcpListener;
 
 pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> {
+    state.lua.gc_stop();
     let globals = state.lua.globals();
 
     match event {
diff --git a/src/http.rs b/src/http.rs
index acd8d97..3ac8bf5 100644
--- a/src/http.rs
+++ b/src/http.rs
@@ -24,8 +24,8 @@ pub async fn serve(
             let bytes = request.into_body().collect().await?.to_bytes();
             match std::str::from_utf8(&bytes) {
                 Ok(code) => Response::new(full(match path.as_str() {
-                    "/eval" => format!("{:?}", eval(&state.lua, code).await),
-                    "/exec" => format!("{:?}", exec(&state.lua, code).await),
+                    "/eval" => format!("{:#?}", eval(&state.lua, code).await),
+                    "/exec" => format!("{:#?}", exec(&state.lua, code).await),
                     _ => unreachable!(),
                 })),
                 Err(error) => status_code_response(
diff --git a/src/lua/block.rs b/src/lua/block.rs
index 25c8cd0..747b442 100644
--- a/src/lua/block.rs
+++ b/src/lua/block.rs
@@ -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)]
 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)
+}
diff --git a/src/lua/client/container.rs b/src/lua/client/container.rs
index d0d72ac..81d166b 100644
--- a/src/lua/client/container.rs
+++ b/src/lua/client/container.rs
@@ -6,6 +6,15 @@ use azalea::{
 use log::error;
 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> {
     Ok(ItemStack {
         inner: client
@@ -26,15 +35,6 @@ pub fn held_slot(_lua: &Lua, client: &Client) -> Result<u8> {
         .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(
     _lua: Lua,
     client: UserDataRef<Client>,
diff --git a/src/lua/client/mod.rs b/src/lua/client/mod.rs
index 6b60d7a..bf25489 100644
--- a/src/lua/client/mod.rs
+++ b/src/lua/client/mod.rs
@@ -5,7 +5,6 @@ mod state;
 mod world;
 
 use super::{
-    block::Block,
     container::item_stack::ItemStack,
     container::{Container, ContainerRef},
     direction::Direction,
@@ -24,6 +23,7 @@ pub struct Client {
 impl UserData for Client {
     fn add_fields<F: UserDataFields<Self>>(f: &mut F) {
         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("eye_position", movement::eye_position);
         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("hunger", state::hunger);
         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("position", movement::position);
         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) {
+        m.add_async_method("goto", movement::goto);
         m.add_async_method("mine", interaction::mine);
         m.add_async_method("open_container_at", container::open_container_at);
         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("block_names_to_states", world::block_names_to_states);
         m.add_method("chat", chat);
         m.add_method("disconnect", disconnect);
         m.add_method("find_blocks", world::find_blocks);
         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_fluid_state", world::get_fluid_state);
         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_mut("attack", interaction::attack);
         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("look_at", movement::look_at);
         m.add_method_mut("open_inventory", container::open_inventory);
diff --git a/src/lua/client/movement.rs b/src/lua/client/movement.rs
index 6025add..5658912 100644
--- a/src/lua/client/movement.rs
+++ b/src/lua/client/movement.rs
@@ -3,11 +3,11 @@ use azalea::{
     BlockPos, BotClientExt, Client as AzaleaClient, SprintDirection, WalkDirection,
     interact::HitResultComponent,
     pathfinder::{
-        ExecutingPath, Pathfinder, PathfinderClientExt,
+        ExecutingPath, GotoEvent, Pathfinder, PathfinderClientExt,
         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> {
     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(
-    lua: &Lua,
-    client: &mut Client,
+pub async fn goto(
+    lua: Lua,
+    client: UserDataRef<Client>,
     (data, metadata): (Value, Option<Table>),
 ) -> Result<()> {
     fn g(client: &AzaleaClient, without_mining: bool, goal: impl Goal + Send + Sync + 'static) {
@@ -55,7 +55,7 @@ pub fn goto(
     match goal_type {
         1 => {
             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(
                 client,
                 without_mining,
@@ -66,7 +66,7 @@ pub fn goto(
             );
         }
         2 => {
-            let p = Vec3::from_lua(data, lua)?;
+            let p = Vec3::from_lua(data, &lua)?;
             g(
                 client,
                 without_mining,
@@ -95,7 +95,7 @@ pub fn goto(
             },
         ),
         _ => {
-            let p = Vec3::from_lua(data, lua)?;
+            let p = Vec3::from_lua(data, &lua)?;
             g(
                 client,
                 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(())
 }
 
diff --git a/src/lua/client/world.rs b/src/lua/client/world.rs
index 5c92814..2049cb4 100644
--- a/src/lua/client/world.rs
+++ b/src/lua/client/world.rs
@@ -1,8 +1,8 @@
-use super::{Block, Client, Entity, FluidState, Vec3};
+use super::{Client, Entity, FluidState, Vec3};
 use azalea::{
     BlockPos,
     auto_tool::AutoToolClientExt,
-    blocks::{Block as AzaleaBlock, BlockState, BlockStates},
+    blocks::{BlockState, BlockStates},
     ecs::query::Without,
     entity::{Dead, EntityKind, EntityUuid, Position as AzaleaPosition, metadata::CustomName},
     world::MinecraftEntityId,
@@ -22,22 +22,6 @@ pub fn best_tool_for_block(lua: &Lua, client: &Client, block_state: u16) -> Resu
     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(
     _lua: &Lua,
     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),
         };
 
-        if filter_fn.call::<bool>(entity.clone()).unwrap() {
+        if filter_fn.call::<bool>(entity.clone())? {
             matched.push(entity);
         }
     }
@@ -101,23 +85,6 @@ pub fn find_entities(_lua: &Lua, client: &Client, filter_fn: Function) -> Result
     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>> {
     #[allow(clippy::cast_possible_truncation)]
     Ok(client
diff --git a/src/lua/container/mod.rs b/src/lua/container/mod.rs
index a71076f..8063982 100644
--- a/src/lua/container/mod.rs
+++ b/src/lua/container/mod.rs
@@ -32,10 +32,14 @@ impl UserData for Container {
     }
 
     fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
-        m.add_method("click", |_, this, operation: Table| {
-            this.inner.click(click_operation_from_table(operation)?);
-            Ok(())
-        });
+        m.add_method(
+            "click",
+            |_, 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(())
         });
 
-        m.add_method("click", |_, this, operation: Table| {
-            this.inner.click(click_operation_from_table(operation)?);
-            Ok(())
-        });
+        m.add_method(
+            "click",
+            |_, 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> {
-    Ok(match o.get("type")? {
+fn click_operation_from_table(op: Table, op_type: Option<u8>) -> Result<ClickOperation> {
+    Ok(match op_type.unwrap_or_default() {
         0 => ClickOperation::Pickup(PickupClick::Left {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         1 => ClickOperation::Pickup(PickupClick::Right {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         2 => ClickOperation::Pickup(PickupClick::LeftOutside),
         3 => ClickOperation::Pickup(PickupClick::RightOutside),
         5 => ClickOperation::QuickMove(QuickMoveClick::Right {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         6 => ClickOperation::Swap(SwapClick {
-            source_slot: o.get("source_slot")?,
-            target_slot: o.get("target_slot")?,
+            source_slot: op.get("source_slot")?,
+            target_slot: op.get("target_slot")?,
         }),
         7 => ClickOperation::Clone(CloneClick {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         8 => ClickOperation::Throw(ThrowClick::Single {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         9 => ClickOperation::Throw(ThrowClick::All {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
         10 => ClickOperation::QuickCraft(QuickCraftClick {
-            kind: match o.get("kind").unwrap_or_default() {
+            kind: match op.get("kind").unwrap_or_default() {
                 1 => QuickCraftKind::Right,
                 2 => QuickCraftKind::Middle,
                 _ => QuickCraftKind::Left,
             },
-            status: match o.get("status").unwrap_or_default() {
+            status: match op.get("status").unwrap_or_default() {
                 1 => QuickCraftStatus::Add {
-                    slot: o.get("slot")?,
+                    slot: op.get("slot")?,
                 },
                 2 => QuickCraftStatus::End,
                 _ => QuickCraftStatus::Start,
             },
         }),
         11 => ClickOperation::PickupAll(PickupAllClick {
-            slot: o.get("slot")?,
-            reversed: o.get("reversed").unwrap_or_default(),
+            slot: op.get("slot")?,
+            reversed: op.get("reversed").unwrap_or_default(),
         }),
         _ => ClickOperation::QuickMove(QuickMoveClick::Left {
-            slot: o.get("slot")?,
+            slot: op.get("slot")?,
         }),
     })
 }
diff --git a/src/lua/mod.rs b/src/lua/mod.rs
index 7e1fb77..827ec15 100644
--- a/src/lua/mod.rs
+++ b/src/lua/mod.rs
@@ -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> {