diff --git a/lib/enum.lua b/lib/enum.lua index 96aadee..e83500e 100644 --- a/lib/enum.lua +++ b/lib/enum.lua @@ -13,3 +13,22 @@ RADIUS_GOAL = 1 REACH_BLOCK_POS_GOAL = 2 XZ_GOAL = 3 Y_GOAL = 4 + +PICKUP_LEFT = 0 +PICKUP_RIGHT = 1 +PICKUP_LEFT_OUTSIDE = 2 +PICKUP_RIGHT_OUTSIDE = 3 +QUICK_MOVE_LEFT = 4 +QUICK_MOVE_RIGHT = 5 +SWAP = 6 +CLONE = 7 +THROW_SINGLE = 8 +THROW_ALL = 9 +QUICK_CRAFT = 10 +QUICK_CRAFT_LEFT = 0 +QUICK_CRAFT_RIGHT = 1 +QUICK_CRAFT_MIDDLE = 2 +QUICK_CRAFT_START = 0 +QUICK_CRAFT_ADD = 1 +QUICK_CRAFT_END = 2 +PICKUP_ALL = 11 diff --git a/src/lua/client/container.rs b/src/lua/client/container.rs new file mode 100644 index 0000000..d0d72ac --- /dev/null +++ b/src/lua/client/container.rs @@ -0,0 +1,88 @@ +use super::{Client, Container, ContainerRef, ItemStack, Vec3}; +use azalea::{ + BlockPos, inventory::Inventory, prelude::ContainerClientExt, + protocol::packets::game::ServerboundSetCarriedItem, +}; +use log::error; +use mlua::{Lua, Result, UserDataRef}; + +pub fn held_item(_lua: &Lua, client: &Client) -> Result { + Ok(ItemStack { + inner: client + .inner + .as_ref() + .unwrap() + .component::() + .held_item(), + }) +} + +pub fn held_slot(_lua: &Lua, client: &Client) -> Result { + Ok(client + .inner + .as_ref() + .unwrap() + .component::() + .selected_hotbar_slot) +} + +pub fn open_container(_lua: &Lua, client: &Client) -> Result> { + Ok(client + .inner + .as_ref() + .unwrap() + .get_open_container() + .map(|c| ContainerRef { inner: c })) +} + +pub async fn open_container_at( + _lua: Lua, + client: UserDataRef, + position: Vec3, +) -> Result> { + #[allow(clippy::cast_possible_truncation)] + Ok(client + .inner + .clone() + .unwrap() + .open_container_at(BlockPos::new( + position.x as i32, + position.y as i32, + position.z as i32, + )) + .await + .map(|c| Container { inner: c })) +} + +pub fn open_inventory(_lua: &Lua, client: &mut Client, _: ()) -> Result> { + Ok(client + .inner + .as_mut() + .unwrap() + .open_inventory() + .map(|c| Container { inner: c })) +} + +pub fn set_held_slot(_lua: &Lua, client: &Client, slot: u8) -> Result<()> { + if slot > 8 { + return Ok(()); + } + + let client = client.inner.as_ref().unwrap(); + { + let mut ecs = client.ecs.lock(); + let mut inventory = client.query::<&mut Inventory>(&mut ecs); + if inventory.selected_hotbar_slot == slot { + return Ok(()); + } + inventory.selected_hotbar_slot = slot; + }; + + if let Err(error) = client.write_packet(ServerboundSetCarriedItem { + slot: u16::from(slot), + }) { + error!("failed to send SetCarriedItem packet: {error:?}"); + } + + Ok(()) +} diff --git a/src/lua/client/mod.rs b/src/lua/client/mod.rs index 794d51f..46cf497 100644 --- a/src/lua/client/mod.rs +++ b/src/lua/client/mod.rs @@ -1,10 +1,17 @@ +mod container; mod interaction; mod movement; mod state; mod world; use super::{ - block::Block, direction::Direction, entity::Entity, fluid_state::FluidState, hunger::Hunger, + block::Block, + container::item_stack::ItemStack, + container::{Container, ContainerRef}, + direction::Direction, + entity::Entity, + fluid_state::FluidState, + hunger::Hunger, vec3::Vec3, }; use azalea::Client as AzaleaClient; @@ -20,8 +27,11 @@ impl UserData for Client { f.add_field_method_get("direction", movement::direction); f.add_field_method_get("eye_position", movement::eye_position); f.add_field_method_get("health", state::health); + f.add_field_method_get("held_item", container::held_item); + 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); @@ -31,13 +41,17 @@ impl UserData for Client { fn add_methods>(m: &mut M) { 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("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); m.add_method("set_mining", interaction::set_mining); m.add_method("stop_pathfinding", movement::stop_pathfinding); m.add_method_mut("attack", interaction::attack); @@ -45,6 +59,7 @@ impl UserData for Client { 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); m.add_method_mut("set_direction", movement::set_direction); m.add_method_mut("set_jumping", movement::set_jumping); m.add_method_mut("sprint", movement::sprint); diff --git a/src/lua/client/world.rs b/src/lua/client/world.rs index 53d0135..5c92814 100644 --- a/src/lua/client/world.rs +++ b/src/lua/client/world.rs @@ -1,17 +1,47 @@ use super::{Block, Client, Entity, FluidState, Vec3}; use azalea::{ BlockPos, + auto_tool::AutoToolClientExt, blocks::{Block as AzaleaBlock, BlockState, BlockStates}, ecs::query::Without, entity::{Dead, EntityKind, EntityUuid, Position as AzaleaPosition, metadata::CustomName}, world::MinecraftEntityId, }; -use mlua::{Function, Lua, Result}; +use mlua::{Function, Lua, Result, Table}; + +pub fn best_tool_for_block(lua: &Lua, client: &Client, block_state: u16) -> Result { + let tr = client + .inner + .as_ref() + .unwrap() + .best_tool_in_hotbar_for_block(BlockState { id: block_state }); + + let tool_result = lua.create_table()?; + tool_result.set("index", tr.index)?; + tool_result.set("percentage_per_tick", tr.percentage_per_tick)?; + Ok(tool_result) +} + +pub fn block_names_to_states( + _lua: &Lua, + _client: &Client, + block_names: Vec, +) -> Result> { + Ok(block_names + .iter() + .flat_map(|n| { + (u32::MIN..u32::MAX) + .map_while(|i| BlockState::try_from(i).ok()) + .filter(move |&b| n == Into::>::into(b).id()) + .map(|b| b.id) + }) + .collect()) +} pub fn find_blocks( _lua: &Lua, client: &Client, - (nearest_to, block_names): (Vec3, Vec), + (nearest_to, block_states): (Vec3, Vec), ) -> Result> { #[allow(clippy::cast_possible_truncation)] Ok(client @@ -27,14 +57,7 @@ pub fn find_blocks( nearest_to.z as i32, ), &BlockStates { - set: block_names - .iter() - .flat_map(|n| { - (u32::MIN..u32::MAX) - .map_while(|i| BlockState::try_from(i).ok()) - .filter(move |&b| n == Into::>::into(b).id()) - }) - .collect(), + set: block_states.iter().map(|&id| BlockState { id }).collect(), }, ) .map(|p| Vec3 { diff --git a/src/lua/container/item_stack.rs b/src/lua/container/item_stack.rs new file mode 100644 index 0000000..0aef768 --- /dev/null +++ b/src/lua/container/item_stack.rs @@ -0,0 +1,50 @@ +use azalea::inventory::components::{CustomName, Damage, MaxDamage}; +use mlua::{UserData, UserDataFields, UserDataMethods}; + +pub struct ItemStack { + pub inner: azalea::inventory::ItemStack, +} + +impl UserData for ItemStack { + fn add_fields>(f: &mut F) { + f.add_field_method_get("is_empty", |_, this| Ok(this.inner.is_empty())); + f.add_field_method_get("is_present", |_, this| Ok(this.inner.is_present())); + f.add_field_method_get("count", |_, this| Ok(this.inner.count())); + f.add_field_method_get("kind", |_, this| Ok(this.inner.kind().to_string())); + f.add_field_method_get("custom_name", |_, this| { + Ok(if let Some(data) = this.inner.as_present() { + data.components + .get::() + .map(|n| n.name.to_string()) + } else { + None + }) + }); + f.add_field_method_get("damage", |_, this| { + Ok(if let Some(data) = this.inner.as_present() { + data.components.get::().map(|d| d.amount) + } else { + None + }) + }); + f.add_field_method_get("max_damage", |_, this| { + Ok(if let Some(data) = this.inner.as_present() { + data.components.get::().map(|d| d.amount) + } else { + None + }) + }); + } + + fn add_methods>(m: &mut M) { + m.add_method_mut("split", |_, this, count: u32| { + Ok(ItemStack { + inner: this.inner.split(count), + }) + }); + m.add_method_mut("update_empty", |_, this, (): ()| { + this.inner.update_empty(); + Ok(()) + }); + } +} diff --git a/src/lua/container/mod.rs b/src/lua/container/mod.rs new file mode 100644 index 0000000..a71076f --- /dev/null +++ b/src/lua/container/mod.rs @@ -0,0 +1,124 @@ +pub mod item_stack; + +use azalea::{ + container::{ContainerHandle, ContainerHandleRef}, + inventory::operations::{ + ClickOperation, CloneClick, PickupAllClick, PickupClick, QuickCraftClick, QuickCraftKind, + QuickCraftStatus, QuickMoveClick, SwapClick, ThrowClick, + }, +}; +use item_stack::ItemStack; +use mlua::{Result, Table, UserData, UserDataFields, UserDataMethods}; + +pub struct Container { + pub inner: ContainerHandle, +} + +impl UserData for Container { + fn add_fields>(f: &mut F) { + f.add_field_method_get("id", |_, this| Ok(this.inner.id())); + + f.add_field_method_get("menu", |_, this| { + Ok(this.inner.menu().map(|m| format!("{m:?}"))) + }); + + f.add_field_method_get("contents", |_, this| { + Ok(this.inner.contents().map(|v| { + v.iter() + .map(|i| ItemStack { inner: i.clone() }) + .collect::>() + })) + }); + } + + fn add_methods>(m: &mut M) { + m.add_method("click", |_, this, operation: Table| { + this.inner.click(click_operation_from_table(operation)?); + Ok(()) + }); + } +} + +pub struct ContainerRef { + pub inner: ContainerHandleRef, +} + +impl UserData for ContainerRef { + fn add_fields>(f: &mut F) { + f.add_field_method_get("id", |_, this| Ok(this.inner.id())); + + f.add_field_method_get("menu", |_, this| { + Ok(this.inner.menu().map(|m| format!("{m:?}"))) + }); + + f.add_field_method_get("contents", |_, this| { + Ok(this.inner.contents().map(|v| { + v.iter() + .map(|i| ItemStack { inner: i.clone() }) + .collect::>() + })) + }); + } + + fn add_methods>(m: &mut M) { + m.add_method("close", |_, this, (): ()| { + this.inner.close(); + Ok(()) + }); + + m.add_method("click", |_, this, operation: Table| { + this.inner.click(click_operation_from_table(operation)?); + Ok(()) + }); + } +} + +fn click_operation_from_table(o: Table) -> Result { + Ok(match o.get("type")? { + 0 => ClickOperation::Pickup(PickupClick::Left { + slot: o.get("slot")?, + }), + 1 => ClickOperation::Pickup(PickupClick::Right { + slot: o.get("slot")?, + }), + 2 => ClickOperation::Pickup(PickupClick::LeftOutside), + 3 => ClickOperation::Pickup(PickupClick::RightOutside), + 5 => ClickOperation::QuickMove(QuickMoveClick::Right { + slot: o.get("slot")?, + }), + 6 => ClickOperation::Swap(SwapClick { + source_slot: o.get("source_slot")?, + target_slot: o.get("target_slot")?, + }), + 7 => ClickOperation::Clone(CloneClick { + slot: o.get("slot")?, + }), + 8 => ClickOperation::Throw(ThrowClick::Single { + slot: o.get("slot")?, + }), + 9 => ClickOperation::Throw(ThrowClick::All { + slot: o.get("slot")?, + }), + 10 => ClickOperation::QuickCraft(QuickCraftClick { + kind: match o.get("kind").unwrap_or_default() { + 1 => QuickCraftKind::Right, + 2 => QuickCraftKind::Middle, + _ => QuickCraftKind::Left, + }, + status: match o.get("status").unwrap_or_default() { + 1 => QuickCraftStatus::Add { + slot: o.get("slot")?, + }, + 2 => QuickCraftStatus::End, + _ => QuickCraftStatus::Start, + }, + }), + 11 => ClickOperation::PickupAll(PickupAllClick { + slot: o.get("slot")?, + reversed: o.get("reversed").unwrap_or_default(), + }), + _ => ClickOperation::QuickMove(QuickMoveClick::Left { + slot: o.get("slot")?, + }), + }) +} diff --git a/src/lua/mod.rs b/src/lua/mod.rs index 6b7c54b..7e1fb77 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -1,5 +1,6 @@ pub mod block; pub mod client; +pub mod container; pub mod direction; pub mod entity; pub mod fluid_state;