diff --git a/CHANGELOG.md b/CHANGELOG.md index 8207518..ca4b39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added [Terrain:GetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor) and [Terrain:SetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor) ([#93]) +- Added support for a variable number of arguments for CFrame methods ([#85]) + +[#93]: https://github.com/filiptibell/lune/pull/93 +[#85]: https://github.com/filiptibell/lune/pull/85 + ## `0.7.7` - August 23rd, 2023 ### Added diff --git a/Cargo.lock b/Cargo.lock index 77ffe46..e8403a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,24 +43,23 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" @@ -82,9 +81,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -296,9 +295,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.23" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" +checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" dependencies = [ "clap_builder", "clap_derive", @@ -307,9 +306,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.23" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" dependencies = [ "anstream", "anstyle", @@ -319,9 +318,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" dependencies = [ "heck", "proc-macro2", @@ -331,9 +330,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clipboard-win" @@ -523,9 +522,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -557,9 +556,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" +checksum = "837c0466252947ada828b975e12daf82e18bb5444e4df87be6038d4469e2a3d2" dependencies = [ "serde", ] @@ -1145,9 +1144,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6356d163eb2ebaa9caafc3a3ed6aedc686128e913557cd3a5591fd4cd726f5" +checksum = "6c3a7a7ff4481ec91b951a733390211a8ace1caba57266ccb5f4d4966704e560" dependencies = [ "bstr", "erased-serde", @@ -1641,9 +1640,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" -version = "0.11.19" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b9b67e2ca7dd9e9f9285b759de30ff538aab981abaaf7bc9bd90b84a0126c3" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ "base64 0.21.2", "bytes", @@ -1870,9 +1869,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.185" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" dependencies = [ "serde_derive", ] @@ -1889,9 +1888,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.185" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" dependencies = [ "proc-macro2", "quote", @@ -2850,9 +2849,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] diff --git a/src/lune/builtins/luau/options.rs b/src/lune/builtins/luau/options.rs index d1febe6..18bc8be 100644 --- a/src/lune/builtins/luau/options.rs +++ b/src/lune/builtins/luau/options.rs @@ -39,7 +39,7 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions { let get_and_check = |name: &'static str| -> LuaResult> { match t.get(name)? { - Some(n @ (0 | 1 | 2)) => Ok(Some(n)), + Some(n @ (0..=2)) => Ok(Some(n)), Some(n) => Err(LuaError::runtime(format!( "'{name}' must be one of: 0, 1, or 2 - got {n}" ))), diff --git a/src/lune/builtins/net/websocket.rs b/src/lune/builtins/net/websocket.rs index 2bd7c5a..aa01c13 100644 --- a/src/lune/builtins/net/websocket.rs +++ b/src/lune/builtins/net/websocket.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, sync::Arc}; +use std::sync::Arc; use hyper::upgrade::Upgraded; use mlua::prelude::*; @@ -46,7 +46,7 @@ return freeze(setmetatable({ #[derive(Debug)] pub struct NetWebSocket { - close_code: Arc>>, + close_code: Arc>>, read_stream: Arc>>>, write_stream: Arc, WsMessage>>>, } @@ -69,7 +69,7 @@ where let (write, read) = value.split(); Self { - close_code: Arc::new(Cell::new(None)), + close_code: Arc::new(AsyncMutex::new(None)), read_stream: Arc::new(AsyncMutex::new(read)), write_stream: Arc::new(AsyncMutex::new(write)), } @@ -137,10 +137,16 @@ fn close_code<'lua, T>( where T: AsyncRead + AsyncWrite + Unpin, { - Ok(match socket.close_code.get() { - Some(code) => LuaValue::Number(code as f64), - None => LuaValue::Nil, - }) + Ok( + match *socket + .close_code + .try_lock() + .expect("Failed to lock close code") + { + Some(code) => LuaValue::Number(code as f64), + None => LuaValue::Nil, + }, + ) } async fn close<'lua, T>( @@ -204,7 +210,8 @@ where let msg = match item { Ok(Some(WsMessage::Close(msg))) => { if let Some(msg) = &msg { - socket.close_code.replace(Some(msg.code.into())); + let mut code = socket.close_code.lock().await; + *code = Some(msg.code.into()); } Ok(Some(WsMessage::Close(msg))) } diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index b4c1c61..55505a0 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -57,7 +57,12 @@ impl<'fut> Scheduler<'fut> { if thread.status() != LuaThreadStatus::Resumable { // NOTE: Threads that were spawned to resume // with an error will not have a result sender - if let Some(sender) = self.thread_senders.borrow_mut().remove(&thread_id) { + if let Some(sender) = self + .thread_senders + .try_lock() + .expect("Failed to get thread senders") + .remove(&thread_id) + { if sender.receiver_count() > 0 { let stored = match res { Err(e) => Err(e), diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 850d3c2..8f3d174 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -14,8 +14,8 @@ impl<'fut> Scheduler<'fut> { pub(super) fn has_thread(&self) -> bool { !self .threads - .try_borrow() - .expect("Failed to borrow threads vec") + .try_lock() + .expect("Failed to lock threads vec") .is_empty() } @@ -27,9 +27,9 @@ impl<'fut> Scheduler<'fut> { pub(super) fn pop_thread(&self) -> LuaResult> { match self .threads - .try_borrow_mut() + .try_lock() .into_lua_err() - .context("Failed to borrow threads vec")? + .context("Failed to lock threads vec")? .pop_front() { Some(thread) => Ok(Some(thread)), @@ -54,9 +54,9 @@ impl<'fut> Scheduler<'fut> { self.state.set_thread_error(thread_id, err); self.threads - .try_borrow_mut() + .try_lock() .into_lua_err() - .context("Failed to borrow threads vec")? + .context("Failed to lock threads vec")? .push_front(thread); // NOTE: We might be resuming futures, need to signal that a @@ -83,16 +83,18 @@ impl<'fut> Scheduler<'fut> { let thread_id = thread.id(); self.threads - .try_borrow_mut() + .try_lock() .into_lua_err() - .context("Failed to borrow threads vec")? + .context("Failed to lock threads vec")? .push_front(thread); // NOTE: We might be resuming the same thread several times and // pushing it to the scheduler several times before it is done, // and we should only ever create one result sender per thread self.thread_senders - .borrow_mut() + .try_lock() + .into_lua_err() + .context("Failed to lock thread senders vec")? .entry(thread_id) .or_insert_with(|| SchedulerThreadSender::new(1)); @@ -120,16 +122,18 @@ impl<'fut> Scheduler<'fut> { let thread_id = thread.id(); self.threads - .try_borrow_mut() + .try_lock() .into_lua_err() - .context("Failed to borrow threads vec")? + .context("Failed to lock threads vec")? .push_back(thread); // NOTE: We might be resuming the same thread several times and // pushing it to the scheduler several times before it is done, // and we should only ever create one result sender per thread self.thread_senders - .borrow_mut() + .try_lock() + .into_lua_err() + .context("Failed to lock thread senders vec")? .entry(thread_id) .or_insert_with(|| SchedulerThreadSender::new(1)); @@ -149,7 +153,7 @@ impl<'fut> Scheduler<'fut> { thread_id: SchedulerThreadId, ) -> LuaResult> { let mut recv = { - let senders = self.thread_senders.borrow(); + let senders = self.thread_senders.lock().await; let sender = senders .get(&thread_id) .expect("Tried to wait for thread that is not queued"); diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index e153234..df1a087 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::{HashMap, VecDeque}, pin::Pin, sync::Arc, @@ -37,8 +36,8 @@ type SchedulerFuture<'fut> = Pin + 'fut>>; #[derive(Debug, Clone)] pub(crate) struct Scheduler<'fut> { state: Arc, - threads: Arc>>, - thread_senders: Arc>>, + threads: Arc>>, + thread_senders: Arc>>, /* FUTURE: Get rid of these, let the tokio runtime handle running and resumption of futures completely, just use our scheduler @@ -64,11 +63,12 @@ impl<'fut> Scheduler<'fut> { /** Creates a new scheduler. */ + #[allow(clippy::arc_with_non_send_sync)] // FIXME: Clippy lints our tokio mutexes that are definitely Send + Sync pub fn new() -> Self { Self { state: Arc::new(SchedulerState::new()), - threads: Arc::new(RefCell::new(VecDeque::new())), - thread_senders: Arc::new(RefCell::new(HashMap::new())), + threads: Arc::new(AsyncMutex::new(VecDeque::new())), + thread_senders: Arc::new(AsyncMutex::new(HashMap::new())), futures_lua: Arc::new(AsyncMutex::new(FuturesUnordered::new())), futures_background: Arc::new(AsyncMutex::new(FuturesUnordered::new())), } diff --git a/src/lune/util/formatting.rs b/src/lune/util/formatting.rs index 165ceb4..2107c14 100644 --- a/src/lune/util/formatting.rs +++ b/src/lune/util/formatting.rs @@ -118,8 +118,8 @@ pub fn pretty_format_value( COLOR_GREEN.apply_to( s.to_string_lossy() .replace('"', r#"\""#) - .replace('\r', r#"\r"#) - .replace('\n', r#"\n"#) + .replace('\r', r"\r") + .replace('\n', r"\n") ) )?, LuaValue::Table(ref tab) => { diff --git a/src/roblox/datatypes/types/cframe.rs b/src/roblox/datatypes/types/cframe.rs index 80705b6..638e730 100644 --- a/src/roblox/datatypes/types/cframe.rs +++ b/src/roblox/datatypes/types/cframe.rs @@ -2,7 +2,7 @@ use core::fmt; use std::ops; use glam::{EulerRot, Mat4, Quat, Vec3}; -use mlua::prelude::*; +use mlua::{prelude::*, Variadic}; use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3}; use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable}; @@ -205,38 +205,46 @@ impl LuaUserData for CFrame { translation, ))) }); - methods.add_method("ToWorldSpace", |_, this, rhs: LuaUserDataRef| { - Ok(*this * *rhs) - }); - methods.add_method("ToObjectSpace", |_, this, rhs: LuaUserDataRef| { - let inverse = this.inverse(); - Ok(inverse * *rhs) - }); + methods.add_method( + "ToWorldSpace", + |_, this, rhs: Variadic>| { + Ok(Variadic::from_iter(rhs.into_iter().map(|cf| *this * *cf))) + }, + ); + methods.add_method( + "ToObjectSpace", + |_, this, rhs: Variadic>| { + let inverse = this.inverse(); + Ok(Variadic::from_iter(rhs.into_iter().map(|cf| inverse * *cf))) + }, + ); methods.add_method( "PointToWorldSpace", - |_, this, rhs: LuaUserDataRef| Ok(*this * *rhs), + |_, this, rhs: Variadic>| { + Ok(Variadic::from_iter(rhs.into_iter().map(|v3| *this * *v3))) + }, ); methods.add_method( "PointToObjectSpace", - |_, this, rhs: LuaUserDataRef| { + |_, this, rhs: Variadic>| { let inverse = this.inverse(); - Ok(inverse * *rhs) + Ok(Variadic::from_iter(rhs.into_iter().map(|v3| inverse * *v3))) }, ); methods.add_method( "VectorToWorldSpace", - |_, this, rhs: LuaUserDataRef| { + |_, this, rhs: Variadic>| { let result = *this - Vector3(this.position()); - Ok(result * *rhs) + Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3))) }, ); methods.add_method( "VectorToObjectSpace", - |_, this, rhs: LuaUserDataRef| { + |_, this, rhs: Variadic>| { let inverse = this.inverse(); let result = inverse - Vector3(inverse.position()); - Ok(result * *rhs) + Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3))) }, ); #[rustfmt::skip] diff --git a/src/roblox/datatypes/types/color3.rs b/src/roblox/datatypes/types/color3.rs index 8026875..33eba6d 100644 --- a/src/roblox/datatypes/types/color3.rs +++ b/src/roblox/datatypes/types/color3.rs @@ -108,6 +108,20 @@ impl LuaExportsTable<'_> for Color3 { } } +impl<'lua> FromLua<'lua> for Color3 { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let LuaValue::UserData(ud) = value { + Ok(*ud.borrow::()?) + } else { + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "Color3", + message: None, + }) + } + } +} + impl LuaUserData for Color3 { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("R", |_, this| Ok(this.r)); @@ -287,20 +301,12 @@ impl From for DomColor3 { impl From for Color3 { fn from(v: DomColor3uint8) -> Self { - Self { - r: (v.r as f32) / 255f32, - g: (v.g as f32) / 255f32, - b: (v.b as f32) / 255f32, - } + Color3::from(DomColor3::from(v)) } } impl From for DomColor3uint8 { fn from(v: Color3) -> Self { - Self { - r: v.r.clamp(u8::MIN as f32, u8::MAX as f32) as u8, - g: v.g.clamp(u8::MIN as f32, u8::MAX as f32) as u8, - b: v.b.clamp(u8::MIN as f32, u8::MAX as f32) as u8, - } + DomColor3uint8::from(DomColor3::from(v)) } } diff --git a/src/roblox/datatypes/types/enum_item.rs b/src/roblox/datatypes/types/enum_item.rs index 1eb1c49..8a3633c 100644 --- a/src/roblox/datatypes/types/enum_item.rs +++ b/src/roblox/datatypes/types/enum_item.rs @@ -74,6 +74,20 @@ impl LuaUserData for EnumItem { } } +impl<'lua> FromLua<'lua> for EnumItem { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let LuaValue::UserData(ud) = value { + Ok(ud.borrow::()?.to_owned()) + } else { + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "EnumItem", + message: None, + }) + } + } +} + impl fmt::Display for EnumItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}", self.parent, self.name) diff --git a/src/roblox/instance/mod.rs b/src/roblox/instance/mod.rs index 1640b1f..2b124f8 100644 --- a/src/roblox/instance/mod.rs +++ b/src/roblox/instance/mod.rs @@ -20,6 +20,7 @@ use crate::{ pub(crate) mod base; pub(crate) mod data_model; +pub(crate) mod terrain; pub(crate) mod workspace; const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes"; @@ -729,6 +730,7 @@ impl LuaUserData for Instance { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { base::add_methods(methods); data_model::add_methods(methods); + terrain::add_methods(methods); } } diff --git a/src/roblox/instance/terrain.rs b/src/roblox/instance/terrain.rs new file mode 100644 index 0000000..2c52acf --- /dev/null +++ b/src/roblox/instance/terrain.rs @@ -0,0 +1,127 @@ +use mlua::prelude::*; +use rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant}; + +use crate::roblox::{ + datatypes::types::{Color3, EnumItem}, + shared::classes::{add_class_restricted_method, add_class_restricted_method_mut}, +}; + +use super::Instance; + +pub const CLASS_NAME: &str = "Terrain"; + +fn material_from_name(material_name: &str) -> Option { + match material_name { + "Grass" => Some(TerrainMaterials::Grass), + "Slate" => Some(TerrainMaterials::Slate), + "Concrete" => Some(TerrainMaterials::Concrete), + "Brick" => Some(TerrainMaterials::Brick), + "Sand" => Some(TerrainMaterials::Sand), + "WoodPlanks" => Some(TerrainMaterials::WoodPlanks), + "Rock" => Some(TerrainMaterials::Rock), + "Glacier" => Some(TerrainMaterials::Glacier), + "Snow" => Some(TerrainMaterials::Snow), + "Sandstone" => Some(TerrainMaterials::Sandstone), + "Mud" => Some(TerrainMaterials::Mud), + "Basalt" => Some(TerrainMaterials::Basalt), + "Ground" => Some(TerrainMaterials::Ground), + "CrackedLava" => Some(TerrainMaterials::CrackedLava), + "Asphalt" => Some(TerrainMaterials::Asphalt), + "Cobblestone" => Some(TerrainMaterials::Cobblestone), + "Ice" => Some(TerrainMaterials::Ice), + "LeafyGrass" => Some(TerrainMaterials::LeafyGrass), + "Salt" => Some(TerrainMaterials::Salt), + "Limestone" => Some(TerrainMaterials::Limestone), + "Pavement" => Some(TerrainMaterials::Pavement), + _ => None, + } +} + +pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M) { + add_class_restricted_method( + methods, + CLASS_NAME, + "GetMaterialColor", + terrain_get_material_color, + ); + + add_class_restricted_method_mut( + methods, + CLASS_NAME, + "SetMaterialColor", + terrain_set_material_color, + ) +} + +fn get_or_create_material_colors(instance: &Instance) -> MaterialColors { + if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors") + { + material_colors + } else { + MaterialColors::default() + } +} + +/** + Returns the color of the given terrain material. + + ### See Also + * [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor) + on the Roblox Developer Hub +*/ +fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult { + let material_colors = get_or_create_material_colors(this); + + if &material.parent.desc.name != "Material" { + return Err(LuaError::RuntimeError(format!( + "Expected Enum.Material, got Enum.{}", + &material.parent.desc.name + ))); + } + + if let Some(terrain_material) = material_from_name(&material.name) { + Ok(material_colors.get_color(terrain_material).into()) + } else { + Err(LuaError::RuntimeError(format!( + "{} is not a valid Terrain material", + &material.name + ))) + } +} + +/** + Sets the color of the given terrain material. + + ### See Also + * [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor) + on the Roblox Developer Hub +*/ +fn terrain_set_material_color( + _: &Lua, + this: &mut Instance, + args: (EnumItem, Color3), +) -> LuaResult<()> { + let mut material_colors = get_or_create_material_colors(this); + let material = args.0; + let color = args.1; + + if &material.parent.desc.name != "Material" { + return Err(LuaError::RuntimeError(format!( + "Expected Enum.Material, got Enum.{}", + &material.parent.desc.name + ))); + } + + let terrain_material = if let Some(terrain_material) = material_from_name(&material.name) { + terrain_material + } else { + return Err(LuaError::RuntimeError(format!( + "{} is not a valid Terrain material", + &material.name + ))); + }; + + material_colors.set_color(terrain_material, color.into()); + this.set_property("MaterialColors", Variant::MaterialColors(material_colors)); + Ok(()) +} diff --git a/src/roblox/shared/classes.rs b/src/roblox/shared/classes.rs index f34db74..90af83a 100644 --- a/src/roblox/shared/classes.rs +++ b/src/roblox/shared/classes.rs @@ -71,7 +71,6 @@ pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Inst }); } -#[allow(dead_code)] pub(crate) fn add_class_restricted_method_mut< 'lua, M: LuaUserDataMethods<'lua, Instance>, diff --git a/src/tests.rs b/src/tests.rs index dbe5de2..0aa8975 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -146,6 +146,7 @@ create_tests! { roblox_instance_classes_data_model: "roblox/instance/classes/DataModel", roblox_instance_classes_workspace: "roblox/instance/classes/Workspace", + roblox_instance_classes_terrain: "roblox/instance/classes/Terrain", roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren", roblox_instance_methods_clone: "roblox/instance/methods/Clone", diff --git a/tests/roblox/datatypes/CFrame.luau b/tests/roblox/datatypes/CFrame.luau index 6ac32e0..85b40bb 100644 --- a/tests/roblox/datatypes/CFrame.luau +++ b/tests/roblox/datatypes/CFrame.luau @@ -103,6 +103,9 @@ local offset = CFrame.new(0, 0, -5) assert(offset:ToWorldSpace(offset).Z == offset.Z * 2) assert(offset:ToObjectSpace(offset).Z == 0) +assert(select("#", offset:ToWorldSpace(offset, offset, offset)) == 3) +assert(select("#", offset:ToObjectSpace(offset, offset, offset)) == 3) + local world = CFrame.fromOrientation(0, math.rad(90), 0) * CFrame.new(0, 0, -5) local world2 = CFrame.fromOrientation(0, -math.rad(90), 0) * CFrame.new(0, 0, -5) assertEq(CFrame.identity:ToObjectSpace(world), world) diff --git a/tests/roblox/instance/classes/Terrain.luau b/tests/roblox/instance/classes/Terrain.luau new file mode 100644 index 0000000..3902d21 --- /dev/null +++ b/tests/roblox/instance/classes/Terrain.luau @@ -0,0 +1,13 @@ +local roblox = require("@lune/roblox") +local Instance = roblox.Instance +local Color3 = roblox.Color3 +local Enum = roblox.Enum + +local game = Instance.new("DataModel") +local workspace = game:GetService("Workspace") +local terrain = (workspace :: any).Terrain + +assert(terrain:GetMaterialColor(Enum.Material.Grass) == Color3.fromRGB(106, 127, 63)) + +terrain:SetMaterialColor(Enum.Material.Sand, Color3.new(1, 1, 1)) +assert(terrain:GetMaterialColor(Enum.Material.Sand) == Color3.new(1, 1, 1))