From 85fab17d36795499b725802ae15bd0a74e9e59d4 Mon Sep 17 00:00:00 2001 From: DEATHB4DEFEAT Date: Fri, 31 Jan 2025 15:57:53 -0800 Subject: [PATCH 01/29] check launcher data dir --- MSBuild/Robust.Engine.Version.props | 10 ++++++---- Robust.Client/GameControllerOptions.cs | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 872468567..862edc0c6 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,6 @@ - - - 240.1.2 - + + + + 0.0.1 + + diff --git a/Robust.Client/GameControllerOptions.cs b/Robust.Client/GameControllerOptions.cs index d386b3906..fdb0b2b2d 100644 --- a/Robust.Client/GameControllerOptions.cs +++ b/Robust.Client/GameControllerOptions.cs @@ -1,3 +1,4 @@ +using System; using Robust.Shared; using Robust.Shared.Utility; @@ -19,7 +20,7 @@ public sealed class GameControllerOptions /// /// Name the userdata directory will have. /// - public string UserDataDirectoryName { get; init; } = "Space Station 14"; + public string UserDataDirectoryName { get; init; } = Environment.GetEnvironmentVariable("SS14_LAUNCHER_DATADIR") ?? "SimpleStation14"; /// /// Name of the configuration file in the user data directory. From 1bf068767181875126c3d53004dc29fa0a444f35 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 1 Feb 2025 15:19:34 +1100 Subject: [PATCH 02/29] Fix poly pooling (#5645) --- .../Physics/Collision/DistanceProxy.cs | 4 ++-- Robust.Shared/Physics/Shapes/Polygon.cs | 10 ++++++++-- .../Systems/SharedPhysicsSystem.Pool.cs | 19 +++++++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Robust.Shared/Physics/Collision/DistanceProxy.cs b/Robust.Shared/Physics/Collision/DistanceProxy.cs index 2438c50fe..60603a31c 100644 --- a/Robust.Shared/Physics/Collision/DistanceProxy.cs +++ b/Robust.Shared/Physics/Collision/DistanceProxy.cs @@ -71,13 +71,13 @@ internal void Set(T shape, int index) where T : IPhysShape case ShapeType.Polygon: if (shape is Polygon poly) { - Vertices = poly.Vertices; + Vertices = poly.Vertices.AsSpan()[..poly.VertexCount]; Radius = poly.Radius; } else { var polyShape = Unsafe.As(shape); - Vertices = polyShape.Vertices; + Vertices = polyShape.Vertices.AsSpan()[..polyShape.VertexCount]; Radius = polyShape.Radius; } diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs index b9a42a143..c96585b62 100644 --- a/Robust.Shared/Physics/Shapes/Polygon.cs +++ b/Robust.Shared/Physics/Shapes/Polygon.cs @@ -16,7 +16,7 @@ internal record struct Polygon : IPhysShape [DataField] public Vector2[] Vertices; - public int VertexCount => Vertices.Length; + public byte VertexCount; public Vector2[] Normals; @@ -42,17 +42,19 @@ public Polygon(PolygonShape polyShape) Array.Copy(polyShape.Vertices, Vertices, Vertices.Length); Array.Copy(polyShape.Normals, Normals, Vertices.Length); + VertexCount = (byte) Vertices.Length; } /// /// Manually constructed polygon for internal use to take advantage of pooling. /// - internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid) + internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid, byte count) { Unsafe.SkipInit(out this); Vertices = vertices; Normals = normals; Centroid = centroid; + VertexCount = count; } public Polygon(Box2 aabb) @@ -72,6 +74,7 @@ public Polygon(Box2 aabb) Normals[2] = new Vector2(0.0f, 1.0f); Normals[3] = new Vector2(-1.0f, 0.0f); + VertexCount = 4; Centroid = aabb.Center; } @@ -89,6 +92,7 @@ public Polygon(Box2Rotated bounds) CalculateNormals(Normals, 4); + VertexCount = 4; Centroid = bounds.Center; } @@ -99,11 +103,13 @@ public Polygon(Vector2[] vertices) if (hull.Count < 3) { + VertexCount = 0; Vertices = []; Normals = []; return; } + VertexCount = (byte) vertices.Length; Vertices = vertices; Normals = new Vector2[vertices.Length]; Set(hull); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs index e8107174e..ef946d3b8 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Pool.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Numerics; +using Microsoft.Extensions.ObjectPool; using Robust.Shared.Maths; using Robust.Shared.Physics.Shapes; @@ -7,15 +8,13 @@ namespace Robust.Shared.Physics.Systems; public abstract partial class SharedPhysicsSystem { - private ArrayPool _vectorPool = ArrayPool.Create(4, 128); - /// /// Gets a polygon with pooled arrays backing it. /// internal Polygon GetPooled(Box2 box) { - var vertices = _vectorPool.Rent(4); - var normals = _vectorPool.Rent(4); + var vertices = ArrayPool.Shared.Rent(4); + var normals = ArrayPool.Shared.Rent(4); var centroid = box.Center; vertices[0] = box.BottomLeft; @@ -28,13 +27,13 @@ internal Polygon GetPooled(Box2 box) normals[2] = new Vector2(0.0f, 1.0f); normals[3] = new Vector2(-1.0f, 0.0f); - return new Polygon(vertices, normals, centroid); + return new Polygon(vertices, normals, centroid, 4); } internal Polygon GetPooled(Box2Rotated box) { - var vertices = _vectorPool.Rent(4); - var normals = _vectorPool.Rent(4); + var vertices = ArrayPool.Shared.Rent(4); + var normals = ArrayPool.Shared.Rent(4); var centroid = box.Center; vertices[0] = box.BottomLeft; @@ -42,7 +41,7 @@ internal Polygon GetPooled(Box2Rotated box) vertices[2] = box.TopRight; vertices[3] = box.TopLeft; - var polygon = new Polygon(vertices, normals, centroid); + var polygon = new Polygon(vertices, normals, centroid, 4); polygon.CalculateNormals(normals, 4); return polygon; @@ -50,7 +49,7 @@ internal Polygon GetPooled(Box2Rotated box) internal void ReturnPooled(Polygon polygon) { - _vectorPool.Return(polygon.Vertices); - _vectorPool.Return(polygon.Normals); + ArrayPool.Shared.Return(polygon.Vertices); + ArrayPool.Shared.Return(polygon.Normals); } } From bd69d51d360ec77e79ca173e01caabb0a31a0a23 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 2 Feb 2025 12:29:24 +1100 Subject: [PATCH 03/29] Version: 242.0.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 17e584b6a..8a0f0d468 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 242.0.0 + 242.0.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 35160cd61..90c9366f4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute` +*None yet* ### Other @@ -54,6 +54,18 @@ END TEMPLATE--> *None yet* +## 242.0.1 + +### Bugfixes + +* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute` +* Fix the pooled polygons using incorrect vertices for EntityLookup and MapManager. + +### Internal + +* Avoid normalizing angles constructed from vectors. + + ## 242.0.0 ### Breaking changes From 2719b9f0c865a311e124927cc42ccba6a15688d9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:15:36 +1100 Subject: [PATCH 04/29] Tweak grid state slightly (#5644) --- .../GameObjects/Systems/SharedMapSystem.Grid.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs index 53cd5af40..7af2d9cb4 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs @@ -312,7 +312,7 @@ private void ApplyChunkData( if (data.IsDeleted()) { - if (!component.Chunks.TryGetValue(index, out var deletedChunk)) + if (!component.Chunks.Remove(index, out var deletedChunk)) return; // Deleted chunks still need to raise tile-changed events. @@ -330,11 +330,13 @@ private void ApplyChunkData( } } - component.Chunks.Remove(index); return; } var chunk = GetOrAddChunk(uid, component, index); + chunk.Fixtures.Clear(); + chunk.Fixtures.UnionWith(data.Fixtures); + chunk.SuppressCollisionRegeneration = true; DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty)); DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize); @@ -355,12 +357,6 @@ private void ApplyChunkData( // These should never refer to the same object DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures); - if (!chunk.Fixtures.SetEquals(data.Fixtures)) - { - chunk.Fixtures.Clear(); - chunk.Fixtures.UnionWith(data.Fixtures); - } - chunk.CachedBounds = data.CachedBounds!.Value; chunk.SuppressCollisionRegeneration = false; } From fc55c8e0d3ff484158ab54463fa6ad8deba16cb2 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:19:22 +1100 Subject: [PATCH 05/29] Add saved BUI positions (#5650) --- RELEASE-NOTES.md | 4 +- .../EntitySystems/UserInterfaceSystem.cs | 61 +++++++++++++++++++ .../UserInterface/BoundUserInterfaceExt.cs | 40 +++++++++++- .../CustomControls/BaseWindow.cs | 13 +++- .../UserInterface/BoundUserInterface.cs | 2 +- .../Systems/SharedUserInterfaceSystem.cs | 52 +++++++++++++--- 6 files changed, 156 insertions(+), 16 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 90c9366f4..361d04eb0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,11 +35,11 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* RemoveChild is called after OnClose for BaseWindow. ### New features -*None yet* +* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow` helper or via manually registering it via RegisterControl. ### Bugfixes diff --git a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs index 7c290cb4e..4e7fb31f2 100644 --- a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -1,10 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Robust.Client.UserInterface; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Robust.Client.GameObjects; public sealed class UserInterfaceSystem : SharedUserInterfaceSystem { + private Dictionary> _savedPositions = new(); + private Dictionary _registeredControls = new(); + public override void Initialize() { base.Initialize(); @@ -17,6 +25,59 @@ public override void Shutdown() ProtoManager.PrototypesReloaded -= OnProtoReload; } + protected override void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) + { + base.OnUserInterfaceShutdown(ent, ref args); + _savedPositions.Remove(ent.Owner); + } + + /// + public override void OpenUi(Entity entity, Enum key, bool predicted = false) + { + var player = Player.LocalEntity; + + if (player == null) + return; + + OpenUi(entity, key, player.Value, predicted); + } + + protected override void SavePosition(BoundUserInterface bui) + { + if (!_registeredControls.Remove(bui, out var control)) + return; + + var keyed = _savedPositions[bui.Owner]; + keyed[bui.UiKey] = control.Position; + } + + /// + /// Registers a control so it will later have its position stored by when the BUI is closed. + /// + public void RegisterControl(BoundUserInterface bui, Control control) + { + DebugTools.Assert(!_registeredControls.ContainsKey(bui)); + _registeredControls[bui] = control; + _savedPositions.GetOrNew(bui.Owner); + } + + public override bool TryGetPosition(Entity entity, Enum key, out Vector2 position) + { + position = default; + + if (!_savedPositions.TryGetValue(entity.Owner, out var keyed)) + { + return false; + } + + if (!keyed.TryGetValue(key, out position)) + { + return false; + } + + return true; + } + private void OnProtoReload(PrototypesReloadedEventArgs obj) { var player = Player.LocalEntity; diff --git a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs index eb59385a3..54a2b635f 100644 --- a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs +++ b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs @@ -1,4 +1,5 @@ using System; +using Robust.Client.GameObjects; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; @@ -6,14 +7,47 @@ namespace Robust.Client.UserInterface; public static class BoundUserInterfaceExt { + private static T GetWindow(BoundUserInterface bui) where T : BaseWindow, new() + { + var window = bui.CreateDisposableControl(); + window.OnClose += bui.Close; + var system = bui.EntMan.System(); + system.RegisterControl(bui, window); + return window; + } + /// /// Helper method to create a window and also handle closing the BUI when it's closed. /// public static T CreateWindow(this BoundUserInterface bui) where T : BaseWindow, new() { - var window = bui.CreateDisposableControl(); - window.OpenCentered(); - window.OnClose += bui.Close; + var window = GetWindow(bui); + + if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position)) + { + window.Open(position); + } + else + { + window.OpenCentered(); + } + + return window; + } + + public static T CreateWindowCenteredLeft(this BoundUserInterface bui) where T : BaseWindow, new() + { + var window = GetWindow(bui); + + if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position)) + { + window.Open(position); + } + else + { + window.OpenCenteredLeft(); + } + return window; } diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs index 2e416f871..fa650834d 100644 --- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs @@ -5,6 +5,7 @@ using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; @@ -36,8 +37,8 @@ public virtual void Close() return; } - Parent.RemoveChild(this); OnClose?.Invoke(); + Parent.RemoveChild(this); } protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) @@ -229,6 +230,16 @@ public void Open() OnOpen?.Invoke(); } + /// + /// Opens the window and places it at the specified position. + /// + public void Open(Vector2 position) + { + Measure(Vector2Helpers.Infinity); + Open(); + LayoutContainer.SetPosition(this, position); + } + public void OpenCentered() => OpenCenteredAt(new Vector2(0.5f, 0.5f)); public void OpenToLeft() => OpenCenteredAt(new Vector2(0, 0.5f)); diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index 9bdc7b208..ea96e6f70 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -11,7 +11,7 @@ namespace Robust.Shared.GameObjects /// public abstract class BoundUserInterface : IDisposable { - [Dependency] protected readonly IEntityManager EntMan = default!; + [Dependency] protected internal readonly IEntityManager EntMan = default!; [Dependency] protected readonly ISharedPlayerManager PlayerManager = default!; protected readonly SharedUserInterfaceSystem UiSystem; diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 540867900..70f2e0773 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using System.Runtime.InteropServices; using JetBrains.Annotations; using Robust.Shared.Collections; @@ -45,6 +46,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem /// private ValueList _keys = new(); + private ValueList _entList = new(); + public override void Initialize() { base.Initialize(); @@ -86,6 +89,11 @@ public override void Initialize() SubscribeLocalEvent(OnActorShutdown); } + private void AddQueued(BoundUserInterface bui, bool value) + { + _queuedBuis.Add((bui, value)); + } + /// /// Validates the received message, and then pass it onto systems/components /// @@ -232,7 +240,7 @@ private void CloseUiInternal(Entity ent, Enum key, Enti if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) { - _queuedBuis.Add((cBui, false)); + AddQueued(cBui, false); } if (ent.Comp.Actors.Count == 0) @@ -274,18 +282,17 @@ private void OnUserInterfaceStartup(Entity ent, ref Comp // PlayerAttachedEvent will catch some of these. foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces) { - _queuedBuis.Add((bui, true)); + AddQueued(bui, true); } } - private void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) + protected virtual void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) { - var actors = new List(); foreach (var (key, acts) in ent.Comp.Actors) { - actors.Clear(); - actors.AddRange(acts); - foreach (var actor in actors) + _entList.Clear(); + _entList.AddRange(acts); + foreach (var actor in _entList) { CloseUiInternal(ent!, key, actor); DebugTools.Assert(!acts.Contains(actor)); @@ -456,7 +463,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref } var bui = ent.Comp.ClientOpenInterfaces[key]; - _queuedBuis.Add((bui, false)); + AddQueued(bui, false); } } @@ -537,7 +544,7 @@ private void EnsureClientBui(Entity entity, Enum key, In if (!open) return; - _queuedBuis.Add((boundUserInterface, true)); + AddQueued(boundUserInterface, true); } /// @@ -653,6 +660,14 @@ public bool TryOpenUi(Entity entity, Enum key, EntityUi return true; } + /// + /// Opens the UI for the local client. Does nothing on server. + /// + public virtual void OpenUi(Entity entity, Enum key, bool predicted = false) + { + + } + public void OpenUi(Entity entity, Enum key, EntityUid? actor, bool predicted = false) { if (actor == null || !UIQuery.Resolve(entity.Owner, ref entity.Comp, false)) @@ -690,6 +705,23 @@ public void OpenUi(Entity entity, Enum key, ICommonSess OpenUi(entity, key, actorEnt.Value, predicted); } + /// + /// Tries to return the saved position of a user interface. + /// + public virtual bool TryGetPosition(Entity entity, Enum key, out Vector2 position) + { + position = Vector2.Zero; + return false; + } + + /// + /// Saves a position for the BUI. + /// + protected virtual void SavePosition(BoundUserInterface bui) + { + + } + /// /// Sets a BUI state and networks it to all clients. /// @@ -1056,6 +1088,7 @@ public override void Update(float frameTime) } #endif } + // Close BUI else { if (UIQuery.TryComp(bui.Owner, out var uiComp)) @@ -1063,6 +1096,7 @@ public override void Update(float frameTime) uiComp.ClientOpenInterfaces.Remove(bui.UiKey); } + SavePosition(bui); bui.Dispose(); } } From 6a336d236b97d8460efc83fcd799471bdcf01320 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 4 Feb 2025 00:25:15 +1100 Subject: [PATCH 06/29] Version: 243.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 8a0f0d468..b15a8423c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 242.0.1 + 243.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 361d04eb0..4ac40839e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,11 +35,11 @@ END TEMPLATE--> ### Breaking changes -* RemoveChild is called after OnClose for BaseWindow. +*None yet* ### New features -* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow` helper or via manually registering it via RegisterControl. +*None yet* ### Bugfixes @@ -54,6 +54,21 @@ END TEMPLATE--> *None yet* +## 243.0.0 + +### Breaking changes + +* RemoveChild is called after OnClose for BaseWindow. + +### New features + +* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow` helper or via manually registering it via RegisterControl. + +### Other + +* Ensure grid fixtures get updated in client state handling even if exceptions occur. + + ## 242.0.1 ### Bugfixes From c4a5752c2affee874f0b9be3b07b86a55a91bf0a Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Tue, 4 Feb 2025 18:23:42 +0100 Subject: [PATCH 07/29] Shared portion of PvsOverrideSystem to allow for usage in shared (#5651) --- Robust.Client/GameStates/PvsOverrideSystem.cs | 5 ++ Robust.Server/GameStates/PvsOverrideSystem.cs | 23 ++++++--- .../GameStates/SharedPvsOverrideSystem.cs | 50 +++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 Robust.Client/GameStates/PvsOverrideSystem.cs create mode 100644 Robust.Shared/GameStates/SharedPvsOverrideSystem.cs diff --git a/Robust.Client/GameStates/PvsOverrideSystem.cs b/Robust.Client/GameStates/PvsOverrideSystem.cs new file mode 100644 index 000000000..5b3dfc12d --- /dev/null +++ b/Robust.Client/GameStates/PvsOverrideSystem.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameStates; + +namespace Robust.Client.GameStates; + +public sealed partial class PvsOverrideSystem : SharedPvsOverrideSystem; diff --git a/Robust.Server/GameStates/PvsOverrideSystem.cs b/Robust.Server/GameStates/PvsOverrideSystem.cs index bef471af0..187e264c2 100644 --- a/Robust.Server/GameStates/PvsOverrideSystem.cs +++ b/Robust.Server/GameStates/PvsOverrideSystem.cs @@ -5,13 +5,14 @@ using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Robust.Server.GameStates; -public sealed class PvsOverrideSystem : EntitySystem +public sealed class PvsOverrideSystem : SharedPvsOverrideSystem { [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IConsoleHost _console = default!; @@ -134,8 +135,10 @@ private void Clear(EntityUid uid) /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations, /// causing them to always be sent to all clients. /// - public void AddGlobalOverride(EntityUid uid) + public override void AddGlobalOverride(EntityUid uid) { + base.AddGlobalOverride(uid); + if (GlobalOverride.Add(uid)) _hasOverride.Add(uid); } @@ -143,8 +146,10 @@ public void AddGlobalOverride(EntityUid uid) /// /// Removes an entity from the global overrides. /// - public void RemoveGlobalOverride(EntityUid uid) + public override void RemoveGlobalOverride(EntityUid uid) { + base.RemoveGlobalOverride(uid); + GlobalOverride.Remove(uid); // Not bothering to clear _hasOverride, as we'd have to check all the other collections, and at that point we // might as well just do that when the entity gets deleted anyways. @@ -203,8 +208,10 @@ public void RemoveForceSend(EntityUid uid, ICommonSession session) /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a /// specific session. /// - public void AddSessionOverride(EntityUid uid, ICommonSession session) + public override void AddSessionOverride(EntityUid uid, ICommonSession session) { + base.AddSessionOverride(uid, session); + if (SessionOverrides.GetOrNew(session).Add(uid)) _hasOverride.Add(uid); } @@ -212,8 +219,10 @@ public void AddSessionOverride(EntityUid uid, ICommonSession session) /// /// Removes an entity from a session's overrides. /// - public void RemoveSessionOverride(EntityUid uid, ICommonSession session) + public override void RemoveSessionOverride(EntityUid uid, ICommonSession session) { + base.RemoveSessionOverride(uid, session); + if (!SessionOverrides.TryGetValue(session, out var overrides)) return; @@ -228,8 +237,10 @@ public void RemoveSessionOverride(EntityUid uid, ICommonSession session) /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations, /// causing them to always be sent to all clients. /// - public void AddSessionOverrides(EntityUid uid, Filter filter) + public override void AddSessionOverrides(EntityUid uid, Filter filter) { + base.AddSessionOverrides(uid, filter); + foreach (var session in filter.Recipients) { AddSessionOverride(uid, session); diff --git a/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs b/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs new file mode 100644 index 000000000..93d76dde7 --- /dev/null +++ b/Robust.Shared/GameStates/SharedPvsOverrideSystem.cs @@ -0,0 +1,50 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Player; + +namespace Robust.Shared.GameStates; + +public abstract class SharedPvsOverrideSystem : EntitySystem +{ + /// + /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations, + /// causing them to always be sent to all clients. + /// + public virtual void AddGlobalOverride(EntityUid uid) + { + + } + + /// + /// Removes an entity from the global overrides. + /// + public virtual void RemoveGlobalOverride(EntityUid uid) + { + + } + + /// + /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a + /// specific session. + /// + public virtual void AddSessionOverride(EntityUid uid, ICommonSession session) + { + + } + + /// + /// Removes an entity from a session's overrides. + /// + public virtual void RemoveSessionOverride(EntityUid uid, ICommonSession session) + { + + } + + /// + /// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations, + /// causing them to always be sent to all clients. + /// + public virtual void AddSessionOverrides(EntityUid uid, Filter filter) + { + + } +} From d2aa8ecb5a0687ec9938d955e3f8a11a3057cae1 Mon Sep 17 00:00:00 2001 From: Mohamed Dwidar <85836470+PROG-MohamedDwidar@users.noreply.github.com> Date: Sat, 8 Feb 2025 08:14:08 +0200 Subject: [PATCH 08/29] Fixing guidebook not resizable from left and right (#5618) * Fixing guidebook not resizable from left and right issue 34504 in space-wizards/space-station-14 needs this fix * update fix guideBood not resizable a safer and more error resistent solution to https://github.com/space-wizards/space-station-14/issues/34504 * Mask DragMode.Move --------- Co-authored-by: ElectroJr --- RELEASE-NOTES.md | 2 +- .../UserInterface/CustomControls/BaseWindow.cs | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4ac40839e..b41638ece 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape. ### Other diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs index fa650834d..deb3d338e 100644 --- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs @@ -103,6 +103,8 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args) { var cursor = CursorShape.Arrow; var previewDragMode = GetDragModeFor(args.RelativePosition); + previewDragMode &= ~DragMode.Move; + switch (previewDragMode) { case DragMode.Top: @@ -117,9 +119,6 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args) case DragMode.Bottom | DragMode.Left: case DragMode.Top | DragMode.Right: - cursor = CursorShape.Crosshair; - break; - case DragMode.Bottom | DragMode.Right: case DragMode.Top | DragMode.Left: cursor = CursorShape.Crosshair; @@ -161,15 +160,6 @@ protected internal override void MouseMove(GUIMouseMoveEventArgs args) var rect = new UIBox2(left, top, right, bottom); LayoutContainer.SetPosition(this, rect.TopLeft); SetSize = rect.Size; - - /* - var timing = IoCManager.Resolve(); - - var l = GetValue(LayoutContainer.MarginLeftProperty); - var t = GetValue(LayoutContainer.MarginTopProperty); - - Logger.Debug($"{timing.CurFrame}: {rect.TopLeft}/({l}, {t}), {rect.Size}/{SetSize}"); - */ } } From 51971d0994d2b1689ec37709138a1bb9b4106961 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:38:40 +1100 Subject: [PATCH 09/29] Revert basewindow change (#5664) --- Robust.Client/UserInterface/CustomControls/BaseWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs index deb3d338e..42184c87d 100644 --- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs @@ -37,8 +37,8 @@ public virtual void Close() return; } - OnClose?.Invoke(); Parent.RemoveChild(this); + OnClose?.Invoke(); } protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) From 1a7e490e4b00200bfa3ebebc6d9b12cb1ec82adf Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 8 Feb 2025 19:06:50 +1100 Subject: [PATCH 10/29] Version: 243.0.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index b15a8423c..50b7d1bdd 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 243.0.0 + 243.0.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b41638ece..1102817bf 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape. +*None yet* ### Other @@ -54,6 +54,14 @@ END TEMPLATE--> *None yet* +## 243.0.1 + +### Bugfixes + +* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape. +* Revert `BaseWindow` OnClose ordering due to prior reliance upon the ordering. + + ## 243.0.0 ### Breaking changes From ae7725aafe684a4866601d3d96082951f21c06a9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:39:41 +1100 Subject: [PATCH 11/29] Add compreg methods to entitymanager (#5655) --- RELEASE-NOTES.md | 2 +- .../EntityManager/HasComponentBenchmark.cs | 96 +++++++++++++++++++ .../GameObjects/EntityManager.Components.cs | 61 ++++++++++-- .../GameObjects/IEntityManager.Components.cs | 17 ++++ 4 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1102817bf..3acf87395 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods. ### Bugfixes diff --git a/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs new file mode 100644 index 000000000..e3afb8e79 --- /dev/null +++ b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs @@ -0,0 +1,96 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using JetBrains.Annotations; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.EntityManager; + +[Virtual] +public partial class HasComponentBenchmark +{ + private static readonly Consumer Consumer = new(); + + private ISimulation _simulation = default!; + private IEntityManager _entityManager = default!; + + private ComponentRegistration _compReg = default!; + + private A _dummyA = new(); + + [UsedImplicitly] + [Params(1, 10, 100, 1000)] + public int N; + + [GlobalSetup] + public void GlobalSetup() + { + _simulation = RobustServerSimulation + .NewSimulation() + .RegisterComponents(f => f.RegisterClass()) + .InitializeInstance(); + + _entityManager = _simulation.Resolve(); + var map = _simulation.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); + _compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A)); + + for (var i = 0; i < N; i++) + { + var uid = _entityManager.SpawnEntity(null, coords); + _entityManager.AddComponent(uid); + } + } + + [Benchmark] + public void HasComponentGeneric() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentCompReg() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid, _compReg); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentType() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid, typeof(A)); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentGetType() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var type = _dummyA.GetType(); + var result = _entityManager.HasComponent(uid, type); + Consumer.Consume(result); + } + } + + [ComponentProtoName("A")] + public sealed partial class A : Component + { + } +} diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 95889c291..ec99bd4da 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -197,17 +197,23 @@ public void AddComponents(EntityUid target, ComponentRegistry registry, bool rem { var reg = _componentFactory.GetRegistration(name); - if (HasComponent(target, reg.Type)) + if (removeExisting) { - if (!removeExisting) + var comp = _componentFactory.GetComponent(reg); + _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); + AddComponentInternal(target, comp, reg, overwrite: true, metadata: metadata); + } + else + { + if (HasComponent(target, reg)) + { continue; + } - RemoveComponent(target, reg.Type, metadata); + var comp = _componentFactory.GetComponent(reg); + _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); + AddComponentInternal(target, comp, reg, overwrite: false, metadata: metadata); } - - var comp = _componentFactory.GetComponent(reg); - _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); - AddComponent(target, comp, metadata: metadata); } } @@ -315,6 +321,22 @@ public void AddComponent(EntityUid uid, T component, bool overwrite = false, AddComponentInternal(uid, component, overwrite, false, metadata); } + private void AddComponentInternal( + EntityUid uid, + T component, + ComponentRegistration compReg, + bool overwrite = false, + MetaDataComponent? metadata = null) where T : IComponent + { + if (!MetaQuery.Resolve(uid, ref metadata, false)) + throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid)); + + DebugTools.Assert(component.Owner == default); + component.Owner = uid; + + AddComponentInternal(uid, component, compReg, overwrite, skipInit: false, metadata); + } + private void AddComponentInternal(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent { if (!MetaQuery.ResolveInternal(uid, ref metadata, false)) @@ -731,6 +753,14 @@ public bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : ICompo return uid.HasValue && HasComponent(uid.Value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool HasComponent(EntityUid uid, ComponentRegistration reg) + { + var dict = _entTraitArray[reg.Idx.Value]; + return dict.TryGetValue(uid, out var comp) && !comp.Deleted; + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Pure] @@ -943,6 +973,23 @@ public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen( return false; } + /// + public bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component) + { + var dict = _entTraitArray[reg.Idx.Value]; + if (dict.TryGetValue(uid, out var comp)) + { + if (!comp.Deleted) + { + component = comp; + return true; + } + } + + component = null; + return false; + } + /// public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component) { diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 84b47c53f..11937cde0 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -176,6 +176,14 @@ public partial interface IEntityManager /// True if the entity has the component type, otherwise false. bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : IComponent; + /// + /// Checks if the entity has a component type. + /// + /// Entity UID to check. + /// The component registration to check for. + /// True if the entity has the component type, otherwise false. + bool HasComponent(EntityUid uid, ComponentRegistration reg); + /// /// Checks if the entity has a component type. /// @@ -294,6 +302,15 @@ public partial interface IEntityManager /// If the component existed in the entity. bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?; + /// + /// Returns the component of a specific type. + /// + /// Entity UID to check. + /// The component registration to check for. + /// Component of the specified type (if exists). + /// If the component existed in the entity. + bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component); + /// /// Returns the component of a specific type. /// From 6979a63b1ede95b8487e00d731c22e4f0134b0be Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:02:31 +1100 Subject: [PATCH 12/29] Add CreateWindowCenteredRight method (#5666) --- .../UserInterface/BoundUserInterfaceExt.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs index 54a2b635f..9cea88e74 100644 --- a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs +++ b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs @@ -51,6 +51,22 @@ public static class BoundUserInterfaceExt return window; } + public static T CreateWindowCenteredRight(this BoundUserInterface bui) where T : BaseWindow, new() + { + var window = GetWindow(bui); + + if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position)) + { + window.Open(position); + } + else + { + window.OpenCenteredRight(); + } + + return window; + } + /// /// Creates a control for this BUI that will be disposed when it is disposed. /// From 9e9ac56c956537a11db5dc349d4996ffbcb6d166 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:54:21 +1100 Subject: [PATCH 13/29] Bump physics speed cap (#5667) --- Robust.Shared/CVars.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 0a0f0d4fc..8e657a7e6 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1349,10 +1349,10 @@ protected CVars() /// MaxLinVelocity is compared to the dot product of linearVelocity * frameTime. /// /// - /// Default is 35 m/s. Around half a tile per tick at 60 ticks per second. + /// Default is 400 m/s in-line with Box2c. Box2d used 120m/s. /// public static readonly CVarDef MaxLinVelocity = - CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED); + CVarDef.Create("physics.maxlinvelocity", 400f, CVar.SERVER | CVar.REPLICATED); /// /// Maximum angular velocity in full rotations per second. @@ -1364,7 +1364,6 @@ protected CVars() public static readonly CVarDef MaxAngVelocity = CVarDef.Create("physics.maxangvelocity", 15f); - /* * User interface */ From a734bc50fa15cc4967a38daf081e428af597904f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:50:33 +1100 Subject: [PATCH 14/29] Avoid unnecessary UpdateState calls for BUIs (#5670) --- .../Components/UserInterface/BoundUserInterface.cs | 10 ++++++++++ .../GameObjects/Systems/SharedUserInterfaceSystem.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index ea96e6f70..f2cd0f1f0 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -15,6 +15,8 @@ public abstract class BoundUserInterface : IDisposable [Dependency] protected readonly ISharedPlayerManager PlayerManager = default!; protected readonly SharedUserInterfaceSystem UiSystem; + public bool IsOpened { get; protected set; } + public readonly Enum UiKey; public EntityUid Owner { get; } @@ -43,6 +45,10 @@ protected BoundUserInterface(EntityUid owner, Enum uiKey) /// protected internal virtual void Open() { + if (IsOpened) + return; + + IsOpened = true; } /// @@ -93,6 +99,10 @@ protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message /// public void Close() { + if (!IsOpened) + return; + + IsOpened = false; UiSystem.CloseUi(Owner, UiKey, PlayerManager.LocalEntity, predicted: true); } diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 70f2e0773..6c950f3fc 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -487,7 +487,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref ent.Comp.States[key] = buiState; - if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) + if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui) || !cBui.IsOpened) continue; cBui.State = buiState; From bb9517fd193dd48b64d198547ca69730cbb28153 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:29:19 +1100 Subject: [PATCH 15/29] Check equatable on client-predicted BUI states (#5671) --- .../GameObjects/Systems/SharedUserInterfaceSystem.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 6c950f3fc..8c4dd88e9 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -755,8 +755,11 @@ public void SetUiState(Entity entity, Enum key, BoundUs // Predict the change on client if (state != null && _netManager.IsClient && entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var bui)) { - bui.UpdateState(state); - bui.Update(); + if (bui.State?.Equals(state) != true) + { + bui.UpdateState(state); + bui.Update(); + } } DirtyField(entity, nameof(UserInterfaceComponent.States)); From fea592e1d53b043bda73a66248576caa6e3f65dc Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 12 Feb 2025 13:31:03 +1100 Subject: [PATCH 16/29] Version: 244.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 50b7d1bdd..21fd8a1ba 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 243.0.1 + 244.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3acf87395..2d07310b9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods. +*None yet* ### Bugfixes @@ -54,6 +54,22 @@ END TEMPLATE--> *None yet* +## 244.0.0 + +### Breaking changes + +* Increase physics speedcap default from 35m/s to 400m/s in-line with box2d v3. + +### New features + +* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods. +* Add CreateWindowCenteredRight for BUIs. + +### Bugfixes + +* Avoid calling UpdateState before opening a BUI. + + ## 243.0.1 ### Bugfixes From d442d90d60fddb6d192252c2afffda5d2943a30f Mon Sep 17 00:00:00 2001 From: Milon Date: Thu, 13 Feb 2025 07:41:42 +0100 Subject: [PATCH 17/29] no more (#5676) --- .../GameObjects/Components/UserInterface/BoundUserInterface.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index f2cd0f1f0..e55c2aa05 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -43,6 +43,7 @@ protected BoundUserInterface(EntityUid owner, Enum uiKey) /// Invoked when the UI is opened. /// Do all creation and opening of things like windows in here. /// + [MustCallBase] protected internal virtual void Open() { if (IsOpened) From afaef645b0881bde3d5c3450cff644e40a9c4ac1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:10:13 +1100 Subject: [PATCH 18/29] Fix MappingDataNode.TryAddCopy() (#5677) --- RELEASE-NOTES.md | 2 +- Robust.Server/Upload/GamePrototypeLoadManager.cs | 3 +++ .../Manager/SerializationManager.Composition.cs | 15 ++++++++++++--- .../Markdown/Mapping/MappingDataNode.cs | 7 ++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2d07310b9..86cfb6b71 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs. ### Other diff --git a/Robust.Server/Upload/GamePrototypeLoadManager.cs b/Robust.Server/Upload/GamePrototypeLoadManager.cs index 3d6500af5..8912624bc 100644 --- a/Robust.Server/Upload/GamePrototypeLoadManager.cs +++ b/Robust.Server/Upload/GamePrototypeLoadManager.cs @@ -50,6 +50,9 @@ protected override void LoadPrototypeData(GamePrototypeLoadMessage message) internal void SendToNewUser(INetChannel channel) { + if (LoadedPrototypes.Count == 0) + return; + // Just dump all the prototypes on connect, before them missing could be an issue. var msg = new GamePrototypeLoadMessage { diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs index 397cf084c..b0b4c459f 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs @@ -26,14 +26,23 @@ private delegate DataNode PushCompositionDelegate( public DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null) { + // TODO SERIALIZATION + // Add variant that doesn't require a parent array. + + // TODO SERIALIZATION + // Change inheritance pushing so that it modifies the passed in child. This avoids re-creating the child + // multiple times when there are multiple children. + // + // I.e., change the PushCompositionDelegate signature to not have a return value, and also add an override + // of this method that modified the given child. + if (parents.Length == 0) return child.Copy(); DebugTools.Assert(parents.All(x => x.GetType() == child.GetType())); - // TODO SERIALIZATION - // Change inheritance pushing so that it modifies the passed in child. - // I.e., move the child.Clone() statement to the beginning here, then make the delegate modify the clone. + + // the child.Clone() statement to the beginning here, then make the delegate modify the clone. // Currently pusing more than one parent requires multiple unnecessary clones. var pusher = GetOrCreatePushCompositionDelegate(type, child); diff --git a/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs b/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs index bd4a35be6..fe605f6e3 100644 --- a/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs +++ b/Robust.Shared/Serialization/Markdown/Mapping/MappingDataNode.cs @@ -400,7 +400,11 @@ public bool Remove(KeyValuePair item) public bool TryAdd(DataNode key, DataNode value) { - return _children.TryAdd(key, value); + if (!_children.TryAdd(key, value)) + return false; + + _list.Add(new(key, value)); + return true; } public bool TryAddCopy(DataNode key, DataNode value) @@ -410,6 +414,7 @@ public bool TryAddCopy(DataNode key, DataNode value) return false; entry = value.Copy(); + _list.Add(new(key, entry)); return true; } } From 55571ef5b131dfa6f00a38e71a356ded34ca5911 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 14 Feb 2025 16:11:50 +1300 Subject: [PATCH 19/29] Version: 245.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 21fd8a1ba..52d55e729 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 244.0.0 + 245.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 86cfb6b71..208cd2310 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs. +*None yet* ### Other @@ -54,6 +54,17 @@ END TEMPLATE--> *None yet* +## 245.0.0 + +### Breaking changes + +* `BoundUserInterface.Open()` now has the `MustCallBase` attribute + +### Bugfixes + +* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs. + + ## 244.0.0 ### Breaking changes From 0c41a041e376cf90b7b35d5a18bd0bd066f01bb5 Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Fri, 14 Feb 2025 04:21:22 +0100 Subject: [PATCH 20/29] Move `ParseObject` method into a public class for content to use (#5674) --- .../Configuration/ConfigurationCommands.cs | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/Robust.Shared/Configuration/ConfigurationCommands.cs b/Robust.Shared/Configuration/ConfigurationCommands.cs index 24f62883d..0b4022773 100644 --- a/Robust.Shared/Configuration/ConfigurationCommands.cs +++ b/Robust.Shared/Configuration/ConfigurationCommands.cs @@ -8,6 +8,58 @@ namespace Robust.Shared.Configuration { + public static class CVarCommandUtil + { + /// + /// Parses a string into an object of the given type. + /// + /// Thrown if the string could not be parsed into the given type. + /// Thrown if the type is not supported. + public static object ParseObject(Type type, string input) + { + if (type == typeof(bool)) + { + if (bool.TryParse(input, out var val)) + return val; + + if (Parse.TryInt32(input, out var intVal)) + { + if (intVal == 0) return false; + if (intVal == 1) return true; + } + + throw new FormatException($"Could not parse bool value: {input}"); + } + + if (type == typeof(string)) + { + return input; + } + + if (type == typeof(int)) + { + return Parse.Int32(input); + } + + if (type == typeof(float)) + { + return Parse.Float(input); + } + + if (type == typeof(long)) + { + return long.Parse(input); + } + + if (type == typeof(ushort)) + { + return ushort.Parse(input); + } + + throw new NotSupportedException(); + } + } + [SuppressMessage("ReSharper", "StringLiteralTypo")] internal sealed class CVarCommand : LocalizedCommands { @@ -51,7 +103,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) var type = _cfg.GetCVarType(name); try { - var parsed = ParseObject(type, value); + var parsed = CVarCommandUtil.ParseObject(type, value); _cfg.SetCVar(name, parsed); } catch (FormatException) @@ -95,50 +147,6 @@ private static string GetCVarValueHint(IConfigurationManager cfg, string cVar) return value; } - - private static object ParseObject(Type type, string input) - { - if (type == typeof(bool)) - { - if (bool.TryParse(input, out var val)) - return val; - - if (Parse.TryInt32(input, out var intVal)) - { - if (intVal == 0) return false; - if (intVal == 1) return true; - } - - throw new FormatException($"Could not parse bool value: {input}"); - } - - if (type == typeof(string)) - { - return input; - } - - if (type == typeof(int)) - { - return Parse.Int32(input); - } - - if (type == typeof(float)) - { - return Parse.Float(input); - } - - if (type == typeof(long)) - { - return long.Parse(input); - } - - if (type == typeof(ushort)) - { - return ushort.Parse(input); - } - - throw new NotSupportedException(); - } } internal sealed class CVarSubsCommand : LocalizedCommands From 7016facb9a5e2d1601eaefe91ff337e1218dbe69 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:16:24 +1100 Subject: [PATCH 21/29] Tweak UserInterfaceComponent shutdown to prevent bugs (#5678) --- .../Systems/SharedUserInterfaceSystem.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 8c4dd88e9..bbeb508bb 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -41,13 +41,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem /// private readonly List<(BoundUserInterface Bui, bool value)> _queuedBuis = new(); - /// - /// Temporary storage for BUI keys - /// - private ValueList _keys = new(); - - private ValueList _entList = new(); - public override void Initialize() { base.Initialize(); @@ -288,11 +281,12 @@ private void OnUserInterfaceStartup(Entity ent, ref Comp protected virtual void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) { + var ents = new ValueList(); foreach (var (key, acts) in ent.Comp.Actors) { - _entList.Clear(); - _entList.AddRange(acts); - foreach (var actor in _entList) + ents.Clear(); + ents.AddRange(acts); + foreach (var actor in ents) { CloseUiInternal(ent!, key, actor); DebugTools.Assert(!acts.Contains(actor)); @@ -897,12 +891,13 @@ public void CloseUserUis(Entity actor) where T: if (actor.Comp.OpenInterfaces.Count == 0) return; + var keys = new ValueList(); foreach (var (uid, enums) in actor.Comp.OpenInterfaces) { - _keys.Clear(); - _keys.AddRange(enums); + keys.Clear(); + keys.AddRange(enums); - foreach (var weh in _keys) + foreach (var weh in keys) { if (weh is not T) continue; @@ -923,12 +918,14 @@ public void CloseUserUis(Entity actor) if (actor.Comp.OpenInterfaces.Count == 0) return; + var keys = new ValueList(); + foreach (var (uid, enums) in actor.Comp.OpenInterfaces) { - _keys.Clear(); - _keys.AddRange(enums); + keys.Clear(); + keys.AddRange(enums); - foreach (var key in _keys) + foreach (var key in keys) { CloseUiInternal(uid, key, actor.Owner); } From fec81bc2a184cb43c991535b9b66d4f5cef3cf5f Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 15 Feb 2025 21:16:23 -0500 Subject: [PATCH 22/29] Add more info to AnchorEntity debug assert (#5668) --- .../GameObjects/Systems/SharedTransformSystem.Component.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index a5e514305..ed03a1c65 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -119,7 +119,8 @@ public bool AnchorEntity(EntityUid uid, TransformComponent xform) public bool AnchorEntity(Entity entity, Entity? grid = null) { - DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid); + DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid, + $"Tried to anchor entity {Name(entity)} to a grid ({grid!.Value.Owner}) different from its GridUid ({entity.Comp.GridUid})"); if (grid == null) { From e163c496c3ad5593f0c63ab7a136ff3a2ff4c045 Mon Sep 17 00:00:00 2001 From: Fildrance Date: Sun, 16 Feb 2025 06:29:59 +0300 Subject: [PATCH 23/29] fix: fixed EntityPrototypeView not reacting on SetPrototype when EnteredTree already was called with _currentPrototype empty (#5649) Co-authored-by: pa.pecherskij --- .../UserInterface/Controls/EntityPrototypeView.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs b/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs index 5173a3da4..bd2e7c1c0 100644 --- a/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs +++ b/Robust.Client/UserInterface/Controls/EntityPrototypeView.cs @@ -9,6 +9,7 @@ public class EntityPrototypeView : SpriteView { private string? _currentPrototype; private EntityUid? _ourEntity; + private bool _isShowing; public EntityPrototypeView() { @@ -31,7 +32,7 @@ public void SetPrototype(EntProtoId? entProto) _currentPrototype = entProto; - if (_ourEntity != null) + if (_ourEntity != null || _isShowing) { UpdateEntity(); } @@ -45,6 +46,8 @@ protected override void EnteredTree() { UpdateEntity(); } + + _isShowing = true; } protected override void ExitedTree() @@ -52,6 +55,8 @@ protected override void ExitedTree() base.ExitedTree(); EntMan.TryQueueDeleteEntity(_ourEntity); _ourEntity = null; + + _isShowing = false; } private void UpdateEntity() From c011eff80e7d166813b24384cd8f737ecd392628 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 16 Feb 2025 14:30:18 +1100 Subject: [PATCH 24/29] Increase audio despawn buffer (#5665) Apparently it can clip and the buffer is really just there so we despawn 'at some point' and rather than hunching over my debugger for potentially an hour this is easier and almost no impact. I've also considered flagging some audio as "play the full thing" if someone misses the start of it but need to thonk on that one a bit in future. --- Robust.Shared/Audio/Systems/SharedAudioSystem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 37bb59ff9..42d003ddd 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -37,6 +37,8 @@ public abstract partial class SharedAudioSystem : EntitySystem [Dependency] protected readonly MetaDataSystem MetadataSys = default!; [Dependency] protected readonly SharedTransformSystem XformSystem = default!; + private const float AudioDespawnBuffer = 1f; + /// /// Default max range at which the sound can be heard. /// @@ -234,7 +236,7 @@ public void SetState(EntityUid? entity, AudioState state, bool force = false, Au { var timed = EnsureComp(entity.Value); var audioLength = GetAudioLength(component.FileName); - timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f; + timed.Lifetime = (float) audioLength.TotalSeconds + AudioDespawnBuffer; } break; } @@ -322,7 +324,7 @@ protected Entity SetupAudio(string? fileName, AudioParams? audio var despawn = AddComp(uid); // Don't want to clip audio too short due to imprecision. - despawn.Lifetime = (float) length.Value.TotalSeconds + 0.01f; + despawn.Lifetime = (float) length.Value.TotalSeconds + AudioDespawnBuffer; } if (comp.Params.Variation != null && comp.Params.Variation.Value != 0f) From bcb5c2d35d9e3597650337ae9cef3d1aff9ddd27 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 16 Feb 2025 14:56:39 +1100 Subject: [PATCH 25/29] Version: 245.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 52d55e729..b42e1e1cf 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 245.0.0 + 245.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 208cd2310..c3c3e00c1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,23 @@ END TEMPLATE--> *None yet* +## 245.1.0 + +### New features + +* Add more info to the AnchorEntity debug message. +* Make ParseObject public where it will parse a supplied Type and string into the specified object. + +### Bugfixes + +* Fix EntityPrototypeView not always updating the entity correctly. +* Tweak BUI shutdown to potentially avoid skipping closing. + +### Other + +* Increase Audio entity despawn buffer to avoid clipping. + + ## 245.0.0 ### Breaking changes From ea1cc5e4468b84378f9029bd51fed1690ffeafae Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 16 Feb 2025 19:29:32 +1100 Subject: [PATCH 26/29] Planet lighting pre-reqs (#5490) * Add another lookup overload * Fix RenderInRenderTarget See the linked issue for what happens. * Also this one * stuff * Fix stencilling * fixes * mix blend * fix * blur fixes * Tile flag * Minor tweak * Fixes * Render state fixes * Fixes * Fix stupidity * More state render bug fixes * MapUid on overlay draw * Remove blur comment * Fixes * Fixes * Remove * Engine vibe --- RELEASE-NOTES.md | 11 +- .../EnginePrototypes/Shaders/stockshaders.yml | 6 + Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 27 +++- .../Graphics/Clyde/Clyde.LightRendering.cs | 123 ++++++++------ .../Graphics/Clyde/Clyde.RenderTargets.cs | 29 +++- .../Graphics/Clyde/Clyde.Rendering.cs | 152 ++++++++++++++---- Robust.Client/Graphics/Clyde/Clyde.cs | 3 +- Robust.Client/Graphics/Clyde/ClydeHeadless.cs | 17 ++ Robust.Client/Graphics/IClyde.cs | 21 +++ Robust.Client/Graphics/IClydeViewport.cs | 1 + Robust.Client/Graphics/IRenderTarget.cs | 39 +++++ .../Graphics/Lighting/GetClearColorEvent.cs | 13 ++ .../Graphics/Lighting/LightManager.cs | 1 - .../Graphics/Overlays/OverlayDrawArgs.cs | 5 + Robust.Server/Maps/MapChunkSerializer.cs | 2 +- Robust.Shared/Enums/OverlaySpaces.cs | 7 +- .../Map/Components/MapLightComponent.cs | 5 +- Robust.Shared/Map/Tile.cs | 25 ++- .../SerializationManager.FlagsAndConstants.cs | 4 +- .../Shared/Map/MapGrid_Tests.cs | 8 +- .../Shared/Map/SingleTileRemoveTest.cs | 2 +- 21 files changed, 381 insertions(+), 120 deletions(-) create mode 100644 Robust.Client/Graphics/Lighting/GetClearColorEvent.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c3c3e00c1..8e1f709da 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,15 +35,20 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior. +* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage. ### New features -*None yet* +* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand. +* Change BlurLights to BlurRenderTarget and make it public for content usage. +* Add ContentFlag to tiles for content-flag usage. +* Add a basic mix shader for doing canvas blends. +* Add GetClearColorEvent for content to override the clear color behavior. ### Bugfixes -*None yet* +* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state. ### Other diff --git a/Resources/EnginePrototypes/Shaders/stockshaders.yml b/Resources/EnginePrototypes/Shaders/stockshaders.yml index 63e2dcf68..13f0d6361 100644 --- a/Resources/EnginePrototypes/Shaders/stockshaders.yml +++ b/Resources/EnginePrototypes/Shaders/stockshaders.yml @@ -4,6 +4,12 @@ kind: canvas light_mode: unshaded +# Simple mix blend +- type: shader + id: Mix + kind: canvas + blend_mode: Mix + - type: shader id: shaded kind: canvas diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index c70f8ef5c..8beaae893 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -125,7 +125,8 @@ private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace { DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace); - var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), vp.Eye!.Position.MapId, worldBox, worldBounds); + var mapId = vp.Eye!.Position.MapId; + var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds); if (!overlay.BeforeDraw(args)) return; @@ -175,8 +176,9 @@ private void RenderOverlaysDirect( var worldBounds = CalcWorldBounds(vp); var worldAABB = worldBounds.CalcBoundingBox(); + var mapId = vp.Eye!.Position.MapId; - var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds); + var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds); foreach (var overlay in list) { @@ -421,12 +423,19 @@ private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearCol var oldTransform = _currentMatrixModel; var oldScissor = _currentScissorState; + var oldMatrixProj = _currentMatrixProj; + var oldMatrixView = _currentMatrixView; + var oldBoundTarget = _currentBoundRenderTarget; + var oldRenderTarget = _currentRenderTarget; + var oldShader = _queuedShaderInstance; + var oldCaps = _glCaps; + + // Need to get state before flushing render queue in case they modify the original state. + var state = PushRenderStateFull(); // Have to flush the render queue so that all commands finish rendering to the previous framebuffer. FlushRenderQueue(); - var state = PushRenderStateFull(); - { BindRenderTargetFull(RtToLoaded(rt)); if (clearColor is not null) @@ -448,8 +457,16 @@ private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearCol PopRenderStateFull(state); _updateUniformConstants(_currentRenderTarget.Size); - SetScissorFull(oldScissor); _currentMatrixModel = oldTransform; + + DebugTools.Assert(oldCaps.Equals(_glCaps)); + DebugTools.Assert(_currentMatrixModel.Equals(oldTransform)); + DebugTools.Assert(_currentScissorState.Equals(oldScissor)); + DebugTools.Assert(_currentMatrixProj.Equals(oldMatrixProj)); + DebugTools.Assert(oldMatrixView.Equals(_currentMatrixView)); + DebugTools.Assert(oldRenderTarget.Equals(_currentRenderTarget)); + DebugTools.Assert(oldBoundTarget.Equals(_currentBoundRenderTarget)); + DebugTools.Assert(oldShader.Equals(_queuedShaderInstance)); } private void RenderViewport(Viewport viewport) diff --git a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs index 80008a627..89a324628 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs @@ -1,21 +1,19 @@ using System; using System.Collections.Generic; using System.Buffers; +using System.Diagnostics.Contracts; using System.Numerics; using OpenToolkit.Graphics.OpenGL4; using Robust.Client.GameObjects; using Robust.Client.ResourceManagement; using Robust.Shared; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Maths; -using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode; using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp; using Robust.Shared.Physics; -using Robust.Client.ComponentTrees; +using Robust.Shared.Enums; using Robust.Shared.Graphics; using static Robust.Shared.GameObjects.OccluderComponent; using Robust.Shared.Utility; @@ -279,8 +277,7 @@ private void PrepareDepthDraw(LoadedRenderTarget target) { const float arbitraryDistanceMax = 1234; - GL.Disable(EnableCap.Blend); - CheckGlError(); + IsBlending = false; GL.Enable(EnableCap.DepthTest); CheckGlError(); @@ -329,8 +326,7 @@ private void FinalizeDepthDraw() GL.Disable(EnableCap.DepthTest); CheckGlError(); - GL.Enable(EnableCap.Blend); - CheckGlError(); + IsBlending = true; } private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye) @@ -394,21 +390,43 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w FinalizeDepthDraw(); } - GL.Enable(EnableCap.StencilTest); - _isStencilling = true; + IsStencilling = true; var (lightW, lightH) = GetLightMapSize(viewport.Size); GL.Viewport(0, 0, lightW, lightH); CheckGlError(); BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget)); + DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId)); CheckGlError(); - GLClearColor(_entityManager.GetComponentOrNull(mapUid)?.AmbientLightColor ?? MapLightComponent.DefaultColor); + + var clearEv = new GetClearColorEvent(); + _entityManager.EventBus.RaiseEvent(EventSource.Local, ref clearEv); + + var clearColor = clearEv.Color ?? GetClearColor(mapUid); + GLClearColor(clearColor); GL.ClearStencil(0xFF); GL.StencilMask(0xFF); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit); CheckGlError(); + var oldTarget = _currentRenderTarget; + var oldProj = _currentMatrixProj; + var oldShader = _queuedShaderInstance; + var oldModel = _currentMatrixModel; + var oldScissor = _currentScissorState; + var state = PushRenderStateFull(); + + RenderOverlays(viewport, OverlaySpace.BeforeLighting, worldAABB, worldBounds); + PopRenderStateFull(state); + + DebugTools.Assert(oldScissor.Equals(_currentScissorState)); + DebugTools.Assert(oldModel.Equals(_currentMatrixModel)); + DebugTools.Assert(oldShader.Equals(_queuedShaderInstance)); + DebugTools.Assert(oldProj.Equals(_currentMatrixProj)); + DebugTools.Assert(oldTarget.Equals(_currentRenderTarget)); + DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId)); + ApplyLightingFovToBuffer(viewport, eye); var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle] @@ -509,13 +527,12 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w } ResetBlendFunc(); - GL.Disable(EnableCap.StencilTest); - _isStencilling = false; + IsStencilling = false; CheckGlError(); if (_cfg.GetCVar(CVars.LightBlur)) - BlurLights(viewport, eye); + BlurRenderTarget(viewport, viewport.LightRenderTarget, viewport.LightBlurTarget, eye, 14f); using (_prof.Group("BlurOntoWalls")) { @@ -531,9 +548,8 @@ private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 w GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y); CheckGlError(); - Array.Clear(_lightsToRenderList, 0, count); - _lightingReady = true; + Array.Clear(_lightsToRenderList, 0, count); } private static bool LightQuery(ref ( @@ -643,21 +659,33 @@ public int Compare( return (state.count, expandedBounds); } - private void BlurLights(Viewport viewport, IEye eye) + /// + [Pure] + public Color GetClearColor(EntityUid mapUid) { - using var _ = DebugGroup(nameof(BlurLights)); + return _entityManager.GetComponentOrNull(mapUid)?.AmbientLightColor ?? + MapLightComponent.DefaultColor; + } - GL.Disable(EnableCap.Blend); - CheckGlError(); + /// + public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier) + { + if (target is not RenderTexture rTexture || blurBuffer is not RenderTexture blurTexture) + return; + + using var _ = DebugGroup(nameof(BlurRenderTarget)); + + var state = PushRenderStateFull(); + IsBlending = false; CalcScreenMatrices(viewport.Size, out var proj, out var view); SetProjViewBuffer(proj, view); var shader = _loadedShaders[_lightBlurShaderHandle].Program; shader.Use(); - SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture); + SetupGlobalUniformsImmediate(shader, rTexture.Texture); - var size = viewport.LightRenderTarget.Size; + var size = target.Size; shader.SetUniformMaybe("size", (Vector2)size); shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0); @@ -667,14 +695,13 @@ private void BlurLights(Viewport viewport, IEye eye) // Initially we're pulling from the light render target. // So we set it out of the loop so // _wallBleedIntermediateRenderTarget2 gets bound at the end of the loop body. - SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture); + SetTexture(TextureUnit.Texture0, rTexture.Texture); // Have to scale the blurring radius based on viewport size and camera zoom. - const float refCameraHeight = 14; var facBase = _cfg.GetCVar(CVars.LightBlurFactor); var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter; // 7e-3f is just a magic factor that makes it look ok. - var factor = facBase * (refCameraHeight / cameraSize); + var factor = facBase * (multiplier / cameraSize); // Multi-iteration gaussian blur. for (var i = 3; i > 0; i--) @@ -683,35 +710,31 @@ private void BlurLights(Viewport viewport, IEye eye) // Set factor. shader.SetUniformMaybe("radius", scale); - BindRenderTargetFull(viewport.LightBlurTarget); + BindRenderTargetImmediate(RtToLoaded(blurBuffer)); // Blur horizontally to _wallBleedIntermediateRenderTarget1. shader.SetUniformMaybe("direction", Vector2.UnitX); _drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader); - SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture); + SetTexture(TextureUnit.Texture0, blurTexture.Texture); - BindRenderTargetFull(viewport.LightRenderTarget); + BindRenderTargetImmediate(RtToLoaded(rTexture)); // Blur vertically to _wallBleedIntermediateRenderTarget2. shader.SetUniformMaybe("direction", Vector2.UnitY); _drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader); - SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture); + SetTexture(TextureUnit.Texture0, rTexture.Texture); } - GL.Enable(EnableCap.Blend); - CheckGlError(); - // We didn't trample over the old _currentMatrices so just roll it back. - SetProjViewBuffer(_currentMatrixProj, _currentMatrixView); + PopRenderStateFull(state); } private void BlurOntoWalls(Viewport viewport, IEye eye) { using var _ = DebugGroup(nameof(BlurOntoWalls)); - GL.Disable(EnableCap.Blend); - CheckGlError(); + IsBlending = false; CalcScreenMatrices(viewport.Size, out var proj, out var view); SetProjViewBuffer(proj, view); @@ -761,8 +784,7 @@ private void BlurOntoWalls(Viewport viewport, IEye eye) SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture); } - GL.Enable(EnableCap.Blend); - CheckGlError(); + IsBlending = true; // We didn't trample over the old _currentMatrices so just roll it back. SetProjViewBuffer(_currentMatrixProj, _currentMatrixView); } @@ -775,8 +797,7 @@ private void MergeWallLayer(Viewport viewport) GL.Viewport(0, 0, viewport.LightRenderTarget.Size.X, viewport.LightRenderTarget.Size.Y); CheckGlError(); - GL.Disable(EnableCap.Blend); - CheckGlError(); + IsBlending = false; var shader = _loadedShaders[_mergeWallLayerShaderHandle].Program; shader.Use(); @@ -796,8 +817,7 @@ private void MergeWallLayer(Viewport viewport) IntPtr.Zero); CheckGlError(); - GL.Enable(EnableCap.Blend); - CheckGlError(); + IsBlending = true; } private void ApplyFovToBuffer(Viewport viewport, IEye eye) @@ -827,8 +847,7 @@ private void ApplyFovToBuffer(Viewport viewport, IEye eye) FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader); GL.StencilMask(0x00); - GL.Disable(EnableCap.StencilTest); - _isStencilling = false; + IsStencilling = false; } private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye) @@ -1135,22 +1154,20 @@ private void RegenLightRts(Viewport viewport) var lightMapSize = GetLightMapSize(viewport.Size); var lightMapSizeQuart = GetLightMapSize(viewport.Size, true); - var lightMapColorFormat = _hasGLFloatFramebuffers - ? RenderTargetColorFormat.R11FG11FB10F - : RenderTargetColorFormat.Rgba8; - var lightMapSampleParameters = new TextureSampleParameters { Filter = true }; viewport.LightRenderTarget?.Dispose(); viewport.WallMaskRenderTarget?.Dispose(); viewport.WallBleedIntermediateRenderTarget1?.Dispose(); viewport.WallBleedIntermediateRenderTarget2?.Dispose(); + var lightMapColorFormat = _hasGLFloatFramebuffers + ? RenderTargetColorFormat.R11FG11FB10F + : RenderTargetColorFormat.Rgba8; + var lightMapSampleParameters = new TextureSampleParameters { Filter = true }; viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8, name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}"); - viewport.LightRenderTarget = CreateRenderTarget(lightMapSize, - new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true), - lightMapSampleParameters, + viewport.LightRenderTarget = (RenderTexture) CreateLightRenderTarget(lightMapSize, $"{viewport.Name}-{nameof(viewport.LightRenderTarget)}"); viewport.LightBlurTarget = CreateRenderTarget(lightMapSize, @@ -1158,11 +1175,13 @@ private void RegenLightRts(Viewport viewport) lightMapSampleParameters, $"{viewport.Name}-{nameof(viewport.LightBlurTarget)}"); - viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat, + viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, + new RenderTargetFormatParameters(lightMapColorFormat), lightMapSampleParameters, $"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget1)}"); - viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat, + viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart, + new RenderTargetFormatParameters(lightMapColorFormat), lightMapSampleParameters, $"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget2)}"); } diff --git a/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs b/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs index a07f1cfc7..5ea57a1ae 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.RenderTargets.cs @@ -30,6 +30,20 @@ private readonly ConcurrentQueue _renderTargetDisposeQueue // It, like _mainWindowRenderTarget, is initialized in Clyde's constructor private LoadedRenderTarget _currentBoundRenderTarget; + + public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true) + { + var lightMapColorFormat = _hasGLFloatFramebuffers + ? RTCF.R11FG11FB10F + : RTCF.Rgba8; + var lightMapSampleParameters = new TextureSampleParameters { Filter = true }; + + return CreateRenderTarget(size, + new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: depthStencil), + lightMapSampleParameters, + name: name); + } + IRenderTexture IClyde.CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format, TextureSampleParameters? sampleParameters, string? name) { @@ -204,7 +218,8 @@ private RenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParame Size = size, TextureHandle = textureObject.TextureId, MemoryPressure = pressure, - ColorFormat = format.ColorFormat + ColorFormat = format.ColorFormat, + SampleParameters = sampleParameters, }; //GC.AddMemoryPressure(pressure); @@ -251,9 +266,15 @@ private void BindRenderTargetFull(RenderTargetBase rt) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private LoadedRenderTarget RtToLoaded(RenderTargetBase rt) + private LoadedRenderTarget RtToLoaded(IRenderTarget rt) { - return _renderTargets[rt.Handle]; + switch (rt) + { + case RenderTargetBase based: + return _renderTargets[based.Handle]; + default: + throw new NotImplementedException(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -302,6 +323,8 @@ private sealed class LoadedRenderTarget // Renderbuffer handle public GLHandle DepthStencilHandle; public long MemoryPressure; + + public TextureSampleParameters? SampleParameters; } private abstract class RenderTargetBase : IRenderTarget diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index af19a2c1a..b0f42f793 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -90,9 +90,61 @@ internal partial class Clyde // (queue) and (misc), current state of the scissor test. Null if disabled. private UIBox2i? _currentScissorState; - // Some simple flags that basically just tracks the current state of glEnable(GL_STENCIL/GL_SCISSOR_TEST) - private bool _isScissoring; - private bool _isStencilling; + /// + /// Tracks enabled GL capabilities for renderer state. + /// + private GLCaps _glCaps = GLCaps.None; + + private bool IsStencilling + { + get => (_glCaps & GLCaps.Stencilling) == GLCaps.Stencilling; + set + { + if (value == IsStencilling) + return; + + if (value) + { + _glCaps |= GLCaps.Stencilling; + GL.Enable(EnableCap.StencilTest); + } + else + { + _glCaps &= ~GLCaps.Stencilling; + GL.Disable(EnableCap.StencilTest); + } + + CheckGlError(); + } + } + + private bool IsBlending + { + get => (_glCaps & GLCaps.Blending) == GLCaps.Blending; + set + { + if (value == IsBlending) + return; + + if (value) + { + _glCaps |= GLCaps.Blending; + GL.Enable(EnableCap.Blend); + } + else + { + _glCaps &= ~GLCaps.Blending; + GL.Disable(EnableCap.Blend); + } + + CheckGlError(); + } + } + + private bool IsScissoring + { + get => _currentScissorState != null; + } private readonly RefList _queuedRenderCommands = new RefList(); @@ -364,16 +416,17 @@ private void SetScissorFull(UIBox2i? state) private void SetScissorImmediate(bool enable, in UIBox2i box) { - var oldIsScissoring = _isScissoring; - _isScissoring = enable; - if (_isScissoring) + if (enable) { - if (!oldIsScissoring) - { - GL.Enable(EnableCap.ScissorTest); - CheckGlError(); - } + GL.Enable(EnableCap.ScissorTest); + } + else + { + GL.Disable(EnableCap.ScissorTest); + } + if (enable) + { // Don't forget to flip it, these coordinates have bottom left as origin. // TODO: Broken when rendering to non-screen render targets. @@ -387,11 +440,6 @@ private void SetScissorImmediate(bool enable, in UIBox2i box) } CheckGlError(); } - else if (oldIsScissoring) - { - GL.Disable(EnableCap.ScissorTest); - CheckGlError(); - } } // NOTE: sRGB IS IN LINEAR IF FRAMEBUFFER_SRGB IS ACTIVE. @@ -420,17 +468,11 @@ private Color ConvertClearFromSrgb(Color color) var program = shader.Program; program.Use(); + IsStencilling = instance.Stencil.Enabled; // Handle stencil parameters. if (instance.Stencil.Enabled) { - if (!_isStencilling) - { - GL.Enable(EnableCap.StencilTest); - CheckGlError(); - _isStencilling = true; - } - GL.StencilMask(instance.Stencil.WriteMask); CheckGlError(); GL.StencilFunc(ToGLStencilFunc(instance.Stencil.Func), instance.Stencil.Ref, instance.Stencil.ReadMask); @@ -438,12 +480,6 @@ private Color ConvertClearFromSrgb(Color color) GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, ToGLStencilOp(instance.Stencil.Op)); CheckGlError(); } - else if (_isStencilling) - { - GL.Disable(EnableCap.StencilTest); - CheckGlError(); - _isStencilling = false; - } if (instance.Parameters.Count == 0) return (program, instance); @@ -859,17 +895,34 @@ private void BreakBatch() private FullStoredRendererState PushRenderStateFull() { - return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget); + return new FullStoredRendererState( + _currentMatrixProj, + _currentMatrixView, + _currentBoundRenderTarget, + _currentRenderTarget, + _queuedShaderInstance, + _currentScissorState, + _glCaps); } private void PopRenderStateFull(in FullStoredRendererState state) { SetProjViewFull(state.ProjMatrix, state.ViewMatrix); - BindRenderTargetFull(state.RenderTarget); + BindRenderTargetImmediate(state.BoundRenderTarget); - var (width, height) = state.RenderTarget.Size; + _queuedShaderInstance = state.QueuedShaderInstance; + _currentRenderTarget = state.RenderTarget; + var (width, height) = state.BoundRenderTarget.Size; GL.Viewport(0, 0, width, height); - CheckGlError(); + + IsStencilling = (state.GLCaps & GLCaps.Stencilling) == GLCaps.Stencilling; + IsBlending = (state.GLCaps & GLCaps.Blending) == GLCaps.Blending; + + SetScissorFull(state.ScissorState); + + GL.ClearStencil(0xFF); + GL.StencilMask(0xFF); + GL.Clear(ClearBufferMask.StencilBufferBit); } private void SetViewportImmediate(Box2i box) @@ -1061,15 +1114,44 @@ private readonly struct FullStoredRendererState { public readonly Matrix3x2 ProjMatrix; public readonly Matrix3x2 ViewMatrix; + public readonly LoadedRenderTarget BoundRenderTarget; public readonly LoadedRenderTarget RenderTarget; + public readonly ClydeShaderInstance QueuedShaderInstance; - public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix, - LoadedRenderTarget renderTarget) + public readonly UIBox2i? ScissorState; + + public readonly GLCaps GLCaps; + + public FullStoredRendererState( + in Matrix3x2 projMatrix, + in Matrix3x2 viewMatrix, + LoadedRenderTarget boundRenderTarget, + LoadedRenderTarget renderTarget, + ClydeShaderInstance queuedShaderInstance, + UIBox2i? scissorState, + GLCaps glcaps + ) { ProjMatrix = projMatrix; ViewMatrix = viewMatrix; + BoundRenderTarget = boundRenderTarget; RenderTarget = renderTarget; + QueuedShaderInstance = queuedShaderInstance; + + ScissorState = scissorState; + GLCaps = glcaps; } } + + [Flags] + private enum GLCaps : ushort + { + // If you add flags here make sure to update PopRenderState! + None = 0, + + Blending = 1 << 0, + + Stencilling = 1 << 2, + } } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs index 736116e5a..4d36e6e59 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.cs @@ -21,6 +21,7 @@ using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Profiling; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode; @@ -245,7 +246,7 @@ private void InitOpenGL() overrideVersion != null, _windowing!.GetDescription()); - GL.Enable(EnableCap.Blend); + IsBlending = true; if (_hasGLSrgb && !_isGLES) { GL.Enable(EnableCap.FramebufferSrgb); diff --git a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs index f8ed887b7..3c242dd00 100644 --- a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs +++ b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs @@ -9,6 +9,7 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -189,6 +190,22 @@ public OwnedTexture CreateBlankTexture( return new DummyTexture(size); } + /// + public Color GetClearColor(EntityUid mapUid) + { + return Color.Transparent; + } + + public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier) + { + // NOOP + } + + public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true) + { + return CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.R8, hasDepthStencil: depthStencil), null, name: name); + } + public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format, TextureSampleParameters? sampleParameters = null, string? name = null) { diff --git a/Robust.Client/Graphics/IClyde.cs b/Robust.Client/Graphics/IClyde.cs index 2c69fcbda..11c20c6f6 100644 --- a/Robust.Client/Graphics/IClyde.cs +++ b/Robust.Client/Graphics/IClyde.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.IO; using System.Numerics; using System.Threading.Tasks; +using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.Maths; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using Color = Robust.Shared.Maths.Color; namespace Robust.Client.Graphics { @@ -71,6 +74,24 @@ OwnedTexture CreateBlankTexture( in TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel; + /// + /// Gets the clear color for the specified map viewport. + /// + [Pure] + Color GetClearColor(EntityUid mapUid); + + /// + /// Applies a blur to the specified render target. Requires a separate buffer with similar properties to draw intermediate steps into. + /// + /// The viewport being used for drawing. + /// The blur target. + /// The separate buffer to draw into. + /// The eye being drawn with. + /// Scale of how much blur to blur by. + void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier); + + IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true); + IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format, TextureSampleParameters? sampleParameters = null, string? name = null); diff --git a/Robust.Client/Graphics/IClydeViewport.cs b/Robust.Client/Graphics/IClydeViewport.cs index de09cfdfa..2f838ba21 100644 --- a/Robust.Client/Graphics/IClydeViewport.cs +++ b/Robust.Client/Graphics/IClydeViewport.cs @@ -18,6 +18,7 @@ public interface IClydeViewport : IDisposable /// IRenderTexture RenderTarget { get; } IRenderTexture LightRenderTarget { get; } + IEye? Eye { get; set; } Vector2i Size { get; } diff --git a/Robust.Client/Graphics/IRenderTarget.cs b/Robust.Client/Graphics/IRenderTarget.cs index 61b277629..dc8cc53c5 100644 --- a/Robust.Client/Graphics/IRenderTarget.cs +++ b/Robust.Client/Graphics/IRenderTarget.cs @@ -1,4 +1,6 @@ using System; +using System.Numerics; +using Robust.Shared.Graphics; using Robust.Shared.Maths; using SixLabors.ImageSharp.PixelFormats; @@ -15,5 +17,42 @@ public interface IRenderTarget : IDisposable Vector2i Size { get; } void CopyPixelsToMemory(CopyPixelsDelegate callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel; + + public Vector2 LocalToWorld(IEye eye, Vector2 point, Vector2 scale) + { + var newPoint = point; + + // (inlined version of UiProjMatrix^-1) + newPoint -= Size / 2f; + newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter; + + // view matrix + eye.GetViewMatrixInv(out var viewMatrixInv, scale); + newPoint = Vector2.Transform(newPoint, viewMatrixInv); + + return newPoint; + } + + public Vector2 WorldToLocal(Vector2 point, IEye eye, Vector2 scale) + { + var newPoint = point; + + eye.GetViewMatrix(out var viewMatrix, scale); + newPoint = Vector2.Transform(newPoint, viewMatrix); + + // (inlined version of UiProjMatrix) + newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter; + newPoint += Size / 2f; + + return newPoint; + } + + public Matrix3x2 GetWorldToLocalMatrix(IEye eye, Vector2 scale) + { + eye.GetViewMatrix(out var viewMatrix, scale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter)); + viewMatrix.M31 += Size.X / 2f; + viewMatrix.M32 += Size.Y / 2f; + return viewMatrix; + } } } diff --git a/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs b/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs new file mode 100644 index 000000000..7fb2b5271 --- /dev/null +++ b/Robust.Client/Graphics/Lighting/GetClearColorEvent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Robust.Client.Graphics; + +/// +/// Raised by the engine if content wishes to override the default clear color. +/// +[ByRefEvent] +public record struct GetClearColorEvent +{ + public Color? Color; +} diff --git a/Robust.Client/Graphics/Lighting/LightManager.cs b/Robust.Client/Graphics/Lighting/LightManager.cs index 19bb4c498..7b4e34dcd 100644 --- a/Robust.Client/Graphics/Lighting/LightManager.cs +++ b/Robust.Client/Graphics/Lighting/LightManager.cs @@ -9,6 +9,5 @@ public sealed class LightManager : ILightManager public bool DrawHardFov { get; set; } = true; public bool DrawLighting { get; set; } = true; public bool LockConsoleAccess { get; set; } = false; - public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black); } } diff --git a/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs b/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs index f07c4fffa..cbdd17bcf 100644 --- a/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs +++ b/Robust.Client/Graphics/Overlays/OverlayDrawArgs.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Enums; +using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -39,6 +40,8 @@ public readonly ref struct OverlayDrawArgs /// public readonly UIBox2i ViewportBounds; + public readonly EntityUid MapUid; + /// /// of the viewport's eye. /// @@ -65,6 +68,7 @@ internal OverlayDrawArgs( IClydeViewport viewport, IRenderHandle renderHandle, in UIBox2i viewportBounds, + in EntityUid mapUid, in MapId mapId, in Box2 worldAabb, in Box2Rotated worldBounds) @@ -78,6 +82,7 @@ internal OverlayDrawArgs( Viewport = viewport; RenderHandle = renderHandle; ViewportBounds = viewportBounds; + MapUid = mapUid; MapId = mapId; WorldAABB = worldAabb; WorldBounds = worldBounds; diff --git a/Robust.Server/Maps/MapChunkSerializer.cs b/Robust.Server/Maps/MapChunkSerializer.cs index cabf4435c..5b81b2f79 100644 --- a/Robust.Server/Maps/MapChunkSerializer.cs +++ b/Robust.Server/Maps/MapChunkSerializer.cs @@ -73,7 +73,7 @@ public MapChunk Read(ISerializationManager serializationManager, MappingDataNode for (ushort x = 0; x < chunk.ChunkSize; x++) { var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32(); - var flags = (TileRenderFlag)reader.ReadByte(); + var flags = reader.ReadByte(); var variant = reader.ReadByte(); var defName = tileMap[id]; diff --git a/Robust.Shared/Enums/OverlaySpaces.cs b/Robust.Shared/Enums/OverlaySpaces.cs index deefdc64c..28be50a76 100644 --- a/Robust.Shared/Enums/OverlaySpaces.cs +++ b/Robust.Shared/Enums/OverlaySpaces.cs @@ -54,6 +54,11 @@ public enum OverlaySpace : ushort /// /// Overlay will be rendered below grids, entities, and everything else. In world space. /// - WorldSpaceBelowWorld = 1 << 8 + WorldSpaceBelowWorld = 1 << 8, + + /// + /// Called after GLClear but before FOV applied to the lighting buffer. + /// + BeforeLighting = 1 << 9, } } diff --git a/Robust.Shared/Map/Components/MapLightComponent.cs b/Robust.Shared/Map/Components/MapLightComponent.cs index a1afe9a28..57126fc53 100644 --- a/Robust.Shared/Map/Components/MapLightComponent.cs +++ b/Robust.Shared/Map/Components/MapLightComponent.cs @@ -17,7 +17,6 @@ public sealed partial class MapLightComponent : Component /// /// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)! /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("ambientLightColor")] - public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black); + [DataField] + public Color AmbientLightColor = Color.FromSrgb(Color.Black); } diff --git a/Robust.Shared/Map/Tile.cs b/Robust.Shared/Map/Tile.cs index 23c6ad9f3..a08470a6d 100644 --- a/Robust.Shared/Map/Tile.cs +++ b/Robust.Shared/Map/Tile.cs @@ -1,5 +1,6 @@ using System; using JetBrains.Annotations; +using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Robust.Shared.Map; @@ -16,9 +17,9 @@ namespace Robust.Shared.Map; public readonly int TypeId; /// - /// Rendering flags. + /// Custom flags for additional tile-data. /// - public readonly TileRenderFlag Flags; + public readonly byte Flags; /// /// Variant of this tile to render. @@ -39,9 +40,9 @@ namespace Robust.Shared.Map; /// Creates a new instance of a grid tile. /// /// Internal type ID. - /// Flags used by toolbox's rendering. + /// Custom tile flags for usage. /// The visual variant this tile is using. - public Tile(int typeId, TileRenderFlag flags = 0, byte variant = 0) + public Tile(int typeId, byte flags = 0, byte variant = 0) { TypeId = typeId; Flags = flags; @@ -112,9 +113,19 @@ public override int GetHashCode() return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode(); } } -} -public enum TileRenderFlag : byte -{ + [Pure] + public Tile WithVariant(byte variant) + { + return new Tile(TypeId, Flags, variant); + } + [Pure] + public Tile WithFlag(byte flag) + { + return new Tile(TypeId, flags: flag, variant: Variant); + } } + +public sealed class TileFlagLayer {} + diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs index a89792651..68391843d 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.FlagsAndConstants.cs @@ -57,13 +57,11 @@ private void InitializeFlagsAndConstants(IEnumerable flags, IEnumerable(true)) { - if (_flagsMapping.ContainsKey(flagType.Tag)) + if (!_flagsMapping.TryAdd(flagType.Tag, bitflagType)) { throw new NotSupportedException($"Multiple bitflag enums declared for the tag {flagType.Tag}."); } - _flagsMapping.Add(flagType.Tag, bitflagType); - var highestBit = bitflagType .GetEnumValues() .Cast() diff --git a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs index 8a6a3eeb0..1ba57f582 100644 --- a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs @@ -37,12 +37,12 @@ public void GetTileRefCoords() gridOptions.ChunkSize = 8; var grid = mapMan.CreateGridEntity(mapId, gridOptions); - mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1)); + mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1)); var result = mapSystem.GetTileRef(grid.Owner, grid.Comp, new Vector2i(-9, -1)); Assert.That(grid.Comp.ChunkCount, Is.EqualTo(1)); Assert.That(mapSystem.GetMapChunks(grid.Owner, grid.Comp).Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1))); - Assert.That(result, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9,-1), new Tile(1, (TileRenderFlag)1, 1)))); + Assert.That(result, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9,-1), new Tile(typeId: 1, flags: 1, variant: 1)))); } /// @@ -169,14 +169,14 @@ public void TryGetTileRefTileExists() gridOptions.ChunkSize = 8; var grid = mapMan.CreateGridEntity(mapId, gridOptions); - mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1)); + mapSystem.SetTile(grid, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1)); var foundTile = mapSystem.TryGetTileRef(grid.Owner, grid.Comp, new Vector2i(-9, -1), out var tileRef); Assert.That(foundTile, Is.True); Assert.That(grid.Comp.ChunkCount, Is.EqualTo(1)); Assert.That(mapSystem.GetMapChunks(grid.Owner, grid.Comp).Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1))); - Assert.That(tileRef, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1)))); + Assert.That(tileRef, Is.EqualTo(new TileRef(grid.Owner, new Vector2i(-9, -1), new Tile(typeId: 1, flags: 1, variant: 1)))); } [Test] diff --git a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs index 0e0db47d1..e38164aaf 100644 --- a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs +++ b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs @@ -78,7 +78,7 @@ await server.WaitPost(() => sMap = sys.CreateMap(out var mapId); var comp = mapMan.CreateGridEntity(mapId); grid = (comp.Owner, comp); - sys.SetTile(grid, grid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag)1, 1)); + sys.SetTile(grid, grid, new Vector2i(0, 0), new Tile(typeId: 1, flags: 1, variant: 1)); var coords = new EntityCoordinates(grid, 0.5f, 0.5f); sPlayer = sEntMan.SpawnEntity(null, coords); From 9d1b15ab4b79d441fb215d1c5069f3a15e3ca675 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 16 Feb 2025 19:32:42 +1100 Subject: [PATCH 27/29] Version: 246.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 31 +++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index b42e1e1cf..c409e5b36 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 245.1.0 + 246.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8e1f709da..52ebc5914 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,20 +35,15 @@ END TEMPLATE--> ### Breaking changes -* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior. -* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage. +*None yet* ### New features -* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand. -* Change BlurLights to BlurRenderTarget and make it public for content usage. -* Add ContentFlag to tiles for content-flag usage. -* Add a basic mix shader for doing canvas blends. -* Add GetClearColorEvent for content to override the clear color behavior. +*None yet* ### Bugfixes -* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state. +*None yet* ### Other @@ -59,6 +54,26 @@ END TEMPLATE--> *None yet* +## 246.0.0 + +### Breaking changes + +* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior. +* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage. + +### New features + +* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand. +* Change BlurLights to BlurRenderTarget and make it public for content usage. +* Add ContentFlag to tiles for content-flag usage. +* Add a basic mix shader for doing canvas blends. +* Add GetClearColorEvent for content to override the clear color behavior. + +### Bugfixes + +* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state. + + ## 245.1.0 ### New features From d418c66eb9fa139296513edff14068fed9c6d968 Mon Sep 17 00:00:00 2001 From: DEATHB4DEFEAT Date: Sat, 22 Feb 2025 17:19:17 -0800 Subject: [PATCH 28/29] allow connecting with multiple of whitelisted auth servers --- .../Console/Commands/LauncherAuthCommand.cs | 51 ++++++++++++--- Robust.Shared/CVars.cs | 8 +-- Robust.Shared/Network/AuthManager.cs | 62 ++++++++++++++----- .../Messages/Handshake/MsgLoginStart.cs | 3 + .../Network/NetManager.ClientConnect.cs | 7 ++- .../Network/NetManager.ServerAuth.cs | 11 +++- 6 files changed, 109 insertions(+), 33 deletions(-) diff --git a/Robust.Client/Console/Commands/LauncherAuthCommand.cs b/Robust.Client/Console/Commands/LauncherAuthCommand.cs index 5fa5c5192..88c1fb1ed 100644 --- a/Robust.Client/Console/Commands/LauncherAuthCommand.cs +++ b/Robust.Client/Console/Commands/LauncherAuthCommand.cs @@ -5,6 +5,7 @@ using Robust.Client.Utility; using Robust.Shared.Console; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Network; namespace Robust.Client.Console.Commands @@ -15,31 +16,61 @@ internal sealed class LauncherAuthCommand : LocalizedCommands [Dependency] private readonly IGameControllerInternal _gameController = default!; public override string Command => "launchauth"; + public override string Help => "Usage: launchauth "; public override void Execute(IConsoleShell shell, string argStr, string[] args) { - var wantName = args.Length > 0 ? args[0] : null; + string? username = null; + string? serverId = null; + string? serverUrl = null; + if (args.Length > 0) + username = args[0]; + if (args.Length > 1) + serverId = args[1]; + if (args.Length > 2) + serverUrl = args[2]; var basePath = UserDataDir.GetRootUserDataDir(_gameController); var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher"; var dbPath = Path.Combine(basePath, launcherDirName, "settings.db"); -#if USE_SYSTEM_SQLITE - SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3()); -#endif + #if USE_SYSTEM_SQLITE + SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3()); + #endif using var con = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly"); con.Open(); using var cmd = con.CreateCommand(); - cmd.CommandText = "SELECT UserId, UserName, Token FROM Login WHERE Expires > datetime('NOW')"; + cmd.CommandText = "SELECT UserId, UserName, Token, Server, ServerUrl FROM Login WHERE Expires > datetime('NOW')"; - if (wantName != null) + if (username != null) { cmd.CommandText += " AND UserName = @userName"; - cmd.Parameters.AddWithValue("@userName", wantName); + cmd.Parameters.AddWithValue("@userName", username); } - cmd.CommandText += " LIMIT 1;"; + if (serverId != null) + { + cmd.CommandText += " AND Server = @serverId"; + cmd.Parameters.AddWithValue("@serverId", serverId); + if (serverId == IAuthManager.CustomServerId) + { + if (serverUrl == null) + { + shell.WriteLine("Custom server requires a URL"); + return; + } + cmd.CommandText += " AND ServerUrl = @serverUrl"; + cmd.Parameters.AddWithValue("@serverUrl", serverUrl); + } + else if (serverUrl != null) + { + shell.WriteLine("Server URL is only valid for custom servers"); + return; + } + } + + cmd.CommandText += " LIMIT 1;"; using var reader = cmd.ExecuteReader(); if (!reader.Read()) @@ -51,11 +82,13 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) var userId = Guid.Parse(reader.GetString(0)); var userName = reader.GetString(1); var token = reader.GetString(2); + serverUrl = reader.GetString(4); _auth.Token = token; _auth.UserId = new NetUserId(userId); + _auth.UserServer = new("unset", new(serverUrl)); - shell.WriteLine($"Logged into account {userName}"); + shell.WriteLine($"Logged into account {userName}@{reader.GetString(3)} ({serverUrl})"); } } } diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 0a0f0d4fc..8111a0ca1 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -942,12 +942,12 @@ protected CVars() public static readonly CVarDef AuthAllowLocal = CVarDef.Create("auth.allowlocal", true, CVar.SERVERONLY); - // Only respected on server, client goes through IAuthManager for security. /// - /// Authentication server address. + /// List of comma separated URLs to use as whitelisted authentication servers /// - public static readonly CVarDef AuthServer = - CVarDef.Create("auth.server", AuthManager.DefaultAuthServer, CVar.SERVERONLY); + /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/" + public static readonly CVarDef AuthServers = + CVarDef.Create("auth.servers", AuthServer.ToStringList(AuthManager.DefaultAuthServers), CVar.SERVERONLY); /* * RENDERING diff --git a/Robust.Shared/Network/AuthManager.cs b/Robust.Shared/Network/AuthManager.cs index ea2250e89..e81c38d80 100644 --- a/Robust.Shared/Network/AuthManager.cs +++ b/Robust.Shared/Network/AuthManager.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Robust.Shared.Serialization; + namespace Robust.Shared.Network { @@ -10,8 +14,11 @@ namespace Robust.Shared.Network /// internal interface IAuthManager { + public const string CustomServerId = "Custom"; + NetUserId? UserId { get; set; } - string? Server { get; set; } + AuthServer UserServer { get; set; } + HashSet Servers { get; set; } string? Token { get; set; } string? PubKey { get; set; } @@ -23,42 +30,67 @@ internal interface IAuthManager void LoadFromEnv(); } + public sealed class AuthServer(string id, Uri authUrl) + { + public string Id { get; } = id; + public Uri AuthUrl { get; } = authUrl; + + /// Returns a string representation of the auth server + /// "Space-Wizards:https://auth.spacestation14.com/" + public override string ToString() => $"{Id}:{AuthUrl}"; + + /// Returns a string representation of a list of auth servers + /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/" + public static string ToStringList(HashSet servers) => string.Join(',', servers.Select(s => s.ToString())); + + /// Takes a representation of an auth server and returns an AuthServer object + /// "Space-Wizards:https://auth.spacestation14.com/" + public static AuthServer FromString(string str) + { + var parts = str.Split(':'); + if (parts.Length != 2) + throw new ArgumentException($"Invalid auth server string: {str}"); + + return new(parts[0], new(parts[1])); + } + + /// Takes a list of auth server representations and returns a HashSet of AuthServer objects + /// "Space-Wizards:https://auth.spacestation14.com/,SimpleStation:https://auth.simplestation.org/" + public static HashSet FromStringList(string str) => new(str.Split(',').Select(FromString)); + } + internal sealed class AuthManager : IAuthManager { - public const string DefaultAuthServer = "https://auth.spacestation14.com/"; + public static readonly AuthServer FallbackAuthServer = new("Space-Wizards", new("https://auth.spacestation14.com/")); + public static readonly HashSet DefaultAuthServers = new() + { + FallbackAuthServer, + new("SimpleStation", new("https://auth.simplestation.org/")), + }; public NetUserId? UserId { get; set; } - public string? Server { get; set; } = DefaultAuthServer; + public AuthServer UserServer { get; set; } = FallbackAuthServer; + public HashSet Servers { get; set; } = DefaultAuthServers; public string? Token { get; set; } public string? PubKey { get; set; } public bool AllowHwid { get; set; } = true; public void LoadFromEnv() { - if (TryGetVar("ROBUST_AUTH_SERVER", out var server)) - { - Server = server; - } + if (TryGetVar("ROBUST_AUTH_SERVERS", out var servers)) + Servers = AuthServer.FromStringList(servers); if (TryGetVar("ROBUST_AUTH_USERID", out var userId)) - { UserId = new NetUserId(Guid.Parse(userId)); - } if (TryGetVar("ROBUST_AUTH_PUBKEY", out var pubKey)) - { PubKey = pubKey; - } if (TryGetVar("ROBUST_AUTH_TOKEN", out var token)) - { Token = token; - } if (TryGetVar("ROBUST_AUTH_ALLOW_HWID", out var allowHwid)) - { AllowHwid = allowHwid.Trim() == "1"; - } static bool TryGetVar(string var, [NotNullWhen(true)] out string? val) { diff --git a/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs b/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs index ca8d39755..a29bd2047 100644 --- a/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs +++ b/Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs @@ -16,6 +16,7 @@ internal sealed class MsgLoginStart : NetMessage public override MsgGroups MsgGroup => MsgGroups.Core; public string UserName; + public string AuthServer; public bool CanAuth; public bool NeedPubKey; public bool Encrypt; @@ -23,6 +24,7 @@ internal sealed class MsgLoginStart : NetMessage public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { UserName = buffer.ReadString(); + AuthServer = buffer.ReadString(); CanAuth = buffer.ReadBoolean(); NeedPubKey = buffer.ReadBoolean(); Encrypt = buffer.ReadBoolean(); @@ -31,6 +33,7 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { buffer.Write(UserName); + buffer.Write(AuthServer); buffer.Write(CanAuth); buffer.Write(NeedPubKey); buffer.Write(Encrypt); diff --git a/Robust.Shared/Network/NetManager.ClientConnect.cs b/Robust.Shared/Network/NetManager.ClientConnect.cs index 7e8a4d3c9..221343c58 100644 --- a/Robust.Shared/Network/NetManager.ClientConnect.cs +++ b/Robust.Shared/Network/NetManager.ClientConnect.cs @@ -126,7 +126,7 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str var encrypt = _config.GetCVar(CVars.NetEncrypt); var authToken = _authManager.Token; var pubKey = _authManager.PubKey; - var authServer = _authManager.Server; + var authServer = _authManager.UserServer; var userId = _authManager.UserId; var hasPubKey = !string.IsNullOrEmpty(pubKey); @@ -137,9 +137,10 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str var msgLogin = new MsgLoginStart { UserName = userNameRequest, + AuthServer = authServer.AuthUrl.ToString(), CanAuth = authenticate, NeedPubKey = !hasPubKey, - Encrypt = encrypt + Encrypt = encrypt, }; var outLoginMsg = peer.Peer.CreateMessage(); @@ -199,7 +200,7 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str } var joinReq = new JoinRequest(authHash, Base64Helpers.ToBase64Nullable(modernHwid)); - var request = new HttpRequestMessage(HttpMethod.Post, authServer + "api/session/join"); + var request = new HttpRequestMessage(HttpMethod.Post, authServer.AuthUrl + "api/session/join"); request.Content = JsonContent.Create(joinReq); request.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken); var joinResp = await _http.Client.SendAsync(request, cancel); diff --git a/Robust.Shared/Network/NetManager.ServerAuth.cs b/Robust.Shared/Network/NetManager.ServerAuth.cs index a8eda20dc..14c8d1db4 100644 --- a/Robust.Shared/Network/NetManager.ServerAuth.cs +++ b/Robust.Shared/Network/NetManager.ServerAuth.cs @@ -50,11 +50,11 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection) var isLocal = IPAddress.IsLoopback(ip) && _config.GetCVar(CVars.AuthAllowLocal); var canAuth = msgLogin.CanAuth; var needPk = msgLogin.NeedPubKey; - var authServer = _config.GetCVar(CVars.AuthServer); + var authServer = msgLogin.AuthServer; _logger.Verbose( $"{connection.RemoteEndPoint}: Received MsgLoginStart. " + - $"canAuth: {canAuth}, needPk: {needPk}, username: {msgLogin.UserName}, encrypt: {msgLogin.Encrypt}"); + $"canAuth: {canAuth}, needPk: {needPk}, username: {msgLogin.UserName}, auth server: {msgLogin.AuthServer}, encrypt: {msgLogin.Encrypt}"); _logger.Verbose( $"{connection.RemoteEndPoint}: Connection is specialized local? {isLocal} "); @@ -78,6 +78,13 @@ private async void HandleHandshake(NetPeerData peer, NetConnection connection) _logger.Verbose( $"{connection.RemoteEndPoint}: Initiating authentication"); + var servers = _authManager.Servers.Select(s => s.AuthUrl.ToString()).ToArray(); + if (!servers.Contains(authServer)) + { + connection.Disconnect($"Invalid or disallowed auth server {authServer}, needs to be one of {string.Join(", ", servers)}"); + return; + } + var verifyToken = new byte[4]; RandomNumberGenerator.Fill(verifyToken); var wantHwid = _config.GetCVar(CVars.NetHWId); From 92d34700f5621fe3fedc0996c18f5da066e931c5 Mon Sep 17 00:00:00 2001 From: DEATHB4DEFEAT Date: Sat, 22 Feb 2025 18:31:56 -0800 Subject: [PATCH 29/29] readme --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a8d9fc8c0..20410395f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -![Robust Toolbox](https://raw.githubusercontent.com/space-wizards/asset-dump/3dd3078e49e3a7e06709a6e0fc6e3223d8d44ca2/robust.png) +Supermatter Engine is a game engine primarily being developed for [Space Station 14](https://github.com/Simple-Station/Einstein-Engines). -Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects. - -Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself. +Use the [content repo](https://github.com/Simple-Station/Einstein-Engines) for actual development, even if you're modifying the engine itself. ## Project Links -[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/) +[Website](https://simplestation.org/) | [Discord](https://discord.gg/X4QEXxUrsJ) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/) ## Documentation/Wiki @@ -14,12 +12,12 @@ The [wiki](https://docs.spacestation14.io/) has documentation on SS14s content, ## Contributing -We are happy to accept contributions from anybody. Get in Discord or IRC if you want to help. We've got a [list of issues](https://github.com/space-wizards/RobustToolbox/issues) that need to be done and anybody can pick them up. Don't be afraid to ask for help either! +We are happy to accept contributions from anybody. It is recommended to join our Discord if you want to help. We've got a [list of issues](https://github.com/Simple-Station/SupermatterEngine/issues) that need to be done and anybody can pick them up. Don't be afraid to ask for help either! ## Building -This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Space Station 13. +This repository is the **engine** part of SS14. It's an engine SS14 servers are built on. As such, it does not start on its own: it needs the [content repo](https://github.com/Simple-Station/Einstein-Engines). Think of SME as BYOND in the context of Space Station 13. ## Legal Info -See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright. +See [legal.md](https://github.com/Simple-Station/SupermatterEngine/blob/master/legal.md) for licenses and copyright.