feat(client): add world interaction methods

This commit is contained in:
Ryan 2025-02-16 18:21:06 -05:00
parent 8e89f8e9ea
commit 04b0bdd756
Signed by: ErrorNoInternet
GPG Key ID: 2486BFB7B1E6A4A3
5 changed files with 230 additions and 42 deletions

48
src/scripting/block.rs Normal file

@ -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<Value> {
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<Self> {
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,
})
}
}
}

@ -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<AzaleaClient>,
@ -87,7 +86,11 @@ impl UserData for Client {
fn add_methods<M: UserDataMethods<Self>>(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<Vec<Entity>> {
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<Dead>>();
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::<bool>(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(())

@ -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<String>),
) -> Result<Vec<Vec3>> {
#[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::<Box<dyn AzaleaBlock>>::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<Vec<Entity>> {
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<Dead>>();
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::<bool>(entity.clone()).unwrap() {
matched.push(entity);
}
}
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
.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<Option<FluidState>> {
#[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,
}))
}

@ -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<Value> {
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<Self> {
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,
})
}
}
}

@ -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;