From 611023acbcb139198bc4eeb7aa154e4c60012243 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 22 May 2026 16:57:11 -0400 Subject: [PATCH 01/30] Add `SharedMapSystem.GetFilledTileCount` (#6562) * Add SharedMapSystem.GetFilledTileCount * Update Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../GameObjects/Systems/SharedMapSystem.Grid.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs index 6c21c94534d..5e4709ffc36 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; @@ -792,6 +793,18 @@ public IEnumerable GetAllTiles(EntityUid uid, MapGridComponent grid, bo } } + /// + /// Returns the total number of tiles on a grid. + /// by summing the counts of filled tiles in each chunk. + /// + /// The target map grid entity + /// The total number of filled tiles in . + [Pure] + public int GetFilledTileCount(Entity ent) + { + return ent.Comp.Chunks.Values.Sum(chunk => chunk.FilledTiles); + } + public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true) { return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty); From 7bdec921c561052b213b092508a6098873f8aa61 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Mon, 25 May 2026 17:25:03 +0200 Subject: [PATCH 02/30] Update .gitignore to ignore C# Dev Kit cache (#6593) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 365c9e0ec40..781d10b905c 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,6 @@ MSBuild/Robust.Custom.targets release/ Robust.Docfx/*-site Robust.Docfx/api + +# C# Dev Kit cache file +*.lscache From 0a648ab619fd2ca9217b0cf99b30f30650023d59 Mon Sep 17 00:00:00 2001 From: Thomas <87614336+Aeshus@users.noreply.github.com> Date: Mon, 25 May 2026 11:14:44 -0500 Subject: [PATCH 03/30] More Cursor Usage in Controls (#6583) * Add cursors for controls Have TextEdot use NotAllowed instead of Arrow for Disabled Make ScrollBar use {H,V}Resize Make BaseButton use Pointer/NotAllowed instead of Arrow Make ItemList use NotALlowed/Pointer/Arrow Make MenuTopButton use Pointer Make Slider use HResize Fix SplitContainer not changing DefaultCursorShape if it changed orientation Make Tree use Pointer/Arrow Make TabContainer use Pointer * Fix ScrollBar It needs to also change MouseMove because we don't have track functionality yet and thus we only want to show interactivity when we're directly hovering/grabbing the scrollbar. * Address review --- .../UserInterface/Controls/BaseButton.cs | 2 + .../UserInterface/Controls/ItemList.cs | 26 ++++++++++++ .../UserInterface/Controls/MenuBar.cs | 1 + .../UserInterface/Controls/ScrollBar.cs | 9 ++++ .../UserInterface/Controls/Slider.cs | 1 + .../UserInterface/Controls/SplitContainer.cs | 3 +- .../UserInterface/Controls/TabContainer.cs | 42 +++++++++++++++++-- .../UserInterface/Controls/TextEdit.cs | 4 +- Robust.Client/UserInterface/Controls/Tree.cs | 16 +++++++ 9 files changed, 98 insertions(+), 6 deletions(-) diff --git a/Robust.Client/UserInterface/Controls/BaseButton.cs b/Robust.Client/UserInterface/Controls/BaseButton.cs index d590b9619c7..e8a6856166f 100644 --- a/Robust.Client/UserInterface/Controls/BaseButton.cs +++ b/Robust.Client/UserInterface/Controls/BaseButton.cs @@ -90,6 +90,7 @@ public bool Disabled if (old != value) { + DefaultCursorShape = Disabled ? CursorShape.NotAllowed : CursorShape.Pointer; DrawModeChanged(); } } @@ -234,6 +235,7 @@ public bool MuteSounds protected BaseButton() { MouseFilter = MouseFilterMode.Stop; + DefaultCursorShape = Disabled ? CursorShape.NotAllowed : CursorShape.Pointer; } protected virtual void DrawModeChanged() diff --git a/Robust.Client/UserInterface/Controls/ItemList.cs b/Robust.Client/UserInterface/Controls/ItemList.cs index b727287bc23..8af6fe965f1 100644 --- a/Robust.Client/UserInterface/Controls/ItemList.cs +++ b/Robust.Client/UserInterface/Controls/ItemList.cs @@ -595,16 +595,42 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args) { base.MouseMove(args); + DefaultCursorShape = CursorShape.Arrow; + for (var idx = 0; idx < _itemList.Count; idx++) { var item = _itemList[idx]; if (item.Region == null) continue; if (!item.Region.Value.Contains(args.RelativePosition)) continue; + + if (SelectMode != ItemListSelectMode.None) + { + if (item.Disabled) + { + DefaultCursorShape = CursorShape.NotAllowed; + } + else if (item.Selectable) + { + DefaultCursorShape = CursorShape.Pointer; + } + else + { + DefaultCursorShape = CursorShape.Arrow; + } + } + OnItemHover?.Invoke(new ItemListHoverEventArgs(idx, this)); break; } } + protected internal override void MouseExited() + { + base.MouseExited(); + + DefaultCursorShape = CursorShape.Arrow; + } + protected internal override void MouseWheel(GUIMouseWheelEventArgs args) { base.MouseWheel(args); diff --git a/Robust.Client/UserInterface/Controls/MenuBar.cs b/Robust.Client/UserInterface/Controls/MenuBar.cs index ede2d89aafd..536a88ca071 100644 --- a/Robust.Client/UserInterface/Controls/MenuBar.cs +++ b/Robust.Client/UserInterface/Controls/MenuBar.cs @@ -235,6 +235,7 @@ public abstract class MenuTopButton : PanelContainer public MenuTopButton(Menu menu) { MouseFilter = MouseFilterMode.Pass; + DefaultCursorShape = CursorShape.Pointer; ChildMenu = menu; } diff --git a/Robust.Client/UserInterface/Controls/ScrollBar.cs b/Robust.Client/UserInterface/Controls/ScrollBar.cs index d6abd432836..47c479445d9 100644 --- a/Robust.Client/UserInterface/Controls/ScrollBar.cs +++ b/Robust.Client/UserInterface/Controls/ScrollBar.cs @@ -45,6 +45,7 @@ protected ScrollBar(OrientationMode orientation) ReservesSpace = true; _orientation = orientation; + DefaultCursorShape = CursorShape.Pointer; } public bool IsAtEnd @@ -127,11 +128,19 @@ protected internal override void KeyBindUp(GUIBoundKeyEventArgs args) protected internal override void MouseMove(GUIMouseMoveEventArgs args) { + DefaultCursorShape = CursorShape.Arrow; + + if (_isHovered || _grabData != null) + { + DefaultCursorShape = CursorShape.Pointer; + } + if (_grabData == null) { var box = _getGrabberBox(); _isHovered = box.Contains(args.RelativePixelPosition); _updatePseudoClass(); + return; } diff --git a/Robust.Client/UserInterface/Controls/Slider.cs b/Robust.Client/UserInterface/Controls/Slider.cs index f2f9c955257..bea5776f6a2 100644 --- a/Robust.Client/UserInterface/Controls/Slider.cs +++ b/Robust.Client/UserInterface/Controls/Slider.cs @@ -81,6 +81,7 @@ public StyleBox? GrabberStyleBoxOverride public Slider() { MouseFilter = MouseFilterMode.Stop; + DefaultCursorShape = CursorShape.Pointer; AddChild(new LayoutContainer { diff --git a/Robust.Client/UserInterface/Controls/SplitContainer.cs b/Robust.Client/UserInterface/Controls/SplitContainer.cs index 4b8baa31529..6b487cb1904 100644 --- a/Robust.Client/UserInterface/Controls/SplitContainer.cs +++ b/Robust.Client/UserInterface/Controls/SplitContainer.cs @@ -176,6 +176,7 @@ public SplitOrientation Orientation set { _orientation = value; + _splitDragArea.DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize; InvalidateMeasure(); } } @@ -185,7 +186,7 @@ public SplitContainer() MouseFilter = MouseFilterMode.Stop; AddChild(_splitDragArea); _splitDragArea.Visible = _resizeMode != SplitResizeMode.NotResizable; - _splitDragArea.DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize; + _splitDragArea.DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize; _splitDragArea.OnMouseUp += StopDragging; _splitDragArea.OnMouseDown += StartDragging; _splitDragArea.OnMouseMove += OnMove; diff --git a/Robust.Client/UserInterface/Controls/TabContainer.cs b/Robust.Client/UserInterface/Controls/TabContainer.cs index 5e0f729addc..df4cba4326f 100644 --- a/Robust.Client/UserInterface/Controls/TabContainer.cs +++ b/Robust.Client/UserInterface/Controls/TabContainer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using Robust.Client.Graphics; using Robust.Shared.Input; @@ -325,14 +326,49 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) args.Handle(); + if (!TryGetHoveredTab(args.RelativePixelPosition, out var index)) + { + return; + } + + CurrentTab = index.Value; + } + + protected internal override void MouseMove(GUIMouseMoveEventArgs args) + { + base.MouseMove(args); + + DefaultCursorShape = TryGetHoveredTab(args.RelativePixelPosition, out _) + ? CursorShape.Pointer + : CursorShape.Arrow; + } + + protected internal override void MouseExited() + { + base.MouseExited(); + + DefaultCursorShape = CursorShape.Arrow; + } + + private bool TryGetHoveredTab(Vector2 position, [NotNullWhen(true)] out int? index) + { + index = null; + + if (!TabsVisible || position.Y < 0 || position.Y > _enclosingTabHeight) + { + return false; + } + foreach (var box in _tabBoxes) { - if (box.Bounding.Contains(args.RelativePixelPosition)) + if (box.Bounding.Contains(position)) { - CurrentTab = box.Index; - return; + index = box.Index; + return true; } } + + return false; } [System.Diagnostics.Contracts.Pure] diff --git a/Robust.Client/UserInterface/Controls/TextEdit.cs b/Robust.Client/UserInterface/Controls/TextEdit.cs index ccba4700082..9f67fe32515 100644 --- a/Robust.Client/UserInterface/Controls/TextEdit.cs +++ b/Robust.Client/UserInterface/Controls/TextEdit.cs @@ -100,7 +100,7 @@ public TextEdit() CanKeyboardFocus = true; KeyboardFocusOnClick = true; MouseFilter = MouseFilterMode.Stop; - DefaultCursorShape = CursorShape.IBeam; + DefaultCursorShape = Editable ? CursorShape.IBeam : CursorShape.NotAllowed; } /// @@ -171,7 +171,7 @@ public bool Editable set { _editable = value; - DefaultCursorShape = _editable ? CursorShape.IBeam : CursorShape.Arrow; + DefaultCursorShape = _editable ? CursorShape.IBeam : CursorShape.NotAllowed; UpdatePseudoClass(); } } diff --git a/Robust.Client/UserInterface/Controls/Tree.cs b/Robust.Client/UserInterface/Controls/Tree.cs index 8f9585fbbdb..2f982e49de0 100644 --- a/Robust.Client/UserInterface/Controls/Tree.cs +++ b/Robust.Client/UserInterface/Controls/Tree.cs @@ -106,6 +106,22 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) } } + protected internal override void MouseMove(GUIMouseMoveEventArgs args) + { + base.MouseMove(args); + + DefaultCursorShape = _tryFindItemAtPosition(args.RelativePixelPosition)?.Selectable == true + ? CursorShape.Pointer + : CursorShape.Arrow; + } + + protected internal override void MouseExited() + { + base.MouseExited(); + + DefaultCursorShape = CursorShape.Arrow; + } + private Item? _tryFindItemAtPosition(Vector2 position) { var font = _getFont(); From a79c6bbc1d0bc4fb901a23d26bf210fcc2484f72 Mon Sep 17 00:00:00 2001 From: Thomas <87614336+Aeshus@users.noreply.github.com> Date: Tue, 26 May 2026 09:25:14 -0500 Subject: [PATCH 04/30] Add Track StyleProperty to ScrollBar (#6559) Add track style property to ScrollBar It makes it easier to see that there's a scrollbar if there's a backing track, especially when the grabber is very small. --- Robust.Client/UserInterface/Controls/ScrollBar.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Robust.Client/UserInterface/Controls/ScrollBar.cs b/Robust.Client/UserInterface/Controls/ScrollBar.cs index 47c479445d9..0aa25ed45a6 100644 --- a/Robust.Client/UserInterface/Controls/ScrollBar.cs +++ b/Robust.Client/UserInterface/Controls/ScrollBar.cs @@ -9,6 +9,7 @@ namespace Robust.Client.UserInterface.Controls { public abstract class ScrollBar : Range { + public const string StylePropertyTrack = "track"; public const string StylePropertyGrabber = "grabber"; public const string StylePseudoClassHover = "hover"; public const string StylePseudoClassGrabbed = "grabbed"; @@ -81,6 +82,9 @@ protected override void FrameUpdate(FrameEventArgs args) protected internal override void Draw(DrawingHandleScreen handle) { + var trackStyle = _getTrackStyleBox(); + trackStyle?.Draw(handle, PixelSizeBox, UIScale); + var styleBox = _getGrabberStyleBox(); styleBox?.Draw(handle, _getGrabberBox(), UIScale); } @@ -202,6 +206,12 @@ private float _getGrabberBoxMinSize() return null; } + [System.Diagnostics.Contracts.Pure] + private StyleBox? _getTrackStyleBox() + { + return StylePropertyDefault(StylePropertyTrack, null); + } + [System.Diagnostics.Contracts.Pure] private float _getOrientationSize() { From 6d5212916d96102528e3bf5e52415905aaf9b283 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 26 May 2026 17:26:23 +0200 Subject: [PATCH 05/30] Update submodules for lscache gitignore --- NetSerializer | 2 +- Robust.LoaderApi | 2 +- XamlX | 2 +- cefglue | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NetSerializer b/NetSerializer index 61b47fbbbd1..c32b75671ac 160000 --- a/NetSerializer +++ b/NetSerializer @@ -1 +1 @@ -Subproject commit 61b47fbbbd15af3369e80d670e2959d83e826e2c +Subproject commit c32b75671acd1c52be7fe2674de0c5a3b1ddeff0 diff --git a/Robust.LoaderApi b/Robust.LoaderApi index 5b467d11005..2b6a7db8d85 160000 --- a/Robust.LoaderApi +++ b/Robust.LoaderApi @@ -1 +1 @@ -Subproject commit 5b467d11005071f420435417927901d11947d5fb +Subproject commit 2b6a7db8d85166b345302e446a8492a213fc92aa diff --git a/XamlX b/XamlX index dca5a5f8c27..5da4e1d570a 160000 --- a/XamlX +++ b/XamlX @@ -1 +1 @@ -Subproject commit dca5a5f8c2759b940a87449584724ec71aa0dd19 +Subproject commit 5da4e1d570a13ee270fafccd3778d29fc6ddc7f2 diff --git a/cefglue b/cefglue index 6b4dcf18337..f8f5135dbcb 160000 --- a/cefglue +++ b/cefglue @@ -1 +1 @@ -Subproject commit 6b4dcf1833739725ddfcd0c2b17624b04d447acd +Subproject commit f8f5135dbcb6d1c1638043966d7a7c620676b5f8 From 5ff88372b279194eeb9bca324d452b744aa19c98 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:02:04 +1000 Subject: [PATCH 06/30] Use AsSpan for audio resource signatures (#6613) CA1832 and less IDE warning. --- .../ResourceManagement/ResourceTypes/AudioResource.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs index aaf759b795c..ece658f7b7f 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs @@ -40,12 +40,12 @@ public override void Load(IDependencyCollection dependencies, ResPath path) seekableStream.Seek(0, SeekOrigin.Begin); var audioManager = dependencies.Resolve(); - if (signature[..OggSignature.Length].SequenceEqual(OggSignature)) + if (signature.AsSpan()[..OggSignature.Length].SequenceEqual(OggSignature)) { AudioStream = audioManager.LoadAudioOggVorbis(seekableStream, path.ToString()); } - else if (signature[..RiffSignature.Length].SequenceEqual(RiffSignature) - && signature[WavSignatureStart..MaxSignatureLength].SequenceEqual(WavSignature)) + else if (signature.AsSpan()[..RiffSignature.Length].SequenceEqual(RiffSignature) + && signature.AsSpan()[WavSignatureStart..MaxSignatureLength].SequenceEqual(WavSignature)) { AudioStream = audioManager.LoadAudioWav(seekableStream, path.ToString()); } From bd6b7068f33706dab9068fdec7a17e220c6978ce Mon Sep 17 00:00:00 2001 From: adamsong Date: Wed, 3 Jun 2026 23:19:57 -0400 Subject: [PATCH 07/30] Small fix to doc comment (#6616) --- Robust.Shared/Localization/LocalizationManager.Functions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.Shared/Localization/LocalizationManager.Functions.cs b/Robust.Shared/Localization/LocalizationManager.Functions.cs index 0704f2b346f..82e3f292bb0 100644 --- a/Robust.Shared/Localization/LocalizationManager.Functions.cs +++ b/Robust.Shared/Localization/LocalizationManager.Functions.cs @@ -217,6 +217,7 @@ private ILocValue FuncDatObj(LocArgs args) /// Returns the respective genitive form (pronoun or possessive adjective) for the entity's gender. /// This is used in languages with a genitive case to indicate possession or related relationships, /// e.g., "у него" (Russian), "seines Vaters" (German). + /// private ILocValue FuncGenitive(LocArgs args) { return new LocValueString(GetString("zzzz-genitive", ("ent", args.Args[0]))); From 94c20cae281e404ad5c5058f103b77e0d0bca163 Mon Sep 17 00:00:00 2001 From: Samuka <47865393+Samuka-C@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:50:18 -0300 Subject: [PATCH 08/30] Add scroll lock key (#6617) * add scroll lock * localization --- Resources/Locale/en-US/input.ftl | 1 + Resources/Locale/pt-BR/input.ftl | 1 + Robust.Client/Graphics/Clyde/Windowing/Sdl3.Key.cs | 1 + Robust.Client/Input/InputDevices.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/Resources/Locale/en-US/input.ftl b/Resources/Locale/en-US/input.ftl index de7d1f83292..4b8e1f72bf3 100644 --- a/Resources/Locale/en-US/input.ftl +++ b/Resources/Locale/en-US/input.ftl @@ -69,6 +69,7 @@ input-key-MouseButton7 = Mouse 7 input-key-MouseButton8 = Mouse 8 input-key-MouseButton9 = Mouse 9 input-key-CapsLock = Caps Lock +input-key-ScrollLock = Scroll Lock input-key-LSystem-win = Left Win input-key-RSystem-win = Right Win diff --git a/Resources/Locale/pt-BR/input.ftl b/Resources/Locale/pt-BR/input.ftl index 90c5ea153a9..d387a67d338 100644 --- a/Resources/Locale/pt-BR/input.ftl +++ b/Resources/Locale/pt-BR/input.ftl @@ -44,6 +44,7 @@ input-key-MouseButton7 = Mouse 7 input-key-MouseButton8 = Mouse 8 input-key-MouseButton9 = Mouse 9 input-key-CapsLock = Caps Lock +input-key-ScrollLock = Scroll Lock input-key-LSystem-win = Left Win input-key-RSystem-win = Right Win diff --git a/Robust.Client/Graphics/Clyde/Windowing/Sdl3.Key.cs b/Robust.Client/Graphics/Clyde/Windowing/Sdl3.Key.cs index 18e9f79dd2f..c5b75daa2fe 100644 --- a/Robust.Client/Graphics/Clyde/Windowing/Sdl3.Key.cs +++ b/Robust.Client/Graphics/Clyde/Windowing/Sdl3.Key.cs @@ -204,6 +204,7 @@ static Sdl3WindowingImpl() MapKey(SC.SDL_SCANCODE_F24, Key.F24); MapKey(SC.SDL_SCANCODE_PAUSE, Key.Pause); MapKey(SC.SDL_SCANCODE_CAPSLOCK, Key.CapsLock); + MapKey(SC.SDL_SCANCODE_SCROLLLOCK, Key.ScrollLock); var keyMapReverse = new Dictionary(); diff --git a/Robust.Client/Input/InputDevices.cs b/Robust.Client/Input/InputDevices.cs index abed7da740b..fb41f35dd25 100644 --- a/Robust.Client/Input/InputDevices.cs +++ b/Robust.Client/Input/InputDevices.cs @@ -174,6 +174,7 @@ public enum Key : byte Pause, World1, CapsLock, + ScrollLock } public static bool IsMouseKey(this Key key) From 338825c8e734f8e79096c5d6079faea0e8dbf42d Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 10 Jun 2026 00:54:31 +1000 Subject: [PATCH 09/30] Add Pure attr to EntityLookup bounds methods (#6622) --- RELEASE-NOTES.md | 2 +- Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e60a7b1ed48..94dd7e357c2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,7 +47,7 @@ END TEMPLATE--> ### Other -*None yet* +* Add Pure attributes to the EntityLookup bounds methods ### Internal diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index 7d7d2ead228..9eee6955726 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; using Robust.Shared.Collections; @@ -803,18 +804,21 @@ public void FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds, Compon #region Bounds + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Box2 GetLocalBounds(Vector2i gridIndices, ushort tileSize) { return new Box2(gridIndices * tileSize, (gridIndices + 1) * tileSize); } + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Box2 GetLocalBounds(TileRef tileRef, ushort tileSize) { return GetLocalBounds(tileRef.GridIndices, tileSize); } + [Pure] public Box2Rotated GetWorldBounds(TileRef tileRef, Matrix3x2? worldMatrix = null, Angle? angle = null) { var grid = _gridQuery.GetComponent(tileRef.GridUid); From 51cdfb6b7ec9db6ff3bf3f9a3109ff1ac7328740 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 18 Jun 2026 10:39:35 +0200 Subject: [PATCH 10/30] Add "hidden" console commands --- RELEASE-NOTES.md | 2 +- Robust.Client/Console/ClientConsoleHost.Completions.cs | 2 +- Robust.Shared/Console/Commands/ListCommand.cs | 2 +- Robust.Shared/Console/ConsoleHost.cs | 5 +++++ Robust.Shared/Console/IConsoleCommand.cs | 3 +++ Robust.Shared/Console/IConsoleHost.cs | 6 ++++++ 6 files changed, 17 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 94dd7e357c2..bd3d89b9a34 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Console commands can now be "hidden" by prefixing them with `_`. ### Bugfixes diff --git a/Robust.Client/Console/ClientConsoleHost.Completions.cs b/Robust.Client/Console/ClientConsoleHost.Completions.cs index 7bda1199f44..6195a952b2e 100644 --- a/Robust.Client/Console/ClientConsoleHost.Completions.cs +++ b/Robust.Client/Console/ClientConsoleHost.Completions.cs @@ -34,7 +34,7 @@ private Task CalcCompletions(List args, string argStr, // Typing out command name, handle this ourselves. var cmdOptions = CompletionResult.FromOptions( AvailableCommands.Values - .Where(c => CanExecute(c.Command)) + .Where(c => CanExecute(c.Command) && !IsCommandHidden(c)) .OrderBy(c => c.Command) .Select(c => new CompletionOption(c.Command, c.Description))); diff --git a/Robust.Shared/Console/Commands/ListCommand.cs b/Robust.Shared/Console/Commands/ListCommand.cs index 53df9233f26..6b9c7f5ded4 100644 --- a/Robust.Shared/Console/Commands/ListCommand.cs +++ b/Robust.Shared/Console/Commands/ListCommand.cs @@ -18,7 +18,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) var builder = new StringBuilder(Loc.GetString("cmd-list-heading")); foreach (var command in host.AvailableCommands.Values - .Where(p => p.Command.Contains(filter)) + .Where(p => p.Command.Contains(filter) && !host.IsCommandHidden(p)) .OrderBy(c => c.Command)) { //TODO: Make this actually check permissions. diff --git a/Robust.Shared/Console/ConsoleHost.cs b/Robust.Shared/Console/ConsoleHost.cs index 5b6c239ec1a..58fd66fa270 100644 --- a/Robust.Shared/Console/ConsoleHost.cs +++ b/Robust.Shared/Console/ConsoleHost.cs @@ -81,6 +81,11 @@ public void LoadConsoleCommands() } } + public bool IsCommandHidden(IConsoleCommand command) + { + return command.Command.StartsWith('_'); + } + protected virtual void UpdateAvailableCommands() { } diff --git a/Robust.Shared/Console/IConsoleCommand.cs b/Robust.Shared/Console/IConsoleCommand.cs index b59d220729e..9f6544d25d7 100644 --- a/Robust.Shared/Console/IConsoleCommand.cs +++ b/Robust.Shared/Console/IConsoleCommand.cs @@ -21,6 +21,9 @@ public interface IConsoleCommand /// /// A string as identifier for this command. /// + /// + /// Commands starting with '_' are treated as "hidden". They will not be shown in listings or completions. + /// string Command { get; } /// diff --git a/Robust.Shared/Console/IConsoleHost.cs b/Robust.Shared/Console/IConsoleHost.cs index 66b86419260..6ee2f4ea897 100644 --- a/Robust.Shared/Console/IConsoleHost.cs +++ b/Robust.Shared/Console/IConsoleHost.cs @@ -66,6 +66,12 @@ public interface IConsoleHost /// void LoadConsoleCommands(); + /// + /// Check whether a console command is hidden. + /// + /// + bool IsCommandHidden(IConsoleCommand command); + #region RegisterCommand /// /// Registers a console command into the console system. This is an alternative to From ead6018a6a1243f9e8b6a5f4aa56bc220c48a3c3 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 19 Jun 2026 22:49:29 +0200 Subject: [PATCH 11/30] Add markup escaping Fluent functions --- RELEASE-NOTES.md | 1 + .../LocalizationManager.Functions.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bd3d89b9a34..87a59447815 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * Console commands can now be "hidden" by prefixing them with `_`. +* Add `ESCAPE()` and `ESCAPE-PARAM()` localization functions, for escaping text for markup formatting. ### Bugfixes diff --git a/Robust.Shared/Localization/LocalizationManager.Functions.cs b/Robust.Shared/Localization/LocalizationManager.Functions.cs index 82e3f292bb0..354db76939f 100644 --- a/Robust.Shared/Localization/LocalizationManager.Functions.cs +++ b/Robust.Shared/Localization/LocalizationManager.Functions.cs @@ -9,6 +9,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Localization; using Robust.Shared.Maths; +using Robust.Shared.Utility; namespace Robust.Shared.Localization { @@ -42,6 +43,10 @@ private void AddBuiltInFunctions(FluentBundle bundle) AddCtxFunction(bundle, "ATTRIB", args => FuncAttrib(bundle, args)); AddCtxFunction(bundle, "CAPITALIZE", FuncCapitalize); AddCtxFunction(bundle, "INDEFINITE", FuncIndefinite); + + // Rich text + AddCtxFunction(bundle, "ESCAPE", FuncEscape); + AddCtxFunction(bundle, "ESCAPE-PARAM", FuncEscapeParam); } /// @@ -370,6 +375,24 @@ public void AddFunction(CultureInfo culture, string name, LocFunction function) bundle.AddFunctionOverriding(name, (args, options) => CallFunction(function, bundle, args, options)); } + + /// + /// Escape the provided string argument for insertion among rich text markup. + /// + private static ILocValue FuncEscape(LocArgs args) + { + var input = args.Args[0].Format(new LocContext()); + return new LocValueString(FormattedMessage.EscapeText(input)); + } + + /// + /// Escape the provided string argument for insertion as a string markup parameter. + /// + private static ILocValue FuncEscapeParam(LocArgs args) + { + var input = args.Args[0].Format(new LocContext()); + return new LocValueString(FormattedMessage.EscapeStringParameter(input)); + } } internal sealed class FluentLocWrapperType : IFluentType From a1a03d9fa1ece48604f7115102bbf106de93bc6e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 19 Jun 2026 22:50:15 +0200 Subject: [PATCH 12/30] Allow specifying tooltip in cmdlink tag --- RELEASE-NOTES.md | 1 + Robust.Client/UserInterface/RichText/CommandLinkTag.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 87a59447815..cefc0c94cc6 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * Console commands can now be "hidden" by prefixing them with `_`. * Add `ESCAPE()` and `ESCAPE-PARAM()` localization functions, for escaping text for markup formatting. +* The `[cmdlink /]` tag can now have a tooltip specified with the optional "title" attribute. ### Bugfixes diff --git a/Robust.Client/UserInterface/RichText/CommandLinkTag.cs b/Robust.Client/UserInterface/RichText/CommandLinkTag.cs index 743ab15f889..78c213f3fad 100644 --- a/Robust.Client/UserInterface/RichText/CommandLinkTag.cs +++ b/Robust.Client/UserInterface/RichText/CommandLinkTag.cs @@ -36,6 +36,9 @@ public bool TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? c label.OnMouseExited += _ => label.FontColorOverride = Color.LightBlue; label.OnKeyBindDown += args => OnKeybindDown(args, command); + if (node.Attributes.TryGetValue("title", out var titleArg)) + label.ToolTip = titleArg.StringValue; + control = label; return true; } From 30cac6ec2aeda014af9b4ec50b3669238ef84073 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 00:10:04 +0200 Subject: [PATCH 13/30] Add CommandParsing.EscapeCommand --- RELEASE-NOTES.md | 1 + .../Utility/CommandParsing_Test.cs | 17 +++++++++ Robust.Shared/Utility/CommandParsing.cs | 38 ++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index cefc0c94cc6..a863a3eb35b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -42,6 +42,7 @@ END TEMPLATE--> * Console commands can now be "hidden" by prefixing them with `_`. * Add `ESCAPE()` and `ESCAPE-PARAM()` localization functions, for escaping text for markup formatting. * The `[cmdlink /]` tag can now have a tooltip specified with the optional "title" attribute. +* Added `CommandParsing.EscapeCommand()` for *formatting* command strings easily. ### Bugfixes diff --git a/Robust.Shared.Tests/Utility/CommandParsing_Test.cs b/Robust.Shared.Tests/Utility/CommandParsing_Test.cs index b99089f2bbe..3a9d7c213ac 100644 --- a/Robust.Shared.Tests/Utility/CommandParsing_Test.cs +++ b/Robust.Shared.Tests/Utility/CommandParsing_Test.cs @@ -33,5 +33,22 @@ public void TestEscape(string source, string expected) Assert.That(escaped, Is.EqualTo(expected)); } + + [TestCase("foo;bar")] + [TestCase("\"foo;bar")] + [TestCase("f oo;bar")] + [TestCase("f\\ oo;bar")] + public void TestEscapeCommand(string source) + { + var args = source.Split(';'); + + var result = new List(); + var escapedCommand = CommandParsing.EscapeCommand(args); + TestContext.Out.WriteLine($"Escaped command: {escapedCommand}"); + + CommandParsing.ParseArguments(escapedCommand, result); + + Assert.That(result, Is.EquivalentTo(args)); + } } } diff --git a/Robust.Shared/Utility/CommandParsing.cs b/Robust.Shared/Utility/CommandParsing.cs index a6e5e16a92c..1fd48dd5baf 100644 --- a/Robust.Shared/Utility/CommandParsing.cs +++ b/Robust.Shared/Utility/CommandParsing.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Text; using Robust.Shared.Collections; @@ -7,6 +8,8 @@ namespace Robust.Shared.Utility { public static class CommandParsing { + private static readonly SearchValues CommandArgumentSeparator = SearchValues.Create(" "); + /// /// Parses a full console command into a list of arguments. /// @@ -63,7 +66,7 @@ internal static void ParseArguments( continue; } - if (chr == ' ' && !inQuotes) + if (CommandArgumentSeparator.Contains(chr) && !inQuotes) { if (sb.Length != 0) { @@ -93,5 +96,38 @@ public static string Escape(string text) { return text.Replace("\\", "\\\\").Replace("\"", "\\\""); } + + /// + /// Split a set of arguments into a string that can be parsed round-trip. + /// + /// + /// + /// This is effectively the inverse of . + /// + /// + public static string EscapeCommand(params string[] arguments) + { + var sb = new StringBuilder(); + + var first = true; + + foreach (var entry in arguments) + { + if (!first) + sb.Append(' '); + first = false; + + var quoted = entry.ContainsAny(CommandArgumentSeparator); + if (quoted) + sb.Append('"'); + + sb.Append(Escape(entry)); + + if (quoted) + sb.Append('"'); + } + + return sb.ToString(); + } } } From a4f42755727580ed9fafa04c3a09bbb5a956706e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 00:21:09 +0200 Subject: [PATCH 14/30] Add IUserInterfaceManager.GetRootForMouse and Popup.OpenAtCursor --- RELEASE-NOTES.md | 2 ++ Robust.Client/UserInterface/Controls/Popup.cs | 29 +++++++++++++++++++ .../UserInterface/IUserInterfaceManager.cs | 8 +++++ .../UserInterface/UserInterfaceManager.cs | 15 ++++++++++ 4 files changed, 54 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a863a3eb35b..00d92d34ef3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,8 @@ END TEMPLATE--> * Add `ESCAPE()` and `ESCAPE-PARAM()` localization functions, for escaping text for markup formatting. * The `[cmdlink /]` tag can now have a tooltip specified with the optional "title" attribute. * Added `CommandParsing.EscapeCommand()` for *formatting* command strings easily. +* Added `IUserInterfaceManager.GetRootForMouse()`. +* Added `Popup.OpenAtCursor()`. ### Bugfixes diff --git a/Robust.Client/UserInterface/Controls/Popup.cs b/Robust.Client/UserInterface/Controls/Popup.cs index 2b4f363ea7e..a2223b3d40d 100644 --- a/Robust.Client/UserInterface/Controls/Popup.cs +++ b/Robust.Client/UserInterface/Controls/Popup.cs @@ -30,6 +30,29 @@ public Popup() public bool CloseOnEscape { get; set; } = true; + private bool _autoOrphan; + + /// + /// Opens the popup at the location of the mouse. + /// + /// + /// + /// The popup is placed in the modal root, and is automatically sized. + /// + /// + /// The popup *automatically* gets removed from the popup root when it is hidden again. + /// Do not remove it manually! + /// + /// + public void OpenAtMouse() + { + _autoOrphan = true; + var root = UserInterfaceManager.GetRootForMouse(); + root.ModalRoot.AddChild(this); + + Open(UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position, Vector2.One)); + } + public virtual void Open(UIBox2? box = null, Vector2? altPos = null, Vector2? altPosUp = null) { if (Visible) @@ -72,6 +95,12 @@ protected internal override void ModalRemoved() Visible = false; OnPopupHide?.Invoke(); + + if (_autoOrphan) + { + Orphan(); + _autoOrphan = false; + } } protected override Vector2 MeasureOverride(Vector2 availableSize) diff --git a/Robust.Client/UserInterface/IUserInterfaceManager.cs b/Robust.Client/UserInterface/IUserInterfaceManager.cs index 0ad4eb29997..271b2df7485 100644 --- a/Robust.Client/UserInterface/IUserInterfaceManager.cs +++ b/Robust.Client/UserInterface/IUserInterfaceManager.cs @@ -167,6 +167,14 @@ public partial interface IUserInterfaceManager /// Exists so that control don't have to inject dependencies or otherwise obtain an instance just to log errors. /// ISawmill ControlSawmill { get; } + + /// + /// Get the UI root responsible for the current mouse position. + /// + /// + /// This is useful to open popups or similar on the current active window. + /// + UIRoot GetRootForMouse(); } public readonly struct PostDrawUIRootEventArgs diff --git a/Robust.Client/UserInterface/UserInterfaceManager.cs b/Robust.Client/UserInterface/UserInterfaceManager.cs index 942e2f5ffef..57c86c35185 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.cs @@ -492,5 +492,20 @@ public void HoverSound() { ClearWindows(); } + + public UIRoot GetRootForMouse() + { + var pos = _inputManager.MouseScreenPosition; + + foreach (var root in _roots) + { + if (root.Window.Id == pos.Window) + { + return root; + } + } + + return RootControl; + } } } From fbab67805807787c13695a96ca746ba5dfaf0d30 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 03:36:49 +0200 Subject: [PATCH 15/30] Add `FormattedStringBuilder` for safely constructing markup with code. --- RELEASE-NOTES.md | 1 + .../RichText/FormattedStringBuilderTest.cs | 255 ++++++++++++ .../RichText/FormattedStringBuilder.cs | 367 ++++++++++++++++++ .../FormattedStringBuilderExtensions.cs | 28 ++ 4 files changed, 651 insertions(+) create mode 100644 Robust.Shared.Tests/RichText/FormattedStringBuilderTest.cs create mode 100644 Robust.Shared/RichText/FormattedStringBuilder.cs create mode 100644 Robust.Shared/RichText/FormattedStringBuilderExtensions.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 00d92d34ef3..2dd80c8ec5f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -45,6 +45,7 @@ END TEMPLATE--> * Added `CommandParsing.EscapeCommand()` for *formatting* command strings easily. * Added `IUserInterfaceManager.GetRootForMouse()`. * Added `Popup.OpenAtCursor()`. +* Added `FormattedStringBuilder` for safely constructing markup with code. ### Bugfixes diff --git a/Robust.Shared.Tests/RichText/FormattedStringBuilderTest.cs b/Robust.Shared.Tests/RichText/FormattedStringBuilderTest.cs new file mode 100644 index 00000000000..431cd925222 --- /dev/null +++ b/Robust.Shared.Tests/RichText/FormattedStringBuilderTest.cs @@ -0,0 +1,255 @@ +using System.Text; +using NUnit.Framework; +using Robust.Shared.Maths; +using Robust.Shared.RichText; +using Robust.Shared.Utility; + +namespace Robust.UnitTesting.Shared.RichText; + +[Parallelizable(ParallelScope.All)] +[TestFixture, TestOf(typeof(FormattedStringBuilder))] +public static class FormattedStringBuilderTest +{ + [Test] + public static void TestPlainText() + { + var fsb = new FormattedStringBuilder(); + fsb.AppendText("Foobar"); + fsb.AppendLine(); + fsb.AppendMarkup("Wawa"); + + AssertMarkup(fsb, "Foobar\nWawa"); + } + + [Test] + public static void TestPlainTextExistingBuilder() + { + var sb = new StringBuilder(); + sb.Append("Guh"); + var fsb = new FormattedStringBuilder(sb); + fsb.AppendText("Foobar"); + fsb.AppendLine(); + fsb.AppendMarkup("Wawa"); + + AssertMarkup(fsb, "GuhFoobar\nWawa"); + } + + [Test] + public static void TestBasicTag() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold"); + fsb.FinishTagOpen(); + + fsb.AppendText("bar"); + fsb.PopTag(); + + AssertMarkup(fsb, "[bold]bar[/bold]"); + } + + [Test] + public static void TestBasicTagFormattedString() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold"); + fsb.FinishTagOpen(); + + fsb.AppendText("bar"); + fsb.PopTag(); + + Assert.That((FormattedMessage)fsb.ToFormattedString(), Is.EqualTo(FormattedMessage.FromMarkupOrThrow("[bold]bar[/bold]"))); + } + + [Test] + public static void TestSelfClosingTag() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold"); + fsb.FinishTagSelfClosed(); + + AssertMarkup(fsb, "[bold /]"); + } + + [Test] + public static void TestTagValueLong() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold", 10); + fsb.FinishTagSelfClosed(); + + AssertMarkup(fsb, "[bold=10 /]"); + } + + [Test] + public static void TestTagValueString() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold", "wawa"); + fsb.FinishTagSelfClosed(); + + AssertMarkup(fsb, "[bold=\"wawa\" /]"); + } + + [Test] + public static void TestTagValueColor() + { + var fsb = new FormattedStringBuilder(); + fsb.BeginTag("bold", Color.FromHex("#AAA")); + fsb.FinishTagSelfClosed(); + + AssertMarkup(fsb, "[bold=#AAA /]"); + } + + [Test] + public static void TestTagAttributeString() + { + AssertMarkup( + Fsb().BeginTag("bold").TagAttribute("a", "b").FinishTagSelfClosed(), + "[bold a=\"b\" /]"); + } + + [Test] + public static void TestTagAttributeLong() + { + AssertMarkup( + Fsb().BeginTag("bold").TagAttribute("a", 10).FinishTagSelfClosed(), + "[bold a=10 /]"); + } + + [Test] + public static void TestTagAttributeColor() + { + AssertMarkup( + Fsb().BeginTag("bold").TagAttribute("a", Color.FromHex("#AAA")).FinishTagSelfClosed(), + "[bold a=#AAA /]"); + } + + [Test] + public static void TestAppendMarkup() + { + AssertMarkup( + Fsb().AppendMarkup("[bold /]"), + "[bold /]"); + } + + [Test] + public static void TestAppendMarkupLine() + { + AssertMarkup( + Fsb().AppendMarkupLine("[bold /]"), + "[bold /]\n"); + } + + [Test] + public static void TestBeginInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.BeginTag("a"), Throws.TypeOf()); + } + + [Test] + public static void TestBeginValueInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.BeginTag("a", "b"), Throws.TypeOf()); + } + + [Test] + public static void TestTagAttributeInvalid() + { + var fsb = Fsb(); + + Assert.That(() => fsb.TagAttribute("a", "b"), Throws.TypeOf()); + } + + [Test] + public static void TestFinishTagSelfClosedInvalid() + { + var fsb = Fsb(); + + Assert.That(() => fsb.FinishTagSelfClosed(), Throws.TypeOf()); + } + + [Test] + public static void TestFinishTagOpenInvalid() + { + var fsb = Fsb(); + + Assert.That(() => fsb.FinishTagOpen(), Throws.TypeOf()); + } + + [Test] + public static void TestPopTagEmpty() + { + var fsb = Fsb(); + + Assert.That(() => fsb.PopTag(), Throws.TypeOf()); + } + + [Test] + public static void TestPopTagInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.PopTag(), Throws.TypeOf()); + } + + [Test] + public static void TestAppendTextInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.AppendText("A"), Throws.TypeOf()); + } + + [Test] + public static void TestAppendMarkupInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.AppendMarkup("A"), Throws.TypeOf()); + } + + [Test] + public static void TestAppendMarkupInvalidMarkup() + { + var fsb = Fsb(); + + Assert.That(() => fsb.AppendMarkup("[wawa"), Throws.TypeOf()); + } + + [Test] + public static void TestAppendLineInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.AppendLine(), Throws.TypeOf()); + } + + [Test] + public static void TestAppendMarkupLineInvalid() + { + var fsb = Fsb().BeginTag("a"); + + Assert.That(() => fsb.AppendMarkupLine("guh"), Throws.TypeOf()); + } + + [Test] + public static void TestAppendMarkupLineInvalidMarkup() + { + var fsb = Fsb(); + + Assert.That(() => fsb.AppendMarkupLine("[guh"), Throws.TypeOf()); + } + + private static void AssertMarkup(FormattedStringBuilder fsb, string expected) + { + Assert.That( + FormattedMessage.FromMarkupOrThrow(fsb.ToString()), + Is.EqualTo(FormattedMessage.FromMarkupOrThrow(expected))); + } + + private static FormattedStringBuilder Fsb() => new FormattedStringBuilder(); +} diff --git a/Robust.Shared/RichText/FormattedStringBuilder.cs b/Robust.Shared/RichText/FormattedStringBuilder.cs new file mode 100644 index 00000000000..0f059dd8b26 --- /dev/null +++ b/Robust.Shared/RichText/FormattedStringBuilder.cs @@ -0,0 +1,367 @@ +using System; +using System.Text; +using Robust.Shared.Collections; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Shared.RichText; + +/// +/// A wrapper around , with convenience methods for safely constructing rich text markup. +/// +/// +/// +/// Tags are written with multiple consecutive calls. Functions may throw if not in the right state, +/// and this can be checked with . +/// It should go without saying that care must be taken to use the underlying +/// while this is the case. +/// +/// +/// While the underlying is accessible, you are of course responsible for writing valid +/// markup and escaping if necessary. +/// +/// +public sealed class FormattedStringBuilder +{ + private ValueList _tagStack; + + /// + /// The underlying used by this instance. + /// + /// + /// you are responsible for writing valid markup and escaping where necessary, if you access this property. + /// + public StringBuilder Builder { get; } + + /// + /// If true, we are currently writing a tag. + /// + /// + /// This can be ended through or . + /// + public bool IsInsideTag { get; private set; } = true; + + /// + /// Create a new builder with an empty underlying . + /// + public FormattedStringBuilder() : this(new StringBuilder()) + { + + } + + /// + /// Create a new builder wrapping an existing . + /// + /// + /// The provided instance is not initially mutated. + /// + public FormattedStringBuilder(StringBuilder builder) + { + Builder = builder; + } + + /// + /// Begin a new tag with the specified name. + /// + /// The name of the tag to begin. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're already inside a tag. + /// + public FormattedStringBuilder BeginTag(string tagName) + { + if (!IsInsideTag) + throw new InvalidOperationException("Cannot begin tag: we're already in a tag"); + + _tagStack.Push(tagName); + IsInsideTag = false; + Builder.Append($"[{tagName}"); + + return this; + } + + /// + /// Begin a new tag with the specified name and a value. + /// + /// The name of the tag to begin. + /// The value of the markup tag. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're already inside a tag. + /// + public FormattedStringBuilder BeginTag(string tagName, MarkupParameter value) + { + BeginTag(tagName); + + Builder.Append(value.ToString()); + + return this; + } + + /// + /// Begin a new tag with the specified name and a value. + /// + /// The name of the tag to begin. + /// The value of the markup tag. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're already inside a tag. + /// + public FormattedStringBuilder BeginTag(string tagName, string value) + { + return BeginTag(tagName, new MarkupParameter(value)); + } + + /// + /// Begin a new tag with the specified name and a value. + /// + /// The name of the tag to begin. + /// The value of the markup tag. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're already inside a tag. + /// + public FormattedStringBuilder BeginTag(string tagName, long value) + { + return BeginTag(tagName, new MarkupParameter(value)); + } + + /// + /// Begin a new tag with the specified name and a value. + /// + /// The name of the tag to begin. + /// The value of the markup tag. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're already inside a tag. + /// + public FormattedStringBuilder BeginTag(string tagName, Color value) + { + return BeginTag(tagName, new MarkupParameter(value)); + } + + /// + /// Specify an attribute for the tag currently being written. + /// + /// + /// This does not check for duplicates. + /// + /// The name of the attribute to write. + /// The value of the attribute. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder TagAttribute(string attributeName, MarkupParameter value) + { + if (IsInsideTag) + throw new InvalidOperationException("Cannot write attribute: we aren't in a tag!"); + + Builder.Append($" {attributeName}{value}"); + return this; + } + + /// + /// Specify an attribute for the tag currently being written. + /// + /// + /// This does not check for duplicates. + /// + /// The name of the attribute to write. + /// The value of the attribute. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder TagAttribute(string attributeName, string value) + { + return TagAttribute(attributeName, new MarkupParameter(value)); + } + + /// + /// Specify an attribute for the tag currently being written. + /// + /// + /// This does not check for duplicates. + /// + /// The name of the attribute to write. + /// The value of the attribute. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder TagAttribute(string attributeName, long value) + { + return TagAttribute(attributeName, new MarkupParameter(value)); + } + + /// + /// Specify an attribute for the tag currently being written. + /// + /// + /// This does not check for duplicates. + /// + /// The name of the attribute to write. + /// The value of the attribute. + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder TagAttribute(string attributeName, Color value) + { + return TagAttribute(attributeName, new MarkupParameter(value)); + } + + /// + /// Finish writing the current tag as self-closed. + /// + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder FinishTagSelfClosed() + { + if (IsInsideTag) + throw new InvalidOperationException("Cannot finish tag: we aren't in a tag!"); + + _tagStack.Pop(); + Builder.Append("/]"); + IsInsideTag = true; + return this; + } + + /// + /// Finish writing the current tag as open. You will have to close it with . + /// + /// The current instance, to enable easy method call chaining. + /// + /// Thrown if we're not currently inside a tag. + /// + public FormattedStringBuilder FinishTagOpen() + { + if (IsInsideTag) + throw new InvalidOperationException("Cannot finish tag: we aren't in a tag!"); + + Builder.Append(']'); + IsInsideTag = true; + return this; + } + + /// + /// Write a closing tag for the most recent open tag. + /// + /// + /// The stack of open tags (from ) is automatically tracked. + /// + /// The current instance, to enable easy method call chaining. + /// + public FormattedStringBuilder PopTag() + { + if (!IsInsideTag) + throw new InvalidOperationException("Cannot begin tag: we're already in a tag"); + + Builder.Append($"[/{_tagStack.Pop()}]"); + return this; + } + + /// + /// Append plain text. + /// + /// The text to append without interpreting formatting. + /// + /// Thrown if we're currently inside a tag. + /// + /// The current instance, to enable easy method call chaining. + public FormattedStringBuilder AppendText(string text) + { + CheckSafe(); + Builder.Append(FormattedMessage.EscapeText(text)); + return this; + } + + /// + /// Append markup. + /// + /// The text to append as markup. + /// + /// Thrown if is not valid markup. + /// + /// + /// Thrown if we're currently inside a tag. + /// + /// The current instance, to enable easy method call chaining. + public FormattedStringBuilder AppendMarkup(string markup) + { + CheckSafe(); + + if (!FormattedMessage.ValidMarkup(markup)) + throw new ArgumentException("Not valid markup!", nameof(markup)); + + Builder.Append(markup); + return this; + } + + /// + /// Append markup, followed by a newline. + /// + /// + /// The added line is always a single Line Feed (LF), not . + /// + /// The text to append as markup. + /// + /// Thrown if is not valid markup. + /// + /// + /// Thrown if we're currently inside a tag. + /// + /// The current instance, to enable easy method call chaining. + public FormattedStringBuilder AppendMarkupLine(string markup) + { + AppendMarkup(markup); + AppendLine(); + return this; + } + + /// + /// Append a newline. + /// + /// + /// The added line is always a single Line Feed (LF), not . + /// + /// + /// Thrown if we're currently inside a tag. + /// + /// The current instance, to enable easy method call chaining. + public FormattedStringBuilder AppendLine() + { + CheckSafe(); + Builder.Append('\n'); + return this; + } + + /// + /// Returns the internal value as a string. + /// + public override string ToString() + { + return Builder.ToString(); + } + + /// + /// Returns the internal value as a . + /// + /// + /// Thrown if the contained string is not valid markup + /// (e.g. if you manually messed with the underlying .) + /// + public FormattedString ToFormattedString() + { + return FormattedString.FromMarkup(ToString()); + } + + private void CheckSafe() + { + if (!IsInsideTag) + throw new InvalidOperationException("Cannot append: we are currently writing a tag."); + } +} diff --git a/Robust.Shared/RichText/FormattedStringBuilderExtensions.cs b/Robust.Shared/RichText/FormattedStringBuilderExtensions.cs new file mode 100644 index 00000000000..6449f9c054f --- /dev/null +++ b/Robust.Shared/RichText/FormattedStringBuilderExtensions.cs @@ -0,0 +1,28 @@ +namespace Robust.Shared.RichText; + +/// +/// Extension methods for . +/// +public static class FormattedStringBuilderExtensions +{ + extension(FormattedStringBuilder builder) + { + /// + /// Write a cmdlink tag. + /// + /// The user-visible tag for the link. + /// The command executed when the user clicks. + /// The tooltip (title) when the user hovers over the link. + /// The current instance, to enable easy method call chaining. + public FormattedStringBuilder MakeCommandLinkTag(string text, string command, string? title = null) + { + builder.BeginTag("cmdlink", text); + builder.TagAttribute("command", command); + if (title != null) + builder.TagAttribute("title", title); + builder.FinishTagSelfClosed(); + + return builder; + } + } +} From 1a09f17dadd98610217503bb8a83b3dc612c0228 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 7 May 2026 17:05:38 +0200 Subject: [PATCH 16/30] Add HBox and VBox convenience types. --- RELEASE-NOTES.md | 1 + Robust.Client/UserInterface/Controls/HBox.cs | 6 ++++++ Robust.Client/UserInterface/Controls/VBox.cs | 12 ++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 Robust.Client/UserInterface/Controls/HBox.cs create mode 100644 Robust.Client/UserInterface/Controls/VBox.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2dd80c8ec5f..b51b1c725b3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -46,6 +46,7 @@ END TEMPLATE--> * Added `IUserInterfaceManager.GetRootForMouse()`. * Added `Popup.OpenAtCursor()`. * Added `FormattedStringBuilder` for safely constructing markup with code. +* Added `VBox` and `HBox` convenience types for more concisely construct `BoxContainer`s. ### Bugfixes diff --git a/Robust.Client/UserInterface/Controls/HBox.cs b/Robust.Client/UserInterface/Controls/HBox.cs new file mode 100644 index 00000000000..c9ce4110c07 --- /dev/null +++ b/Robust.Client/UserInterface/Controls/HBox.cs @@ -0,0 +1,6 @@ +namespace Robust.Client.UserInterface.Controls; + +/// +/// Convenience type to describe a horizontal . +/// +public sealed class HBox : BoxContainer; diff --git a/Robust.Client/UserInterface/Controls/VBox.cs b/Robust.Client/UserInterface/Controls/VBox.cs new file mode 100644 index 00000000000..1476f749b65 --- /dev/null +++ b/Robust.Client/UserInterface/Controls/VBox.cs @@ -0,0 +1,12 @@ +namespace Robust.Client.UserInterface.Controls; + +/// +/// Convenience type to describe a vertical . +/// +public sealed class VBox : BoxContainer +{ + public VBox() + { + Orientation = LayoutOrientation.Vertical; + } +} From c1919263f4604ad306a95a11f190d0e8ce3c7b4f Mon Sep 17 00:00:00 2001 From: Axionyx Date: Sat, 20 Jun 2026 20:00:38 +0200 Subject: [PATCH 17/30] Track isLocal in user data (#6641) --- Robust.Shared/Network/NetManager.ServerAuth.cs | 6 ++++-- Robust.Shared/Network/NetUserData.cs | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Network/NetManager.ServerAuth.cs b/Robust.Shared/Network/NetManager.ServerAuth.cs index f11458d669c..175fbfe6f69 100644 --- a/Robust.Shared/Network/NetManager.ServerAuth.cs +++ b/Robust.Shared/Network/NetManager.ServerAuth.cs @@ -179,7 +179,8 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection) HWId = legacyHwid, ModernHWIds = modernHWIds, Trust = joinedRespJson.ConnectionData!.Trust, - CreatedTime = joinedRespJson.UserData.CreatedTime + CreatedTime = joinedRespJson.UserData.CreatedTime, + IsLocal = isLocal }; padSuccessMessage = false; type = LoginType.LoggedIn; @@ -223,7 +224,8 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection) userData = new NetUserData(userId, name) { HWId = [], - ModernHWIds = [] + ModernHWIds = [], + IsLocal = isLocal }; } diff --git a/Robust.Shared/Network/NetUserData.cs b/Robust.Shared/Network/NetUserData.cs index 7ee61388e9a..1aa7aa25a87 100644 --- a/Robust.Shared/Network/NetUserData.cs +++ b/Robust.Shared/Network/NetUserData.cs @@ -41,6 +41,11 @@ public sealed record NetUserData /// public float Trust { get; init; } + /// + /// True if the player is connecting from a local address. + /// + public bool IsLocal { get; init; } + public NetUserData(NetUserId userId, string userName) { UserId = userId; From f41b2d5faf3ce6ff0b0ab06cafcf46a47dc753c6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 20:01:18 +0200 Subject: [PATCH 18/30] bump natives to 0.2.5 --- Directory.Packages.props | 2 +- RELEASE-NOTES.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 69000ce2613..92e4a7258df 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -48,7 +48,7 @@ - + diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b51b1c725b3..055f19eb795 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -55,6 +55,7 @@ END TEMPLATE--> ### Other * Add Pure attributes to the EntityLookup bounds methods +* Bump `Robust.Natives` to `0.2.5`. ### Internal From 24da77408e250d9ac67f0b08cfcf204189b655de Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 20:06:10 +0200 Subject: [PATCH 19/30] Release notes for c1919263f4604ad306a95a11f190d0e8ce3c7b4f --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 055f19eb795..db5e6f6ced6 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,6 +47,7 @@ END TEMPLATE--> * Added `Popup.OpenAtCursor()`. * Added `FormattedStringBuilder` for safely constructing markup with code. * Added `VBox` and `HBox` convenience types for more concisely construct `BoxContainer`s. +* Added `IsLocal` to `NetUserData`. ### Bugfixes From 8af60684da330d5abc83685097f6b074eec967a0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 20:34:27 +0200 Subject: [PATCH 20/30] Update release notes --- RELEASE-NOTES.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index db5e6f6ced6..66742c6e5a1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -48,19 +48,29 @@ END TEMPLATE--> * Added `FormattedStringBuilder` for safely constructing markup with code. * Added `VBox` and `HBox` convenience types for more concisely construct `BoxContainer`s. * Added `IsLocal` to `NetUserData`. +* Add `SharedMapSystem.GetFilledTileCount()` +* Add a `track` style property for scroll bars. +* Added `ScrollLock` key. ### Bugfixes -*None yet* +* Fixes override properties in `WrapContainer` not being respected properly. +* Fix `BoxContainer.SeparationOverride` not being respected properly and not invalidating layout. +* Fixed swapped arguments being passed through in various `FindGridsIntersecting` overloads. +* Fixed a doc comment in `LocalizationManager`. ### Other * Add Pure attributes to the EntityLookup bounds methods * Bump `Robust.Natives` to `0.2.5`. +* Minor performance improvement in `IsHardCollidable()`. +* Remove an outdated paragraph from `[DependencyAttribute]` documentation related to `readonly` fields. +* More stock controls use alternative cursor shapes where appropriate. +* Minor performance improvement to audio loading. ### Internal -*None yet* +* Added `.lscache` to `.gitignore`. ## 277.0.0 From 7cfce436333f185333c64dcabb2529e40f1c9f50 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 20 Jun 2026 20:34:37 +0200 Subject: [PATCH 21/30] Version: 277.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index bac96a1d378..e366ed6a67f 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 277.0.0 + 277.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 66742c6e5a1..255d6b4afb0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,25 @@ END TEMPLATE--> ### New features +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 277.1.0 + +### New features + * Console commands can now be "hidden" by prefixing them with `_`. * Add `ESCAPE()` and `ESCAPE-PARAM()` localization functions, for escaping text for markup formatting. * The `[cmdlink /]` tag can now have a tooltip specified with the optional "title" attribute. From 931b2a0989f08ba18274f371fde5cd6b611bef2a Mon Sep 17 00:00:00 2001 From: Axionyx Date: Sun, 21 Jun 2026 22:05:22 +0200 Subject: [PATCH 22/30] Add trust scores for localhost@ and guest@ connections (#6642) Add trust scores for localhost@ and guest@ connections, configurable via cvar --- Robust.Shared/CVars.cs | 12 ++++++++++++ Robust.Shared/Network/NetManager.ServerAuth.cs | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index fa1608e43ba..d4c67e3998d 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -996,6 +996,18 @@ protected CVars() public static readonly CVarDef AuthServer = CVarDef.Create("auth.server", AuthManager.DefaultAuthServer, CVar.SERVERONLY); + /// + /// Trust score for unauthenticated localhost connections + /// + public static readonly CVarDef AuthLocalTrust = + CVarDef.Create("auth.localtrust", 1f, CVar.SERVERONLY); + + /// + /// Trust score for guest connections + /// + public static readonly CVarDef AuthGuestTrust = + CVarDef.Create("auth.guesttrust", 0f, CVar.SERVERONLY); + /* * RENDERING */ diff --git a/Robust.Shared/Network/NetManager.ServerAuth.cs b/Robust.Shared/Network/NetManager.ServerAuth.cs index 175fbfe6f69..7f1303d4b74 100644 --- a/Robust.Shared/Network/NetManager.ServerAuth.cs +++ b/Robust.Shared/Network/NetManager.ServerAuth.cs @@ -221,10 +221,14 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection) _logger.Verbose( $"{connection.RemoteEndPoint}: Assigned user ID: {userId}"); + var localTrust = _config.GetCVar(CVars.AuthLocalTrust); + var guestTrust = _config.GetCVar(CVars.AuthGuestTrust); + userData = new NetUserData(userId, name) { HWId = [], ModernHWIds = [], + Trust = isLocal ? localTrust : guestTrust, IsLocal = isLocal }; } From 31053007fb3eb128ca35e225f70f4daf405ab7bd Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 23 Jun 2026 18:25:21 -0500 Subject: [PATCH 23/30] fix websocket no dispose (#6646) fix pls --- Robust.Client/Network/Transfer/ClientTransferManager.cs | 7 +++++++ .../Network/Transfer/ServerTransferImplWebSocket.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/Robust.Client/Network/Transfer/ClientTransferManager.cs b/Robust.Client/Network/Transfer/ClientTransferManager.cs index 781679d9e63..4f3af82a39b 100644 --- a/Robust.Client/Network/Transfer/ClientTransferManager.cs +++ b/Robust.Client/Network/Transfer/ClientTransferManager.cs @@ -46,6 +46,13 @@ public void Initialize() _netManager.RegisterNetMessage(RxTransferInit, NetMessageAccept.Client | NetMessageAccept.Handshake); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(RxTransferData, NetMessageAccept.Client | NetMessageAccept.Handshake); + _netManager.Disconnect += OnNetDisconnect; + } + + private void OnNetDisconnect(object? sender, NetDisconnectedArgs e) + { + _transferImpl?.Dispose(); + _transferImpl = null; } private async void RxTransferInit(MsgTransferInit message) diff --git a/Robust.Server/Network/Transfer/ServerTransferImplWebSocket.cs b/Robust.Server/Network/Transfer/ServerTransferImplWebSocket.cs index 641b960f3a0..6541aa15374 100644 --- a/Robust.Server/Network/Transfer/ServerTransferImplWebSocket.cs +++ b/Robust.Server/Network/Transfer/ServerTransferImplWebSocket.cs @@ -118,5 +118,7 @@ public async Task HandleApiRequest(NetUserId userId, IStatusHandlerContext conte public override void Dispose() { _connectTcs.TrySetCanceled(); + + base.Dispose(); } } From b8c946a274d7d6e0894ae70de4b953726ea0fa94 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 24 Jun 2026 02:54:57 +0200 Subject: [PATCH 24/30] Release notes --- RELEASE-NOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 255d6b4afb0..6eafd7f6175 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,11 +39,11 @@ END TEMPLATE--> ### New features -*None yet* +* Local and guest trust scores are now assigned via the `auth.localtrust` and `auth.guesttrust` CVars. ### Bugfixes -*None yet* +* Fixed exceptions related to WebSocket transfer system. ### Other From f600a84ad482608c75cbfa8992a9dc9cb569d175 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 24 Jun 2026 02:55:06 +0200 Subject: [PATCH 25/30] Version: 277.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index e366ed6a67f..932b9cd7a5f 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 277.1.0 + 277.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6eafd7f6175..466a43c216a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,11 +39,11 @@ END TEMPLATE--> ### New features -* Local and guest trust scores are now assigned via the `auth.localtrust` and `auth.guesttrust` CVars. +*None yet* ### Bugfixes -* Fixed exceptions related to WebSocket transfer system. +*None yet* ### Other @@ -54,6 +54,17 @@ END TEMPLATE--> *None yet* +## 277.2.0 + +### New features + +* Local and guest trust scores are now assigned via the `auth.localtrust` and `auth.guesttrust` CVars. + +### Bugfixes + +* Fixed exceptions related to WebSocket transfer system. + + ## 277.1.0 ### New features From 6712c4fd4ead566c7e6fd1e5e63922695cc54a01 Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Thu, 25 Jun 2026 20:07:14 +0200 Subject: [PATCH 26/30] fix publish --- .github/workflows/publish-client.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-client.yml b/.github/workflows/publish-client.yml index 4767740a2ff..5319e4bec30 100644 --- a/.github/workflows/publish-client.yml +++ b/.github/workflows/publish-client.yml @@ -33,10 +33,10 @@ jobs: mkdir "release/${{ steps.parse_version.outputs.version }}" mv release/*.zip "release/${{ steps.parse_version.outputs.version }}" - - name: Upload files to Suns + - name: Upload files to Dropwig uses: appleboy/scp-action@master with: - host: suns.spacestation14.com + host: dropwig.playss14.com username: robust-build-push key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }} source: "release/${{ steps.parse_version.outputs.version }}" @@ -46,7 +46,7 @@ jobs: - name: Update manifest JSON uses: appleboy/ssh-action@master with: - host: suns.spacestation14.com + host: dropwig.playss14.com username: robust-build-push key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }} script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }} From 2b63bfdcc0ddde3b71435a4f01407bffe24a2751 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 25 Jun 2026 11:29:40 -0700 Subject: [PATCH 27/30] Update Lidgren.Network --- Lidgren.Network/Lidgren.Network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lidgren.Network/Lidgren.Network b/Lidgren.Network/Lidgren.Network index 1d85b82e058..726dd552a4b 160000 --- a/Lidgren.Network/Lidgren.Network +++ b/Lidgren.Network/Lidgren.Network @@ -1 +1 @@ -Subproject commit 1d85b82e058101b7ebd60cc8883af5359e4c263a +Subproject commit 726dd552a4b104fb2b701848705f1250a073f060 From d71a1cc1b0bf0c29ded1de7cde22ed557c8adeaf Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 25 Jun 2026 11:32:20 -0700 Subject: [PATCH 28/30] Release notes --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 466a43c216a..a249406e42f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed a bug in Lidgren.Network HandleReleasedFragment that could cause out of memory errors. ### Other From f47f6b02bf08bec9be36328208b728f9cff64aad Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 25 Jun 2026 12:36:13 -0700 Subject: [PATCH 29/30] Version: 277.2.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 932b9cd7a5f..22df1e03bc5 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 277.2.0 + 277.2.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a249406e42f..6887d3164b5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixed a bug in Lidgren.Network HandleReleasedFragment that could cause out of memory errors. +*None yet* ### Other @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 277.2.1 + +### Bugfixes + +* Fixed a bug in Lidgren.Network HandleReleasedFragment that could cause out of memory errors. + + ## 277.2.0 ### New features From 01ee260bf6e1c6b0542d559d2fa45355ba56c0e3 Mon Sep 17 00:00:00 2001 From: Darkiich Date: Fri, 3 Jul 2026 16:51:07 +0300 Subject: [PATCH 30/30] try clean better this --- MSBuild/Robust.Engine.Version.props | 12 ++++++++---- RELEASE-NOTES.md | 1 + Robust.Server/runtimeconfig.template.json | 5 +++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 Robust.Server/runtimeconfig.template.json diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 22df1e03bc5..a27c1563a11 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,8 @@ - - - 277.2.1 - + + + + + 277.2.1-leak + + + diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6887d3164b5..570099d67be 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,6 +53,7 @@ END TEMPLATE--> *None yet* +## 277.2.1-leak ## 277.2.1 diff --git a/Robust.Server/runtimeconfig.template.json b/Robust.Server/runtimeconfig.template.json new file mode 100644 index 00000000000..1cf377784bd --- /dev/null +++ b/Robust.Server/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.GC.ConserveMemory": 5 + } +}