From f24e8f9c0dbb72b2dcfb965b2333cc28c4b44998 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 29 Oct 2024 21:51:54 -0500 Subject: [PATCH 1/3] Halves `OpenDreamClient` warnings (#2032) Co-authored-by: ike709 --- OpenDreamClient/EntryPoint.cs | 1 - .../ContextMenu/ContextMenuPopup.xaml.cs | 2 +- OpenDreamClient/Interface/BrowsePopup.cs | 8 +-- .../Interface/Controls/ControlChild.cs | 8 +-- .../Interface/Controls/ControlTab.cs | 27 ++++---- .../Interface/Controls/ControlWindow.cs | 4 +- OpenDreamClient/Interface/DMF/IDMFProperty.cs | 63 ++++++++++++++----- .../Descriptors/InterfaceDescriptor.cs | 2 +- .../Interface/DreamInterfaceManager.cs | 2 +- OpenDreamClient/Interface/Html/HtmlParser.cs | 20 +++--- OpenDreamClient/Interface/InterfaceElement.cs | 4 +- OpenDreamClient/OpenDreamClient.csproj | 1 + OpenDreamClient/Rendering/AtomGlideSystem.cs | 7 ++- .../Rendering/ClientImagesSystem.cs | 2 - 14 files changed, 94 insertions(+), 57 deletions(-) diff --git a/OpenDreamClient/EntryPoint.cs b/OpenDreamClient/EntryPoint.cs index b9f70af63c..00e2b13b6c 100644 --- a/OpenDreamClient/EntryPoint.cs +++ b/OpenDreamClient/EntryPoint.cs @@ -22,7 +22,6 @@ namespace OpenDreamClient; public sealed class EntryPoint : GameClient { [Dependency] private readonly IDreamInterfaceManager _dreamInterface = default!; [Dependency] private readonly IDreamResourceManager _dreamResource = default!; - [Dependency] private readonly IDreamSoundEngine _soundEngine = default!; [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly ILightManager _lightManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; diff --git a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs index 9d564cd1f9..42ce42f878 100644 --- a/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs +++ b/OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs @@ -52,7 +52,7 @@ public void RepopulateEntities(ClientObjectReference[] entities, int? turfId) { foreach (var objectReference in entities) { if (objectReference.Type == ClientObjectReference.RefType.Entity) { var entity = _entityManager.GetEntity(objectReference.Entity); - if (_xformQuery.TryGetComponent(entity, out TransformComponent? transform) && !_mapManager.IsGrid(_transformSystem.GetParent(transform)!.Owner)) // Not a child of another entity + if (_xformQuery.TryGetComponent(entity, out TransformComponent? transform) && !_mapManager.IsGrid(_transformSystem.GetParentUid(entity))) // Not a child of another entity continue; if (!_spriteQuery.TryGetComponent(entity, out DMISpriteComponent? sprite)) // Has a sprite continue; diff --git a/OpenDreamClient/Interface/BrowsePopup.cs b/OpenDreamClient/Interface/BrowsePopup.cs index 6504c4ca19..92407d7040 100644 --- a/OpenDreamClient/Interface/BrowsePopup.cs +++ b/OpenDreamClient/Interface/BrowsePopup.cs @@ -7,12 +7,12 @@ namespace OpenDreamClient.Interface; internal sealed class BrowsePopup { - public event Action Closed; + public event Action? Closed; - public ControlBrowser Browser; - public ControlWindow WindowElement; + public readonly ControlBrowser Browser; + public readonly ControlWindow WindowElement; - private OSWindow _window; + private readonly OSWindow _window; public BrowsePopup( string name, diff --git a/OpenDreamClient/Interface/Controls/ControlChild.cs b/OpenDreamClient/Interface/Controls/ControlChild.cs index d00504dc0c..af8ae4d1f0 100644 --- a/OpenDreamClient/Interface/Controls/ControlChild.cs +++ b/OpenDreamClient/Interface/Controls/ControlChild.cs @@ -21,10 +21,10 @@ protected override Control CreateUIElement() { protected override void UpdateElementDescriptor() { base.UpdateElementDescriptor(); - var newLeftElement = ChildDescriptor.Left.Value != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Left.Value, out var leftWindow) + var newLeftElement = _interfaceManager.Windows.TryGetValue(ChildDescriptor.Left.Value, out var leftWindow) ? leftWindow.UIElement : null; - var newRightElement = ChildDescriptor.Right.Value != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Right.Value, out var rightWindow) + var newRightElement = _interfaceManager.Windows.TryGetValue(ChildDescriptor.Right.Value, out var rightWindow) ? rightWindow.UIElement : null; @@ -45,9 +45,9 @@ protected override void UpdateElementDescriptor() { } public override void Shutdown() { - if (ChildDescriptor.Left.Value != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Left.Value, out var left)) + if (_interfaceManager.Windows.TryGetValue(ChildDescriptor.Left.Value, out var left)) left.Shutdown(); - if (ChildDescriptor.Right.Value != null && _interfaceManager.Windows.TryGetValue(ChildDescriptor.Right.Value, out var right)) + if (_interfaceManager.Windows.TryGetValue(ChildDescriptor.Right.Value, out var right)) right.Shutdown(); } diff --git a/OpenDreamClient/Interface/Controls/ControlTab.cs b/OpenDreamClient/Interface/Controls/ControlTab.cs index fd588abbeb..d434c87c94 100644 --- a/OpenDreamClient/Interface/Controls/ControlTab.cs +++ b/OpenDreamClient/Interface/Controls/ControlTab.cs @@ -25,20 +25,19 @@ protected override void UpdateElementDescriptor() { _tabs.Clear(); _tab.RemoveAllChildren(); - if (TabDescriptor.Tabs.Value != null) { - var tabIds = TabDescriptor.Tabs.Value.Split(',', - StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - - foreach (var tabId in tabIds) { - if (!_interfaceManager.Windows.TryGetValue(tabId, out var pane)) - continue; - - TabContainer.SetTabTitle(pane.UIElement, pane.Title); - _tab.AddChild(pane.UIElement); - _tabs.Add(pane); - if (TabDescriptor.CurrentTab.Value == pane.Title) - _tab.CurrentTab = pane.UIElement.GetPositionInParent(); - } + + var tabIds = TabDescriptor.Tabs.Value.Split(',', + StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + foreach (var tabId in tabIds) { + if (!_interfaceManager.Windows.TryGetValue(tabId, out var pane)) + continue; + + TabContainer.SetTabTitle(pane.UIElement, pane.Title); + _tab.AddChild(pane.UIElement); + _tabs.Add(pane); + if (TabDescriptor.CurrentTab.Value == pane.Title) + _tab.CurrentTab = pane.UIElement.GetPositionInParent(); } } diff --git a/OpenDreamClient/Interface/Controls/ControlWindow.cs b/OpenDreamClient/Interface/Controls/ControlWindow.cs index a0780bdb9a..ea29a7358d 100644 --- a/OpenDreamClient/Interface/Controls/ControlWindow.cs +++ b/OpenDreamClient/Interface/Controls/ControlWindow.cs @@ -19,7 +19,7 @@ public sealed class ControlWindow : InterfaceControl { public readonly List ChildControls = new(); - public string Title => WindowDescriptor.Title.Value ?? (WindowDescriptor.IsDefault.Value ? "OpenDream World" : WindowDescriptor.Id.AsRaw()); + public string Title => WindowDescriptor.Title.Value; public InterfaceMacroSet Macro => _interfaceManager.MacroSets[WindowDescriptor.Macro.AsRaw()]; private WindowDescriptor WindowDescriptor => (WindowDescriptor)ElementDescriptor; @@ -37,7 +37,7 @@ protected override void UpdateElementDescriptor() { // Don't call base.UpdateElementDescriptor(); _menuContainer.RemoveAllChildren(); - if (WindowDescriptor.Menu.Value != null && _interfaceManager.Menus.TryGetValue(WindowDescriptor.Menu.Value, out var menu)) { + if (_interfaceManager.Menus.TryGetValue(WindowDescriptor.Menu.Value, out var menu)) { _menuContainer.AddChild(menu.MenuBar); _menuContainer.Visible = true; } else { diff --git a/OpenDreamClient/Interface/DMF/IDMFProperty.cs b/OpenDreamClient/Interface/DMF/IDMFProperty.cs index c137a3bafe..661ea118fa 100644 --- a/OpenDreamClient/Interface/DMF/IDMFProperty.cs +++ b/OpenDreamClient/Interface/DMF/IDMFProperty.cs @@ -18,7 +18,7 @@ public interface IDMFProperty { public string AsJson(); public string AsJsonDM(); public string AsRaw(); - + /// winget() calls do not act the same as embedded winget calls, and the default behaviour is some whacky combination of AsEscaped() and AsRaw(). This proc handles that. Fucking BYOND. public string AsSnowflake(); } @@ -41,15 +41,13 @@ Value is formatted as a DM-escaped string with surrounding quotes. */ public struct DMFPropertyString(string value) : IDMFProperty { - public string? Value = value; + public string Value = value; public string AsArg() { - return Value != null ? "\""+AsEscaped()+"\"" : "\"\""; + return "\""+AsEscaped()+"\""; } public string AsEscaped() { - if(Value == null) - return ""; return Value .Replace("\\", "\\\\") .Replace("\"", "\\\""); @@ -60,10 +58,7 @@ public string AsString() { } public string AsParams() { - if(Value == null) - return ""; - else - return System.Web.HttpUtility.UrlEncode(Value); + return System.Web.HttpUtility.UrlEncode(Value); } public string AsJson() { @@ -80,13 +75,13 @@ public string AsJsonDM() { } public string AsRaw() { - return Value ?? ""; + return Value; } public string AsSnowflake() { return AsRaw(); } - + public override string ToString() { return AsRaw(); } @@ -140,7 +135,7 @@ public string AsRaw() { public string AsSnowflake() { return AsRaw(); } - + public override string ToString() { return AsRaw(); } @@ -151,7 +146,7 @@ public bool Equals(string comparison) { } } -public struct DMFPropertyVec2 : IDMFProperty { +public struct DMFPropertyVec2 : IDMFProperty, IEquatable { public int X; public int Y; public char Delim = ','; @@ -226,9 +221,21 @@ public bool Equals(string comparison) { DMFPropertyVec2 comparisonVec = new(comparison); return comparisonVec.X == X && comparisonVec.Y == Y; } + + public bool Equals(DMFPropertyVec2 other) { + return X == other.X && Y == other.Y && Delim == other.Delim; + } + + public override bool Equals(object? obj) { + return obj is DMFPropertyVec2 other && Equals(other); + } + + public override int GetHashCode() { + return HashCode.Combine(X, Y, Delim); + } } -public struct DMFPropertySize : IDMFProperty { +public struct DMFPropertySize : IDMFProperty, IEquatable { private DMFPropertyVec2 _value; public int X {get => _value.X; set => _value.X = value;} public int Y {get => _value.Y; set => _value.Y = value;} @@ -295,11 +302,23 @@ public bool Equals(string comparison) { return _value.Equals(comparison); } + public bool Equals(DMFPropertySize other) { + return _value.Equals(other._value); + } + + public override bool Equals(object? obj) { + return obj is DMFPropertySize other && Equals(other); + } + + public override int GetHashCode() { + return _value.GetHashCode(); + } + public static bool operator ==(DMFPropertySize a, DMFPropertySize b) => a.Vector == b.Vector; public static bool operator !=(DMFPropertySize a, DMFPropertySize b) => a.Vector != b.Vector; } -public struct DMFPropertyPos : IDMFProperty { +public struct DMFPropertyPos : IDMFProperty, IEquatable { private DMFPropertyVec2 _value; public int X => _value.X; public int Y => _value.Y; @@ -366,6 +385,18 @@ public bool Equals(string comparison) { return _value.Equals(comparison); } + public bool Equals(DMFPropertyPos other) { + return _value.Equals(other._value); + } + + public override bool Equals(object? obj) { + return obj is DMFPropertyPos other && Equals(other); + } + + public override int GetHashCode() { + return _value.GetHashCode(); + } + public static bool operator ==(DMFPropertyPos a, DMFPropertyPos b) => a.Vector == b.Vector; public static bool operator !=(DMFPropertyPos a, DMFPropertyPos b) => a.Vector != b.Vector; } @@ -479,7 +510,7 @@ public string AsRaw() { public string AsSnowflake() { return Value ? "true" : "false"; } - + public override string ToString() { return AsRaw(); } diff --git a/OpenDreamClient/Interface/Descriptors/InterfaceDescriptor.cs b/OpenDreamClient/Interface/Descriptors/InterfaceDescriptor.cs index e15710a253..d60c5c099d 100644 --- a/OpenDreamClient/Interface/Descriptors/InterfaceDescriptor.cs +++ b/OpenDreamClient/Interface/Descriptors/InterfaceDescriptor.cs @@ -38,7 +38,7 @@ public DMFPropertyString Id { init => _id = value; } - public DMFPropertyString Name => new(_name.Value ?? Id.AsRaw()); + public DMFPropertyString Name => new(_name.Value); public DMFPropertyString Type { get => _type; diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index e586ea1490..9e1bde3088 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -444,7 +444,7 @@ public void RunCommand(string fullCommand) { } default: { - string[] argsRaw = fullCommand.Split(' ', 2, StringSplitOptions.TrimEntries); + string[] argsRaw = fullCommand!.Split(' ', 2, StringSplitOptions.TrimEntries); string command = argsRaw[0].ToLowerInvariant(); // Case-insensitive if (!_entitySystemManager.TryGetEntitySystem(out ClientVerbSystem? verbSystem)) diff --git a/OpenDreamClient/Interface/Html/HtmlParser.cs b/OpenDreamClient/Interface/Html/HtmlParser.cs index 6573405366..c2394d8af9 100644 --- a/OpenDreamClient/Interface/Html/HtmlParser.cs +++ b/OpenDreamClient/Interface/Html/HtmlParser.cs @@ -7,6 +7,12 @@ namespace OpenDreamClient.Interface.Html; public static class HtmlParser { private const string TagNotClosedError = "HTML tag was not closed"; + private static readonly ISawmill Sawmill; + + static HtmlParser() { + Sawmill = IoCManager.Resolve().GetSawmill("opendream.html_parser"); + } + public static void Parse(string text, FormattedMessage appendTo) { StringBuilder currentText = new(); Stack tags = new(); @@ -32,7 +38,7 @@ void PushCurrentText() { i++; SkipWhitespace(); if (i >= text.Length) { - Logger.Error(TagNotClosedError); + Sawmill.Error(TagNotClosedError); return; } @@ -51,7 +57,7 @@ void PushCurrentText() { } while (i < text.Length); if (c != '>') { - Logger.Error(TagNotClosedError); + Sawmill.Error(TagNotClosedError); return; } @@ -62,10 +68,10 @@ void PushCurrentText() { currentText.Clear(); if (closingTag) { if (tags.Count == 0) { - Logger.Error("Unexpected closing tag"); + Sawmill.Error("Unexpected closing tag"); return; } else if (tags.Peek() != tagType) { - Logger.Error($"Invalid closing tag , expected "); + Sawmill.Error($"Invalid closing tag , expected "); return; } @@ -88,7 +94,7 @@ void PushCurrentText() { // browsers usually allow for some fallibility here break; } - + string insideEntity = text.Substring(i + 1, end - (i + 1)); i = end; @@ -115,7 +121,7 @@ void PushCurrentText() { break; } } - + break; default: currentText.Append(c); @@ -159,7 +165,7 @@ private static Dictionary ParseAttributes(string[] attr parameter = new(color); break; default: - Logger.Debug($"Unimplemented HTML attribute \"{attributeName}\""); + Sawmill.Debug($"Unimplemented HTML attribute \"{attributeName}\""); continue; } diff --git a/OpenDreamClient/Interface/InterfaceElement.cs b/OpenDreamClient/Interface/InterfaceElement.cs index e1ca16bc85..0508b308ed 100644 --- a/OpenDreamClient/Interface/InterfaceElement.cs +++ b/OpenDreamClient/Interface/InterfaceElement.cs @@ -42,7 +42,9 @@ public void PopulateElementDescriptor(MappingDataNode node, ISerializationManage MappingDataNode newNode = original.Merge(node); - ElementDescriptor = (ElementDescriptor)serializationManager.Read(ElementDescriptor.GetType(), newNode); + var descriptor = serializationManager.Read(ElementDescriptor.GetType(), newNode); + if(descriptor is null) throw new NullReferenceException(); // We're in a try/catch anyway, just play it safe + ElementDescriptor = (ElementDescriptor)descriptor; UpdateElementDescriptor(); } catch (Exception e) { Logger.GetSawmill("opendream.interface").Error($"Error while populating values of \"{Id}\": {e}"); diff --git a/OpenDreamClient/OpenDreamClient.csproj b/OpenDreamClient/OpenDreamClient.csproj index 2ba9bcc9e4..0421e82885 100644 --- a/OpenDreamClient/OpenDreamClient.csproj +++ b/OpenDreamClient/OpenDreamClient.csproj @@ -8,6 +8,7 @@ ..\bin\Content.Client\ Exe enable + NU1507 diff --git a/OpenDreamClient/Rendering/AtomGlideSystem.cs b/OpenDreamClient/Rendering/AtomGlideSystem.cs index ba75f5fc6f..e0c405476c 100644 --- a/OpenDreamClient/Rendering/AtomGlideSystem.cs +++ b/OpenDreamClient/Rendering/AtomGlideSystem.cs @@ -8,7 +8,8 @@ namespace OpenDreamClient.Rendering; /// Disables RobustToolbox's transform lerping and replaces it with our own gliding /// public sealed class AtomGlideSystem : EntitySystem { - private sealed class Glide(TransformComponent transform, DMISpriteComponent sprite) { + private sealed class Glide(EntityUid uid, TransformComponent transform, DMISpriteComponent sprite) { + public readonly EntityUid Uid = uid; public readonly TransformComponent Transform = transform; public readonly DMISpriteComponent Sprite = sprite; public Vector2 EndPos; @@ -82,7 +83,7 @@ public override void FrameUpdate(float frameTime) { } _ignoreMoveEvent = true; - _transformSystem.SetLocalPositionNoLerp(glide.Transform, newPos); + _transformSystem.SetLocalPositionNoLerp(glide.Uid, newPos, glide.Transform); _ignoreMoveEvent = false; } } @@ -123,7 +124,7 @@ private void OnTransformMove(ref MoveEvent e) { } if (glide == null) { - glide = new(e.Component, sprite); + glide = new(e.Sender, e.Component, sprite); _currentGlides.Add(glide); } diff --git a/OpenDreamClient/Rendering/ClientImagesSystem.cs b/OpenDreamClient/Rendering/ClientImagesSystem.cs index 81d702ff9c..7460489e30 100644 --- a/OpenDreamClient/Rendering/ClientImagesSystem.cs +++ b/OpenDreamClient/Rendering/ClientImagesSystem.cs @@ -6,8 +6,6 @@ namespace OpenDreamClient.Rendering; internal sealed class ClientImagesSystem : SharedClientImagesSystem { [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!; private readonly Dictionary> _turfClientImages = new(); private readonly Dictionary> _amClientImages = new(); From 609322e99a927a41072212be9d28dfb88d19549b Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 2 Nov 2024 20:12:36 -0500 Subject: [PATCH 2/3] Bitshift const fold peephole opt (#2060) Co-authored-by: ike709 --- DMCompiler/DM/Builders/DMASTFolder.cs | 14 ----- DMCompiler/Optimizer/PeepholeOptimizations.cs | 53 +++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/DMCompiler/DM/Builders/DMASTFolder.cs b/DMCompiler/DM/Builders/DMASTFolder.cs index ef41916191..b3058f23a6 100644 --- a/DMCompiler/DM/Builders/DMASTFolder.cs +++ b/DMCompiler/DM/Builders/DMASTFolder.cs @@ -219,20 +219,6 @@ private DMASTExpression FoldExpression(DMASTExpression? expression) { break; } - case DMASTLeftShift leftShift: { - if (leftShift is { LHS: DMASTConstantInteger lhsInt, RHS: DMASTConstantInteger rhsInt }) { - return new DMASTConstantInteger(expression.Location, lhsInt.Value << rhsInt.Value); - } - - break; - } - case DMASTRightShift rightShift: { - if (rightShift is { LHS: DMASTConstantInteger lhsInt, RHS: DMASTConstantInteger rhsInt }) { - return new DMASTConstantInteger(expression.Location, lhsInt.Value >> rhsInt.Value); - } - - break; - } case DMASTBinaryAnd binaryAnd: { if (binaryAnd is { LHS: DMASTConstantInteger lhsInt, RHS: DMASTConstantInteger rhsInt }) { return new DMASTConstantInteger(expression.Location, lhsInt.Value & rhsInt.Value); diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs index 72446f68b8..6f5c04d2f7 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizations.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs @@ -775,4 +775,57 @@ public void Apply(List input, int index) { new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } + +// PushFloat [constant] +// PushFloat [constant] +// BitshiftLeft +// -> PushFloat [result] +internal sealed class ConstFoldBitshiftLeft : IPeepholeOptimization { + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushFloat, + DreamProcOpcode.PushFloat, + DreamProcOpcode.BitShiftLeft, + ]; + } + + public void Apply(List input, int index) { + var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + + // At runtime, given "A << B" we pop B then A + // In the peephole optimizer, index is "A", index+1 is "B" + var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 << (int)pushVal2), firstInstruction.Location)}; + + IPeepholeOptimization.ReplaceInstructions(input, index, 3, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); + } +} + +// PushFloat [constant] +// PushFloat [constant] +// BitshiftRight +// -> PushFloat [result] +internal sealed class ConstFoldBitshiftRight : IPeepholeOptimization { + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushFloat, + DreamProcOpcode.PushFloat, + DreamProcOpcode.BitShiftRight, + ]; + } + + public void Apply(List input, int index) { + var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + + // At runtime, given "A >> B" we pop B then A + // In the peephole optimizer, index is "A", index+1 is "B" + var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 >> (int)pushVal2), firstInstruction.Location)}; + + IPeepholeOptimization.ReplaceInstructions(input, index, 3, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); + } +} + #endregion From 7fe3aba8bc94a20c5a24437bc9b672c3f3f4b790 Mon Sep 17 00:00:00 2001 From: ike709 Date: Thu, 7 Nov 2024 13:20:19 -0600 Subject: [PATCH 3/3] Fix DMF property strings being null (#2074) --- OpenDreamClient/Interface/DMF/IDMFProperty.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenDreamClient/Interface/DMF/IDMFProperty.cs b/OpenDreamClient/Interface/DMF/IDMFProperty.cs index 661ea118fa..136e7da826 100644 --- a/OpenDreamClient/Interface/DMF/IDMFProperty.cs +++ b/OpenDreamClient/Interface/DMF/IDMFProperty.cs @@ -40,8 +40,8 @@ Value is formatted as a DM-escaped string with surrounding quotes. Does not change the value's text representation in any way; assumes it's already formatted correctly for the purpose. This is similar to as arg but does no escaping and no quotes. */ -public struct DMFPropertyString(string value) : IDMFProperty { - public string Value = value; +public struct DMFPropertyString(string? value) : IDMFProperty { + public string Value = value ?? string.Empty; public string AsArg() { return "\""+AsEscaped()+"\"";