From 08b358c192dedbb15628ac687169aa362f4901c2 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Wed, 23 Apr 2025 14:14:34 -0700
Subject: [PATCH] roblox_emulator: Vector2

---
 .../src/runner/instance/instance.rs           | 15 ++-
 lib/roblox_emulator/src/runner/mod.rs         |  1 +
 lib/roblox_emulator/src/runner/runner.rs      |  1 +
 lib/roblox_emulator/src/runner/vector2.rs     | 93 +++++++++++++++++++
 4 files changed, 105 insertions(+), 5 deletions(-)
 create mode 100644 lib/roblox_emulator/src/runner/vector2.rs

diff --git a/lib/roblox_emulator/src/runner/instance/instance.rs b/lib/roblox_emulator/src/runner/instance/instance.rs
index 2427c4c..844882d 100644
--- a/lib/roblox_emulator/src/runner/instance/instance.rs
+++ b/lib/roblox_emulator/src/runner/instance/instance.rs
@@ -298,6 +298,7 @@ impl mlua::UserData for Instance{
 					Some(rbx_types::Variant::Enum(e))=>return crate::runner::r#enum::EnumItem::from(e).into_lua(lua),
 					Some(rbx_types::Variant::Color3(c))=>return crate::runner::color3::Color3::from(c).into_lua(lua),
 					Some(rbx_types::Variant::CFrame(cf))=>return crate::runner::cframe::CFrame::from(cf).into_lua(lua),
+					Some(rbx_types::Variant::Vector2(v))=>return crate::runner::vector2::Vector2::from(v).into_lua(lua),
 					Some(rbx_types::Variant::Vector3(v))=>return crate::runner::vector3::Vector3::from(v).into_lua(lua),
 					None=>(),
 					other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
@@ -338,10 +339,6 @@ impl mlua::UserData for Instance{
 					mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
 				)?;
 				let value=match &property.data_type{
-					rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
-						let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?;
-						rbx_types::Variant::Vector3(typed_value.into())
-					},
 					rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
 						let typed_value=Number::from_lua(value.clone(),lua)?.to_f32();
 						rbx_types::Variant::Float32(typed_value)
@@ -394,8 +391,16 @@ impl mlua::UserData for Instance{
 						let typed_value:&crate::runner::color_sequence::ColorSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
 						rbx_types::Variant::ColorSequence(typed_value.clone().into())
 					},
+					rbx_reflection::DataType::Value(rbx_types::VariantType::Vector2)=>{
+						let typed_value:crate::runner::vector2::Vector2=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector2"))?.borrow()?;
+						rbx_types::Variant::Vector2(typed_value.clone().into())
+					},
+					rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
+						let typed_value:crate::runner::vector3::Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector3"))?.borrow()?;
+						rbx_types::Variant::Vector3(typed_value.clone().into())
+					},
 					rbx_reflection::DataType::Value(rbx_types::VariantType::CFrame)=>{
-						let typed_value:&crate::runner::cframe::CFrame=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
+						let typed_value:crate::runner::cframe::CFrame=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
 						rbx_types::Variant::CFrame(typed_value.clone().into())
 					},
 					rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
diff --git a/lib/roblox_emulator/src/runner/mod.rs b/lib/roblox_emulator/src/runner/mod.rs
index 171f50c..d7ee1da 100644
--- a/lib/roblox_emulator/src/runner/mod.rs
+++ b/lib/roblox_emulator/src/runner/mod.rs
@@ -10,6 +10,7 @@ mod udim2;
 mod color3;
 mod cframe;
 mod number;
+mod vector2;
 mod vector3;
 mod brickcolor;
 mod tween_info;
diff --git a/lib/roblox_emulator/src/runner/runner.rs b/lib/roblox_emulator/src/runner/runner.rs
index fff425d..5ce3551 100644
--- a/lib/roblox_emulator/src/runner/runner.rs
+++ b/lib/roblox_emulator/src/runner/runner.rs
@@ -39,6 +39,7 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
 	super::udim2::set_globals(lua,&globals)?;
 	super::color3::set_globals(lua,&globals)?;
 	super::brickcolor::set_globals(lua,&globals)?;
+	super::vector2::set_globals(lua,&globals)?;
 	super::vector3::set_globals(lua,&globals)?;
 	super::cframe::set_globals(lua,&globals)?;
 	super::instance::instance::set_globals(lua,&globals)?;
diff --git a/lib/roblox_emulator/src/runner/vector2.rs b/lib/roblox_emulator/src/runner/vector2.rs
new file mode 100644
index 0000000..e5ddcda
--- /dev/null
+++ b/lib/roblox_emulator/src/runner/vector2.rs
@@ -0,0 +1,93 @@
+use mlua::FromLua;
+
+use super::number::Number;
+
+#[derive(Clone,Copy)]
+pub struct Vector2(glam::Vec2);
+
+impl Vector2{
+	pub const fn new(x:f32,y:f32)->Self{
+		Self(glam::vec2(x,y))
+	}
+}
+
+pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
+	let table=lua.create_table()?;
+
+	//Vector2.new
+	table.raw_set("new",
+		lua.create_function(|_,(x,y):(Option<Number>,Option<Number>)|
+			match (x,y){
+				(Some(x),Some(y))=>Ok(Vector2::new(x.into(),y.into())),
+				(None,None)=>Ok(Vector2(glam::Vec2::ZERO)),
+				_=>Err(mlua::Error::runtime("Unsupported arguments to Vector2.new")),
+			}
+		)?
+	)?;
+
+	globals.set("Vector2",table)?;
+
+	Ok(())
+}
+
+impl From<Vector2> for rbx_types::Vector2{
+	fn from(Vector2(v):Vector2)->rbx_types::Vector2{
+		rbx_types::Vector2::new(v.x,v.y)
+	}
+}
+
+impl From<rbx_types::Vector2> for Vector2{
+	fn from(value:rbx_types::Vector2)->Vector2{
+		Vector2::new(value.x,value.y)
+	}
+}
+
+impl mlua::UserData for Vector2{
+	fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
+		fields.add_field_method_get("magnitude",|_,Vector2(this)|Ok(this.length()));
+		fields.add_field_method_get("Magnitude",|_,Vector2(this)|Ok(this.length()));
+		fields.add_field_method_get("unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
+		fields.add_field_method_get("Unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
+		fields.add_field_method_get("x",|_,Vector2(this)|Ok(this.x));
+		fields.add_field_method_get("X",|_,Vector2(this)|Ok(this.x));
+		fields.add_field_method_get("y",|_,Vector2(this)|Ok(this.y));
+		fields.add_field_method_get("Y",|_,Vector2(this)|Ok(this.y));
+	}
+
+	fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
+		//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
+
+		methods.add_meta_function(mlua::MetaMethod::Add,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this+val)));
+		methods.add_meta_function(mlua::MetaMethod::Sub,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this-val)));
+		methods.add_meta_function(mlua::MetaMethod::Mul,|lua,(lhs,rhs):(mlua::Value,mlua::Value)|{
+			match (lhs,rhs){
+				(mlua::Value::UserData(lhs),mlua::Value::UserData(rhs))=>lhs.borrow_scoped(|Vector2(lhs):&Vector2|rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs*rhs)))?,
+				(lhs,mlua::Value::UserData(rhs))=>{
+					let lhs=Number::from_lua(lhs,lua)?;
+					rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs.to_f32()*rhs))
+				},
+				(mlua::Value::UserData(lhs),rhs)=>{
+					let rhs=Number::from_lua(rhs,lua)?;
+					lhs.borrow_scoped(|Vector2(lhs):&Vector2|Self(lhs*rhs.to_f32()))
+				},
+				_=>Err(mlua::Error::runtime(format!("Expected Vector2")))
+			}
+		});
+		methods.add_meta_function(mlua::MetaMethod::Div,|_,(Vector2(this),val):(Self,mlua::Value)|{
+			match val{
+				mlua::Value::Integer(n)=>Ok(Self(this/(n as f32))),
+				mlua::Value::Number(n)=>Ok(Self(this/(n as f32))),
+				mlua::Value::UserData(ud)=>ud.borrow_scoped(|Vector2(rhs):&Vector2|Self(this/rhs)),
+				other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector2 by {other:?}"))),
+			}
+		});
+		methods.add_meta_function(mlua::MetaMethod::ToString,|_,Vector2(this):Self|
+			Ok(format!("Vector2.new({},{})",
+				this.x,
+				this.y,
+			))
+		);
+	}
+}
+
+type_from_lua_userdata!(Vector2);