From d5bdf4365180761847bb585370d37d32d928e1f2 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 8 Feb 2025 11:47:51 -0600 Subject: [PATCH 01/15] Implement `/sound.offset` (#2202) Co-authored-by: ike709 --- DMCompiler/DMStandard/Types/Sound.dm | 2 +- OpenDreamClient/Audio/DreamSoundEngine.cs | 6 +++--- OpenDreamClient/Audio/IDreamSoundEngine.cs | 2 +- OpenDreamRuntime/DreamConnection.cs | 4 +++- OpenDreamShared/Network/Messages/MsgSound.cs | 3 +++ 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/DMCompiler/DMStandard/Types/Sound.dm b/DMCompiler/DMStandard/Types/Sound.dm index a5b678a011..49102d7e9a 100644 --- a/DMCompiler/DMStandard/Types/Sound.dm +++ b/DMCompiler/DMStandard/Types/Sound.dm @@ -18,7 +18,7 @@ var/environment as opendream_unimplemented var/echo as opendream_unimplemented var/len as opendream_unimplemented - var/offset as opendream_unimplemented + var/offset var/priority = 0 as opendream_unimplemented var/status = 0 as opendream_unimplemented diff --git a/OpenDreamClient/Audio/DreamSoundEngine.cs b/OpenDreamClient/Audio/DreamSoundEngine.cs index 220863ac31..9178caa914 100644 --- a/OpenDreamClient/Audio/DreamSoundEngine.cs +++ b/OpenDreamClient/Audio/DreamSoundEngine.cs @@ -30,7 +30,7 @@ public void Initialize() { _netManager.Disconnect += DisconnectedFromServer; } - public void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume) { + public void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume, float offset) { if (_audioSystem == null) _entitySystemManager.Resolve(ref _audioSystem); @@ -59,7 +59,7 @@ public void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sou } var db = 20 * MathF.Log10(volume); // convert from DM volume (0-100) to OpenAL volume (db) - var source = _audioSystem.PlayGlobal(stream, AudioParams.Default.WithVolume(db)); // TODO: Positional audio. + var source = _audioSystem.PlayGlobal(stream, AudioParams.Default.WithVolume(db).WithPlayOffset(offset)); // TODO: Positional audio. if (source == null) { _sawmill.Error($"Failed to play audio ${sound}"); return; @@ -86,7 +86,7 @@ public void StopAllChannels() { private void RxSound(MsgSound msg) { if (msg.ResourceId.HasValue) { _resourceManager.LoadResourceAsync(msg.ResourceId.Value, - sound => PlaySound(msg.Channel, msg.Format!.Value, sound, msg.Volume / 100.0f)); + sound => PlaySound(msg.Channel, msg.Format!.Value, sound, msg.Volume / 100.0f, msg.Offset)); } else { StopChannel(msg.Channel); } diff --git a/OpenDreamClient/Audio/IDreamSoundEngine.cs b/OpenDreamClient/Audio/IDreamSoundEngine.cs index 3ca5361eb9..953b5cc6d1 100644 --- a/OpenDreamClient/Audio/IDreamSoundEngine.cs +++ b/OpenDreamClient/Audio/IDreamSoundEngine.cs @@ -5,7 +5,7 @@ namespace OpenDreamClient.Audio; public interface IDreamSoundEngine { void Initialize(); - void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume); + void PlaySound(int channel, MsgSound.FormatType format, ResourceSound sound, float volume, float offset); void StopChannel(int channel); void StopAllChannels(); } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 4237c4eb9a..9183aa44b0 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -212,11 +212,13 @@ public void OutputDreamValue(DreamValue value) { if (value.TryGetValueAsDreamObject(out var outputObject)) { ushort channel = (ushort)outputObject.GetVariable("channel").GetValueAsInteger(); ushort volume = (ushort)outputObject.GetVariable("volume").GetValueAsInteger(); + float offset = outputObject.GetVariable("offset").UnsafeGetValueAsFloat(); DreamValue file = outputObject.GetVariable("file"); var msg = new MsgSound() { Channel = channel, - Volume = volume + Volume = volume, + Offset = offset }; if (!file.TryGetValueAsDreamResource(out var soundResource)) { diff --git a/OpenDreamShared/Network/Messages/MsgSound.cs b/OpenDreamShared/Network/Messages/MsgSound.cs index 4a3255759f..b2e4f05c7b 100644 --- a/OpenDreamShared/Network/Messages/MsgSound.cs +++ b/OpenDreamShared/Network/Messages/MsgSound.cs @@ -14,6 +14,7 @@ public enum FormatType : byte { public ushort Channel; public ushort Volume; + public float Offset; public int? ResourceId; public FormatType? Format; // TODO: This should probably be sent along with the sound resource instead somehow //TODO: Frequency and friends @@ -21,6 +22,7 @@ public enum FormatType : byte { public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { Channel = buffer.ReadUInt16(); Volume = buffer.ReadUInt16(); + Offset = buffer.ReadFloat(); if (buffer.ReadBoolean()) { ResourceId = buffer.ReadInt32(); @@ -31,6 +33,7 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { buffer.Write(Channel); buffer.Write(Volume); + buffer.Write(Offset); buffer.Write(ResourceId != null); if (ResourceId != null) { From c237a04ca24dae15c4456986730899fa334cac98 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 11 Feb 2025 11:10:17 -0600 Subject: [PATCH 02/15] Stub 516 `/sound` vars (#2214) --- DMCompiler/DMStandard/Types/Sound.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DMCompiler/DMStandard/Types/Sound.dm b/DMCompiler/DMStandard/Types/Sound.dm index 49102d7e9a..7fa3fb8e05 100644 --- a/DMCompiler/DMStandard/Types/Sound.dm +++ b/DMCompiler/DMStandard/Types/Sound.dm @@ -11,6 +11,9 @@ var/pan = 0 as opendream_unimplemented var/params = null as opendream_unimplemented var/falloff = 1 as opendream_unimplemented + + var/atom/atom as opendream_unimplemented + var/transform as opendream_unimplemented var/x as opendream_unimplemented var/y as opendream_unimplemented var/z as opendream_unimplemented From 1b19fed97c77206ace556ff5c05bee34fef7f767 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 11 Feb 2025 19:45:39 -0600 Subject: [PATCH 03/15] Implement `sign()` (#2207) Co-authored-by: ike709 --- .github/workflows/compiler-test.yml | 6 +++--- Content.Tests/DMProject/Tests/Builtins/sign.dm | 10 ++++++++++ DMCompiler/DMStandard/_Standard.dm | 1 + OpenDreamRuntime/Procs/Native/DreamProcNative.cs | 1 + .../Procs/Native/DreamProcNativeRoot.cs | 16 ++++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/Builtins/sign.dm diff --git a/.github/workflows/compiler-test.yml b/.github/workflows/compiler-test.yml index 9d44fc0634..face818642 100644 --- a/.github/workflows/compiler-test.yml +++ b/.github/workflows/compiler-test.yml @@ -75,7 +75,7 @@ jobs: ref: dev path: nebula - name: Compile Nebula Dev - run: main\bin\DMCompiler\DMCompiler.exe nebula\nebula.dme --suppress-unimplemented + run: main\bin\DMCompiler\DMCompiler.exe nebula\nebula.dme --suppress-unimplemented --version=516.1655 - name: Checkout /vg/station Master uses: actions/checkout@v2 with: @@ -83,7 +83,7 @@ jobs: ref: Bleeding-Edge path: vg - name: Compile /vg/station Master - run: main\bin\DMCompiler\DMCompiler.exe vg\vgstation13.dme --suppress-unimplemented + run: main\bin\DMCompiler\DMCompiler.exe vg\vgstation13.dme --suppress-unimplemented --version=516.1655 - name: Checkout CM Master uses: actions/checkout@v2 with: @@ -99,4 +99,4 @@ jobs: ref: master path: aurora - name: Compile Aurora Master - run: main\bin\DMCompiler\DMCompiler.exe aurora\aurorastation.dme --suppress-unimplemented + run: main\bin\DMCompiler\DMCompiler.exe aurora\aurorastation.dme --suppress-unimplemented --version=516.1655 diff --git a/Content.Tests/DMProject/Tests/Builtins/sign.dm b/Content.Tests/DMProject/Tests/Builtins/sign.dm new file mode 100644 index 0000000000..26786f754d --- /dev/null +++ b/Content.Tests/DMProject/Tests/Builtins/sign.dm @@ -0,0 +1,10 @@ + +/proc/RunTest() + ASSERT(sign(5.2) == 1) + ASSERT(sign(-5.2) == -1) + ASSERT(sign(0) == 0) + ASSERT(sign(null) == 0) + ASSERT(sign("") == 0) + ASSERT(sign("foo") == 0) + ASSERT(sign(list(1)) == 0) + \ No newline at end of file diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 5eabf6be5a..80a283ea9e 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -83,6 +83,7 @@ proc/roll(ndice = 1, sides) as num proc/round(A, B) as num proc/sha1(input) as text|null proc/shutdown(Addr,Natural = 0) +proc/sign(A) as num proc/sleep(Delay) proc/sorttext(T1, T2) as num proc/sorttextEx(T1, T2) as num diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 45d9737617..d2020f15f3 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -84,6 +84,7 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_round); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sha1); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_shutdown); + objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sign); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sleep); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sorttext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sorttextEx); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index e20346626c..75b9e96013 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2425,6 +2425,22 @@ public static DreamValue NativeProc_shutdown(NativeProc.Bundle bundle, DreamObje return DreamValue.Null; } + [DreamProc("sign")] + [DreamProcParameter("A", Type = DreamValueTypeFlag.Float)] + public static DreamValue NativeProc_sign(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + if (bundle.Arguments.Length != 1) throw new Exception($"expected 1 argument (found {bundle.Arguments.Length})"); + DreamValue arg = bundle.GetArgument(0, "A"); + + // Any non-num returns 0 + if (!arg.TryGetValueAsFloat(out var value)) return new DreamValue(0); + + return value switch { + 0 => new DreamValue(0), + < 0 => new DreamValue(-1), + _ => new DreamValue(1) + }; + } + [DreamProc("sleep")] [DreamProcParameter("Delay", Type = DreamValueTypeFlag.Float)] public static async Task NativeProc_sleep(AsyncNativeProc.State state) { From 93ff3af3c776dcbba6309c5142017c8a49128884 Mon Sep 17 00:00:00 2001 From: "Mikhail G." Date: Thu, 13 Feb 2025 00:34:03 +0700 Subject: [PATCH 04/15] README updates, delete unused scripts (#2210) --- DMCompiler/copy_standard.bat | 5 ----- DMCompiler/copy_standard.sh | 19 ------------------- README.md | 16 ++++++++++------ 3 files changed, 10 insertions(+), 30 deletions(-) delete mode 100644 DMCompiler/copy_standard.bat delete mode 100755 DMCompiler/copy_standard.sh diff --git a/DMCompiler/copy_standard.bat b/DMCompiler/copy_standard.bat deleted file mode 100644 index d53a132c2d..0000000000 --- a/DMCompiler/copy_standard.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -if not exist bin\Debug\net8.0\DMStandard mkdir bin\Debug\net8.0\DMStandard -xcopy DMStandard bin\Debug\net8.0\DMStandard /y /s /e -if not exist bin\Release\net8.0\DMStandard mkdir bin\Release\net8.0\DMStandard -xcopy DMStandard bin\Release\net8.0\DMStandard /y /s /e diff --git a/DMCompiler/copy_standard.sh b/DMCompiler/copy_standard.sh deleted file mode 100755 index 3532579229..0000000000 --- a/DMCompiler/copy_standard.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set -xe - -SCRIPT=$(readlink -f "$0") -SCRIPTPATH=$(dirname "$SCRIPT") - -if [ -d "$SCRIPTPATH/bin/Debug/net8.0/DMStandard" ]; then - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net8.0/DMStandard -else - mkdir -p $SCRIPTPATH/bin/Debug/net8.0/DMStandard - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Debug/net8.0/DMStandard -fi - -if [ -d "$SCRIPTPATH/bin/Release/net8.0/DMStandard" ]; then - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net8.0/DMStandard -else - mkdir -p $SCRIPTPATH/bin/Release/net8.0/DMStandard - cp -r $SCRIPTPATH/DMStandard $SCRIPTPATH/bin/Release/net8.0/DMStandard -fi diff --git a/README.md b/README.md index 147d077986..9f2154c342 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![OpenDream](.github/assets/OpenDream.png)](#) +[![OpenDream logo](.github/assets/OpenDream.png)](#) **OpenDream** is a C# project that aims to compile games made in the [DM language], and run them. @@ -13,6 +13,7 @@ A detailed description of differences with BYOND can be found [here](https://git ## Running ### Very Easy Mode + Install the OpenDream vscode extension from [the marketplace](https://marketplace.visualstudio.com/items?itemName=ss13.opendream). Open the folder containing your `.dme` file in vscode. Press the "Start debugging (F5)" button. The extension will automatically handle getting the latest OpenDream and SS14 Launcher binaries, compile and run everything for you. @@ -33,28 +34,31 @@ There's 3 main parts: Compiler, Server, and Client: ## Building -The first step to building OpenDream is initializing the submodule for the game engine, [Robust Toolbox](https://github.com/space-wizards/RobustToolbox). +The first step to building OpenDream is initializing the submodule for the game engine, [Robust Toolbox](https://github.com/space-wizards/RobustToolbox). To do this, simply run `git submodule update --init --recursive` in git bash and let it finish. -**OpenDream requires .NET 8.** You can check your version by running `dotnet --version`. It should be at least `8.0.0`. +**OpenDream requires .NET 9.** You can check your version by running `dotnet --version`. It should be at least `9.0.0`. To build, one can use a C# compiler (such as MSBuild) to compile the various projects described in the solution. To use the .NET build system, simply run `dotnet build -c Release` in the OpenDream directory. This will build all of the solutions in release mode and put the resultant binaries in `bin` ## Testing + OpenDream makes use of a unit testing framework. You can run these unit tests by running `dotnet test` in the OpenDream directory. This will run all of the RobustToolbox tests, as well as the DM language tests which can be found under `Content.Tests/DMProject/Tests/`. To add to the unit tests, simply create a `.dm` file under the Tests directory with `/proc/RunTest()` as the entry point. Optionally you can also add flags to the test, such as `// COMPILE ERROR` to mark that this test should cause a compile error. ## Requests for Comment + New features unrelated to BYOND parity go through an RFC process to solicit input from the wider community. The RFCs and more info on the RFC process can be found [here](https://github.com/OpenDreamProject/rfcs). ## Screenshots -![](./.github/assets/screenshot.png?raw=true) + +![/tg/station screenshot](./.github/assets/screenshot.png?raw=true) _[/tg/station](https://github.com/tgstation/tgstation)_ -![](./.github/assets/screenshot2.png?raw=true) +![Paradise screenshot](./.github/assets/screenshot2.png?raw=true) _Version of [Paradise with a 64-bit rustg DLL](https://github.com/ike709/Paradise/tree/rustg_64)_ -![](./.github/assets/screenshot3.png?raw=true) +![Goonstation screenshot](./.github/assets/screenshot3.png?raw=true) _[Goonstation](https://github.com/goonstation/goonstation)_ [DM Language]: http://secure.byond.com/ From 23cd63ad38c9955e777871d743786b1db1b9c730 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 15 Feb 2025 16:24:52 -0600 Subject: [PATCH 05/15] Bumps RT (#2216) Co-authored-by: ike709 --- OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs | 4 ++-- RobustToolbox | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs b/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs index 7ba057e5a2..a5f7395e5a 100644 --- a/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs +++ b/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs @@ -111,7 +111,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { _inputManager.ViewportKeyEvent(this, args); } - protected override void Draw(DrawingHandleScreen handle) { + protected override void Draw(IRenderHandle handle) { EnsureViewportCreated(); DebugTools.AssertNotNull(_viewport); @@ -133,7 +133,7 @@ protected override void Draw(DrawingHandleScreen handle) { var drawBox = GetDrawBox(); var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition); _viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal); - handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); + handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); _viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal); } diff --git a/RobustToolbox b/RobustToolbox index e4190f4f29..fea592e1d5 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit e4190f4f2900634e332208a77cd6df9cef75c29a +Subproject commit fea592e1d53b043bda73a66248576caa6e3f65dc From 145856a1f04f83298165d0e59d8e9a7e29854390 Mon Sep 17 00:00:00 2001 From: Richard Van Tassel Date: Tue, 18 Feb 2025 00:54:57 -0500 Subject: [PATCH 06/15] Adds html parser check for void elements (#2217) --- OpenDreamClient/Interface/Html/HtmlParser.cs | 36 +++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/OpenDreamClient/Interface/Html/HtmlParser.cs b/OpenDreamClient/Interface/Html/HtmlParser.cs index ef434e83c2..6641c1294f 100644 --- a/OpenDreamClient/Interface/Html/HtmlParser.cs +++ b/OpenDreamClient/Interface/Html/HtmlParser.cs @@ -84,7 +84,8 @@ void PushCurrentText() { } else { tags.Push(tagType); - appendTo.PushTag(new MarkupNode(tagType, null, ParseAttributes(attributes)), selfClosing: attributes[^1] == "/"); + bool isSelfClosing = IsSelfClosing(tagType, attributes); + appendTo.PushTag(new MarkupNode(tagType, null, ParseAttributes(attributes)), selfClosing: isSelfClosing); } break; @@ -139,6 +140,39 @@ void PushCurrentText() { appendTo.Pop(); } + /** + * + * Returns if a tag is written in old self-closing form, or if the tag + * represents a void element, which must have no children + * + */ + private static bool IsSelfClosing(string tagType, string[] attributes) { + if (attributes[^1] == "/") { + return true; + } + + switch (tagType) { + case "area": + case "base": + case "br": + case "col": + case "embed": + case "hr": + case "img": + case "input": + case "link": + case "meta": + case "param": + case "source": + case "track": + case "wbr": + return true; + + default: + return false; + } + } + private static Dictionary ParseAttributes(string[] attributes) { Dictionary parsedAttributes = new(); From b1134c3e4e7023b2432ff50f3b1db9d82fe7d471 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 18 Feb 2025 00:10:00 -0600 Subject: [PATCH 07/15] Implement `astype()` (#2211) Co-authored-by: ike709 Co-authored-by: wixoa --- .../DMProject/Tests/Builtins/astype.dm | 18 ++++++++ DMCompiler/Bytecode/DreamProcOpcode.cs | 3 +- .../Compiler/DM/AST/DMAST.ExpressionBinary.cs | 1 + .../Compiler/DM/AST/DMAST.ExpressionUnary.cs | 1 + DMCompiler/Compiler/DM/DMParser.cs | 10 +++++ DMCompiler/DM/Builders/DMExpressionBuilder.cs | 35 +++++++++------ DMCompiler/DM/DMProc.cs | 4 ++ DMCompiler/DM/Expressions/Builtins.cs | 24 ++++++++++ OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 44 +++++++++++++------ OpenDreamRuntime/Procs/DMProc.cs | 1 + 10 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/Builtins/astype.dm diff --git a/Content.Tests/DMProject/Tests/Builtins/astype.dm b/Content.Tests/DMProject/Tests/Builtins/astype.dm new file mode 100644 index 0000000000..0a41b553ff --- /dev/null +++ b/Content.Tests/DMProject/Tests/Builtins/astype.dm @@ -0,0 +1,18 @@ + +/datum/foo +/datum/foo/bar + +/proc/test_null() + var/datum/D = new + var/datum/foo/bar/B = astype(D) + return isnull(B) + +/proc/test_type() + var/datum/foo/bar/B = new + var/datum/D = astype(B) + var/datum/foo/F = astype(D) + return F.type + +/proc/RunTest() + ASSERT(test_null()) + ASSERT(test_type() == /datum/foo/bar) diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index d583165388..00852ad0bb 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -140,7 +140,8 @@ public enum DreamProcOpcode : byte { Ftp = 0x46, [OpcodeMetadata(-1)] Initial = 0x47, - //0x48 + [OpcodeMetadata(-1)] + AsType = 0x48, [OpcodeMetadata(-1)] IsType = 0x49, [OpcodeMetadata(-2)] diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionBinary.cs b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionBinary.cs index aa024889f6..00d97cbbd1 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionBinary.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionBinary.cs @@ -48,6 +48,7 @@ public sealed class DMASTPower(Location location, DMASTExpression a, DMASTExpres public sealed class DMASTAdd(Location location, DMASTExpression a, DMASTExpression b) : DMASTBinary(location, a, b); public sealed class DMASTSubtract(Location location, DMASTExpression a, DMASTExpression b) : DMASTBinary(location, a, b); public sealed class DMASTArctan2(Location location, DMASTExpression xExpression, DMASTExpression yExpression) : DMASTBinary(location, xExpression, yExpression); +public sealed class DMASTAsType(Location location, DMASTExpression value, DMASTExpression type) : DMASTBinary(location, value, type); public sealed class DMASTIsType(Location location, DMASTExpression value, DMASTExpression type) : DMASTBinary(location, value, type); public sealed class DMASTGetStep(Location location, DMASTExpression refValue, DMASTExpression dir) : DMASTBinary(location, refValue, dir); public sealed class DMASTGetDir(Location location, DMASTExpression loc1, DMASTExpression loc2) : DMASTBinary(location, loc1, loc2); diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionUnary.cs b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionUnary.cs index eee1db30da..aee10c3427 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionUnary.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionUnary.cs @@ -31,6 +31,7 @@ public sealed class DMASTNameof(Location location, DMASTExpression expression) : public sealed class DMASTIsSaved(Location location, DMASTExpression expression) : DMASTUnary(location, expression); public sealed class DMASTIsNull(Location location, DMASTExpression value) : DMASTUnary(location, value); public sealed class DMASTLength(Location location, DMASTExpression value) : DMASTUnary(location, value); +public sealed class DMASTImplicitAsType(Location location, DMASTExpression value) : DMASTUnary(location, value); public sealed class DMASTImplicitIsType(Location location, DMASTExpression value) : DMASTUnary(location, value); /// diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index c0ecacbba4..ec01b396ae 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2641,6 +2641,16 @@ private void BracketWhitespace() { ? new DMASTLog(callLoc, callParameters[0].Value, null) : new DMASTLog(callLoc, callParameters[1].Value, callParameters[0].Value); } + case "astype": { + if (callParameters.Length != 1 && callParameters.Length != 2) { + Emit(WarningCode.InvalidArgumentCount, callLoc, "astype() requires 1 or 2 arguments"); + return new DMASTInvalidExpression(callLoc); + } + + return callParameters.Length == 1 + ? new DMASTImplicitAsType(callLoc, callParameters[0].Value) + : new DMASTAsType(callLoc, callParameters[0].Value, callParameters[1].Value); + } case "istype": { if (callParameters.Length != 1 && callParameters.Length != 2) { Emit(WarningCode.InvalidArgumentCount, callLoc, "istype() requires 1 or 2 arguments"); diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs index 9e25dc50fe..9ef101ba4a 100644 --- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs @@ -74,6 +74,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe case DMASTNotEqual notEqual: result = BuildNotEqual(notEqual, inferredPath); break; case DMASTDereference deref: result = BuildDereference(deref, inferredPath); break; case DMASTLocate locate: result = BuildLocate(locate, inferredPath); break; + case DMASTImplicitAsType implicitAsType: result = BuildImplicitAsType(implicitAsType, inferredPath); break; case DMASTImplicitIsType implicitIsType: result = BuildImplicitIsType(implicitIsType, inferredPath); break; case DMASTList list: result = BuildList(list, inferredPath); break; case DMASTDimensionalList dimensionalList: result = BuildDimensionalList(dimensionalList, inferredPath); break; @@ -338,25 +339,21 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe BuildExpression(locateCoordinates.Y, inferredPath), BuildExpression(locateCoordinates.Z, inferredPath)); break; + case DMASTAsType asType: { + var lhs = BuildExpression(asType.LHS, inferredPath); + var rhs = BuildExpression(asType.RHS, lhs.Path); + + result = new AsType(asType.Location, lhs, rhs); + break; + } case DMASTIsSaved isSaved: result = new IsSaved(isSaved.Location, BuildExpression(isSaved.Value, inferredPath)); break; case DMASTIsType isType: { - if (isType.RHS is DMASTIdentifier { Identifier: "__IMPLIED_TYPE__" }) { - var expr = BuildExpression(isType.LHS, inferredPath); - if (expr.Path is null) { - result = BadExpression(WarningCode.BadExpression, isType.Location, - "A type could not be inferred!"); - break; - } + var lhs = BuildExpression(isType.LHS, inferredPath); + var rhs = BuildExpression(isType.RHS, lhs.Path); - result = new IsTypeInferred(isType.Location, expr, expr.Path.Value); - break; - } - - result = new IsType(isType.Location, - BuildExpression(isType.LHS, inferredPath), - BuildExpression(isType.RHS, inferredPath)); + result = new IsType(isType.Location, lhs, rhs); break; } @@ -1072,6 +1069,16 @@ private DMExpression BuildLocate(DMASTLocate locate, DreamPath? inferredPath) { return new Locate(locate.Location, pathExpr, container); } + private DMExpression BuildImplicitAsType(DMASTImplicitAsType asType, DreamPath? inferredPath) { + var expr = BuildExpression(asType.Value, inferredPath); + + if (inferredPath is null) { + return BadExpression(WarningCode.BadExpression, asType.Location, "Could not infer a type"); + } + + return new AsTypeInferred(asType.Location, expr, inferredPath.Value); + } + private DMExpression BuildImplicitIsType(DMASTImplicitIsType isType, DreamPath? inferredPath) { var expr = BuildExpression(isType.Value, inferredPath); diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 4dfa008168..670442f16b 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -969,6 +969,10 @@ public void IsSaved() { WriteOpcode(DreamProcOpcode.IsSaved); } + public void AsType() { + WriteOpcode(DreamProcOpcode.AsType); + } + public void IsType() { WriteOpcode(DreamProcOpcode.IsType); } diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index e11b037cf7..6092ce591d 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -291,6 +291,30 @@ public override void EmitPushValue(ExpressionContext ctx) { } } +// astype(x, y) +internal sealed class AsType(Location location, DMExpression expr, DMExpression path) : DMExpression(location) { + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + path.EmitPushValue(ctx); + ctx.Proc.AsType(); + } +} + +// astype(x) +internal sealed class AsTypeInferred(Location location, DMExpression expr, DreamPath path) : DMExpression(location) { + public override void EmitPushValue(ExpressionContext ctx) { + if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) { + ctx.Compiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {path} does not exist"); + + return; + } + + expr.EmitPushValue(ctx); + ctx.Proc.PushType(typeId); + ctx.Proc.AsType(); + } +} + // istype(x, y) internal sealed class IsType(Location location, DMExpression expr, DMExpression path) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 5c6fbff735..777574d9dc 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -1403,39 +1403,57 @@ public static ProcStatus IsInRange(DMProcState state) { return ProcStatus.Continue; } + public static ProcStatus AsType(DMProcState state) { + DreamValue typeValue = state.Pop(); + DreamValue value = state.Pop(); + + state.Push(TypecheckHelper(typeValue, value, true)); + + return ProcStatus.Continue; + } + public static ProcStatus IsType(DMProcState state) { DreamValue typeValue = state.Pop(); DreamValue value = state.Pop(); + + state.Push(TypecheckHelper(typeValue, value, false)); + + return ProcStatus.Continue; + } + + private static DreamValue TypecheckHelper(DreamValue typeValue, DreamValue value, bool doCast) { + // astype() returns null, istype() returns false + DreamValue nullOrFalse = doCast ? DreamValue.Null : DreamValue.False; TreeEntry? type; if (typeValue.TryGetValueAsDreamObject(out var typeObject)) { if (typeObject == null) { - state.Push(DreamValue.False); - return ProcStatus.Continue; + return nullOrFalse; } type = typeObject.ObjectDefinition.TreeEntry; } else if (typeValue.TryGetValueAsAppearance(out _)) { // /image matches an appearance - state.Push(value.TryGetValueAsDreamObject(out _) - ? DreamValue.True - : DreamValue.False); + if (value.TryGetValueAsDreamObject(out var imageObject)) { + return doCast ? new DreamValue(imageObject) : DreamValue.True; + } - return ProcStatus.Continue; + return nullOrFalse; } else if (!typeValue.TryGetValueAsType(out type)) { - state.Push(DreamValue.False); - - return ProcStatus.Continue; + return nullOrFalse; } if (value.TryGetValueAsDreamObject(out var dreamObject) && dreamObject != null) { - state.Push(new DreamValue(dreamObject.IsSubtypeOf(type) ? 1 : 0)); - } else { - state.Push(DreamValue.False); + if (dreamObject.IsSubtypeOf(type)) { + return doCast ? new DreamValue(dreamObject) : DreamValue.True; + } + + return nullOrFalse; } - return ProcStatus.Continue; + return nullOrFalse; } + #endregion Comparisons #region Flow diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 9b8a536854..f0913cd35b 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -251,6 +251,7 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.Link, DMOpcodeHandlers.Link}, {DreamProcOpcode.Ftp, DMOpcodeHandlers.Ftp}, {DreamProcOpcode.Initial, DMOpcodeHandlers.Initial}, + {DreamProcOpcode.AsType, DMOpcodeHandlers.AsType}, {DreamProcOpcode.IsType, DMOpcodeHandlers.IsType}, {DreamProcOpcode.LocateCoord, DMOpcodeHandlers.LocateCoord}, {DreamProcOpcode.Locate, DMOpcodeHandlers.Locate}, From 5175290bdec4c5a92834c1ff043fe65d9c911b82 Mon Sep 17 00:00:00 2001 From: ike709 Date: Wed, 19 Feb 2025 12:34:36 -0600 Subject: [PATCH 08/15] Implement `list.Splice()` (#2201) Co-authored-by: ike709 --- .../DMProject/Tests/List/ListSplice.dm | 10 +++++++ DMCompiler/DMStandard/Types/List.dm | 4 +-- .../Procs/Native/DreamProcNative.cs | 1 + .../Procs/Native/DreamProcNativeList.cs | 30 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/List/ListSplice.dm diff --git a/Content.Tests/DMProject/Tests/List/ListSplice.dm b/Content.Tests/DMProject/Tests/List/ListSplice.dm new file mode 100644 index 0000000000..9839d62ad9 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/ListSplice.dm @@ -0,0 +1,10 @@ + +/proc/RunTest() + var/list/L = list("foo", "bar", "test", "value") + L.Splice(2, 4, "lorem", "ipsum", "word", "another word") + ASSERT(L ~= list("foo","lorem","ipsum","word","another word","value")) + + // Again with list() as the arg + var/list/L2 = list("foo", "bar", "test", "value") + L2.Splice(2, 4, list("lorem", "ipsum", "word", "another word")) + ASSERT(L2 ~= list("foo","lorem","ipsum","word","another word","value")) diff --git a/DMCompiler/DMStandard/Types/List.dm b/DMCompiler/DMStandard/Types/List.dm index 013c1e56c3..e8d25d096d 100644 --- a/DMCompiler/DMStandard/Types/List.dm +++ b/DMCompiler/DMStandard/Types/List.dm @@ -13,6 +13,4 @@ proc/Remove(Item1) proc/RemoveAll(Item1) proc/Swap(Index1, Index2) - - proc/Splice(Start=1,End=0, ...) - set opendream_unimplemented = TRUE + proc/Splice(Start = 1 as num, End = 0 as num, Item1, ...) as null diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index d2020f15f3..7c28ec7f0e 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -128,6 +128,7 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Join); objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Remove); objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_RemoveAll); + objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Splice); objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Swap); objectTree.SetNativeProc(objectTree.Matrix, DreamProcNativeMatrix.NativeProc_Add); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs index bb6c98276a..48152e7dd2 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs @@ -163,6 +163,36 @@ private static int ListRemove(DreamList list, ReadOnlySpan args) { return itemRemoved; } + [DreamProc("Splice")] + [DreamProcParameter("Start", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] + [DreamProcParameter("End", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] + [DreamProcParameter("Item1")] + public static DreamValue NativeProc_Splice(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + int startIndex = bundle.GetArgument(0, "Start").MustGetValueAsInteger(); //1-indexed + int end = bundle.GetArgument(1, "End").MustGetValueAsInteger(); //1-indexed + DreamList list = (DreamList)src!; + + list.Cut(startIndex, end); + + if (startIndex <= 0) startIndex = list.GetLength() + 1; + if (bundle.Arguments.Length < 3) return DreamValue.Null; + + // i = 2 is Item1 + for (var i = 2; i < bundle.Arguments.Length; i++) { + var item = bundle.Arguments[i]; + + if (item.TryGetValueAsDreamList(out var valueList)) { + foreach (DreamValue value in valueList.GetValues()) { + list.Insert(startIndex++, value); + } + } else { + list.Insert(startIndex++, item); + } + } + + return DreamValue.Null; + } + [DreamProc("Swap")] [DreamProcParameter("Index1", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("Index2", Type = DreamValueTypeFlag.Float)] From 9b76a5474e96120d123f562e871d969bc4aeb1e0 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Thu, 20 Feb 2025 19:06:15 +0000 Subject: [PATCH 09/15] Fix autogenerated component state stuff (#2224) --- OpenDreamShared/OpenDreamShared.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenDreamShared/OpenDreamShared.csproj b/OpenDreamShared/OpenDreamShared.csproj index 2b9f66a9b7..c69bb75e29 100644 --- a/OpenDreamShared/OpenDreamShared.csproj +++ b/OpenDreamShared/OpenDreamShared.csproj @@ -18,4 +18,5 @@ + From 6ffc5197e365cbe4d35d14704edb5f9f2fe4bd30 Mon Sep 17 00:00:00 2001 From: Richard Van Tassel Date: Thu, 20 Feb 2025 14:16:12 -0500 Subject: [PATCH 10/15] Additional label optimization (#2218) --- DMCompiler/Optimizer/BytecodeOptimizer.cs | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index dc1ffb7c7f..cfed5be887 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -12,8 +12,17 @@ internal void Optimize(List input) { RemoveUnreferencedLabels(input); JoinAndForwardLabels(input); RemoveUnreferencedLabels(input); + RemoveImmediateJumps(input); + RemoveUnreferencedLabels(input); _peepholeOptimizer.RunPeephole(input); + + // Run label optimizations again due to possibly removed jumps in peephole optimizers + RemoveUnreferencedLabels(input); + JoinAndForwardLabels(input); + RemoveUnreferencedLabels(input); + RemoveImmediateJumps(input); + RemoveUnreferencedLabels(input); } private void RemoveUnreferencedLabels(List input) { @@ -40,6 +49,22 @@ private void RemoveUnreferencedLabels(List input) { } } + /** + * Removes jumps for which the next element is the jump's destination + */ + private void RemoveImmediateJumps(List input) { + for (int i = input.Count - 2; i >= 0; i--) { + if (input[i] is AnnotatedBytecodeInstruction { Opcode: Bytecode.DreamProcOpcode.Jump } instruction) { + if (input[i + 1] is AnnotatedBytecodeLabel followingLabel) { + AnnotatedBytecodeLabel jumpLabelName = instruction.GetArg(0); + if (jumpLabelName.LabelName == followingLabel.LabelName) { + input.RemoveAt(i); + } + } + } + } + } + private void JoinAndForwardLabels(List input) { Dictionary labelAliases = new(); for (int i = 0; i < input.Count; i++) { From fa88b9d7d1ff1e1780d6c386b921bb45beeb6f68 Mon Sep 17 00:00:00 2001 From: wixoa Date: Thu, 20 Feb 2025 16:34:05 -0500 Subject: [PATCH 11/15] Initial vector implementation (#2221) --- DMCompiler/DM/DMObjectTree.cs | 1 + DMCompiler/DMStandard/Types/Vector.dm | 27 ++++ DMCompiler/DMStandard/_Standard.dm | 1 + OpenDreamRuntime/Objects/DreamObjectTree.cs | 4 + .../Objects/Types/DreamObjectVector.cs | 147 ++++++++++++++++++ OpenDreamRuntime/Procs/DreamProcArguments.cs | 15 +- .../Procs/Native/DreamProcNativeRoot.cs | 37 +++-- 7 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 DMCompiler/DMStandard/Types/Vector.dm create mode 100644 OpenDreamRuntime/Objects/Types/DreamObjectVector.cs 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 { From 30a332d6136c8ccfc854058135cdefb2185c7714 Mon Sep 17 00:00:00 2001 From: Richard Van Tassel Date: Fri, 21 Feb 2025 15:08:42 -0500 Subject: [PATCH 12/15] Completely ignore/don't require closing tags of void elements (#2225) --- OpenDreamClient/Interface/Html/HtmlParser.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OpenDreamClient/Interface/Html/HtmlParser.cs b/OpenDreamClient/Interface/Html/HtmlParser.cs index 6641c1294f..98f11e6a57 100644 --- a/OpenDreamClient/Interface/Html/HtmlParser.cs +++ b/OpenDreamClient/Interface/Html/HtmlParser.cs @@ -70,8 +70,13 @@ void PushCurrentText() { string tagType = attributes[0].ToLowerInvariant(); currentText.Clear(); + bool isSelfClosing = IsSelfClosing(tagType, attributes); if (closingTag) { - if (tags.Count == 0) { + if (isSelfClosing) { + // ignore closing tags of void elements since they don't + // do anything anyway. Should probably warn. + return; + } else if (tags.Count == 0) { Sawmill.Error("Unexpected closing tag"); return; } else if (tags.Peek() != tagType) { @@ -82,9 +87,9 @@ void PushCurrentText() { appendTo.Pop(); tags.Pop(); } else { - tags.Push(tagType); - - bool isSelfClosing = IsSelfClosing(tagType, attributes); + if (!isSelfClosing) { + tags.Push(tagType); + } appendTo.PushTag(new MarkupNode(tagType, null, ParseAttributes(attributes)), selfClosing: isSelfClosing); } From a7661f6b6fdd8eaa7561e42e2e99e73182c78139 Mon Sep 17 00:00:00 2001 From: ike709 Date: Fri, 21 Feb 2025 14:10:56 -0600 Subject: [PATCH 13/15] Stub 516 `/atom` vars (#2215) Co-authored-by: ike709 --- DMCompiler/DMStandard/Types/Atoms/_Atom.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm index c057353928..62c079e2a3 100644 --- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm +++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm @@ -23,6 +23,9 @@ var/pixel_y = 0 var/pixel_z = 0 var/pixel_w = 0 + + var/icon_w = 0 as opendream_unimplemented + var/icon_z = 0 as opendream_unimplemented var/icon = null var/icon_state = "" From cab4e056b2a79baa1958a8ff4fdfd426ce5d1f91 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Sat, 22 Feb 2025 02:31:47 +0000 Subject: [PATCH 14/15] Implement focus and macro selection for windows (#2192) --- .../Interface/Controls/ControlWindow.cs | 4 +++ .../Interface/DebugWindows/MacrosWindow.cs | 4 +-- .../Interface/DreamInterfaceManager.cs | 25 +++++++++++++++++++ OpenDreamClient/Interface/InterfaceMacro.cs | 4 +-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/OpenDreamClient/Interface/Controls/ControlWindow.cs b/OpenDreamClient/Interface/Controls/ControlWindow.cs index ea29a7358d..e76c4907c7 100644 --- a/OpenDreamClient/Interface/Controls/ControlWindow.cs +++ b/OpenDreamClient/Interface/Controls/ControlWindow.cs @@ -33,6 +33,10 @@ public ControlWindow(WindowDescriptor windowDescriptor) : base(windowDescriptor, IoCManager.InjectDependencies(this); } + public IClydeWindow? GetClydeWindow() { + return _myWindow.osWindow is null ? _myWindow.clydeWindow : _myWindow.osWindow.ClydeWindow; + } + protected override void UpdateElementDescriptor() { // Don't call base.UpdateElementDescriptor(); diff --git a/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs b/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs index c320f978b6..51afec74f9 100644 --- a/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs +++ b/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs @@ -43,8 +43,8 @@ private GridContainer CreateMacroTable(InterfaceMacroSet macroSet) { foreach (var macro in macroSet.Macros.Values) { var idText = macro.Id; - if (macro.ElementDescriptor.Name.Value != idText.Value) - idText.Value += $" ({macro.ElementDescriptor.Name.AsRaw()})"; + if (macro.ElementDescriptor.Id.Value != idText.Value) + idText.Value += $" ({macro.ElementDescriptor.Id.AsRaw()})"; var idLabel = new Label { Text = idText.AsRaw() }; var commandLabel = new Label { Text = macro.Command }; diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index 1e97447d53..708b51ced5 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -21,6 +21,7 @@ using Robust.Shared.Utility; using SixLabors.ImageSharp; using System.Linq; +using Robust.Shared.Map; namespace OpenDreamClient.Interface; @@ -55,6 +56,7 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager { public Dictionary Windows { get; } = new(); public Dictionary Menus { get; } = new(); public Dictionary MacroSets { get; } = new(); + private Dictionary ClydeWindowIdToControl { get; } = new(); public ViewRange View { get => _view; @@ -124,6 +126,7 @@ public void Initialize() { _netManager.RegisterNetMessage(RxLoadInterface); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxUpdateClientInfo); + _clyde.OnWindowFocused += OnWindowFocused; } private void RxUpdateStatPanels(MsgUpdateStatPanels message) { @@ -914,6 +917,24 @@ private void LoadInterface(InterfaceDescriptor descriptor) { LayoutContainer.SetAnchorBottom(DefaultWindow.UIElement, 1); _uiManager.StateRoot.AddChild(DefaultWindow.UIElement); + + if (DefaultWindow.GetClydeWindow() is { } clydeWindow) { + ClydeWindowIdToControl.Add(clydeWindow.Id, DefaultWindow); + } + } + + private void OnWindowFocused(WindowFocusedEventArgs args) { + if(ClydeWindowIdToControl.TryGetValue(args.Window.Id, out var controlWindow)){ + _sawmill.Debug($"window id {controlWindow.Id} was {(args.Focused ? "focused" : "defocused")}"); + WindowDescriptor descriptor = (WindowDescriptor) controlWindow.ElementDescriptor; + descriptor.Focus = new DMFPropertyBool(args.Focused); + if(args.Focused && MacroSets.TryGetValue(descriptor.Macro.AsRaw(), out var windowMacroSet)){ + _sawmill.Debug($"Activating macroset {descriptor.Macro}"); + windowMacroSet.SetActive(); + } + } + else + _sawmill.Debug($"window id was not found (probably a modal) but was {(args.Focused ? "focused" : "defocused")}"); } private void LoadDescriptor(ElementDescriptor descriptor) { @@ -936,6 +957,10 @@ private void LoadDescriptor(ElementDescriptor descriptor) { DefaultWindow = window; } + if (window.GetClydeWindow() is { } clydeWindow) { + ClydeWindowIdToControl.Add(clydeWindow.Id, window); + } + break; } } diff --git a/OpenDreamClient/Interface/InterfaceMacro.cs b/OpenDreamClient/Interface/InterfaceMacro.cs index be94c0a168..76accd076a 100644 --- a/OpenDreamClient/Interface/InterfaceMacro.cs +++ b/OpenDreamClient/Interface/InterfaceMacro.cs @@ -25,7 +25,7 @@ public InterfaceMacroSet(MacroSetDescriptor descriptor, IEntitySystemManager ent _entitySystemManager = entitySystemManager; _uiManager = uiManager; - _inputContextName = $"{InputContextPrefix}{ElementDescriptor.Name}"; + _inputContextName = $"{InputContextPrefix}{ElementDescriptor.Id}"; if (inputManager.Contexts.TryGetContext(_inputContextName, out var existingContext)) { _inputContext = existingContext; } else { @@ -45,7 +45,7 @@ public override void AddChild(ElementDescriptor descriptor) { } public void SetActive() { - _inputManager.Contexts.SetActiveContext($"{InputContextPrefix}{ElementDescriptor.Name}"); + _inputManager.Contexts.SetActiveContext($"{InputContextPrefix}{ElementDescriptor.Id}"); } } From 7d528cebb7270c91472ed272f4c38de871450304 Mon Sep 17 00:00:00 2001 From: Richard Van Tassel Date: Fri, 21 Feb 2025 22:08:47 -0500 Subject: [PATCH 15/15] Updates atom alpha when atom color is set (#2222) --- OpenDreamShared/Dream/MutableAppearance.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenDreamShared/Dream/MutableAppearance.cs b/OpenDreamShared/Dream/MutableAppearance.cs index 76c853cb03..8d011e761c 100644 --- a/OpenDreamShared/Dream/MutableAppearance.cs +++ b/OpenDreamShared/Dream/MutableAppearance.cs @@ -308,6 +308,8 @@ public void SetColor(string color) { if (!ColorHelpers.TryParseColor(color, out Color)) { Color = Color.White; } + + Alpha = (byte)(Color.A * 255); } ///