diff --git a/.github/workflows/compiler-test.yml b/.github/workflows/compiler-test.yml index 39d7e38ea4..329007ebf9 100644 --- a/.github/workflows/compiler-test.yml +++ b/.github/workflows/compiler-test.yml @@ -33,14 +33,14 @@ jobs: - name: Build run: dotnet build main/OpenDream.sln --configuration Release --no-restore /m - name: Compile TestGame - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe main\TestGame\environment.dme + run: main\bin\DMCompiler\DMCompiler.exe main\TestGame\environment.dme - name: Checkout Modified /tg/station uses: actions/checkout@v2 with: repository: wixoaGit/tgstation path: tg - name: Compile Modified /tg/station - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe tg\tgstation.dme + run: main\bin\DMCompiler\DMCompiler.exe tg\tgstation.dme - name: Checkout 64-bit Paradise uses: actions/checkout@v2 with: @@ -48,4 +48,4 @@ jobs: ref: rustg_64 path: para - name: Compile 64-bit Paradise - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe para\paradise.dme + run: main\bin\DMCompiler\DMCompiler.exe para\paradise.dme diff --git a/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm b/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm deleted file mode 100644 index 073b9306dc..0000000000 --- a/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm +++ /dev/null @@ -1,17 +0,0 @@ - -// TODO: This test needs further cleanup/validation but I cba and we need more issaved() tests - -//# issue 684 - -/obj - var/V - var/const/C - - proc/log_vars() - for(var/vname in vars) - world.log << (issaved(vars[vname])) - -/proc/RunTest() - var/obj/o = new - o.log_vars() - ASSERT(FALSE) // To ensure this test fails until it's been revisited (in case we add CI to check broken tests) diff --git a/Content.Tests/DMProject/Tests/Expression/const_eval.dm b/Content.Tests/DMProject/Tests/Expression/const_eval.dm new file mode 100644 index 0000000000..abe38dbb81 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Expression/const_eval.dm @@ -0,0 +1,56 @@ +/datum + var/composite_expr = 4 * sin(3 + cos(2)) + var/sintest = sin(45) + var/costest = cos(123) + var/tantest = tan(123) + var/sqrttest = sqrt(123) + var/arcsintest = arcsin(sin(45)) + var/arccostest = arccos(cos(123)) + var/arctantest = arctan(tan(69)) + var/log_test = log(10) + var/log10_test = log(10, 100) + var/arctan2_test = arctan(1, 3) + var/abs_test = abs(-213) + +#define EPSILON 4e-6 +#define APX_EQUAL(a, b) ASSERT(abs(a - b) < EPSILON) + +/proc/RunTest() + var/break_const_eval = null + var/datum/d = new /datum + + APX_EQUAL(initial(d.composite_expr), 4 * sin(3 + cos(break_const_eval || 2))) + APX_EQUAL(d.composite_expr, 4 * sin(3 + cos(break_const_eval || 2))) + + APX_EQUAL(d.sintest, sin(break_const_eval || 45)) + APX_EQUAL(d.sintest, 0.707106769084930419921875) + + APX_EQUAL(d.costest, cos(break_const_eval || 123)) + APX_EQUAL(d.costest, -0.544639050960540771484375) + + APX_EQUAL(d.tantest, tan(break_const_eval || 123)) + APX_EQUAL(d.tantest, -1.539865016937255859375) + + APX_EQUAL(d.sqrttest, sqrt(break_const_eval || 123)) + APX_EQUAL(d.sqrttest, 11.0905361175537109375) + + APX_EQUAL(d.arcsintest, arcsin(sin(break_const_eval || 45))) + APX_EQUAL(d.arcsintest, 45) + + APX_EQUAL(d.arccostest, arccos(cos(break_const_eval || 123))) + APX_EQUAL(d.arccostest, 123) + + APX_EQUAL(d.arctantest, arctan(tan(break_const_eval || 69))) + APX_EQUAL(d.arctantest, 69) + + APX_EQUAL(d.log_test, log(break_const_eval || 10)) + APX_EQUAL(d.log_test, 2.302585124969482421875) + + APX_EQUAL(d.log10_test, log(break_const_eval || 10, 100)) + APX_EQUAL(d.log10_test, 2) + + APX_EQUAL(d.arctan2_test, arctan(break_const_eval || 1, 3)) + APX_EQUAL(d.arctan2_test, 71.5650482177734375) + + APX_EQUAL(d.abs_test, abs(break_const_eval || -213)) + \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Math/Addition.dm b/Content.Tests/DMProject/Tests/Math/Addition.dm deleted file mode 100644 index 9cd65b11c8..0000000000 --- a/Content.Tests/DMProject/Tests/Math/Addition.dm +++ /dev/null @@ -1,3 +0,0 @@ -/proc/RunTest() - var/add = 5 + 2 - ASSERT(add == 7) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Addition.dm b/Content.Tests/DMProject/Tests/Operators/Addition.dm new file mode 100644 index 0000000000..fe1fd03560 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Addition.dm @@ -0,0 +1,131 @@ +#include "Shared/operator_testing.dm" + +/proc/add(a, b) + return a + b + +/proc/RunTest() + var/list/expected = list( + 20, + "Error", + 10, + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "ABCABC", + "ABC", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "ABC/datum/proc", + "ABC/datum/verb", + 10, + "ABC", + null, + 'Shared/file.txt', + list("A"), + operator_test_object, + /datum, + /datum/proc/foo, + /datum/verb/bar, + /datum/proc, + /datum/verb, + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + list("A", 10), + list("A", "ABC"), + list("A", null), + list("A", 'Shared/file.txt'), + list("A", "A"), + list("A", operator_test_object), + list("A", /datum), + list("A", /datum/proc/foo), + list("A", /datum/verb/bar), + list("A", /datum/proc), + list("A", /datum/verb), + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/procABC", + "/datum/proc", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/proc/datum/proc", + "/datum/proc/datum/verb", + "Error", + "/datum/verbABC", + "/datum/verb", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/verb/datum/proc", + "/datum/verb/datum/verb" + ) + + test_operator(/proc/add, expected) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Shared/file.txt b/Content.Tests/DMProject/Tests/Operators/Shared/file.txt new file mode 100644 index 0000000000..ff722d7d2f --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Shared/file.txt @@ -0,0 +1 @@ +Used in operator_testing.dm \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm b/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm new file mode 100644 index 0000000000..4cc869d16c --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm @@ -0,0 +1,37 @@ +// IGNORE + +// A helper method for testing operators against many kinds of values + +/var/datum/operator_test_object = new /datum() + +/datum/proc/foo() +/datum/verb/bar() + +/proc/test_operator(var/operator_proc, var/list/expected) + var/list/values = list( + 10, + "ABC", + null, + 'file.txt', + list("A"), + operator_test_object, + /datum, + /datum/proc/foo, + /datum/verb/bar, + /datum/proc, + /datum/verb + ) + + var/i = 1 + for (var/a in values) + for (var/b in values) + var/expected_result = expected[i++] + var/result + + try + result = call(operator_proc)(a, b) + catch + result = "Error" + + if (result ~! expected_result) + CRASH("Expected [json_encode(expected_result)] for [json_encode(a)] and [json_encode(b)], instead got [json_encode(result)]") diff --git a/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm b/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm new file mode 100644 index 0000000000..f8bdfd60be --- /dev/null +++ b/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm @@ -0,0 +1,10 @@ +#define CONDITION_TRUE + +#ifdef CONDITION_TRUE +#warn Oh no you're gonna have a bad time +#endif + +/proc/RunTest() + return + +#warn end of file \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm b/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm index 293fcc7d2b..f77f95dc52 100644 --- a/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm +++ b/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm @@ -1,23 +1,23 @@ -// !issaved(B) commented out because of TODO in DMOpcodeHandlers.IsSaved /obj/o var/A - //var/tmp/B + var/tmp/B /obj/o/proc/IsSavedSrcVars() ASSERT(issaved(A)) - //ASSERT(!issaved(B)) + ASSERT(!issaved(B)) ASSERT(issaved(vars["A"])) - //ASSERT(!issaved(vars["B"])) + ASSERT(!issaved(vars["B"])) /proc/RunTest() var/obj/o/test = new + ASSERT(!issaved(test.type)) ASSERT(issaved(test.A)) - //ASSERT(!issaved(test.B)) + ASSERT(!issaved(test.B)) // Note that this doesn't work on most lists and will instead return false ASSERT(issaved(test.vars["A"])) - //ASSERT(!issaved(test.vars["B"])) + ASSERT(!issaved(test.vars["B"])) /* var/expected = prob(50) diff --git a/Content.Tests/DummyDreamMapManager.cs b/Content.Tests/DummyDreamMapManager.cs index 88182b22a3..8a4180808b 100644 --- a/Content.Tests/DummyDreamMapManager.cs +++ b/Content.Tests/DummyDreamMapManager.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using OpenDreamRuntime; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; @@ -17,9 +18,9 @@ public void Initialize() { } public void UpdateTiles() { } - public void LoadAreasAndTurfs(DreamMapJson map) { } + public void LoadMaps(List? maps) { } - public void InitializeAtoms(DreamMapJson map) { } + public void InitializeAtoms(List? maps) { } public void SetTurf(DreamObjectTurf turf, DreamObjectDefinition type, DreamProcArguments creationArguments) { } diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index 57df1fb0e0..5c7a1599a8 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -105,8 +105,8 @@ public enum DreamProcOpcode : byte { PushGlobalVars = 0x5F, ModulusModulus = 0x60, ModulusModulusReference = 0x61, - PushProcStub = 0x62, - PushVerbStub = 0x63, + //0x62 + //0x63 JumpIfNull = 0x64, JumpIfNullNoPop = 0x65, JumpIfTrueReference = 0x66, @@ -127,7 +127,18 @@ public enum DreamProcOpcode : byte { GetStep = 0x75, Length = 0x76, GetDir = 0x77, - DebuggerBreakpoint = 0x78 + DebuggerBreakpoint = 0x78, + Sin = 0x79, + Cos = 0x7A, + Tan = 0x7B, + ArcSin = 0x7C, + ArcCos = 0x7D, + ArcTan = 0x7E, + ArcTan2 = 0x7F, + Sqrt = 0x80, + Log = 0x81, + LogE = 0x82, + Abs = 0x83, } /// diff --git a/DMCompiler/Compiler/DM/DMAST.cs b/DMCompiler/Compiler/DM/DMAST.cs index 5ad40281e0..b60eb9a1be 100644 --- a/DMCompiler/Compiler/DM/DMAST.cs +++ b/DMCompiler/Compiler/DM/DMAST.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using System.Linq; using OpenDreamShared.Compiler; using OpenDreamShared.Dream; @@ -262,7 +263,36 @@ public void VisitGradient(DMASTGradient gradient) { public void VisitPick(DMASTPick pick) { throw new NotImplementedException(); } - + public void VisitSin(DMASTSin sin) { + throw new NotImplementedException(); + } + public void VisitCos(DMASTCos cos) { + throw new NotImplementedException(); + } + public void VisitTan(DMASTTan tan) { + throw new NotImplementedException(); + } + public void VisitArcsin(DMASTArcsin asin) { + throw new NotImplementedException(); + } + public void VisitArccos(DMASTArccos acos) { + throw new NotImplementedException(); + } + public void VisitArctan(DMASTArctan atan) { + throw new NotImplementedException(); + } + public void VisitArctan2(DMASTArctan2 atan) { + throw new NotImplementedException(); + } + public void VisitSqrt(DMASTSqrt sqrt) { + throw new NotImplementedException(); + } + public void VisitLog(DMASTLog log) { + throw new NotImplementedException(); + } + public void VisitAbs(DMASTAbs abs) { + throw new NotImplementedException(); + } public void VisitCall(DMASTCall call) { throw new NotImplementedException(); } @@ -1554,6 +1584,129 @@ public override void Visit(DMASTVisitor visitor) { } } + public class DMASTSin : DMASTExpression { + public DMASTExpression Expression; + + public DMASTSin(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitSin(this); + } + } + + public class DMASTCos : DMASTExpression { + public DMASTExpression Expression; + + public DMASTCos(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitCos(this); + } + } + + public class DMASTTan : DMASTExpression { + public DMASTExpression Expression; + + public DMASTTan(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitTan(this); + } + } + + public class DMASTArcsin : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArcsin(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArcsin(this); + } + } + + public class DMASTArccos : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArccos(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArccos(this); + } + } + + public class DMASTArctan : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArctan(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArctan(this); + } + } + + public class DMASTArctan2 : DMASTExpression { + public DMASTExpression XExpression; + public DMASTExpression YExpression; + + public DMASTArctan2(Location location, DMASTExpression xExpression, DMASTExpression yExpression) : base(location) { + XExpression = xExpression; + YExpression = yExpression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArctan2(this); + } + } + + public class DMASTSqrt : DMASTExpression { + public DMASTExpression Expression; + + public DMASTSqrt(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitSqrt(this); + } + } + + public class DMASTLog : DMASTExpression { + public DMASTExpression Expression; + public DMASTExpression? BaseExpression; + + public DMASTLog(Location location, DMASTExpression expression, DMASTExpression? baseExpression) : base(location) { + Expression = expression; + BaseExpression = baseExpression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitLog(this); + } + } + + public class DMASTAbs : DMASTExpression { + public DMASTExpression Expression; + + public DMASTAbs(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitAbs(this); + } + } public sealed class DMASTCall : DMASTExpression { public readonly DMASTCallParameter[] CallParameters, ProcParameters; diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index e1b9d8d8de..66990b5a7e 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2412,6 +2412,55 @@ private DMASTExpression ParseProcCall(DMASTExpression expression) { return new DMASTIsSaved(identifier.Location, callParameters[0].Value); } + case "sin": { + if (callParameters.Length != 1) Error("sin() requires 1 argument"); + + return new DMASTSin(identifier.Location, callParameters[0].Value); + } + case "cos": { + if (callParameters.Length != 1) Error("cos() requires 1 argument"); + + return new DMASTCos(identifier.Location, callParameters[0].Value); + } + case "tan": { + if (callParameters.Length != 1) Error("tan() requires 1 argument"); + + return new DMASTTan(identifier.Location, callParameters[0].Value); + } + case "arcsin": { + if (callParameters.Length != 1) Error("arcsin() requires 1 argument"); + + return new DMASTArcsin(identifier.Location, callParameters[0].Value); + } + case "arccos": { + if (callParameters.Length != 1) Error("arccos() requires 1 argument"); + + return new DMASTArccos(identifier.Location, callParameters[0].Value); + } + case "arctan": { + if (callParameters.Length != 1 && callParameters.Length != 2) + Error("arctan() requires 1 or 2 arguments"); + if (callParameters.Length == 1) + return new DMASTArctan(identifier.Location, callParameters[0].Value); + return new DMASTArctan2(identifier.Location, callParameters[0].Value, callParameters[1].Value); + } + case "sqrt": { + if (callParameters.Length != 1) Error("sqrt() requires 1 argument"); + + return new DMASTSqrt(identifier.Location, callParameters[0].Value); + } + case "log": { + if (callParameters.Length != 1 && callParameters.Length != 2) + Error("log() requires 1 or 2 arguments"); + if (callParameters.Length == 1) + return new DMASTLog(identifier.Location, callParameters[0].Value, null); + return new DMASTLog(identifier.Location, callParameters[1].Value, callParameters[0].Value); + } + case "abs": { + if (callParameters.Length != 1) Error("abs() requires 1 argument"); + + return new DMASTAbs(identifier.Location, callParameters[0].Value); + } case "istype": { if (callParameters.Length == 1) { return new DMASTImplicitIsType(identifier.Location, callParameters[0].Value); diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index 49398833ba..f5c7c48782 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -299,7 +299,30 @@ private void HandleDefineDirective(Token defineToken) { GetLineOfTokens(); // consume what's on this line and leave return; } - if(defineIdentifier.Text == "defined") { + + // #define FILE_DIR is a little special + // Every define will add to a list of directories to check for resource files + if (defineIdentifier.Text == "FILE_DIR") { + Token dirToken = GetNextToken(true); + string? dirTokenValue = dirToken.Type switch { + TokenType.DM_Preproc_ConstantString => (string?)dirToken.Value, + TokenType.DM_Preproc_Punctuator_Period => ".", + _ => null + }; + + if (dirTokenValue is null) { + DMCompiler.Emit(WarningCode.BadDirective, dirToken.Location, $"\"{dirToken.Text}\" is not a valid directory"); + return; + } + + DMPreprocessorLexer currentLexer = _lexerStack.Peek(); + string dir = Path.Combine(currentLexer.IncludeDirectory, dirTokenValue); + DMCompiler.AddResourceDirectory(dir); + + // In BYOND it goes on to set the FILE_DIR macro's value to the added directory + // I don't see any reason to do that + return; + } else if (defineIdentifier.Text == "defined") { DMCompiler.Emit(WarningCode.SoftReservedKeyword, defineIdentifier.Location, "Reserved keyword 'defined' cannot be used as macro name"); } @@ -310,11 +333,10 @@ private void HandleDefineDirective(Token defineToken) { if (macroToken.Type == TokenType.DM_Preproc_Punctuator_LeftParenthesis) { // We're a macro function! parameters = new List(1); //Read in the parameters - Token parameterToken; bool canConsumeComma = false; bool foundVariadic = false; while(true) { - parameterToken = GetNextToken(true); + var parameterToken = GetNextToken(true); switch(parameterToken.Type) { case TokenType.DM_Preproc_Identifier: canConsumeComma = true; @@ -560,21 +582,10 @@ private void HandleErrorOrWarningDirective(Token token) { if (!VerifyDirectiveUsage(token)) return; - StringBuilder messageBuilder = new StringBuilder(); - - Token messageToken = GetNextToken(true); - while (messageToken.Type != TokenType.EndOfFile) { - if (messageToken.Type == TokenType.Newline) break; - - messageBuilder.Append(messageToken.Text); - messageToken = GetNextToken(); - } - - string message = messageBuilder.ToString(); if (token.Type == TokenType.DM_Preproc_Error) { - DMCompiler.Emit(WarningCode.ErrorDirective, token.Location, message); + DMCompiler.Emit(WarningCode.ErrorDirective, token.Location, token.Text); } else { - DMCompiler.Emit(WarningCode.WarningDirective, token.Location, message); + DMCompiler.Emit(WarningCode.WarningDirective, token.Location, token.Text); } } diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs index 6cd410bcf8..d4f4785748 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs @@ -428,7 +428,28 @@ protected override Token ParseNextToken() { private bool TryMacroKeyword(string text, out Token token) { switch (text) { case "warn": - case "warning": token = CreateToken(TokenType.DM_Preproc_Warning, "#warn"); break; + case "warning": { + StringBuilder message = new StringBuilder(); + + while (GetCurrent() is not '\0' and not '\n') { + message.Append(GetCurrent()); + Advance(); + } + + token = CreateToken(TokenType.DM_Preproc_Warning, "#warn" + message.ToString()); + break; + } + case "error": { + StringBuilder message = new StringBuilder(); + + while (GetCurrent() is not '\0' and not '\n') { + message.Append(GetCurrent()); + Advance(); + } + + token = CreateToken(TokenType.DM_Preproc_Error, "#error" + message.ToString()); + break; + } case "include": token = CreateToken(TokenType.DM_Preproc_Include, "#include"); break; case "define": token = CreateToken(TokenType.DM_Preproc_Define, "#define"); break; case "undef": token = CreateToken(TokenType.DM_Preproc_Undefine, "#undef"); break; @@ -438,7 +459,6 @@ private bool TryMacroKeyword(string text, out Token token) { case "elif": token = CreateToken(TokenType.DM_Preproc_Elif, "#elif"); break; case "else": token = CreateToken(TokenType.DM_Preproc_Else, "#else"); break; case "endif": token = CreateToken(TokenType.DM_Preproc_EndIf, "#endif"); break; - case "error": token = CreateToken(TokenType.DM_Preproc_Error, "#error"); break; //OD-specific directives case "pragma": token = CreateToken(TokenType.DM_Preproc_Pragma, "#pragma"); break; default: diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index 1c488a92b4..de02e80c06 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -21,6 +21,8 @@ internal sealed class DMObject { public Dictionary VariableOverrides = new(); public Dictionary GlobalVariables = new(); /// A list of var and verb initializations implicitly done before the user's New() is called. + public HashSet ConstVariables = new(); + public HashSet TmpVariables = new(); public List InitializationProcExpressions = new(); public int? InitializationProc; @@ -169,6 +171,14 @@ public DreamTypeJson CreateJsonRepresentation() { typeJson.GlobalVariables = GlobalVariables; } + if (ConstVariables.Count > 0) { + typeJson.ConstVariables = ConstVariables; + } + + if (TmpVariables.Count > 0) { + typeJson.TmpVariables = TmpVariables; + } + if (InitializationProc != null) { typeJson.InitProc = InitializationProc; } diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index daffd397c0..6773b4f58d 100644 --- a/DMCompiler/DM/DMObjectTree.cs +++ b/DMCompiler/DM/DMObjectTree.cs @@ -173,7 +173,7 @@ public static bool TryGetTypeId(DreamPath path, out int typeId) { public static int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, DMValueType valType = DMValueType.Anything) { int id = Globals.Count; - global = new DMVariable(type, name, true, isConst, valType); + global = new DMVariable(type, name, true, isConst, false, valType); Globals.Add(global); return id; } diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 06b6f9e263..4f7320cb1e 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -945,6 +945,52 @@ public void LessThanOrEqual() { WriteOpcode(DreamProcOpcode.CompareLessThanOrEqual); } + public void Sin() { + WriteOpcode(DreamProcOpcode.Sin); + } + + public void Cos() { + WriteOpcode(DreamProcOpcode.Cos); + } + + public void Tan() { + WriteOpcode(DreamProcOpcode.Tan); + } + + public void ArcSin() { + WriteOpcode(DreamProcOpcode.ArcSin); + } + + public void ArcCos() { + WriteOpcode(DreamProcOpcode.ArcCos); + } + + public void ArcTan() { + WriteOpcode(DreamProcOpcode.ArcTan); + } + + public void ArcTan2() { + ShrinkStack(1); + WriteOpcode(DreamProcOpcode.ArcTan2); + } + + public void Sqrt() { + WriteOpcode(DreamProcOpcode.Sqrt); + } + + public void Log() { + ShrinkStack(1); + WriteOpcode(DreamProcOpcode.Log); + } + + public void LogE() { + WriteOpcode(DreamProcOpcode.LogE); + } + + public void Abs() { + WriteOpcode(DreamProcOpcode.Abs); + } + public void PushFloat(float value) { GrowStack(1); WriteOpcode(DreamProcOpcode.PushFloat); @@ -975,25 +1021,12 @@ public void PushProc(int procId) { WriteInt(procId); } - public void PushProcStub(int typeId) { - GrowStack(1); - WriteOpcode(DreamProcOpcode.PushProcStub); - WriteInt(typeId); - } - - public void PushVerbStub(int typeId) { - GrowStack(1); - WriteOpcode(DreamProcOpcode.PushVerbStub); - WriteInt(typeId); - } - public void PushNull() { GrowStack(1); WriteOpcode(DreamProcOpcode.PushNull); } - public void PushGlobalVars() - { + public void PushGlobalVars() { GrowStack(1); WriteOpcode(DreamProcOpcode.PushGlobalVars); } diff --git a/DMCompiler/DM/DMVariable.cs b/DMCompiler/DM/DMVariable.cs index 86f63e91c9..6f2efaea20 100644 --- a/DMCompiler/DM/DMVariable.cs +++ b/DMCompiler/DM/DMVariable.cs @@ -10,14 +10,16 @@ sealed class DMVariable { /// NOTE: This DMVariable may be forced constant through opendream_compiletimereadonly. This only marks that the variable has the DM quality of /const/ness. /// public bool IsConst; + public bool IsTmp; public DMExpression Value; public DMValueType ValType; - public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, DMValueType valType = DMValueType.Anything) { + public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, bool isTmp, DMValueType valType = DMValueType.Anything) { Type = type; Name = name; IsGlobal = isGlobal; IsConst = isConst; + IsTmp = isTmp; Value = null; ValType = valType; } @@ -33,7 +35,7 @@ public DMVariable WriteToValue(Expressions.Constant value) { return this; } - DMVariable clone = new DMVariable(Type, Name, IsGlobal, IsConst, ValType); + DMVariable clone = new DMVariable(Type, Name, IsGlobal, IsConst, IsTmp, ValType); clone.Value = value; return clone; } diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index ece83bc408..ab46a0fc31 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -3,6 +3,7 @@ using OpenDreamShared.Dream; using OpenDreamShared.Json; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using DMCompiler.Bytecode; namespace DMCompiler.DM.Expressions { @@ -643,4 +644,339 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { proc.PushProc(proc.Id); } } + + internal class Sin : DMExpression { + private readonly DMExpression _expr; + + public Sin(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, sin(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Sin(x)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Sin(); + } + } + + internal class Cos : DMExpression { + private readonly DMExpression _expr; + + public Cos(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, cos(0) will always be 1"); + } + + constant = new Number(Location, SharedOperations.Cos(x)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Cos(); + } + } + + internal class Tan : DMExpression { + private readonly DMExpression _expr; + + public Tan(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, tan(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Tan(x)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Tan(); + } + } + + internal class ArcSin : DMExpression { + private readonly DMExpression _expr; + + public ArcSin(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arcsin(0) will always be 0"); + } + + if (x is < -1 or > 1) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + x = 0; + } + + constant = new Number(Location, SharedOperations.ArcSin(x)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.ArcSin(); + } + } + + internal class ArcCos : DMExpression { + private readonly DMExpression _expr; + + public ArcCos(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arccos(0) will always be 1"); + } + + if (x is < -1 or > 1) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + x = 0; + } + + constant = new Number(Location, SharedOperations.ArcCos(x)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.ArcCos(); + } + } + + internal class ArcTan : DMExpression { + private readonly DMExpression _expr; + + public ArcTan(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arctan(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.ArcTan(a)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.ArcTan(); + } + } + + internal class ArcTan2 : DMExpression { + private readonly DMExpression _xExpr; + private readonly DMExpression _yExpr; + + public ArcTan2(Location location, DMExpression xExpr, DMExpression yExpr) : base(location) { + _xExpr = xExpr; + _yExpr = yExpr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_xExpr.TryAsConstant(out var xConst) || !_yExpr.TryAsConstant(out var yConst)) { + constant = null; + return false; + } + + if (xConst is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _xExpr.Location, "Invalid x value treated as 0"); + } + + if (yConst is not Number {Value: var y}) { + y = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _xExpr.Location, "Invalid y value treated as 0"); + } + + constant = new Number(Location, SharedOperations.ArcTan(x, y)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _xExpr.EmitPushValue(dmObject, proc); + _yExpr.EmitPushValue(dmObject, proc); + proc.ArcTan2(); + } + } + + internal class Sqrt : DMExpression { + private readonly DMExpression _expr; + + public Sqrt(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, sqrt(0) will always be 0"); + } + + if (a < 0) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, + $"Cannot get the square root of a negative number ({a})"); + } + + constant = new Number(Location, SharedOperations.Sqrt(a)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Sqrt(); + } + } + + internal class Log : DMExpression { + private readonly DMExpression _expr; + private readonly DMExpression? _baseExpr; + + public Log(Location location, DMExpression expr, DMExpression? baseExpr) : base(location) { + _expr = expr; + _baseExpr = baseExpr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var value} || value <= 0) { + value = 1; + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, + "Invalid value, must be a number greater than 0"); + } + + if (_baseExpr == null) { + constant = new Number(Location, SharedOperations.Log(value)); + return true; + } + + if (!_baseExpr.TryAsConstant(out var baseConstant)) { + constant = null; + return false; + } + + if (baseConstant is not Number {Value: var baseValue} || baseValue <= 0) { + baseValue = 10; + DMCompiler.Emit(WarningCode.BadArgument, _baseExpr.Location, + "Invalid base, must be a number greater than 0"); + } + + constant = new Number(Location, SharedOperations.Log(value, baseValue)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + if (_baseExpr == null) { + proc.LogE(); + } else { + _baseExpr.EmitPushValue(dmObject, proc); + proc.Log(); + } + } + } + + internal class Abs : DMExpression { + private readonly DMExpression _expr; + + public Abs(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, abs(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Abs(a)); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Abs(); + } + } } diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 7fa01a60f3..824f7680c7 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -3,6 +3,7 @@ using OpenDreamShared.Json; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -364,24 +365,32 @@ public Resource(Location location, string filePath) : base(location) { // Treat backslashes as forward slashes on Linux filePath = filePath.Replace('\\', '/'); - string? finalFilePath = null; - - var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files[0]) ?? "/"; + var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files?[0]) ?? "/"; if (string.IsNullOrEmpty(outputDir)) outputDir = "./"; + string? finalFilePath = null; + var fileName = System.IO.Path.GetFileName(filePath); var fileDir = System.IO.Path.GetDirectoryName(filePath) ?? string.Empty; - var directory = FindDirectory(outputDir, fileDir); - if (directory != null) { - // Perform a case-insensitive search for the file - finalFilePath = FindFile(directory, fileName); + + // Search every defined FILE_DIR + foreach (string resourceDir in DMCompiler.ResourceDirectories) { + var directory = FindDirectory(resourceDir, fileDir); + + if (directory != null) { + // Perform a case-insensitive search for the file + finalFilePath = FindFile(directory, fileName); + + if (finalFilePath != null) + break; + } } - // Search relative to the source file if it wasn't in the project's directory + // Search relative to the source file if it wasn't in one of the FILE_DIRs if (finalFilePath == null) { var sourceDir = System.IO.Path.Combine(outputDir, System.IO.Path.GetDirectoryName(Location.SourceFile) ?? string.Empty); - directory = FindDirectory(sourceDir, fileDir); + var directory = FindDirectory(sourceDir, fileDir); if (directory != null) finalFilePath = FindFile(directory, fileName); @@ -503,10 +512,11 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { proc.PushProc(pathInfo.Value.Id); break; case PathType.ProcStub: - proc.PushProcStub(pathInfo.Value.Id); - break; case PathType.VerbStub: - proc.PushVerbStub(pathInfo.Value.Id); + var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; + + // /datum/proc and /datum/verb just compile down to strings lmao + proc.PushString($"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"); break; default: DMCompiler.ForcedError(Location, $"Invalid PathType {pathInfo.Value.Type}"); @@ -526,11 +536,17 @@ public override bool TryAsJsonRepresentation(out object? json) { return false; } + if (pathInfo.Value.Type is PathType.ProcStub or PathType.VerbStub) { + var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; + + json = $"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"; + return true; + } + JsonVariableType jsonType = pathInfo.Value.Type switch { PathType.TypeReference => JsonVariableType.Type, PathType.ProcReference => JsonVariableType.Proc, - PathType.ProcStub => JsonVariableType.ProcStub, - PathType.VerbStub => JsonVariableType.VerbStub + _ => throw new UnreachableException() }; json = new Dictionary() { diff --git a/DMCompiler/DM/Visitors/DMObjectBuilder.cs b/DMCompiler/DM/Visitors/DMObjectBuilder.cs index 1b766b1719..9d62e01b4d 100644 --- a/DMCompiler/DM/Visitors/DMObjectBuilder.cs +++ b/DMCompiler/DM/Visitors/DMObjectBuilder.cs @@ -274,8 +274,16 @@ private static void ProcessVarDefinition(DMObject? varObject, DMASTObjectVarDefi if (varDefinition.IsStatic) { variable = varObject.CreateGlobalVariable(varDefinition.Type, varDefinition.Name, varDefinition.IsConst, varDefinition.ValType); } else { - variable = new DMVariable(varDefinition.Type, varDefinition.Name, false, varDefinition.IsConst,varDefinition.ValType); + variable = new DMVariable(varDefinition.Type, varDefinition.Name, false, varDefinition.IsConst, varDefinition.IsTmp, varDefinition.ValType); varObject.Variables[variable.Name] = variable; + if(varDefinition.IsConst){ + varObject.ConstVariables ??= new HashSet(); + varObject.ConstVariables.Add(varDefinition.Name); + } + if(varDefinition.IsTmp){ + varObject.TmpVariables ??= new HashSet(); + varObject.TmpVariables.Add(varDefinition.Name); + } } } diff --git a/DMCompiler/DM/Visitors/DMVisitorExpression.cs b/DMCompiler/DM/Visitors/DMVisitorExpression.cs index fcd6dc9514..8acb35a0dc 100644 --- a/DMCompiler/DM/Visitors/DMVisitorExpression.cs +++ b/DMCompiler/DM/Visitors/DMVisitorExpression.cs @@ -978,6 +978,61 @@ public void VisitPick(DMASTPick pick) { Result = new Expressions.Pick(pick.Location, pickValues); } + + public void VisitSin(DMASTSin sin) { + var expr = DMExpression.Create(_dmObject, _proc, sin.Expression, _inferredPath); + Result = new Expressions.Sin(sin.Location, expr); + } + + public void VisitCos(DMASTCos cos) { + var expr = DMExpression.Create(_dmObject, _proc, cos.Expression, _inferredPath); + Result = new Expressions.Cos(cos.Location, expr); + } + + public void VisitTan(DMASTTan tan) { + var expr = DMExpression.Create(_dmObject, _proc, tan.Expression, _inferredPath); + Result = new Expressions.Tan(tan.Location, expr); + } + + public void VisitArcsin(DMASTArcsin arcsin) { + var expr = DMExpression.Create(_dmObject, _proc, arcsin.Expression, _inferredPath); + Result = new Expressions.ArcSin(arcsin.Location, expr); + } + + public void VisitArccos(DMASTArccos arccos) { + var expr = DMExpression.Create(_dmObject, _proc, arccos.Expression, _inferredPath); + Result = new Expressions.ArcCos(arccos.Location, expr); + } + + public void VisitArctan(DMASTArctan arctan) { + var expr = DMExpression.Create(_dmObject, _proc, arctan.Expression, _inferredPath); + Result = new Expressions.ArcTan(arctan.Location, expr); + } + + public void VisitArctan2(DMASTArctan2 arctan2) { + var xexpr = DMExpression.Create(_dmObject, _proc, arctan2.XExpression, _inferredPath); + var yexpr = DMExpression.Create(_dmObject, _proc, arctan2.YExpression, _inferredPath); + Result = new Expressions.ArcTan2(arctan2.Location, xexpr, yexpr); + } + + public void VisitSqrt(DMASTSqrt sqrt) { + var expr = DMExpression.Create(_dmObject, _proc, sqrt.Expression, _inferredPath); + Result = new Expressions.Sqrt(sqrt.Location, expr); + } + + public void VisitLog(DMASTLog log) { + var expr = DMExpression.Create(_dmObject, _proc, log.Expression, _inferredPath); + DMExpression? baseExpr = null; + if (log.BaseExpression != null) { + baseExpr = DMExpression.Create(_dmObject, _proc, log.BaseExpression, _inferredPath); + } + Result = new Expressions.Log(log.Location, expr, baseExpr); + } + + public void VisitAbs(DMASTAbs abs) { + var expr = DMExpression.Create(_dmObject, _proc, abs.Expression, _inferredPath); + Result = new Expressions.Abs(abs.Location, expr); + } public void VisitCall(DMASTCall call) { var procArgs = new ArgumentList(call.Location, _dmObject, _proc, call.ProcParameters, _inferredPath); diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 69844b001e..86f91cd376 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -20,11 +20,13 @@ namespace DMCompiler { //TODO: Make this not a static class public static class DMCompiler { - public static int ErrorCount = 0; - public static int WarningCount = 0; + public static int ErrorCount; + public static int WarningCount; public static DMCompilerSettings Settings; + public static IReadOnlyList ResourceDirectories => _resourceDirectories; - private static DMCompilerConfiguration Config; + private static readonly DMCompilerConfiguration Config = new(); + private static readonly List _resourceDirectories = new(); private static DateTime _compileStartTime; public static bool Compile(DMCompilerSettings settings) { @@ -32,13 +34,18 @@ public static bool Compile(DMCompilerSettings settings) { WarningCount = 0; Settings = settings; if (Settings.Files == null) return false; - Config = new(); + Config.Reset(); + _resourceDirectories.Clear(); //TODO: Only use InvariantCulture where necessary instead of it being the default CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; _compileStartTime = DateTime.Now; +#if DEBUG + ForcedWarning("This compiler was compiled in the Debug .NET configuration. This will impact compile speed."); +#endif + if (settings.SuppressUnimplementedWarnings) { ForcedWarning("Unimplemented proc & var warnings are currently suppressed"); } @@ -50,25 +57,18 @@ public static bool Compile(DMCompilerSettings settings) { DMPreprocessor preprocessor = Preprocess(settings.Files, settings.MacroDefines); bool successfulCompile = preprocessor is not null && Compile(preprocessor); - if (successfulCompile) - { + if (successfulCompile) { //Output file is the first file with the extension changed to .json string outputFile = Path.ChangeExtension(settings.Files[0], "json"); List maps = ConvertMaps(preprocessor.IncludedMaps); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { var output = SaveJson(maps, preprocessor.IncludedInterface, outputFile); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { Console.WriteLine($"Compilation succeeded with {WarningCount} warnings"); Console.WriteLine(output); } @@ -80,13 +80,19 @@ public static bool Compile(DMCompilerSettings settings) { } TimeSpan duration = DateTime.Now - _compileStartTime; - Console.WriteLine($"Total time: {duration.ToString(@"mm\:ss")}"); + Console.WriteLine($"Total time: {duration:mm\\:ss}"); return successfulCompile; } - private static DMPreprocessor? Preprocess(List files, Dictionary macroDefines) { - DMPreprocessor? build() { + public static void AddResourceDirectory(string dir) { + dir = dir.Replace('\\', Path.DirectorySeparatorChar); + + _resourceDirectories.Add(dir); + } + + private static DMPreprocessor? Preprocess(List files, Dictionary? macroDefines) { + DMPreprocessor? Build() { DMPreprocessor preproc = new DMPreprocessor(true); if (macroDefines != null) { foreach (var (key, value) in macroDefines) { @@ -130,7 +136,7 @@ public static bool Compile(DMCompilerSettings settings) { if (Settings.DumpPreprocessor) { //Preprocessing is done twice because the output is used up when dumping it StringBuilder result = new(); - foreach (Token t in build()) { + foreach (Token t in Build()) { result.Append(t.Text); } @@ -140,7 +146,8 @@ public static bool Compile(DMCompilerSettings settings) { File.WriteAllText(outputPath, result.ToString()); Console.WriteLine($"Preprocessor output dumped to {outputPath}"); } - return build(); + + return Build(); } private static bool Compile(IEnumerable preprocessedTokens) { @@ -154,11 +161,6 @@ private static bool Compile(IEnumerable preprocessedTokens) { Emit(warning); } - if (astFile is null) { - VerbosePrint("Parsing failed, exiting compilation"); - return false; - } - DMASTSimplifier astSimplifier = new DMASTSimplifier(); VerbosePrint("Constant folding"); astSimplifier.SimplifyAST(astFile); @@ -190,7 +192,7 @@ public static void Emit(CompilerEmission emission) { /// Emits the given warning, according to its ErrorLevel as set in our config. /// True if the warning was an error, false if not. public static bool Emit(WarningCode code, Location loc, string message) { - ErrorLevel level = Config.errorConfig[code]; + ErrorLevel level = Config.ErrorConfig[code]; Emit(new CompilerEmission(level, code, loc, message)); return level == ErrorLevel.Error; } @@ -328,7 +330,7 @@ private static string SaveJson(List maps, string interfaceFile, st public static void DefineFatalErrors() { foreach (WarningCode code in Enum.GetValues()) { if((int)code < 1_000) { - Config.errorConfig[code] = ErrorLevel.Error; + Config.ErrorConfig[code] = ErrorLevel.Error; } } } @@ -338,32 +340,32 @@ public static void DefineFatalErrors() { /// public static void CheckAllPragmasWereSet() { foreach(WarningCode code in Enum.GetValues()) { - if (!Config.errorConfig.ContainsKey(code)) { + if (!Config.ErrorConfig.ContainsKey(code)) { ForcedWarning($"Warning #{(int)code:d4} '{code.ToString()}' was never declared as error, warning, notice, or disabled."); - Config.errorConfig.Add(code, ErrorLevel.Disabled); + Config.ErrorConfig.Add(code, ErrorLevel.Disabled); } } } public static void SetPragma(WarningCode code, ErrorLevel level) { - Config.errorConfig[code] = level; + Config.ErrorConfig[code] = level; } public static ErrorLevel CodeToLevel(WarningCode code) { - bool didFind = Config.errorConfig.TryGetValue(code, out var ret); + bool didFind = Config.ErrorConfig.TryGetValue(code, out var ret); DebugTools.Assert(didFind); return ret; } } public struct DMCompilerSettings { - public List Files = null; + public List? Files = null; public bool SuppressUnimplementedWarnings = false; public bool NoticesEnabled = false; public bool DumpPreprocessor = false; public bool NoStandard = false; public bool Verbose = false; - public Dictionary MacroDefines = null; + public Dictionary? MacroDefines = null; /// A user-provided pragma config file, if one was provided. public string? PragmaFileOverride = null; @@ -375,10 +377,11 @@ public DMCompilerSettings() { } } - class DMCompilerConfiguration { - public Dictionary errorConfig; - public DMCompilerConfiguration() { - errorConfig = new(Enum.GetValues().Length); + internal class DMCompilerConfiguration { + public readonly Dictionary ErrorConfig = new(Enum.GetValues().Length); + + public void Reset() { + ErrorConfig.Clear(); } } } diff --git a/DMCompiler/DMCompiler.csproj b/DMCompiler/DMCompiler.csproj index 9b7014b99e..c533fdee4f 100644 --- a/DMCompiler/DMCompiler.csproj +++ b/DMCompiler/DMCompiler.csproj @@ -6,6 +6,8 @@ enable Debug;Release;Tools AnyCPU + false + ..\bin\DMCompiler\ diff --git a/DMCompiler/DMStandard/DefaultPragmaConfig.dm b/DMCompiler/DMStandard/DefaultPragmaConfig.dm index 7526cc2396..2c71d6c4e4 100644 --- a/DMCompiler/DMStandard/DefaultPragmaConfig.dm +++ b/DMCompiler/DMStandard/DefaultPragmaConfig.dm @@ -19,6 +19,7 @@ #pragma PointlessParentCall warning #pragma PointlessBuiltinCall warning #pragma SuspiciousMatrixCall warning +#pragma FallbackBuiltinArgument warning #pragma MalformedRange warning #pragma InvalidRange error #pragma InvalidSetStatement error diff --git a/DMCompiler/DMStandard/Defines.dm b/DMCompiler/DMStandard/Defines.dm index acf07008f0..c32d2e50ea 100644 --- a/DMCompiler/DMStandard/Defines.dm +++ b/DMCompiler/DMStandard/Defines.dm @@ -2,6 +2,7 @@ #define FALSE 0 #define OPENDREAM 1 +#define OPENDREAM_TOPIC_PORT_EXISTS 1 // Remove this if world.opendream_topic_port is ever removed #define NORTH 1 #define SOUTH 2 diff --git a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm index 772b090d71..faee9c15d4 100644 --- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm +++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm @@ -1,7 +1,7 @@ /atom parent_type = /datum - var/name = "atom" + var/name = null var/text = null var/desc = null var/suffix = null as opendream_unimplemented diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm index 4ec61aeeab..4f6f895580 100644 --- a/DMCompiler/DMStandard/Types/Client.dm +++ b/DMCompiler/DMStandard/Types/Client.dm @@ -9,7 +9,7 @@ var/default_verb_category = "Commands" var/tag - var/type = /client + var/const/type = /client var/mob/mob var/atom/eye diff --git a/DMCompiler/DMStandard/Types/Datum.dm b/DMCompiler/DMStandard/Types/Datum.dm index 66e47ef3ba..ce2d1c7b01 100644 --- a/DMCompiler/DMStandard/Types/Datum.dm +++ b/DMCompiler/DMStandard/Types/Datum.dm @@ -1,8 +1,8 @@ /datum - var/type - var/parent_type + var/const/type + var/tmp/parent_type - var/list/vars + var/const/list/vars var/tag diff --git a/DMCompiler/DMStandard/Types/List.dm b/DMCompiler/DMStandard/Types/List.dm index ca0faa0822..ce16246e56 100644 --- a/DMCompiler/DMStandard/Types/List.dm +++ b/DMCompiler/DMStandard/Types/List.dm @@ -1,6 +1,6 @@ /list var/len - var/type = /list + var/const/type = /list proc/New(Size) diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index 7d59b85272..05573c5394 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -10,7 +10,7 @@ var/name = "OpenDream World" var/time - var/timezone = 0 as opendream_unimplemented + var/timezone = 0 var/timeofday var/realtime var/tick_lag = 1 @@ -19,9 +19,9 @@ var/tick_usage var/loop_checks = 0 as opendream_unimplemented - var/maxx = 0 - var/maxy = 0 - var/maxz = 0 + var/maxx = null + var/maxy = null + var/maxz = null var/icon_size = 32 var/view = 5 var/movement_mode = LEGACY_MOVEMENT_MODE as opendream_unimplemented @@ -38,15 +38,12 @@ var/visibility = 0 as opendream_unimplemented var/status as opendream_unimplemented var/process - var/list/params = null as opendream_unimplemented + var/list/params = null var/sleep_offline = 0 as opendream_unimplemented var/system_type - proc/New() - proc/Del() - var/map_cpu = 0 as opendream_unimplemented var/hub as opendream_unimplemented var/hub_password as opendream_unimplemented @@ -56,6 +53,13 @@ var/map_format = TOPDOWN_MAP as opendream_unimplemented var/cache_lifespan = 30 as opendream_unimplemented var/executor as opendream_unimplemented + + // An OpenDream read-only var that tells you what port Topic() is listening on + // Remove OPENDREAM_TOPIC_PORT_EXISTS if this is ever removed + var/const/opendream_topic_port + + proc/New() + proc/Del() proc/Profile(command, type, format) set opendream_unimplemented = TRUE @@ -82,7 +86,6 @@ proc/Import() set opendream_unimplemented = TRUE proc/Topic(T,Addr,Master,Keys) - set opendream_unimplemented = TRUE proc/SetScores() set opendream_unimplemented = TRUE diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 39c834b989..5295ea0176 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -1,13 +1,9 @@ /var/world/world = null //These procs should be in alphabetical order, as in DreamProcNativeRoot.cs -proc/abs(A) proc/addtext(...) proc/alert(Usr = usr, Message, Title, Button1 = "Ok", Button2, Button3) proc/animate(Object, time, loop, easing, flags) -proc/arccos(X) -proc/arcsin(X) -proc/arctan(A) proc/ascii2text(N) proc/block(atom/Start, atom/End, StartZ, EndX=Start, EndY=End, EndZ=StartZ) proc/ceil(A) @@ -17,7 +13,6 @@ proc/clamp(Value, Low, High) proc/cmptext(T1) proc/copytext(T, Start = 1, End = 0) proc/copytext_char(T,Start=1,End=0) -proc/cos(X) proc/CRASH(msg) proc/fcopy(Src, Dst) proc/fcopy_rsc(File) @@ -63,7 +58,6 @@ proc/json_encode(Value) proc/length(E) proc/length_char(E) proc/list2params(List) -proc/log(X, Y) proc/lowertext(T) proc/max(A) proc/md5(T) @@ -86,7 +80,6 @@ proc/roll(ndice = 1, sides) proc/round(A, B) proc/sha1(input) proc/shutdown(Addr,Natural = 0) -proc/sin(X) proc/sleep(Delay) proc/sorttext(T1, T2) proc/sorttextEx(T1, T2) @@ -96,10 +89,8 @@ proc/spantext_char(Haystack,Needles,Start=1) proc/splicetext(Text, Start = 1, End = 0, Insert = "") proc/splicetext_char(Text, Start = 1, End = 0, Insert = "") proc/splittext(Text, Delimiter) -proc/sqrt(A) proc/stat(Name, Value) proc/statpanel(Panel, Name, Value) -proc/tan(X) proc/text2ascii(T, pos = 1) proc/text2ascii_char(T, pos = 1) proc/text2file(Text, File) diff --git a/DMCompiler/Program.cs b/DMCompiler/Program.cs index 51b7f0d3cc..8cbd5c0a3e 100644 --- a/DMCompiler/Program.cs +++ b/DMCompiler/Program.cs @@ -1,24 +1,19 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; -using OpenDreamShared.Compiler; using Robust.Shared.Utility; namespace DMCompiler { - - struct Argument { + internal struct Argument { /// The text we found that's in the '--whatever' format. May be null if no such text was present. public string? Name; /// The value, either set in a '--whatever=whoever' format or just left by itself anonymously. May be null. public string? Value; } - - - class Program { - static void Main(string[] args) { + internal static class Program { + private static void Main(string[] args) { if (!TryParseArguments(args, out DMCompilerSettings settings)) { Environment.Exit(1); return; diff --git a/DMCompiler/SharedOperations.cs b/DMCompiler/SharedOperations.cs new file mode 100644 index 0000000000..a29482757c --- /dev/null +++ b/DMCompiler/SharedOperations.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.CompilerServices; + +namespace DMCompiler; + +/// +/// A class containing operations used by both the compiler and the server. +/// Helps make sure things like sin() and cos() give the same result on both. +/// +public static class SharedOperations { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float x) { + return MathF.Sin(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float x) { + return MathF.Cos(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Tan(float x) { + return MathF.Tan(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcSin(float x) { + return MathF.Asin(x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcCos(float x) { + return MathF.Acos(x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcTan(float a) { + return MathF.Atan(a) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcTan(float x, float y) { + return MathF.Atan2(y, x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sqrt(float a) { + return MathF.Sqrt(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log(float y) { + return MathF.Log(y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log(float y, float baseValue) { + return MathF.Log(y, baseValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Abs(float a) { + return MathF.Abs(a); + } +} diff --git a/OpenDream.sln b/OpenDream.sln index 8a83b2891b..bc0d5a7dd3 100644 --- a/OpenDream.sln +++ b/OpenDream.sln @@ -7,8 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestGame", "TestGame", "{72 ProjectSection(SolutionItems) = preProject TestGame\code.dm = TestGame\code.dm TestGame\environment.dme = TestGame\environment.dme - TestGame\map.dmm = TestGame\map.dmm TestGame\renderer_tests.dm = TestGame\renderer_tests.dm + TestGame\map_z1.dmm = TestGame\map_z1.dmm + TestGame\map_z2.dmm = TestGame\map_z2.dmm + TestGame\map_z3.dmm = TestGame\map_z3.dmm EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "icons", "icons", "{4F1F4C68-4117-4ECC-8174-D399C1D6C409}" diff --git a/OpenDream.sln.DotSettings b/OpenDream.sln.DotSettings index 06cd921c58..b9158a88ff 100644 --- a/OpenDream.sln.DotSettings +++ b/OpenDream.sln.DotSettings @@ -9,6 +9,9 @@ RHS UI True + True + True + True True True True diff --git a/OpenDreamClient/Input/DreamCommandSystem.cs b/OpenDreamClient/Input/DreamCommandSystem.cs deleted file mode 100644 index b85b1e86bf..0000000000 --- a/OpenDreamClient/Input/DreamCommandSystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using OpenDreamShared.Input; -using OpenDreamClient.Interface; -using Robust.Shared.Network; - -namespace OpenDreamClient.Input { - public sealed class DreamCommandSystem : SharedDreamCommandSystem { - [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; - - public void RunCommand(string command) { - string[] split = command.Split(" "); - string verb = split[0]; - - switch (verb) { - case ".quit": - IoCManager.Resolve().ClientDisconnect(".quit used"); - break; - - case ".screenshot": - _interfaceManager.SaveScreenshot(split.Length == 1 || split[1] != "auto"); - break; - - case ".configure": - Log.Warning(".configure command is not implemented"); - break; - - case ".winset": - // Everything after .winset, excluding the space and quotes - string winsetParams = command.Substring(verb.Length + 2, command.Length - verb.Length - 3); - - _interfaceManager.WinSet(null, winsetParams); - break; - - default: { - // Send the entire command to the server. - // It has more info about argument types so it can parse it better than we can. - RaiseNetworkEvent(new CommandEvent(command)); - break; - } - } - } - - public void StartRepeatingCommand(string command) { - RaiseNetworkEvent(new RepeatCommandEvent(command)); - } - - public void StopRepeatingCommand(string command) { - RaiseNetworkEvent(new StopRepeatCommandEvent(command)); - } - } -} diff --git a/OpenDreamClient/Interface/Controls/ControlBrowser.cs b/OpenDreamClient/Interface/Controls/ControlBrowser.cs index 3d3b4c7991..c49169b0f6 100644 --- a/OpenDreamClient/Interface/Controls/ControlBrowser.cs +++ b/OpenDreamClient/Interface/Controls/ControlBrowser.cs @@ -30,7 +30,6 @@ internal sealed class ControlBrowser : InterfaceControl { [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IClientNetManager _netManager = default!; - [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; [Dependency] private readonly IDreamResourceManager _dreamResource = default!; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.browser"); diff --git a/OpenDreamClient/Interface/Controls/ControlButton.cs b/OpenDreamClient/Interface/Controls/ControlButton.cs index 40b9e06b7c..36f1f5f484 100644 --- a/OpenDreamClient/Interface/Controls/ControlButton.cs +++ b/OpenDreamClient/Interface/Controls/ControlButton.cs @@ -7,7 +7,6 @@ namespace OpenDreamClient.Interface.Controls; internal sealed class ControlButton : InterfaceControl { public const string StyleClassDMFButton = "DMFbutton"; - private Button _button; public ControlButton(ControlDescriptor controlDescriptor, ControlWindow window) : base(controlDescriptor, window) { } @@ -36,7 +35,7 @@ private void OnButtonClick(BaseButton.ButtonEventArgs args) { ControlDescriptorButton controlDescriptor = (ControlDescriptorButton)ElementDescriptor; if (controlDescriptor.Command != null) { - EntitySystem.Get().RunCommand(controlDescriptor.Command); + _interfaceManager.RunCommand(controlDescriptor.Command); } } } diff --git a/OpenDreamClient/Interface/Controls/ControlChild.cs b/OpenDreamClient/Interface/Controls/ControlChild.cs index d6cc930e23..39c7ce5a2c 100644 --- a/OpenDreamClient/Interface/Controls/ControlChild.cs +++ b/OpenDreamClient/Interface/Controls/ControlChild.cs @@ -8,7 +8,6 @@ internal sealed class ControlChild : InterfaceControl { // todo: robust needs GridSplitter. // and a non-shit grid control. - [Dependency] private readonly IDreamInterfaceManager _dreamInterface = default!; private ControlDescriptorChild ChildDescriptor => (ControlDescriptorChild)ElementDescriptor; @@ -26,10 +25,10 @@ protected override Control CreateUIElement() { protected override void UpdateElementDescriptor() { base.UpdateElementDescriptor(); - var newLeftElement = ChildDescriptor.Left != null && _dreamInterface.Windows.TryGetValue(ChildDescriptor.Left, out var leftWindow) + var newLeftElement = ChildDescriptor.Left != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Left, out var leftWindow) ? leftWindow.UIElement : null; - var newRightElement = ChildDescriptor.Right != null && _dreamInterface.Windows.TryGetValue(ChildDescriptor.Right, out var rightWindow) + var newRightElement = ChildDescriptor.Right != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Right, out var rightWindow) ? rightWindow.UIElement : null; @@ -64,18 +63,19 @@ protected override void UpdateElementDescriptor() { _grid.Children.Add(_rightElement); } + if(_leftElement is not null) - _leftElement.SetPositionFirst(); - if(_rightElement is not null) - _rightElement.SetPositionLast(); + _leftElement.SetPositionInParent(0); + if (_rightElement is not null) + _rightElement.SetPositionInParent(1); UpdateGrid(); } public override void Shutdown() { - if (ChildDescriptor.Left != null && _dreamInterface.Windows.TryGetValue(ChildDescriptor.Left, out var left)) + if (ChildDescriptor.Left != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Left, out var left)) left.Shutdown(); - if (ChildDescriptor.Right != null && _dreamInterface.Windows.TryGetValue(ChildDescriptor.Right, out var right)) + if (ChildDescriptor.Right != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Right, out var right)) right.Shutdown(); } diff --git a/OpenDreamClient/Interface/Controls/ControlInfo.cs b/OpenDreamClient/Interface/Controls/ControlInfo.cs index a55a390b56..a6f3bce85b 100644 --- a/OpenDreamClient/Interface/Controls/ControlInfo.cs +++ b/OpenDreamClient/Interface/Controls/ControlInfo.cs @@ -176,7 +176,7 @@ public void RefreshVerbs() { verbButton.Label.Margin = new Thickness(6, 0, 6, 2); verbButton.OnPressed += _ => { - EntitySystem.Get().RunCommand(verbId); + _dreamInterface.RunCommand(verbId); }; _grid.Children.Add(verbButton); diff --git a/OpenDreamClient/Interface/Controls/ControlInput.cs b/OpenDreamClient/Interface/Controls/ControlInput.cs index 29a1aa58c0..8059555dfb 100644 --- a/OpenDreamClient/Interface/Controls/ControlInput.cs +++ b/OpenDreamClient/Interface/Controls/ControlInput.cs @@ -7,7 +7,6 @@ namespace OpenDreamClient.Interface.Controls; internal sealed class ControlInput : InterfaceControl { private LineEdit _textBox; - public ControlInput(ControlDescriptor controlDescriptor, ControlWindow window) : base(controlDescriptor, window) { } protected override Control CreateUIElement() { @@ -18,7 +17,7 @@ protected override Control CreateUIElement() { } private void TextBox_OnSubmit(LineEdit.LineEditEventArgs lineEditEventArgs) { - EntitySystem.Get().RunCommand(_textBox.Text); + _interfaceManager.RunCommand(_textBox.Text); _textBox.Clear(); } } diff --git a/OpenDreamClient/Interface/Controls/ControlMap.cs b/OpenDreamClient/Interface/Controls/ControlMap.cs index c163b1d364..75e2689fbf 100644 --- a/OpenDreamClient/Interface/Controls/ControlMap.cs +++ b/OpenDreamClient/Interface/Controls/ControlMap.cs @@ -11,11 +11,25 @@ public sealed class ControlMap : InterfaceControl { public ScalingViewport Viewport { get; private set; } [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - [Dependency] private readonly IDreamInterfaceManager _dreamInterfaceManager = default!; private MouseInputSystem _mouseInput; public ControlMap(ControlDescriptor controlDescriptor, ControlWindow window) : base(controlDescriptor, window) { } + protected override void UpdateElementDescriptor() { + base.UpdateElementDescriptor(); + + ControlDescriptorMap mapDescriptor = (ControlDescriptorMap)ElementDescriptor; + + Viewport.StretchMode = mapDescriptor.ZoomMode switch { + "blur" => ScalingViewportStretchMode.Bilinear, + "distort" => ScalingViewportStretchMode.Nearest, + + // TODO: "tries to keep the look of individual pixels, + // but will adjust to non-integer zooms (like 1.1x) by blending neighboring pixels" + "normal" or _ => ScalingViewportStretchMode.Nearest + }; + } + public void UpdateViewRange(ViewRange view) { Viewport.ViewportSize = (Math.Max(view.Width, 1) * 32, Math.Max(view.Height, 1) * 32); } @@ -23,8 +37,19 @@ public void UpdateViewRange(ViewRange view) { protected override Control CreateUIElement() { Viewport = new ScalingViewport { MouseFilter = Control.MouseFilterMode.Stop }; Viewport.OnKeyBindDown += OnViewportKeyBindDown; + Viewport.OnVisibilityChanged += (args) => { + if (args.Visible) { + OnShowEvent(); + } else { + OnHideEvent(); + } + }; + if(ControlDescriptor.IsVisible) + OnShowEvent(); + else + OnHideEvent(); - UpdateViewRange(_dreamInterfaceManager.View); + UpdateViewRange(_interfaceManager.View); return new PanelContainer { StyleClasses = {"MapBackground"}, Children = { Viewport } }; } @@ -39,4 +64,18 @@ private void OnViewportKeyBindDown(GUIBoundKeyEventArgs e) { } } } + + public void OnShowEvent() { + ControlDescriptorMap controlDescriptor = (ControlDescriptorMap)ControlDescriptor; + if (controlDescriptor.OnShowCommand != null) { + _interfaceManager.RunCommand(controlDescriptor.OnShowCommand); + } + } + + public void OnHideEvent() { + ControlDescriptorMap controlDescriptor = (ControlDescriptorMap)ControlDescriptor; + if (controlDescriptor.OnHideCommand != null) { + _interfaceManager.RunCommand(controlDescriptor.OnHideCommand); + } + } } diff --git a/OpenDreamClient/Interface/Controls/ControlWindow.cs b/OpenDreamClient/Interface/Controls/ControlWindow.cs index aef84ebe0b..378f7caaa6 100644 --- a/OpenDreamClient/Interface/Controls/ControlWindow.cs +++ b/OpenDreamClient/Interface/Controls/ControlWindow.cs @@ -8,7 +8,6 @@ namespace OpenDreamClient.Interface.Controls; public sealed class ControlWindow : InterfaceControl { [Dependency] private readonly IUserInterfaceManager _uiMgr = default!; - [Dependency] private readonly IDreamInterfaceManager _dreamInterface = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private readonly ISawmill _sawmill = Logger.GetSawmill("opendream.window"); @@ -19,7 +18,7 @@ public sealed class ControlWindow : InterfaceControl { public readonly List ChildControls = new(); - public InterfaceMacroSet Macro => _dreamInterface.MacroSets[WindowDescriptor.Macro]; + public InterfaceMacroSet Macro => _interfaceManager.MacroSets[WindowDescriptor.Macro]; private WindowDescriptor WindowDescriptor => (WindowDescriptor)ElementDescriptor; @@ -35,7 +34,7 @@ protected override void UpdateElementDescriptor() { // Don't call base.UpdateElementDescriptor(); _menuContainer.RemoveAllChildren(); - if (WindowDescriptor.Menu != null && _dreamInterface.Menus.TryGetValue(WindowDescriptor.Menu, out var menu)) { + if (WindowDescriptor.Menu != null && _interfaceManager.Menus.TryGetValue(WindowDescriptor.Menu, out var menu)) { _menuContainer.AddChild(menu.MenuBar); _menuContainer.Visible = true; } else { @@ -63,8 +62,8 @@ public OSWindow CreateWindow() { window.SetHeight = window.MaxHeight; window.Closing += _ => { // A window can have a command set to be run when it's closed - if (WindowDescriptor.OnClose != null && _entitySystemManager.TryGetEntitySystem(out DreamCommandSystem? commandSystem)) { - commandSystem.RunCommand(WindowDescriptor.OnClose); + if (WindowDescriptor.OnClose != null) { + _interfaceManager.RunCommand(WindowDescriptor.OnClose); } _openWindows.Remove((window, null)); diff --git a/OpenDreamClient/Interface/DMF/DMFParser.cs b/OpenDreamClient/Interface/DMF/DMFParser.cs index 67a34b47b4..1f6be372b9 100644 --- a/OpenDreamClient/Interface/DMF/DMFParser.cs +++ b/OpenDreamClient/Interface/DMF/DMFParser.cs @@ -188,8 +188,10 @@ private bool TryGetAttribute(out string? element, [NotNullWhen(true)] out string Token attributeToken = Current(); if (Check(_attributeTokenTypes)) { - if (Check(TokenType.DMF_Period)) { // element.attribute=value - element = attributeToken.Text; + while(Check(TokenType.DMF_Period)) { // element.attribute=value + element ??= ""; + if(element.Length > 0) element += "."; + element += attributeToken.Text; attributeToken = Current(); if (!Check(_attributeTokenTypes)) { diff --git a/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs b/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs index 85612b8c12..5c5974fa2e 100644 --- a/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs +++ b/OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs @@ -11,11 +11,10 @@ namespace OpenDreamClient.Interface.DebugWindows; public sealed class MacrosWindow : OSWindow { [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - private readonly DreamCommandSystem _commandSystem; + public MacrosWindow() { IoCManager.InjectDependencies(this); - _commandSystem = _entitySystemManager.GetEntitySystem(); Title = "Macros"; SizeToContent = WindowSizeToContent.WidthAndHeight; @@ -60,13 +59,13 @@ private GridContainer CreateMacroTable(InterfaceMacroSet macroSet) { if (value == null) return; // Cancelled - _commandSystem.RunCommand(macro.Command.Replace("[[*]]", (string)value)); + _interfaceManager.RunCommand(macro.Command.Replace("[[*]]", (string)value)); }); prompt.Owner = ClydeWindow; prompt.Show(); } else { - _commandSystem.RunCommand(macro.Command); + _interfaceManager.RunCommand(macro.Command); } }; diff --git a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs index 748150c46d..5da523cc61 100644 --- a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs +++ b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs @@ -97,6 +97,8 @@ public override ElementDescriptor CreateCopy(ISerializationManager serialization var copy = serializationManager.CreateCopy(this, notNullableOverride: true); copy._id = id; + foreach(var child in this.ControlDescriptors) + copy.ControlDescriptors.Add(serializationManager.CreateCopy(child, notNullableOverride: false)); return copy; } @@ -138,6 +140,12 @@ public sealed partial class ControlDescriptorInfo : ControlDescriptor { } public sealed partial class ControlDescriptorMap : ControlDescriptor { + [DataField("on-show")] + public string? OnShowCommand; + [DataField("on-hide")] + public string? OnHideCommand; + [DataField("zoom-mode")] + public string ZoomMode = "normal"; } public sealed partial class ControlDescriptorBrowser : ControlDescriptor { diff --git a/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs b/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs index 555b3c638d..c908df301a 100644 --- a/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs +++ b/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs @@ -48,6 +48,14 @@ public string? Category { [DataField("can-check")] public bool CanCheck { get; private set; } + [DataField("is-checked")] + public bool IsChecked { get; set; } + + [DataField("group")] + public string? Group { get; private set; } + [DataField("index")] + public int Index { get; private set; } + public MenuElementDescriptor WithCategory(ISerializationManager serialization, string category) { var copy = serialization.CreateCopy(this, notNullableOverride: true); diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index ec1cfe9ec8..6110be3052 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -434,6 +434,47 @@ public void SaveScreenshot(bool openDialog) { }); } + public void RunCommand(string command){ + switch (command) { + case string x when x.StartsWith(".quit"): + IoCManager.Resolve().ClientDisconnect(".quit used"); + break; + + case string x when x.StartsWith(".screenshot"): + string[] split = command.Split(" "); + SaveScreenshot(split.Length == 1 || split[1] != "auto"); + break; + + case string x when x.StartsWith(".configure"): + _sawmill.Warning(".configure command is not implemented"); + break; + + case string x when x.StartsWith(".winset"): + // Everything after .winset, excluding the space and quotes + string winsetParams = command.Substring(7); //clip .winset + winsetParams = winsetParams.Trim(); //clip space + winsetParams = winsetParams.Trim('\"'); //clip quotes + + WinSet(null, winsetParams); + break; + + default: { + // Send the entire command to the server. + // It has more info about argument types so it can parse it better than we can. + _netManager.ClientSendMessage(new MsgCommand(){Command = command}); + break; + } + } + } + + public void StartRepeatingCommand(string command) { + _netManager.ClientSendMessage(new MsgCommandRepeatStart(){Command = command}); + } + + public void StopRepeatingCommand(string command) { + _netManager.ClientSendMessage(new MsgCommandRepeatStop(){Command = command}); + } + public void WinSet(string? controlId, string winsetParams) { DMFLexer lexer = new DMFLexer($"winset({controlId}, \"{winsetParams}\")", winsetParams); DMFParser parser = new DMFParser(lexer, _serializationManager); @@ -470,9 +511,7 @@ bool CheckParserErrors() { if (elementId == null) { if (winSet.Attribute == "command") { - DreamCommandSystem commandSystem = _entitySystemManager.GetEntitySystem(); - - commandSystem.RunCommand(winSet.Value); + RunCommand(winSet.Value); } else { _sawmill.Error($"Invalid global winset \"{winsetParams}\""); } @@ -485,7 +524,7 @@ bool CheckParserErrors() { if (element != null) { element.PopulateElementDescriptor(node, _serializationManager); } else { - _sawmill.Error($"Invalid element \"{controlId}\""); + _sawmill.Error($"Invalid element \"{elementId}\""); } } } @@ -605,6 +644,9 @@ public void WinClone(string controlId, string cloneId) { } LoadDescriptor(elementDescriptor); + if(elementDescriptor is WindowDescriptor && Windows.TryGetValue(cloneId, out var window)){ + window.CreateChildControls(); + } } private void LoadInterface(InterfaceDescriptor descriptor) { @@ -698,5 +740,9 @@ public interface IDreamInterfaceManager { InterfaceElement? FindElementWithId(string id); void SaveScreenshot(bool openDialog); void LoadInterfaceFromSource(string source); + + void RunCommand(string command); + void StartRepeatingCommand(string command); + void StopRepeatingCommand(string command); void WinSet(string? controlId, string winsetParams); } diff --git a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs index 0261317cde..16c167f0dc 100644 --- a/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DummyDreamInterfaceManager.cs @@ -43,4 +43,16 @@ public void LoadInterfaceFromSource(string source) { public void WinSet(string? controlId, string winsetParams) { } + + public void RunCommand(string command) { + + } + + public void StartRepeatingCommand(string command) { + + } + + public void StopRepeatingCommand(string command) { + + } } diff --git a/OpenDreamClient/Interface/InterfaceElement.cs b/OpenDreamClient/Interface/InterfaceElement.cs index 5f01f8adb5..3068e8bb9b 100644 --- a/OpenDreamClient/Interface/InterfaceElement.cs +++ b/OpenDreamClient/Interface/InterfaceElement.cs @@ -10,20 +10,26 @@ public class InterfaceElement { public string Id => ElementDescriptor.Id; public ElementDescriptor ElementDescriptor; - + [Dependency] protected readonly IDreamInterfaceManager _interfaceManager = default!; protected InterfaceElement(ElementDescriptor elementDescriptor) { ElementDescriptor = elementDescriptor; + IoCManager.InjectDependencies(this); } public void PopulateElementDescriptor(MappingDataNode node, ISerializationManager serializationManager) { - MappingDataNode original = (MappingDataNode)serializationManager.WriteValue(ElementDescriptor.GetType(), ElementDescriptor); - foreach (var key in node.Keys) { - original.Remove(key); + try { + MappingDataNode original = + (MappingDataNode)serializationManager.WriteValue(ElementDescriptor.GetType(), ElementDescriptor); + foreach (var key in node.Keys) { + original.Remove(key); + } + + MappingDataNode newNode = original.Merge(node); + ElementDescriptor = (ElementDescriptor)serializationManager.Read(ElementDescriptor.GetType(), newNode); + UpdateElementDescriptor(); + } catch (Exception e) { + Logger.GetSawmill("opendream.interface").Error($"Error while populating values of \"{Id}\": {e}"); } - - MappingDataNode newNode = original.Merge(node); - ElementDescriptor = (ElementDescriptor)serializationManager.Read(ElementDescriptor.GetType(), newNode); - UpdateElementDescriptor(); } /// diff --git a/OpenDreamClient/Interface/InterfaceMacro.cs b/OpenDreamClient/Interface/InterfaceMacro.cs index 8070956280..b3f4044ca0 100644 --- a/OpenDreamClient/Interface/InterfaceMacro.cs +++ b/OpenDreamClient/Interface/InterfaceMacro.cs @@ -224,9 +224,8 @@ public static ParsedKeybind Parse(string keybind) { public sealed class InterfaceMacro : InterfaceElement { public string Command => MacroDescriptor.Command; - - private MacroDescriptor MacroDescriptor => (MacroDescriptor)ElementDescriptor; + private MacroDescriptor MacroDescriptor => (MacroDescriptor)ElementDescriptor; private readonly IEntitySystemManager _entitySystemManager; private readonly IUserInterfaceManager _uiManager; private readonly IInputCmdContext _inputContext; @@ -293,14 +292,14 @@ private void FirstChanceKeyHandler(KeyEventArgs args, KeyEventType type) { return; } - if (_entitySystemManager.TryGetEntitySystem(out DreamCommandSystem? commandSystem)) { - string? keyName = ParsedKeybind.KeyToKeyName(args.Key); - if (keyName == null) - return; - string command = Command.Replace("[[*]]", keyName); - commandSystem.RunCommand(command); - // args.Handle() omitted on purpose, in BYOND both the "specific" keybind and the ANY keybind are triggered - } + + string? keyName = ParsedKeybind.KeyToKeyName(args.Key); + if (keyName == null) + return; + string command = Command.Replace("[[*]]", keyName); + _interfaceManager.RunCommand(command); + // args.Handle() omitted on purpose, in BYOND both the "specific" keybind and the ANY keybind are triggered + } private void OnMacroPress(ICommonSession? session) { @@ -309,26 +308,26 @@ private void OnMacroPress(ICommonSession? session) { if (_isRelease) return; - if (_entitySystemManager.TryGetEntitySystem(out DreamCommandSystem? commandSystem)) { - if (_isRepeating) { - commandSystem.StartRepeatingCommand(Command); - } else { - commandSystem.RunCommand(Command); - } + + if (_isRepeating) { + _interfaceManager.StartRepeatingCommand(Command); + } else { + _interfaceManager.RunCommand(Command); } + } private void OnMacroRelease(ICommonSession? session) { if (string.IsNullOrEmpty(Command)) return; - if (_entitySystemManager.TryGetEntitySystem(out DreamCommandSystem? commandSystem)) { - if (_isRepeating) { - commandSystem.StopRepeatingCommand(Command); - } else if (_isRelease) { - commandSystem.RunCommand(Command); - } + + if (_isRepeating) { + _interfaceManager.StopRepeatingCommand(Command); + } else if (_isRelease) { + _interfaceManager.RunCommand(Command); } + } private static KeyBindingRegistration? CreateMacroBinding(BoundKeyFunction function, ParsedKeybind keybind) { diff --git a/OpenDreamClient/Interface/InterfaceMenu.cs b/OpenDreamClient/Interface/InterfaceMenu.cs index cd98515bad..86caf74745 100644 --- a/OpenDreamClient/Interface/InterfaceMenu.cs +++ b/OpenDreamClient/Interface/InterfaceMenu.cs @@ -23,6 +23,17 @@ public InterfaceMenu(MenuDescriptor descriptor) : base(descriptor) { CreateMenu(); } + public void SetGroupChecked(string group, string id) { + foreach (MenuElement menuElement in MenuElements.Values) { + if (menuElement.ElementDescriptor is not MenuElementDescriptor menuElementDescriptor) + continue; + + if (menuElementDescriptor.Group == group) { + menuElementDescriptor.IsChecked = menuElementDescriptor.Id == id; + } + } + } + public override void AddChild(ElementDescriptor descriptor) { if (descriptor is not MenuElementDescriptor elementDescriptor) throw new ArgumentException($"Attempted to add a {descriptor} to a menu", nameof(descriptor)); @@ -80,7 +91,6 @@ public sealed class MenuElement : InterfaceElement { private MenuElementDescriptor MenuElementDescriptor => (MenuElementDescriptor) ElementDescriptor; public string? Category => MenuElementDescriptor.Category; public string Command => MenuElementDescriptor.Command; - private readonly InterfaceMenu _menu; public MenuElement(MenuElementDescriptor data, InterfaceMenu menu) : base(data) { @@ -106,16 +116,53 @@ public MenuBar.MenuEntry CreateMenuEntry() { if (String.IsNullOrEmpty(text)) return new MenuBar.MenuSeparator(); + if(MenuElementDescriptor.CanCheck) + if(MenuElementDescriptor.IsChecked) + text = text + " ☑"; + MenuBar.MenuButton menuButton = new() { Text = text }; - //result.IsCheckable = MenuElementDescriptor.CanCheck; - if (!String.IsNullOrEmpty(Command)) - menuButton.OnPressed += () => { EntitySystem.Get().RunCommand(Command); }; + + menuButton.OnPressed += () => { + if(MenuElementDescriptor.CanCheck) + if(!String.IsNullOrEmpty(MenuElementDescriptor.Group)) + _menu.SetGroupChecked(MenuElementDescriptor.Group, MenuElementDescriptor.Id); + else + MenuElementDescriptor.IsChecked = !MenuElementDescriptor.IsChecked; + _menu.CreateMenu(); + if(!string.IsNullOrEmpty(MenuElementDescriptor.Command)) + _interfaceManager.RunCommand(Command); + }; return menuButton; } + public override bool TryGetProperty(string property, out string value) { + switch (property) { + case "command": + value = Command; + return true; + case "category": + value = Category ?? ""; + return true; + case "can-check": + value = MenuElementDescriptor.CanCheck.ToString(); + return true; + case "is-checked": + value = MenuElementDescriptor.IsChecked.ToString(); + return true; + case "group": + value = MenuElementDescriptor.Group ?? ""; + return true; + case "index": + value = MenuElementDescriptor.Index.ToString(); + return true; + default: + return base.TryGetProperty(property, out value); + } + } + public override void AddChild(ElementDescriptor descriptor) { // Set the child's category to this element // TODO: The "parent" and "category" attributes seem to be treated differently in BYOND; not the same thing. diff --git a/OpenDreamClient/Rendering/DMISpriteComponent.cs b/OpenDreamClient/Rendering/DMISpriteComponent.cs index 883e283192..fede2a08c7 100644 --- a/OpenDreamClient/Rendering/DMISpriteComponent.cs +++ b/OpenDreamClient/Rendering/DMISpriteComponent.cs @@ -1,43 +1,40 @@ using OpenDreamShared.Dream; using OpenDreamShared.Rendering; -using Robust.Client.GameObjects; -using Robust.Shared.Map; - -namespace OpenDreamClient.Rendering { - [RegisterComponent] - [ComponentReference(typeof(SharedDMISpriteComponent))] - internal sealed partial class DMISpriteComponent : SharedDMISpriteComponent { - [ViewVariables] public DreamIcon Icon { get; set; } = new DreamIcon(); - [ViewVariables] public ScreenLocation? ScreenLocation { get; set; } - - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEntitySystemManager _entitySystemMan = default!; - private EntityLookupSystem? _lookupSystem; - - public DMISpriteComponent() { - Icon.SizeChanged += OnIconSizeChanged; - } - public bool IsVisible(bool checkWorld = true, int seeInvis = 0) { - if (Icon.Appearance?.Invisibility > seeInvis) return false; +namespace OpenDreamClient.Rendering; - if (checkWorld) { - //Only render movables not inside another movable's contents (parented to the grid) - //TODO: Use RobustToolbox's container system/components? - if (!_entityManager.TryGetComponent(Owner, out var transform)) - return false; +[RegisterComponent] +internal sealed partial class DMISpriteComponent : SharedDMISpriteComponent { + [ViewVariables] public DreamIcon Icon { get; set; } = new DreamIcon(); + [ViewVariables] public ScreenLocation? ScreenLocation { get; set; } - if (transform.ParentUid != transform.GridUid) - return false; - } + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemMan = default!; + private EntityLookupSystem? _lookupSystem; - return true; - } + public DMISpriteComponent() { + Icon.SizeChanged += OnIconSizeChanged; + } + + public bool IsVisible(bool checkWorld = true, int seeInvis = 0) { + if (Icon.Appearance?.Invisibility > seeInvis) return false; - private void OnIconSizeChanged() { - _entityManager.TryGetComponent(Owner, out var transform); - _lookupSystem ??= _entitySystemMan.GetEntitySystem(); - _lookupSystem?.FindAndAddToEntityTree(Owner, xform: transform); + if (checkWorld) { + //Only render movables not inside another movable's contents (parented to the grid) + //TODO: Use RobustToolbox's container system/components? + if (!_entityManager.TryGetComponent(Owner, out var transform)) + return false; + + if (transform.ParentUid != transform.GridUid) + return false; } + + return true; + } + + private void OnIconSizeChanged() { + _entityManager.TryGetComponent(Owner, out var transform); + _lookupSystem ??= _entitySystemMan.GetEntitySystem(); + _lookupSystem?.FindAndAddToEntityTree(Owner, xform: transform); } } diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 7e4029b864..ce6dd1c04e 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -253,6 +253,15 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u renderTargetPlaceholder.Layer = current.Layer; renderTargetPlaceholder.RenderSource = current.RenderTarget; renderTargetPlaceholder.MouseOpacity = current.MouseOpacity; + if((current.AppearanceFlags & AppearanceFlags.PlaneMaster) != 0){ //Plane masters with render targets get special handling + renderTargetPlaceholder.TransformToApply = current.TransformToApply; + renderTargetPlaceholder.ColorToApply = current.ColorToApply; + renderTargetPlaceholder.ColorMatrixToApply = current.ColorMatrixToApply; + renderTargetPlaceholder.AlphaToApply = current.AlphaToApply; + renderTargetPlaceholder.BlendMode = current.BlendMode; + } + renderTargetPlaceholder.AppearanceFlags = current.AppearanceFlags; + current.AppearanceFlags = current.AppearanceFlags & ~AppearanceFlags.PlaneMaster; //only the placeholder should be marked as master result.Add(renderTargetPlaceholder); } diff --git a/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs b/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs index 6fff63b762..f11ec9be97 100644 --- a/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs +++ b/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs @@ -6,37 +6,33 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; -namespace OpenDreamClient.States.Connecting -{ - [GenerateTypedNameReferences] - public sealed partial class ConnectingControl : Control - { - public ConnectingControl(IResourceCache resCache, IConfigurationManager configMan) - { +namespace OpenDreamClient.States.Connecting; - RobustXamlLoader.Load(this); +[GenerateTypedNameReferences] +public sealed partial class ConnectingControl : Control { + public ConnectingControl(IResourceCache resCache, IConfigurationManager configMan) { + RobustXamlLoader.Load(this); - Panel.PanelOverride = new StyleBoxFlat(Color.Black); + Panel.PanelOverride = new StyleBoxFlat(Color.Black); - ConnectingLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Regular.ttf"), 24); - WIPLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Bold.ttf"), 32); + ConnectingLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Regular.ttf"), 24); + WIPLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Bold.ttf"), 32); - LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); - LayoutContainer.SetAnchorPreset(VBox, LayoutContainer.LayoutPreset.Center); - LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(VBox, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(VBox, LayoutContainer.LayoutPreset.Center); + LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(VBox, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetAnchorPreset(ConnectingLabel, LayoutContainer.LayoutPreset.Center); - LayoutContainer.SetGrowHorizontal(ConnectingLabel, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(ConnectingLabel, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(ConnectingLabel, LayoutContainer.LayoutPreset.Center); + LayoutContainer.SetGrowHorizontal(ConnectingLabel, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(ConnectingLabel, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetAnchorPreset(WIP, LayoutContainer.LayoutPreset.VerticalCenterWide); - LayoutContainer.SetGrowHorizontal(WIP, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(WIP, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(WIP, LayoutContainer.LayoutPreset.VerticalCenterWide); + LayoutContainer.SetGrowHorizontal(WIP, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(WIP, LayoutContainer.GrowDirection.Both); - var logoTexture = resCache.GetResource("/OpenDream/Logo/logo.png"); - Logo.Texture = logoTexture; - } + var logoTexture = resCache.GetResource("/OpenDream/Logo/logo.png"); + Logo.Texture = logoTexture; } } diff --git a/OpenDreamClient/States/Connecting/ConnectingState.cs b/OpenDreamClient/States/Connecting/ConnectingState.cs index b8e35c93e1..2a521e4b5b 100644 --- a/OpenDreamClient/States/Connecting/ConnectingState.cs +++ b/OpenDreamClient/States/Connecting/ConnectingState.cs @@ -3,25 +3,21 @@ using Robust.Client.UserInterface; using Robust.Shared.Configuration; -namespace OpenDreamClient.States.Connecting -{ - public sealed class ConnectingState : State - { - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; +namespace OpenDreamClient.States.Connecting; - private ConnectingControl _connectingControl = default!; +public sealed class ConnectingState : State { + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; - protected override void Startup() - { - _connectingControl = new ConnectingControl(_resourceCache, _configurationManager); - _userInterfaceManager.StateRoot.AddChild(_connectingControl); - } + private ConnectingControl _connectingControl = default!; - protected override void Shutdown() - { - _connectingControl.Dispose(); - } + protected override void Startup() { + _connectingControl = new ConnectingControl(_resourceCache, _configurationManager); + _userInterfaceManager.StateRoot.AddChild(_connectingControl); + } + + protected override void Shutdown() { + _connectingControl.Dispose(); } } diff --git a/OpenDreamClient/States/DreamUserInterfaceStateManager.cs b/OpenDreamClient/States/DreamUserInterfaceStateManager.cs index d8a86a6dc3..609e7b29c6 100644 --- a/OpenDreamClient/States/DreamUserInterfaceStateManager.cs +++ b/OpenDreamClient/States/DreamUserInterfaceStateManager.cs @@ -4,51 +4,45 @@ using Robust.Client; using Robust.Client.State; -namespace OpenDreamClient.States -{ - /// - /// Handles changing the UI state depending on connection status. - /// - [UsedImplicitly] - public sealed class DreamUserInterfaceStateManager - { - [Dependency] private readonly IGameController _gameController = default!; - [Dependency] private readonly IBaseClient _client = default!; - [Dependency] private readonly IStateManager _stateManager = default!; +namespace OpenDreamClient.States; - public void Initialize() - { - _client.RunLevelChanged += ((_, args) => - { - switch (args.NewLevel) - { - case ClientRunLevel.InGame: - case ClientRunLevel.Connected: - case ClientRunLevel.SinglePlayerGame: - _stateManager.RequestStateChange(); - break; - - case ClientRunLevel.Initialize when args.OldLevel < ClientRunLevel.Connected: - _stateManager.RequestStateChange(); - break; +/// +/// Handles changing the UI state depending on connection status. +/// +[UsedImplicitly] +public sealed class DreamUserInterfaceStateManager { + [Dependency] private readonly IGameController _gameController = default!; + [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IStateManager _stateManager = default!; - // When we disconnect from the server: - case ClientRunLevel.Error: - case ClientRunLevel.Initialize when args.OldLevel >= ClientRunLevel.Connected: - if (_gameController.LaunchState.FromLauncher) - { - _stateManager.RequestStateChange(); - break; - } + public void Initialize() { + _client.RunLevelChanged += ((_, args) => { + switch (args.NewLevel) { + case ClientRunLevel.InGame: + case ClientRunLevel.Connected: + case ClientRunLevel.SinglePlayerGame: + _stateManager.RequestStateChange(); + break; - _stateManager.RequestStateChange(); - break; + case ClientRunLevel.Initialize when args.OldLevel < ClientRunLevel.Connected: + _stateManager.RequestStateChange(); + break; - case ClientRunLevel.Connecting: + // When we disconnect from the server: + case ClientRunLevel.Error: + case ClientRunLevel.Initialize when args.OldLevel >= ClientRunLevel.Connected: + if (_gameController.LaunchState.FromLauncher) { _stateManager.RequestStateChange(); break; - } - }); - } + } + + _stateManager.RequestStateChange(); + break; + + case ClientRunLevel.Connecting: + _stateManager.RequestStateChange(); + break; + } + }); } } diff --git a/OpenDreamClient/States/InGameState.cs b/OpenDreamClient/States/InGameState.cs index 5218d7615a..cb5b5430fd 100644 --- a/OpenDreamClient/States/InGameState.cs +++ b/OpenDreamClient/States/InGameState.cs @@ -1,15 +1,11 @@ using Robust.Client.State; -namespace OpenDreamClient.States -{ - public sealed class InGameState : State - { - protected override void Startup() - { - } +namespace OpenDreamClient.States; - protected override void Shutdown() - { - } +public sealed class InGameState : State { + protected override void Startup() { + } + + protected override void Shutdown() { } } diff --git a/OpenDreamClient/States/MainMenu/MainMenuControl.xaml b/OpenDreamClient/States/MainMenu/MainMenuControl.xaml index ce59f5801a..2dc42010df 100644 --- a/OpenDreamClient/States/MainMenu/MainMenuControl.xaml +++ b/OpenDreamClient/States/MainMenu/MainMenuControl.xaml @@ -6,33 +6,45 @@ Orientation="Vertical"> - - - - -