diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs index 4136587..2c2c1e9 100644 --- a/packages/lib-roblox/src/datatypes/conversion.rs +++ b/packages/lib-roblox/src/datatypes/conversion.rs @@ -116,11 +116,12 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { use super::types::*; use RbxVariant as Rbx; + // NOTE: Enum is intentionally left out here, it has a custom + // conversion going from instance property > datatype instead, + // check `EnumItem::from_instance_property` for specifics Ok(match variant.clone() { // Not yet implemented datatypes - // Rbx::Axes(_) => todo!(), // Rbx::CFrame(_) => todo!(), - // Rbx::Enum(_) => todo!(), // Rbx::Faces(_) => todo!(), // Rbx::NumberRange(_) => todo!(), // Rbx::NumberSequence(_) => todo!(), @@ -131,8 +132,9 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { // Rbx::Region3(_) => todo!(), // Rbx::Region3int16(_) => todo!(), - Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?, + Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?, + Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?, Rbx::Color3(value) => lua.create_userdata(Color3::from(value))?, Rbx::Color3uint8(value) => lua.create_userdata(Color3::from(value))?, Rbx::ColorSequence(value) => lua.create_userdata(ColorSequence::from(value))?, @@ -167,8 +169,9 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> { use rbx_dom_weak::types as rbx; let f = match variant_type { - RbxVariantType::BrickColor => convert::, + RbxVariantType::Axes => convert::, + RbxVariantType::BrickColor => convert::, RbxVariantType::Color3 => convert::, RbxVariantType::Color3uint8 => convert::, RbxVariantType::ColorSequence => convert::, diff --git a/packages/lib-roblox/src/datatypes/types/axes.rs b/packages/lib-roblox/src/datatypes/types/axes.rs new file mode 100644 index 0000000..3c7ce1a --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/axes.rs @@ -0,0 +1,133 @@ +use core::fmt; + +use mlua::prelude::*; +use rbx_dom_weak::types::Axes as RbxAxes; + +use super::{super::*, EnumItem}; + +/** + An implementation of the [Axes](https://create.roblox.com/docs/reference/engine/datatypes/Axes) Roblox datatype. + + This implements all documented properties, methods & constructors of the Axes class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Axes { + pub(crate) x: bool, + pub(crate) y: bool, + pub(crate) z: bool, +} + +impl Axes { + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + datatype_table.set( + "new", + lua.create_function(|_, args: LuaMultiValue| { + let mut x = false; + let mut y = false; + let mut z = false; + let mut check = |e: &EnumItem| { + if e.parent.desc.name == "Axis" { + match &e.name { + name if name == "X" => x = true, + name if name == "Y" => y = true, + name if name == "Z" => z = true, + _ => {} + } + } else if e.parent.desc.name == "NormalId" { + match &e.name { + name if name == "Left" || name == "Right" => x = true, + name if name == "Top" || name == "Bottom" => y = true, + name if name == "Front" || name == "Back" => z = true, + _ => {} + } + } + }; + for (index, arg) in args.into_iter().enumerate() { + if let LuaValue::UserData(u) = arg { + if let Ok(e) = u.borrow::() { + check(&e); + } else { + return Err(LuaError::RuntimeError(format!( + "Expected argument #{} to be an EnumItem, got userdata", + index + ))); + } + } else { + return Err(LuaError::RuntimeError(format!( + "Expected argument #{} to be an EnumItem, got {}", + index, + arg.type_name() + ))); + } + } + Ok(Axes { x, y, z }) + })?, + )?; + Ok(()) + } +} + +impl LuaUserData for Axes { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("X", |_, this| Ok(this.x)); + fields.add_field_method_get("Y", |_, this| Ok(this.y)); + fields.add_field_method_get("Z", |_, this| Ok(this.z)); + fields.add_field_method_get("Left", |_, this| Ok(this.x)); + fields.add_field_method_get("Right", |_, this| Ok(this.x)); + fields.add_field_method_get("Top", |_, this| Ok(this.y)); + fields.add_field_method_get("Bottom", |_, this| Ok(this.y)); + fields.add_field_method_get("Front", |_, this| Ok(this.z)); + fields.add_field_method_get("Back", |_, this| Ok(this.z)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string); + } +} + +impl fmt::Display for Axes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut got_value = false; + if self.x { + write!(f, "X")?; + got_value = true; + } + if self.y { + if got_value { + write!(f, ", Y")?; + } else { + write!(f, "Y")?; + got_value = true; + } + } + if self.z { + if got_value { + write!(f, ", Z")?; + } else { + write!(f, "Z")?; + } + } + Ok(()) + } +} + +impl From for Axes { + fn from(v: RbxAxes) -> Self { + let bits = v.bits(); + let x = (bits & 1) == 1; + let y = ((bits >> 1) & 1) == 1; + let z = ((bits >> 2) & 1) == 1; + Self { x, y, z } + } +} + +impl From for RbxAxes { + fn from(v: Axes) -> Self { + let mut bits = 0; + bits += v.x as u8; + bits += (v.y as u8) << 1; + bits += (v.z as u8) << 2; + RbxAxes::from_bits(bits).expect("Invalid bits") + } +} diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs index db3d455..b29d498 100644 --- a/packages/lib-roblox/src/datatypes/types/mod.rs +++ b/packages/lib-roblox/src/datatypes/types/mod.rs @@ -1,3 +1,4 @@ +mod axes; mod brick_color; mod color3; mod color_sequence; @@ -12,6 +13,7 @@ mod vector2int16; mod vector3; mod vector3int16; +pub use axes::Axes; pub use brick_color::BrickColor; pub use color3::Color3; pub use color_sequence::ColorSequence; @@ -68,6 +70,7 @@ mod tests { } create_tests! { + axes: "datatypes/Axes", brick_color: "datatypes/BrickColor", color3: "datatypes/Color3", color_sequence: "datatypes/ColorSequence", diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index 34d863f..519574d 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -19,6 +19,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult> { use datatypes::types::*; Ok(vec![ // Classes + ("Axes", make_dt(lua, Axes::make_table)?), ("BrickColor", make_dt(lua, BrickColor::make_table)?), ("Color3", make_dt(lua, Color3::make_table)?), ("ColorSequence", make_dt(lua, ColorSequence::make_table)?), diff --git a/tests/roblox/datatypes/Axes.luau b/tests/roblox/datatypes/Axes.luau new file mode 100644 index 0000000..2694657 --- /dev/null +++ b/tests/roblox/datatypes/Axes.luau @@ -0,0 +1,44 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local Axes = _G.Axes +local Enum = _G.Enum + +-- Constructors & properties + +Axes.new() +Axes.new(Enum.Axis.X) +Axes.new(Enum.Axis.X, Enum.NormalId.Top) + +assert(not pcall(function() + return Axes.new(false) +end)) +assert(not pcall(function() + return Axes.new({}) +end)) +assert(not pcall(function() + return Axes.new(newproxy(true)) +end)) + +assert(Axes.new().X == false) +assert(Axes.new().Y == false) +assert(Axes.new().Z == false) + +assert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).X == true) +assert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).Y == true) +assert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).Z == false) + +assert(Axes.new(Enum.Axis.X, Enum.NormalId.Left).X == true) +assert(Axes.new(Enum.Axis.X, Enum.NormalId.Right).X == true) + +assert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).X == false) +assert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).Y == false) +assert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).Z == true) + +-- Ops + +assert(not pcall(function() + return Axes.new() + Axes.new() +end)) +assert(not pcall(function() + return Axes.new() / Axes.new() +end))