From 04b0bdd756ef5fcf0ef625038c91806c5e7cbb11 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Sun, 16 Feb 2025 18:21:06 -0500 Subject: [PATCH] feat(client): add world interaction methods --- src/scripting/block.rs | 48 +++++++++++++ src/scripting/client/mod.rs | 54 ++++---------- src/scripting/client/world.rs | 132 ++++++++++++++++++++++++++++++++++ src/scripting/fluid_state.rs | 36 ++++++++++ src/scripting/mod.rs | 2 + 5 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 src/scripting/block.rs create mode 100644 src/scripting/client/world.rs create mode 100644 src/scripting/fluid_state.rs diff --git a/src/scripting/block.rs b/src/scripting/block.rs new file mode 100644 index 0000000..25c8cd0 --- /dev/null +++ b/src/scripting/block.rs @@ -0,0 +1,48 @@ +use mlua::{FromLua, IntoLua, Lua, Result, Value}; + +#[derive(Clone)] +pub struct Block { + pub id: String, + pub friction: f32, + pub jump_factor: f32, + pub destroy_time: f32, + pub explosion_resistance: f32, + pub requires_correct_tool_for_drops: bool, +} + +impl IntoLua for Block { + fn into_lua(self, lua: &Lua) -> Result { + let table = lua.create_table()?; + table.set("id", self.id)?; + table.set("friction", self.friction)?; + table.set("jump_factor", self.jump_factor)?; + table.set("destroy_time", self.destroy_time)?; + table.set("explosion_resistance", self.explosion_resistance)?; + table.set( + "requires_correct_tool_for_drops", + self.requires_correct_tool_for_drops, + )?; + Ok(Value::Table(table)) + } +} + +impl FromLua for Block { + fn from_lua(value: Value, _lua: &Lua) -> Result { + if let Value::Table(table) = value { + Ok(Self { + id: table.get("id")?, + friction: table.get("friction")?, + jump_factor: table.get("jump_factor")?, + destroy_time: table.get("destroy_time")?, + explosion_resistance: table.get("explosion_resistance")?, + requires_correct_tool_for_drops: table.get("requires_correct_tool_for_drops")?, + }) + } else { + Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Block".to_string(), + message: None, + }) + } + } +} diff --git a/src/scripting/client/mod.rs b/src/scripting/client/mod.rs index 9c92473..bce4b44 100644 --- a/src/scripting/client/mod.rs +++ b/src/scripting/client/mod.rs @@ -1,18 +1,17 @@ mod interaction; mod movement; mod state; +mod world; -use super::{direction::Direction, entity::Entity, hunger::Hunger, vec3::Vec3}; +use super::{ + block::Block, direction::Direction, entity::Entity, fluid_state::FluidState, hunger::Hunger, + vec3::Vec3, +}; use azalea::{ Client as AzaleaClient, - ecs::query::Without, - entity::{ - Dead, EntityKind, EntityUuid, Position as AzaleaPosition, - metadata::{AirSupply, CustomName, Score}, - }, - world::MinecraftEntityId, + entity::metadata::{AirSupply, Score}, }; -use mlua::{Function, Lua, Result, UserData, UserDataFields, UserDataMethods}; +use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; pub struct Client { pub inner: Option, @@ -87,7 +86,11 @@ impl UserData for Client { fn add_methods>(m: &mut M) { m.add_async_method("set_client_information", state::set_client_information); m.add_method("chat", chat); - m.add_method("find_entities", find_entities); + 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("stop_pathfinding", movement::stop_pathfinding); m.add_method_mut("attack", interaction::attack); m.add_method_mut("block_interact", interaction::block_interact); @@ -102,39 +105,6 @@ impl UserData for Client { } } -fn find_entities(_lua: &Lua, client: &Client, filter_fn: Function) -> Result> { - let mut matched = Vec::new(); - - let mut ecs = client.inner.as_ref().unwrap().ecs.lock(); - let mut query = ecs.query_filtered::<( - &MinecraftEntityId, - &EntityUuid, - &EntityKind, - &AzaleaPosition, - &CustomName, - ), Without>(); - - for (&id, uuid, kind, position, custom_name) in query.iter(&ecs) { - let entity = Entity { - id: id.0, - uuid: uuid.to_string(), - kind: kind.to_string(), - position: Vec3 { - x: position.x, - y: position.y, - z: position.z, - }, - custom_name: custom_name.as_ref().map(ToString::to_string), - }; - - if filter_fn.call::(entity.clone()).unwrap() { - matched.push(entity); - } - } - - Ok(matched) -} - fn chat(_lua: &Lua, client: &Client, message: String) -> Result<()> { client.inner.as_ref().unwrap().chat(&message); Ok(()) diff --git a/src/scripting/client/world.rs b/src/scripting/client/world.rs new file mode 100644 index 0000000..53d0135 --- /dev/null +++ b/src/scripting/client/world.rs @@ -0,0 +1,132 @@ +use super::{Block, Client, Entity, FluidState, Vec3}; +use azalea::{ + BlockPos, + 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}; + +pub fn find_blocks( + _lua: &Lua, + client: &Client, + (nearest_to, block_names): (Vec3, Vec), +) -> Result> { + #[allow(clippy::cast_possible_truncation)] + Ok(client + .inner + .as_ref() + .unwrap() + .world() + .read() + .find_blocks( + BlockPos::new( + nearest_to.x as i32, + nearest_to.y as i32, + 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(), + }, + ) + .map(|p| Vec3 { + x: f64::from(p.x), + y: f64::from(p.y), + z: f64::from(p.z), + }) + .collect()) +} + +pub fn find_entities(_lua: &Lua, client: &Client, filter_fn: Function) -> Result> { + let mut matched = Vec::new(); + + let mut ecs = client.inner.as_ref().unwrap().ecs.lock(); + let mut query = ecs.query_filtered::<( + &MinecraftEntityId, + &EntityUuid, + &EntityKind, + &AzaleaPosition, + &CustomName, + ), Without>(); + + for (&id, uuid, kind, position, custom_name) in query.iter(&ecs) { + let entity = Entity { + id: id.0, + uuid: uuid.to_string(), + kind: kind.to_string(), + position: Vec3 { + x: position.x, + y: position.y, + z: position.z, + }, + custom_name: custom_name.as_ref().map(ToString::to_string), + }; + + if filter_fn.call::(entity.clone()).unwrap() { + matched.push(entity); + } + } + + Ok(matched) +} + +pub fn get_block_from_state(_lua: &Lua, _client: &Client, state: u32) -> Result> { + let Ok(state) = BlockState::try_from(state) else { + return Ok(None); + }; + let block: Box = 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> { + #[allow(clippy::cast_possible_truncation)] + Ok(client + .inner + .as_ref() + .unwrap() + .world() + .read() + .get_block_state(&BlockPos::new( + position.x as i32, + position.y as i32, + position.z as i32, + )) + .map(|b| b.id)) +} + +pub fn get_fluid_state(_lua: &Lua, client: &Client, position: Vec3) -> Result> { + #[allow(clippy::cast_possible_truncation)] + Ok(client + .inner + .as_ref() + .unwrap() + .world() + .read() + .get_fluid_state(&BlockPos::new( + position.x as i32, + position.y as i32, + position.z as i32, + )) + .map(|f| FluidState { + kind: f.kind as u8, + amount: f.amount, + falling: f.falling, + })) +} diff --git a/src/scripting/fluid_state.rs b/src/scripting/fluid_state.rs new file mode 100644 index 0000000..6233f75 --- /dev/null +++ b/src/scripting/fluid_state.rs @@ -0,0 +1,36 @@ +use mlua::{FromLua, IntoLua, Lua, Result, Value}; + +#[derive(Clone)] +pub struct FluidState { + pub kind: u8, + pub amount: u8, + pub falling: bool, +} + +impl IntoLua for FluidState { + fn into_lua(self, lua: &Lua) -> Result { + let table = lua.create_table()?; + table.set("kind", self.kind)?; + table.set("amount", self.amount)?; + table.set("falling", self.falling)?; + Ok(Value::Table(table)) + } +} + +impl FromLua for FluidState { + fn from_lua(value: Value, _lua: &Lua) -> Result { + if let Value::Table(table) = value { + Ok(Self { + kind: table.get("kind")?, + amount: table.get("amount")?, + falling: table.get("falling")?, + }) + } else { + Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "FluidState".to_string(), + message: None, + }) + } + } +} diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 10f71b2..6b7c54b 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,6 +1,8 @@ +pub mod block; pub mod client; pub mod direction; pub mod entity; +pub mod fluid_state; pub mod hunger; pub mod logging; pub mod vec3;