diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index fe25b20d74..1d763352b5 100644 --- a/DMCompiler/DM/DMObjectTree.cs +++ b/DMCompiler/DM/DMObjectTree.cs @@ -66,6 +66,7 @@ public DMObject GetOrCreateDMObject(DreamPath path) { case "client": case "datum": case "list": + case "vector": case "savefile": case "world": parent = GetOrCreateDMObject(DreamPath.Root); diff --git a/DMCompiler/DMStandard/Types/Vector.dm b/DMCompiler/DMStandard/Types/Vector.dm new file mode 100644 index 0000000000..71ceb34a22 --- /dev/null +++ b/DMCompiler/DMStandard/Types/Vector.dm @@ -0,0 +1,27 @@ +/vector + // TODO: Verify these default values + var/len = 2 as num + var/size = 0 as num + var/x = 0 as num + var/y = 0 as num + var/z = 0 as num + + proc/New(x, y, z) + + proc/Cross(vector/B) + set opendream_unimplemented = TRUE + + proc/Dot(vector/B) + set opendream_unimplemented = TRUE + + proc/Interpolate(vector/B, t) + set opendream_unimplemented = TRUE + + proc/Normalize() + set opendream_unimplemented = TRUE + + proc/Turn(angle) + set opendream_unimplemented = TRUE + +/proc/vector(x, y, z) + return new /vector(x, y, z) diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 80a283ea9e..4795053892 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -135,6 +135,7 @@ proc/winset(player, control_id, params) #include "Types\Regex.dm" #include "Types\Savefile.dm" #include "Types\Sound.dm" +#include "Types\Vector.dm" #include "Types\World.dm" #include "Types\Atoms\_Atom.dm" #include "Types\Atoms\Area.dm" diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 668b00cab0..2fdd1a785a 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -39,6 +39,7 @@ public sealed class DreamObjectTree { public TreeEntry DatabaseQuery { get; private set; } public TreeEntry Regex { get; private set; } public TreeEntry Filter { get; private set; } + public TreeEntry Vector { get; private set; } public TreeEntry Icon { get; private set; } public TreeEntry Image { get; private set; } public TreeEntry MutableAppearance { get; private set; } @@ -177,6 +178,8 @@ public DreamObject CreateObject(TreeEntry type) { throw new Exception("New turfs must be created by the map manager"); if (type.ObjectDefinition.IsSubtypeOf(Exception)) return new DreamObjectException(type.ObjectDefinition); + if (type.ObjectDefinition.IsSubtypeOf(Vector)) + return new DreamObjectVector(type.ObjectDefinition); return new DreamObject(type.ObjectDefinition); } @@ -294,6 +297,7 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc DatabaseQuery = GetTreeEntry("/database/query"); Regex = GetTreeEntry("/regex"); Filter = GetTreeEntry("/dm_filter"); + Vector = GetTreeEntry("/vector"); Icon = GetTreeEntry("/icon"); Image = GetTreeEntry("/image"); MutableAppearance = GetTreeEntry("/mutable_appearance"); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs b/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs new file mode 100644 index 0000000000..7887b8239d --- /dev/null +++ b/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs @@ -0,0 +1,147 @@ +using System.Linq; +using OpenDreamRuntime.Procs; + +namespace OpenDreamRuntime.Objects.Types; + +public sealed class DreamObjectVector(DreamObjectDefinition definition) : DreamObject(definition) { + public float X, Y; + + public float Z { + get => Is3D ? _z : 0; + set { + if (!Is3D) + return; + _z = value; + } + } + + public bool Is3D { get; private set; } + + public float Size { + get => MathF.Sqrt(X * X + Y * Y + Z * Z); + set { + if (X == 0 && Y == 0 && Z == 0) + return; + + var magnitude = Size; + X = X / magnitude * value; + Y = Y / magnitude * value; + Z = Z / magnitude * value; + } + } + + private float _z; + + public override void Initialize(DreamProcArguments args) { + base.Initialize(args); + + var arg1 = args.GetArgument(0); + if (arg1.TryGetValueAsFloat(out var x) && args.Count is 2 or 3) { // X, Y, optionally Z + X = x; + Y = args.GetArgument(1).UnsafeGetValueAsFloat(); + if (args.Count == 3) { + Is3D = true; + Z = args.GetArgument(2).UnsafeGetValueAsFloat(); + } + + return; + } else if (arg1.TryGetValueAsString(out var vectorStr)) { // Numbers with a comma or 'x' as a delimiter + var components = vectorStr.Split(',', 'x'); + + if (components.Length is 2 or 3) { + X = float.Parse(components[0]); + Y = float.Parse(components[1]); + if (components.Length == 3) { + Is3D = true; + Z = float.Parse(components[2]); + } + + return; + } + } else if (arg1.TryGetValueAsDreamList(out var vectorList)) { // list(X, Y) or list(X, Y, Z) + var components = vectorList.GetValues(); + + if (components.Count is 2 or 3 && components.All(v => v.Type == DreamValue.DreamValueType.Float)) { + X = components[0].UnsafeGetValueAsFloat(); + Y = components[1].UnsafeGetValueAsFloat(); + if (components.Count == 3) { + Is3D = true; + Z = components[2].UnsafeGetValueAsFloat(); + } + + return; + } + } else if (arg1.TryGetValueAsDreamObject(out var vectorCopy)) { // new /vector(vector) + Is3D = vectorCopy.Is3D; + X = vectorCopy.X; + Y = vectorCopy.Y; + Z = vectorCopy.Z; + + return; + } + + // TODO: Allow pixloc as an arg + throw new Exception($"Bad vector arguments {args.ToString()}"); + } + + protected override bool TryGetVar(string varName, out DreamValue value) { + switch (varName) { + case "type": + value = new(ObjectDefinition.TreeEntry); + return true; + case "len": + value = new(Is3D ? 3 : 2); + return true; + case "size": + value = new(Size); + return true; + case "x": + value = new(X); + return true; + case "y": + value = new(Y); + return true; + case "z": + value = new(Z); + return true; + default: + // Hide the base vars + throw new Exception($"Invalid vector variable \"{varName}\""); + } + } + + protected override void SetVar(string varName, DreamValue value) { + switch (varName) { + case "type": + throw new Exception("Cannot set type var"); + case "len": + var newLen = value.UnsafeGetValueAsFloat(); + + // Something like 2.3 actually isn't valid here; it doesn't cast to an int + if (!newLen.Equals(2f) && !newLen.Equals(3f)) { + throw new Exception($"Invalid vector len {value}"); + } + + Is3D = newLen.Equals(3f); + break; + case "size": + Size = value.UnsafeGetValueAsFloat(); + break; + case "x": + X = value.UnsafeGetValueAsFloat(); + break; + case "y": + Y = value.UnsafeGetValueAsFloat(); + break; + case "z": + Z = value.UnsafeGetValueAsFloat(); + break; + default: + // Hide the base vars + throw new Exception($"Invalid vector variable \"{varName}\""); + } + } + + // TODO: Operators, supports indexing and "most math" + // TODO: For loop support +} diff --git a/OpenDreamRuntime/Procs/DreamProcArguments.cs b/OpenDreamRuntime/Procs/DreamProcArguments.cs index 1d78d51c84..70e27366f7 100644 --- a/OpenDreamRuntime/Procs/DreamProcArguments.cs +++ b/OpenDreamRuntime/Procs/DreamProcArguments.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Text; namespace OpenDreamRuntime.Procs; @@ -29,6 +30,18 @@ public DreamValue GetArgument(int argumentPosition) { } public override string ToString() { - return $""; + var strBuilder = new StringBuilder((Count * 2 - 1) + 4); + + strBuilder.Append("("); + for (int i = 0; i < Count; i++) { + strBuilder.Append(Values[i]); + if (i != Count - 1) + strBuilder.Append(", "); + } + + strBuilder.Append(')'); + return strBuilder.ToString(); } } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 75b9e96013..5aa3761ff9 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -1467,20 +1467,33 @@ private static void JsonEncode(Utf8JsonWriter writer, DreamValue value) { writer.WriteEndArray(); } } else if (value.TryGetValueAsDreamObject(out var dreamObject)) { - if (dreamObject == null) - writer.WriteNullValue(); - else if (dreamObject is DreamObjectMatrix matrix) { // Special behaviour for /matrix values - writer.WriteStartArray(); + switch (dreamObject) { + case null: + writer.WriteNullValue(); + break; + case DreamObjectMatrix matrix: { // Special behaviour for /matrix values + writer.WriteStartArray(); - foreach (var f in DreamObjectMatrix.EnumerateMatrix(matrix)) { - writer.WriteNumberValue(f); - } + foreach (var f in DreamObjectMatrix.EnumerateMatrix(matrix)) { + writer.WriteNumberValue(f); + } - writer.WriteEndArray(); - // This doesn't have any corresponding snowflaking in CreateValueFromJsonElement() - // because BYOND actually just forgets that this was a matrix after doing json encoding. - } else - writer.WriteStringValue(value.Stringify()); + writer.WriteEndArray(); + // This doesn't have any corresponding snowflaking in CreateValueFromJsonElement() + // because BYOND actually just forgets that this was a matrix after doing json encoding. + break; + } + case DreamObjectVector vector: { // Special behaviour for /vector values + if (vector.Is3D) + writer.WriteStringValue($"vector({vector.X},{vector.Y},{vector.Z})"); + else + writer.WriteStringValue($"vector({vector.X},{vector.Y})"); + break; + } + default: + writer.WriteStringValue(value.Stringify()); + break; + } } else if (value.TryGetValueAsDreamResource(out var dreamResource)) { writer.WriteStringValue(dreamResource.ResourcePath); } else {