From e686e1b4ccabc9ff184f83b828660ded26b0ed37 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Sat, 16 Mar 2024 15:35:04 -0700 Subject: [PATCH 001/130] Add collection parsing to the dev window for UI (#4959) * Debug window collection popup * Try fixing? * DevUI shows children and string collections * XAMLify the popup * Rename popup * Deduplicate code * Simplify popup creation * MouseFilter.Ignore is default so it can be removed --- Robust.Client/Console/Commands/Debug.cs | 24 ++++++++++++- .../DevWindow/DevWindowTabUI.xaml | 6 +++- .../DevWindow/DevWindowTabUI.xaml.cs | 34 ++++++++++++++----- .../DevWindow/DevWindowTabUIPopup.xaml | 11 ++++++ .../DevWindow/DevWindowTabUIPopup.xaml.cs | 20 +++++++++++ 5 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml create mode 100644 Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml.cs diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs index 893710777f3..4bf7ceb6240 100644 --- a/Robust.Client/Console/Commands/Debug.cs +++ b/Robust.Client/Console/Commands/Debug.cs @@ -555,7 +555,7 @@ internal static List GetAllMembers(Control control) if (type != typeof(Control)) cname = $"Control > {cname}"; - returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null")); + returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", "))); } foreach (var (attachedProperty, value) in control.AllAttachedProperties) @@ -570,6 +570,28 @@ internal static List GetAllMembers(Control control) } return returnVal; } + + internal static string PropertyValuesString(Control control, string key) + { + var member = GetAllMembers(control).Find(m => m.Name == key); + return GetMemberValue(member, control, "\n", "\"{0}\""); + } + + private static string GetMemberValue(MemberInfo? member, Control control, string separator, string + wrap = "{0}") + { + var value = member?.GetValue(control); + var o = value switch + { + ICollection controls => string.Join(separator, + controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")), + ICollection list => string.Join(separator, list), + null => null, + _ => value.ToString() + }; + // Convert to quote surrounded string or null with no quotes + return o is not null ? string.Format(wrap, o) : "null"; + } } internal sealed class SetClipboardCommand : LocalizedCommands diff --git a/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml b/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml index d3136adff39..59a6aa4371a 100644 --- a/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml +++ b/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml @@ -20,10 +20,14 @@ - + + + + + diff --git a/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml.cs b/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml.cs index aaea6a9d8c2..a7343f4eb31 100644 --- a/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml.cs +++ b/Robust.Client/UserInterface/DevWindow/DevWindowTabUI.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Numerics; using Robust.Client.AutoGenerated; using Robust.Client.Console.Commands; using Robust.Client.Graphics; @@ -208,25 +209,40 @@ private void Refresh() }); foreach (var (prop, value) in values) { - ControlProperties.AddChild(new BoxContainer + var button = new ContainerButton { - Orientation = BoxContainer.LayoutOrientation.Horizontal, - SeparationOverride = 3, - Margin = new Thickness(3, 1), Children = { new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, + SeparationOverride = 3, + Margin = new Thickness(3, 1), Children = { - new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow }, - new Label { Text = ":" }, // this is for the non colored ":", intentional + new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow }, + new Label { Text = ":" }, // this is for the non colored ":", intentional + } + }, + new Label { Text = $"{value}" }, } - }, - new Label { Text = $"{value}" }, + } } - }); + }; + button.OnPressed += _ => + { + // TODO replace with parenting to popup container on WindowRoot + UIPopup.Text = GuiDumpCommand.PropertyValuesString(SelectedControl, prop); + var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position + - GlobalPosition, Vector2.One); + UIPopup.Open(box); + }; + ControlProperties.AddChild(button); } } } diff --git a/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml b/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml new file mode 100644 index 00000000000..a2cfba4c89e --- /dev/null +++ b/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml.cs b/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml.cs new file mode 100644 index 00000000000..b22f66bd60a --- /dev/null +++ b/Robust.Client/UserInterface/DevWindow/DevWindowTabUIPopup.xaml.cs @@ -0,0 +1,20 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Robust.Client.UserInterface; + +[GenerateTypedNameReferences] +internal sealed partial class DevWindowTabUIPopup : Popup +{ + public string? Text + { + get => TextLabel.Text; + set => TextLabel.Text = value; + } + + public DevWindowTabUIPopup() + { + RobustXamlLoader.Load(this); + } +} From 83371885faec852cc1e1c54b7605aed290f7fb46 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:28:50 +1100 Subject: [PATCH 002/130] Support transform states with unknown parents (#4972) --- .../GameStates/ClientGameStateManager.cs | 2 +- .../GameStates/PvsSystem.ToSendSet.cs | 39 ++-- Robust.Server/GameStates/PvsSystem.cs | 7 - .../SharedTransformSystem.Component.cs | 5 +- Robust.UnitTesting/RobustIntegrationTest.cs | 6 + .../Server/GameStates/MissingParentTest.cs | 171 ++++++++++++++++++ 6 files changed, 191 insertions(+), 39 deletions(-) create mode 100644 Robust.UnitTesting/Server/GameStates/MissingParentTest.cs diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 77174dc090d..f048353f8f6 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -700,7 +700,7 @@ public IEnumerable ApplyGameState(GameState curState, GameState? next { using var _ = _timing.StartStateApplicationArea(); - // TODO repays optimize this. + // TODO replays optimize this. // This currently just saves game states as they are applied. // However this is inefficient and may have redundant data. // E.g., we may record states: [10 to 15] [11 to 16] *error* [0 to 18] [18 to 19] [18 to 20] ... diff --git a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs index 7ca5e51d4e2..f6211f40b60 100644 --- a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs +++ b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs @@ -48,30 +48,17 @@ private void AddPvsChunk(PvsChunk chunk, float distance, PvsSession session) // We add chunk-size here so that its consistent with the normal PVS range setting. // I.e., distance here is the Chebyshev distance to the centre of each chunk, but the normal pvs range only // required that the chunk be touching the box, not the centre. - var limit = distance < (_lowLodDistance + ChunkSize) / 2 + var count = distance < (_lowLodDistance + ChunkSize) / 2 ? chunk.Contents.Count : chunk.LodCounts[0]; - // If the PVS budget is exceeded, it should still be safe to send all of the chunk's direct children, though - // after that we have no guarantee that an entity's parent got sent. - var directChildren = Math.Min(limit, chunk.LodCounts[2]); - // Send entities on the chunk. - var span = CollectionsMarshal.AsSpan(chunk.Contents); - for (var i = 0; i < limit; i++) + var span = CollectionsMarshal.AsSpan(chunk.Contents)[..count]; + foreach (ref var ent in span) { - var ent = span[i]; ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index); - - if ((mask & meta.VisMask) != meta.VisMask) - continue; - - // TODO PVS improve this somehow - // Having entities "leave" pvs view just because the pvs entry budget was exceeded sucks. - // This probably requires changing client game state manager to support receiving entities with unknown parents. - // Probably needs to do something similar to pending net entity states, but for entity spawning. - if (!AddEntity(session, ref ent, ref meta, fromTick)) - limit = directChildren; + if ((mask & meta.VisMask) == meta.VisMask) + AddEntity(session, ref ent, ref meta, fromTick); } } @@ -80,26 +67,26 @@ private void AddPvsChunk(PvsChunk chunk, float distance, PvsSession session) /// /// Returns false if the entity would exceed the client's PVS budget. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref PvsMetadata meta, + private void AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref PvsMetadata meta, GameTick fromTick) { DebugTools.Assert(fromTick < _gameTiming.CurTick); ref var data = ref session.DataMemory.GetRef(ent.Ptr.Index); if (data.LastSeen == _gameTiming.CurTick) - return true; + return; if (meta.LifeStage >= EntityLifeStage.Terminating) { Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}"); EntityManager.QueueDeleteEntity(ent.Uid); - return true; + return; } var (entered,budgetExceeded) = IsEnteringPvsRange(ref data, fromTick, ref session.Budget); if (budgetExceeded) - return false; + return; data.LastSeen = _gameTiming.CurTick; session.ToSend!.Add(ent.Ptr); @@ -108,25 +95,23 @@ private bool AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref Pvs { var state = GetFullEntityState(session.Session, ent.Uid, ent.Meta); session.States.Add(state); - return true; + return; } if (entered) { var state = GetEntityState(session.Session, ent.Uid, data.EntityLastAcked, ent.Meta); session.States.Add(state); - return true; + return; } if (meta.LastModifiedTick <= fromTick) - return true; + return; var entState = GetEntityState(session.Session, ent.Uid, fromTick , ent.Meta); if (!entState.Empty) session.States.Add(entState); - - return true; } /// diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index a451b31a983..4df38b34a95 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -372,13 +372,6 @@ private void VerifySessionData(PvsSession pvsSession) { ref var data = ref pvsSession.DataMemory.GetRef(intPtr.Index); DebugTools.AssertEqual(data.LastSeen, _gameTiming.CurTick); - - // if an entity is visible, its parents should always be visible. - if (_xformQuery.GetComponent(GetEntity(IndexToNetEntity(intPtr))).ParentUid is not {Valid: true} pUid) - continue; - - DebugTools.Assert(toSendSet.Contains(GetNetEntity(pUid)), - $"Attempted to send an entity without sending it's parents. Entity: {ToPrettyString(pUid)}."); } pvsSession.PreviouslySent.TryGetValue(_gameTiming.CurTick - 1, out var lastSent); diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index da50c99bd85..97f4069c862 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -722,10 +722,7 @@ internal void OnHandleState(EntityUid uid, TransformComponent xform, ref Compone { if (args.Current is TransformComponentState newState) { - var parent = GetEntity(newState.ParentID); - if (!parent.IsValid() && newState.ParentID.IsValid()) - Log.Error($"Received transform component state with an unknown parent Id. Entity: {ToPrettyString(uid)}. Net parent: {newState.ParentID}"); - + var parent = EnsureEntity(newState.ParentID, uid); var oldAnchored = xform.Anchored; // update actual position data, if required diff --git a/Robust.UnitTesting/RobustIntegrationTest.cs b/Robust.UnitTesting/RobustIntegrationTest.cs index d74bd11ab92..a26a545e959 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.cs @@ -303,6 +303,12 @@ public MetaDataComponent MetaData(EntityUid uid) return EntMan.GetComponent(uid); } + public MetaDataComponent MetaData(NetEntity uid) + => MetaData(EntMan.GetEntity(uid)); + + public TransformComponent Transform(NetEntity uid) + => Transform(EntMan.GetEntity(uid)); + public async Task ExecuteCommand(string cmd) { await WaitPost(() => ConsoleHost.ExecuteCommand(cmd)); diff --git a/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs b/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs new file mode 100644 index 00000000000..233e25cb263 --- /dev/null +++ b/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs @@ -0,0 +1,171 @@ +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; + +namespace Robust.UnitTesting.Server.GameStates; + +public sealed class MissingParentTest : RobustIntegrationTest +{ + /// + /// Check that PVS & clients can handle entities being sent before their parents are. + /// + [Test] + public async Task TestMissingParent() + { + var server = StartServer(); + var client = StartClient(); + + await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); + + var mapMan = server.ResolveDependency(); + var sEntMan = server.ResolveDependency(); + var confMan = server.ResolveDependency(); + var sPlayerMan = server.ResolveDependency(); + + var cEntMan = client.ResolveDependency(); + var netMan = client.ResolveDependency(); + var cPlayerMan = client.ResolveDependency(); + var cConfMan = client.ResolveDependency(); + + Assert.DoesNotThrow(() => client.SetConnectTarget(server)); + client.Post(() => netMan.ClientConnect(null!, 0, null!)); + server.Post(() => confMan.SetCVar(CVars.NetPVS, true)); + + for (int i = 0; i < 10; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + + // Limit client to receiving at most 1 entity per tick. + cConfMan.SetCVar(CVars.NetPVSEntityBudget, 1); + + for (int i = 0; i < 10; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + + // Ensure client & server ticks are synced. + // Client runs 1 tick ahead + { + var sTick = (int)server.Timing.CurTick.Value; + var cTick = (int)client.Timing.CurTick.Value; + var delta = cTick - sTick; + + if (delta > 1) + await server.WaitRunTicks(delta - 1); + else if (delta < 1) + await client.WaitRunTicks(1 - delta); + + sTick = (int)server.Timing.CurTick.Value; + cTick = (int)client.Timing.CurTick.Value; + delta = cTick - sTick; + Assert.That(delta, Is.EqualTo(1)); + } + + // Set up map and spawn player + EntityUid map = default; + NetEntity player = default; + NetEntity entity = default; + EntityCoordinates coords = default; + NetCoordinates nCoords = default; + await server.WaitPost(() => + { + var mapId = mapMan.CreateMap(); + map = mapMan.GetMapEntityId(mapId); + coords = new(map, default); + + var playerUid = sEntMan.SpawnEntity(null, coords); + var entUid = sEntMan.SpawnEntity(null, coords); + entity = sEntMan.GetNetEntity(entUid); + player = sEntMan.GetNetEntity(playerUid); + nCoords = sEntMan.GetNetCoordinates(coords); + + // Attach player. + var session = sPlayerMan.Sessions.First(); + server.PlayerMan.SetAttachedEntity(session, playerUid); + sPlayerMan.JoinGame(session); + }); + + for (int i = 0; i < 10; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + + Assert.That(player, Is.Not.EqualTo(NetEntity.Invalid)); + Assert.That(entity, Is.Not.EqualTo(NetEntity.Invalid)); + + // Check player got properly attached, and has received the other entity. + Assert.That(cEntMan.TryGetEntityData(entity, out _, out var meta)); + Assert.That(cEntMan.TryGetEntity(player, out var cPlayerUid)); + Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(cPlayerUid)); + Assert.That(server.Transform(player).Coordinates, Is.EqualTo(coords)); + Assert.That(client.Transform(player).Coordinates, Is.EqualTo(client.EntMan.GetCoordinates(nCoords))); + Assert.That(client.Transform(entity).ParentUid.IsValid(), Is.True); + Assert.That(client.MetaData(entity).Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None)); + + // Spawn 20 new entities + NetEntity first = default; + NetEntity last = default; + await server.WaitPost(() => + { + first = sEntMan.GetNetEntity(sEntMan.SpawnEntity(null, coords)); + for (var i = 0; i < 18; i++) + { + sEntMan.SpawnEntity(null, coords); + } + last = sEntMan.GetNetEntity(sEntMan.SpawnEntity(null, coords)); + }); + + // Wait for the client to receive some, but not all, of the entities + for (int i = 0; i < 8; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + Assert.That(cEntMan.TryGetEntity(first, out _), Is.True); + Assert.That(cEntMan.TryGetEntity(last, out _), Is.False); + + // Re-parent the known entity to an entity that the client has not received yet. + await server.WaitPost(() => + { + var newCoords = new EntityCoordinates(sEntMan.GetEntity(last), default); + server.System().SetCoordinates(sEntMan.GetEntity(entity), newCoords); + }); + + // Wait a few more ticks + for (int i = 0; i < 8; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + + // Client should still not have received the new parent, however this shouldn't cause any issues. + // The already known entity should just have been moved to nullspace. + Assert.That(cEntMan.TryGetEntity(last, out _), Is.False); + Assert.That(client.Transform(entity).ParentUid.IsValid(), Is.False); + Assert.That(client.MetaData(entity).Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None)); + + // Wait untill the client receives the parent entity + for (int i = 0; i < 10; i++) + { + await server.WaitRunTicks(1); + await client.WaitRunTicks(1); + } + + // now that the parent was received the entity should no longer be in nullspace. + Assert.That(cEntMan.TryGetEntity(last, out var newParent), Is.True); + Assert.That(client.Transform(entity).ParentUid.IsValid(), Is.True); + Assert.That(client.Transform(entity).ParentUid, Is.EqualTo(newParent)); + Assert.That(client.MetaData(entity).Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None)); + } +} + From 43a32e70158421bba81c47ed50121330b0b69c99 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 17 Mar 2024 16:40:06 +0100 Subject: [PATCH 003/130] Fix dump_netserializer_type_map to show full data. The output it gave didn't show the full info to generate the manifest (e.g. assembly version numbers) --- .../Commands/DumpSerializerTypeMapCommand.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Robust.Shared/Console/Commands/DumpSerializerTypeMapCommand.cs b/Robust.Shared/Console/Commands/DumpSerializerTypeMapCommand.cs index 3ab807ee602..1f24ab33f28 100644 --- a/Robust.Shared/Console/Commands/DumpSerializerTypeMapCommand.cs +++ b/Robust.Shared/Console/Commands/DumpSerializerTypeMapCommand.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.IO; using Robust.Shared.IoC; using Robust.Shared.Serialization; @@ -12,11 +12,16 @@ internal sealed class DumpSerializerTypeMapCommand : LocalizedCommands public override void Execute(IConsoleShell shell, string argStr, string[] args) { - foreach (var (type, index) in _robustSerializer.GetTypeMap().OrderBy(x => x.Value)) - { - shell.WriteLine($"{index}: {type}"); - } + var stream = new MemoryStream(); + ((RobustSerializer)_robustSerializer).GetHashManifest(stream, true); + stream.Position = 0; + using var streamReader = new StreamReader(stream); shell.WriteLine($"Hash: {_robustSerializer.GetSerializableTypesHashString()}"); + shell.WriteLine("Manifest:"); + while (streamReader.ReadLine() is { } line) + { + shell.WriteLine(line); + } } } From 86ecfaa56b80bf10433f97c789223f1555038f6e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 17 Mar 2024 16:43:01 +0100 Subject: [PATCH 004/130] Allow replays with mismatching type hashes. Things like .NET bumps and engine minor version updates break the hashes. Just allow mismatching hashes, printing a warning to the log at least. If the hash mismatches it'll explode with some weird error like invalidcast/index so whatever. It'd be better if there was a "hey this might not work, you sure you want to load this?" screen, but I don't feel like rewriting the entire replay loading system right now. Fixes https://github.com/space-wizards/SS14.Launcher/issues/142 --- RELEASE-NOTES.md | 2 +- Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9d6f5fb0749..b65bee19a2f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,7 +47,7 @@ END TEMPLATE--> ### Other -*None yet* +* The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates). ### Internal diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs index 885501e85e9..a77c455e713 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs @@ -153,7 +153,8 @@ public async Task LoadReplayAsync(IReplayFileReader fileReader, Load duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value); } - var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value); + var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value; + var typeHash = Convert.FromHexString(typeHashString); var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value); var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value; var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value; @@ -161,7 +162,9 @@ public async Task LoadReplayAsync(IReplayFileReader fileReader, Load var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value); if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash())) - throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?"); + { + _sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!!! Our hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}"); + } using var stringFile = fileReader.Open(FileStrings); var stringData = new byte[stringFile.Length]; From c8cb13f83284d3b86d9a92cfd3b9258996e8a223 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:01:00 +1100 Subject: [PATCH 005/130] Fix serialization error logging (#4973) --- .../Implementations/ComponentRegistrySerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/ComponentRegistrySerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/ComponentRegistrySerializer.cs index fdc658611a2..f0f97b0d260 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/ComponentRegistrySerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/ComponentRegistrySerializer.cs @@ -57,7 +57,7 @@ public ComponentRegistry Read(ISerializationManager serializationManager, dependencies .Resolve() .GetSawmill(SerializationManager.LogCategory) - .Error(SerializationManager.LogCategory, $"Component of type '{compType}' defined twice in prototype!"); + .Error($"Component of type '{compType}' defined twice in prototype!"); continue; } From 0245c371aee2a88df08b35a93b1105afab4671f4 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 18 Mar 2024 08:50:00 +1100 Subject: [PATCH 006/130] Make the replay has error use the ReplayIgnoreErrors cvar (#4974) --- .../Replays/Loading/ReplayLoadManager.Read.cs | 25 ++++++++----------- .../Replays/UI/ReplayControlWidget.cs | 5 ++-- Robust.Shared/CVars.cs | 3 ++- Robust.Shared/Replays/ReplayData.cs | 4 +-- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs index a77c455e713..023f2aab7ba 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs @@ -129,7 +129,7 @@ public async Task LoadReplayAsync(IReplayFileReader fileReader, Load return parsed.FirstOrDefault()?.Root as MappingDataNode; } - private (MappingDataNode YamlData, HashSet CVars, TimeSpan Duration, TimeSpan StartTime, bool ClientSide) + private (MappingDataNode YamlData, HashSet CVars, TimeSpan? Duration, TimeSpan StartTime, bool ClientSide) LoadMetadata(IReplayFileReader fileReader) { _sawmill.Info($"Reading replay metadata"); @@ -137,21 +137,13 @@ public async Task LoadReplayAsync(IReplayFileReader fileReader, Load if (data == null) throw new Exception("Failed to load yaml metadata"); - TimeSpan duration; var finalData = LoadYamlFinalMetadata(fileReader); - if (finalData == null) - { - var msg = "Failed to load final yaml metadata"; - if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors)) - throw new Exception(msg); + TimeSpan? duration = finalData == null + ? null + : TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value); - _sawmill.Error(msg); - duration = TimeSpan.FromDays(1); - } - else - { - duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value); - } + if (finalData == null) + _sawmill.Warning("Failed to load final yaml metadata. Partial/incomplete replay?"); var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value; var typeHash = Convert.FromHexString(typeHashString); @@ -163,7 +155,10 @@ public async Task LoadReplayAsync(IReplayFileReader fileReader, Load if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash())) { - _sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!!! Our hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}"); + if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors)) + throw new Exception($"RobustSerializer hash mismatch. do not match. Client hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}."); + + _sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!"); } using var stringFile = fileReader.Open(FileStrings); diff --git a/Robust.Client/Replays/UI/ReplayControlWidget.cs b/Robust.Client/Replays/UI/ReplayControlWidget.cs index dc8589733c1..3e9db292e30 100644 --- a/Robust.Client/Replays/UI/ReplayControlWidget.cs +++ b/Robust.Client/Replays/UI/ReplayControlWidget.cs @@ -90,6 +90,7 @@ protected override void FrameUpdate(FrameEventArgs args) var maxIndex = Math.Max(1, replay.States.Count - 1); var state = replay.States[index]; var replayTime = TimeSpan.FromSeconds(TickSlider.Value); + var end = replay.Duration == null ? "N/A" : replay.Duration.Value.ToString(TimeFormat); IndexLabel.Text = Loc.GetString("replay-time-box-index-label", ("current", index), ("total", maxIndex), ("percentage", percentage)); @@ -98,10 +99,10 @@ protected override void FrameUpdate(FrameEventArgs args) ("current", state.ToSequence), ("total", replay.States[^1].ToSequence), ("percentage", percentage)); TimeLabel.Text = Loc.GetString("replay-time-box-replay-time-label", - ("current", replayTime.ToString(TimeFormat)), ("end", replay.Duration.ToString(TimeFormat)), ("percentage", percentage)); + ("current", replayTime.ToString(TimeFormat)), ("end", end), ("percentage", percentage)); var serverTime = (replayTime + replay.StartTime).ToString(TimeFormat); - var duration = (replay.Duration + replay.StartTime).ToString(TimeFormat); + string duration = replay.Duration == null ? "N/A" : (replay.Duration + replay.StartTime).Value.ToString(TimeFormat); ServerTimeLabel.Text = Loc.GetString("replay-time-box-server-time-label", ("current", serverTime), ("end", duration), ("percentage", percentage)); diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index da908fd9e6e..7f715bf9181 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1611,7 +1611,8 @@ protected CVars() /// original exception rather than sending people on a wild-goose chase to find a non-existent bug. /// public static readonly CVarDef ReplayIgnoreErrors = - CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY | CVar.ARCHIVE); + CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY); + /* * CFG */ diff --git a/Robust.Shared/Replays/ReplayData.cs b/Robust.Shared/Replays/ReplayData.cs index 027915ddb93..20aa05451df 100644 --- a/Robust.Shared/Replays/ReplayData.cs +++ b/Robust.Shared/Replays/ReplayData.cs @@ -50,7 +50,7 @@ public sealed class ReplayData /// /// The length of this recording. /// - public readonly TimeSpan Duration; + public readonly TimeSpan? Duration; /// /// Array of checkpoint states. These are full game states that make it faster to jump around in time. @@ -95,7 +95,7 @@ public ReplayData(List states, TimeSpan[] replayTime, GameTick tickOffset, TimeSpan startTime, - TimeSpan duration, + TimeSpan? duration, CheckpointState[] checkpointStates, ReplayMessage? initData, bool clientSideRecording, From 16c7c71ca6262ae629eb72374c52adc088204f0b Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 18 Mar 2024 16:36:52 -0400 Subject: [PATCH 007/130] Code cleanup: Dirty(Comp) (#4979) * The easy ones * SharedPhysicsSystem --- .../Systems/SharedMapSystem.Light.cs | 5 +- .../Controllers/Gravity2DController.cs | 4 +- .../Physics/Systems/FixtureSystem.cs | 4 +- .../Systems/SharedPhysicsSystem.Components.cs | 84 +++++++++++++------ .../Systems/SharedPhysicsSystem.Island.cs | 2 +- .../Systems/SharedPhysicsSystem.Shapes.cs | 4 +- 6 files changed, 70 insertions(+), 33 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Light.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Light.cs index 9cd9bcf4532..d7052777404 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Light.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Light.cs @@ -11,13 +11,14 @@ public abstract partial class SharedMapSystem { public void SetAmbientLight(MapId mapId, Color color) { - var mapComp = EnsureComp(MapManager.GetMapEntityId(mapId)); + var mapUid = MapManager.GetMapEntityId(mapId); + var mapComp = EnsureComp(mapUid); if (mapComp.AmbientLightColor.Equals(color)) return; mapComp.AmbientLightColor = color; - Dirty(mapComp); + Dirty(mapUid, mapComp); } private void OnMapLightGetState(EntityUid uid, MapLightComponent component, ref ComponentGetState args) diff --git a/Robust.Shared/Physics/Controllers/Gravity2DController.cs b/Robust.Shared/Physics/Controllers/Gravity2DController.cs index f3d8d8b6aff..81bd5cbe9c5 100644 --- a/Robust.Shared/Physics/Controllers/Gravity2DController.cs +++ b/Robust.Shared/Physics/Controllers/Gravity2DController.cs @@ -67,7 +67,7 @@ public void SetGravity(EntityUid uid, Vector2 value) gravity.Gravity = value; WakeBodiesRecursive(uid, GetEntityQuery(), GetEntityQuery()); - Dirty(gravity); + Dirty(uid, gravity); } public void SetGravity(MapId mapId, Vector2 value) @@ -80,7 +80,7 @@ public void SetGravity(MapId mapId, Vector2 value) gravity.Gravity = value; WakeBodiesRecursive(mapUid, GetEntityQuery(), GetEntityQuery()); - Dirty(gravity); + Dirty(mapUid, gravity); } private void WakeBodiesRecursive(EntityUid uid, EntityQuery xformQuery, EntityQuery bodyQuery) diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.cs b/Robust.Shared/Physics/Systems/FixtureSystem.cs index cbb763112b6..03c2a6fb9a5 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.cs @@ -114,7 +114,7 @@ internal void CreateFixture( // Don't need to dirty here as we'll just manually call it after (we 100% need to call it). FixtureUpdate(uid, false, manager: manager, body: body); // Don't need to ResetMassData as FixtureUpdate already does it. - Dirty(manager); + Dirty(uid, manager); } // TODO: Set newcontacts to true. } @@ -361,7 +361,7 @@ public void FixtureUpdate(EntityUid uid, bool dirty = true, bool resetMass = tru } if (dirty) - Dirty(manager); + Dirty(uid, manager); } public int GetFixtureCount(EntityUid uid, FixturesComponent? manager = null) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 6a11a1280d6..487c06b231b 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -99,9 +99,9 @@ private void OnPhysicsHandleState(EntityUid uid, PhysicsComponent component, ref SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager); SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager); SetBodyType(uid, newState.BodyType, manager, component); - SetFriction(component, newState.Friction); - SetLinearDamping(component, newState.LinearDamping); - SetAngularDamping(component, newState.AngularDamping); + SetFriction(uid, component, newState.Friction); + SetLinearDamping(uid, component, newState.LinearDamping); + SetAngularDamping(uid, component, newState.AngularDamping); } #endregion @@ -132,7 +132,7 @@ public void ApplyForce(EntityUid uid, Vector2 force, Vector2 point, FixturesComp body.Force += force; body.Torque += Vector2Helpers.Cross(point - body._localCenter, force); - Dirty(body); + Dirty(uid, body); } public void ApplyForce(EntityUid uid, Vector2 force, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -143,7 +143,7 @@ public void ApplyForce(EntityUid uid, Vector2 force, FixturesComponent? manager } body.Force += force; - Dirty(body); + Dirty(uid, body); } public void ApplyTorque(EntityUid uid, float torque, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -154,7 +154,7 @@ public void ApplyTorque(EntityUid uid, float torque, FixturesComponent? manager } body.Torque += torque; - Dirty(body); + Dirty(uid, body); } public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -202,7 +202,7 @@ public void DestroyContacts(PhysicsComponent body) /// /// Completely resets a dynamic body. /// - public void ResetDynamics(PhysicsComponent body, bool dirty = true) + public void ResetDynamics(EntityUid uid, PhysicsComponent body, bool dirty = true) { var updated = false; @@ -231,7 +231,13 @@ public void ResetDynamics(PhysicsComponent body, bool dirty = true) } if (updated && dirty) - Dirty(body); + Dirty(uid, body); + } + + [Obsolete("Use overload that takes EntityUid")] + public void ResetDynamics(PhysicsComponent body, bool dirty = true) + { + ResetDynamics(body.Owner, body, dirty); } public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -261,7 +267,7 @@ public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, Phys if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0) { body._localCenter = Vector2.Zero; - Dirty(body); + Dirty(uid, body); return; } @@ -296,7 +302,7 @@ public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, Phys // Update center of mass velocity. body.LinearVelocity += Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter); - Dirty(body); + Dirty(uid, body); } public void SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null) @@ -350,7 +356,7 @@ public void SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true Dirty(uid, body); } - public void SetAngularDamping(PhysicsComponent body, float value, bool dirty = true) + public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { if (MathHelper.CloseTo(body.AngularDamping, value)) return; @@ -358,10 +364,16 @@ public void SetAngularDamping(PhysicsComponent body, float value, bool dirty = t body.AngularDamping = value; if (dirty) - Dirty(body); + Dirty(uid, body); } - public void SetLinearDamping(PhysicsComponent body, float value, bool dirty = true) + [Obsolete("Use overload that takes EntityUid")] + public void SetAngularDamping(PhysicsComponent body, float value, bool dirty = true) + { + SetAngularDamping(body.Owner, body, value, dirty); + } + + public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { if (MathHelper.CloseTo(body.LinearDamping, value)) return; @@ -369,7 +381,13 @@ public void SetLinearDamping(PhysicsComponent body, float value, bool dirty = tr body.LinearDamping = value; if (dirty) - Dirty(body); + Dirty(uid, body); + } + + [Obsolete("Use overload that takes EntityUid")] + public void SetLinearDamping(PhysicsComponent body, float value, bool dirty = true) + { + SetLinearDamping(body.Owner, body, value, dirty); } [Obsolete("Use SetAwake with EntityUid")] @@ -403,7 +421,7 @@ public void SetAwake(Entity ent, bool value, bool updateSleepT { var ev = new PhysicsSleepEvent(uid, body); RaiseLocalEvent(uid, ref ev, true); - ResetDynamics(body, dirty: false); + ResetDynamics(ent, body, dirty: false); } // Update wake system last, if sleeping but still colliding. @@ -470,7 +488,7 @@ public void SetBodyType(EntityUid uid, BodyType value, FixturesComponent? manage } } - public void SetBodyStatus(PhysicsComponent body, BodyStatus status, bool dirty = true) + public void SetBodyStatus(EntityUid uid, PhysicsComponent body, BodyStatus status, bool dirty = true) { if (body.BodyStatus == status) return; @@ -478,7 +496,13 @@ public void SetBodyStatus(PhysicsComponent body, BodyStatus status, bool dirty = body.BodyStatus = status; if (dirty) - Dirty(body); + Dirty(uid, body); + } + + [Obsolete("Use overload that takes EntityUid")] + public void SetBodyStatus(PhysicsComponent body, BodyStatus status, bool dirty = true) + { + SetBodyStatus(body.Owner, body, status, dirty); } /// @@ -531,7 +555,7 @@ public bool SetCanCollide( } if (dirty) - Dirty(body); + Dirty(uid, body); return value; } @@ -546,10 +570,10 @@ public void SetFixedRotation(EntityUid uid, bool value, bool dirty = true, Fixtu ResetMassData(uid, manager: manager, body: body); if (dirty) - Dirty(body); + Dirty(uid, body); } - public void SetFriction(PhysicsComponent body, float value, bool dirty = true) + public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { if (MathHelper.CloseTo(body.Friction, value)) return; @@ -557,10 +581,16 @@ public void SetFriction(PhysicsComponent body, float value, bool dirty = true) body._friction = value; if (dirty) - Dirty(body); + Dirty(uid, body); } - public void SetInertia(PhysicsComponent body, float value, bool dirty = true) + [Obsolete("Use overload that takes EntityUid")] + public void SetFriction(PhysicsComponent body, float value, bool dirty = true) + { + SetFriction(body.Owner, body, value, dirty); + } + + public void SetInertia(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { DebugTools.Assert(!float.IsNaN(value)); @@ -575,10 +605,16 @@ public void SetInertia(PhysicsComponent body, float value, bool dirty = true) body.InvI = 1.0f / body._inertia; if (dirty) - Dirty(body); + Dirty(uid, body); } } + [Obsolete("Use overload that takes EntityUid")] + public void SetInertia(PhysicsComponent body, float value, bool dirty = true) + { + SetInertia(body.Owner, body, value, dirty); + } + public void SetLocalCenter(EntityUid uid, PhysicsComponent body, Vector2 value) { if (body.BodyType != BodyType.Dynamic) return; @@ -599,7 +635,7 @@ public void SetSleepingAllowed(EntityUid uid, PhysicsComponent body, bool value, body.SleepingAllowed = value; if (dirty) - Dirty(body); + Dirty(uid, body); } public void SetSleepTime(PhysicsComponent body, float value) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs index 17fc48d3a72..0e4fb5a7288 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs @@ -1049,7 +1049,7 @@ private void UpdateBodies( } // TODO: Should check if the values update. - Dirty(body, metaQuery.GetComponent(uid)); + Dirty(uid, body, metaQuery.GetComponent(uid)); } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs index b6854bc0d52..ddece0e5d0e 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs @@ -65,7 +65,7 @@ public void SetPositionRadius( _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); } - Dirty(manager); + Dirty(uid, manager); } public void SetPosition( @@ -91,7 +91,7 @@ public void SetPosition( _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); } - Dirty(manager); + Dirty(uid, manager); } #endregion From d8b03be651971d8f6074d16f479419373e3a9641 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 19 Mar 2024 07:37:25 +1100 Subject: [PATCH 008/130] Add debug assert to Dirty(uid, comp) (#4978) --- Robust.Shared/GameObjects/EntityManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 2ea4e71cfb4..621b88d74ef 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -354,7 +354,7 @@ public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = nul } /// - [Obsolete("use override with an EntityUid")] + [Obsolete("use override with an EntityUid or Entity")] public void Dirty(IComponent component, MetaDataComponent? meta = null) { Dirty(component.Owner, component, meta); @@ -365,6 +365,7 @@ public virtual void Dirty(EntityUid uid, IComponent component, MetaDataComponent { DebugTools.Assert(component.GetType().HasCustomAttribute(), $"Attempted to dirty a non-networked component: {component.GetType()}"); + DebugTools.AssertOwner(uid, component); if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled) return; From 28cf7442ced3beccda3614b67184ac2d2c3df04e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 18 Mar 2024 19:55:55 +0100 Subject: [PATCH 009/130] Fix naming of ResizableMemoryRegion metrics --- Robust.Shared/Utility/ResizableMemoryRegion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Utility/ResizableMemoryRegion.cs b/Robust.Shared/Utility/ResizableMemoryRegion.cs index 8580f155911..fc3ac148250 100644 --- a/Robust.Shared/Utility/ResizableMemoryRegion.cs +++ b/Robust.Shared/Utility/ResizableMemoryRegion.cs @@ -15,9 +15,9 @@ namespace Robust.Shared.Utility; /// internal static class ResizableMemoryRegionMetrics { - public static readonly Meter Meter = new("Robust.Shared.Utility.ResizableMemoryRegion"); + public static readonly Meter Meter = new("Robust.ResizableMemoryRegion"); - public const string GaugeName = "robust_resizable_memory_used_bytes"; + public const string GaugeName = "used_bytes"; } // TODO: Proper implementation on Linux that uses mmap()/madvise()/mprotect(). From 390f3997505f58f100123d0e341db5c35e8bc6e0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 20 Mar 2024 09:17:38 +0100 Subject: [PATCH 010/130] Add IMetricsManager.UpdateMetrics system This callback enables code to update its metrics only when required. Needed this for SS14 since online admin count stats are not something I want to update on an "arbitrary" basis. Tons of consideration and commenting for how this plays in with stuff like dotnet-counters. Added the metrics.update_interval CVar to act as a fallback for this event when dotnet-counters and such is in use. --- RELEASE-NOTES.md | 3 +- Robust.Server/BaseServer.cs | 4 +- .../MetricsManager.MetricsServer.cs | 17 ++++- .../MetricsManager.UpdateMetrics.cs | 62 +++++++++++++++++++ Robust.Server/DataMetrics/MetricsManager.cs | 44 +++++++++++-- Robust.Server/ServerIoC.cs | 1 + Robust.Shared/Asynchronous/TaskManager.cs | 27 ++++++++ Robust.Shared/CVars.cs | 29 +++++++++ 8 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b65bee19a2f..01e0f63290c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,8 @@ END TEMPLATE--> ### New features -*None yet* +* Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped. + * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. ### Bugfixes diff --git a/Robust.Server/BaseServer.cs b/Robust.Server/BaseServer.cs index f8e92bc7cbc..8bc2cf55f47 100644 --- a/Robust.Server/BaseServer.cs +++ b/Robust.Server/BaseServer.cs @@ -89,7 +89,7 @@ internal sealed class BaseServer : IBaseServerInternal, IPostInjectInit [Dependency] private readonly IWatchdogApi _watchdogApi = default!; [Dependency] private readonly HubManager _hubManager = default!; [Dependency] private readonly IScriptHost _scriptHost = default!; - [Dependency] private readonly IMetricsManager _metricsManager = default!; + [Dependency] private readonly IMetricsManagerInternal _metricsManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!; [Dependency] private readonly ILocalizationManagerInternal _loc = default!; @@ -749,6 +749,8 @@ private void FrameUpdate(FrameEventArgs frameEventArgs) _hubManager.Heartbeat(); _modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs); + + _metricsManager.FrameUpdate(); } void IPostInjectInit.PostInject() diff --git a/Robust.Server/DataMetrics/MetricsManager.MetricsServer.cs b/Robust.Server/DataMetrics/MetricsManager.MetricsServer.cs index 958a3e903a6..35c6abbb66f 100644 --- a/Robust.Server/DataMetrics/MetricsManager.MetricsServer.cs +++ b/Robust.Server/DataMetrics/MetricsManager.MetricsServer.cs @@ -18,13 +18,20 @@ internal sealed partial class MetricsManager private sealed class ManagedHttpListenerMetricsServer : MetricHandler { private readonly ISawmill _sawmill; + private readonly Func? _beforeCollect; private readonly HttpListener _listener; private readonly CollectorRegistry _registry; - public ManagedHttpListenerMetricsServer(ISawmill sawmill, string host, int port, string url = "metrics/", - CollectorRegistry? registry = null) + public ManagedHttpListenerMetricsServer( + ISawmill sawmill, + string host, + int port, + string url = "metrics/", + CollectorRegistry? registry = null, + Func? beforeCollect = null) { _sawmill = sawmill; + _beforeCollect = beforeCollect; _listener = new HttpListener(); _listener.Prefixes.Add($"http://{host}:{port}/{url}"); _registry = registry ?? Metrics.DefaultRegistry; @@ -57,6 +64,12 @@ private async Task ListenerThread(CancellationToken cancel) { MetricsEvents.Log.ScrapeStart(); + // prometheus-net does have a "before collect" callback of its own. + // But it doesn't get ran before stuff like their System.Diagnostics.Metrics integration, + // So I'm just gonna make my own here. + if (_beforeCollect != null) + await _beforeCollect(cancel); + var stream = resp.OutputStream; // prometheus-net is a terrible library and have to do all this insanity, // just to handle the ScrapeFailedException correctly. diff --git a/Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs b/Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs new file mode 100644 index 00000000000..d91ffa1b895 --- /dev/null +++ b/Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Robust.Shared; +using Robust.Shared.Asynchronous; +using Robust.Shared.IoC; +using Robust.Shared.Timing; + +namespace Robust.Server.DataMetrics; + +internal sealed partial class MetricsManager +{ + // + // Handles the implementation of the "UpdateMetrics" callback. + // + + public event Action? UpdateMetrics; + + private TimeSpan _fixedUpdateInterval; + private TimeSpan _nextFixedUpdate; + + [Dependency] private readonly IGameTiming _gameTiming = default!; + + private void InitializeUpdateMetrics() + { + _cfg.OnValueChanged( + CVars.MetricsUpdateInterval, + seconds => + { + _fixedUpdateInterval = TimeSpan.FromSeconds(seconds); + _nextFixedUpdate = _gameTiming.RealTime + _fixedUpdateInterval; + }, + true); + } + + public void FrameUpdate() + { + if (_fixedUpdateInterval == TimeSpan.Zero) + return; + + var time = _gameTiming.RealTime; + + if (_nextFixedUpdate > time) + return; + + _nextFixedUpdate = time + _fixedUpdateInterval; + + _sawmill.Verbose("Running fixed metrics update"); + UpdateMetrics?.Invoke(); + } + + private async Task BeforeCollectCallback(CancellationToken cancel) + { + if (UpdateMetrics == null) + return; + + await _taskManager.TaskOnMainThread(() => + { + UpdateMetrics?.Invoke(); + }); + } +} diff --git a/Robust.Server/DataMetrics/MetricsManager.cs b/Robust.Server/DataMetrics/MetricsManager.cs index 29c0d29d103..2dbbb661bae 100644 --- a/Robust.Server/DataMetrics/MetricsManager.cs +++ b/Robust.Server/DataMetrics/MetricsManager.cs @@ -3,24 +3,50 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Prometheus; using Prometheus.DotNetRuntime; using Prometheus.DotNetRuntime.Metrics.Producers; using Robust.Shared; +using Robust.Shared.Asynchronous; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; using EventSource = System.Diagnostics.Tracing.EventSource; -#nullable enable - namespace Robust.Server.DataMetrics; -internal sealed partial class MetricsManager : IMetricsManager, IDisposable +/// +/// Manages OpenTelemetry metrics exposure. +/// +/// +/// +/// If enabled via , metrics about the game server are exposed via a HTTP server +/// in an OpenTelemetry-compatible format (Prometheus). +/// +/// +/// Metrics can be added through the types in System.Diagnostics.Metrics or Prometheus. +/// +/// +public interface IMetricsManager +{ + /// + /// An event that gets raised on the main thread when complex metrics should be updated. + /// + /// + /// This event is raised on the main thread before a Prometheus collection happens, + /// and also with a fixed interval if is set. + /// You can use it to update complex metrics that can't "just" be stuffed into a counter. + /// + event Action UpdateMetrics; +} + +internal sealed partial class MetricsManager : IMetricsManagerInternal, IDisposable { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly ITaskManager _taskManager = default!; private bool _initialized; @@ -55,6 +81,8 @@ void ValueChanged(CVarDef cVar) where T : notnull { _cfg.OnValueChanged(cVar, _ => Reload()); } + + InitializeUpdateMetrics(); } private async Task Stop() @@ -100,7 +128,12 @@ private async void Reload() _sawmill.Info("Prometheus metrics enabled, host: {1} port: {0}", port, host); var sawmill = Logger.GetSawmill("metrics.server"); - _metricServer = new ManagedHttpListenerMetricsServer(sawmill, host, port); + _metricServer = new ManagedHttpListenerMetricsServer( + sawmill, + host, + port, + registry: Metrics.DefaultRegistry, + beforeCollect: BeforeCollectCallback); _metricServer.Start(); if (_cfg.GetCVar(CVars.MetricsRuntime)) @@ -190,7 +223,8 @@ private sealed class MetricsEvents : EventSource } } -internal interface IMetricsManager +internal interface IMetricsManagerInternal : IMetricsManager { void Initialize(); + void FrameUpdate(); } diff --git a/Robust.Server/ServerIoC.cs b/Robust.Server/ServerIoC.cs index 8d9ca24fc86..8b0710573f0 100644 --- a/Robust.Server/ServerIoC.cs +++ b/Robust.Server/ServerIoC.cs @@ -80,6 +80,7 @@ internal static void RegisterIoC(IDependencyCollection deps) deps.Register(); deps.Register(); deps.Register(); + deps.Register(); deps.Register(); deps.Register(); deps.Register(); diff --git a/Robust.Shared/Asynchronous/TaskManager.cs b/Robust.Shared/Asynchronous/TaskManager.cs index 2662a1ace64..709b46df57c 100644 --- a/Robust.Shared/Asynchronous/TaskManager.cs +++ b/Robust.Shared/Asynchronous/TaskManager.cs @@ -74,4 +74,31 @@ public interface ITaskManager /// void BlockWaitOnTask(Task task); } + + internal static class TaskManagerExt + { + /// + /// Run a callback on the main thread, returning a task that represents its completion. + /// + /// + public static Task TaskOnMainThread(this ITaskManager taskManager, Action callback) + { + var tcs = new TaskCompletionSource(); + + taskManager.RunOnMainThread(() => + { + try + { + callback(); + tcs.SetResult(); + } + catch (Exception e) + { + tcs.TrySetException(e); + } + }); + + return tcs.Task; + } + } } diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 7f715bf9181..c06211b5721 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -416,6 +416,35 @@ protected CVars() public static readonly CVarDef MetricsPort = CVarDef.Create("metrics.port", 44880, CVar.SERVERONLY); + /// + /// Sets a fixed interval (seconds) for internal collection of certain metrics, + /// when not using the Prometheus metrics server. + /// + /// + /// + /// Most metrics are internally implemented directly via the prometheus-net library. + /// These metrics can only be scraped by the Prometheus metrics server (). + /// However, newer metrics are implemented with the System.Diagnostics.Metrics library in the .NET runtime. + /// These metrics can be scraped through more means, such as dotnet counters. + /// + /// + /// While many metrics are simple counters that can "just" be reported, + /// some metrics require more advanced internal work and need some code to be ran internally + /// before their values are made current. When collecting metrics via a + /// method other than the Prometheus metrics server, these metrics pose a problem, + /// as there is no way for the game to update them before collection properly. + /// + /// + /// This CVar acts as a fallback: if set to a value other than 0 (disabled), + /// these metrics will be internally updated at the interval provided. + /// + /// + /// This does not need to be enabled if metrics are collected exclusively via the Prometheus metrics server. + /// + /// + public static readonly CVarDef MetricsUpdateInterval = + CVarDef.Create("metrics.update_interval", 0f, CVar.SERVERONLY); + /// /// Enable detailed runtime metrics. Empty to disable. /// From dae4041e61303be86992cc0f61cd7a8475295184 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 20 Mar 2024 11:09:18 +0100 Subject: [PATCH 011/130] Make CheckBox texture vertically centered. This makes it look better in circumstances where the checkbox is vertically big, for example due to surrounding inline buttons. --- RELEASE-NOTES.md | 1 + Robust.Client/UserInterface/Controls/CheckBox.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 01e0f63290c..8df162bf773 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -49,6 +49,7 @@ END TEMPLATE--> ### Other * The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates). +* `CheckBox`'s interior texture is now vertically centered. ### Internal diff --git a/Robust.Client/UserInterface/Controls/CheckBox.cs b/Robust.Client/UserInterface/Controls/CheckBox.cs index d7b93b7e732..4f12ae0c0b9 100644 --- a/Robust.Client/UserInterface/Controls/CheckBox.cs +++ b/Robust.Client/UserInterface/Controls/CheckBox.cs @@ -29,6 +29,7 @@ public CheckBox() TextureRect = new TextureRect { StyleClasses = { StyleClassCheckBox }, + VerticalAlignment = VAlignment.Center, }; hBox.AddChild(TextureRect); From 6697e36e841777e0669f02128e4a28615943ea1d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 20 Mar 2024 11:58:11 +0100 Subject: [PATCH 012/130] Fix ResizableMemoryRegion metrics This was broken for more reason than one. So the obvious reason they weren't counting right is because I forgot to add a call to update the metric in Shrink(). Whoops. The second issue is that .NET's new metric types are unintuitive as shit and Microsoft would've done better to just link to the OpenTelemetry specification instead of whatever garbage they documented instead. It turns out UpDownCounter just wasn't the correct choice at all! Because it only ever gets DELTA VALUES sent to collection tools, it can never be used for absolute measurements (such as memory usage) **despite Microsoft literally using examples of absolute values in their docs** ("queue size"). The OpenTelemetry spec is clear on this. This means writing more code, because the API is shit. Great. --- .../Utility/ResizableMemoryRegion.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Robust.Shared/Utility/ResizableMemoryRegion.cs b/Robust.Shared/Utility/ResizableMemoryRegion.cs index fc3ac148250..acdcae53b73 100644 --- a/Robust.Shared/Utility/ResizableMemoryRegion.cs +++ b/Robust.Shared/Utility/ResizableMemoryRegion.cs @@ -3,6 +3,7 @@ using System.Diagnostics.Metrics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using Robust.Shared.Maths; using static TerraFX.Interop.Windows.Windows; using static TerraFX.Interop.Windows.MEM; @@ -41,12 +42,20 @@ internal static class ResizableMemoryRegionMetrics /// The type of elements stored in the memory region. internal sealed unsafe class ResizableMemoryRegion : IDisposable where T : unmanaged { - private static readonly UpDownCounter MemoryUsageGauge = - ResizableMemoryRegionMetrics.Meter.CreateUpDownCounter( + private static readonly KeyValuePair[] MetricTags = + [new KeyValuePair("type", typeof(T).FullName)]; + + // ReSharper disable once StaticMemberInGenericType + private static long _memoryUsed; + + static ResizableMemoryRegion() + { + ResizableMemoryRegionMetrics.Meter.CreateObservableUpDownCounter( ResizableMemoryRegionMetrics.GaugeName, + () => new Measurement(_memoryUsed, MetricTags), "bytes", - "The amount of committed memory used by ResizableMemoryRegion instances.", - new[] { new KeyValuePair("type", typeof(T).FullName) }); + "The amount of committed memory used by ResizableMemoryRegion instances."); + } /// /// The pointer to the start of the allocated memory region. Use with care! @@ -158,7 +167,7 @@ public void Expand(int newElementSize) CurrentSize = newElementSize; - MemoryUsageGauge.Add((newElementSize - previousSize) * sizeof(T)); + Interlocked.Add(ref _memoryUsed, (newElementSize - previousSize) * sizeof(T)); } /// @@ -188,7 +197,7 @@ public void Shrink(int newElementSize) var newByteSize = (nuint)sizeof(T) * (nuint)newElementSize; // If the new max size cuts a page in the middle we can't free it so round up to the next page. - var newPageSize = MathHelper.CeilMultipleOfPowerOfTwo(newByteSize, (nuint) Environment.SystemPageSize); + var newPageSize = MathHelper.CeilMultipleOfPowerOfTwo(newByteSize, (nuint)Environment.SystemPageSize); if (OperatingSystem.IsWindows()) { var freeBaseAddress = (byte*)BaseAddress + newPageSize; @@ -203,6 +212,8 @@ public void Shrink(int newElementSize) } CurrentSize = newElementSize; + + Interlocked.Add(ref _memoryUsed, (long)(newByteSize - currentByteSize)); } /// @@ -312,7 +323,7 @@ private void ReleaseUnmanagedResources() NativeMemory.Free(BaseAddress); } - MemoryUsageGauge.Add(-CurrentSize * sizeof(T)); + Interlocked.Add(ref _memoryUsed, -CurrentSize * sizeof(T)); BaseAddress = null; CurrentSize = 0; From e3954494e757dc4e8699291bc0ec3a96bfadcaa3 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Wed, 20 Mar 2024 10:48:19 -0400 Subject: [PATCH 013/130] Just two (#4982) --- Robust.Server/Console/Commands/SpinCommand.cs | 2 +- Robust.UnitTesting/Shared/Physics/Stack_Test.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Server/Console/Commands/SpinCommand.cs b/Robust.Server/Console/Commands/SpinCommand.cs index 9f48332a96a..f60369a4a67 100644 --- a/Robust.Server/Console/Commands/SpinCommand.cs +++ b/Robust.Server/Console/Commands/SpinCommand.cs @@ -66,7 +66,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) } var physicsSystem = _entities.System(); - physicsSystem.SetAngularDamping(physics, drag); + physicsSystem.SetAngularDamping(target.Value, physics, drag); physicsSystem.SetAngularVelocity(target.Value, speed, body: physics); } } diff --git a/Robust.UnitTesting/Shared/Physics/Stack_Test.cs b/Robust.UnitTesting/Shared/Physics/Stack_Test.cs index 713aef0ba53..fb843d50084 100644 --- a/Robust.UnitTesting/Shared/Physics/Stack_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Stack_Test.cs @@ -207,7 +207,7 @@ await server.WaitPost(() => var circle = entityManager.AddComponent(circleUid); var manager = entityManager.EnsureComponent(circleUid); - physSystem.SetLinearDamping(circle, 0.05f); + physSystem.SetLinearDamping(circleUid, circle, 0.05f); physSystem.SetBodyType(circleUid, BodyType.Dynamic, manager: manager, body: circle); shape = new PhysShapeCircle(0.5f); fixtureSystem.CreateFixture(circleUid, "fix1", new Fixture(shape, 1, 1, true), manager: manager, body: circle); From 9bfe889c86c16b2fda2cb5474c1ff270f796f3ed Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 21 Mar 2024 11:31:33 -0400 Subject: [PATCH 014/130] Code cleanup: Purge obsolete MapManager methods (#4981) * GetGrid * GridExists * TryGetGrid --- Robust.Client/Console/Commands/Debug.cs | 6 +-- .../Placement/Modes/AlignSnapgridBorder.cs | 3 +- .../Placement/Modes/AlignSnapgridCenter.cs | 2 +- Robust.Client/Placement/Modes/AlignTileAny.cs | 3 +- .../Placement/Modes/AlignTileDense.cs | 3 +- .../Placement/Modes/AlignTileEmpty.cs | 3 +- .../Placement/Modes/AlignTileNonDense.cs | 3 +- Robust.Client/Placement/PlacementManager.cs | 5 +- Robust.Client/Placement/PlacementMode.cs | 3 +- Robust.Server/Physics/GridFixtureSystem.cs | 4 +- .../ScriptGlobalsShared.cs | 4 +- Robust.Shared/Console/Commands/MapCommands.cs | 2 +- .../Systems/SharedTransformSystem.cs | 2 +- Robust.Shared/Map/CoordinatesExtensions.cs | 2 +- Robust.Shared/Map/EntityCoordinates.cs | 3 +- .../Map/MapManager.GridCollection.cs | 2 +- .../Systems/AnchoredSystemTests.cs | 48 +++++++------------ .../Shared/Map/MapManager_Tests.cs | 3 +- 18 files changed, 47 insertions(+), 54 deletions(-) diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs index 4bf7ceb6240..3a5e2df0aa9 100644 --- a/Robust.Client/Console/Commands/Debug.cs +++ b/Robust.Client/Console/Commands/Debug.cs @@ -294,7 +294,6 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) internal sealed class SnapGridGetCell : LocalizedCommands { [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IMapManager _map = default!; public override string Command => "sggcell"; @@ -320,7 +319,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid)) + if (_entManager.TryGetComponent(_entManager.GetEntity(gridNet), out var grid)) { foreach (var entity in grid.GetAnchoredEntities(new Vector2i( int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture), @@ -429,7 +428,6 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) internal sealed class GridTileCount : LocalizedCommands { [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IMapManager _map = default!; public override string Command => "gridtc"; @@ -448,7 +446,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (_map.TryGetGrid(gridUid, out var grid)) + if (_entManager.TryGetComponent(gridUid, out var grid)) { shell.WriteLine(grid.GetAllTiles().Count().ToString()); } diff --git a/Robust.Client/Placement/Modes/AlignSnapgridBorder.cs b/Robust.Client/Placement/Modes/AlignSnapgridBorder.cs index 1f84bc116b5..f3a4d3dff2d 100644 --- a/Robust.Client/Placement/Modes/AlignSnapgridBorder.cs +++ b/Robust.Client/Placement/Modes/AlignSnapgridBorder.cs @@ -2,6 +2,7 @@ using System.Numerics; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; namespace Robust.Client.Placement.Modes @@ -24,7 +25,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) SnapSize = 1f; if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - Grid = pManager.MapManager.GetGrid(gridId); + Grid = pManager.EntityManager.GetComponent(gridId); SnapSize = Grid.TileSize; //Find snap size for the grid. } else diff --git a/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs b/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs index eb12cddd234..7c3266b53d3 100644 --- a/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs +++ b/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs @@ -56,7 +56,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) SnapSize = 1f; if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - Grid = pManager.MapManager.GetGrid(gridId); + Grid = pManager.EntityManager.GetComponent(gridId); SnapSize = Grid.TileSize; //Find snap size for the grid. } else diff --git a/Robust.Client/Placement/Modes/AlignTileAny.cs b/Robust.Client/Placement/Modes/AlignTileAny.cs index b0774b65c6e..89e239f1398 100644 --- a/Robust.Client/Placement/Modes/AlignTileAny.cs +++ b/Robust.Client/Placement/Modes/AlignTileAny.cs @@ -1,5 +1,6 @@ using System.Numerics; using Robust.Shared.Map; +using Robust.Shared.Map.Components; namespace Robust.Client.Placement.Modes { @@ -19,7 +20,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) var gridId = MouseCoords.GetGridUid(pManager.EntityManager); - if (!pManager.MapManager.TryGetGrid(gridId, out var mapGrid)) + if (!pManager.EntityManager.TryGetComponent(gridId, out var mapGrid)) return; CurrentTile = mapGrid.GetTileRef(MouseCoords); diff --git a/Robust.Client/Placement/Modes/AlignTileDense.cs b/Robust.Client/Placement/Modes/AlignTileDense.cs index 86e2cf55c47..50af90644b8 100644 --- a/Robust.Client/Placement/Modes/AlignTileDense.cs +++ b/Robust.Client/Placement/Modes/AlignTileDense.cs @@ -1,6 +1,7 @@ using System.Numerics; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Map.Components; namespace Robust.Client.Placement.Modes { @@ -20,7 +21,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - var mapGrid = pManager.MapManager.GetGrid(gridId); + var mapGrid = pManager.EntityManager.GetComponent(gridId); tileSize = mapGrid.TileSize; //convert from ushort to float } diff --git a/Robust.Client/Placement/Modes/AlignTileEmpty.cs b/Robust.Client/Placement/Modes/AlignTileEmpty.cs index 06461dd7587..c73e805758c 100644 --- a/Robust.Client/Placement/Modes/AlignTileEmpty.cs +++ b/Robust.Client/Placement/Modes/AlignTileEmpty.cs @@ -2,6 +2,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; namespace Robust.Client.Placement.Modes @@ -22,7 +23,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - var mapGrid = pManager.MapManager.GetGrid(gridId); + var mapGrid = pManager.EntityManager.GetComponent(gridId); CurrentTile = mapGrid.GetTileRef(MouseCoords); tileSize = mapGrid.TileSize; //convert from ushort to float } diff --git a/Robust.Client/Placement/Modes/AlignTileNonDense.cs b/Robust.Client/Placement/Modes/AlignTileNonDense.cs index 07915221292..ef601ef74df 100644 --- a/Robust.Client/Placement/Modes/AlignTileNonDense.cs +++ b/Robust.Client/Placement/Modes/AlignTileNonDense.cs @@ -1,6 +1,7 @@ using System.Numerics; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Map.Components; namespace Robust.Client.Placement.Modes { @@ -20,7 +21,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager); if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - var mapGrid = pManager.MapManager.GetGrid(gridId); + var mapGrid = pManager.EntityManager.GetComponent(gridId); tileSize = mapGrid.TileSize; //convert from ushort to float } diff --git a/Robust.Client/Placement/PlacementManager.cs b/Robust.Client/Placement/PlacementManager.cs index a6bc7221e9a..34793ed65ef 100644 --- a/Robust.Client/Placement/PlacementManager.cs +++ b/Robust.Client/Placement/PlacementManager.cs @@ -21,6 +21,7 @@ using Robust.Shared.Utility; using Robust.Shared.Log; using Direction = Robust.Shared.Maths.Direction; +using Robust.Shared.Map.Components; namespace Robust.Client.Placement { @@ -332,7 +333,7 @@ private void HandlePlacementMessage(MsgPlacement msg) private void HandleTileChanged(ref TileChangedEvent args) { - var coords = MapManager.GetGrid(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices); + var coords = EntityManager.GetComponent(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices); _pendingTileChanges.RemoveAll(c => c.Item1 == coords); } @@ -753,7 +754,7 @@ private void RequestPlacement(EntityCoordinates coordinates) // If we have actually placed something on a valid grid... if (gridIdOpt is EntityUid gridId && gridId.IsValid()) { - var grid = MapManager.GetGrid(gridId); + var grid = EntityManager.GetComponent(gridId); // no point changing the tile to the same thing. if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType) diff --git a/Robust.Client/Placement/PlacementMode.cs b/Robust.Client/Placement/PlacementMode.cs index bb367b36d60..fd9a1048213 100644 --- a/Robust.Client/Placement/PlacementMode.cs +++ b/Robust.Client/Placement/PlacementMode.cs @@ -11,6 +11,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; @@ -193,7 +194,7 @@ public IEnumerable GridCoordinates() public TileRef GetTileRef(EntityCoordinates coordinates) { var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager); - return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.MapManager.GetGrid(gridUid).GetTileRef(MouseCoords) + return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent(gridUid).GetTileRef(MouseCoords) : new TileRef(gridUidOpt ?? EntityUid.Invalid, MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty); } diff --git a/Robust.Server/Physics/GridFixtureSystem.cs b/Robust.Server/Physics/GridFixtureSystem.cs index 5453599fa6b..28622aa8b68 100644 --- a/Robust.Server/Physics/GridFixtureSystem.cs +++ b/Robust.Server/Physics/GridFixtureSystem.cs @@ -236,7 +236,7 @@ private void CheckSplits(EntityUid uid, HashSet dirtyNodes) grids.Add(foundSplits); } - var oldGrid = _mapManager.GetGrid(uid); + var oldGrid = Comp(uid); var oldGridUid = uid; // Split time @@ -605,7 +605,7 @@ private HashSet GenerateSplitNode(EntityUid gridEuid, MapChunk c DebugTools.Assert(chunk.FilledTiles > 0); - var grid = _mapManager.GetGrid(gridEuid); + var grid = Comp(gridEuid); var group = CreateNodes(gridEuid, grid, chunk); _nodes[gridEuid][chunk.Indices] = group; diff --git a/Robust.Shared.Scripting/ScriptGlobalsShared.cs b/Robust.Shared.Scripting/ScriptGlobalsShared.cs index 63da98c273c..c494f0d2ab2 100644 --- a/Robust.Shared.Scripting/ScriptGlobalsShared.cs +++ b/Robust.Shared.Scripting/ScriptGlobalsShared.cs @@ -67,12 +67,12 @@ public EntityUid eid(int i) public MapGridComponent getgrid(int i) { - return map.GetGrid(new EntityUid(i)); + return ent.GetComponent(new EntityUid(i)); } public MapGridComponent getgrid(EntityUid mapId) { - return map.GetGrid(mapId); + return ent.GetComponent(mapId); } public T res() diff --git a/Robust.Shared/Console/Commands/MapCommands.cs b/Robust.Shared/Console/Commands/MapCommands.cs index b9dfe0be647..17a2f314325 100644 --- a/Robust.Shared/Console/Commands/MapCommands.cs +++ b/Robust.Shared/Console/Commands/MapCommands.cs @@ -84,7 +84,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) var gridIdNet = NetEntity.Parse(args[0]); - if (!_entManager.TryGetEntity(gridIdNet, out var gridId) || !_map.GridExists(gridId)) + if (!_entManager.TryGetEntity(gridIdNet, out var gridId) || !_entManager.HasComponent(gridId)) { shell.WriteError($"Grid {gridId} does not exist."); return; diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index 6e74558ed78..aedb98f747e 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -80,7 +80,7 @@ private void MapManagerOnTileChanged(ref TileChangedEvent e) /// private void DeparentAllEntsOnTile(EntityUid gridId, Vector2i tileIndices) { - if (!TryComp(gridId, out BroadphaseComponent? lookup) || !_mapManager.TryGetGrid(gridId, out var grid)) + if (!TryComp(gridId, out BroadphaseComponent? lookup) || !TryComp(gridId, out var grid)) return; if (!XformQuery.TryGetComponent(gridId, out var gridXform)) diff --git a/Robust.Shared/Map/CoordinatesExtensions.cs b/Robust.Shared/Map/CoordinatesExtensions.cs index 05dc629e503..12b6503cd3b 100644 --- a/Robust.Shared/Map/CoordinatesExtensions.cs +++ b/Robust.Shared/Map/CoordinatesExtensions.cs @@ -15,7 +15,7 @@ public static EntityCoordinates AlignWithClosestGridTile(this EntityCoordinates var gridId = coords.GetGridUid(entityManager); var mapSystem = entityManager.System(); - if (mapManager.TryGetGrid(gridId, out var mapGrid)) + if (entityManager.TryGetComponent(gridId, out var mapGrid)) { return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords)); } diff --git a/Robust.Shared/Map/EntityCoordinates.cs b/Robust.Shared/Map/EntityCoordinates.cs index e4d13fd4fea..f45b8f08a21 100644 --- a/Robust.Shared/Map/EntityCoordinates.cs +++ b/Robust.Shared/Map/EntityCoordinates.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -200,7 +201,7 @@ public Vector2i ToVector2i( var gridIdOpt = GetGridUid(entityManager); if (gridIdOpt is { } gridId && gridId.IsValid()) { - var grid = mapManager.GetGrid(gridId); + var grid = entityManager.GetComponent(gridId); return mapSystem.GetTileRef(gridId, grid, this).GridIndices; } diff --git a/Robust.Shared/Map/MapManager.GridCollection.cs b/Robust.Shared/Map/MapManager.GridCollection.cs index 4d142a84b65..ac49ec69167 100644 --- a/Robust.Shared/Map/MapManager.GridCollection.cs +++ b/Robust.Shared/Map/MapManager.GridCollection.cs @@ -113,7 +113,7 @@ public virtual void DeleteGrid(EntityUid euid) #endif // Possible the grid was already deleted / is invalid - if (!TryGetGrid(euid, out var iGrid)) + if (!EntityManager.TryGetComponent(euid, out var iGrid)) { DebugTools.Assert($"Calling {nameof(DeleteGrid)} with unknown uid {euid}."); return; // Silently fail on release diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs index b6753ac59fa..c34c679b2a2 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs @@ -5,6 +5,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -71,12 +72,11 @@ public void OnAnchored_WorldPosition_TileCenter() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after @@ -196,12 +196,11 @@ public void OnAnchored_Parent_SetToGrid() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var traversal = entMan.System(); @@ -224,9 +223,8 @@ public void OnAnchored_EmptyTile_Nop() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, Tile.Empty); @@ -247,9 +245,8 @@ public void OnAnchored_NonEmptyTile_Anchors() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -274,14 +271,12 @@ public void Anchored_SetPosition_Nop() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var xform = entMan.System(); // coordinates are already tile centered to prevent snapping and MoveEvent var coordinates = new MapCoordinates(new Vector2(7.5f, 7.5f), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after @@ -308,7 +303,7 @@ public void Anchored_ChangeParent_Unanchors() var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, coordinates); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); @@ -333,9 +328,8 @@ public void Anchored_SetParentSame_Nop() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -356,9 +350,8 @@ public void Anchored_TileToSpace_Unanchors() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -385,9 +378,8 @@ public void Anchored_AddToContainer_Unanchors() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -414,9 +406,8 @@ public void Anchored_AddPhysComp_IsStaticBody() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -437,13 +428,12 @@ public void OnAnchored_HasPhysicsComp_IsStaticBody() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); var physSystem = sim.Resolve().GetEntitySystem(); var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var ent1 = entMan.SpawnEntity(null, coordinates); @@ -464,9 +454,8 @@ public void OnUnanchored_HasPhysicsComp_IsDynamicBody() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -487,9 +476,8 @@ public void SpawnAnchored_EmptyTile_Unanchors() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); // Act var ent1 = entMan.SpawnEntity("anchoredEnt", new MapCoordinates(new Vector2(7, 7), TestMapId)); @@ -508,9 +496,8 @@ public void OnAnchored_InContainer_Nop() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -542,7 +529,7 @@ public void Unanchored_Unanchor_Nop() var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var traversal = entMan.System(); @@ -565,12 +552,11 @@ public void Anchored_Unanchored_ParentUnchanged() { var (sim, gridId) = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); // can only be anchored to a tile - var grid = mapMan.GetGrid(gridId); + var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); var ent1 = entMan.SpawnEntity("anchoredEnt", grid.MapToGrid(coordinates)); diff --git a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs index 1457e6b59de..16ba206069f 100644 --- a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs @@ -44,6 +44,7 @@ public void Restart_ExistingGrid_IsRemoved() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); + var entMan = sim.Resolve(); var mapID = new MapId(11); mapMan.CreateMap(mapID); @@ -51,7 +52,7 @@ public void Restart_ExistingGrid_IsRemoved() mapMan.Restart(); - Assert.That(mapMan.GridExists(grid), Is.False); + Assert.That(entMan.HasComponent(grid), Is.False); } /// From f5ade69f6de41859d041cd01e6283c5427847be1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 22 Mar 2024 00:18:38 +0100 Subject: [PATCH 015/130] Show MTU in network debug panel. --- .../CustomControls/DebugMonitorControls/DebugNetPanel.cs | 2 +- Robust.Shared/Network/INetChannel.cs | 8 +++++++- Robust.Shared/Network/NetManager.NetChannel.cs | 2 ++ Robust.UnitTesting/RobustIntegrationTest.NetManager.cs | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugNetPanel.cs b/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugNetPanel.cs index ce06806b46d..70a6de84e23 100644 --- a/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugNetPanel.cs +++ b/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugNetPanel.cs @@ -90,7 +90,7 @@ protected override void FrameUpdate(FrameEventArgs args) contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer, $@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt -PING: {NetManager.ServerChannel?.Ping ?? -1} ms"); +PING: {NetManager.ServerChannel?.Ping ?? -1} ms, MTU: {NetManager.ServerChannel?.CurrentMtu} B"); } } } diff --git a/Robust.Shared/Network/INetChannel.cs b/Robust.Shared/Network/INetChannel.cs index 802d1bdd6e3..90fd25044d2 100644 --- a/Robust.Shared/Network/INetChannel.cs +++ b/Robust.Shared/Network/INetChannel.cs @@ -67,6 +67,13 @@ public interface INetChannel /// bool IsHandshakeComplete { get; } + /// + /// Diagnostic indicating the maximum transmission unit being used for this connection. + /// + /// + [ViewVariables] + public int CurrentMtu { get; } + /// /// Creates a new NetMessage to be filled up and sent. /// @@ -94,6 +101,5 @@ T CreateNetMessage() /// Reason why it was disconnected. /// If false, we ghost the remote client and don't tell them they got disconnected properly. void Disconnect(string reason, bool sendBye); - } } diff --git a/Robust.Shared/Network/NetManager.NetChannel.cs b/Robust.Shared/Network/NetManager.NetChannel.cs index 7bc9074bd42..2bf2cd0b122 100644 --- a/Robust.Shared/Network/NetManager.NetChannel.cs +++ b/Robust.Shared/Network/NetManager.NetChannel.cs @@ -51,6 +51,8 @@ private sealed class NetChannel : INetChannel // Only used on server, contains the encryption to use for this channel. public NetEncryption? Encryption { get; set; } + [ViewVariables] public int CurrentMtu => _connection.CurrentMTU; + /// /// Creates a new instance of a NetChannel. /// diff --git a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs index 04f822384a6..29a5d333dd3 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs @@ -440,6 +440,7 @@ private sealed class IntegrationNetChannel : INetChannel public NetUserData UserData { get; } // integration tests don't simulate serializer handshake so this is always true. public bool IsHandshakeComplete => true; + public int CurrentMtu => 1000; // Arbitrary. // TODO: Should this port value make sense? public IPEndPoint RemoteEndPoint { get; } = new(IPAddress.Loopback, 1212); From f76092952740a17027a90956fdac0c92a42734f0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 22 Mar 2024 22:19:13 +0100 Subject: [PATCH 016/130] Add IMeterFactory implementation to IoC This will be useful as we start using more System.Diagnostics.Metrics. --- RELEASE-NOTES.md | 1 + .../DataMetrics/MetricsManager.Factory.cs | 89 +++++++++++++++++++ Robust.Server/DataMetrics/MetricsManager.cs | 4 + Robust.Server/ServerIoC.cs | 2 + 4 files changed, 96 insertions(+) create mode 100644 Robust.Server/DataMetrics/MetricsManager.Factory.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8df162bf773..0784023d995 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped. * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. +* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters. ### Bugfixes diff --git a/Robust.Server/DataMetrics/MetricsManager.Factory.cs b/Robust.Server/DataMetrics/MetricsManager.Factory.cs new file mode 100644 index 00000000000..f9543db929f --- /dev/null +++ b/Robust.Server/DataMetrics/MetricsManager.Factory.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using Robust.Shared.Utility; + +namespace Robust.Server.DataMetrics; + +internal sealed partial class MetricsManager : IMeterFactory +{ + private readonly Dictionary> _meterCache = new(); + private readonly object _meterCacheLock = new(); + + Meter IMeterFactory.Create(MeterOptions options) + { + if (options.Scope != null && options.Scope != this) + throw new InvalidOperationException("Cannot specify a custom scope when creating a meter"); + + lock (_meterCacheLock) + { + if (LockedFindCachedMeter(options) is { } cached) + return cached.Meter; + + var meter = new Meter(options.Name, options.Version, options.Tags, this); + var meterList = _meterCache.GetOrNew(options.Name); + meterList.Add(new CachedMeter(options.Version, TagsToDict(options.Tags), meter)); + return meter; + } + } + + private CachedMeter? LockedFindCachedMeter(MeterOptions options) + { + if (!_meterCache.TryGetValue(options.Name, out var metersList)) + return null; + + var tagsDict = TagsToDict(options.Tags); + + foreach (var cachedMeter in metersList) + { + if (cachedMeter.Version == options.Version && TagsMatch(tagsDict, cachedMeter.Tags)) + return cachedMeter; + } + + return null; + } + + private static bool TagsMatch(Dictionary a, Dictionary b) + { + if (a.Count != b.Count) + return false; + + foreach (var (key, valueA) in a) + { + if (!b.TryGetValue(key, out var valueB)) + return false; + + if (!Equals(valueA, valueB)) + return false; + } + + return true; + } + + private static Dictionary TagsToDict(IEnumerable>? tags) + { + return tags?.ToDictionary() ?? []; + } + + private void DisposeMeters() + { + lock (_meterCacheLock) + { + foreach (var meters in _meterCache.Values) + { + foreach (var meter in meters) + { + meter.Meter.Dispose(); + } + } + } + } + + private sealed class CachedMeter(string? version, Dictionary tags, Meter meter) + { + public readonly string? Version = version; + public readonly Dictionary Tags = tags; + public readonly Meter Meter = meter; + } +} diff --git a/Robust.Server/DataMetrics/MetricsManager.cs b/Robust.Server/DataMetrics/MetricsManager.cs index 2dbbb661bae..804ea02fb61 100644 --- a/Robust.Server/DataMetrics/MetricsManager.cs +++ b/Robust.Server/DataMetrics/MetricsManager.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; using System.Globalization; using System.Linq; @@ -26,6 +27,7 @@ namespace Robust.Server.DataMetrics; /// /// /// Metrics can be added through the types in System.Diagnostics.Metrics or Prometheus. +/// IoC contains an implementation of that can be used to instantiate meters. /// /// public interface IMetricsManager @@ -101,6 +103,8 @@ private async Task Stop() async void IDisposable.Dispose() { + DisposeMeters(); + await Stop(); _initialized = false; diff --git a/Robust.Server/ServerIoC.cs b/Robust.Server/ServerIoC.cs index 8b0710573f0..016e8271ee1 100644 --- a/Robust.Server/ServerIoC.cs +++ b/Robust.Server/ServerIoC.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Metrics; using Robust.Server.Configuration; using Robust.Server.Console; using Robust.Server.DataMetrics; @@ -81,6 +82,7 @@ internal static void RegisterIoC(IDependencyCollection deps) deps.Register(); deps.Register(); deps.Register(); + deps.Register(); deps.Register(); deps.Register(); deps.Register(); From 1f2b38a6d1776b9b9430f1d9a7357cdde6fe31ea Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 22 Mar 2024 22:07:33 -0400 Subject: [PATCH 017/130] Code cleanup: Purge calls to obsolete EntityCoordinates methods (#4983) --- .../GameObjects/EntitySystems/InputSystem.cs | 3 ++- Robust.Client/Placement/Modes/AlignSimilar.cs | 2 +- Robust.Client/Placement/PlacementMode.cs | 20 ++++++++++++------- Robust.Shared/Map/CoordinatesExtensions.cs | 3 ++- Robust.Shared/Map/EntityCoordinates.cs | 4 ++-- Robust.Shared/Player/Filter.cs | 2 +- .../Shared/Map/EntityCoordinates_Tests.cs | 6 ++++-- 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs index 1d4e87b5131..f73ad60aa49 100644 --- a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs @@ -26,6 +26,7 @@ public sealed class InputSystem : SharedInputSystem, IPostInjectInit [Dependency] private readonly IConsoleHost _conHost = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; private ISawmill _sawmillInputContext = default!; @@ -151,7 +152,7 @@ private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] a var pxform = Transform(pent); var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3])); - var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID)); + var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager); var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction); diff --git a/Robust.Client/Placement/Modes/AlignSimilar.cs b/Robust.Client/Placement/Modes/AlignSimilar.cs index ae5bb28ab98..94a1eb191f8 100644 --- a/Robust.Client/Placement/Modes/AlignSimilar.cs +++ b/Robust.Client/Placement/Modes/AlignSimilar.cs @@ -34,7 +34,7 @@ public override void AlignPlacementMode(ScreenCoordinates mouseScreen) var snapToEntities = EntitySystem.Get().GetEntitiesInRange(MouseCoords, SnapToRange) .Where(entity => pManager.EntityManager.GetComponent(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent(entity).MapID == mapId) - .OrderBy(entity => (pManager.EntityManager.GetComponent(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared()) + .OrderBy(entity => (pManager.EntityManager.GetComponent(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System())).LengthSquared()) .ToList(); if (snapToEntities.Count == 0) diff --git a/Robust.Client/Placement/PlacementMode.cs b/Robust.Client/Placement/PlacementMode.cs index fd9a1048213..5f8c9fd8684 100644 --- a/Robust.Client/Placement/PlacementMode.cs +++ b/Robust.Client/Placement/PlacementMode.cs @@ -116,11 +116,12 @@ public virtual void Render(DrawingHandleWorld handle) var dirAng = pManager.Direction.ToAngle(); var spriteSys = pManager.EntityManager.System(); + var transformSys = pManager.EntityManager.System(); foreach (var coordinate in locationcollection) { if (!coordinate.IsValid(pManager.EntityManager)) return; // Just some paranoia just in case - var worldPos = coordinate.ToMapPos(pManager.EntityManager); + var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys); var worldRot = pManager.EntityManager.GetComponent(coordinate.EntityId).WorldRotation + dirAng; sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor; @@ -137,11 +138,12 @@ public IEnumerable LineCoordinates() { var mouseScreen = pManager.InputManager.MouseScreenPosition; var mousePos = pManager.EyeManager.PixelToMap(mouseScreen); + var transformSys = pManager.EntityManager.System(); if (mousePos.MapId == MapId.Nullspace) yield break; - var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint; + var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint; float iterations; Vector2 distance; if (Math.Abs(x) > Math.Abs(y)) @@ -168,11 +170,12 @@ public IEnumerable GridCoordinates() { var mouseScreen = pManager.InputManager.MouseScreenPosition; var mousePos = pManager.EyeManager.PixelToMap(mouseScreen); + var transformSys = pManager.EntityManager.System(); if (mousePos.MapId == MapId.Nullspace) yield break; - var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint; + var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint; var xSign = Math.Sign(placementdiff.X); var ySign = Math.Sign(placementdiff.Y); @@ -196,7 +199,7 @@ public TileRef GetTileRef(EntityCoordinates coordinates) var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager); return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent(gridUid).GetTileRef(MouseCoords) : new TileRef(gridUidOpt ?? EntityUid.Invalid, - MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty); + MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System()), Tile.Empty); } public TextureResource GetSprite(string key) @@ -224,7 +227,8 @@ public bool RangeCheck(EntityCoordinates coordinates) } var range = pManager.CurrentPermission!.Range; - if (range > 0 && !pManager.EntityManager.GetComponent(controlled).Coordinates.InRange(pManager.EntityManager, coordinates, range)) + var transformSys = pManager.EntityManager.System(); + if (range > 0 && !pManager.EntityManager.GetComponent(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range)) return false; return true; } @@ -232,7 +236,8 @@ public bool RangeCheck(EntityCoordinates coordinates) public bool IsColliding(EntityCoordinates coordinates) { var bounds = pManager.ColliderAABB; - var mapCoords = coordinates.ToMap(pManager.EntityManager); + var transformSys = pManager.EntityManager.System(); + var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys); var (x, y) = mapCoords.Position; var collisionBox = Box2.FromDimensions( @@ -262,7 +267,8 @@ protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords) return EntityCoordinates.FromMap(pManager.MapManager, mapCoords); } - return EntityCoordinates.FromMap(pManager.EntityManager, gridUid, mapCoords); + var transformSys = pManager.EntityManager.System(); + return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager); } } } diff --git a/Robust.Shared/Map/CoordinatesExtensions.cs b/Robust.Shared/Map/CoordinatesExtensions.cs index 12b6503cd3b..6462f678e7a 100644 --- a/Robust.Shared/Map/CoordinatesExtensions.cs +++ b/Robust.Shared/Map/CoordinatesExtensions.cs @@ -20,7 +20,8 @@ public static EntityCoordinates AlignWithClosestGridTile(this EntityCoordinates return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords)); } - var mapCoords = coords.ToMap(entityManager); + var transformSystem = entityManager.System(); + var mapCoords = coords.ToMap(entityManager, transformSystem); if (mapManager.TryFindGridAt(mapCoords, out var gridUid, out mapGrid)) { diff --git a/Robust.Shared/Map/EntityCoordinates.cs b/Robust.Shared/Map/EntityCoordinates.cs index f45b8f08a21..41c60c39aa3 100644 --- a/Robust.Shared/Map/EntityCoordinates.cs +++ b/Robust.Shared/Map/EntityCoordinates.cs @@ -111,7 +111,7 @@ public MapCoordinates ToMap(IEntityManager entityManager, SharedTransformSystem [Obsolete("Use ToMapPos() with TransformSystem overload")] public Vector2 ToMapPos(IEntityManager entityManager) { - return ToMap(entityManager).Position; + return ToMap(entityManager, entityManager.System()).Position; } /// @@ -161,7 +161,7 @@ public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordin [Obsolete("Use overload with other parameter order.")] public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates) { - return FromMap(entityUid, coordinates, entityManager); + return FromMap(entityUid, coordinates, entityManager.System(), entityManager); } /// diff --git a/Robust.Shared/Player/Filter.cs b/Robust.Shared/Player/Filter.cs index bd3cbca0d0c..ce8d421b177 100644 --- a/Robust.Shared/Player/Filter.cs +++ b/Robust.Shared/Player/Filter.cs @@ -64,7 +64,7 @@ public Filter AddPlayersByPvs(TransformComponent origin, float rangeMultiplier = public Filter AddPlayersByPvs(EntityCoordinates origin, float rangeMultiplier = 2f, IEntityManager? entityMan = null, ISharedPlayerManager? playerMan = null) { IoCManager.Resolve(ref entityMan, ref playerMan); - return AddPlayersByPvs(origin.ToMap(entityMan), rangeMultiplier, entityMan, playerMan); + return AddPlayersByPvs(origin.ToMap(entityMan, entityMan.System()), rangeMultiplier, entityMan, playerMan); } /// diff --git a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs index aa661835cb4..d1613b1e477 100644 --- a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs @@ -254,16 +254,18 @@ public void ToMap_MoveGrid(float x1, float y1, float x2, float y2) var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); + var transformSystem = entityManager.System(); + var mapId = mapManager.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, entPos)); - Assert.That(IoCManager.Resolve().GetComponent(newEnt).Coordinates.ToMap(entityManager), Is.EqualTo(new MapCoordinates(entPos, mapId))); + Assert.That(IoCManager.Resolve().GetComponent(newEnt).Coordinates.ToMap(entityManager, transformSystem), Is.EqualTo(new MapCoordinates(entPos, mapId))); IoCManager.Resolve().GetComponent(gridEnt).LocalPosition += gridPos; - Assert.That(IoCManager.Resolve().GetComponent(newEnt).Coordinates.ToMap(entityManager), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId))); + Assert.That(IoCManager.Resolve().GetComponent(newEnt).Coordinates.ToMap(entityManager, transformSystem), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId))); } [Test] From 0a79382a629d7360db894ddf5c5388d82d2295cc Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Sat, 23 Mar 2024 16:27:54 +0200 Subject: [PATCH 018/130] Add helper command for Player toolshed commands (#4987) Allows you to invoke players:entity with a username to immediate get that player's attached entity(if any) --- Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs index fab35bdaf83..6ff6c1040a4 100644 --- a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs +++ b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs @@ -69,6 +69,12 @@ public EntityUid GetPlayerEntity([PipedArgument] ICommonSession sessions) { return sessions.AttachedEntity ?? default; } + + [CommandImplementation("entity")] + public EntityUid GetPlayerEntity([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] string username) + { + return GetPlayerEntity(Immediate(ctx, username)); + } } public record struct NoSuchPlayerError(string Username) : IConError From 0fb41e06c8fff9e69d0c1a051f24a32ea2c051d6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Mar 2024 16:21:34 +0100 Subject: [PATCH 019/130] Upgrade to Lidgren v0.3.0 --- Lidgren.Network/Lidgren.Network | 2 +- RELEASE-NOTES.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Lidgren.Network/Lidgren.Network b/Lidgren.Network/Lidgren.Network index 45f89ca2639..61a56c60bd9 160000 --- a/Lidgren.Network/Lidgren.Network +++ b/Lidgren.Network/Lidgren.Network @@ -1 +1 @@ -Subproject commit 45f89ca2639ec05d4700691a1160fccd79cb4c86 +Subproject commit 61a56c60bd99d000c25bd3f105b6de090076c98f diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0784023d995..ec7d7f64eb9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -51,6 +51,7 @@ END TEMPLATE--> * The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates). * `CheckBox`'s interior texture is now vertically centered. +* Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md). ### Internal From 3097784cd77df57e3b3a320eb547f2da120fe061 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sat, 23 Mar 2024 07:22:06 -0800 Subject: [PATCH 020/130] Lower default MTU to 900 (#4985) Some players continue to have "stuck at connected" issues connecting to most servers, but apparently this issue has become more prominent in the last two weeks or so? It is almost certainly an MTU problem because there are at least two servers on the hub that run a lower default MTU, and these players had no problem connecting to them. For one of these reporters, I actually increased the MTU to 1000 and they could no longer connect, and could connect again once it was lowered to 900. It's not clear what recent changes, either to the codebase or to the public Internet that have been exercising this MTU issue more. For those experiencing MTU issues, it seems that connecting to a less full server results in higher probability of success. Nevertheless, bringing down the default MTU and then possibly enabling MTU expansion in the future would make this game playable for a small but not insignificant bit of players. --- Robust.Shared/CVars.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index c06211b5721..8ed4fd188d0 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -69,7 +69,7 @@ protected CVars() /// /// public static readonly CVarDef NetMtu = - CVarDef.Create("net.mtu", 1000, CVar.ARCHIVE); + CVarDef.Create("net.mtu", 900, CVar.ARCHIVE); /// /// If set, automatically try to detect MTU above . From eedadb250fd42b1b43f19645186120c97b458649 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Mar 2024 16:25:25 +0100 Subject: [PATCH 021/130] Add net.mtu_ipv6 CVar. Wires up to the new MaximumTransmissionUnitV6 configuration in Lidgren. Default is that of Lidgren. --- RELEASE-NOTES.md | 1 + Robust.Shared/CVars.cs | 12 +++++++++++- Robust.Shared/Network/NetManager.cs | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ec7d7f64eb9..271ed862319 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -42,6 +42,7 @@ END TEMPLATE--> * Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped. * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. * IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters. +* `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6. ### Bugfixes diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 8ed4fd188d0..13fd50d44ce 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -65,16 +65,26 @@ protected CVars() CVarDef.Create("net.pool_size", 512, CVar.CLIENT | CVar.SERVER); /// - /// Maximum UDP payload size to send. + /// Maximum UDP payload size to send by default, for IPv4. /// /// + /// public static readonly CVarDef NetMtu = CVarDef.Create("net.mtu", 900, CVar.ARCHIVE); + /// + /// Maximum UDP payload size to send by default, for IPv6. + /// + /// + /// + public static readonly CVarDef NetMtuIpv6 = + CVarDef.Create("net.mtu_ipv6", NetPeerConfiguration.kDefaultMTUV6, CVar.ARCHIVE); + /// /// If set, automatically try to detect MTU above . /// /// + /// /// /// public static readonly CVarDef NetMtuExpand = diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs index 58c14da538d..e7b9a093e93 100644 --- a/Robust.Shared/Network/NetManager.cs +++ b/Robust.Shared/Network/NetManager.cs @@ -619,6 +619,7 @@ private NetPeerConfiguration _getBaseNetPeerConfig() // MTU stuff. netConfig.MaximumTransmissionUnit = _config.GetCVar(CVars.NetMtu); + netConfig.MaximumTransmissionUnitV6 = _config.GetCVar(CVars.NetMtuIpv6); netConfig.AutoExpandMTU = _config.GetCVar(CVars.NetMtuExpand); netConfig.ExpandMTUFrequency = _config.GetCVar(CVars.NetMtuExpandFrequency); netConfig.ExpandMTUFailAttempts = _config.GetCVar(CVars.NetMtuExpandFailAttempts); From e484eac29cc6c033142050666122c3ba5b50215a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Mar 2024 16:25:49 +0100 Subject: [PATCH 022/130] Changelog for 3097784cd77df57e3b3a320eb547f2da120fe061 --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 271ed862319..dc2172c7f04 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,6 +53,7 @@ END TEMPLATE--> * The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates). * `CheckBox`'s interior texture is now vertically centered. * Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md). +* Lowered default IPv4 MTU to 900 (from 1000). ### Internal From 919ec014772e563909cc61a43626e3a245b3c70e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Mar 2024 16:27:33 +0100 Subject: [PATCH 023/130] Enable MTU expansion by default. Due to yet another need to lower the MTU we should enable this by default. Improves network efficiency. --- RELEASE-NOTES.md | 1 + Robust.Shared/CVars.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index dc2172c7f04..b87f8bdd2ef 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,7 @@ END TEMPLATE--> * `CheckBox`'s interior texture is now vertically centered. * Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md). * Lowered default IPv4 MTU to 900 (from 1000). +* Automatic MTU expansion (`net.mtu_expand`) is now enabled by default. ### Internal diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 13fd50d44ce..ce6c37386f4 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -88,7 +88,7 @@ protected CVars() /// /// public static readonly CVarDef NetMtuExpand = - CVarDef.Create("net.mtu_expand", false, CVar.ARCHIVE); + CVarDef.Create("net.mtu_expand", true, CVar.ARCHIVE); /// /// Interval between MTU expansion attempts, in seconds. From b7ea4d0cca8ebd3d3968d6730b7d91dc32a0fb2b Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Mar 2024 16:38:19 +0100 Subject: [PATCH 024/130] Add release notes for #4987 --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b87f8bdd2ef..4a536b8dec4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,7 @@ END TEMPLATE--> * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. * IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters. * `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6. +* Allows `player:entity` to take a parameter representing the player name. ### Bugfixes From b9b565d53e0e8d09fccab2ed26f45f2716c5451e Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 23 Mar 2024 15:55:45 -0400 Subject: [PATCH 025/130] Fix uncaught overflow exception when parsing NetEntities from strings. (#4989) * Fixed uncaught overflow exception when parsing NetEntities from strings. * or --- Robust.Shared/GameObjects/NetEntity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/NetEntity.cs b/Robust.Shared/GameObjects/NetEntity.cs index 22a7be1630a..857fd0878fa 100644 --- a/Robust.Shared/GameObjects/NetEntity.cs +++ b/Robust.Shared/GameObjects/NetEntity.cs @@ -68,7 +68,7 @@ public static bool TryParse(ReadOnlySpan uid, out NetEntity entity) entity = Parse(uid); return true; } - catch (FormatException) + catch (Exception ex) when (ex is FormatException or OverflowException) { entity = Invalid; return false; From 033a617102c1dbd3d60e716030771c49f527ad76 Mon Sep 17 00:00:00 2001 From: Vasilis Date: Sat, 23 Mar 2024 22:23:39 +0100 Subject: [PATCH 026/130] Requirement for https://github.com/space-wizards/space-station-14/pull/25569 (#4992) --- Robust.Client/Robust.Client.csproj | 2 +- Robust.Server/Robust.Server.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Client/Robust.Client.csproj b/Robust.Client/Robust.Client.csproj index 40c335746bf..5ea5e896458 100644 --- a/Robust.Client/Robust.Client.csproj +++ b/Robust.Client/Robust.Client.csproj @@ -23,7 +23,7 @@ - + diff --git a/Robust.Server/Robust.Server.csproj b/Robust.Server/Robust.Server.csproj index 30fcd66ca8e..cea71c5afdf 100644 --- a/Robust.Server/Robust.Server.csproj +++ b/Robust.Server/Robust.Server.csproj @@ -29,7 +29,7 @@ - + From 6daa3ad2fcf858bf634c568fe640c82463907b7f Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 24 Mar 2024 13:24:11 +1100 Subject: [PATCH 027/130] Version: 215.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 32 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 4dcb01ee645..2e2ba704845 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 214.2.0 + 215.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4a536b8dec4..b57e211baa7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,15 +39,43 @@ END TEMPLATE--> ### New features +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 215.0.0 + +### Breaking changes + +* Update Lidgren to 0.3.0 + +### New features + * Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped. * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. * IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters. * `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6. * Allows `player:entity` to take a parameter representing the player name. +* Add collection parsing to the dev window for UI. +* Add a debug assert to Dirty(uid, comp) to catch mismatches being passed in. ### Bugfixes -*None yet* +* Support transform states with unknown parents. +* Fix serialization error logging. +* Fix naming of ResizableMemoryRegion metrics. +* Fix uncaught overflow exception when parsing NetEntities. ### Other @@ -59,7 +87,7 @@ END TEMPLATE--> ### Internal -*None yet* +* Cleanup some Dirty component calls internally. ## 214.2.0 From 5c1a5e9826b8f96158ce834e33bf69228da3945c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:00:21 +1100 Subject: [PATCH 028/130] Add 2 random methods (#4980) * Add 2 random methods One for valuelist shuffle and one for NextAngle range. * rn * Fix RN --- RELEASE-NOTES.md | 2 +- Robust.Shared/Random/RandomExtensions.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b57e211baa7..fd57ad6d432 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Add Random.NextAngle(min, max) method and Pick for `ValueList`. ### Bugfixes diff --git a/Robust.Shared/Random/RandomExtensions.cs b/Robust.Shared/Random/RandomExtensions.cs index 891105128ae..a78725470fb 100644 --- a/Robust.Shared/Random/RandomExtensions.cs +++ b/Robust.Shared/Random/RandomExtensions.cs @@ -32,6 +32,12 @@ public static ref T Pick(this IRobustRandom random, ValueList list) return ref list[index]; } + public static ref T Pick(this System.Random random, ValueList list) + { + var index = random.Next(list.Count); + return ref list[index]; + } + /// Picks a random element from a collection. /// /// This is O(n). @@ -108,6 +114,12 @@ public static double NextGaussian(this System.Random random, double μ = 0, doub public static Angle NextAngle(this System.Random random) => NextFloat(random) * MathF.Tau; + public static Angle NextAngle(this System.Random random, Angle minAngle, Angle maxAngle) + { + DebugTools.Assert(minAngle < maxAngle); + return minAngle + (maxAngle - minAngle) * random.NextDouble(); + } + public static float NextFloat(this IRobustRandom random) { // This is pretty much the CoreFX implementation. From 8d477716b09c146d8f795a873e2de15b18f4d36e Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:12:31 +1100 Subject: [PATCH 029/130] Add audio filepath completion helper (#4968) * Add audio filepath completion helper Due to how audio is packaged server doesn't have most audio files. * RN * weh --- RELEASE-NOTES.md | 1 + Robust.Shared/Collections/ValueList.cs | 8 +++ Robust.Shared/Console/CompletionHelper.cs | 48 ++++++++++++++++-- Robust.Shared/Console/CompletionResult.cs | 8 ++- Robust.Shared/Utility/ResPath.cs | 60 +++++++++++++++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index fd57ad6d432..8bf4e99448b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,7 @@ END TEMPLATE--> ### New features +* Add a CompletionHelper for audio filepaths that handles server packaging. * Add Random.NextAngle(min, max) method and Pick for `ValueList`. ### Bugfixes diff --git a/Robust.Shared/Collections/ValueList.cs b/Robust.Shared/Collections/ValueList.cs index 6892778ab6c..22b31f2f1f1 100644 --- a/Robust.Shared/Collections/ValueList.cs +++ b/Robust.Shared/Collections/ValueList.cs @@ -607,4 +607,12 @@ public void EnsureLength(int newCount) region.Clear(); Count = newCount; } + + public void AddRange(IEnumerable select) + { + foreach (var result in select) + { + Add(result); + } + } } diff --git a/Robust.Shared/Console/CompletionHelper.cs b/Robust.Shared/Console/CompletionHelper.cs index 440e66e6156..ff0b483993b 100644 --- a/Robust.Shared/Console/CompletionHelper.cs +++ b/Robust.Shared/Console/CompletionHelper.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Collections; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -23,7 +26,34 @@ public static class CompletionHelper public static IEnumerable Booleans => new[] { new CompletionOption(bool.FalseString), new CompletionOption(bool.TrueString) }; - public static IEnumerable ContentFilePath(string arg, IResourceManager res) + /// + /// Special-cased file handler for audio that accounts for serverside completion. + /// + public static IEnumerable AudioFilePath(string arg, IPrototypeManager protoManager, + IResourceManager res) + { + var resPath = GetUpdatedPath(arg); + var paths = new HashSet(); + + foreach (var path in res.ContentGetDirectoryEntries(resPath)) + { + paths.Add(path); + } + + foreach (var audioProto in protoManager.EnumeratePrototypes()) + { + var hero = new ResPath(audioProto.ID); + + if (!hero.TryRelativeTo(resPath, out _)) + continue; + + paths.Add(hero.GetNextSegment(resPath).ToString()); + } + + return GetPaths(resPath, paths, res); + } + + private static ResPath GetUpdatedPath(string arg) { var curPath = arg; if (!curPath.StartsWith("/")) @@ -31,12 +61,18 @@ public static IEnumerable ContentFilePath(string arg, IResourc var resPath = new ResPath(curPath); - if (!curPath.EndsWith("/")){ + if (!curPath.EndsWith("/")) + { resPath /= ".."; resPath = resPath.Clean(); } - var options = res.ContentGetDirectoryEntries(resPath) + return resPath; + } + + private static IEnumerable GetPaths(ResPath resPath, IEnumerable inputs, IResourceManager res) + { + var options = inputs .OrderBy(c => c) .Select(c => { @@ -51,6 +87,12 @@ public static IEnumerable ContentFilePath(string arg, IResourc return options; } + public static IEnumerable ContentFilePath(string arg, IResourceManager res) + { + var resPath = GetUpdatedPath(arg); + return GetPaths(resPath, res.ContentGetDirectoryEntries(resPath), res); + } + public static IEnumerable ContentDirPath(string arg, IResourceManager res) { var curPath = arg; diff --git a/Robust.Shared/Console/CompletionResult.cs b/Robust.Shared/Console/CompletionResult.cs index f3e2c37a13f..64eab32568d 100644 --- a/Robust.Shared/Console/CompletionResult.cs +++ b/Robust.Shared/Console/CompletionResult.cs @@ -38,7 +38,7 @@ private static CompletionOption[] ConvertOptions(IEnumerable stringOpts) /// /// Possible option to tab-complete in a . /// -public record struct CompletionOption(string Value, string? Hint = null, CompletionOptionFlags Flags = default) +public record struct CompletionOption(string Value, string? Hint = null, CompletionOptionFlags Flags = default) : IComparable { /// /// The value that will be filled in if completed. @@ -54,6 +54,12 @@ public record struct CompletionOption(string Value, string? Hint = null, Complet /// Flags that control how this completion is used. /// public CompletionOptionFlags Flags { get; set; } = Flags; + + public int CompareTo(CompletionOption other) + { + var valueComparison = string.Compare(Value, other.Value, StringComparison.CurrentCultureIgnoreCase); + return valueComparison; + } } /// diff --git a/Robust.Shared/Utility/ResPath.cs b/Robust.Shared/Utility/ResPath.cs index 3e347a88e75..5b6d3d8e0c8 100644 --- a/Robust.Shared/Utility/ResPath.cs +++ b/Robust.Shared/Utility/ResPath.cs @@ -569,6 +569,66 @@ public static ResPath Clean(this ResPath path) : new ResPath(sb.ToString()); } + /// + /// Gets the segments in common with 2 paths. + /// + public static ResPath GetCommonSegments(this ResPath path, ResPath other) + { + var segmentsA = path.EnumerateSegments(); + var segmentsB = other.EnumerateSegments(); + + var count = Math.Min(segmentsA.Length, segmentsB.Length); + var common = new ValueList(); + + for (var i = 0; i < count; i++) + { + if (segmentsA[i] == segmentsB[i]) + { + common.Add(segmentsA[i]); + continue; + } + + break; + } + + return new ResPath(string.Join(ResPath.Separator, common)); + } + + /// + /// Gets the next segment after where the common segments end. + /// + public static ResPath GetNextSegment(this ResPath path, ResPath other) + { + var segmentsA = path.EnumerateSegments(); + var segmentsB = other.EnumerateSegments(); + + var count = Math.Min(segmentsA.Length, segmentsB.Length); + var matched = 0; + var nextSegment = string.Empty; + + for (var i = 0; i < count; i++) + { + if (segmentsA[i] == segmentsB[i]) + { + nextSegment = segmentsA[i]; + matched++; + continue; + } + + break; + } + + if (matched < segmentsA.Length) + { + // Is this the easiest way to tell it's a file? + // Essentially once we know how far we matched we want the next segment along if it exists + // Also add in the trailing separator if it's a directory. + nextSegment = segmentsA[matched] + (matched != segmentsA.Length - 1 || path.Extension.Length == 0 ? ResPath.SeparatorStr : string.Empty); + } + + return new ResPath(nextSegment); + } + /// /// Enumerates segments skipping over first element in /// From df0945f3cdc35d2949e3839246c8dd61349349e7 Mon Sep 17 00:00:00 2001 From: "Wrexbe (Josh)" <81056464+wrexbe@users.noreply.github.com> Date: Sat, 23 Mar 2024 20:13:07 -0700 Subject: [PATCH 030/130] VV editor for EntProtoId? (#4986) Co-authored-by: wrexbe --- .../ClientViewVariablesManager.cs | 8 +++-- .../Editors/VVPropEditorNullableEntProtoId.cs | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 Robust.Client/ViewVariables/Editors/VVPropEditorNullableEntProtoId.cs diff --git a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs index f8045ec94d0..4db12ced18f 100644 --- a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs +++ b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs @@ -126,8 +126,12 @@ public VVPropEditor PropertyFor(Type? type) return new VVPropEditorString(); } - if (type == typeof(EntProtoId) || - type == typeof(EntProtoId?)) + if (type == typeof(EntProtoId?)) + { + return new VVPropEditorNullableEntProtoId(); + } + + if (type == typeof(EntProtoId)) { return new VVPropEditorEntProtoId(); } diff --git a/Robust.Client/ViewVariables/Editors/VVPropEditorNullableEntProtoId.cs b/Robust.Client/ViewVariables/Editors/VVPropEditorNullableEntProtoId.cs new file mode 100644 index 00000000000..d0137f1f8c5 --- /dev/null +++ b/Robust.Client/ViewVariables/Editors/VVPropEditorNullableEntProtoId.cs @@ -0,0 +1,35 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; + +namespace Robust.Client.ViewVariables.Editors; + +internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor +{ + protected override Control MakeUI(object? value) + { + var lineEdit = new LineEdit + { + Text = value is EntProtoId protoId ? protoId.Id : "", + Editable = !ReadOnly, + HorizontalExpand = true, + }; + + if (!ReadOnly) + { + lineEdit.OnTextEntered += e => + { + if (string.IsNullOrWhiteSpace(e.Text)) + { + ValueChanged(null); + } + else + { + ValueChanged((EntProtoId) e.Text); + } + }; + } + + return lineEdit; + } +} From b4c161833848b7f5469d1871aacd53cd6076750f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:18:49 +1100 Subject: [PATCH 031/130] Misc Toolshed tweaks (#4990) * Toolshed tweaks * oops * Apply suggestions from code review Co-authored-by: Moony * Re-add NotImplementedException * Move error message --------- Co-authored-by: Moony Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- RELEASE-NOTES.md | 3 + Resources/Locale/en-US/commands.ftl | 1 + Robust.Shared/Toolshed/Syntax/Expression.cs | 4 ++ .../Toolshed/ToolshedCommand.Entities.cs | 2 +- Robust.Shared/Toolshed/ToolshedCommand.cs | 38 ++++++----- .../Toolshed/ToolshedCommandImplementor.cs | 17 ++++- .../Toolshed/TypeParsers/EntityTypeParser.cs | 1 - .../TypeParsers/PrototypeTypeParser.cs | 2 + .../Toolshed/TypeParsers/SessionTypeParser.cs | 64 +++++++++++++++++++ .../Toolshed/TypeParsers/TypeParser.cs | 6 +- 10 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8bf4e99448b..c42ffad908b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,9 +41,12 @@ END TEMPLATE--> * Add a CompletionHelper for audio filepaths that handles server packaging. * Add Random.NextAngle(min, max) method and Pick for `ValueList`. +* Added an `ICommonSession` parser for toolshed commands. ### Bugfixes +* Fixed some issues where toolshed commands were generating completions for the wrong arguments + *None yet* ### Other diff --git a/Resources/Locale/en-US/commands.ftl b/Resources/Locale/en-US/commands.ftl index c997a65501e..caeab57bfff 100644 --- a/Resources/Locale/en-US/commands.ftl +++ b/Resources/Locale/en-US/commands.ftl @@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID. cmd-parse-failure-mapid = {$arg} is not a valid MapId. cmd-parse-failure-grid = {$arg} is not a valid grid. cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity. +cmd-parse-failure-session = There is no session with username: {$username} cmd-error-file-not-found = Could not find file: {$file}. cmd-error-dir-not-found = Could not find directory: {$dir}. diff --git a/Robust.Shared/Toolshed/Syntax/Expression.cs b/Robust.Shared/Toolshed/Syntax/Expression.cs index 3bbea42c0d3..d7c10149014 100644 --- a/Robust.Shared/Toolshed/Syntax/Expression.cs +++ b/Robust.Shared/Toolshed/Syntax/Expression.cs @@ -46,6 +46,10 @@ public static bool TryParse(bool doAutocomplete, if (parserContext.EatTerminator()) break; + + // Prevent auto completions from dumping a list of all commands at the end of any complete command. + if (parserContext.Index > parserContext.MaxIndex) + break; } if (error is OutOfInputError && noCommand) diff --git a/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs b/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs index 1daf8d19eec..7f6ed2d8dcc 100644 --- a/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs +++ b/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs @@ -111,7 +111,7 @@ protected bool HasComp(EntityUid entityUid) /// A shorthand for attempting to retrieve the given component for an entity. /// [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] - protected bool TryComp(EntityUid? entity, [NotNullWhen(true)] out T? component) + protected bool TryComp([NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out T? component) where T: IComponent => EntityManager.TryGetComponent(entity, out component); diff --git a/Robust.Shared/Toolshed/ToolshedCommand.cs b/Robust.Shared/Toolshed/ToolshedCommand.cs index 32b7250981a..8bec960e735 100644 --- a/Robust.Shared/Toolshed/ToolshedCommand.cs +++ b/Robust.Shared/Toolshed/ToolshedCommand.cs @@ -7,6 +7,7 @@ using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Reflection; using Robust.Shared.Toolshed.Errors; using Robust.Shared.Toolshed.Syntax; @@ -47,6 +48,7 @@ namespace Robust.Shared.Toolshed; public abstract partial class ToolshedCommand { [Dependency] protected readonly ToolshedManager Toolshed = default!; + [Dependency] protected readonly ILocalizationManager Loc = default!; /// /// The user-facing name of the command. @@ -99,7 +101,7 @@ protected ToolshedCommand() }; var impls = GetGenericImplementations(); - Dictionary> parameters = new(); + Dictionary<(string, Type?), SortedDictionary> parameters = new(); foreach (var impl in impls) { @@ -117,27 +119,26 @@ protected ToolshedCommand() }; } + Type? pipedType = null; foreach (var param in impl.GetParameters()) { if (param.GetCustomAttribute() is not null) - { - if (parameters.ContainsKey(param.Name!)) - continue; - - myParams.Add(param.Name!, param.ParameterType); - } - } + myParams.TryAdd(param.Name!, param.ParameterType); - if (parameters.TryGetValue(subCmd ?? "", out var existing)) - { - if (!existing.SequenceEqual(existing)) + if (param.GetCustomAttribute() is not null) { - throw new NotImplementedException("All command implementations of a given subcommand must share the same parameters!"); + if (pipedType != null) + throw new NotSupportedException($"Commands cannot have more than one piped argument"); + pipedType = param.ParameterType; } } - else - parameters.Add(subCmd ?? "", myParams); + var key = (subCmd ?? "", pipedType); + if (parameters.TryAdd(key, myParams)) + continue; + + if (!parameters[key].SequenceEqual(myParams)) + throw new NotImplementedException("All command implementations of a given subcommand with the same pipe type must share the same argument types"); } } @@ -184,14 +185,11 @@ internal sealed class CommandArgumentBundle public required Type[] TypeArguments; } -internal readonly record struct CommandDiscriminator(Type? PipedType, Type[] TypeArguments) : IEquatable +internal readonly record struct CommandDiscriminator(Type? PipedType, Type[] TypeArguments) { - public bool Equals(CommandDiscriminator? other) + public bool Equals(CommandDiscriminator other) { - if (other is not {} value) - return false; - - return value.PipedType == PipedType && value.TypeArguments.SequenceEqual(TypeArguments); + return other.PipedType == PipedType && other.TypeArguments.SequenceEqual(TypeArguments); } public override int GetHashCode() diff --git a/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs b/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs index 97314aba66f..6dd023ab1ce 100644 --- a/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs +++ b/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs @@ -115,6 +115,7 @@ public bool TryParseArguments( return false; } + autocomplete = null; args = new(); foreach (var argument in impl.ConsoleGetArguments()) { @@ -124,8 +125,9 @@ public bool TryParseArguments( { error?.Contextualize(parserContext.Input, (start, parserContext.Index)); args = null; - autocomplete = null; - if (doAutocomplete) + + // Only generate auto-completions if the parsing error happened for the last argument. + if (doAutocomplete && parserContext.Index > parserContext.MaxIndex) { parserContext.Restore(chkpoint); autocomplete = _toolshedManager.TryAutocomplete(parserContext, argument.ParameterType, null); @@ -133,10 +135,19 @@ public bool TryParseArguments( return false; } args[argument.Name!] = parsed; + + if (!doAutocomplete || parserContext.Index <= parserContext.MaxIndex) + continue; + + // This was the end of the input, so we want to get completions for the current argument, not the next argument. + doAutocomplete = false; + var chkpoint2 = parserContext.Save(); + parserContext.Restore(chkpoint); + autocomplete = _toolshedManager.TryAutocomplete(parserContext, argument.ParameterType, null); + parserContext.Restore(chkpoint2); } error = null; - autocomplete = null; return true; } diff --git a/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs index cad7a2f63c4..f9f7d38deef 100644 --- a/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Robust.Shared.Console; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Toolshed.Errors; using Robust.Shared.Toolshed.Syntax; diff --git a/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs index 16c0265f4e7..c8dcb62cdf7 100644 --- a/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs @@ -57,6 +57,8 @@ public override bool TryParse(ParserContext parserContext, [NotNullWhen(true)] o public readonly record struct Prototype(T Value) : IAsType where T : class, IPrototype { + public ProtoId Id => Value.ID; + public string AsType() { return Value.ID; diff --git a/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs new file mode 100644 index 00000000000..69524e841dd --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs @@ -0,0 +1,64 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Player; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +/// +/// Parse a username to an +/// +internal sealed class SessionTypeParser : TypeParser +{ + [Dependency] private ISharedPlayerManager _player = default!; + + public override bool TryParse(ParserContext parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var start = parser.Index; + var word = parser.GetWord(); + error = null; + result = null; + + if (word == null) + { + error = new OutOfInputError(); + return false; + } + + if (_player.TryGetSessionByUsername(word, out var session)) + { + result = session; + return true; + } + + error = new InvalidUsername(Loc, word); + error.Contextualize(parser.Input, (start, parser.Index)); + return false; + } + + public override async ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ParserContext parserContext, + string? argName) + { + var opts = CompletionHelper.SessionNames(true, _player); + return (CompletionResult.FromHintOptions(opts, ""), null); + } + + public record InvalidUsername(ILocalizationManager Loc, string Username) : IConError + { + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup(Loc.GetString("cmd-parse-failure-session", ("username", Username))); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs index 4f94473ee59..4d9078709d7 100644 --- a/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using Robust.Shared.Console; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Toolshed.Errors; using Robust.Shared.Toolshed.Syntax; @@ -26,8 +27,9 @@ public abstract class TypeParser : ITypeParser where T: notnull { [Dependency] private readonly ILogManager _log = default!; + [Dependency] protected readonly ILocalizationManager Loc = default!; - protected ISawmill _sawmill = default!; + protected ISawmill Log = default!; public virtual Type Parses => typeof(T); @@ -37,6 +39,6 @@ public abstract class TypeParser : ITypeParser public virtual void PostInject() { - _sawmill = _log.GetSawmill(GetType().PrettyName()); + Log = _log.GetSawmill(GetType().PrettyName()); } } From 71f0491f10df60782a5c427c039c9ad323efb0ea Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 24 Mar 2024 14:21:33 +1100 Subject: [PATCH 032/130] Version: 215.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 2e2ba704845..01fb9b10769 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 215.0.0 + 215.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c42ffad908b..bd589ba2092 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,14 +39,10 @@ END TEMPLATE--> ### New features -* Add a CompletionHelper for audio filepaths that handles server packaging. -* Add Random.NextAngle(min, max) method and Pick for `ValueList`. -* Added an `ICommonSession` parser for toolshed commands. +*None yet* ### Bugfixes -* Fixed some issues where toolshed commands were generating completions for the wrong arguments - *None yet* ### Other @@ -58,6 +54,17 @@ END TEMPLATE--> *None yet* +## 215.1.0 + +### New features + +* Add a CompletionHelper for audio filepaths that handles server packaging. +* Add Random.NextAngle(min, max) method and Pick for `ValueList`. +* Added an `ICommonSession` parser for toolshed commands. + +### Bugfixes + + ## 215.0.0 ### Breaking changes From a2d8fa7a9b074d144f31e59451403f3793b73074 Mon Sep 17 00:00:00 2001 From: KISS <59531932+YuriyKiss@users.noreply.github.com> Date: Sun, 24 Mar 2024 07:32:57 +0200 Subject: [PATCH 033/130] Making possible to QueueDeleteEntity on EndCollideEvent (#4883) * made possible to destroy entity on EndCollideEvent * figured queue delete issue * review --------- Co-authored-by: metalgearsloth --- RELEASE-NOTES.md | 2 +- .../Physics/Dynamics/Contacts/Contact.cs | 3 +++ .../Systems/SharedPhysicsSystem.Components.cs | 12 ++++++++++-- .../Systems/SharedPhysicsSystem.Contacts.cs | 16 +++++++++------- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bd589ba2092..e7cb195519e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fix QueueDel during EndCollideEvents from throwing while removing contacts. ### Other diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index 2e6279c133c..52ef03d0ef3 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -376,6 +376,9 @@ internal enum ContactFlags : byte /// Grid = 1 << 3, + /// + /// Set right before the contact is deleted + /// Deleting = 1 << 4, } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 487c06b231b..6b15cecf17e 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -186,17 +186,25 @@ public void DestroyContacts(PhysicsComponent body) { if (body.Contacts.Count == 0) return; + // This variable is only used in edge-case scenario when contact flagged Deleting raises + // EndCollideEvent which will QueueDelete contact's entity + ushort contactsFlaggedDeleting = 0; var node = body.Contacts.First; while (node != null) { var contact = node.Value; node = node.Next; + // Destroy last so the linked-list doesn't get touched. - DestroyContact(contact); + if (!DestroyContact(contact)) + { + contactsFlaggedDeleting++; + } } - DebugTools.Assert(body.Contacts.Count == 0); + // This contact will be deleted before SimulateWorld runs since it is already set to be Deleted + DebugTools.Assert(body.Contacts.Count == contactsFlaggedDeleting); } /// diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs index fbd944ec756..678b594bd22 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs @@ -310,11 +310,10 @@ internal static bool ShouldCollide(Fixture fixtureA, Fixture fixtureB) (fixtureB.CollisionMask & fixtureA.CollisionLayer) == 0x0); } - public void DestroyContact(Contact contact) + public bool DestroyContact(Contact contact) { - // Don't recursive update or we're in for a bad time. if ((contact.Flags & ContactFlags.Deleting) != 0x0) - return; + return false; Fixture fixtureA = contact.FixtureA!; Fixture fixtureB = contact.FixtureB!; @@ -326,7 +325,7 @@ public void DestroyContact(Contact contact) if (contact.IsTouching) { - var ev1 = new EndCollideEvent(aUid, bUid, contact.FixtureAId, contact.FixtureBId ,fixtureA, fixtureB, bodyA, bodyB); + var ev1 = new EndCollideEvent(aUid, bUid, contact.FixtureAId, contact.FixtureBId, fixtureA, fixtureB, bodyA, bodyB); var ev2 = new EndCollideEvent(bUid, aUid, contact.FixtureBId, contact.FixtureAId, fixtureB, fixtureA, bodyB, bodyA); RaiseLocalEvent(aUid, ref ev1); RaiseLocalEvent(bUid, ref ev2); @@ -335,10 +334,10 @@ public void DestroyContact(Contact contact) if (contact.Manifold.PointCount > 0 && contact.FixtureA?.Hard == true && contact.FixtureB?.Hard == true) { if (bodyA.CanCollide) - SetAwake(aUid, bodyA, true); + SetAwake((aUid, bodyA), true); if (bodyB.CanCollide) - SetAwake(bUid, bodyB, true); + SetAwake((bUid, bodyB), true); } // Remove from the world @@ -347,16 +346,19 @@ public void DestroyContact(Contact contact) // Remove from body 1 DebugTools.Assert(fixtureA.Contacts.ContainsKey(fixtureB)); fixtureA.Contacts.Remove(fixtureB); - DebugTools.Assert(bodyA.Contacts.Contains(contact.BodyANode!.Value)); + DebugTools.Assert(bodyA.Contacts.Contains(contact.BodyANode.Value)); bodyA.Contacts.Remove(contact.BodyANode); // Remove from body 2 DebugTools.Assert(fixtureB.Contacts.ContainsKey(fixtureA)); fixtureB.Contacts.Remove(fixtureA); + DebugTools.Assert(bodyB.Contacts.Contains(contact.BodyBNode.Value)); bodyB.Contacts.Remove(contact.BodyBNode); // Insert into the pool. _contactPool.Return(contact); + + return true; } internal void CollideContacts() From 4460454563c10b2aab597d53131d32448c07e4c5 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:59:40 +1100 Subject: [PATCH 034/130] Implement sound VV (#4966) * Implement sound VV No params yet because I'm lazy but paths and collections work. * Fixes * rn * review --- RELEASE-NOTES.md | 2 +- Resources/Locale/en-US/vv.ftl | 3 + Robust.Client/Audio/AudioSystem.cs | 39 ++++--- .../ClientViewVariablesManager.cs | 10 ++ .../Editors/VVPropEditorSoundSpecifier.cs | 110 ++++++++++++++++++ Robust.Server/Audio/AudioSystem.cs | 34 ++++-- .../ServerViewVariablesManager.cs | 2 +- .../ViewVariables/ViewVariablesTrait.cs | 4 + .../Audio/Systems/SharedAudioSystem.cs | 22 ++-- 9 files changed, 187 insertions(+), 39 deletions(-) create mode 100644 Resources/Locale/en-US/vv.ftl create mode 100644 Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e7cb195519e..15d2edb5f4d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Implement basic VV for SoundSpecifiers. ### Bugfixes diff --git a/Resources/Locale/en-US/vv.ftl b/Resources/Locale/en-US/vv.ftl new file mode 100644 index 00000000000..e69181b7c97 --- /dev/null +++ b/Resources/Locale/en-US/vv.ftl @@ -0,0 +1,3 @@ +vv-sound-none = None +vv-sound-path = Path +vv-sound-collection = Collection \ No newline at end of file diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index dccd639a9b3..d3d33cbe6f9 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -173,7 +173,7 @@ private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentSt private void SetupSource(Entity entity, AudioResource audioResource, TimeSpan? length = null) { var component = entity.Comp; - + if (TryAudioLimit(component.FileName)) { var newSource = _audio.CreateAudioSource(audioResource); @@ -427,13 +427,13 @@ private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResour return false; } - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates, + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null) { return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null) { return PlayEntity(filename, Filter.Local(), uid, true, audioParams); } @@ -460,8 +460,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// /// The resource path to the OGG Vorbis file to play. /// - private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true) { + if (string.IsNullOrEmpty(filename)) + return null; + if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage @@ -493,8 +496,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// /// The resource path to the OGG Vorbis file to play. /// The entity "emitting" the audio. - private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true) { + if (string.IsNullOrEmpty(filename)) + return null; + if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioEntityMessage @@ -534,8 +540,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun /// The resource path to the OGG Vorbis file to play. /// The coordinates at which to play the audio. /// - private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true) + private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true) { + if (string.IsNullOrEmpty(filename)) + return null; + if (recordReplay && _replayRecording.IsRecording) { _replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage @@ -569,25 +578,25 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { return PlayGlobal(filename, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) { return PlayEntity(filename, entity, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { return PlayStatic(filename, coordinates, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null) { return PlayGlobal(filename, audioParams); } @@ -603,31 +612,31 @@ public override void LoadStream(Entity entity, T stream) } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null) { return PlayGlobal(filename, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { return PlayEntity(filename, uid, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { return PlayEntity(filename, uid, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return PlayStatic(filename, coordinates, audioParams); } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return PlayStatic(filename, coordinates, audioParams); } diff --git a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs index 4db12ced18f..006042f4dd3 100644 --- a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs +++ b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs @@ -8,6 +8,8 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Client.ViewVariables.Editors; using Robust.Client.ViewVariables.Instances; +using Robust.Shared.Audio; +using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -29,6 +31,8 @@ internal sealed partial class ClientViewVariablesManager : ViewVariablesManager, [Dependency] private readonly IClientNetManager _netManager = default!; [Dependency] private readonly IRobustSerializer _robustSerializer = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly IResourceManager _resManager = default!; private uint _nextReqId = 1; private readonly Vector2i _defaultWindowSize = (640, 420); @@ -226,6 +230,12 @@ public VVPropEditor PropertyFor(Type? type) return new VVPropEditorTimeSpan(); } + if (typeof(SoundSpecifier).IsAssignableFrom(type)) + { + var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager); + return control; + } + if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { diff --git a/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs b/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs new file mode 100644 index 00000000000..b4b9f3acb20 --- /dev/null +++ b/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs @@ -0,0 +1,110 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Audio; +using Robust.Shared.ContentPack; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Robust.Client.ViewVariables.Editors; + +public sealed class VVPropEditorSoundSpecifier : VVPropEditor +{ + private readonly IPrototypeManager _protoManager; + private readonly IResourceManager _resManager; + + public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager) + { + _protoManager = protoManager; + _resManager = resManager; + } + + protected override Control MakeUI(object? value) + { + var typeButton = new OptionButton() + { + Disabled = ReadOnly, + }; + + typeButton.AddItem(Loc.GetString("vv-sound-none")); + typeButton.AddItem(Loc.GetString("vv-sound-collection"), 1); + typeButton.AddItem(Loc.GetString("vv-sound-path"), 2); + + var editBox = new LineEdit() + { + HorizontalExpand = true, + Editable = !ReadOnly, + }; + + var controls = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + typeButton, + editBox + }, + SetSize = new Vector2(384f, 32f) + }; + + if (value != null) + { + switch (value) + { + case SoundCollectionSpecifier collection: + typeButton.SelectId(1); + editBox.Text = collection.Collection ?? string.Empty; + break; + case SoundPathSpecifier path: + typeButton.SelectId(2); + editBox.Text = path.Path.ToString(); + break; + } + } + + typeButton.OnItemSelected += args => + { + typeButton.SelectId(args.Id); + editBox.Text = string.Empty; + + editBox.Editable = !ReadOnly && typeButton.SelectedId > 0; + + if (typeButton.SelectedId == 0) + { + // Dummy value + ValueChanged(new SoundPathSpecifier("")); + } + }; + + editBox.OnTextEntered += args => + { + if (string.IsNullOrEmpty(args.Text)) + return; + + switch (typeButton.SelectedId) + { + case 1: + if (!_protoManager.HasIndex(args.Text)) + return; + + ValueChanged(new SoundCollectionSpecifier(args.Text)); + break; + case 2: + var path = new ResPath(args.Text); + + if (!_resManager.ContentFileExists(path)) + return; + + ValueChanged(new SoundPathSpecifier(args.Text)); + break; + default: + return; + } + }; + + return controls; + } +} diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs index c7ecb695dac..f89bc0c675e 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -68,7 +68,7 @@ private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filt } /// - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { var entity = Spawn("Audio", MapCoordinates.Nullspace); var audio = SetupAudio(entity, filename, audioParams); @@ -78,8 +78,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string } /// - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) { + if (string.IsNullOrEmpty(filename)) + return null; + if (TerminatingOrDeleted(uid)) { Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}"); @@ -94,8 +97,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string } /// - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null) { + if (string.IsNullOrEmpty(filename)) + return null; + if (TerminatingOrDeleted(uid)) { Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}"); @@ -109,8 +115,11 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string fil } /// - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { + if (string.IsNullOrEmpty(filename)) + return null; + if (TerminatingOrDeleted(coordinates.EntityId)) { Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}"); @@ -128,9 +137,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string } /// - public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates, + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null) { + if (string.IsNullOrEmpty(filename)) + return null; + if (TerminatingOrDeleted(coordinates.EntityId)) { Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}"); @@ -176,12 +188,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun return audio; } - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null) { return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayGlobal(filename, actor.PlayerSession, audioParams); @@ -189,12 +201,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string return null; } - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayEntity(filename, actor.PlayerSession, uid, audioParams); @@ -202,12 +214,12 @@ public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string return null; } - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams); } - public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { if (TryComp(recipient, out ActorComponent? actor)) return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams); diff --git a/Robust.Server/ViewVariables/ServerViewVariablesManager.cs b/Robust.Server/ViewVariables/ServerViewVariablesManager.cs index 38406613e2a..610efe4c47d 100644 --- a/Robust.Server/ViewVariables/ServerViewVariablesManager.cs +++ b/Robust.Server/ViewVariables/ServerViewVariablesManager.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Robust.Server.Console; using Robust.Server.Player; +using Robust.Shared.Audio; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -296,7 +297,6 @@ private bool TryReinterpretValue(object? input, [NotNullWhen(true)] out object? output = prototype; return true; - default: return false; } diff --git a/Robust.Server/ViewVariables/ViewVariablesTrait.cs b/Robust.Server/ViewVariables/ViewVariablesTrait.cs index 15880adbf80..2c65f4bdf3e 100644 --- a/Robust.Server/ViewVariables/ViewVariablesTrait.cs +++ b/Robust.Server/ViewVariables/ViewVariablesTrait.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Robust.Server.ViewVariables.Traits; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network.Messages; @@ -96,6 +97,9 @@ public virtual bool TryModifyProperty(object[] property, object value) if (value is EntityUid uid) return IoCManager.Resolve().GetComponentOrNull(uid)?.NetEntity ?? NetEntity.Invalid; + if (value is SoundSpecifier) + return value; + var valType = value.GetType(); if (!valType.IsValueType) { diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index e01a6cd0bea..93c609488fa 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -234,7 +234,7 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -253,7 +253,7 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -274,7 +274,7 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null); /// /// Play an audio file globally, without position. @@ -294,7 +294,7 @@ public TimeSpan GetAudioLength(string filename) /// The set of players that will hear the sound. /// The UID of the entity "emitting" the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -303,7 +303,7 @@ public TimeSpan GetAudioLength(string filename) /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -312,7 +312,7 @@ public TimeSpan GetAudioLength(string filename) /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); /// /// Play an audio file following an entity. @@ -378,7 +378,7 @@ public TimeSpan GetAudioLength(string filename) /// The sound specifier that points the audio file(s) that should be played. /// The EntityCoordinates to attach the audio source to. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename, + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null); /// @@ -387,7 +387,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The resource path to the OGG Vorbis file to play. /// The UID of the entity "emitting" the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename, EntityUid uid, + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null); /// @@ -419,7 +419,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The set of players that will hear the sound. /// The coordinates at which to play the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); /// /// Play an audio file at a static position. @@ -428,7 +428,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The player that will hear the sound. /// The coordinates at which to play the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// /// Play an audio file at a static position. @@ -437,7 +437,7 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The player that will hear the sound. /// The coordinates at which to play the audio. [return: NotNullIfNotNull("filename")] - public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// /// Play an audio file at a static position. From 25bbb21dc868b4e0b43b6d28e899710891b35868 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 25 Mar 2024 00:57:09 +1100 Subject: [PATCH 035/130] Version: 215.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 01fb9b10769..05573ef86ba 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 215.1.0 + 215.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 15d2edb5f4d..4aa653365b2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,11 +39,11 @@ END TEMPLATE--> ### New features -* Implement basic VV for SoundSpecifiers. +*None yet* ### Bugfixes -* Fix QueueDel during EndCollideEvents from throwing while removing contacts. +*None yet* ### Other @@ -54,6 +54,17 @@ END TEMPLATE--> *None yet* +## 215.2.0 + +### New features + +* Implement basic VV for SoundSpecifiers. + +### Bugfixes + +* Fix QueueDel during EndCollideEvents from throwing while removing contacts. + + ## 215.1.0 ### New features From d933f03a54be53a23581e602f65fb27fbb5272b6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 25 Mar 2024 07:40:28 +1100 Subject: [PATCH 036/130] Add `TryComp` and `HasComp` to `EntityQuery` (#4996) --- .../GameObjects/EntityManager.Components.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 0d6357557aa..6a4906676df 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1452,6 +1452,24 @@ public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? compo return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool TryComp(EntityUid uid, [NotNullWhen(true)] out TComp1? component) + => TryGetComponent(uid, out component); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool TryComp(EntityUid? uid, [NotNullWhen(true)] out TComp1? component) + => TryGetComponent(uid, out component); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool HasComp(EntityUid uid) => HasComponent(uid); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool HasComp(EntityUid? uid) => HasComponent(uid); + [MethodImpl(MethodImplOptions.AggressiveInlining)] [Pure] public bool HasComponent(EntityUid uid) From 9a2a3d658df91ed7c8cb5766365b38fdf34e1dfb Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Mon, 25 Mar 2024 01:44:11 -0500 Subject: [PATCH 037/130] Provide option to stop PlaceManager from changing the player control scheme (#4977) --- Robust.Client/Placement/PlacementManager.cs | 4 ++ Robust.Shared/Enums/PlacementInformation.cs | 55 ++++++++++++++++----- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Robust.Client/Placement/PlacementManager.cs b/Robust.Client/Placement/PlacementManager.cs index 34793ed65ef..d28c90a1a05 100644 --- a/Robust.Client/Placement/PlacementManager.cs +++ b/Robust.Client/Placement/PlacementManager.cs @@ -80,6 +80,10 @@ public bool IsActive private set { _isActive = value; + + if (CurrentPermission?.UseEditorContext is false) + return; + SwitchEditorContext(value); } } diff --git a/Robust.Shared/Enums/PlacementInformation.cs b/Robust.Shared/Enums/PlacementInformation.cs index 1062d62f085..4e13aba1030 100644 --- a/Robust.Shared/Enums/PlacementInformation.cs +++ b/Robust.Shared/Enums/PlacementInformation.cs @@ -1,15 +1,46 @@ -using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects; -namespace Robust.Shared.Enums +namespace Robust.Shared.Enums; + +public sealed class PlacementInformation { - public sealed class PlacementInformation - { - public string? EntityType { get; set; } - public bool IsTile { get; set; } - public EntityUid MobUid { get; set; } - public string? PlacementOption { get; set; } - public int Range { get; set; } - public int TileType { get; set; } - public int Uses { get; set; } = 1; - } + /// + /// Entity prototype to be placed + /// + public string? EntityType { get; set; } + + /// + /// Indiciates if the entity prototype to be placed is in fact a tile + /// + public bool IsTile { get; set; } + + /// + /// ID of the mob that has permission to place the prototype + /// + public EntityUid MobUid { get; set; } + + /// + /// Specifies the placement alignment + /// + public string? PlacementOption { get; set; } + + /// + /// Determines the max range at which the entity prototype can be placed + /// + public int Range { get; set; } + + /// + /// + /// + public int TileType { get; set; } + + /// + /// Number of times the entity can be placed + /// + public int Uses { get; set; } = 1; + + /// + /// Sets whether the input context should switch to 'editor' mode + /// + public bool UseEditorContext { get; set; } = true; } From 2946cd866c5b93bc1f26e42ec316319fd715dbed Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 25 Mar 2024 11:08:43 -0400 Subject: [PATCH 038/130] Added length comparison helpers for Vector2 (#4999) --- Robust.Shared.Maths/Vector2Helpers.cs | 186 ++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/Robust.Shared.Maths/Vector2Helpers.cs b/Robust.Shared.Maths/Vector2Helpers.cs index 726e77c1a9b..3744c178b1d 100644 --- a/Robust.Shared.Maths/Vector2Helpers.cs +++ b/Robust.Shared.Maths/Vector2Helpers.cs @@ -59,6 +59,192 @@ public static float Normalize(ref this Vector2 vec) return length; } + /// + /// Compares the lengths of two vectors. + /// + /// + /// Avoids square root computations by using squared lengths. + /// + /// + /// a positive value if is longer than , + /// a negative value if is longer than , + /// or 0 if and have equal lengths. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float CompareLength(Vector2 a, Vector2 b) + { + return a.LengthSquared() - b.LengthSquared(); + } + + /// + /// Compares the length of a vector with a scalar. + /// + /// + /// Avoids a square root computation by using squared length. + /// + /// + /// a positive value if is longer than , + /// a negative value if is shorter than , + /// or 0 if has a length equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float CompareLength(Vector2 vec, float scalar) + { + return vec.LengthSquared() - (scalar * scalar); + } + + /// + /// Compares the length of this vector with a scalar. + /// + /// + /// Avoids a square root computation by using squared length. + /// + /// + /// a positive value if this vector is longer than , + /// a negative value if this vector is shorter than , + /// or 0 if this vector has a length equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float CompareLengthTo(this Vector2 vec, float scalar) + { + return CompareLength(vec, scalar); + } + + /// + /// Compares the length of this vector with another. + /// + /// + /// Avoids square root computations by using squared lengths. + /// + /// + /// a positive value if this vector is longer than , + /// a negative value if this vector is shorter than , + /// or 0 if this vector and have equal lengths. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float CompareLengthTo(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec); + } + + /// + /// Is the length of this vector greater than ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLongerThan(this Vector2 vec, float scalar) + { + return CompareLength(vec, scalar) > 0; + } + + /// + /// Is the length of this vector greater than the length of ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLongerThan(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec) > 0; + } + + /// + /// Is the length of this vector greater than or equal to ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLongerThanOrEqualTo(this Vector2 vec, float scalar) + { + return CompareLength(vec, scalar) >= 0; + } + + /// + /// Is the length of this vector greater than or equal to the length of ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLongerThanOrEqualTo(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec) >= 0; + } + + /// + /// Is the length of this vector less than ? + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsShorterThan(this Vector2 vec, float scalar) + { + return CompareLength(vec, scalar) < 0; + } + + /// + /// Is the length of this vector less than the length of ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsShorterThan(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec) < 0; + } + + /// + /// Is the length of this vector less than or equal to ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsShorterThanOrEqualTo(this Vector2 vec, float scalar) + { + return CompareLength(vec, scalar) <= 0; + } + + /// + /// Is the length of this vector less than or equal to the length of ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsShorterThanOrEqualTo(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec) <= 0; + } + + /// + /// Returns true if this vector's length is equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool LengthEquals(this Vector2 thisVec, float scalar) + { + return CompareLength(thisVec, scalar) == 0; + } + + /// + /// Is this vector the same length as ? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEqualLengthTo(this Vector2 thisVec, Vector2 otherVec) + { + return CompareLength(thisVec, otherVec) == 0; + } + + /// + /// Compares the length of a vector with a scalar. + /// + /// + /// Avoids a square root computation by using squared length. + /// + /// + /// a positive value if is shorter than , + /// a negative value if is longer than , + /// or 0 if has a length equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float CompareLength(float scalar, Vector2 vec) + { + return (scalar * scalar) - vec.LengthSquared(); + } + + /// + /// Is the length of this vector zero? + /// + public static bool IsLengthZero(this Vector2 vec) + { + return vec.LengthSquared() == 0; + } + /// /// Perform the cross product on a scalar and a vector. In 2D this produces /// a vector. From eee771c5f1ece9f529931b79eb18c19910c3f00a Mon Sep 17 00:00:00 2001 From: "Wrexbe (Josh)" <81056464+wrexbe@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:20:39 -0700 Subject: [PATCH 039/130] Zstd Update (#5002) --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index aead66c49ed..a6088ed5d5b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + @@ -44,7 +44,7 @@ - + @@ -61,4 +61,4 @@ - + \ No newline at end of file From 0bf99e173c6fad5e2ded4afe5ea25b88ca364907 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 01:11:30 +0100 Subject: [PATCH 040/130] Texture GetPixel() fixes Wow this API is bad. Well the API is just terrible for perf for multiple reasons but not fixing that... 1. Was using DSA GetTextureImage() instead of GetnTexImage() so only worked if DSA was available. 2. Was using stackalloc with a potentially huge amount of memory (original bug). 3. Had an off-by-one for the vertical coordinate. All fixed now. Fixes #5001 --- RELEASE-NOTES.md | 4 ++- .../Graphics/Clyde/Clyde.Textures.cs | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4aa653365b2..c9ba035c567 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,9 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* `Texture.GetPixel()`: fixed off-by-one with Y coordinate. +* `Texture.GetPixel()`: fix stack overflow when reading large images. +* `Texture.GetPixel()`: use more widely compatible OpenGL calls. ### Other diff --git a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs index 8a453a248ee..6b8180917cb 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs @@ -649,24 +649,30 @@ public override string ToString() return $"ClydeTexture: ({TextureId})"; } - public override Color GetPixel(int x, int y) + public override unsafe Color GetPixel(int x, int y) { if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded)) { throw new DataException("Texture not found"); } - Span rgba = stackalloc byte[4*this.Size.X*this.Size.Y]; - unsafe - { - fixed (byte* p = rgba) - { + var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D); + var bufSize = 4 * loaded.Size.X * loaded.Size.Y; + var buffer = ArrayPool.Shared.Rent(bufSize); + + GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle); - GL.GetTextureImage(loaded.OpenGLObject.Handle, 0, PF.Rgba, PT.UnsignedByte, 4*this.Size.X*this.Size.Y, (IntPtr) p); - } + fixed (byte* p = buffer) + { + GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p); } - int pixelPos = (this.Size.X*(this.Size.Y-y) + x)*4; - return new Color(rgba[pixelPos+0], rgba[pixelPos+1], rgba[pixelPos+2], rgba[pixelPos+3]); + + GL.BindTexture(TextureTarget.Texture2D, curTexture2D); + + var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4; + var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]); + ArrayPool.Shared.Return(buffer); + return color; } } From 00c58c76a870b2e6943cefa038d042f9cd91a8b1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 02:49:47 +0100 Subject: [PATCH 041/130] Disable MTU Expansion again Reports of problems, double checked with Grafana. Great. I am going to do manual test runs of this system against Miros again if I end up looking to improve the situation. --- RELEASE-NOTES.md | 2 +- Robust.Shared/CVars.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c9ba035c567..e68c7b3079f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -49,7 +49,7 @@ END TEMPLATE--> ### Other -*None yet* +* Disabled `net.mtu_expand` again by default, as it was causing issues. ### Internal diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index ce6c37386f4..13fd50d44ce 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -88,7 +88,7 @@ protected CVars() /// /// public static readonly CVarDef NetMtuExpand = - CVarDef.Create("net.mtu_expand", true, CVar.ARCHIVE); + CVarDef.Create("net.mtu_expand", false, CVar.ARCHIVE); /// /// Interval between MTU expansion attempts, in seconds. From ff38e9f12a50d5e83b29c21f74a50dea46660f49 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 02:52:36 +0100 Subject: [PATCH 042/130] Version: 215.3.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 05573ef86ba..bb2ee5ab01c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 215.2.0 + 215.3.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e68c7b3079f..693133dda24 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,19 +43,37 @@ END TEMPLATE--> ### Bugfixes -* `Texture.GetPixel()`: fixed off-by-one with Y coordinate. -* `Texture.GetPixel()`: fix stack overflow when reading large images. -* `Texture.GetPixel()`: use more widely compatible OpenGL calls. +*None yet* ### Other -* Disabled `net.mtu_expand` again by default, as it was causing issues. +*None yet* ### Internal *None yet* +## 215.3.0 + +### New features + +* `EntityQuery` now has `HasComp` and `TryComp` methods that are shorter than its existing ones. +* Added `PlacementInformation.UseEditorContext`. +* Added `Vector2Helpers` functions for comparing ranges between vectors. + +### Bugfixes + +* `Texture.GetPixel()`: fixed off-by-one with Y coordinate. +* `Texture.GetPixel()`: fix stack overflow when reading large images. +* `Texture.GetPixel()`: use more widely compatible OpenGL calls. + +### Other + +* Disabled `net.mtu_expand` again by default, as it was causing issues. +* Updated `SharpZstd` dependency. + + ## 215.2.0 ### New features From 6df53d60ed1dab0410a2eb3c91bd812317efa4c0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 15:06:38 +0100 Subject: [PATCH 043/130] Changelog --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 693133dda24..84f3d95d7e9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* Log levels now work differently. When a message gets logged to a sawmill, the message will be logged if the FIRST sawmill up the chain has a passing log level set, instead of requiring all log levels up the chain to pass. This makes it possible to make specific sawmills do verbose logging, without needing to set the root too. ### New features From 7a0d02463c0fc0ce6c5c2c2d243acdbc727d87c2 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 18:46:34 +0100 Subject: [PATCH 044/130] Revert "Zstd Update (#5002)" This reverts commit eee771c5f1ece9f529931b79eb18c19910c3f00a. --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a6088ed5d5b..aead66c49ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + @@ -44,7 +44,7 @@ - + @@ -61,4 +61,4 @@ - \ No newline at end of file + From 536fca4115ec005e9cbaa45bcc79e31c5eb08f30 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 26 Mar 2024 18:47:35 +0100 Subject: [PATCH 045/130] Version: 215.3.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index bb2ee5ab01c..ed7d3a29005 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 215.3.0 + 215.3.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 84f3d95d7e9..d1c6530fea6 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -* Log levels now work differently. When a message gets logged to a sawmill, the message will be logged if the FIRST sawmill up the chain has a passing log level set, instead of requiring all log levels up the chain to pass. This makes it possible to make specific sawmills do verbose logging, without needing to set the root too. +*None yet* ### New features @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 215.3.1 + +### Bugfixes + +* Revert zstd update. + + ## 215.3.0 ### New features From ab2bff8f40a911015832e37796d269418166d719 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 27 Mar 2024 03:27:54 +0100 Subject: [PATCH 046/130] Make sawmill levels work differently. (#5006) Before, sawmill log levels were very clunky to use. We set the root sawmill to something like debug or info, but then it becomes impossible to specify specific sawmills to be verbose. This is because sawmills checked the log level at every part of the hierarchy when navigating "up" to find log handlers to write into. This changes the behavior so that the filter log level is the FIRST set log level encountered on the hierarchy. This means the log level is compared only once, and a sawmill down the hierarchy that has a level like Verbose set can cause verbose messages to come up even if the root is still set to Debug. This matches the logging behavior in libraries such as ASP.NET Core. --- Robust.Shared/Log/LogManager.Sawmill.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Robust.Shared/Log/LogManager.Sawmill.cs b/Robust.Shared/Log/LogManager.Sawmill.cs index 25354f5167c..faaef647120 100644 --- a/Robust.Shared/Log/LogManager.Sawmill.cs +++ b/Robust.Shared/Log/LogManager.Sawmill.cs @@ -77,6 +77,10 @@ public void Log(LogLevel level, Exception? exception, string message, params obj return; var msg = new LogEvent(DateTimeOffset.Now, level.ToSerilog(), exception, parsedTemplate, properties); + + if (level < GetPracticalLevel()) + return; + LogInternal(Name, msg); } @@ -99,11 +103,6 @@ public void Log(LogLevel level, string message) private void LogInternal(string sourceSawmill, LogEvent message) { - if (message.Level.ToRobust() < GetPracticalLevel()) - { - return; - } - _handlerLock.EnterReadLock(); try { From eba1d866fb765dca42853214ff32d1cfe7c7b16a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:58:52 +1300 Subject: [PATCH 047/130] Change default PVS LoD cvars (#5008) --- RELEASE-NOTES.md | 2 +- .../GameStates/PvsSystem.ToSendSet.cs | 2 +- Robust.Server/GameStates/PvsSystem.cs | 19 +++++++++++-------- Robust.Shared/CVars.cs | 18 ++++++++++++------ 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d1c6530fea6..2998e26f234 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25. ### New features diff --git a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs index f6211f40b60..a9f05606ef5 100644 --- a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs +++ b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs @@ -48,7 +48,7 @@ private void AddPvsChunk(PvsChunk chunk, float distance, PvsSession session) // We add chunk-size here so that its consistent with the normal PVS range setting. // I.e., distance here is the Chebyshev distance to the centre of each chunk, but the normal pvs range only // required that the chunk be touching the box, not the centre. - var count = distance < (_lowLodDistance + ChunkSize) / 2 + var count = distance <= (_viewSize + ChunkSize) / 2 ? chunk.Contents.Count : chunk.LodCounts[0]; diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index 4df38b34a95..184eb2f28e8 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -62,12 +62,14 @@ internal sealed partial class PvsSystem : EntitySystem public bool CullingEnabled { get; private set; } /// - /// Size of the side of the view bounds square. + /// Size of the side of the view bounds square. Related to /// private float _viewSize; - // see CVars.NetLowLodDistance - private float _lowLodDistance; + /// + /// Size of the side of the priority view bounds square. Related to + /// + private float _priorityViewSize; /// /// Per-tick ack data to avoid re-allocating. @@ -139,7 +141,7 @@ public override void Initialize() Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true); Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true); - Subs.CVar(_configManager, CVars.NetLowLodRange, OnLodChanged, true); + Subs.CVar(_configManager, CVars.NetPvsPriorityRange, OnPriorityRangeChanged, true); Subs.CVar(_configManager, CVars.NetForceAckThreshold, OnForceAckChanged, true); Subs.CVar(_configManager, CVars.NetPvsAsync, OnAsyncChanged, true); Subs.CVar(_configManager, CVars.NetPvsCompressLevel, ResetParallelism, true); @@ -276,12 +278,13 @@ private void ForceFullState(PvsSession session) private void OnViewsizeChanged(float value) { - _viewSize = value; + _viewSize = Math.Max(ChunkSize, value); + OnPriorityRangeChanged(_configManager.GetCVar(CVars.NetPvsPriorityRange)); } - private void OnLodChanged(float value) + private void OnPriorityRangeChanged(float value) { - _lowLodDistance = Math.Clamp(value, ChunkSize, 100f); + _priorityViewSize = Math.Max(_viewSize, value); } private void OnForceAckChanged(int value) @@ -387,7 +390,7 @@ private void VerifySessionData(PvsSession pvsSession) private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity eye) { - var size = Math.Max(eye.Comp2?.PvsSize ?? _viewSize, 1); + var size = Math.Max(eye.Comp2?.PvsSize ?? _priorityViewSize, 1); return (_transform.GetWorldPosition(eye.Comp1), size / 2f, eye.Comp1.MapUid); } diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 13fd50d44ce..9bfa376f9c4 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -225,18 +225,24 @@ protected CVars() CVarDef.Create("net.pvs_async", true, CVar.ARCHIVE | CVar.SERVERONLY); /// - /// View size to take for PVS calculations, - /// as the size of the sides of a square centered on the view points of clients. + /// View size to take for PVS calculations, as the size of the sides of a square centered on the view points of + /// clients. See also . /// public static readonly CVarDef NetMaxUpdateRange = CVarDef.Create("net.pvs_range", 25f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); /// - /// Chunks whose centre is further than this distance away from a player's eye will contain fewer entities. - /// This has no effect if it is smaller than + /// A variant of that is used to limit the view-distance of entities with the + /// flag set. This can be used to extend the range at which certain + /// entities become visible. /// - public static readonly CVarDef NetLowLodRange = - CVarDef.Create("net.low_lod_distance", 100f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + /// + /// This is useful for entities like lights and occluders to try and prevent noticeable pop-in as players + /// move around. Note that this has no effect if it is less than , and that this + /// only works for entities that are directly parented to a grid or map. + /// + public static readonly CVarDef NetPvsPriorityRange = + CVarDef.Create("net.pvs_priority_range", 32.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); /// /// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the From c59ef5ab2dd63e7968da403b59222088ff9a5143 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:07:54 +1100 Subject: [PATCH 048/130] Fix buffered audio disposals (#5009) --- RELEASE-NOTES.md | 2 +- Robust.Client/GameController/GameController.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2998e26f234..c01625a4f1b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fix buffered audio sources not being disposed. ### Other diff --git a/Robust.Client/GameController/GameController.cs b/Robust.Client/GameController/GameController.cs index cb21e096b75..f855d902ba2 100644 --- a/Robust.Client/GameController/GameController.cs +++ b/Robust.Client/GameController/GameController.cs @@ -612,6 +612,8 @@ private void Update(FrameEventArgs frameEventArgs) { _modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs); } + + _audio.FlushALDisposeQueues(); } internal static void SetupLogging( From 2a9de462d5a9d66a6c0374e106c785f6a75c392a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 27 Mar 2024 04:14:19 +0100 Subject: [PATCH 049/130] Preserve tile maps when saving maps & related changes (#5003) * Un-hardcode behavior to make a component not saved to map file. MapSaveId is a special component that can't be saved to map files due to a hardcoded type check. This behavior can now be applied to any component with [UnsavedComponent]. Moved "component registration" attributes into a single file because they don't deserve their own (poorly organized) .cs files. * Add ITileDefinitionManager.TryGetDefinition Try-pattern version of the existing indexers. * Preserve tile maps when saving maps This changes the map saver and loader code so that the "tilemap" can be preserved between map modifications as much as possible. The tile map from the loaded map gets stored onto MapSaveTileMapComponent components on all loaded grids. This tile map is then used when saving, meaning that changes to the engine's internal tile IDs do not cause diffs. Fixes #5000 * Changelog * Fix tests --- RELEASE-NOTES.md | 4 +- .../EntitySystems/MapLoaderSystem.cs | 83 ++++++++++++++++--- .../GameObjects/MapSaveIdComponent.cs | 2 +- .../GameObjects/MapSaveTileMapComponent.cs | 24 ++++++ Robust.Server/Maps/MapChunkSerializer.cs | 14 +++- .../GameObjects/ComponentAttributes.cs | 28 +++++++ Robust.Shared/GameObjects/ComponentFactory.cs | 4 +- .../ComponentProtoNameAttribute.cs | 18 ---- .../GameObjects/ComponentRegistration.cs | 9 +- .../GameObjects/RegisterComponentAttribute.cs | 16 ---- Robust.Shared/Map/ITileDefinitionManager.cs | 25 +++++- Robust.Shared/Map/MapSerializationContext.cs | 3 + Robust.Shared/Map/TileDefinitionManager.cs | 18 ++++ Robust.UnitTesting/RobustUnitTest.cs | 6 ++ 14 files changed, 202 insertions(+), 52 deletions(-) create mode 100644 Robust.Server/GameObjects/MapSaveTileMapComponent.cs create mode 100644 Robust.Shared/GameObjects/ComponentAttributes.cs delete mode 100644 Robust.Shared/GameObjects/ComponentProtoNameAttribute.cs delete mode 100644 Robust.Shared/GameObjects/RegisterComponentAttribute.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c01625a4f1b..417f3b036bd 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,9 @@ END TEMPLATE--> ### New features -*None yet* +* You can now specify a component to not be saved to map files with `[UnsavedComponent]`. +* Added `ITileDefinitionManager.TryGetDefinition`. +* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed. ### Bugfixes diff --git a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs index b112319ebc3..6a0d2282fb5 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs @@ -280,6 +280,9 @@ private bool Deserialize(MapData data) // Load the prototype data onto entities, e.g. transform parents, etc. LoadEntities(data); + // Assign MapSaveTileMapComponent to all read grids. + SaveGridTileMap(data); + // Build the scene graph / transform hierarchy to know the order to startup entities. // This also allows us to swap out the root node up front if necessary. BuildEntityHierarchy(data); @@ -576,6 +579,19 @@ private void LoadEntity(EntityUid uid, MappingDataNode data, MetaDataComponent m meta.LastComponentRemoved = _timing.CurTick; } + private void SaveGridTileMap(MapData mapData) + { + DebugTools.Assert(_context.TileMap != null); + + foreach (var entity in mapData.EntitiesToDeserialize.Keys) + { + if (HasComp(entity)) + { + EnsureComp(entity).TileMap = _context.TileMap; + } + } + } + private void BuildEntityHierarchy(MapData mapData) { _stopwatch.Restart(); @@ -981,28 +997,74 @@ private void WriteTileMapSection(MappingDataNode rootNode, List entit var gridQuery = GetEntityQuery(); var tileDefs = new HashSet(); + Dictionary? origTileMap = null; foreach (var ent in entities) { if (!gridQuery.TryGetComponent(ent, out var grid)) continue; - var tileEnumerator = grid.GetAllTilesEnumerator(false); - + var tileEnumerator = _mapSystem.GetAllTilesEnumerator(ent, grid, ignoreEmpty: false); while (tileEnumerator.MoveNext(out var tileRef)) { tileDefs.Add(tileRef.Value.Tile.TypeId); } + + if (TryComp(ent, out MapSaveTileMapComponent? saveTileMap)) + origTileMap ??= saveTileMap.TileMap; + } + + Dictionary tileIdMap; + if (origTileMap != null) + { + tileIdMap = new Dictionary(); + + // We are re-saving a map, so we have an original tile map we can preserve. + foreach (var (origId, prototypeId) in origTileMap) + { + // Skip removed tile definitions. + if (!_tileDefManager.TryGetDefinition(prototypeId, out var definition)) + continue; + + tileIdMap.Add(definition.TileId, origId); + } + + // Assign new IDs for all new tile types. + var nextId = 0; + foreach (var tileId in tileDefs) + { + if (tileIdMap.ContainsKey(tileId)) + continue; + + // New tile, assign new ID that isn't taken by original tile map. + while (origTileMap.ContainsKey(nextId)) + { + nextId += 1; + } + + tileIdMap.Add(tileId, nextId); + nextId += 1; + } + } + else + { + // Make no-op tile ID map. + tileIdMap = tileDefs.ToDictionary(x => x, x => x); } + DebugTools.Assert( + tileIdMap.Count == tileIdMap.Values.Distinct().Count(), + "Tile ID map has double mapped values??"); + + _context.TileWriteMap = tileIdMap; + var tileMap = new MappingDataNode(); rootNode.Add("tilemap", tileMap); - var ordered = new List(tileDefs); - ordered.Sort(); - foreach (var tyleId in ordered) + foreach (var (nativeId, mapId) in tileIdMap.OrderBy(x => x.Key)) { - var tileDef = _tileDefManager[tyleId]; - tileMap.Add(tyleId.ToString(CultureInfo.InvariantCulture), tileDef.ID); + tileMap.Add( + mapId.ToString(CultureInfo.InvariantCulture), + _tileDefManager[nativeId].ID); } } @@ -1176,11 +1238,12 @@ private void WriteEntitySection(MappingDataNode rootNode, Dictionary - [RegisterComponent] + [RegisterComponent, UnsavedComponent] public sealed partial class MapSaveIdComponent : Component { public int Uid { get; set; } diff --git a/Robust.Server/GameObjects/MapSaveTileMapComponent.cs b/Robust.Server/GameObjects/MapSaveTileMapComponent.cs new file mode 100644 index 00000000000..bdd4cde887c --- /dev/null +++ b/Robust.Server/GameObjects/MapSaveTileMapComponent.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; + +namespace Robust.Server.GameObjects; + +/// +/// Used by to track the original tile map from when a map was loaded. +/// +/// +/// +/// This component is used to reduce differences on map saving, by making it so that a tile map can be re-used between map saves even if internal engine IDs change. +/// +/// +/// This component is created on every grid entity read during map load. +/// This means loading a multi-grid map will create multiple of these components. +/// When re-saving the map, the map loader will arbitrarily choose which available +/// to use. +/// +/// +[RegisterComponent, UnsavedComponent] +internal sealed partial class MapSaveTileMapComponent : Component +{ + public Dictionary TileMap = []; +} diff --git a/Robust.Server/Maps/MapChunkSerializer.cs b/Robust.Server/Maps/MapChunkSerializer.cs index d06e45679b5..cabf4435c23 100644 --- a/Robust.Server/Maps/MapChunkSerializer.cs +++ b/Robust.Server/Maps/MapChunkSerializer.cs @@ -104,12 +104,16 @@ public DataNode Write(ISerializationManager serializationManager, MapChunk value root.Add("version", new ValueDataNode("6")); - gridNode.Value = SerializeTiles(value); + Dictionary? tileWriteMap = null; + if (context is MapSerializationContext mapContext) + tileWriteMap = mapContext.TileWriteMap; + + gridNode.Value = SerializeTiles(value, tileWriteMap); return root; } - private static string SerializeTiles(MapChunk chunk) + private static string SerializeTiles(MapChunk chunk, Dictionary? tileWriteMap) { // number of bytes written per tile, because sizeof(Tile) is useless. const int structSize = 6; @@ -125,7 +129,11 @@ private static string SerializeTiles(MapChunk chunk) for (ushort x = 0; x < chunk.ChunkSize; x++) { var tile = chunk.GetTile(x, y); - writer.Write(tile.TypeId); + var typeId = tile.TypeId; + if (tileWriteMap != null) + typeId = tileWriteMap[typeId]; + + writer.Write(typeId); writer.Write((byte)tile.Flags); writer.Write(tile.Variant); } diff --git a/Robust.Shared/GameObjects/ComponentAttributes.cs b/Robust.Shared/GameObjects/ComponentAttributes.cs new file mode 100644 index 00000000000..7a538ec0b0c --- /dev/null +++ b/Robust.Shared/GameObjects/ComponentAttributes.cs @@ -0,0 +1,28 @@ +using System; +using JetBrains.Annotations; + +namespace Robust.Shared.GameObjects; + +/// +/// Marks a component as being automatically registered by +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[BaseTypeRequired(typeof(IComponent))] +[MeansImplicitUse] +public sealed class RegisterComponentAttribute : Attribute; + +/// +/// Defines Name that this component is represented with in prototypes. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class ComponentProtoNameAttribute(string prototypeName) : Attribute +{ + public string PrototypeName { get; } = prototypeName; +} + +/// +/// Marks a component as not being saved when saving maps/grids. +/// +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class UnsavedComponentAttribute : Attribute; diff --git a/Robust.Shared/GameObjects/ComponentFactory.cs b/Robust.Shared/GameObjects/ComponentFactory.cs index d0437ada609..3a2b836d92d 100644 --- a/Robust.Shared/GameObjects/ComponentFactory.cs +++ b/Robust.Shared/GameObjects/ComponentFactory.cs @@ -121,9 +121,11 @@ private ComponentRegistration Register(Type type, if (!overwrite && lowerCaseNames.TryGetValue(lowerCaseName, out var prevName)) throw new InvalidOperationException($"{lowerCaseName} is already registered, previous: {prevName}"); + var unsaved = type.HasCustomAttribute(); + var idx = CompIdx.Index(type); - var registration = new ComponentRegistration(name, type, idx); + var registration = new ComponentRegistration(name, type, idx, unsaved); idxToType[idx] = type; names[name] = registration; diff --git a/Robust.Shared/GameObjects/ComponentProtoNameAttribute.cs b/Robust.Shared/GameObjects/ComponentProtoNameAttribute.cs deleted file mode 100644 index e1fa90d39e7..00000000000 --- a/Robust.Shared/GameObjects/ComponentProtoNameAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Robust.Shared.GameObjects -{ - /// - /// Defines Name that this component is represented with in prototypes. - /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class ComponentProtoNameAttribute : Attribute - { - public string PrototypeName { get; } - - public ComponentProtoNameAttribute(string prototypeName) - { - PrototypeName = prototypeName; - } - } -} diff --git a/Robust.Shared/GameObjects/ComponentRegistration.cs b/Robust.Shared/GameObjects/ComponentRegistration.cs index 7e6006f8fb5..84947c40d5f 100644 --- a/Robust.Shared/GameObjects/ComponentRegistration.cs +++ b/Robust.Shared/GameObjects/ComponentRegistration.cs @@ -21,6 +21,12 @@ public sealed class ComponentRegistration public CompIdx Idx { get; } + /// + /// If this is true, the component will not be saved when saving a map/grid. + /// + /// + public bool Unsaved { get; } + /// /// ID used to reference the component type across the network. /// If null, no network synchronization will be available for this component. @@ -35,11 +41,12 @@ public sealed class ComponentRegistration // Internal for sandboxing. // Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated. - internal ComponentRegistration(string name, Type type, CompIdx idx) + internal ComponentRegistration(string name, Type type, CompIdx idx, bool unsaved = false) { Name = name; Type = type; Idx = idx; + Unsaved = unsaved; } public override string ToString() diff --git a/Robust.Shared/GameObjects/RegisterComponentAttribute.cs b/Robust.Shared/GameObjects/RegisterComponentAttribute.cs deleted file mode 100644 index 22b1986cb35..00000000000 --- a/Robust.Shared/GameObjects/RegisterComponentAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Robust.Shared.GameObjects -{ - /// - /// Marks a component as being automatically registered by - /// - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [BaseTypeRequired(typeof(IComponent))] - [MeansImplicitUse] - public sealed class RegisterComponentAttribute : Attribute - { - - } -} diff --git a/Robust.Shared/Map/ITileDefinitionManager.cs b/Robust.Shared/Map/ITileDefinitionManager.cs index 4175350f5ba..fd1f13226dd 100644 --- a/Robust.Shared/Map/ITileDefinitionManager.cs +++ b/Robust.Shared/Map/ITileDefinitionManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Robust.Shared.Random; namespace Robust.Shared.Map @@ -22,6 +23,7 @@ public interface ITileDefinitionManager : IEnumerable /// /// The name of the tile definition. /// The named tile definition. + /// ITileDefinition this[string name] { get; } /// @@ -29,8 +31,29 @@ public interface ITileDefinitionManager : IEnumerable /// /// The ID of the tile definition. /// The tile definition. + /// ITileDefinition this[int id] { get; } - // TODO add a try get and get-or-null variant. + + /// + /// Try to retrieve a tile definition by name. + /// + /// + /// Note: In the presence of tile aliases, this[A].ID does not necessarily equal A. + /// + /// The name of the tile definition to look up. + /// The found tile definition, if it exists. + /// True if a tile definition was resolved, false otherwise. + /// + bool TryGetDefinition(string name, [NotNullWhen(true)] out ITileDefinition? definition); + + /// + /// Try to retrieve a tile definition by tile ID. + /// + /// The ID of the tile definition to look up. + /// The found tile definition, if it exists. + /// True if a tile definition was resolved, false otherwise. + /// + bool TryGetDefinition(int id, [NotNullWhen(true)] out ITileDefinition? definition); /// /// The number of tile definitions contained inside of this manager. diff --git a/Robust.Shared/Map/MapSerializationContext.cs b/Robust.Shared/Map/MapSerializationContext.cs index 5a67dd1d2ab..72d106d4597 100644 --- a/Robust.Shared/Map/MapSerializationContext.cs +++ b/Robust.Shared/Map/MapSerializationContext.cs @@ -33,6 +33,9 @@ internal sealed class MapSerializationContext : ISerializationContext, IEntityLo private Dictionary _uidEntityMap = new(); private Dictionary _entityUidMap = new(); + // Native tile ID -> map tile ID map for writing maps. + public Dictionary TileWriteMap = []; + /// /// Are we currently iterating prototypes or entities for writing. /// diff --git a/Robust.Shared/Map/TileDefinitionManager.cs b/Robust.Shared/Map/TileDefinitionManager.cs index 01803cbbf3d..a4694510d5e 100644 --- a/Robust.Shared/Map/TileDefinitionManager.cs +++ b/Robust.Shared/Map/TileDefinitionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -111,6 +112,23 @@ public Tile GetVariantTile(ITileDefinition tileDef, System.Random random) public ITileDefinition this[int id] => TileDefs[id]; + public bool TryGetDefinition(string name, [NotNullWhen(true)] out ITileDefinition? definition) + { + return _tileNames.TryGetValue(name, out definition); + } + + public bool TryGetDefinition(int id, [NotNullWhen(true)] out ITileDefinition? definition) + { + if (id >= TileDefs.Count) + { + definition = null; + return false; + } + + definition = TileDefs[id]; + return true; + } + public int Count => TileDefs.Count; public IEnumerator GetEnumerator() diff --git a/Robust.UnitTesting/RobustUnitTest.cs b/Robust.UnitTesting/RobustUnitTest.cs index 0cacf50afee..6d0c0253933 100644 --- a/Robust.UnitTesting/RobustUnitTest.cs +++ b/Robust.UnitTesting/RobustUnitTest.cs @@ -166,6 +166,12 @@ public void BaseSetup() if (ExtraComponents != null) compFactory.RegisterTypes(ExtraComponents); + if (Project == UnitTestProject.Server) + { + compFactory.RegisterClass(); + compFactory.RegisterClass(); + } + deps.Resolve().Initialize(); // So by default EntityManager does its own EntitySystemManager initialize during Startup. From 8607ba1f16ce676a849b59a41efd389a6e467f5c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 27 Mar 2024 14:14:54 +1100 Subject: [PATCH 050/130] Version: 216.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index ed7d3a29005..2ff45d7e77c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 215.3.1 + 216.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 417f3b036bd..6e67530eb9a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,17 +35,15 @@ END TEMPLATE--> ### Breaking changes -* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25. +*None yet* ### New features -* You can now specify a component to not be saved to map files with `[UnsavedComponent]`. -* Added `ITileDefinitionManager.TryGetDefinition`. -* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed. +*None yet* ### Bugfixes -* Fix buffered audio sources not being disposed. +*None yet* ### Other @@ -56,6 +54,23 @@ END TEMPLATE--> *None yet* +## 216.0.0 + +### Breaking changes + +* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25. + +### New features + +* You can now specify a component to not be saved to map files with `[UnsavedComponent]`. +* Added `ITileDefinitionManager.TryGetDefinition`. +* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed. + +### Bugfixes + +* Fix buffered audio sources not being disposed. + + ## 215.3.1 ### Bugfixes From 02ac314b1ae87e227971d95d034619cc4b9e8ba3 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Tue, 26 Mar 2024 20:33:59 -0700 Subject: [PATCH 051/130] Fix first measure of ScrollContainer scroll bars (#5005) The first measure would include scroll bar size even if the available size fit all the contents. This would make the returned size larger than it should be. --- Robust.Client/UserInterface/Controls/ScrollContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/Controls/ScrollContainer.cs b/Robust.Client/UserInterface/Controls/ScrollContainer.cs index 5733fab6b4f..4741855a22c 100644 --- a/Robust.Client/UserInterface/Controls/ScrollContainer.cs +++ b/Robust.Client/UserInterface/Controls/ScrollContainer.cs @@ -123,10 +123,10 @@ protected override Vector2 MeasureOverride(Vector2 availableSize) if (!ReturnMeasure) return Vector2.Zero; - if (_vScrollEnabled) + if (_vScrollEnabled && size.Y >= availableSize.Y) size.X += _vScrollBar.DesiredSize.X; - if (_hScrollEnabled) + if (_hScrollEnabled && size.X >= availableSize.X) size.Y += _hScrollBar.DesiredSize.Y; return size; From 9ef7f7cb37960caced6bee205cb8273bccbe080c Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:36:11 +0000 Subject: [PATCH 052/130] add AddUi to shared ui system (#4984) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Systems/SharedUserInterfaceSystem.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 4c5cac1af69..5ad50f9d995 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -25,8 +25,7 @@ private void OnUserInterfaceInit(EntityUid uid, UserInterfaceComponent component foreach (var prototypeData in component.InterfaceData) { - component.Interfaces[prototypeData.UiKey] = new PlayerBoundUserInterface(prototypeData, uid); - component.MappedInterfaceData[prototypeData.UiKey] = prototypeData; + AddUi((uid, component), prototypeData); } } @@ -105,6 +104,19 @@ protected virtual void CloseShared(PlayerBoundUserInterface bui, ICommonSession { } + /// + /// Add a UI after an entity has been created. + /// It cannot be added already. + /// + public void AddUi(Entity ent, PrototypeData data) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + ent.Comp.Interfaces[data.UiKey] = new PlayerBoundUserInterface(data, ent); + ent.Comp.MappedInterfaceData[data.UiKey] = data; + } + public bool TryGetUi(EntityUid uid, Enum uiKey, [NotNullWhen(true)] out PlayerBoundUserInterface? bui, UserInterfaceComponent? ui = null) { bui = null; From c79217ab6628776fdedab84b5a8bc85e78939a34 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:49:26 +1100 Subject: [PATCH 053/130] Rework SetWorldPosition (#4915) * Remove ents from containers for worldpos updates Avoids bugs from content not checking for this every time. Physics and anything else important manually uses localposition updates so shouldn't adversely impact performance that much. * Fixes * Fix grid pos * Also fix the joint setter * Add both * Update Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --------- Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --- RELEASE-NOTES.md | 2 +- .../SharedTransformSystem.Component.cs | 47 ++++++--------- .../Components/TransformIntegration_Test.cs | 60 +++++++++++++++++++ .../Shared/Map/GridSplit_Tests.cs | 1 - 4 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6e67530eb9a..557c4bdf247 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this). ### New features diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 97f4069c862..5acb8eaa577 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -975,36 +975,25 @@ public Vector2 GetRelativePosition( [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPosition(EntityUid uid, Vector2 worldPos) { - var xform = Transform(uid); + var xform = XformQuery.GetComponent(uid); SetWorldPosition(xform, worldPos); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetWorldPosition(EntityUid uid, Vector2 worldPos, EntityQuery xformQuery) - { - var component = xformQuery.GetComponent(uid); - SetWorldPosition(component, worldPos, xformQuery); - } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPosition(TransformComponent component, Vector2 worldPos) { - SetWorldPosition(component, worldPos, XformQuery); + SetWorldPosition((component.Owner, component), worldPos); } + /// + /// Sets the position of the entity in world-terms to the specified position. + /// May also de-parent the entity. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetWorldPosition(TransformComponent component, Vector2 worldPos, EntityQuery xformQuery) + public void SetWorldPosition(Entity entity, Vector2 worldPos) { - if (!component._parent.IsValid()) - { - DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?"); - return; - } - - var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component, xformQuery); - var negativeParentWorldRot = component._localRotation - curWorldRot; - var newLocalPos = component._localPosition + negativeParentWorldRot.RotateVec(worldPos - curWorldPos); - SetLocalPosition(component, newLocalPos); + SetWorldPositionRotation(entity.Owner, worldPos, entity.Comp.LocalRotation, entity.Comp); } #endregion @@ -1094,20 +1083,22 @@ public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worl if (!XformQuery.Resolve(uid, ref component)) return; - if (!component._parent.IsValid()) + if (!component._parent.IsValid() || component.MapUid == null) { DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?"); return; } - var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component); - - var negativeParentWorldRot = component.LocalRotation - curWorldRot; - - var newLocalPos = component.LocalPosition + negativeParentWorldRot.RotateVec(worldPos - curWorldPos); - var newLocalRot = component.LocalRotation + worldRot - curWorldRot; - - SetLocalPositionRotation(uid, newLocalPos, newLocalRot, component); + if (component.GridUid != uid && _mapManager.TryFindGridAt(component.MapUid.Value, worldPos, out var targetGrid, out _)) + { + var (_, gridRot, invWorldMatrix) = GetWorldPositionRotationInvMatrix(targetGrid); + var localRot = worldRot - gridRot; + SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(worldPos)), rotation: localRot); + } + else + { + SetCoordinates(uid, component, new EntityCoordinates(component.MapUid.Value, worldPos), rotation: worldRot); + } } [Obsolete("Use override with EntityUid")] diff --git a/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs new file mode 100644 index 00000000000..9da5438ef64 --- /dev/null +++ b/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs @@ -0,0 +1,60 @@ +using System.Numerics; +using NUnit.Framework; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Robust.UnitTesting.Server.GameObjects.Components; + +[TestFixture] +public sealed class TransformIntegration_Test +{ + /// + /// Asserts that calling SetWorldPosition while in a container correctly removes the entity from its container. + /// + [Test] + public void WorldPositionContainerSet() + { + var factory = RobustServerSimulation.NewSimulation(); + + var sim = factory.InitializeInstance(); + + var entManager = sim.Resolve(); + var mapManager = sim.Resolve(); + var containerSystem = entManager.System(); + var xformSystem = entManager.System(); + + var map1Id = mapManager.CreateMap(); + var map1 = mapManager.GetMapEntityId(map1Id); + + var ent1 = entManager.SpawnEntity(null, new EntityCoordinates(map1, Vector2.Zero)); + var ent2 = entManager.SpawnEntity(null, new EntityCoordinates(map1, Vector2.Zero)); + var ent3 = entManager.SpawnEntity(null, new EntityCoordinates(map1, Vector2.Zero)); + + var container = containerSystem.EnsureContainer(ent1, "a"); + + // Assert that setting worldpos updates parent correctly. + containerSystem.Insert(ent2, container, force: true); + + Assert.That(containerSystem.IsEntityInContainer(ent2)); + + xformSystem.SetWorldPosition(ent2, Vector2.One); + + Assert.That(!containerSystem.IsEntityInContainer(ent2)); + Assert.That(xformSystem.GetWorldPosition(ent2), Is.EqualTo(Vector2.One)); + + // Assert that you can set recursively contained (but not directly contained) entities correctly. + containerSystem.Insert(ent2, container); + xformSystem.SetParent(ent3, ent2); + + Assert.That(xformSystem.GetParentUid(ent3), Is.EqualTo(ent2)); + + xformSystem.SetWorldPosition(ent3, Vector2.One); + + Assert.That(xformSystem.GetParentUid(ent3), Is.EqualTo(map1)); + Assert.That(xformSystem.GetWorldPosition(ent3).Equals(Vector2.One)); + + // Cleanup + entManager.DeleteEntity(map1); + } +} diff --git a/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs b/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs index 8b1607d1c33..e5768eb1b65 100644 --- a/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs @@ -117,7 +117,6 @@ public void TriSplit() var mapSystem = sim.Resolve().System(); var mapId = mapManager.CreateMap(); var gridEnt = mapManager.CreateGridEntity(mapId); - var grid = gridEnt.Comp; for (var x = 0; x < 3; x++) { From 5164d99996e2b16b987384e2d9793d77a7c399ea Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:54:14 +1100 Subject: [PATCH 054/130] Implement VV for AudioParams + dump obsolete params (#4994) * Implement sound VV No params yet because I'm lazy but paths and collections work. * Fixes * rn * review * pitch * VV sound params + dump unneeded params BusName never used and per-source Attenuation got dropped. * weh? --- Resources/Locale/en-US/view-variables.ftl | 15 + Resources/Locale/en-US/vv.ftl | 3 - .../Editors/VVPropEditorSoundSpecifier.cs | 289 +++++++++++++++++- Robust.Shared/Audio/AudioParams.cs | 50 +-- 4 files changed, 310 insertions(+), 47 deletions(-) delete mode 100644 Resources/Locale/en-US/vv.ftl diff --git a/Resources/Locale/en-US/view-variables.ftl b/Resources/Locale/en-US/view-variables.ftl index 0a27bb4162e..c16b1434fdd 100644 --- a/Resources/Locale/en-US/view-variables.ftl +++ b/Resources/Locale/en-US/view-variables.ftl @@ -10,3 +10,18 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search view-variable-instance-entity-server-components-search-bar-placeholder = Search view-variable-instance-entity-add-window-server-components = Add Component [S] view-variable-instance-entity-add-window-client-components = Add Component [C] + + +## SoundSpecifier +vv-sound-none = None +vv-sound-path = Path +vv-sound-collection = Collection + +vv-sound-volume = volume +vv-sound-pitch = Pitch +vv-sound-max-distance = Max Distance +vv-sound-rolloff-factor = Rolloff Factor +vv-sound-reference-distance = Reference Distance +vv-sound-loop = Loop +vv-sound-play-offset = Play Offset (s) +vv-sound-variation = Pitch variation diff --git a/Resources/Locale/en-US/vv.ftl b/Resources/Locale/en-US/vv.ftl deleted file mode 100644 index e69181b7c97..00000000000 --- a/Resources/Locale/en-US/vv.ftl +++ /dev/null @@ -1,3 +0,0 @@ -vv-sound-none = None -vv-sound-path = Path -vv-sound-collection = Collection \ No newline at end of file diff --git a/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs b/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs index b4b9f3acb20..d4441dff995 100644 --- a/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs +++ b/Robust.Client/ViewVariables/Editors/VVPropEditorSoundSpecifier.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Numerics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -16,6 +17,10 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor private readonly IPrototypeManager _protoManager; private readonly IResourceManager _resManager; + // Need to cache to some level just to make sure each edit doesn't reset the specifier to the default. + + private SoundSpecifier? _specifier; + public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager) { _protoManager = protoManager; @@ -39,7 +44,7 @@ protected override Control MakeUI(object? value) Editable = !ReadOnly, }; - var controls = new BoxContainer() + var pathControls = new BoxContainer() { Orientation = BoxContainer.LayoutOrientation.Horizontal, Children = @@ -57,10 +62,15 @@ protected override Control MakeUI(object? value) case SoundCollectionSpecifier collection: typeButton.SelectId(1); editBox.Text = collection.Collection ?? string.Empty; + _specifier = collection; break; case SoundPathSpecifier path: typeButton.SelectId(2); editBox.Text = path.Path.ToString(); + _specifier = path; + break; + default: + _specifier = null; break; } } @@ -90,7 +100,11 @@ protected override Control MakeUI(object? value) if (!_protoManager.HasIndex(args.Text)) return; - ValueChanged(new SoundCollectionSpecifier(args.Text)); + _specifier = new SoundCollectionSpecifier(args.Text) + { + Params = _specifier?.Params ?? AudioParams.Default, + }; + ValueChanged(_specifier); break; case 2: var path = new ResPath(args.Text); @@ -98,13 +112,282 @@ protected override Control MakeUI(object? value) if (!_resManager.ContentFileExists(path)) return; - ValueChanged(new SoundPathSpecifier(args.Text)); + _specifier = new SoundPathSpecifier(args.Text) + { + Params = _specifier?.Params ?? AudioParams.Default, + }; + + ValueChanged(_specifier); break; default: return; } }; + // Audio params + + /* Volume */ + + var volumeEdit = new LineEdit() + { + Text = _specifier?.Params.Volume.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + volumeEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithVolume(floatValue); + ValueChanged(_specifier); + }; + + var volumeContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-volume"), + }, + volumeEdit, + } + }; + + /* Pitch */ + + var pitchEdit = new LineEdit() + { + Text = _specifier?.Params.Pitch.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + pitchEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithPitchScale(floatValue); + ValueChanged(_specifier); + }; + + var pitchContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-pitch"), + }, + pitchEdit, + } + }; + + /* MaxDistance */ + + var maxDistanceEdit = new LineEdit() + { + Text = _specifier?.Params.MaxDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + maxDistanceEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithMaxDistance(floatValue); + ValueChanged(_specifier); + }; + + var maxDistanceContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-max-distance"), + }, + maxDistanceEdit, + } + }; + + /* RolloffFactor */ + + var rolloffFactorEdit = new LineEdit() + { + Text = _specifier?.Params.RolloffFactor.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + rolloffFactorEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithRolloffFactor(floatValue); + ValueChanged(_specifier); + }; + + var rolloffFactorContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-rolloff-factor"), + }, + rolloffFactorEdit, + } + }; + + /* ReferenceDistance */ + + var referenceDistanceEdit = new LineEdit() + { + Text = _specifier?.Params.ReferenceDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + referenceDistanceEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithReferenceDistance(floatValue); + ValueChanged(_specifier); + }; + + var referenceDistanceContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-reference-distance"), + }, + referenceDistanceEdit, + } + }; + + /* Loop */ + + var loopButton = new Button() + { + Text = Loc.GetString("vv-sound-loop"), + Pressed = _specifier?.Params.Loop ?? false, + ToggleMode = true, + Disabled = ReadOnly || _specifier == null, + }; + + loopButton.OnPressed += args => + { + if (_specifier == null) + return; + + _specifier.Params = _specifier.Params.WithLoop(args.Button.Pressed); + ValueChanged(_specifier); + }; + + /* PlayOffsetSeconds */ + + var playOffsetEdit = new LineEdit() + { + Text = _specifier?.Params.PlayOffsetSeconds.ToString(CultureInfo.InvariantCulture) ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + playOffsetEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithPlayOffset(floatValue); + ValueChanged(_specifier); + }; + + var playOffsetContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-play-offset"), + }, + playOffsetEdit, + } + }; + + /* Variation */ + + var variationEdit = new LineEdit() + { + Text = _specifier?.Params.Variation.ToString() ?? string.Empty, + HorizontalExpand = true, + Editable = !ReadOnly && _specifier != null, + }; + + variationEdit.OnTextEntered += args => + { + if (!float.TryParse(args.Text, out var floatValue) || _specifier == null) + return; + + _specifier.Params = _specifier.Params.WithVariation(floatValue); + ValueChanged(_specifier); + }; + + var variationContainer = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + Children = + { + new Label() + { + Text = Loc.GetString("vv-sound-variation"), + }, + variationEdit, + } + }; + + var audioParamsControls = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Children = + { + volumeContainer, + pitchContainer, + maxDistanceContainer, + rolloffFactorContainer, + referenceDistanceContainer, + loopButton, + playOffsetContainer, + variationContainer, + } + }; + + var controls = new BoxContainer() + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + Children = + { + pathControls, + audioParamsControls, + } + }; + return controls; } } diff --git a/Robust.Shared/Audio/AudioParams.cs b/Robust.Shared/Audio/AudioParams.cs index a8db8910cc3..fa7557dece7 100644 --- a/Robust.Shared/Audio/AudioParams.cs +++ b/Robust.Shared/Audio/AudioParams.cs @@ -29,12 +29,6 @@ public enum Attenuation : int [DataDefinition] public partial struct AudioParams { - /// - /// The DistanceModel to use for this specific source. - /// - [DataField] - public Attenuation Attenuation { get; set; } = Attenuation.LinearDistanceClamped; - /// /// Base volume to play the audio at, in dB. /// @@ -45,13 +39,13 @@ public partial struct AudioParams /// Scale for the audio pitch. /// [DataField] - public float Pitch { get; set; } = Default.Pitch; + public float Pitch + { + get => _pitch; + set => _pitch = MathF.Max(0f, value); + } - /// - /// Audio bus to play on. - /// - [DataField] - public string BusName { get; set; } = Default.BusName; + private float _pitch = Default.Pitch; /// /// Only applies to positional audio. @@ -89,7 +83,7 @@ public partial struct AudioParams /// /// The "default" audio configuration. /// - public static readonly AudioParams Default = new(0, 1, "Master", SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f); + public static readonly AudioParams Default = new(0, 1, SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f); // explicit parameterless constructor required so that default values get set properly. public AudioParams() { } @@ -97,21 +91,19 @@ public AudioParams() { } public AudioParams( float volume, float pitch, - string busName, float maxDistance, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) - : this(volume, pitch, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation) + : this(volume, pitch, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation) { } - public AudioParams(float volume, float pitch, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this() + public AudioParams(float volume, float pitch, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this() { Volume = volume; Pitch = pitch; - BusName = busName; MaxDistance = maxDistance; RolloffFactor = rolloffFactor; ReferenceDistance = refDistance; @@ -167,18 +159,6 @@ public readonly AudioParams WithPitchScale(float pitch) return me; } - /// - /// Returns a copy of this instance with a new bus name set, for easy chaining. - /// - /// The new bus name. - [Pure] - public readonly AudioParams WithBusName(string bus) - { - var me = this; - me.BusName = bus; - return me; - } - /// /// Returns a copy of this instance with a new max distance set, for easy chaining. /// @@ -227,18 +207,6 @@ public readonly AudioParams WithLoop(bool loop) return me; } - /// - /// Returns a copy of this instance with attenuation set, for easy chaining. - /// - /// The new attenuation. - [Pure] - public readonly AudioParams WithAttenuation(Attenuation attenuation) - { - var me = this; - me.Attenuation = attenuation; - return me; - } - [Pure] public readonly AudioParams WithPlayOffset(float offset) { From c38a14e78ff11207b77d5fa11d7952a8e5574038 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:56:25 +1100 Subject: [PATCH 055/130] WorldPos review fix (#5011) Forgot I merged ghub and tried pushing and didn't check it failed. --- .../GameObjects/Systems/SharedTransformSystem.Component.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 5acb8eaa577..ff660a5b835 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -1091,9 +1091,11 @@ public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worl if (component.GridUid != uid && _mapManager.TryFindGridAt(component.MapUid.Value, worldPos, out var targetGrid, out _)) { - var (_, gridRot, invWorldMatrix) = GetWorldPositionRotationInvMatrix(targetGrid); + var targetGridXform = XformQuery.GetComponent(targetGrid); + var invLocalMatrix = targetGridXform.InvLocalMatrix; + var gridRot = targetGridXform.LocalRotation; var localRot = worldRot - gridRot; - SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(worldPos)), rotation: localRot); + SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invLocalMatrix.Transform(worldPos)), rotation: localRot); } else { From 4002cbddb9c9de9030a81480b45b13d978b87526 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 29 Mar 2024 17:04:51 +1100 Subject: [PATCH 056/130] Version: 217.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 2ff45d7e77c..71f6420a89f 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 216.0.0 + 217.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 557c4bdf247..97cf5652708 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this). +*None yet* ### New features @@ -54,6 +54,22 @@ END TEMPLATE--> *None yet* +## 217.0.0 + +### Breaking changes + +* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this). + +### New features + +* Implement VV for AudioParams on SoundSpecifiers. +* Add AddUi to the shared UI system. + +### Bugfixes + +* Fix the first measure of ScrollContainer bars. + + ## 216.0.0 ### Breaking changes From 958b5dd06d90ffd5ca6b7c801a10272bfd084395 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 29 Mar 2024 16:54:27 +0100 Subject: [PATCH 057/130] Fix MapComponent.LightingEnabled funky FOV. There's two separate bool checks, one wasn't turned off, meaning the FOV kept being rendered but not updated. --- RELEASE-NOTES.md | 2 +- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 97cf5652708..493959482cf 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state. ### Other diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 17e40bc9dbb..66abf120b94 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -11,6 +11,7 @@ using Robust.Shared.Enums; using Robust.Shared.Graphics; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Profiling; using Robust.Shared.Utility; @@ -514,7 +515,9 @@ private void RenderViewport(Viewport viewport) if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov) { - ApplyFovToBuffer(viewport, eye); + var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId); + if (_entityManager.GetComponent(mapUid).LightingEnabled) + ApplyFovToBuffer(viewport, eye); } } From 73c1449811f43ebc34b0c68a7aaa56ee06444c69 Mon Sep 17 00:00:00 2001 From: Fildrance Date: Sat, 30 Mar 2024 02:42:39 +0300 Subject: [PATCH 058/130] Add GetItems() extension for IRobustRandom (#4975) * refactor: RobustRandom and RandomExtensions namespace change to file-scoped * refactor: IRobustRandom xml-doc methods rearranged to be more structured. * feat: GetItems methods added to RandomExtensions, tests for new methods added. * fix: GetItems will not request count-1 from next random, as System.Random.Next have upper bound excluded. * fix: enforced standard deviation on picking next items in RandomExtensions.GetItems + fixed hashet initial capacity +removed mandatory hashset allocation * refactor: specified border values interaction in IRobustRandom xml-doc * refactor: updated relese-notes * refactor: changed release-notes PROPERLY * fix: order by which unique random items are picked in RandomExtensions.GetItems were fixed to ACTUALLY follow normal distribution * refractor: added comment for devious RandomExtensions.GetItems only-unique logic * reduce code duplication * Cleanup code a bit Rename variables, and make it a bit more compact. Also, IMO the description is unnecessary * Remove obsolete extension * Remove incorrect O(n) comments. --------- Co-authored-by: pa.pecherskij --- RELEASE-NOTES.md | 6 +- .../Prototypes/PrototypeManager.YamlLoad.cs | 2 +- Robust.Shared/Prototypes/PrototypeManager.cs | 2 +- Robust.Shared/Random/IRobustRandom.cs | 141 ++++++-- Robust.Shared/Random/RandomExtensions.cs | 325 ++++++++++++------ Robust.Shared/Random/RobustRandom.cs | 111 +++--- .../Shared/Random/RandomExtensionsTests.cs | 284 +++++++++++++++ 7 files changed, 667 insertions(+), 204 deletions(-) create mode 100644 Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 493959482cf..feb9992ece6 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,4 +1,4 @@ -# Release notes for RobustToolbox. +# Release notes for RobustToolbox. ### New features -*None yet* +* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections. ### Bugfixes @@ -51,7 +51,7 @@ END TEMPLATE--> ### Internal -*None yet* +* `Shuffle(Span, System.Random)` has been removed, just use the builtin method. ## 217.0.0 diff --git a/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs b/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs index 003a4fb399d..f05c0f48e90 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs @@ -36,7 +36,7 @@ public void LoadDirectory(ResPath path, bool overwrite = false, .ToArray(); // Shuffle to avoid input data patterns causing uneven thread workloads. - RandomExtensions.Shuffle(streams.AsSpan(), new System.Random()); + (new System.Random()).Shuffle(streams.AsSpan()); var sawmill = _logManager.GetSawmill("eng"); diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 3149e5213dd..4292ef58292 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -432,7 +432,7 @@ public void ResolveResults() }).ToArray(); // Randomize to remove any patterns that could cause uneven load. - RandomExtensions.Shuffle(allResults.AsSpan(), rand); + rand.Shuffle(allResults.AsSpan()); // Create channel that all AfterDeserialization hooks in this group will be sent into. var hooksChannelOptions = new UnboundedChannelOptions diff --git a/Robust.Shared/Random/IRobustRandom.cs b/Robust.Shared/Random/IRobustRandom.cs index b7a0e2bdbdd..4e368c74822 100644 --- a/Robust.Shared/Random/IRobustRandom.cs +++ b/Robust.Shared/Random/IRobustRandom.cs @@ -1,56 +1,141 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using Robust.Shared.Collections; using Robust.Shared.Maths; -using Robust.Shared.Toolshed.Commands.Generic; namespace Robust.Shared.Random; +/// +/// Wrapper around random number generator helping methods. +/// public interface IRobustRandom { - /// - /// Get the underlying System.Random - /// - /// + /// Get the underlying . System.Random GetRandom(); + + /// Set seed for underlying . void SetSeed(int seed); + /// Get random value between 0 (included) and 1 (excluded). float NextFloat(); + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. public float NextFloat(float minValue, float maxValue) => NextFloat() * (maxValue - minValue) + minValue; + + /// Get random value in range of 0 (included) and (excluded). + /// Random value should be less then this value. public float NextFloat(float maxValue) => NextFloat() * maxValue; + + /// Get random value. int Next(); - int Next(int minValue, int maxValue); - TimeSpan Next(TimeSpan minTime, TimeSpan maxTime); - TimeSpan Next(TimeSpan maxTime); + + /// Get random value in range of 0 (included) and (excluded). + /// Random value should be less then this value. int Next(int maxValue); + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. + int Next(int minValue, int maxValue); + + /// Get random value between 0 (included) and (excluded). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte() + => NextByte(byte.MaxValue); + + /// Get random value in range of 0 (included) and (excluded). + /// Random value should be less then this value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte(byte maxValue) + => NextByte(0, maxValue); + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte(byte minValue, byte maxValue) + => (byte)Next(minValue, maxValue); + + /// Get random value between 0 (included) and 1 (excluded). double NextDouble(); - double NextDouble(double minValue, double maxValue) => NextDouble() * (maxValue - minValue) + minValue; + + /// Get random value in range of 0 (included) and (excluded). + /// Random value should be less then this value. + double Next(double maxValue) + => NextDouble() * maxValue; + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. + double NextDouble(double minValue, double maxValue) + => NextDouble() * (maxValue - minValue) + minValue; + + /// Get random value in range of (included) and (excluded). + /// Random value should be less then this value. + TimeSpan Next(TimeSpan maxTime); + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. + TimeSpan Next(TimeSpan minTime, TimeSpan maxTime); + + /// Fill buffer with random bytes (values). void NextBytes(byte[] buffer); - public Angle NextAngle() => NextFloat() * MathF.Tau; - public Angle NextAngle(Angle minValue, Angle maxValue) => NextFloat() * (maxValue - minValue) + minValue; - public Angle NextAngle(Angle maxValue) => NextFloat() * maxValue; + /// Get random value in range of 0 (included) and (excluded). + public Angle NextAngle() + => NextFloat() * MathF.Tau; + + /// Get random value in range of 0 (included) and (excluded). + /// Random value should be less then this value. + public Angle NextAngle(Angle maxValue) + => NextFloat() * maxValue; + + /// Get random value in range of (included) and (excluded). + /// Random value should be greater or equal to this value. + /// Random value should be less then this value. + public Angle NextAngle(Angle minValue, Angle maxValue) + => NextFloat() * (maxValue - minValue) + minValue; + + /// + /// Random vector, created from a uniform distribution of magnitudes and angles. + /// + /// Max value for randomized vector magnitude (excluded). + public Vector2 NextVector2(float maxMagnitude = 1) + => NextVector2(0, maxMagnitude); /// /// Random vector, created from a uniform distribution of magnitudes and angles. /// + /// Min value for randomized vector magnitude (included). + /// Max value for randomized vector magnitude (excluded). /// /// In general, NextVector2(1) will tend to result in vectors with smaller magnitudes than /// NextVector2Box(1,1), even if you ignored any vectors with a magnitude larger than one. /// - public Vector2 NextVector2(float minMagnitude, float maxMagnitude) => NextAngle().RotateVec(new Vector2(NextFloat(minMagnitude, maxMagnitude), 0)); - public Vector2 NextVector2(float maxMagnitude = 1) => NextVector2(0, maxMagnitude); + public Vector2 NextVector2(float minMagnitude, float maxMagnitude) + => NextAngle().RotateVec(new Vector2(NextFloat(minMagnitude, maxMagnitude), 0)); /// /// Random vector, created from a uniform distribution of x and y coordinates lying inside some box. /// - public Vector2 NextVector2Box(float minX, float minY, float maxX, float maxY) => new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY)); - public Vector2 NextVector2Box(float maxAbsX = 1, float maxAbsY = 1) => NextVector2Box(-maxAbsX, -maxAbsY, maxAbsX, maxAbsY); + public Vector2 NextVector2Box(float minX, float minY, float maxX, float maxY) + => new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY)); + /// + /// Random vector, created from a uniform distribution of x and y coordinates lying inside some box. + /// Box will have coordinates starting at [- , -] + /// and ending in [ , ] + /// + public Vector2 NextVector2Box(float maxAbsX = 1, float maxAbsY = 1) + => NextVector2Box(-maxAbsX, -maxAbsY, maxAbsX, maxAbsY); + + /// Randomly switches positions in collection. void Shuffle(IList list) { var n = list.Count; @@ -62,6 +147,7 @@ void Shuffle(IList list) } } + /// Randomly switches positions in collection. void Shuffle(Span list) { var n = list.Length; @@ -73,6 +159,7 @@ void Shuffle(Span list) } } + /// Randomly switches positions in collection. void Shuffle(ValueList list) { var n = list.Count; @@ -83,24 +170,6 @@ void Shuffle(ValueList list) (list[k], list[n]) = (list[n], list[k]); } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte NextByte(byte maxValue) - { - return NextByte(0, maxValue); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte NextByte() - { - return NextByte(byte.MaxValue); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte NextByte(byte minValue, byte maxValue) - { - return (byte) Next(minValue, maxValue); - } } public static class RandomHelpers @@ -136,6 +205,6 @@ public static byte NextByte(this System.Random random) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte NextByte(this System.Random random, byte minValue, byte maxValue) { - return (byte) random.Next(minValue, maxValue); + return (byte)random.Next(minValue, maxValue); } } diff --git a/Robust.Shared/Random/RandomExtensions.cs b/Robust.Shared/Random/RandomExtensions.cs index a78725470fb..079e1981586 100644 --- a/Robust.Shared/Random/RandomExtensions.cs +++ b/Robust.Shared/Random/RandomExtensions.cs @@ -1,160 +1,263 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Robust.Shared.Collections; using Robust.Shared.Maths; using Robust.Shared.Utility; -namespace Robust.Shared.Random +namespace Robust.Shared.Random; + +public static class RandomExtensions { - public static class RandomExtensions - { - /// - /// Generate a random number from a normal (gaussian) distribution. - /// - /// The random object to generate the number from. - /// The average or "center" of the normal distribution. - /// The standard deviation of the normal distribution. - public static double NextGaussian(this IRobustRandom random, double μ = 0, double σ = 1) - { - return random.GetRandom().NextGaussian(μ, σ); - } + /// + /// Generate a random number from a normal (gaussian) distribution. + /// + /// The random object to generate the number from. + /// The average or "center" of the normal distribution. + /// The standard deviation of the normal distribution. + public static double NextGaussian(this IRobustRandom random, double μ = 0, double σ = 1) + { + return random.GetRandom().NextGaussian(μ, σ); + } - public static T Pick(this IRobustRandom random, IReadOnlyList list) - { - var index = random.Next(list.Count); - return list[index]; - } + /// Picks a random element from a collection. + public static T Pick(this IRobustRandom random, IReadOnlyList list) + { + var index = random.Next(list.Count); + return list[index]; + } - public static ref T Pick(this IRobustRandom random, ValueList list) - { - var index = random.Next(list.Count); - return ref list[index]; - } + /// Picks a random element from a collection. + public static ref T Pick(this IRobustRandom random, ValueList list) + { + var index = random.Next(list.Count); + return ref list[index]; + } - public static ref T Pick(this System.Random random, ValueList list) + /// Picks a random element from a collection. + public static ref T Pick(this System.Random random, ValueList list) + { + var index = random.Next(list.Count); + return ref list[index]; + } + + /// Picks a random element from a collection. + /// + /// This is O(n). + /// + public static T Pick(this IRobustRandom random, IReadOnlyCollection collection) + { + var index = random.Next(collection.Count); + var i = 0; + foreach (var t in collection) { - var index = random.Next(list.Count); - return ref list[index]; + if (i++ == index) + { + return t; + } } - /// Picks a random element from a collection. - /// - /// This is O(n). - /// - public static T Pick(this IRobustRandom random, IReadOnlyCollection collection) + throw new UnreachableException("This should be unreachable!"); + } + + /// + /// Picks a random element from a list, removes it from list and returns it. + /// This is O(n) as it preserves the order of other items in the list. + /// + public static T PickAndTake(this IRobustRandom random, IList list) + { + var index = random.Next(list.Count); + var element = list[index]; + list.RemoveAt(index); + return element; + } + + /// + /// Picks a random element from a set and returns it. + /// This is O(n) as it has to iterate the collection until the target index. + /// + public static T Pick(this System.Random random, ICollection collection) + { + var index = random.Next(collection.Count); + var i = 0; + foreach (var t in collection) { - var index = random.Next(collection.Count); - var i = 0; - foreach (var t in collection) + if (i++ == index) { - if (i++ == index) - { - return t; - } + return t; } - - throw new UnreachableException("This should be unreachable!"); } - public static T PickAndTake(this IRobustRandom random, IList list) + throw new UnreachableException("This should be unreachable!"); + } + + /// + /// Picks a random from a collection then removes it and returns it. + /// This is O(n) as it has to iterate the collection until the target index. + /// + public static T PickAndTake(this System.Random random, ICollection set) + { + var tile = Pick(random, set); + set.Remove(tile); + return tile; + } + + /// + /// Generate a random number from a normal (gaussian) distribution. + /// + /// The random object to generate the number from. + /// The average or "center" of the normal distribution. + /// The standard deviation of the normal distribution. + public static double NextGaussian(this System.Random random, double μ = 0, double σ = 1) + { + // https://stackoverflow.com/a/218600 + var α = random.NextDouble(); + var β = random.NextDouble(); + + var randStdNormal = Math.Sqrt(-2.0 * Math.Log(α)) * Math.Sin(2.0 * Math.PI * β); + + return μ + σ * randStdNormal; + } + + public static Angle NextAngle(this System.Random random) => NextFloat(random) * MathF.Tau; + + public static Angle NextAngle(this System.Random random, Angle minAngle, Angle maxAngle) + { + DebugTools.Assert(minAngle < maxAngle); + return minAngle + (maxAngle - minAngle) * random.NextDouble(); + } + + public static float NextFloat(this IRobustRandom random) + { + // This is pretty much the CoreFX implementation. + // So credits to that. + // Except using float instead of double. + return random.Next() * 4.6566128752458E-10f; + } + + public static float NextFloat(this System.Random random) + { + return random.Next() * 4.6566128752458E-10f; + } + + /// + /// Have a certain chance to return a boolean. + /// + /// The random instance to run on. + /// The chance to pass, from 0 to 1. + public static bool Prob(this IRobustRandom random, float chance) + { + DebugTools.Assert(chance <= 1 && chance >= 0, $"Chance must be in the range 0-1. It was {chance}."); + + return random.NextDouble() < chance; + } + + /// + /// Get set amount of random items from a collection. + /// If is false and + /// is smaller then - returns shuffled clone. + /// If is empty, and/or is 0, returns empty. + /// + /// Instance of random to invoke upon. + /// Collection from which items should be picked. + /// Number of random items to be picked. + /// If true, items are allowed to be picked more than once. + public static T[] GetItems(this IRobustRandom random, IList source, int count, bool allowDuplicates = true) + { + if (source.Count == 0 || count <= 0) + return Array.Empty(); + + if (allowDuplicates == false && count >= source.Count) { - var index = random.Next(list.Count); - var element = list[index]; - list.RemoveAt(index); - return element; + var arr = source.ToArray(); + random.Shuffle(arr); + return arr; } - /// - /// Picks a random element from a set and returns it. - /// This is O(n) as it has to iterate the collection until the target index. - /// - public static T Pick(this System.Random random, ICollection collection) + var sourceCount = source.Count; + var result = new T[count]; + + if (allowDuplicates) { - var index = random.Next(collection.Count); - var i = 0; - foreach (var t in collection) + for (var i = 0; i < count; i++) { - if (i++ == index) - { - return t; - } + result[i] = source[random.Next(sourceCount)]; } - throw new UnreachableException("This should be unreachable!"); + return result; } - /// - /// Picks a random from a collection then removes it and returns it. - /// This is O(n) as it has to iterate the collection until the target index. - /// - public static T PickAndTake(this System.Random random, ICollection set) + var indices = sourceCount <= 1024 ? stackalloc int[sourceCount] : new int[sourceCount]; + for (var i = 0; i < sourceCount; i++) { - var tile = Pick(random, set); - set.Remove(tile); - return tile; + indices[i] = i; } - /// - /// Generate a random number from a normal (gaussian) distribution. - /// - /// The random object to generate the number from. - /// The average or "center" of the normal distribution. - /// The standard deviation of the normal distribution. - public static double NextGaussian(this System.Random random, double μ = 0, double σ = 1) + for (var i = 0; i < count; i++) { - // https://stackoverflow.com/a/218600 - var α = random.NextDouble(); - var β = random.NextDouble(); + var j = random.Next(sourceCount - i); + result[i] = source[indices[j]]; + indices[j] = indices[sourceCount - i - 1]; + } - var randStdNormal = Math.Sqrt(-2.0 * Math.Log(α)) * Math.Sin(2.0 * Math.PI * β); + return result; + } - return μ + σ * randStdNormal; - } + /// + public static T[] GetItems(this IRobustRandom random, ValueList source, int count, bool allowDuplicates = true) + { + return GetItems(random, source.Span, count, allowDuplicates); + } - public static Angle NextAngle(this System.Random random) => NextFloat(random) * MathF.Tau; + /// + public static T[] GetItems(this IRobustRandom random, T[] source, int count, bool allowDuplicates = true) + { + return GetItems(random, source.AsSpan(), count, allowDuplicates); + } - public static Angle NextAngle(this System.Random random, Angle minAngle, Angle maxAngle) - { - DebugTools.Assert(minAngle < maxAngle); - return minAngle + (maxAngle - minAngle) * random.NextDouble(); - } + /// + public static T[] GetItems(this IRobustRandom random, Span source, int count, bool allowDuplicates = true) + { + if (source.Length == 0 || count <= 0) + return Array.Empty(); - public static float NextFloat(this IRobustRandom random) + if (allowDuplicates == false && count >= source.Length) { - // This is pretty much the CoreFX implementation. - // So credits to that. - // Except using float instead of double. - return random.Next() * 4.6566128752458E-10f; + var arr = source.ToArray(); + random.Shuffle(arr); + return arr; } - public static float NextFloat(this System.Random random) + var sourceCount = source.Length; + var result = new T[count]; + + if (allowDuplicates) { - return random.Next() * 4.6566128752458E-10f; + // TODO RANDOM consider just using System.Random.GetItems() + // However, the different implementations might mean that lists & arrays shuffled using the same seed + // generate different results, which might be undesirable? + for (var i = 0; i < count; i++) + { + result[i] = source[random.Next(sourceCount)]; + } + + return result; } - /// - /// Have a certain chance to return a boolean. - /// - /// The random instance to run on. - /// The chance to pass, from 0 to 1. - public static bool Prob(this IRobustRandom random, float chance) + var indices = sourceCount <= 1024 ? stackalloc int[sourceCount] : new int[sourceCount]; + for (var i = 0; i < sourceCount; i++) { - DebugTools.Assert(chance <= 1 && chance >= 0, $"Chance must be in the range 0-1. It was {chance}."); - - return random.NextDouble() < chance; + indices[i] = i; } - internal static void Shuffle(Span array, System.Random random) + for (var i = 0; i < count; i++) { - var n = array.Length; - while (n > 1) - { - n--; - var k = random.Next(n + 1); - (array[k], array[n]) = - (array[n], array[k]); - } + var j = random.Next(sourceCount - i); + result[i] = source[indices[j]]; + indices[j] = indices[sourceCount - i - 1]; } + + return result; } } diff --git a/Robust.Shared/Random/RobustRandom.cs b/Robust.Shared/Random/RobustRandom.cs index 28981c76fb6..4f447b2e64c 100644 --- a/Robust.Shared/Random/RobustRandom.cs +++ b/Robust.Shared/Random/RobustRandom.cs @@ -1,58 +1,65 @@ using System; using Robust.Shared.Utility; -namespace Robust.Shared.Random +namespace Robust.Shared.Random; + +/// +/// Wrapper for . +/// +/// +/// This should not contain any logic, not directly related to calling specific methods of . +/// To write additional logic, attached to random roll, please create interface-implemented methods on +/// or add it to . +/// +public sealed class RobustRandom : IRobustRandom { - public sealed class RobustRandom : IRobustRandom - { - private System.Random _random = new(); - - public System.Random GetRandom() => _random; - - public void SetSeed(int seed) - { - _random = new(seed); - } - - public float NextFloat() - { - return _random.NextFloat(); - } - - public int Next() - { - return _random.Next(); - } - - public int Next(int minValue, int maxValue) - { - return _random.Next(minValue, maxValue); - } - - public TimeSpan Next(TimeSpan minTime, TimeSpan maxTime) - { - DebugTools.Assert(minTime < maxTime); - return minTime + (maxTime - minTime) * _random.NextDouble(); - } - - public TimeSpan Next(TimeSpan maxTime) - { - return Next(TimeSpan.Zero, maxTime); - } - - public int Next(int maxValue) - { - return _random.Next(maxValue); - } - - public double NextDouble() - { - return _random.NextDouble(); - } - - public void NextBytes(byte[] buffer) - { - _random.NextBytes(buffer); - } + private System.Random _random = new(); + + public System.Random GetRandom() => _random; + + public void SetSeed(int seed) + { + _random = new(seed); + } + + public float NextFloat() + { + return _random.NextFloat(); + } + + public int Next() + { + return _random.Next(); + } + + public int Next(int minValue, int maxValue) + { + return _random.Next(minValue, maxValue); + } + + public TimeSpan Next(TimeSpan minTime, TimeSpan maxTime) + { + DebugTools.Assert(minTime < maxTime); + return minTime + (maxTime - minTime) * _random.NextDouble(); + } + + public TimeSpan Next(TimeSpan maxTime) + { + return Next(TimeSpan.Zero, maxTime); + } + + public int Next(int maxValue) + { + return _random.Next(maxValue); + } + + public double NextDouble() + { + return _random.NextDouble(); + } + + public void NextBytes(byte[] buffer) + { + _random.NextBytes(buffer); } } diff --git a/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs b/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs new file mode 100644 index 00000000000..15c66fa0825 --- /dev/null +++ b/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Moq; +using NUnit.Framework; +using Robust.Shared.Collections; +using Robust.Shared.Random; + +// ReSharper disable AccessToStaticMemberViaDerivedType + +namespace Robust.UnitTesting.Shared.Random; + +/// Instantiable tests for . +[TestFixture] +public sealed class RandomExtensionsGetItemsWithListTests : RandomExtensionsTests> +{ + /// + protected override IList CreateCollection() + => new List(CollectionForTests); + + /// + protected override IReadOnlyCollection Invoke(IList collection, int count, bool allowDuplicates) + => _underlyingRandom.GetItems(collection, count, allowDuplicates); +} + +/// Instantiable tests for . +[TestFixture] +public sealed class RandomExtensionsGetItemsWithSpanTests : RandomExtensionsTests +{ + /// + protected override string[] CreateCollection() + => CollectionForTests; + + /// + protected override IReadOnlyCollection Invoke(string[] collection, int count, bool allowDuplicates) + { + var span = new Span(collection); + return _underlyingRandom.GetItems(span, count, allowDuplicates) + .ToArray(); + } +} + +/// Instantiable tests for . +[TestFixture] +public sealed class RandomExtensionsGetItemsWithValueListTests : RandomExtensionsTests> +{ + /// + protected override ValueList CreateCollection() + => new ValueList(CollectionForTests); + + /// + protected override IReadOnlyCollection Invoke(ValueList collection, int count, bool allowDuplicates) + => _underlyingRandom.GetItems(collection, count, allowDuplicates) + .ToArray(); +} + +[TestFixture] +public abstract class RandomExtensionsTests +{ + protected IRobustRandom _underlyingRandom = default!; + + protected readonly string[] CollectionForTests = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }; + + private T _collection = default!; + + private int Count => CollectionForTests.Length; + + [SetUp] + public void Setup() + { + _underlyingRandom = Mock.Of(); + _collection = CreateCollection(); + } + + [Test] + public void GetItems_PickOneFromList_ReturnOfRandomizedIndex() + { + // Arrange + Mock.Get(_underlyingRandom) + .Setup(x => x.Next(Count)) + .Returns(8); + + // Act + var result = Invoke(_collection, 1, true); + + // Assert + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.Single(), Is.EqualTo("8")); + } + + + [Test] + public void GetItems_PickOneFromListWithoutDuplicates_ReturnOfRandomizedIndex() + { + // Arrange + Mock.Get(_underlyingRandom) + .Setup(x => x.Next(Count)) + .Returns(8); + + // Act + var result = Invoke(_collection, 1, allowDuplicates: false); + + // Assert + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.Single(), Is.EqualTo("8")); + } + + [Test] + public void GetItems_PickSomeFromList_ReturnOfRandomizedIndex() + { + // Arrange + Mock.Get(_underlyingRandom) + .SetupSequence(x => x.Next(Count)) + .Returns(8) + .Returns(3) + .Returns(2); + + // Act + var result = Invoke(_collection, 3, true); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "8", "3", "2" })); + } + + [Test] + public void GetItems_PickSomeFromListWhileRollingDuplicates_ReturnWithDuplicates() + { + // Arrange + Mock.Get(_underlyingRandom) + .SetupSequence(x => x.Next(Count)) + .Returns(8) + .Returns(2) + .Returns(2) + .Returns(2); + + // Act + var result = Invoke(_collection, 4, allowDuplicates: true); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "8", "2", "2", "2" })); + } + + [Test] + public void GetItems_PickSameAmountAsOriginalCollection_ReturnWithDuplicates() + { + // Arrange + Mock.Get(_underlyingRandom) + .SetupSequence(x => x.Next(Count)) + .Returns(0) + .Returns(2) + .Returns(2) + .Returns(4) + .Returns(6) + .Returns(5) + .Returns(4) + .Returns(3) + .Returns(2) + .Returns(1) + .Returns(0); + + // Act + var result = Invoke(_collection, 11, allowDuplicates: true); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "0", "2", "2", "4", "6", "5", "4", "3", "2", "1", "0" })); + } + + [Test] + public void GetItems_PickMoreItemsThenOriginalCollectionHave_ReturnWithDuplicates() + { + // Arrange + Mock.Get(_underlyingRandom) + .SetupSequence(x => x.Next(Count)) + .Returns(0) + .Returns(2) + .Returns(2) + .Returns(4) + .Returns(6) + .Returns(5) + .Returns(4) + .Returns(3) + .Returns(2) + .Returns(1) + .Returns(9) + .Returns(9); + + // Act + var result = Invoke(_collection, 12, allowDuplicates: true); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "0", "2", "2", "4", "6", "5", "4", "3", "2", "1", "9", "9" })); + } + + [Test] + public void GetItems_PickSomeItemsWithoutDuplicates_ReturnWithoutDuplicates() + { + // Arrange + var mock = Mock.Get(_underlyingRandom); + mock.Setup(x => x.Next(Count)).Returns(1); + mock.Setup(x => x.Next(Count - 1)).Returns(1); + mock.Setup(x => x.Next(Count - 2)).Returns(6); + + // Act + var result = Invoke(_collection, 3, allowDuplicates: false); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "1", "10", "6" })); + } + + [Test] + public void GetItems_PickOneLessItemsThenOriginalCollectionHaveWithoutDuplicates_ReturnWithoutDuplicates() + { + // Arrange + var mock = Mock.Get(_underlyingRandom); + mock.Setup(x => x.Next(Count)).Returns(1); + mock.Setup(x => x.Next(Count - 1)).Returns(1); + mock.Setup(x => x.Next(Count - 2)).Returns(6); + mock.Setup(x => x.Next(Count - 3)).Returns(6); + mock.Setup(x => x.Next(Count - 4)).Returns(3); + mock.Setup(x => x.Next(Count - 5)).Returns(4); + mock.Setup(x => x.Next(Count - 6)).Returns(4); + mock.Setup(x => x.Next(Count - 7)).Returns(3); + mock.Setup(x => x.Next(Count - 8)).Returns(1); + mock.Setup(x => x.Next(Count - 9)).Returns(1); + + // Act + var result = Invoke(_collection, 10, allowDuplicates: false); + + // Assert + Assert.That(result, Is.EqualTo(new[] { "1", "10", "6", "8", "3", "4", "5", "7", "9", "2" })); + } + + [Test] + public void GetItems_PickAllItemsWithoutDuplicates_ReturnOriginalCollectionShuffledWithoutDuplicates() + { + // Arrange + var shuffled = new[] { "9", "0", "4", "2", "3", "7", "5", "8", "6", "10", "1" }; + Mock.Get(_underlyingRandom) + .Setup(x => x.Shuffle(It.IsAny>())) + .Callback>(x => + { + for (int i = 0; i < shuffled.Length; i++) + { + x[i] = shuffled[i]; + } + }); + + // Act + var result = Invoke(_collection, 11, allowDuplicates: false); + + // Assert + Assert.That(result, Is.EqualTo(shuffled)); + Mock.Get(_underlyingRandom).Verify(x => x.Next(It.IsAny()), Times.Never); + } + + [Test] + public void GetItems_PickMoreItemsThenOriginalHaveWithoutDuplicates_ReturnOriginalShuffledOriginalCollectionWithoutDuplicates() + { + // Arrange + var shuffled = new[] { "9", "0", "4", "2", "3", "7", "5", "8", "6", "10", "1" }; + Mock.Get(_underlyingRandom) + .Setup(x => x.Shuffle(It.IsAny>())) + .Callback>(x => + { + for (int i = 0; i < shuffled.Length; i++) + { + x[i] = shuffled[i]; + } + }); + + // Act + var result = Invoke(_collection, 30, allowDuplicates: false); + + // Assert + Assert.That(result, Is.EqualTo(shuffled)); + Mock.Get(_underlyingRandom).Verify(x => x.Next(It.IsAny()), Times.Never); + } + + /// Create concrete collection for tests. + protected abstract T CreateCollection(); + + /// Invoke method under test. Separate implementation types will have different overrides to be tested. + protected abstract IReadOnlyCollection Invoke(T collection, int count, bool allowDuplicates); +} From 30eed7957ff41e3e15f3c3f75163152efba407a0 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 31 Mar 2024 14:15:30 +1300 Subject: [PATCH 059/130] Add an `EffectiveCurTime` for physics subticks (#5014) * Add an `EffectiveCurTime` for physics subticks * Release notes --- RELEASE-NOTES.md | 1 + .../Systems/SharedPhysicsSystem.Components.cs | 2 +- Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index feb9992ece6..201595c5d32 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections. +* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step. ### Bugfixes diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 6b15cecf17e..675e8d38fa9 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -737,7 +737,7 @@ public Box2 GetHardAABB(EntityUid uid, FixturesComponent? manager = null, Physic public (int Layer, int Mask) GetHardCollision(EntityUid uid, FixturesComponent? manager = null) { - if (!Resolve(uid, ref manager)) + if (!_fixturesQuery.Resolve(uid, ref manager, false)) { return (0, 0); } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 416df5ba3c7..61698717b30 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -13,6 +13,7 @@ using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Threading; +using Robust.Shared.Timing; using Robust.Shared.Utility; using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; @@ -60,6 +61,12 @@ public abstract partial class SharedPhysicsSystem : EntitySystem private int _substeps; + /// + /// A variation of that takes into account the current physics sub-step. + /// Useful for some entities that need to interpolate their positions during sub-steps. + /// + public TimeSpan? EffectiveCurTime; + public bool MetricsEnabled { get; protected set; } private EntityQuery _fixturesQuery; @@ -284,6 +291,7 @@ protected void SimulateWorld(float deltaTime, bool prediction) { var frameTime = deltaTime / _substeps; + EffectiveCurTime = _gameTiming.CurTime; for (int i = 0; i < _substeps; i++) { var updateBeforeSolve = new PhysicsUpdateBeforeSolveEvent(prediction, frameTime); @@ -323,7 +331,11 @@ protected void SimulateWorld(float deltaTime, bool prediction) FinalStep(comp); } } + + EffectiveCurTime += TimeSpan.FromSeconds(frameTime); } + + EffectiveCurTime = null; } protected virtual void FinalStep(PhysicsMapComponent component) From b28b5ed09b361c4f2da5dd9c3e392b79e6b23c51 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sat, 30 Mar 2024 21:16:44 -0400 Subject: [PATCH 060/130] Version: 217.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 71f6420a89f..aa60d50aa79 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 217.0.0 + 217.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 201595c5d32..df621c2c06e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,12 +39,11 @@ END TEMPLATE--> ### New features -* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections. -* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step. +*None yet* ### Bugfixes -* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state. +*None yet* ### Other @@ -52,6 +51,22 @@ END TEMPLATE--> ### Internal +*None yet* + + +## 217.1.0 + +### New features + +* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections. +* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step. + +### Bugfixes + +* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state. + +### Internal + * `Shuffle(Span, System.Random)` has been removed, just use the builtin method. From 91d3f67a94e3b7db54882d089a8a969ca31f8c16 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 31 Mar 2024 16:48:37 +1300 Subject: [PATCH 061/130] Make `IntersectRayWithPredicate` ignore non-hard fixtures (#5017) --- Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index a6d9855ad59..6ec9d3fe6cd 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -291,7 +291,7 @@ public IEnumerable IntersectRayWithPredicate(MapId mapId if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; - if (!proxy.Body.Hard) + if (!proxy.Fixture.Hard) return true; if (predicate.Invoke(proxy.Entity, state) == true) @@ -319,7 +319,7 @@ public IEnumerable IntersectRayWithPredicate(MapId mapId if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; - if (!proxy.Body.Hard) + if (!proxy.Fixture.Hard) return true; if (predicate.Invoke(proxy.Entity, state) == true) @@ -567,6 +567,7 @@ public bool TryGetNearest(EntityUid uid, MapCoordinates coordinates, // No requirement on collision being enabled so chainshapes will fail foreach (var fixtureA in manager.Fixtures.Values) { + // We ignore non-hard fixtures if there is at least one hard fixture (i.e., if the body is hard) if (body.Hard && !fixtureA.Hard) continue; From 92d7f2723a00b35fcea330e9948bfcc064065840 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 31 Mar 2024 14:54:01 +1100 Subject: [PATCH 062/130] Add ComponentRegistry helpers to EntityManager (#4934) * Add ComponentRegistry helpers to EntityManager * Metadata here too * Remove object cast * knock * Plural --- RELEASE-NOTES.md | 2 +- .../GameObjects/EntityManager.Components.cs | 55 ++++++++++++++++ .../GameObjects/IEntityManager.Components.cs | 21 ++++++ .../EntityManager_Components_Tests.cs | 65 ++++++++++++++++++- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index df621c2c06e..d3545867edf 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes. ### Bugfixes diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 6a4906676df..6c6ba2d36d1 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -11,6 +11,7 @@ using Robust.Shared.Log; using Robust.Shared.Physics.Components; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; #if EXCEPTION_TOLERANCE @@ -176,6 +177,60 @@ public void StartComponents(EntityUid uid) } } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddComponents(EntityUid target, EntityPrototype prototype, bool removeExisting = true) + { + AddComponents(target, prototype.Components, removeExisting); + } + + /// + public void AddComponents(EntityUid target, ComponentRegistry registry, bool removeExisting = true) + { + if (registry.Count == 0) + return; + + var metadata = MetaQuery.GetComponent(target); + + foreach (var (name, entry) in registry) + { + var reg = _componentFactory.GetRegistration(name); + + if (HasComponent(target, reg.Type)) + { + if (!removeExisting) + continue; + + RemoveComponent(target, reg.Type, metadata); + } + + var comp = _componentFactory.GetComponent(reg); + _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); + AddComponent(target, comp, metadata: metadata); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveComponents(EntityUid target, EntityPrototype prototype) + { + RemoveComponents(target, prototype.Components); + } + + /// + public void RemoveComponents(EntityUid target, ComponentRegistry registry) + { + if (registry.Count == 0) + return; + + var metadata = MetaQuery.GetComponent(target); + + foreach (var entry in registry.Values) + { + RemoveComponent(target, entry.Component.GetType(), metadata); + } + } + public IComponent AddComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null) { var newComponent = _componentFactory.GetComponent(netId); diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 75e1472b32a..550a9f372f2 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Robust.Shared.GameObjects @@ -38,6 +39,26 @@ public partial interface IEntityManager /// int Count(Type component); + /// + /// Adds the specified components from the + /// + void AddComponents(EntityUid target, EntityPrototype prototype, bool removeExisting = true); + + /// + /// Adds the specified registry components to the target entity. + /// + void AddComponents(EntityUid target, ComponentRegistry registry, bool removeExisting = true); + + /// + /// Removes the specified entity prototype components from the target entity. + /// + void RemoveComponents(EntityUid target, EntityPrototype prototype); + + /// + /// Removes the specified registry components from the target entity. + /// + void RemoveComponents(EntityUid target, ComponentRegistry registry); + /// /// Adds a Component type to an entity. If the entity is already Initialized, the component will /// automatically be Initialized and Started. diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs index 6b91432452f..46064fdeb85 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs @@ -1,11 +1,14 @@ +using System.Collections.Generic; using System.Linq; using System.Numerics; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared.GameObjects @@ -15,6 +18,64 @@ public sealed partial class EntityManager_Components_Tests { private static readonly EntityCoordinates DefaultCoords = new(EntityUid.FirstUid, Vector2.Zero); + private const string DummyLoad = @" + - type: entity + id: DummyLoad + name: weh + components: + - type: Joint + - type: Physics +"; + + [Test] + public void AddRegistryComponentTest() + { + var sim = RobustServerSimulation + .NewSimulation() + .RegisterPrototypes(fac => fac.LoadString(DummyLoad)) + .InitializeInstance(); + + sim.AddMap(1); + + var entMan = sim.Resolve(); + var protoManager = sim.Resolve(); + + var entity = entMan.SpawnEntity(null, DefaultCoords); + Assert.That(!entMan.HasComponent(entity)); + var proto = protoManager.Index("DummyLoad"); + + entMan.AddComponents(entity, proto); + Assert.Multiple(() => + { + Assert.That(entMan.HasComponent(entity)); + Assert.That(entMan.HasComponent(entity)); + }); + } + + [Test] + public void RemoveRegistryComponentTest() + { + var sim = RobustServerSimulation + .NewSimulation() + .RegisterPrototypes(fac => fac.LoadString(DummyLoad)) + .InitializeInstance(); + + sim.AddMap(1); + + var entMan = sim.Resolve(); + var protoManager = sim.Resolve(); + + var entity = entMan.SpawnEntity("DummyLoad", DefaultCoords); + var proto = protoManager.Index("DummyLoad"); + + entMan.RemoveComponents(entity, proto); + Assert.Multiple(() => + { + Assert.That(!entMan.HasComponent(entity)); + Assert.That(!entMan.HasComponent(entity)); + }); + } + [Test] public void AddComponentTest() { From 36eb857b55e333e48f17a87683775f74464250d3 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:02:18 +1100 Subject: [PATCH 063/130] Fix TimeSpan addition (#5018) Rider thinks nullable timespan += is fine but it no fine. --- Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 61698717b30..5e338ba00a2 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -332,7 +332,7 @@ protected void SimulateWorld(float deltaTime, bool prediction) } } - EffectiveCurTime += TimeSpan.FromSeconds(frameTime); + EffectiveCurTime = EffectiveCurTime.Value + TimeSpan.FromSeconds(frameTime); } EffectiveCurTime = null; From 9e2ab2a917df2a57208c050d479081adfd8897b1 Mon Sep 17 00:00:00 2001 From: faint <46868845+ficcialfaint@users.noreply.github.com> Date: Sun, 31 Mar 2024 07:25:31 +0300 Subject: [PATCH 064/130] Double-clicking in LineEdit (#4831) * Double-clicking in LineEdit epic * Fix IGameTiming dependency * remove iocmanager.resolves * test fix * Update Robust.Client/UserInterface/Controls/LineEdit.cs Co-authored-by: ShadowCommander * review --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: ShadowCommander Co-authored-by: metalgearsloth --- .../UserInterface/Controls/LineEdit.cs | 25 +++++++++++++++++++ Robust.Shared/CVars.cs | 16 ++++++++++++ .../UserInterface/Controls/LineEditTest.cs | 5 +++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Robust.Client/UserInterface/Controls/LineEdit.cs b/Robust.Client/UserInterface/Controls/LineEdit.cs index ec3850a1f3d..71ff8398f76 100644 --- a/Robust.Client/UserInterface/Controls/LineEdit.cs +++ b/Robust.Client/UserInterface/Controls/LineEdit.cs @@ -4,6 +4,8 @@ using System.Text; using JetBrains.Annotations; using Robust.Client.Graphics; +using Robust.Shared; +using Robust.Shared.Configuration; using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -20,6 +22,8 @@ namespace Robust.Client.UserInterface.Controls public class LineEdit : Control { [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IConfigurationManager _cfgManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; private const float MouseScrollDelay = 0.001f; @@ -46,6 +50,9 @@ public class LineEdit : Control private bool _mouseSelectingText; private float _lastMousePosition; + private TimeSpan? _lastClickTime; + private Vector2? _lastClickPosition; + private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null; public event Action? OnTextChanged; @@ -685,8 +692,26 @@ async void DoPaste() args.Handle(); } } + // Double-clicking. Clicks delay should be <= 250ms and the distance < 10 pixels. + else if (args.Function == EngineKeyFunctions.UIClick && _lastClickPosition != null && _lastClickTime != null + && _timing.RealTime - _lastClickTime <= TimeSpan.FromMilliseconds(_cfgManager.GetCVar(CVars.DoubleClickDelay)) + && (_lastClickPosition.Value - args.PointerLocation.Position).IsShorterThan(_cfgManager.GetCVar(CVars.DoubleClickRange))) + { + _lastClickTime = _timing.RealTime; + _lastClickPosition = args.PointerLocation.Position; + + _lastMousePosition = args.RelativePosition.X; + + _selectionStart = TextEditShared.PrevWordPosition(_text, GetIndexAtPos(args.RelativePosition.X)); + _cursorPosition = TextEditShared.EndWordPosition(_text, GetIndexAtPos(args.RelativePosition.X)); + + args.Handle(); + } else { + _lastClickTime = _timing.RealTime; + _lastClickPosition = args.PointerLocation.Position; + _mouseSelectingText = true; _lastMousePosition = args.RelativePosition.X; diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 9bfa376f9c4..1656984e3b8 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -886,6 +886,22 @@ protected CVars() public static readonly CVarDef RenderFOVColor = CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY); + /* + * CONTROLS + */ + + /// + /// Milliseconds to wait to consider double-click delays. + /// + public static readonly CVarDef DoubleClickDelay = + CVarDef.Create("controls.double_click_delay", 250, CVar.ARCHIVE | CVar.CLIENTONLY); + + /// + /// Range in pixels for double-clicks + /// + public static readonly CVarDef DoubleClickRange = + CVarDef.Create("controls.double_click_range", 10, CVar.ARCHIVE | CVar.CLIENTONLY); + /* * DISPLAY */ diff --git a/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs b/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs index 163ac149d54..d102001ddc5 100644 --- a/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs +++ b/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using Robust.Client.Graphics; using Robust.Client.Graphics.Clyde; @@ -6,6 +6,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.IoC; +using Robust.Shared.Timing; namespace Robust.UnitTesting.Client.UserInterface.Controls { @@ -18,12 +19,14 @@ public void Setup() { var uiMgr = new Mock(); var clyde = new ClydeHeadless(); + var timing = new Mock(); var deps = IoCManager.InitThread(); deps.Clear(); deps.RegisterInstance(uiMgr.Object); deps.RegisterInstance(uiMgr.Object); deps.RegisterInstance(clyde); + deps.RegisterInstance(timing.Object); deps.BuildGraph(); } From 99c5b0ad08351af347db3a122373f2c4482e94dc Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 31 Mar 2024 15:29:26 +1100 Subject: [PATCH 065/130] Version: 217.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index aa60d50aa79..b7f15712321 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 217.1.0 + 217.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d3545867edf..ff4fe042ba6 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes. +*None yet* ### Bugfixes @@ -54,6 +54,19 @@ END TEMPLATE--> *None yet* +## 217.2.0 + +### New features + +* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes. +* Add double-clicking to LineEdit. + +### Bugfixes + +* Properly ignore non-hard fixtures for IntersectRayWithPredicate. +* Fix nullable TimeSpan addition on some platforms. + + ## 217.1.0 ### New features From fdc1de24300d7597a84c41704d2d7e5e8c7a0357 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 31 Mar 2024 16:58:15 +1100 Subject: [PATCH 066/130] Fix LineEdit tests (#5021) --- .../UserInterface/Controls/LineEditTest.cs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs b/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs index d102001ddc5..825b146b1f3 100644 --- a/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs +++ b/Robust.UnitTesting/Client/UserInterface/Controls/LineEditTest.cs @@ -1,34 +1,16 @@ -using Moq; using NUnit.Framework; -using Robust.Client.Graphics; -using Robust.Client.Graphics.Clyde; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; -using Robust.Shared.IoC; -using Robust.Shared.Timing; +using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Client.UserInterface.Controls { [TestFixture] [TestOf(typeof(LineEdit))] - public sealed class LineEditTest + public sealed class LineEditTest : RobustUnitTest { - [OneTimeSetUp] - public void Setup() - { - var uiMgr = new Mock(); - var clyde = new ClydeHeadless(); - var timing = new Mock(); - - var deps = IoCManager.InitThread(); - deps.Clear(); - deps.RegisterInstance(uiMgr.Object); - deps.RegisterInstance(uiMgr.Object); - deps.RegisterInstance(clyde); - deps.RegisterInstance(timing.Object); - deps.BuildGraph(); - } + public override UnitTestProject Project => UnitTestProject.Client; [Test] public void TestRuneBackspace() From 2b55d39e5177bebfc7683754df96809c5464fa04 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 31 Mar 2024 19:01:03 +1300 Subject: [PATCH 067/130] Make various ValueList enumerators use spans (#5019) * Make various ValueList enumerators use spans * Remove reference to EntityEventBus.OrderedRegistration --------- Co-authored-by: metalgearsloth --- .../ValueListEnumerationBenchmarks.cs | 83 +++++++++++++++++++ .../EntitySystems/ContainerSystem.cs | 6 +- .../UserInterface/Control.Animations.cs | 2 +- .../GameObjects/EntityEventBus.Broadcast.cs | 2 +- .../GameObjects/EntityEventBus.Ordering.cs | 4 +- .../Systems/EntityLookup.Queries.cs | 2 +- .../EntityLookupSystem.ComponentQueries.cs | 2 +- .../Systems/SharedGridFixtureSystem.cs | 4 +- .../Physics/Systems/FixtureSystem.cs | 4 +- 9 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 Robust.Benchmarks/Collections/ValueListEnumerationBenchmarks.cs diff --git a/Robust.Benchmarks/Collections/ValueListEnumerationBenchmarks.cs b/Robust.Benchmarks/Collections/ValueListEnumerationBenchmarks.cs new file mode 100644 index 00000000000..69681f1294d --- /dev/null +++ b/Robust.Benchmarks/Collections/ValueListEnumerationBenchmarks.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Robust.Shared.Analyzers; +using Robust.Shared.Collections; + +namespace Robust.Benchmarks.Collections; + +[Virtual] +public class ValueListEnumerationBenchmarks +{ + [Params(4, 16, 64)] + public int N { get; set; } + + private sealed class Data(int i) + { + public readonly int I = i; + } + + private ValueList _valueList; + private Data[] _array = default!; + + [GlobalSetup] + public void Setup() + { + var list = new List(N); + for (var i = 0; i < N; i++) + { + list.Add(new(i)); + } + + _array = list.ToArray(); + _valueList = new(list.ToArray()); + } + + [Benchmark] + public int ValueList() + { + var total = 0; + foreach (var ev in _valueList) + { + total += ev.I; + } + + return total; + } + + [Benchmark] + public int ValueListSpan() + { + var total = 0; + foreach (var ev in _valueList.Span) + { + total += ev.I; + } + + return total; + } + + [Benchmark] + public int Array() + { + var total = 0; + foreach (var ev in _array) + { + total += ev.I; + } + + return total; + } + + [Benchmark] + public int Span() + { + var total = 0; + foreach (var ev in _array.AsSpan()) + { + total += ev.I; + } + + return total; + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs index 973b601e86d..661d412cdfd 100644 --- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs @@ -107,7 +107,7 @@ private void HandleComponentState(EntityUid uid, ContainerManagerComponent compo toDelete.Add(id); } - foreach (var dead in toDelete) + foreach (var dead in toDelete.Span) { component.Containers.Remove(dead); } @@ -142,7 +142,7 @@ private void HandleComponentState(EntityUid uid, ContainerManagerComponent compo toRemove.Add(entity); } - foreach (var entity in toRemove) + foreach (var entity in toRemove.Span) { Remove( (entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)), @@ -162,7 +162,7 @@ private void HandleComponentState(EntityUid uid, ContainerManagerComponent compo removedExpected.Add(netEntity); } - foreach (var entityUid in removedExpected) + foreach (var entityUid in removedExpected.Span) { RemoveExpectedEntity(entityUid, out _); } diff --git a/Robust.Client/UserInterface/Control.Animations.cs b/Robust.Client/UserInterface/Control.Animations.cs index 2db744627f2..0d94d0a155f 100644 --- a/Robust.Client/UserInterface/Control.Animations.cs +++ b/Robust.Client/UserInterface/Control.Animations.cs @@ -57,7 +57,7 @@ private void ProcessAnimations(FrameEventArgs args) toRemove.Add(key); } - foreach (var key in toRemove) + foreach (var key in toRemove.Span) { _playingAnimations.Remove(key); AnimationCompleted?.Invoke(key); diff --git a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs index 736209cd9e2..bfafd0098d0 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs @@ -329,7 +329,7 @@ private static void ProcessSingleEventCore( ref Unit unitRef, EventData subs) { - foreach (var handler in subs.BroadcastRegistrations) + foreach (var handler in subs.BroadcastRegistrations.Span) { if ((handler.Mask & source) != 0) handler.Handler(ref unitRef); diff --git a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs index 5f9837c96ac..6bb026526a7 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs @@ -13,7 +13,7 @@ private static void CollectBroadcastOrdered( EventData sub, ref ValueList found) { - foreach (var handler in sub.BroadcastRegistrations) + foreach (var handler in sub.BroadcastRegistrations.Span) { if ((handler.Mask & source) != 0) found.Add(new OrderedEventDispatch(handler.Handler, handler.Order)); @@ -44,7 +44,7 @@ private static void DispatchOrderedEvents(ref Unit eventArgs, ref ValueList intersecting, LookupFlags flags) } } - foreach (var uid in toAdd) + foreach (var uid in toAdd.Span) { intersecting.Add(uid); } diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index e787c64b329..dd4306971ba 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -58,7 +58,7 @@ private void AddContained(HashSet> intersecting, LookupFlags flags, } } - foreach (var uid in toAdd) + foreach (var uid in toAdd.Span) { intersecting.Add(uid); } diff --git a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs index 2d3bda26421..a60f78c84c1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs @@ -180,7 +180,7 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles toRemove.Add((oldId, oldFixture)); } - foreach (var (id, fixture) in toRemove) + foreach (var (id, fixture) in toRemove.Span) { // TODO add a DestroyFixture() override that takes in a list. // reduced broadphase lookups @@ -194,7 +194,7 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles } // Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet). - foreach (var (id, fixture) in newFixtures) + foreach (var (id, fixture) in newFixtures.Span) { var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager); // Check if it's the same (otherwise remove anyway). diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.cs b/Robust.Shared/Physics/Systems/FixtureSystem.cs index 03c2a6fb9a5..c07c6831f08 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.cs @@ -289,7 +289,7 @@ private void OnHandleState(EntityUid uid, FixturesComponent component, ref Compo // TODO add a DestroyFixture() override that takes in a list. // reduced broadphase lookups - foreach (var (id, fixture) in toRemoveFixtures) + foreach (var (id, fixture) in toRemoveFixtures.Span) { computeProperties = true; DestroyFixture(uid, id, fixture, false, physics, component); @@ -298,7 +298,7 @@ private void OnHandleState(EntityUid uid, FixturesComponent component, ref Compo // TODO: We also still need event listeners for shapes (Probably need C# events) // Or we could just make it so shapes can only be updated via fixturesystem which handles it // automagically (friends or something?) - foreach (var (id, fixture) in toAddFixtures) + foreach (var (id, fixture) in toAddFixtures.Span) { computeProperties = true; CreateFixture(uid, id, fixture, false, component, physics, xform); From 6764ed56b06309b56bd35c8ebffdf64882d4c4c1 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 31 Mar 2024 17:02:37 +1100 Subject: [PATCH 068/130] Version: 217.2.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index b7f15712321..0187983222a 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 217.2.0 + 217.2.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ff4fe042ba6..3b77a373dd9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,17 @@ END TEMPLATE--> *None yet* +## 217.2.1 + +### Bugfixes + +* Fix LineEdit tests on engine. + +### Internal + +* Make various ValueList enumerators access the span directly for performance. + + ## 217.2.0 ### New features From 84360c653d26baa4ce7510d8d1b003ea617aa02b Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Apr 2024 02:24:57 +0200 Subject: [PATCH 069/130] Add better environment variable config system ROBUST_CVARS had multiple issues: * Not composable, i.e. two independent systems can't easily layer CVars to set as they all have to go into one var * Not sanitary, there's no way to store things that have a ";" in them because it'd always get used as separator. This adds a new ROBUST_CVAR_* system. For example I can set ROBUST_CVAR_game__hostname=foobar to set a CVar via a single env var. A double underscore ("__") is replaced with a period to make the CVar names safe for environment variables. Also made Robust.Shared.Configuration.EnvironmentVariables internal because wtf that should not be public no. --- RELEASE-NOTES.md | 6 ++-- .../Configuration/EnvironmentVariables.cs | 32 ++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3b77a373dd9..d557a845cc9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,11 +35,11 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content. ### New features -*None yet* +* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". ### Bugfixes @@ -69,7 +69,7 @@ END TEMPLATE--> ### New features -* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes. +* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes. * Add double-clicking to LineEdit. ### Bugfixes diff --git a/Robust.Shared/Configuration/EnvironmentVariables.cs b/Robust.Shared/Configuration/EnvironmentVariables.cs index 0bb7fc169a9..e642abfe197 100644 --- a/Robust.Shared/Configuration/EnvironmentVariables.cs +++ b/Robust.Shared/Configuration/EnvironmentVariables.cs @@ -1,9 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; namespace Robust.Shared.Configuration { - public static class EnvironmentVariables + internal static class EnvironmentVariables { /// /// The environment variable for configuring CVar overrides. The value @@ -12,23 +13,38 @@ public static class EnvironmentVariables /// public const string ConfigVarEnvironmentVariable = "ROBUST_CVARS"; + public const string SingleVarPrefix = "ROBUST_CVAR_"; + /// /// Get the CVar overrides defined in the relevant environment variable. /// - public static IEnumerable<(string, string)> GetEnvironmentCVars() + internal static IEnumerable<(string, string)> GetEnvironmentCVars() { - var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable); - - if (eVarString == null) - { - yield break; - } + // Handle ROBUST_CVARS. + var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable) ?? ""; foreach (var cVarPair in eVarString.Split(';', StringSplitOptions.RemoveEmptyEntries)) { var pairParts = cVarPair.Split('=', 2); yield return (pairParts[0], pairParts[1]); } + + // Handle ROBUST_CVAR_* + + foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) + { + var key = (string)entry.Key; + var value = (string?)entry.Value; + + if (value == null) + continue; + + if (!key.StartsWith(SingleVarPrefix)) + continue; + + var varName = key[SingleVarPrefix.Length..].Replace("__", "."); + yield return (varName, value); + } } } } From b31940b4896a1f4eff7ac005c571f3ee8449787e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 5 Apr 2024 02:22:06 +0200 Subject: [PATCH 070/130] Improve logging for watchdog pinsg --- Robust.Server/ServerStatus/WatchdogApi.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Robust.Server/ServerStatus/WatchdogApi.cs b/Robust.Server/ServerStatus/WatchdogApi.cs index 8677d40ed60..2a5e3fa3b07 100644 --- a/Robust.Server/ServerStatus/WatchdogApi.cs +++ b/Robust.Server/ServerStatus/WatchdogApi.cs @@ -157,11 +157,13 @@ public async void Heartbeat() try { // Passing null as content works so... + _sawmill.Debug("Sending ping to watchdog..."); await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!); + _sawmill.Debug("Succeeded in sending ping to watchdog"); } catch (HttpRequestException e) { - _sawmill.Warning("Failed to send ping to watchdog:\n{0}", e); + _sawmill.Error("Failed to send ping to watchdog:\n{0}", e); } } From ed406c06b7518dc205cd5ba7fd393d5315572f0d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 5 Apr 2024 23:23:46 +0200 Subject: [PATCH 071/130] Fix HTTP errors on watchdog ping not being reported --- Robust.Server/ServerStatus/WatchdogApi.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.Server/ServerStatus/WatchdogApi.cs b/Robust.Server/ServerStatus/WatchdogApi.cs index 2a5e3fa3b07..55afe1ea08e 100644 --- a/Robust.Server/ServerStatus/WatchdogApi.cs +++ b/Robust.Server/ServerStatus/WatchdogApi.cs @@ -158,7 +158,8 @@ public async void Heartbeat() { // Passing null as content works so... _sawmill.Debug("Sending ping to watchdog..."); - await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!); + using var resp = await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!); + resp.EnsureSuccessStatusCode(); _sawmill.Debug("Succeeded in sending ping to watchdog"); } catch (HttpRequestException e) From 0c271fc2f843b8b6526ba0c50f4a874302e5fe59 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:24:09 +1200 Subject: [PATCH 072/130] Remove uneccesary `Exists()` checks in container system (#5031) * Remove `Exists()` checks in container system * A * A --- .../GameStates/ClientGameStateManager.cs | 4 ++-- .../Containers/SharedContainerSystem.cs | 16 ++++++++++++---- .../Systems/SharedTransformSystem.Component.cs | 3 +-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index f048353f8f6..3b764031d3f 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -1138,7 +1138,7 @@ private void Detach(GameTick maxTick, if ((meta.Flags & MetaDataFlags.InContainer) != 0 && metas.TryGetComponent(xform.ParentUid, out var containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0 && - containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true)) + containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container)) { containerSys.Remove((ent.Value, xform, meta), container, false, true); } @@ -1414,7 +1414,7 @@ private void DetachEntCommand(IConsoleShell shell, string argStr, string[] args) _entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0) { - containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true); + containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container); } _entities.EntitySysManager.GetEntitySystem().DetachParentToNull(uid, xform); diff --git a/Robust.Shared/Containers/SharedContainerSystem.cs b/Robust.Shared/Containers/SharedContainerSystem.cs index 707f8edff8e..91b909264a8 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.cs @@ -168,9 +168,16 @@ public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out Ba return containerManager.Containers.TryGetValue(id, out container); } - public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false) + [Obsolete("Use variant without skipExistCheck argument")] + public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, bool skipExistCheck) { - if (!Resolve(uid, ref containerManager, false) || !(skipExistCheck || Exists(containedUid))) + return TryGetContainingContainer(uid, containedUid, out container); + } + + public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null) + { + DebugTools.Assert(Exists(containedUid)); + if (!Resolve(uid, ref containerManager, false)) { container = null; return false; @@ -191,7 +198,8 @@ public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [No public bool ContainsEntity(EntityUid uid, EntityUid containedUid, ContainerManagerComponent? containerManager = null) { - if (!Resolve(uid, ref containerManager, false) || !Exists(containedUid)) + DebugTools.Assert(Exists(containedUid)); + if (!Resolve(uid, ref containerManager, false)) return false; foreach (var container in containerManager.Containers.Values) @@ -251,7 +259,7 @@ public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out Bas if (!Resolve(uid, ref transform, false)) return false; - return TryGetContainingContainer(transform.ParentUid, uid, out container, skipExistCheck: true); + return TryGetContainingContainer(transform.ParentUid, uid, out container); } /// diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index ff660a5b835..31c48daaee1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -1453,8 +1453,7 @@ public void DropNextTo(Entity entity, Entity Date: Mon, 8 Apr 2024 13:27:02 -0400 Subject: [PATCH 073/130] Add Type tracking to FieldNotFoundErrorNode (#5032) * Add Type tracking to FieldNotFoundErrorNode * Suggested changes, plus xmldoc and primary constructor conversion. --- .../Markdown/Validation/FieldNotFoundErrorNode.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Robust.Shared/Serialization/Markdown/Validation/FieldNotFoundErrorNode.cs b/Robust.Shared/Serialization/Markdown/Validation/FieldNotFoundErrorNode.cs index 02e91a8952a..377ba2e53b7 100644 --- a/Robust.Shared/Serialization/Markdown/Validation/FieldNotFoundErrorNode.cs +++ b/Robust.Shared/Serialization/Markdown/Validation/FieldNotFoundErrorNode.cs @@ -3,9 +3,10 @@ namespace Robust.Shared.Serialization.Markdown.Validation; -public sealed class FieldNotFoundErrorNode : ErrorNode +public sealed class FieldNotFoundErrorNode(ValueDataNode key, Type type) : ErrorNode(key, $"Field \"{key.Value}\" not found in \"{type}\".", false) { - public FieldNotFoundErrorNode(ValueDataNode key, Type type) : base(key, $"Field \"{key.Value}\" not found in \"{type}\".", false) - { - } + /// + /// The Type in which the field was not found. + /// + public Type FieldType { get; } = type; } From 4b193bad263a898129c1fed5ff51ca362fcbd77e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 11 Apr 2024 02:25:40 +0200 Subject: [PATCH 074/130] Add non-generic GetCVar. Surprised we didn't have this. --- RELEASE-NOTES.md | 1 + Robust.Shared/Configuration/ConfigurationManager.cs | 12 ++++++++---- Robust.Shared/Configuration/IConfigurationManager.cs | 7 +++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d557a845cc9..d24c7207316 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". +* Added non-generic variant of `GetCVar` to `IConfigurationManager`. ### Bugfixes diff --git a/Robust.Shared/Configuration/ConfigurationManager.cs b/Robust.Shared/Configuration/ConfigurationManager.cs index 725bb746ea9..b9750a7f7f6 100644 --- a/Robust.Shared/Configuration/ConfigurationManager.cs +++ b/Robust.Shared/Configuration/ConfigurationManager.cs @@ -558,17 +558,21 @@ public void OverrideDefault(CVarDef def, T value) where T : notnull OverrideDefault(def.Name, value); } - /// - public T GetCVar(string name) + public object GetCVar(string name) { using var _ = Lock.ReadGuard(); if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered) - //TODO: Make flags work, required non-derpy net system. - return (T)(GetConfigVarValue(cVar))!; + return GetConfigVarValue(cVar); throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'"); } + /// + public T GetCVar(string name) + { + return (T)GetCVar(name); + } + public T GetCVar(CVarDef def) where T : notnull { return GetCVar(def.Name); diff --git a/Robust.Shared/Configuration/IConfigurationManager.cs b/Robust.Shared/Configuration/IConfigurationManager.cs index aababead8b5..6412e566ef4 100644 --- a/Robust.Shared/Configuration/IConfigurationManager.cs +++ b/Robust.Shared/Configuration/IConfigurationManager.cs @@ -118,6 +118,13 @@ void RegisterCVar(string name, T defaultValue, CVar flags = CVar.NONE, Action /// The new default value of the CVar. void OverrideDefault(CVarDef def, T value) where T : notnull; + /// + /// Get the value of a CVar. + /// + /// The name of the CVar. + /// + object GetCVar(string name); + /// /// Get the value of a CVar. /// From c1b8bf8e526b0948127f389be525fa6d1e1246ca Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 11 Apr 2024 02:28:46 +0200 Subject: [PATCH 075/130] Make StatusHost request headers case insensitive Woops that's how HTTP is supposed to work. --- RELEASE-NOTES.md | 2 +- Robust.Server/ServerStatus/StatusHost.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d24c7207316..feb2eeae6ff 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,7 +44,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Request headers in `IStatusHandlerContext` are now case-insensitive. ### Other diff --git a/Robust.Server/ServerStatus/StatusHost.cs b/Robust.Server/ServerStatus/StatusHost.cs index abd75472725..062c1dd8c07 100644 --- a/Robust.Server/ServerStatus/StatusHost.cs +++ b/Robust.Server/ServerStatus/StatusHost.cs @@ -256,7 +256,7 @@ public ContextImpl(HttpListenerContext context) _context = context; RequestMethod = new HttpMethod(context.Request.HttpMethod!); - var headers = new Dictionary(); + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string? key in context.Request.Headers.Keys) { if (key == null) From f6a5120e5688414861258a27278529ca6671fbae Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 02:05:40 +0200 Subject: [PATCH 076/130] Fix exception when inspecting element in some cases. Happened when I was trying to develop item status stuff. --- Robust.Client/UserInterface/Control.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Robust.Client/UserInterface/Control.cs b/Robust.Client/UserInterface/Control.cs index 82222b8177e..1eb9b7a444e 100644 --- a/Robust.Client/UserInterface/Control.cs +++ b/Robust.Client/UserInterface/Control.cs @@ -641,7 +641,11 @@ public void RemoveAllChildren() foreach (var child in Children.ToArray()) { - RemoveChild(child); + // This checks fails in some obscure cases like using the element inspector in the dev window. + // Why? Well I could probably spend 15 minutes in a debugger to find out, + // but I'd probably still end up with this fix. + if (child.Parent == this) + RemoveChild(child); } } From 8c4deb20670b42895c2aa7ad52a564a52128aa72 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 02:08:56 +0200 Subject: [PATCH 077/130] Allow control layout properties to be set via style sheet. This works by setting the stylesheet values into the regular control property fields when updated. This means 0 performance overhead except when updating styles, and even then it's probably negligible. A bitfield is used to track which properties are set and how. This code is all done manual for now. I wanted to make a source gen for this but couldn't be arsed at the moment. The code manually written here is basically what a future source gen would generate optimally. --- RELEASE-NOTES.md | 1 + .../UserInterface/Control.Layout.Styling.cs | 132 ++++++++++++++++++ Robust.Client/UserInterface/Control.Layout.cs | 32 ++++- .../UserInterface/Control.Styling.cs | 1 + 4 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 Robust.Client/UserInterface/Control.Layout.Styling.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index feb2eeae6ff..c70988797bf 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". * Added non-generic variant of `GetCVar` to `IConfigurationManager`. +* Control layout properties such as `Margin` can now be set via style sheets. ### Bugfixes diff --git a/Robust.Client/UserInterface/Control.Layout.Styling.cs b/Robust.Client/UserInterface/Control.Layout.Styling.cs new file mode 100644 index 00000000000..fdc1a4a4c83 --- /dev/null +++ b/Robust.Client/UserInterface/Control.Layout.Styling.cs @@ -0,0 +1,132 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Robust.Client.UserInterface; + +public partial class Control +{ + private LayoutStyleProperties _layoutStyleOverride; + private LayoutStyleProperties _layoutStyleSheet; + + private void UpdateLayoutStyleProperties() + { + var propertiesSet = LayoutStyleProperties.None; + + // Assumed most controls will have little or no style properties, + // so iterating once is less expensive overall then checking 10+ properties. + // C# switch statements are compiled efficiently anyways. + foreach (var (key, value) in _styleProperties) + { + switch (key) + { + case nameof(SizeFlagsStretchRatio): + UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio); + break; + case nameof(MinWidth): + UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth); + break; + case nameof(MinHeight): + UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight); + break; + case nameof(SetWidth): + UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth); + break; + case nameof(SetHeight): + UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight); + break; + case nameof(MaxWidth): + UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth); + break; + case nameof(MaxHeight): + UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight); + break; + case nameof(HorizontalExpand): + UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand); + break; + case nameof(VerticalExpand): + UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand); + break; + case nameof(HorizontalAlignment): + UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment); + break; + case nameof(VerticalAlignment): + UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment); + break; + case nameof(Margin): + UpdateField(ref _margin, value, LayoutStyleProperties.Margin); + break; + } + } + + // Reset cleared properties back to defaults. + var toClear = _layoutStyleSheet & ~propertiesSet; + if (toClear != 0) + { + ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio); + ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth); + ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight); + ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth); + ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight); + ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth); + ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight); + ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand); + ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand); + ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment); + ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment); + ClearField(ref _margin, default, LayoutStyleProperties.Margin); + } + + _layoutStyleSheet = propertiesSet; + + return; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void UpdateField(ref T field, object value, LayoutStyleProperties flag) + { + if ((_layoutStyleOverride & flag) != 0) + return; + + // TODO: Probably need better error handling... + if (value is not T valueCast) + return; + + field = valueCast; + propertiesSet |= flag; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ClearField(ref T field, T defaultValue, LayoutStyleProperties flag) + { + if ((toClear & flag) == 0) + return; + + field = defaultValue; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetLayoutStyleProp(LayoutStyleProperties flag) + { + _layoutStyleOverride |= flag; + } + + [Flags] + private enum LayoutStyleProperties : short + { + // @formatter:off + None = 0, + Margin = 1 << 0, + MinWidth = 1 << 1, + MinHeight = 1 << 2, + SetWidth = 1 << 3, + SetHeight = 1 << 4, + MaxWidth = 1 << 5, + MaxHeight = 1 << 6, + StretchRatio = 1 << 7, + HorizontalExpand = 1 << 8, + VerticalExpand = 1 << 9, + HorizontalAlignment = 1 << 10, + VerticalAlignment = 1 << 11, + // @formatter:on + } +} diff --git a/Robust.Client/UserInterface/Control.Layout.cs b/Robust.Client/UserInterface/Control.Layout.cs index 88872de3cd4..1bf1aee901d 100644 --- a/Robust.Client/UserInterface/Control.Layout.cs +++ b/Robust.Client/UserInterface/Control.Layout.cs @@ -12,24 +12,30 @@ namespace Robust.Client.UserInterface public partial class Control { + private const float DefaultStretchRatio = 1; + private const float DefaultSetSize = float.NaN; + private const float DefaultMaxSize = float.NaN; + private const HAlignment DefaultHAlignment = HAlignment.Stretch; + private const VAlignment DefaultVAlignment = VAlignment.Stretch; + private Vector2 _size; [ViewVariables] internal Vector2? PreviousMeasure; [ViewVariables] internal UIBox2? PreviousArrange; - private float _sizeFlagsStretchRatio = 1; + private float _sizeFlagsStretchRatio = DefaultStretchRatio; private float _minWidth; private float _minHeight; - private float _setWidth = float.NaN; - private float _setHeight = float.NaN; - private float _maxWidth = float.PositiveInfinity; - private float _maxHeight = float.PositiveInfinity; + private float _setWidth = DefaultSetSize; + private float _setHeight = DefaultSetSize; + private float _maxWidth = DefaultMaxSize; + private float _maxHeight = DefaultMaxSize; private bool _horizontalExpand; private bool _verticalExpand; - private HAlignment _horizontalAlignment = HAlignment.Stretch; - private VAlignment _verticalAlignment = VAlignment.Stretch; + private HAlignment _horizontalAlignment = DefaultHAlignment; + private VAlignment _verticalAlignment = DefaultVAlignment; private Thickness _margin; private bool _measuring; private bool _arranging; @@ -53,6 +59,7 @@ public Thickness Margin set { _margin = value; + SetLayoutStyleProp(LayoutStyleProperties.Margin); InvalidateMeasure(); } } @@ -242,6 +249,7 @@ public HAlignment HorizontalAlignment set { _horizontalAlignment = value; + SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment); InvalidateArrange(); } } @@ -258,6 +266,7 @@ public VAlignment VerticalAlignment set { _verticalAlignment = value; + SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment); InvalidateArrange(); } } @@ -276,6 +285,7 @@ public bool HorizontalExpand set { _horizontalExpand = value; + SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand); Parent?.InvalidateMeasure(); } } @@ -294,6 +304,7 @@ public bool VerticalExpand set { _verticalExpand = value; + SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand); Parent?.InvalidateArrange(); } } @@ -318,6 +329,7 @@ public float SizeFlagsStretchRatio _sizeFlagsStretchRatio = value; + SetLayoutStyleProp(LayoutStyleProperties.StretchRatio); Parent?.InvalidateArrange(); } } @@ -394,6 +406,7 @@ public float MinWidth set { _minWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.MinWidth); InvalidateMeasure(); } } @@ -408,6 +421,7 @@ public float MinHeight set { _minHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.MinHeight); InvalidateMeasure(); } } @@ -422,6 +436,7 @@ public float SetWidth set { _setWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.SetWidth); InvalidateMeasure(); } } @@ -436,6 +451,7 @@ public float SetHeight set { _setHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.SetHeight); InvalidateMeasure(); } } @@ -450,6 +466,7 @@ public float MaxWidth set { _maxWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.MaxWidth); InvalidateMeasure(); } } @@ -464,6 +481,7 @@ public float MaxHeight set { _maxHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.MaxHeight); InvalidateMeasure(); } } diff --git a/Robust.Client/UserInterface/Control.Styling.cs b/Robust.Client/UserInterface/Control.Styling.cs index 4f54cf56444..fbe7a1e7004 100644 --- a/Robust.Client/UserInterface/Control.Styling.cs +++ b/Robust.Client/UserInterface/Control.Styling.cs @@ -239,6 +239,7 @@ internal void DoStyleUpdate() protected virtual void StylePropertiesChanged() { + UpdateLayoutStyleProperties(); InvalidateMeasure(); } From 814ad08884963b8ce64d140fe89c8458801d9964 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 02:10:01 +0200 Subject: [PATCH 078/130] Allow scaling the line height of a RichTextLabel --- RELEASE-NOTES.md | 1 + .../UserInterface/Controls/RichTextLabel.cs | 25 +++++++++++++++++-- Robust.Client/UserInterface/RichTextEntry.cs | 18 +++++++++---- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c70988797bf..06c441bbe66 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -42,6 +42,7 @@ END TEMPLATE--> * You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". * Added non-generic variant of `GetCVar` to `IConfigurationManager`. * Control layout properties such as `Margin` can now be set via style sheets. +* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`. ### Bugfixes diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs index 8836b42e3c0..8e4f2e8c967 100644 --- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs +++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs @@ -6,6 +6,7 @@ using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; namespace Robust.Client.UserInterface.Controls { @@ -16,6 +17,26 @@ public class RichTextLabel : Control private FormattedMessage? _message; private RichTextEntry _entry; + private float _lineHeightScale = 1; + private bool _lineHeightOverride; + + [ViewVariables(VVAccess.ReadWrite)] + public float LineHeightScale + { + get + { + if (!_lineHeightOverride && TryGetStyleProperty(nameof(LineHeightScale), out float value)) + return value; + + return _lineHeightScale; + } + set + { + _lineHeightScale = value; + _lineHeightOverride = true; + InvalidateMeasure(); + } + } public RichTextLabel() { @@ -47,7 +68,7 @@ protected override Vector2 MeasureOverride(Vector2 availableSize) } var font = _getFont(); - _entry.Update(font, availableSize.X * UIScale, UIScale); + _entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale); return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale); } @@ -61,7 +82,7 @@ protected internal override void Draw(DrawingHandleScreen handle) return; } - _entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale); + _entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale); } [Pure] diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs index 529bdd15c0f..4c42084a59c 100644 --- a/Robust.Client/UserInterface/RichTextEntry.cs +++ b/Robust.Client/UserInterface/RichTextEntry.cs @@ -71,7 +71,8 @@ public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager /// The font being used for display. /// The maximum horizontal size of the container of this entry. /// - public void Update(Font defaultFont, float maxSizeX, float uiScale) + /// + public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1) { // This method is gonna suck due to complexity. // Bear with me here. @@ -159,7 +160,7 @@ void CheckLineBreak(ref RichTextEntry src, int? line) if (!context.Font.TryPeek(out var font)) font = defaultFont; - src.Height += font.GetLineHeight(uiScale); + src.Height += GetLineHeight(font, uiScale, lineHeightScale); } } } @@ -170,7 +171,8 @@ public readonly void Draw( UIBox2 drawBox, float verticalOffset, MarkupDrawingContext context, - float uiScale) + float uiScale, + float lineHeightScale = 1) { context.Clear(); context.Color.Push(_defaultColor); @@ -197,7 +199,7 @@ public readonly void Draw( if (lineBreakIndex < LineBreaks.Count && LineBreaks[lineBreakIndex] == globalBreakCounter) { - baseLine = new Vector2(drawBox.Left, baseLine.Y + font.GetLineHeight(uiScale) + controlYAdvance); + baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance); controlYAdvance = 0; lineBreakIndex += 1; } @@ -216,7 +218,7 @@ public readonly void Draw( control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale); control.Measure(new Vector2(Width, Height)); var advanceX = control.DesiredPixelSize.X; - controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - font.GetLineHeight(uiScale)) * invertedScale); + controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale); baseLine += new Vector2(advanceX, 0); } } @@ -242,5 +244,11 @@ private readonly string ProcessNode(MarkupNode node, MarkupDrawingContext contex tag.PopDrawContext(node, context); return tag.TextAfter(node); } + + private static int GetLineHeight(Font font, float uiScale, float lineHeightScale) + { + var height = font.GetLineHeight(uiScale); + return (int)(height * lineHeightScale); + } } } From 4874b1db68f0cdc0f811b235dc4c43daff6cfa1f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 02:15:53 +0200 Subject: [PATCH 079/130] Update UI themes on prototype reload. --- RELEASE-NOTES.md | 1 + .../UserInterfaceManager.Themes.cs | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 06c441bbe66..c15430bbc50 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,7 @@ END TEMPLATE--> * Added non-generic variant of `GetCVar` to `IConfigurationManager`. * Control layout properties such as `Margin` can now be set via style sheets. * Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`. +* UI theme prototypes are now updated when reloaded. ### Bugfixes diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Themes.cs b/Robust.Client/UserInterface/UserInterfaceManager.Themes.cs index 435e4579f56..e8198db21a4 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Themes.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Themes.cs @@ -2,6 +2,7 @@ using Robust.Client.UserInterface.Themes; using Robust.Shared; using Robust.Shared.Log; +using Robust.Shared.Prototypes; namespace Robust.Client.UserInterface; @@ -18,11 +19,29 @@ private void _initThemes() { DefaultTheme = _protoManager.Index(UITheme.DefaultName); CurrentTheme = DefaultTheme; + ReloadThemes(); + _configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true); + _protoManager.PrototypesReloaded += OnPrototypesReloaded; + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs eventArgs) + { + if (eventArgs.WasModified()) + { + _sawmillUI.Debug("Reloading UI themes due to prototype reload"); + ReloadThemes(); + } + } + + private void ReloadThemes() + { + _themes.Clear(); foreach (var proto in _protoManager.EnumeratePrototypes()) { _themes.Add(proto.ID, proto); } - _configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true); + + SetThemeOrPrevious(CurrentTheme.ID); } //Try to set the current theme, if the theme is not found do nothing From 57b328e8c2433357e7a7fe47e3537e6932481b78 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sun, 14 Apr 2024 00:24:31 +0000 Subject: [PATCH 080/130] add CopyData to appearance system (#5022) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Systems/SharedAppearanceSystem.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs index e6bd0b12793..76bb98f5b52 100644 --- a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs @@ -74,6 +74,28 @@ public bool TryGetData(EntityUid uid, Enum key, [NotNullWhen(true)] out object? return component.AppearanceData.TryGetValue(key, out value); } + + /// + /// Copies appearance data from src to dest. + /// If src has no nothing is done. + /// If dest has no AppearanceComponent then it is created. + /// + public void CopyData(Entity src, Entity dest) + { + if (!Resolve(src, ref src.Comp, false)) + return; + + dest.Comp ??= EnsureComp(dest); + dest.Comp.AppearanceData.Clear(); + + foreach (var (key, value) in src.Comp.AppearanceData) + { + dest.Comp.AppearanceData[key] = value; + } + + Dirty(dest, dest.Comp); + QueueUpdate(dest, dest.Comp); + } } [Serializable, NetSerializable] From cd67c67a5cbb51c913790fd1192ce86b96633ec1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 05:12:01 +0200 Subject: [PATCH 081/130] Add analyzer to warn for assignment to dependency fields. --- RELEASE-NOTES.md | 1 + .../DependencyAssignAnalyzerTest.cs | 65 +++++++++++++++++++ Robust.Analyzers/DependencyAssignAnalyzer.cs | 61 +++++++++++++++++ Robust.Roslyn.Shared/Diagnostics.cs | 1 + 4 files changed, 128 insertions(+) create mode 100644 Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs create mode 100644 Robust.Analyzers/DependencyAssignAnalyzer.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c15430bbc50..8cf8f2f75ab 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,6 +44,7 @@ END TEMPLATE--> * Control layout properties such as `Margin` can now be set via style sheets. * Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`. * UI theme prototypes are now updated when reloaded. +* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields. ### Bugfixes diff --git a/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs b/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs new file mode 100644 index 00000000000..f8b696992f1 --- /dev/null +++ b/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs @@ -0,0 +1,65 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using NUnit.Framework; +using VerifyCS = + Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier; + +namespace Robust.Analyzers.Tests; + +[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] +[TestFixture] +public sealed class DependencyAssignAnalyzerTest +{ + private const string BaseCode = """ + using System; + + namespace Robust.Shared.IoC; + + [AttributeUsage(AttributeTargets.Field)] + public sealed class DependencyAttribute : Attribute + { + } + """; + + private static Task Verifier(string code, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerTest() + { + TestState = + { + AdditionalReferences = { typeof(DependencyAssignAnalyzer).Assembly }, + Sources = { code, BaseCode } + }, + }; + + // ExpectedDiagnostics cannot be set, so we need to AddRange here... + test.TestState.ExpectedDiagnostics.AddRange(expected); + + return test.RunAsync(); + } + + [Test] + public async Task Test() + { + const string code = """ + using Robust.Shared.IoC; + + public sealed class Foo + { + [Dependency] + private object? Field; + + public Foo() + { + Field = "A"; + } + } + """; + + await Verifier(code, + // /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead. + VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field")); + } +} diff --git a/Robust.Analyzers/DependencyAssignAnalyzer.cs b/Robust.Analyzers/DependencyAssignAnalyzer.cs new file mode 100644 index 00000000000..607a1e5d919 --- /dev/null +++ b/Robust.Analyzers/DependencyAssignAnalyzer.cs @@ -0,0 +1,61 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Robust.Roslyn.Shared; + +namespace Robust.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer +{ + private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute"; + + private static readonly DiagnosticDescriptor Rule = new ( + Diagnostics.IdDependencyFieldAssigned, + "Assignment to dependency field", + "Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.", + "Usage", + DiagnosticSeverity.Warning, + true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment); + } + + private static void CheckAssignment(OperationAnalysisContext context) + { + if (context.Operation is not ISimpleAssignmentOperation assignment) + return; + + if (assignment.Target is not IFieldReferenceOperation fieldRef) + return; + + var field = fieldRef.Field; + var attributes = field.GetAttributes(); + if (attributes.Length == 0) + return; + + var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType); + if (!HasAttribute(attributes, depAttribute)) + return; + + context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name)); + } + + private static bool HasAttribute(ImmutableArray attributes, ISymbol symbol) + { + foreach (var attribute in attributes) + { + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol)) + return true; + } + + return false; + } +} diff --git a/Robust.Roslyn.Shared/Diagnostics.cs b/Robust.Roslyn.Shared/Diagnostics.cs index b829a2f4c30..94709a5f293 100644 --- a/Robust.Roslyn.Shared/Diagnostics.cs +++ b/Robust.Roslyn.Shared/Diagnostics.cs @@ -28,6 +28,7 @@ public static class Diagnostics public const string IdComponentPauseNoFields = "RA0022"; public const string IdComponentPauseNoParentAttribute = "RA0023"; public const string IdComponentPauseWrongTypeAttribute = "RA0024"; + public const string IdDependencyFieldAssigned = "RA0025"; public static SuppressionDescriptor MeansImplicitAssignment => new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned."); From caa8ff0f2d3aba3fe216e2fe1ac39acb59acc7af Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 14 Apr 2024 15:47:21 +1200 Subject: [PATCH 082/130] Modify container/spawn helper methods (#5030) * Modify container/spawn helper methods * A --- .../SharedContainerSystem.Insert.cs | 19 +++++++++ .../GameObjects/EntityManager.Spawn.cs | 42 +++++++++---------- .../GameObjects/IEntityManager.Spawn.cs | 21 +++++++--- .../Shared/Spawning/TrySpawnNextToTest.cs | 7 ++-- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs index 5d18b7f9f31..ca1b0234cb7 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs @@ -135,6 +135,25 @@ public bool Insert(Entity + /// Attempts to insert an entity into a container. If it fails, it will instead drop the entity next to the + /// container entity. + /// + /// Whether or not the entity was successfully inserted + public bool InsertOrDrop(Entity toInsert, + BaseContainer container, + TransformComponent? containerXform = null) + { + if (!Resolve(toInsert.Owner, ref toInsert.Comp1) || !Resolve(container.Owner, ref containerXform)) + return false; + + if (Insert(toInsert, container, containerXform)) + return true; + + _transform.DropNextTo(toInsert, (container.Owner, containerXform)); + return false; + } + /// /// Checks if the entity can be inserted into the given container. /// diff --git a/Robust.Shared/GameObjects/EntityManager.Spawn.cs b/Robust.Shared/GameObjects/EntityManager.Spawn.cs index 7eb98c9c52f..32e3e96d422 100644 --- a/Robust.Shared/GameObjects/EntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/EntityManager.Spawn.cs @@ -111,32 +111,18 @@ public bool TrySpawnNextTo( if (!xform.ParentUid.IsValid()) return false; - if (!MetaQuery.TryGetComponent(target, out var meta)) - return false; - - if ((meta.Flags & MetaDataFlags.InContainer) == 0) + if (!_containers.TryGetContainingContainer(target, out var container)) { - uid = SpawnAttachedTo(protoName, xform.Coordinates, overrides); + uid = SpawnNextToOrDrop(protoName, target, xform, overrides); return true; } - if (!TryGetComponent(xform.ParentUid, out ContainerManagerComponent? containerComp)) - return false; - - foreach (var container in containerComp.Containers.Values) - { - if (!container.Contains(target)) - continue; - - uid = Spawn(protoName, overrides); - if (_containers.Insert(uid.Value, container)) - return true; - - DeleteEntity(uid.Value); - uid = null; - return false; - } + uid = Spawn(protoName, overrides); + if (_containers.Insert(uid.Value, container)) + return true; + DeleteEntity(uid.Value); + uid = null; return false; } @@ -183,14 +169,28 @@ public EntityUid SpawnInContainerOrDrop( TransformComponent? xform = null, ContainerManagerComponent? containerComp = null, ComponentRegistry? overrides = null) + { + return SpawnInContainerOrDrop(protoName, containerUid, containerId, out _, xform, containerComp, overrides); + } + + public EntityUid SpawnInContainerOrDrop( + string? protoName, + EntityUid containerUid, + string containerId, + out bool inserted, + TransformComponent? xform = null, + ContainerManagerComponent? containerComp = null, + ComponentRegistry? overrides = null) { var uid = Spawn(protoName, overrides); + inserted = true; if ((containerComp == null && !TryGetComponent(containerUid, out containerComp)) || !containerComp.Containers.TryGetValue(containerId, out var container) || !_containers.Insert(uid, container)) { + inserted = false; xform ??= TransformQuery.GetComponent(containerUid); if (xform.ParentUid.IsValid()) _xforms.DropNextTo(uid, (containerUid, xform)); diff --git a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs index 11b1d9edc74..1fddff23055 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs @@ -46,7 +46,7 @@ EntityUid[] SpawnEntities(EntityCoordinates coordinates, List protoName EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null); /// - /// Attempts to spawn an entity inside of a container. + /// Attempt to spawn an entity and insert it into a container. If the insertion fails, the entity gets deleted. /// bool TrySpawnInContainer( string? protoName, @@ -58,9 +58,9 @@ bool TrySpawnInContainer( /// /// Attempts to spawn an entity inside of a container. If it fails to insert into the container, it will - /// instead attempt to spawn the entity next to the target. + /// instead drop the entity next to the target (see ). /// - public EntityUid SpawnInContainerOrDrop( + EntityUid SpawnInContainerOrDrop( string? protoName, EntityUid containerUid, string containerId, @@ -68,9 +68,20 @@ public EntityUid SpawnInContainerOrDrop( ContainerManagerComponent? containerComp = null, ComponentRegistry? overrides = null); + /// + EntityUid SpawnInContainerOrDrop( + string? protoName, + EntityUid containerUid, + string containerId, + out bool inserted, + TransformComponent? xform = null, + ContainerManagerComponent? containerComp = null, + ComponentRegistry? overrides = null); + /// - /// Attempts to spawn an entity adjacent to some other entity. If the other entity is in a container, this will - /// attempt to insert the new entity into the same container. + /// Attempts to spawn an entity adjacent to some other target entity. If the target entity is in + /// a container, this will attempt to insert the spawned entity into the same container. If the insertion fails, + /// the entity is deleted. If the entity is not in a container, this behaves like . /// bool TrySpawnNextTo( string? protoName, diff --git a/Robust.UnitTesting/Shared/Spawning/TrySpawnNextToTest.cs b/Robust.UnitTesting/Shared/Spawning/TrySpawnNextToTest.cs index 1731e90bb14..765b9233f4a 100644 --- a/Robust.UnitTesting/Shared/Spawning/TrySpawnNextToTest.cs +++ b/Robust.UnitTesting/Shared/Spawning/TrySpawnNextToTest.cs @@ -31,15 +31,14 @@ await Server.WaitPost(() => Assert.That(EntMan.EntityExists(uid), Is.False); }); - // Spawning next to an entity that is not in a container will simply spawn it in the same position + // Spawning next to an entity that is not in a container will drop it await Server.WaitPost(() => { Assert.That(EntMan.TrySpawnNextTo(null, GrandChildB, out var uid)); Assert.That(EntMan.EntityExists(uid)); - Assert.That(Xforms.GetParentUid(uid!.Value), Is.EqualTo(ChildB)); - Assert.That(Container.IsEntityInContainer(uid.Value), Is.False); + Assert.That(Xforms.GetParentUid(uid!.Value), Is.EqualTo(Parent)); + Assert.That(Container.IsEntityInContainer(uid.Value)); Assert.That(Container.IsEntityOrParentInContainer(uid.Value)); - Assert.That(EntMan.GetComponent(uid.Value).Coordinates, Is.EqualTo(GrandChildBPos)); }); // Spawning "next to" a nullspace entity will fail. From af36d24892dd2a9a469c016b8179fab46dcbb09b Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:14:44 +1000 Subject: [PATCH 083/130] Revert "Allow control layout properties to be set via style sheet." (#5035) This reverts commit 8c4deb20670b42895c2aa7ad52a564a52128aa72. # Conflicts: # RELEASE-NOTES.md --- RELEASE-NOTES.md | 4 - .../UserInterface/Control.Layout.Styling.cs | 132 ------------------ Robust.Client/UserInterface/Control.Layout.cs | 32 +---- .../UserInterface/Control.Styling.cs | 1 - 4 files changed, 7 insertions(+), 162 deletions(-) delete mode 100644 Robust.Client/UserInterface/Control.Layout.Styling.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8cf8f2f75ab..feb2eeae6ff 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,10 +41,6 @@ END TEMPLATE--> * You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". * Added non-generic variant of `GetCVar` to `IConfigurationManager`. -* Control layout properties such as `Margin` can now be set via style sheets. -* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`. -* UI theme prototypes are now updated when reloaded. -* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields. ### Bugfixes diff --git a/Robust.Client/UserInterface/Control.Layout.Styling.cs b/Robust.Client/UserInterface/Control.Layout.Styling.cs deleted file mode 100644 index fdc1a4a4c83..00000000000 --- a/Robust.Client/UserInterface/Control.Layout.Styling.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace Robust.Client.UserInterface; - -public partial class Control -{ - private LayoutStyleProperties _layoutStyleOverride; - private LayoutStyleProperties _layoutStyleSheet; - - private void UpdateLayoutStyleProperties() - { - var propertiesSet = LayoutStyleProperties.None; - - // Assumed most controls will have little or no style properties, - // so iterating once is less expensive overall then checking 10+ properties. - // C# switch statements are compiled efficiently anyways. - foreach (var (key, value) in _styleProperties) - { - switch (key) - { - case nameof(SizeFlagsStretchRatio): - UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio); - break; - case nameof(MinWidth): - UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth); - break; - case nameof(MinHeight): - UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight); - break; - case nameof(SetWidth): - UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth); - break; - case nameof(SetHeight): - UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight); - break; - case nameof(MaxWidth): - UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth); - break; - case nameof(MaxHeight): - UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight); - break; - case nameof(HorizontalExpand): - UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand); - break; - case nameof(VerticalExpand): - UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand); - break; - case nameof(HorizontalAlignment): - UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment); - break; - case nameof(VerticalAlignment): - UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment); - break; - case nameof(Margin): - UpdateField(ref _margin, value, LayoutStyleProperties.Margin); - break; - } - } - - // Reset cleared properties back to defaults. - var toClear = _layoutStyleSheet & ~propertiesSet; - if (toClear != 0) - { - ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio); - ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth); - ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight); - ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth); - ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight); - ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth); - ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight); - ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand); - ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand); - ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment); - ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment); - ClearField(ref _margin, default, LayoutStyleProperties.Margin); - } - - _layoutStyleSheet = propertiesSet; - - return; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void UpdateField(ref T field, object value, LayoutStyleProperties flag) - { - if ((_layoutStyleOverride & flag) != 0) - return; - - // TODO: Probably need better error handling... - if (value is not T valueCast) - return; - - field = valueCast; - propertiesSet |= flag; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void ClearField(ref T field, T defaultValue, LayoutStyleProperties flag) - { - if ((toClear & flag) == 0) - return; - - field = defaultValue; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetLayoutStyleProp(LayoutStyleProperties flag) - { - _layoutStyleOverride |= flag; - } - - [Flags] - private enum LayoutStyleProperties : short - { - // @formatter:off - None = 0, - Margin = 1 << 0, - MinWidth = 1 << 1, - MinHeight = 1 << 2, - SetWidth = 1 << 3, - SetHeight = 1 << 4, - MaxWidth = 1 << 5, - MaxHeight = 1 << 6, - StretchRatio = 1 << 7, - HorizontalExpand = 1 << 8, - VerticalExpand = 1 << 9, - HorizontalAlignment = 1 << 10, - VerticalAlignment = 1 << 11, - // @formatter:on - } -} diff --git a/Robust.Client/UserInterface/Control.Layout.cs b/Robust.Client/UserInterface/Control.Layout.cs index 1bf1aee901d..88872de3cd4 100644 --- a/Robust.Client/UserInterface/Control.Layout.cs +++ b/Robust.Client/UserInterface/Control.Layout.cs @@ -12,30 +12,24 @@ namespace Robust.Client.UserInterface public partial class Control { - private const float DefaultStretchRatio = 1; - private const float DefaultSetSize = float.NaN; - private const float DefaultMaxSize = float.NaN; - private const HAlignment DefaultHAlignment = HAlignment.Stretch; - private const VAlignment DefaultVAlignment = VAlignment.Stretch; - private Vector2 _size; [ViewVariables] internal Vector2? PreviousMeasure; [ViewVariables] internal UIBox2? PreviousArrange; - private float _sizeFlagsStretchRatio = DefaultStretchRatio; + private float _sizeFlagsStretchRatio = 1; private float _minWidth; private float _minHeight; - private float _setWidth = DefaultSetSize; - private float _setHeight = DefaultSetSize; - private float _maxWidth = DefaultMaxSize; - private float _maxHeight = DefaultMaxSize; + private float _setWidth = float.NaN; + private float _setHeight = float.NaN; + private float _maxWidth = float.PositiveInfinity; + private float _maxHeight = float.PositiveInfinity; private bool _horizontalExpand; private bool _verticalExpand; - private HAlignment _horizontalAlignment = DefaultHAlignment; - private VAlignment _verticalAlignment = DefaultVAlignment; + private HAlignment _horizontalAlignment = HAlignment.Stretch; + private VAlignment _verticalAlignment = VAlignment.Stretch; private Thickness _margin; private bool _measuring; private bool _arranging; @@ -59,7 +53,6 @@ public Thickness Margin set { _margin = value; - SetLayoutStyleProp(LayoutStyleProperties.Margin); InvalidateMeasure(); } } @@ -249,7 +242,6 @@ public HAlignment HorizontalAlignment set { _horizontalAlignment = value; - SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment); InvalidateArrange(); } } @@ -266,7 +258,6 @@ public VAlignment VerticalAlignment set { _verticalAlignment = value; - SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment); InvalidateArrange(); } } @@ -285,7 +276,6 @@ public bool HorizontalExpand set { _horizontalExpand = value; - SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand); Parent?.InvalidateMeasure(); } } @@ -304,7 +294,6 @@ public bool VerticalExpand set { _verticalExpand = value; - SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand); Parent?.InvalidateArrange(); } } @@ -329,7 +318,6 @@ public float SizeFlagsStretchRatio _sizeFlagsStretchRatio = value; - SetLayoutStyleProp(LayoutStyleProperties.StretchRatio); Parent?.InvalidateArrange(); } } @@ -406,7 +394,6 @@ public float MinWidth set { _minWidth = value; - SetLayoutStyleProp(LayoutStyleProperties.MinWidth); InvalidateMeasure(); } } @@ -421,7 +408,6 @@ public float MinHeight set { _minHeight = value; - SetLayoutStyleProp(LayoutStyleProperties.MinHeight); InvalidateMeasure(); } } @@ -436,7 +422,6 @@ public float SetWidth set { _setWidth = value; - SetLayoutStyleProp(LayoutStyleProperties.SetWidth); InvalidateMeasure(); } } @@ -451,7 +436,6 @@ public float SetHeight set { _setHeight = value; - SetLayoutStyleProp(LayoutStyleProperties.SetHeight); InvalidateMeasure(); } } @@ -466,7 +450,6 @@ public float MaxWidth set { _maxWidth = value; - SetLayoutStyleProp(LayoutStyleProperties.MaxWidth); InvalidateMeasure(); } } @@ -481,7 +464,6 @@ public float MaxHeight set { _maxHeight = value; - SetLayoutStyleProp(LayoutStyleProperties.MaxHeight); InvalidateMeasure(); } } diff --git a/Robust.Client/UserInterface/Control.Styling.cs b/Robust.Client/UserInterface/Control.Styling.cs index fbe7a1e7004..4f54cf56444 100644 --- a/Robust.Client/UserInterface/Control.Styling.cs +++ b/Robust.Client/UserInterface/Control.Styling.cs @@ -239,7 +239,6 @@ internal void DoStyleUpdate() protected virtual void StylePropertiesChanged() { - UpdateLayoutStyleProperties(); InvalidateMeasure(); } From 44cc7127fa8cb475b54100643448ec15023051a1 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:28:16 +1000 Subject: [PATCH 084/130] Default SetWorldPos to 0 rotation (#5034) More closely aligns with the old default. --- .../Systems/SharedTransformSystem.Component.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 31c48daaee1..a9ee06f7002 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -993,7 +993,7 @@ public void SetWorldPosition(TransformComponent component, Vector2 worldPos) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPosition(Entity entity, Vector2 worldPos) { - SetWorldPositionRotation(entity.Owner, worldPos, entity.Comp.LocalRotation, entity.Comp); + SetWorldPositionRotationInternal(entity.Owner, worldPos, null, entity.Comp); } #endregion @@ -1079,10 +1079,17 @@ public void SetWorldPositionRotation(TransformComponent component, Vector2 world [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, TransformComponent? component = null) + { + SetWorldPositionRotationInternal(uid, worldPos, worldRot, component); + } + + private void SetWorldPositionRotationInternal(EntityUid uid, Vector2 worldPos, Angle? worldRot = null, TransformComponent? component = null) { if (!XformQuery.Resolve(uid, ref component)) return; + // If no worldRot supplied then default the new rotation to 0. + if (!component._parent.IsValid() || component.MapUid == null) { DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?"); @@ -1095,11 +1102,11 @@ public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worl var invLocalMatrix = targetGridXform.InvLocalMatrix; var gridRot = targetGridXform.LocalRotation; var localRot = worldRot - gridRot; - SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invLocalMatrix.Transform(worldPos)), rotation: localRot); + SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invLocalMatrix.Transform(worldPos)), rotation: localRot ?? Angle.Zero); } else { - SetCoordinates(uid, component, new EntityCoordinates(component.MapUid.Value, worldPos), rotation: worldRot); + SetCoordinates(uid, component, new EntityCoordinates(component.MapUid.Value, worldPos), rotation: worldRot ?? Angle.Zero); } } From cd24fd46b6cabb37375cd004fb46336489059ef9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:47:30 +1000 Subject: [PATCH 085/130] Default worldpos to null (#5036) I think this is slightly more robust than defaulting to 0 actually while still fixing the issue. --- .../GameObjects/Systems/SharedTransformSystem.Component.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index a9ee06f7002..a60cef3a836 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -1102,11 +1102,11 @@ private void SetWorldPositionRotationInternal(EntityUid uid, Vector2 worldPos, A var invLocalMatrix = targetGridXform.InvLocalMatrix; var gridRot = targetGridXform.LocalRotation; var localRot = worldRot - gridRot; - SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invLocalMatrix.Transform(worldPos)), rotation: localRot ?? Angle.Zero); + SetCoordinates(uid, component, new EntityCoordinates(targetGrid, invLocalMatrix.Transform(worldPos)), rotation: localRot); } else { - SetCoordinates(uid, component, new EntityCoordinates(component.MapUid.Value, worldPos), rotation: worldRot ?? Angle.Zero); + SetCoordinates(uid, component, new EntityCoordinates(component.MapUid.Value, worldPos), rotation: worldRot); } } From fd60dc288766fce6ddcce6741177a8b2437524a2 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:55:55 +1000 Subject: [PATCH 086/130] Add EntManager + ProtoManager helpers for random picks (#4869) * Add EntManager + ProtoManager helpers for random picks Lets us cleanup content a bit from these being re-implemented. * weh --- RELEASE-NOTES.md | 3 +- Robust.Shared/GameObjects/EntityManagerExt.cs | 79 ++++++++++++++++++- Robust.Shared/Prototypes/IPrototypeManager.cs | 6 ++ Robust.Shared/Prototypes/PrototypeManager.cs | 28 +++++++ 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index feb2eeae6ff..dd2fb5d95b5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,8 +39,7 @@ END TEMPLATE--> ### New features -* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". -* Added non-generic variant of `GetCVar` to `IConfigurationManager`. +* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type. ### Bugfixes diff --git a/Robust.Shared/GameObjects/EntityManagerExt.cs b/Robust.Shared/GameObjects/EntityManagerExt.cs index f99ad91ea5d..f9ee17da860 100644 --- a/Robust.Shared/GameObjects/EntityManagerExt.cs +++ b/Robust.Shared/GameObjects/EntityManagerExt.cs @@ -1,4 +1,7 @@ -namespace Robust.Shared.GameObjects +using Robust.Shared.Collections; +using Robust.Shared.Random; + +namespace Robust.Shared.GameObjects { public static class EntityManagerExt { @@ -19,5 +22,79 @@ public static class EntityManagerExt return default; } + + /// + /// Picks an entity at random with the supplied component. + /// + public static bool TryGetRandom(this IEntityManager entManager, IRobustRandom random, out EntityUid entity, bool includePaused = false) where TComp1 : IComponent + { + var entities = new ValueList(); + + if (includePaused) + { + var query = entManager.AllEntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _)) + { + entities.Add(uid); + } + } + else + { + var query = entManager.EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _)) + { + entities.Add(uid); + } + } + + if (entities.Count == 0) + { + entity = EntityUid.Invalid; + return false; + } + + entity = random.Pick(entities); + return true; + } + + /// + /// Picks an entity at random with the supplied components. + /// + public static bool TryGetRandom(this IEntityManager entManager, IRobustRandom random, out EntityUid entity, bool includePaused = false) + where TComp1 : IComponent + where TComp2 : IComponent + { + var entities = new ValueList(); + + if (includePaused) + { + var query = entManager.AllEntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _, out _)) + { + entities.Add(uid); + } + } + else + { + var query = entManager.EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _, out _)) + { + entities.Add(uid); + } + } + + if (entities.Count == 0) + { + entity = EntityUid.Invalid; + return false; + } + + entity = random.Pick(entities); + return true; + } } } diff --git a/Robust.Shared/Prototypes/IPrototypeManager.cs b/Robust.Shared/Prototypes/IPrototypeManager.cs index 1922e1a9c1c..e070d978495 100644 --- a/Robust.Shared/Prototypes/IPrototypeManager.cs +++ b/Robust.Shared/Prototypes/IPrototypeManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using Robust.Shared.Random; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown; @@ -386,6 +387,11 @@ void ReloadPrototypes(Dictionary> modified, /// For example: /Prototypes/Guidebook /// void AbstractDirectory(ResPath path); + + /// + /// Tries to get a random prototype. + /// + bool TryGetRandom(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype; } internal interface IPrototypeManagerInternal : IPrototypeManager diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 4292ef58292..6020e602e68 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -1082,6 +1082,34 @@ public IReadOnlyDictionary GetPrototypeData(EntityProto _prototypeDataCache[prototype.ID] = data; return data; } + + public bool TryGetRandom(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype + { + var count = Count(); + + if (count == 0) + { + prototype = null; + return false; + } + + var index = 0; + + var picked = random.Next(count); + + foreach (var proto in EnumeratePrototypes()) + { + if (index == picked) + { + prototype = proto; + return true; + } + + index++; + } + + throw new ArgumentOutOfRangeException($"Unable to pick valid prototype for {typeof(T)}?"); + } } public sealed class InvalidPrototypeNameException : Exception From af8fb52a4f057fbfef1d285e12b98915de870f5c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 14 Apr 2024 15:00:07 +1000 Subject: [PATCH 087/130] Version: 218.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 37 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 0187983222a..386c60285ef 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 217.2.1 + 218.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index dd2fb5d95b5..b6520c518c7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,23 +35,56 @@ END TEMPLATE--> ### Breaking changes +*None yet* + +### New features + +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 218.0.0 + +### Breaking changes + * `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content. ### New features * Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type. +* Add CopyData to AppearanceSystem. +* Update UI themes on prototype reloads. +* Allow scaling the line height of a RichTextLabel. +* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". +* Added non-generic variant of `GetCVar` to `IConfigurationManager`. +* Add type tracking to FieldNotFoundErrorNode for serialization. ### Bugfixes * Request headers in `IStatusHandlerContext` are now case-insensitive. +* SetWorldPosition rotation now more closely aligns with prior behavior. +* Fix exception when inspecting elements in some cases. +* Fix HTTP errors on watchdog ping not being reported. ### Other -*None yet* +* Add an analyzer for redundantly assigning to dependency fields. ### Internal -*None yet* +* Remove redundant Exists checks in ContainerSystem. +* Improve logging on watchdog pings. ## 217.2.1 From 03a4d3e0a0e922a7c3aa2b9bf5af0eee924ad635 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 08:36:25 +0200 Subject: [PATCH 088/130] Add IEquatable`1.Equals to sandbox Why wasn't this in here wtf. --- Robust.Shared/ContentPack/Sandbox.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 897e2a97672..201244c6ea1 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -951,7 +951,9 @@ Types: IComparable: { All: True } IComparable`1: { All: True } IDisposable: { All: True } - IEquatable`1: { } + IEquatable`1: + Methods: + - "bool Equals(!0)" IFormatProvider: { All: True } IFormattable: { All: True } Index: { All: True } From b50f68866fb39bce4fc046edd6dbb54b1066dd8e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Apr 2024 09:26:07 +0200 Subject: [PATCH 089/130] Enable roslyn extension tests in CI (#5038) * Enable roslyn extension tests in CI * I'll be real I kinda just hoped that last one would work. dotnet test's --help documentation is useless garbage so I couldn't tell if that was supported or not. Guess not. * Actually fix the Roslyn tests. As far as I can tell, Roslyn tests haven't worked since #2976. The tests used a pretty awful technique of linking the test code against the analyzer, so that the analyzer's copy of the relevant attributes got included into the test. This then broke when the namespace got changed by the linked PR. Now the tests get an EmbeddedResource for the necessary test files compiled instead. Also applied this to DependencyAssignAnalyzerTest because why not. --- .github/workflows/build-test.yml | 5 +++-- Robust.Analyzers.Tests/AccessAnalyzer_Test.cs | 7 +++++- .../DependencyAssignAnalyzerTest.cs | 19 +++++----------- .../Robust.Analyzers.Tests.csproj | 7 ++++++ Robust.Analyzers.Tests/TestHelper.cs | 22 +++++++++++++++++++ Robust.Analyzers/Robust.Analyzers.csproj | 13 +++++++---- Robust.Shared/Analyzers/AccessAttribute.cs | 2 +- Robust.Shared/Analyzers/AccessPermissions.cs | 2 +- .../Analyzers/NotNullableFlagAttribute.cs | 2 +- .../PreferGenericVariantAttribute.cs | 2 +- 10 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 Robust.Analyzers.Tests/TestHelper.cs diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 7d113e790ac..38eb985ac7a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -27,7 +27,8 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore /p:WarningsAsErrors=nullable - - name: Test Engine + - name: Robust.UnitTesting run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0 - + - name: Robust.Analyzers.Tests + run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0 diff --git a/Robust.Analyzers.Tests/AccessAnalyzer_Test.cs b/Robust.Analyzers.Tests/AccessAnalyzer_Test.cs index 49be0c67ffc..ab6e46111b1 100644 --- a/Robust.Analyzers.Tests/AccessAnalyzer_Test.cs +++ b/Robust.Analyzers.Tests/AccessAnalyzer_Test.cs @@ -20,11 +20,16 @@ public Task Verifier(string code, params DiagnosticResult[] expected) { TestState = { - AdditionalReferences = { typeof(AccessAnalyzer).Assembly }, Sources = { code } }, }; + TestHelper.AddEmbeddedSources( + test.TestState, + "Robust.Shared.Analyzers.AccessAttribute.cs", + "Robust.Shared.Analyzers.AccessPermissions.cs" + ); + // ExpectedDiagnostics cannot be set, so we need to AddRange here... test.TestState.ExpectedDiagnostics.AddRange(expected); diff --git a/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs b/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs index f8b696992f1..f55a2cf65a0 100644 --- a/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs +++ b/Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs @@ -12,28 +12,21 @@ namespace Robust.Analyzers.Tests; [TestFixture] public sealed class DependencyAssignAnalyzerTest { - private const string BaseCode = """ - using System; - - namespace Robust.Shared.IoC; - - [AttributeUsage(AttributeTargets.Field)] - public sealed class DependencyAttribute : Attribute - { - } - """; - private static Task Verifier(string code, params DiagnosticResult[] expected) { var test = new CSharpAnalyzerTest() { TestState = { - AdditionalReferences = { typeof(DependencyAssignAnalyzer).Assembly }, - Sources = { code, BaseCode } + Sources = { code } }, }; + TestHelper.AddEmbeddedSources( + test.TestState, + "Robust.Shared.IoC.DependencyAttribute.cs" + ); + // ExpectedDiagnostics cannot be set, so we need to AddRange here... test.TestState.ExpectedDiagnostics.AddRange(expected); diff --git a/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj b/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj index 954204dcbe4..513bdebfbfa 100644 --- a/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj +++ b/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj @@ -6,6 +6,13 @@ + + + + + + + false diff --git a/Robust.Analyzers.Tests/TestHelper.cs b/Robust.Analyzers.Tests/TestHelper.cs new file mode 100644 index 00000000000..2e5e6374c3e --- /dev/null +++ b/Robust.Analyzers.Tests/TestHelper.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Robust.Analyzers.Tests; + +public static class TestHelper +{ + public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles) + { + AddEmbeddedSources(state, (IEnumerable) embeddedFiles); + } + + public static void AddEmbeddedSources(SolutionState state, IEnumerable embeddedFiles) + { + foreach (var fileName in embeddedFiles) + { + using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!; + state.Sources.Add((fileName, SourceText.From(stream))); + } + } +} diff --git a/Robust.Analyzers/Robust.Analyzers.csproj b/Robust.Analyzers/Robust.Analyzers.csproj index c115cd001bf..8bbb58f0fcf 100644 --- a/Robust.Analyzers/Robust.Analyzers.csproj +++ b/Robust.Analyzers/Robust.Analyzers.csproj @@ -2,24 +2,29 @@ - + - - + + - + disable + + $(DefineConstants);ROBUST_ANALYZERS_IMPL diff --git a/Robust.Shared/Analyzers/AccessAttribute.cs b/Robust.Shared/Analyzers/AccessAttribute.cs index 94e2a91db2b..aa84ce7711a 100644 --- a/Robust.Shared/Analyzers/AccessAttribute.cs +++ b/Robust.Shared/Analyzers/AccessAttribute.cs @@ -1,6 +1,6 @@ using System; -#if NETSTANDARD2_0 +#if ROBUST_ANALYZERS_IMPL namespace Robust.Shared.Analyzers.Implementation; #else namespace Robust.Shared.Analyzers; diff --git a/Robust.Shared/Analyzers/AccessPermissions.cs b/Robust.Shared/Analyzers/AccessPermissions.cs index 88ebd1a5e2a..13ea5c503a3 100644 --- a/Robust.Shared/Analyzers/AccessPermissions.cs +++ b/Robust.Shared/Analyzers/AccessPermissions.cs @@ -1,6 +1,6 @@ using System; -#if NETSTANDARD2_0 +#if ROBUST_ANALYZERS_IMPL namespace Robust.Shared.Analyzers.Implementation; #else namespace Robust.Shared.Analyzers; diff --git a/Robust.Shared/Analyzers/NotNullableFlagAttribute.cs b/Robust.Shared/Analyzers/NotNullableFlagAttribute.cs index 518f1a18c94..5b4a1722a2c 100644 --- a/Robust.Shared/Analyzers/NotNullableFlagAttribute.cs +++ b/Robust.Shared/Analyzers/NotNullableFlagAttribute.cs @@ -1,6 +1,6 @@ using System; -#if NETSTANDARD2_0 +#if ROBUST_ANALYZERS_IMPL namespace Robust.Shared.Analyzers.Implementation; #else namespace Robust.Shared.Analyzers; diff --git a/Robust.Shared/Analyzers/PreferGenericVariantAttribute.cs b/Robust.Shared/Analyzers/PreferGenericVariantAttribute.cs index 2b713f121fe..426148795cc 100644 --- a/Robust.Shared/Analyzers/PreferGenericVariantAttribute.cs +++ b/Robust.Shared/Analyzers/PreferGenericVariantAttribute.cs @@ -1,6 +1,6 @@ using System; -#if NETSTANDARD2_0 +#if ROBUST_ANALYZERS_IMPL namespace Robust.Shared.Analyzers.Implementation; #else namespace Robust.Shared.Analyzers; From e2525a210333d97f0c071f39c984f37eff14ac60 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Mon, 15 Apr 2024 19:13:43 +0200 Subject: [PATCH 090/130] Fix division remainder issue in color.cs (#5040) --- Robust.Shared.Maths/Color.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Robust.Shared.Maths/Color.cs b/Robust.Shared.Maths/Color.cs index 0d341552db5..4e56b73cb9e 100644 --- a/Robust.Shared.Maths/Color.cs +++ b/Robust.Shared.Maths/Color.cs @@ -592,7 +592,11 @@ public static Vector4 ToHsv(Color rgb) if (c != 0) { if (max == rgb.R) + { h = (rgb.G - rgb.B) / c % 6.0f; + if (h < 0f) + h += 6.0f; + } else if (max == rgb.G) h = (rgb.B - rgb.R) / c + 2.0f; else if (max == rgb.B) @@ -774,7 +778,11 @@ public static Vector4 ToHcy(Color rgb) var h = 0.0f; if (max == rgb.R) + { h = (rgb.G - rgb.B) / c % 6.0f; + if (h < 0f) + h += 6.0f; + } else if (max == rgb.G) h = (rgb.B - rgb.R) / c + 2.0f; else if (max == rgb.B) From a5494d1df2b077384a5aa116ed3518e3338ce217 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 15 Apr 2024 22:27:25 +0200 Subject: [PATCH 091/130] Change default hub URL --- RELEASE-NOTES.md | 2 +- Robust.Shared/CVars.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b6520c518c7..725c63f42f4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,7 +47,7 @@ END TEMPLATE--> ### Other -*None yet* +* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`. ### Internal diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 1656984e3b8..b0988a27fe3 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1445,7 +1445,7 @@ protected CVars() /// Comma-separated list of URLs of hub servers to advertise to. /// public static readonly CVarDef HubUrls = - CVarDef.Create("hub.hub_urls", "https://central.spacestation14.io/hub/", CVar.SERVERONLY); + CVarDef.Create("hub.hub_urls", "https://hub.spacestation14.com/", CVar.SERVERONLY); /// /// URL of this server to advertise. From 37796f4806ef3488027cefb6916fe49bd7e51bdd Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:33:43 +1000 Subject: [PATCH 092/130] Add a Vertical tab container (#4948) * Vertical tab container * weh * Tab review --- .../Controls/VerticalTabContainer.xaml | 34 +++++++ .../Controls/VerticalTabContainer.xaml.cs | 97 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml create mode 100644 Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml.cs diff --git a/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml b/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml new file mode 100644 index 00000000000..09202d49c63 --- /dev/null +++ b/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml.cs b/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml.cs new file mode 100644 index 00000000000..0c9ce2dc1dd --- /dev/null +++ b/Robust.Client/UserInterface/Controls/VerticalTabContainer.xaml.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Maths; + +namespace Robust.Client.UserInterface.Controls; + +[GenerateTypedNameReferences] +public sealed partial class VerticalTabContainer : BoxContainer +{ + private readonly Dictionary _tabs = new(); + + // Just used to order controls in case one gets removed. + private readonly List _controls = new(); + + private readonly ButtonGroup _tabGroup = new(false); + + private Control? _currentControl; + + public VerticalTabContainer() + { + RobustXamlLoader.Load(this); + } + + public int AddTab(Control control, string title) + { + var button = new Button() + { + Text = title, + Group = _tabGroup, + }; + + TabContainer.AddChild(button); + ContentsContainer.AddChild(control); + var index = ChildCount - 1; + button.OnPressed += args => + { + SelectTab(control); + }; + + _controls.Add(control); + _tabs.Add(control, button); + + // Existing tabs + if (ContentsContainer.ChildCount > 1) + { + control.Visible = false; + } + // First tab + else + { + SelectTab(control); + } + + return index; + } + + protected override void ChildRemoved(Control child) + { + if (_tabs.Remove(child, out var button)) + { + button.Dispose(); + } + + // Set the current tab to a different control + if (_currentControl == child) + { + var previous = _controls.IndexOf(child) - 1; + + if (previous > -1) + { + var setControl = _controls[previous]; + SelectTab(setControl); + } + else + { + _currentControl = null; + } + } + + _controls.Remove(child); + base.ChildRemoved(child); + } + + private void SelectTab(Control control) + { + if (_currentControl != null) + { + _currentControl.Visible = false; + } + + var button = _tabs[control]; + button.Pressed = true; + control.Visible = true; + _currentControl = control; + } +} From a6e7224672402c36f0b61d7c082427b4ff31250e Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 16 Apr 2024 22:39:23 +1000 Subject: [PATCH 093/130] Version: 218.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 386c60285ef..2945cdf0465 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 218.0.0 + 218.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 725c63f42f4..ebecb90abf7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,13 +47,30 @@ END TEMPLATE--> ### Other -* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`. +*None yet* ### Internal *None yet* +## 218.1.0 + +### New features + +* Add IEquatable.Equals to the sandbox. +* Enable roslyn extensions tests in CI. +* Add a VerticalTabContainer control to match the horizontal one. + +### Bugfixes + +* Fix divison remainder issue for Colors, fixing purples. + +### Other + +* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`. + + ## 218.0.0 ### Breaking changes From 0fadfc2d9b0308aec3bcb29fb82487451ac43aff Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 17 Apr 2024 00:42:12 +0200 Subject: [PATCH 094/130] Allow control layout properties to be set via style sheet. (take 2) (#5037) * Reapply "Allow control layout properties to be set via style sheet." (#5035) This reverts commit af36d24892dd2a9a469c016b8179fab46dcbb09b. * Fix tests MaxSize properties had wrong default. Oops. --- RELEASE-NOTES.md | 5 +- .../UserInterface/Control.Layout.Styling.cs | 132 ++++++++++++++++++ Robust.Client/UserInterface/Control.Layout.cs | 32 ++++- .../UserInterface/Control.Styling.cs | 1 + 4 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 Robust.Client/UserInterface/Control.Layout.Styling.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ebecb90abf7..20bd983bfb9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Control layout properties such as `Margin` can now be set via style sheets. ### Bugfixes @@ -86,6 +86,9 @@ END TEMPLATE--> * You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".". * Added non-generic variant of `GetCVar` to `IConfigurationManager`. * Add type tracking to FieldNotFoundErrorNode for serialization. +* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`. +* UI theme prototypes are now updated when reloaded. +* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields. ### Bugfixes diff --git a/Robust.Client/UserInterface/Control.Layout.Styling.cs b/Robust.Client/UserInterface/Control.Layout.Styling.cs new file mode 100644 index 00000000000..fdc1a4a4c83 --- /dev/null +++ b/Robust.Client/UserInterface/Control.Layout.Styling.cs @@ -0,0 +1,132 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Robust.Client.UserInterface; + +public partial class Control +{ + private LayoutStyleProperties _layoutStyleOverride; + private LayoutStyleProperties _layoutStyleSheet; + + private void UpdateLayoutStyleProperties() + { + var propertiesSet = LayoutStyleProperties.None; + + // Assumed most controls will have little or no style properties, + // so iterating once is less expensive overall then checking 10+ properties. + // C# switch statements are compiled efficiently anyways. + foreach (var (key, value) in _styleProperties) + { + switch (key) + { + case nameof(SizeFlagsStretchRatio): + UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio); + break; + case nameof(MinWidth): + UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth); + break; + case nameof(MinHeight): + UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight); + break; + case nameof(SetWidth): + UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth); + break; + case nameof(SetHeight): + UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight); + break; + case nameof(MaxWidth): + UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth); + break; + case nameof(MaxHeight): + UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight); + break; + case nameof(HorizontalExpand): + UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand); + break; + case nameof(VerticalExpand): + UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand); + break; + case nameof(HorizontalAlignment): + UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment); + break; + case nameof(VerticalAlignment): + UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment); + break; + case nameof(Margin): + UpdateField(ref _margin, value, LayoutStyleProperties.Margin); + break; + } + } + + // Reset cleared properties back to defaults. + var toClear = _layoutStyleSheet & ~propertiesSet; + if (toClear != 0) + { + ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio); + ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth); + ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight); + ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth); + ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight); + ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth); + ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight); + ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand); + ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand); + ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment); + ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment); + ClearField(ref _margin, default, LayoutStyleProperties.Margin); + } + + _layoutStyleSheet = propertiesSet; + + return; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void UpdateField(ref T field, object value, LayoutStyleProperties flag) + { + if ((_layoutStyleOverride & flag) != 0) + return; + + // TODO: Probably need better error handling... + if (value is not T valueCast) + return; + + field = valueCast; + propertiesSet |= flag; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ClearField(ref T field, T defaultValue, LayoutStyleProperties flag) + { + if ((toClear & flag) == 0) + return; + + field = defaultValue; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetLayoutStyleProp(LayoutStyleProperties flag) + { + _layoutStyleOverride |= flag; + } + + [Flags] + private enum LayoutStyleProperties : short + { + // @formatter:off + None = 0, + Margin = 1 << 0, + MinWidth = 1 << 1, + MinHeight = 1 << 2, + SetWidth = 1 << 3, + SetHeight = 1 << 4, + MaxWidth = 1 << 5, + MaxHeight = 1 << 6, + StretchRatio = 1 << 7, + HorizontalExpand = 1 << 8, + VerticalExpand = 1 << 9, + HorizontalAlignment = 1 << 10, + VerticalAlignment = 1 << 11, + // @formatter:on + } +} diff --git a/Robust.Client/UserInterface/Control.Layout.cs b/Robust.Client/UserInterface/Control.Layout.cs index 88872de3cd4..95904dda045 100644 --- a/Robust.Client/UserInterface/Control.Layout.cs +++ b/Robust.Client/UserInterface/Control.Layout.cs @@ -12,24 +12,30 @@ namespace Robust.Client.UserInterface public partial class Control { + private const float DefaultStretchRatio = 1; + private const float DefaultSetSize = float.NaN; + private const float DefaultMaxSize = float.PositiveInfinity; + private const HAlignment DefaultHAlignment = HAlignment.Stretch; + private const VAlignment DefaultVAlignment = VAlignment.Stretch; + private Vector2 _size; [ViewVariables] internal Vector2? PreviousMeasure; [ViewVariables] internal UIBox2? PreviousArrange; - private float _sizeFlagsStretchRatio = 1; + private float _sizeFlagsStretchRatio = DefaultStretchRatio; private float _minWidth; private float _minHeight; - private float _setWidth = float.NaN; - private float _setHeight = float.NaN; - private float _maxWidth = float.PositiveInfinity; - private float _maxHeight = float.PositiveInfinity; + private float _setWidth = DefaultSetSize; + private float _setHeight = DefaultSetSize; + private float _maxWidth = DefaultMaxSize; + private float _maxHeight = DefaultMaxSize; private bool _horizontalExpand; private bool _verticalExpand; - private HAlignment _horizontalAlignment = HAlignment.Stretch; - private VAlignment _verticalAlignment = VAlignment.Stretch; + private HAlignment _horizontalAlignment = DefaultHAlignment; + private VAlignment _verticalAlignment = DefaultVAlignment; private Thickness _margin; private bool _measuring; private bool _arranging; @@ -53,6 +59,7 @@ public Thickness Margin set { _margin = value; + SetLayoutStyleProp(LayoutStyleProperties.Margin); InvalidateMeasure(); } } @@ -242,6 +249,7 @@ public HAlignment HorizontalAlignment set { _horizontalAlignment = value; + SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment); InvalidateArrange(); } } @@ -258,6 +266,7 @@ public VAlignment VerticalAlignment set { _verticalAlignment = value; + SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment); InvalidateArrange(); } } @@ -276,6 +285,7 @@ public bool HorizontalExpand set { _horizontalExpand = value; + SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand); Parent?.InvalidateMeasure(); } } @@ -294,6 +304,7 @@ public bool VerticalExpand set { _verticalExpand = value; + SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand); Parent?.InvalidateArrange(); } } @@ -318,6 +329,7 @@ public float SizeFlagsStretchRatio _sizeFlagsStretchRatio = value; + SetLayoutStyleProp(LayoutStyleProperties.StretchRatio); Parent?.InvalidateArrange(); } } @@ -394,6 +406,7 @@ public float MinWidth set { _minWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.MinWidth); InvalidateMeasure(); } } @@ -408,6 +421,7 @@ public float MinHeight set { _minHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.MinHeight); InvalidateMeasure(); } } @@ -422,6 +436,7 @@ public float SetWidth set { _setWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.SetWidth); InvalidateMeasure(); } } @@ -436,6 +451,7 @@ public float SetHeight set { _setHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.SetHeight); InvalidateMeasure(); } } @@ -450,6 +466,7 @@ public float MaxWidth set { _maxWidth = value; + SetLayoutStyleProp(LayoutStyleProperties.MaxWidth); InvalidateMeasure(); } } @@ -464,6 +481,7 @@ public float MaxHeight set { _maxHeight = value; + SetLayoutStyleProp(LayoutStyleProperties.MaxHeight); InvalidateMeasure(); } } diff --git a/Robust.Client/UserInterface/Control.Styling.cs b/Robust.Client/UserInterface/Control.Styling.cs index 4f54cf56444..fbe7a1e7004 100644 --- a/Robust.Client/UserInterface/Control.Styling.cs +++ b/Robust.Client/UserInterface/Control.Styling.cs @@ -239,6 +239,7 @@ internal void DoStyleUpdate() protected virtual void StylePropertiesChanged() { + UpdateLayoutStyleProperties(); InvalidateMeasure(); } From 8c25a830666dfe6e4ea616e02dcc2f9945a88b1b Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:17:12 -0700 Subject: [PATCH 095/130] Expose worldPosition in SpriteComponent.Render (#5043) * Expose worldPosition in SpriteComponent.Render * Set default value to zero --- .../GameObjects/Components/Renderable/SpriteComponent.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index e29d45c5853..f42d3a8afa6 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -8,11 +8,9 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.Utility; -using Robust.Shared; using Robust.Shared.Animations; using Robust.Shared.ComponentTrees; using Robust.Shared.GameObjects; -using Robust.Shared.Graphics; using Robust.Shared.Graphics.RSI; using Robust.Shared.IoC; using Robust.Shared.Log; @@ -1237,9 +1235,9 @@ public RSI.StateId LayerGetState(int layer) public IEnumerable AllLayers => Layers; // Lobby SpriteView rendering path - public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null) + public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default) { - RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection); + RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection); } [DataField("noRot")] private bool _screenLock = false; From 0a00e7ec294be91591c0e98c9a1afb1767935256 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:55:22 +1000 Subject: [PATCH 096/130] Network audio states (#5024) * Network audio states Lets server pause or stop audio or whatever. * Better API and state fix * Bunch of fixes - TimedDespawn proccing too early. - PlaybackPosition setting - SetState setting. * Clamps and despawn fixes * fix --- Robust.Client/Audio/AudioSystem.cs | 27 ++++ .../Audio/Sources/BaseAudioSource.cs | 2 + .../Audio/Components/AudioComponent.cs | 25 ++- .../Audio/Systems/SharedAudioSystem.cs | 143 ++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index d3d33cbe6f9..1838d5655de 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -126,6 +126,33 @@ private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAuto { component.Source.SetAuxiliary(null); } + + switch (component.State) + { + case AudioState.Playing: + component.StartPlaying(); + break; + case AudioState.Paused: + component.Pause(); + break; + case AudioState.Stopped: + component.StopPlaying(); + component.PlaybackPosition = 0f; + break; + } + + // If playback position changed then update it. + if (!string.IsNullOrEmpty(component.FileName)) + { + var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds; + var currentPosition = component.Source.PlaybackPosition; + var diff = Math.Abs(position - currentPosition); + + if (diff > 0.1f) + { + component.PlaybackPosition = position; + } + } } /// diff --git a/Robust.Client/Audio/Sources/BaseAudioSource.cs b/Robust.Client/Audio/Sources/BaseAudioSource.cs index 1523b41194e..073e25a9fa7 100644 --- a/Robust.Client/Audio/Sources/BaseAudioSource.cs +++ b/Robust.Client/Audio/Sources/BaseAudioSource.cs @@ -314,6 +314,8 @@ public float PlaybackPosition set { _checkDisposed(); + + value = MathF.Max(value, 0f); AL.Source(SourceHandle, ALSourcef.SecOffset, value); Master._checkAlError($"Tried to set invalid playback position of {value:0.00}"); } diff --git a/Robust.Shared/Audio/Components/AudioComponent.cs b/Robust.Shared/Audio/Components/AudioComponent.cs index 2b1162617b8..c2b104670c6 100644 --- a/Robust.Shared/Audio/Components/AudioComponent.cs +++ b/Robust.Shared/Audio/Components/AudioComponent.cs @@ -6,6 +6,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.ViewVariables; @@ -90,11 +91,24 @@ public sealed partial class AudioComponent : Component, IAudioSource public void StartPlaying() => Source.StartPlaying(); /// - public void StopPlaying() => Source.StopPlaying(); + public void StopPlaying() + { + PlaybackPosition = 0f; + Source.StopPlaying(); + } /// public void Restart() => Source.Restart(); + [DataField, AutoNetworkedField] + public AudioState State = AudioState.Playing; + + /// + /// Time when the audio was paused so we can offset it later if relevant. + /// + [DataField, AutoNetworkedField] + public TimeSpan? PauseTime; + /// /// /// @@ -208,6 +222,7 @@ public float Occlusion /// /// [ViewVariables] + [Access(Other = AccessPermissions.ReadWriteExecute)] public float PlaybackPosition { get => Source.PlaybackPosition; @@ -240,6 +255,14 @@ public void Dispose() } } +[Serializable, NetSerializable] +public enum AudioState : byte +{ + Stopped, + Playing, + Paused, +} + [Flags] public enum AudioFlags : byte { diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 93c609488fa..1013554cfbf 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -55,6 +55,141 @@ public override void Initialize() SubscribeLocalEvent(OnAudioUnpaused); } + /// + /// Sets the playback position of audio to the specified spot. + /// + public void SetPlaybackPosition(Entity? nullEntity, float position) + { + if (nullEntity == null) + return; + + var entity = nullEntity.Value; + + if (!Resolve(entity.Owner, ref entity.Comp, false)) + return; + + var audioLength = GetAudioLength(entity.Comp.FileName); + + if (audioLength.TotalSeconds < position) + { + // Just stop it and return + if (!_netManager.IsClient) + QueueDel(nullEntity.Value); + + entity.Comp.StopPlaying(); + return; + } + + if (position < 0f) + { + Log.Error($"Tried to set playback position for {ToPrettyString(entity.Owner)} / {entity.Comp.FileName} outside of bounds"); + return; + } + + // If we're paused then the current position is , else it's + var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart; + var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds); + + DebugTools.Assert(currentPos > TimeSpan.Zero); + + // Rounding. + if (Math.Abs(timeOffset.TotalSeconds) <= 0.01) + { + return; + } + + if (entity.Comp.PauseTime != null) + { + entity.Comp.PauseTime = entity.Comp.PauseTime.Value + timeOffset; + + // Paused audio doesn't have TimedDespawn so. + } + else + { + // Bump it back so the actual playback positions moves forward + entity.Comp.AudioStart -= timeOffset; + + // need to ensure it doesn't despawn too early. + if (TryComp(entity.Owner, out TimedDespawnComponent? despawn)) + { + despawn.Lifetime -= (float) timeOffset.TotalSeconds; + } + } + + entity.Comp.PlaybackPosition = position; + // Network the new playback position. + Dirty(entity); + } + + /// + /// Calculates playback position considering length paused. + /// + /// + /// + private float GetPlaybackPosition(AudioComponent component) + { + return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds; + } + + /// + /// Sets the shared state for an audio entity. + /// + public void SetState(EntityUid? entity, AudioState state, bool force = false, AudioComponent? component = null) + { + if (entity == null || !Resolve(entity.Value, ref component, false)) + return; + + if (component.State == state && !force) + return; + + // Unpause it + if (component.State == AudioState.Paused && state == AudioState.Playing) + { + var pauseOffset = Timing.CurTime - component.PauseTime; + component.AudioStart += pauseOffset ?? TimeSpan.Zero; + component.PlaybackPosition = (float) (Timing.CurTime - component.AudioStart).TotalSeconds; + } + + // If we were stopped then played then restart audiostart to now. + if (component.State == AudioState.Stopped && state == AudioState.Playing) + { + component.AudioStart = Timing.CurTime; + component.PauseTime = null; + } + + switch (state) + { + case AudioState.Stopped: + component.AudioStart = Timing.CurTime; + component.PauseTime = null; + component.StopPlaying(); + RemComp(entity.Value); + break; + case AudioState.Paused: + // Set it to current time so we can easily unpause it later. + component.PauseTime = Timing.CurTime; + component.Pause(); + RemComp(entity.Value); + break; + case AudioState.Playing: + component.PauseTime = null; + component.StartPlaying(); + + // Reset TimedDespawn so the audio still gets cleaned up. + + if (!component.Looping) + { + var timed = EnsureComp(entity.Value); + var audioLength = GetAudioLength(component.FileName); + timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f; + } + break; + } + + component.State = state; + Dirty(entity.Value, component); + } + protected void SetZOffset(float value) { ZOffset = value; @@ -505,4 +640,12 @@ protected sealed class PlayAudioEntityMessage : AudioMessage { public NetEntity NetEntity; } + + public bool IsPlaying(EntityUid? stream, AudioComponent? component = null) + { + if (stream == null || !Resolve(stream.Value, ref component, false)) + return false; + + return component.State == AudioState.Playing; + } } From 8cdec92be625fd81de9dd7a7e2ca2fa7d6ed137e Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:04:52 +1000 Subject: [PATCH 097/130] Add slider locking (#5044) * Add slider locking Useful if you just want like a playback slider without the client intefering. * Name alignment --- Robust.Client/UserInterface/Controls/Slider.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/Controls/Slider.cs b/Robust.Client/UserInterface/Controls/Slider.cs index 29840f1ea8d..f2f9c955257 100644 --- a/Robust.Client/UserInterface/Controls/Slider.cs +++ b/Robust.Client/UserInterface/Controls/Slider.cs @@ -2,6 +2,7 @@ using System.Numerics; using Robust.Client.Graphics; using Robust.Shared.Input; +using Robust.Shared.Log; using Robust.Shared.Maths; using static Robust.Client.UserInterface.Controls.LayoutContainer; @@ -32,6 +33,11 @@ public class Slider : Range public bool Grabbed => _grabbed; + /// + /// Whether the slider can be adjusted. + /// + public bool Disabled { get; set; } + public StyleBox? ForegroundStyleBoxOverride { get => _foregroundStyleBoxOverride; @@ -132,7 +138,7 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) { base.KeyBindDown(args); - if (args.Function != EngineKeyFunctions.UIClick) + if (args.Function != EngineKeyFunctions.UIClick || Disabled) { return; } @@ -146,7 +152,7 @@ protected internal override void KeyBindUp(GUIBoundKeyEventArgs args) { base.KeyBindUp(args); - if (args.Function != EngineKeyFunctions.UIClick) return; + if (args.Function != EngineKeyFunctions.UIClick || !_grabbed) return; _grabbed = false; OnReleased?.Invoke(this); From 19010597553d15f5ca239497ac66eab876ecc720 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 17 Apr 2024 19:06:59 +1000 Subject: [PATCH 098/130] Version: 218.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 2945cdf0465..6963955a275 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 218.1.0 + 218.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 20bd983bfb9..a84601b1408 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* Control layout properties such as `Margin` can now be set via style sheets. +*None yet* ### Bugfixes @@ -54,6 +54,16 @@ END TEMPLATE--> *None yet* +## 218.2.0 + +### New features + +* Control layout properties such as `Margin` can now be set via style sheets. +* Expose worldposition in SpriteComponent.Render +* Network audio entity Play/Pause/Stop states and playback position. +* Add `Disabled` functionality to `Slider` control. + + ## 218.1.0 ### New features From 8c6170661d814d8cf0f2d29dd4cb3cf9199f218d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 18 Apr 2024 03:11:24 +0200 Subject: [PATCH 099/130] Die --- Robust.Client/UserInterface/Controllers/UIController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Robust.Client/UserInterface/Controllers/UIController.cs b/Robust.Client/UserInterface/Controllers/UIController.cs index 59021d0dffe..ab186486acc 100644 --- a/Robust.Client/UserInterface/Controllers/UIController.cs +++ b/Robust.Client/UserInterface/Controllers/UIController.cs @@ -5,7 +5,6 @@ namespace Robust.Client.UserInterface.Controllers; -// Notices your UIController, *UwU Whats this?* /// /// Each is instantiated as a singleton by /// can use for regular IoC dependencies From d5c49816484ec7af295eb1719b42eb86366bee8c Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:05:02 +1200 Subject: [PATCH 100/130] Partial MapManager refactor (#5042) * MapManager rejig * Update Tests * A --- .../AddRemoveComponentBenchmark.cs | 5 +- .../ComponentIteratorBenchmark.cs | 4 +- .../EntityManager/GetComponentBenchmark.cs | 4 +- .../SpawnDeleteEntityBenchmark.cs | 7 +- .../Transform/RecursiveMoveBenchmark.cs | 3 +- .../GameObjects/EntitySystems/MapSystem.cs | 19 +- .../GameStates/ClientGameStateManager.cs | 19 +- .../Graphics/Clyde/Clyde.GridRendering.cs | 2 +- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 4 +- .../Console/Commands/TestbedCommand.cs | 12 +- .../EntitySystems/MapLoaderSystem.cs | 46 ++--- .../GameObjects/EntitySystems/MapSystem.cs | 17 +- Robust.Shared/Console/Commands/MapCommands.cs | 10 +- .../Transform/TransformComponent.cs | 1 - .../GameObjects/EntityManager.Components.cs | 5 + .../GameObjects/EntityManager.Spawn.cs | 23 ++- Robust.Shared/GameObjects/EntityManager.cs | 22 ++- .../GameObjects/EntitySystem.Proxy.cs | 4 +- .../GameObjects/IEntityManager.Spawn.cs | 2 +- Robust.Shared/GameObjects/IEntityManager.cs | 2 + .../Systems/SharedMapSystem.Map.cs | 156 +++++++++++++-- .../Systems/SharedMapSystem.MapInit.cs | 89 +++++++++ .../Systems/SharedMapSystem.Pause.cs | 60 ++++++ .../GameObjects/Systems/SharedMapSystem.cs | 6 + .../SharedTransformSystem.Component.cs | 61 +++--- .../Systems/SharedTransformSystem.cs | 2 + Robust.Shared/Map/Components/MapComponent.cs | 28 ++- Robust.Shared/Map/IMapManager.cs | 48 ++--- Robust.Shared/Map/IMapManagerInternal.cs | 8 - Robust.Shared/Map/MapId.cs | 2 + .../Map/MapManager.GridCollection.cs | 47 ++--- Robust.Shared/Map/MapManager.GridTrees.cs | 12 -- Robust.Shared/Map/MapManager.MapCollection.cs | 186 ++---------------- Robust.Shared/Map/MapManager.Pause.cs | 128 ++---------- Robust.Shared/Map/MapManager.cs | 30 --- .../Components/TransformComponentTests.cs | 10 +- .../GameObjects/ComponentMapInitTest.cs | 2 +- .../GameObjects/Components/Container_Test.cs | 58 +++--- .../Components/TransformIntegration_Test.cs | 5 +- .../GameObjects/Components/Transform_Test.cs | 8 +- .../ThrowingEntityDeletion_Test.cs | 5 +- .../Server/GameStates/DefaultEntityTest.cs | 3 +- .../Server/GameStates/MissingParentTest.cs | 4 +- .../Server/GameStates/PvsChunkTest.cs | 9 +- .../Server/GameStates/PvsReEntryTest.cs | 3 +- .../Server/GameStates/PvsSystemTests.cs | 3 +- .../Server/Maps/MapLoaderTest.cs | 10 +- .../Server/RobustServerSimulation.cs | 17 +- .../Shared/EntityLookup_Test.cs | 4 +- .../Shared/GameObjects/ContainerTests.cs | 8 +- .../EntityEventBusTests.OrderedEvents.cs | 3 +- .../EntityEventBusTests.RefDirectedEvents.cs | 8 +- .../EntityManager_Components_Tests.cs | 83 ++++---- .../Shared/GameObjects/IEntityManagerTests.cs | 13 +- .../Systems/AnchoredSystemTests.cs | 86 ++++---- .../Systems/TransformSystemTests.cs | 10 +- .../GameObjects/TransformComponent_Tests.cs | 6 +- .../Shared/GameState/ComponentStateTests.cs | 14 +- .../GameState/DeletionNetworkingTests.cs | 3 +- .../GameState/NoSharedReferencesTest.cs | 7 +- .../Shared/Map/EntityCoordinates_Tests.cs | 37 ++-- .../Shared/Map/GridCollision_Test.cs | 2 +- .../Shared/Map/GridContraction_Test.cs | 4 +- .../Shared/Map/GridFixtures_Tests.cs | 2 +- .../Shared/Map/GridMerge_Tests.cs | 2 +- .../Shared/Map/GridRotation_Tests.cs | 4 +- .../Shared/Map/GridSplit_Tests.cs | 10 +- .../Shared/Map/MapGridMap_Tests.cs | 4 +- .../Shared/Map/MapGrid_Tests.cs | 18 +- .../Shared/Map/MapManager_Tests.cs | 55 +----- .../Shared/Map/MapPauseTests.cs | 143 ++++---------- .../Shared/Map/SingleTileRemoveTest.cs | 4 +- .../Physics/BroadphaseNetworkingTest.cs | 6 +- .../Shared/Physics/Broadphase_Test.cs | 22 +-- .../Shared/Physics/CollisionWake_Test.cs | 2 +- .../Shared/Physics/Collision_Test.cs | 4 +- .../Shared/Physics/Fixtures_Test.cs | 2 +- .../Shared/Physics/GridDeletion_Test.cs | 2 +- .../Shared/Physics/GridMovement_Test.cs | 2 +- .../Shared/Physics/JointDeletion_Test.cs | 2 +- .../Shared/Physics/Joints_Test.cs | 4 +- .../Shared/Physics/MapVelocity_Test.cs | 4 +- .../Shared/Physics/PhysicsComponent_Test.cs | 2 +- .../Shared/Physics/PhysicsMap_Test.cs | 4 +- .../Shared/Physics/RecursiveUpdateTest.cs | 4 +- .../Shared/Physics/Stack_Test.cs | 7 +- .../Shared/Prototypes/HotReloadTest.cs | 2 +- .../InheritanceSerializationTest.cs | 7 +- .../Shared/Spawning/EntitySpawnHelpersTest.cs | 3 +- .../Shared/Spawning/SpawnNextToOrDropTest.cs | 14 ++ .../TransformTests/GridTraversalTest.cs | 4 +- 91 files changed, 756 insertions(+), 1082 deletions(-) create mode 100644 Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs create mode 100644 Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs delete mode 100644 Robust.Shared/Map/MapManager.GridTrees.cs diff --git a/Robust.Benchmarks/EntityManager/AddRemoveComponentBenchmark.cs b/Robust.Benchmarks/EntityManager/AddRemoveComponentBenchmark.cs index 767ba7db3be..adb9020c204 100644 --- a/Robust.Benchmarks/EntityManager/AddRemoveComponentBenchmark.cs +++ b/Robust.Benchmarks/EntityManager/AddRemoveComponentBenchmark.cs @@ -26,9 +26,8 @@ public void GlobalSetup() .InitializeInstance(); _entityManager = _simulation.Resolve(); - - var coords = new MapCoordinates(0, 0, new MapId(1)); - _simulation.AddMap(coords.MapId); + var map = _simulation.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); for (var i = 0; i < N; i++) { diff --git a/Robust.Benchmarks/EntityManager/ComponentIteratorBenchmark.cs b/Robust.Benchmarks/EntityManager/ComponentIteratorBenchmark.cs index f8bbba7bd78..bd551658b63 100644 --- a/Robust.Benchmarks/EntityManager/ComponentIteratorBenchmark.cs +++ b/Robust.Benchmarks/EntityManager/ComponentIteratorBenchmark.cs @@ -29,8 +29,8 @@ public void GlobalSetup() Comps = new A[N+2]; - var coords = new MapCoordinates(0, 0, new MapId(1)); - _simulation.AddMap(coords.MapId); + var map = _simulation.CreateMap().MapId; + var coords = new MapCoordinates(default, map); for (var i = 0; i < N; i++) { diff --git a/Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs b/Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs index d3f16b6d387..72568de6824 100644 --- a/Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs +++ b/Robust.Benchmarks/EntityManager/GetComponentBenchmark.cs @@ -31,8 +31,8 @@ public void GlobalSetup() Comps = new A[N+2]; - var coords = new MapCoordinates(0, 0, new MapId(1)); - _simulation.AddMap(coords.MapId); + var map = _simulation.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); for (var i = 0; i < N; i++) { diff --git a/Robust.Benchmarks/EntityManager/SpawnDeleteEntityBenchmark.cs b/Robust.Benchmarks/EntityManager/SpawnDeleteEntityBenchmark.cs index 4acb57101e3..d7a47a10aa9 100644 --- a/Robust.Benchmarks/EntityManager/SpawnDeleteEntityBenchmark.cs +++ b/Robust.Benchmarks/EntityManager/SpawnDeleteEntityBenchmark.cs @@ -29,10 +29,9 @@ public void GlobalSetup() .InitializeInstance(); _entityManager = _simulation.Resolve(); - - _mapCoords = new MapCoordinates(0, 0, new MapId(1)); - var uid = _simulation.AddMap(_mapCoords.MapId); - _entCoords = new EntityCoordinates(uid, 0, 0); + var (map, mapId) = _simulation.CreateMap(); + _mapCoords = new MapCoordinates(default, mapId); + _entCoords = new EntityCoordinates(map, 0, 0); } [Benchmark(Baseline = true)] diff --git a/Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs b/Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs index fba81cd10fc..6deaa7e1290 100644 --- a/Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs +++ b/Robust.Benchmarks/Transform/RecursiveMoveBenchmark.cs @@ -91,8 +91,7 @@ public void GlobalSetup() // Set up map and spawn player server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - var map = mapMan.GetMapEntityId(mapId); + var map = server.ResolveDependency().CreateMap(out var mapId); var gridComp = mapMan.CreateGridEntity(mapId); var grid = gridComp.Owner; mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1)); diff --git a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs index 44ef2827552..9077957e04e 100644 --- a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs @@ -1,12 +1,9 @@ using Robust.Client.Graphics; using Robust.Client.Map; -using Robust.Client.Physics; using Robust.Client.ResourceManagement; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Dynamics; namespace Robust.Client.GameObjects; @@ -16,6 +13,17 @@ public sealed class MapSystem : SharedMapSystem [Dependency] private readonly IResourceCache _resource = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + protected override MapId GetNextMapId() + { + // Client-side map entities use negative map Ids to avoid conflict with server-side maps. + var id = new MapId(--LastMapId); + while (MapManager.MapExists(id)) + { + id = new MapId(--LastMapId); + } + return id; + } + public override void Initialize() { base.Initialize(); @@ -27,9 +35,4 @@ public override void Shutdown() base.Shutdown(); _overlayManager.RemoveOverlay(); } - - protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) - { - EnsureComp(uid); - } } diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 3b764031d3f..8e66deafdea 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -1329,23 +1329,8 @@ private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataCompo foreach (var (comp, cur, next) in _compStateWork.Values) { - try - { - var handleState = new ComponentHandleState(cur, next); - bus.RaiseComponentEvent(comp, ref handleState); - } -#pragma warning disable CS0168 // Variable is declared but never used - catch (Exception e) -#pragma warning restore CS0168 // Variable is declared but never used - { -#if EXCEPTION_TOLERANCE - _sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}"); - _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}"); -#else - _sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}"); - throw; -#endif - } + var handleState = new ComponentHandleState(cur, next); + bus.RaiseComponentEvent(comp, ref handleState); } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs index 724bde9b088..d718479778a 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs @@ -124,7 +124,7 @@ private void CullEmptyChunks() { foreach (var (grid, chunks) in _mapChunkData) { - var gridComp = _mapManager.GetGridComp(grid); + var gridComp = _entityManager.GetComponent(grid); foreach (var (index, chunk) in chunks) { if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index)) diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 66abf120b94..a4cc29756f7 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -251,10 +251,8 @@ private List GetOverlaysForSpace(OverlaySpace space) private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye) { var mapId = eye.Position.MapId; - if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId)) - { + if (mapId == MapId.Nullspace) return; - } RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds); var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities); diff --git a/Robust.Server/Console/Commands/TestbedCommand.cs b/Robust.Server/Console/Commands/TestbedCommand.cs index e18a22ad8ed..37a3f8c44c3 100644 --- a/Robust.Server/Console/Commands/TestbedCommand.cs +++ b/Robust.Server/Console/Commands/TestbedCommand.cs @@ -70,6 +70,11 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) } var mapId = new MapId(mapInt); + if (!_map.MapExists(mapId)) + { + shell.WriteError($"map {args[0]} does not exist"); + return; + } if (shell.Player == null) { @@ -110,13 +115,6 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) private void SetupPlayer(MapId mapId, IConsoleShell shell) { - if (mapId == MapId.Nullspace) return; - - if (!_map.MapExists(mapId)) - { - _map.CreateMap(mapId); - } - _map.SetMapPaused(mapId, false); var mapUid = _map.GetMapEntityIdOrThrow(mapId); _ent.System().SetGravity(mapUid, new Vector2(0, -9.8f)); diff --git a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs index 6a0d2282fb5..f8d11219917 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs @@ -658,11 +658,12 @@ private void SwapRootNode(MapData data) var xformQuery = GetEntityQuery(); // We just need to cache the old mapuid and point to the new mapuid. - if (HasComp(rootNode)) + if (TryComp(rootNode, out MapComponent? mapComp)) { // If map exists swap out - if (_mapManager.MapExists(data.TargetMap)) + if (_mapSystem.TryGetMap(data.TargetMap, out var existing)) { + data.MapIsPaused = _mapSystem.IsPaused(existing.Value); // Map exists but we also have a map file with stuff on it soooo swap out the old map. if (data.Options.LoadMap) { @@ -675,26 +676,28 @@ private void SwapRootNode(MapData data) data.Options.Rotation = Angle.Zero; } - _mapManager.SetMapEntity(data.TargetMap, rootNode); + Del(existing); EnsureComp(rootNode); + + mapComp.MapId = data.TargetMap; + DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing); } // Otherwise just ignore the map in the file. else { var oldRootUid = data.Entities[0]; - var newRootUid = _mapManager.GetMapEntityId(data.TargetMap); - data.Entities[0] = newRootUid; + data.Entities[0] = existing.Value; foreach (var ent in data.Entities) { - if (ent == newRootUid) + if (ent == existing) continue; var xform = xformQuery.GetComponent(ent); if (!xform.ParentUid.IsValid() || xform.ParentUid.Equals(oldRootUid)) { - _transform.SetParent(ent, xform, newRootUid); + _transform.SetParent(ent, xform, existing.Value); } } @@ -703,16 +706,8 @@ private void SwapRootNode(MapData data) } else { - // If we're loading a file with a map then swap out the entityuid - // TODO: Mapmanager nonsense - var AAAAA = _mapManager.CreateMap(data.TargetMap); - - if (!data.MapIsPostInit) - { - _mapManager.AddUninitializedMap(data.TargetMap); - } - - _mapManager.SetMapEntity(data.TargetMap, rootNode); + mapComp.MapId = data.TargetMap; + DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing); EnsureComp(rootNode); // Nothing should have invalid uid except for the root node. @@ -721,17 +716,14 @@ private void SwapRootNode(MapData data) else { // No map file root, in that case create a new map / get the one we're loading onto. - var mapNode = _mapManager.GetMapEntityId(data.TargetMap); - - if (!mapNode.IsValid()) + if (!_mapSystem.TryGetMap(data.TargetMap, out var mapNode)) { // Map doesn't exist so we'll start it up now so we can re-attach the preinit entities to it for later. - _mapManager.CreateMap(data.TargetMap); - _mapManager.AddUninitializedMap(data.TargetMap); - mapNode = _mapManager.GetMapEntityId(data.TargetMap); - DebugTools.Assert(mapNode.IsValid()); + mapNode = _mapSystem.CreateMap(data.TargetMap, false); } + data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value); + // If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map. foreach (var ent in data.Entities) { @@ -743,12 +735,11 @@ private void SwapRootNode(MapData data) if (!xform.ParentUid.IsValid()) { - _transform.SetParent(ent, xform, mapNode); + _transform.SetParent(ent, xform, mapNode.Value); } } } - data.MapIsPaused = _mapManager.IsMapPaused(data.TargetMap); _logLoader.Debug($"Swapped out root node in {_stopwatch.Elapsed}"); } @@ -896,6 +887,7 @@ private void StartupEntity(EntityUid uid, MetaDataComponent metadata, MapData da { EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized); } + // TODO MAP LOAD cache this else if (_mapManager.IsMapInitialized(data.TargetMap)) { _serverEntityManager.RunMapInit(uid, metadata); @@ -964,7 +956,7 @@ public MappingDataNode GetSaveData(EntityUid uid) // Yes, post-init maps do not have EntityLifeStage >= EntityLifeStage.MapInitialized bool postInit; if (TryComp(uid, out MapComponent? mapComp)) - postInit = !mapComp.MapPreInit; + postInit = mapComp.MapInitialized; else postInit = metadata.EntityLifeStage >= EntityLifeStage.MapInitialized; diff --git a/Robust.Server/GameObjects/EntitySystems/MapSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapSystem.cs index 2d5fce29eb7..c7ce2e6354c 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapSystem.cs @@ -5,9 +5,9 @@ using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Map.Events; -using Robust.Shared.Physics.Dynamics; namespace Robust.Server.GameObjects { @@ -18,6 +18,16 @@ public sealed class MapSystem : SharedMapSystem private bool _deleteEmptyGrids; + protected override MapId GetNextMapId() + { + var id = new MapId(++LastMapId); + while (MapManager.MapExists(id)) + { + id = new MapId(++LastMapId); + } + return id; + } + protected override void UpdatePvsChunks(Entity grid) { _pvs.GridParentChanged(grid); @@ -31,11 +41,6 @@ public override void Initialize() Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true); } - protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) - { - EnsureComp(uid); - } - private void SetGridDeletion(bool value) { _deleteEmptyGrids = value; diff --git a/Robust.Shared/Console/Commands/MapCommands.cs b/Robust.Shared/Console/Commands/MapCommands.cs index 17a2f314325..517dcf938d6 100644 --- a/Robust.Shared/Console/Commands/MapCommands.cs +++ b/Robust.Shared/Console/Commands/MapCommands.cs @@ -10,7 +10,8 @@ namespace Robust.Shared.Console.Commands; sealed class AddMapCommand : LocalizedCommands { - [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly IMapManagerInternal _map = default!; + [Dependency] private readonly IEntityManager _entMan = default!; public override string Command => "addmap"; public override bool RequireServerOrSingleplayer => true; @@ -24,11 +25,8 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) if (!_map.MapExists(mapId)) { - _map.CreateMap(mapId); - if (args.Length >= 2 && args[1] == "false") - { - _map.AddUninitializedMap(mapId); - } + var init = args.Length < 2 || !bool.Parse(args[1]); + _entMan.System().CreateMap(mapId, runMapInit: init); shell.WriteLine($"Map with ID {mapId} created."); return; diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index 1ff62c83220..97db539045a 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -101,7 +101,6 @@ public Matrix3 InvLocalMatrix internal bool _mapIdInitialized; internal bool _gridInitialized; - // TODO: Cache this. /// /// The EntityUid of the map which this object is on, if any. /// diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 6c6ba2d36d1..9dc9a57abb8 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1013,6 +1013,11 @@ public IEnumerable GetComponents(EntityUid uid) } } + /// + /// Internal variant of that directly returns the actual component set. + /// + internal IReadOnlyCollection GetComponentsInternal(EntityUid uid) => _entCompIndex[uid]; + /// public int ComponentCount(EntityUid uid) { diff --git a/Robust.Shared/GameObjects/EntityManager.Spawn.cs b/Robust.Shared/GameObjects/EntityManager.Spawn.cs index 32e3e96d422..a5ba665f653 100644 --- a/Robust.Shared/GameObjects/EntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/EntityManager.Spawn.cs @@ -83,8 +83,12 @@ public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates co } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null) - => Spawn(protoName, MapCoordinates.Nullspace, overrides); + public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true) + { + var entity = CreateEntityUninitialized(protoName, MapCoordinates.Nullspace, overrides); + InitializeAndStartEntity(entity, doMapInit); + return entity; + } public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null) { @@ -117,7 +121,8 @@ public bool TrySpawnNextTo( return true; } - uid = Spawn(protoName, overrides); + var doMapInit = _mapSystem.IsInitialized(xform.MapUid); + uid = Spawn(protoName, overrides, doMapInit); if (_containers.Insert(uid.Value, container)) return true; @@ -141,7 +146,8 @@ public bool TrySpawnInContainer( if (!containerComp.Containers.TryGetValue(containerId, out var container)) return false; - uid = Spawn(protoName, overrides); + var doMapInit = _mapSystem.IsInitialized(TransformQuery.GetComponent(containerUid).MapUid); + uid = Spawn(protoName, overrides, doMapInit); if (_containers.Insert(uid.Value, container)) return true; @@ -157,7 +163,8 @@ public EntityUid SpawnNextToOrDrop(string? protoName, EntityUid target, Transfor if (!xform.ParentUid.IsValid()) return Spawn(protoName); - var uid = Spawn(protoName, overrides); + var doMapInit = _mapSystem.IsInitialized(xform.MapUid); + var uid = Spawn(protoName, overrides, doMapInit); _xforms.DropNextTo(uid, target); return uid; } @@ -182,16 +189,16 @@ public EntityUid SpawnInContainerOrDrop( ContainerManagerComponent? containerComp = null, ComponentRegistry? overrides = null) { - var uid = Spawn(protoName, overrides); inserted = true; + xform ??= TransformQuery.GetComponent(containerUid); + var doMapInit = _mapSystem.IsInitialized(xform.MapUid); + var uid = Spawn(protoName, overrides, doMapInit); if ((containerComp == null && !TryGetComponent(containerUid, out containerComp)) || !containerComp.Containers.TryGetValue(containerId, out var container) || !_containers.Insert(uid, container)) { - inserted = false; - xform ??= TransformQuery.GetComponent(containerUid); if (xform.ParentUid.IsValid()) _xforms.DropNextTo(uid, (containerUid, xform)); } diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 621b88d74ef..dbd0f870485 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -304,7 +304,6 @@ public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoo if (coordinates.MapId == MapId.Nullspace) { - DebugTools.Assert(_mapManager.GetMapEntityId(coordinates.MapId) == EntityUid.Invalid); transform._parent = EntityUid.Invalid; transform.Anchored = false; return newEntity; @@ -821,15 +820,22 @@ private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null) { + var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID); + InitializeAndStartEntity(entity, doMapInit); + } + + public void InitializeAndStartEntity(Entity entity, bool doMapInit) + { + if (!MetaQuery.Resolve(entity.Owner, ref entity.Comp)) + return; + try { - var meta = MetaQuery.GetComponent(entity); - InitializeEntity(entity, meta); - StartEntity(entity); + InitializeEntity(entity.Owner, entity.Comp); + StartEntity(entity.Owner); - // If the map we're initializing the entity on is initialized, run map init on it. - if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID)) - RunMapInit(entity, meta); + if (doMapInit) + RunMapInit(entity.Owner, entity.Comp); } catch (Exception e) { @@ -859,7 +865,7 @@ public void RunMapInit(EntityUid entity, MetaDataComponent meta) DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized, $"Expected entity {ToPrettyString(entity)} to be initialized, was {meta.EntityLifeStage}"); SetLifeStage(meta, EntityLifeStage.MapInitialized); - EventBus.RaiseLocalEvent(entity, MapInitEventInstance, false); + EventBus.RaiseLocalEvent(entity, MapInitEventInstance); } /// diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 392d0f8cc29..662b2a8f5ea 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -713,8 +713,8 @@ protected EntityUid Spawn(string? prototype, MapCoordinates coordinates) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected EntityUid Spawn(string? prototype = null) - => EntityManager.Spawn(prototype); + protected EntityUid Spawn(string? prototype = null, ComponentRegistry? overrides = null, bool doMapInit = true) + => EntityManager.Spawn(prototype, overrides, doMapInit); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs index 1fddff23055..2bcd44ce436 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs @@ -27,7 +27,7 @@ EntityUid[] SpawnEntities(EntityCoordinates coordinates, List protoName /// /// Spawns an entity in nullspace. /// - EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null); + EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true); /// /// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid. diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs index fc052dc8bfc..ea9acf23414 100644 --- a/Robust.Shared/GameObjects/IEntityManager.cs +++ b/Robust.Shared/GameObjects/IEntityManager.cs @@ -80,6 +80,8 @@ public partial interface IEntityManager void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null); + void InitializeAndStartEntity(Entity entity, bool doMapInit); + void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null); void StartEntity(EntityUid entity); diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Map.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Map.cs index b8f1d4ea19f..6b848b50197 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Map.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Map.cs @@ -1,68 +1,188 @@ +using System; +using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameStates; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; public abstract partial class SharedMapSystem { + protected int LastMapId; + private void InitializeMap() { - SubscribeLocalEvent(OnMapAdd); - SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnComponentAdd); + SubscribeLocalEvent(OnCompInit); + SubscribeLocalEvent(OnCompStartup); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMapRemoved); SubscribeLocalEvent(OnMapHandleState); SubscribeLocalEvent(OnMapGetState); } + public bool MapExists([NotNullWhen(true)] MapId? mapId) + { + return mapId != null && Maps.ContainsKey(mapId.Value); + } + + public EntityUid GetMap(MapId mapId) + { + return Maps[mapId]; + } + + public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid) + { + if (mapId == null || !Maps.TryGetValue(mapId.Value, out var map)) + { + uid = null; + return false; + } + + uid = map; + return true; + } + private void OnMapHandleState(EntityUid uid, MapComponent component, ref ComponentHandleState args) { if (args.Current is not MapComponentState state) return; - component.MapId = state.MapId; - - if (!MapManager.MapExists(state.MapId)) + if (component.MapId == MapId.Nullspace) { - var mapInternal = (IMapManagerInternal)MapManager; - mapInternal.CreateMap(state.MapId, uid); + if (state.MapId == MapId.Nullspace) + throw new Exception($"Received invalid map state? {ToPrettyString(uid)}"); + + component.MapId = state.MapId; + Maps.Add(component.MapId, uid); + RecursiveMapIdUpdate(uid, uid, component.MapId); } + DebugTools.AssertEqual(component.MapId, state.MapId); component.LightingEnabled = state.LightingEnabled; - var xformQuery = GetEntityQuery(); + component.MapInitialized = state.Initialized; - xformQuery.GetComponent(uid).ChangeMapId(state.MapId, xformQuery); + if (LifeStage(uid) >= EntityLifeStage.Initialized) + SetPaused(uid, state.MapPaused); + else + component.MapPaused = state.MapPaused; + } + + private void RecursiveMapIdUpdate(EntityUid uid, EntityUid mapUid, MapId mapId) + { + // This is required only in the event where an entity becomes a map AFTER children have already been attached to it. + // AFAIK, this currently only happens when the client applies entity states out of order (i.e., ignoring transform hierarchy), + // which itself only happens if PVS is disabled. + // TODO MAPS remove this - MapManager.SetMapPaused(state.MapId, state.MapPaused); + var xform = Transform(uid); + xform.MapUid = mapUid; + xform.MapID = mapId; + xform._mapIdInitialized = true; + foreach (var child in xform._children) + { + RecursiveMapIdUpdate(child, mapUid, mapId); + } } private void OnMapGetState(EntityUid uid, MapComponent component, ref ComponentGetState args) { - args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused); + args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused, component.MapInitialized); } - protected abstract void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args); + protected abstract MapId GetNextMapId(); - private void OnMapInit(EntityUid uid, MapComponent component, ComponentInit args) + private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args) { + // ordered startups when + EnsureComp(uid); EnsureComp(uid); EnsureComp(uid); + } + + private void OnCompInit(EntityUid uid, MapComponent component, ComponentInit args) + { + if (component.MapId == MapId.Nullspace) + component.MapId = GetNextMapId(); + + DebugTools.AssertEqual(component.MapId.IsClientSide, IsClientSide(uid)); + if (!Maps.TryAdd(component.MapId, uid)) + { + if (Maps[component.MapId] != uid) + throw new Exception($"Attempted to initialize a map {ToPrettyString(uid)} with a duplicate map id {component.MapId}"); + } var msg = new MapChangedEvent(uid, component.MapId, true); RaiseLocalEvent(uid, msg, true); } + private void OnCompStartup(EntityUid uid, MapComponent component, ComponentStartup args) + { + if (component.MapPaused) + RecursiveSetPaused(uid, true); + } + private void OnMapRemoved(EntityUid uid, MapComponent component, ComponentShutdown args) { DebugTools.Assert(component.MapId != MapId.Nullspace); - Log.Info($"Deleting map {component.MapId}"); - - var iMap = (IMapManagerInternal)MapManager; - iMap.RemoveMapId(component.MapId); + Maps.Remove(component.MapId); var msg = new MapChangedEvent(uid, component.MapId, false); RaiseLocalEvent(uid, msg, true); } + + /// + /// Creates a new map, automatically assigning a map id. + /// + public EntityUid CreateMap(out MapId mapId, bool runMapInit = true) + { + mapId = GetNextMapId(); + var uid = CreateMap(mapId, runMapInit); + return uid; + } + + /// + public EntityUid CreateMap(bool runMapInit = true) => CreateMap(out _, runMapInit); + + /// + /// Creates a new map with the specified map id. + /// + /// Throws if an invalid or already existing map id is provided. + public EntityUid CreateMap(MapId mapId, bool runMapInit = true) + { + if (Maps.ContainsKey(mapId)) + throw new ArgumentException($"Map with id {mapId} already exists"); + + if (mapId == MapId.Nullspace) + throw new ArgumentException($"Cannot create a null-space map"); + + if (_netManager.IsServer && mapId.IsClientSide) + throw new ArgumentException($"Attempted to create a client-side map on the server?"); + + if (_netManager.IsClient && _netManager.IsConnected && !mapId.IsClientSide) + throw new ArgumentException($"Attempted to create a client-side map entity with a non client-side map ID?"); + + var uid = EntityManager.CreateEntityUninitialized(null); + var map = _factory.GetComponent(); + map.MapId = mapId; + AddComp(uid, map); + + // Give the entity a name, mainly for debugging. Content can always override this with a localized name. + var meta = MetaData(uid); + _meta.SetEntityName(uid, $"Map Entity", meta); + + // Initialize components. this should add the map id to the collections. + EntityManager.InitializeComponents(uid, meta); + EntityManager.StartComponents(uid); + DebugTools.Assert(Maps[mapId] == uid); + + if (runMapInit) + InitializeMap((uid, map)); + else + SetPaused((uid, map), true); + + return uid; + } } diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs new file mode 100644 index 00000000000..f6122e0c1fa --- /dev/null +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +public abstract partial class SharedMapSystem +{ + private List _toInitialize = new(); + + public bool IsInitialized(MapId mapId) + { + if (mapId == MapId.Nullspace) + return true; // Nullspace is always initialized + + if(!Maps.TryGetValue(mapId, out var uid)) + throw new ArgumentException($"Map {mapId} does not exist."); + + return IsInitialized(uid); + } + public bool IsInitialized(EntityUid? map) + { + if (map == null) + return true; // Nullspace is always initialized + + return IsInitialized(map.Value); + } + + public bool IsInitialized(Entity map) + { + if (!_mapQuery.Resolve(map, ref map.Comp)) + return false; + + return map.Comp.MapInitialized; + } + + private void OnMapInit(EntityUid uid, MapComponent component, MapInitEvent args) + { + DebugTools.Assert(!component.MapInitialized); + component.MapInitialized = true; + EntityManager.Dirty(uid, component); + } + + public void InitializeMap(MapId mapId, bool unpause = true) + { + if(!Maps.TryGetValue(mapId, out var uid)) + throw new ArgumentException($"Map {mapId} does not exist."); + + InitializeMap(uid, unpause); + } + + public void InitializeMap(Entity map, bool unpause = true) + { + if (!_mapQuery.Resolve(map, ref map.Comp)) + return; + + if (map.Comp.MapInitialized) + throw new ArgumentException($"Map {ToPrettyString(map)} is already initialized."); + + RecursiveMapInit(map.Owner); + + if (unpause) + SetPaused(map, false); + } + + private void RecursiveMapInit(EntityUid entity) + { + _toInitialize.Clear(); + _toInitialize.Add(entity); + + for (var i = 0; i < _toInitialize.Count; i++) + { + var uid = _toInitialize[i]; + // _toInitialize might contain deleted entities. + if(!_metaQuery.TryComp(uid, out var meta)) + continue; + + if (meta.EntityLifeStage == EntityLifeStage.MapInitialized) + continue; + + _toInitialize.AddRange(Transform(uid)._children); + EntityManager.RunMapInit(uid, meta); + } + + _toInitialize.Clear(); + } +} diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs new file mode 100644 index 00000000000..cd62f2c2dfe --- /dev/null +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs @@ -0,0 +1,60 @@ +using System; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Robust.Shared.GameObjects; + +public abstract partial class SharedMapSystem +{ + public bool IsPaused(MapId mapId) + { + if (mapId == MapId.Nullspace) + return false; + + if(!Maps.TryGetValue(mapId, out var uid)) + throw new ArgumentException($"Map {mapId} does not exist."); + + return IsPaused(uid); + } + + public bool IsPaused(Entity map) + { + if (!_mapQuery.Resolve(map, ref map.Comp)) + return false; + + return map.Comp.MapPaused; + } + + public void SetPaused(MapId mapId, bool paused) + { + if(!Maps.TryGetValue(mapId, out var uid)) + throw new ArgumentException($"Map {mapId} does not exist."); + + SetPaused(uid, paused); + } + + public void SetPaused(Entity map, bool paused) + { + if (!_mapQuery.Resolve(map, ref map.Comp)) + return; + + if (map.Comp.MapPaused == paused) + return; + + map.Comp.MapPaused = paused; + if (map.Comp.LifeStage < ComponentLifeStage.Initializing) + return; + + Dirty(map); + RecursiveSetPaused(map, paused); + } + + private void RecursiveSetPaused(EntityUid entity, bool paused) + { + _meta.SetEntityPaused(entity, paused); + foreach (var child in Transform(entity)._children) + { + RecursiveSetPaused(child, paused); + } + } +} diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs index 8045aee7e8b..6267c1ecaa8 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs @@ -20,17 +20,23 @@ public abstract partial class SharedMapSystem : EntitySystem [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; private EntityQuery _mapQuery; private EntityQuery _gridQuery; + private EntityQuery _metaQuery; private EntityQuery _xformQuery; + internal Dictionary Maps { get; } = new(); + public override void Initialize() { base.Initialize(); _mapQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); + _metaQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); InitializeMap(); diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index a60cef3a836..29a69391852 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -199,48 +199,35 @@ public bool IsParentOf(TransformComponent parent, EntityUid child) #region Component Lifetime - private void OnCompInit(EntityUid uid, TransformComponent component, ComponentInit args) + private (EntityUid?, MapId) InitializeMapUid(EntityUid uid, TransformComponent xform) { - // Children MAY be initialized here before their parents are. - // We do this whole dance to handle this recursively, - // setting _mapIdInitialized along the way to avoid going to the MapComponent every iteration. - static MapId FindMapIdAndSet(EntityUid uid, TransformComponent xform, IEntityManager entMan, EntityQuery xformQuery, IMapManager mapManager) - { - if (xform._mapIdInitialized) - return xform.MapID; + if (xform._mapIdInitialized) + return (xform.MapUid, xform.MapID); - MapId value; - - if (xform.ParentUid.IsValid()) - { - value = FindMapIdAndSet(xform.ParentUid, xformQuery.GetComponent(xform.ParentUid), entMan, xformQuery, mapManager); - } - else - { - // second level node, terminates recursion up the branch of the tree - if (entMan.TryGetComponent(uid, out MapComponent? mapComp)) - { - value = mapComp.MapId; - } - else - { - // We allow entities to be spawned directly into null-space. - value = MapId.Nullspace; - } - } - - xform.MapUid = value == MapId.Nullspace ? null : mapManager.GetMapEntityId(value); - xform.MapID = value; - xform._mapIdInitialized = true; - return value; + if (xform.ParentUid.IsValid()) + { + (xform.MapUid, xform.MapID) = InitializeMapUid(xform.ParentUid, Transform(xform.ParentUid)); } - - if (!component._mapIdInitialized) + else if (_mapQuery.TryComp(uid, out var mapComp)) + { + DebugTools.AssertNotEqual(mapComp.MapId, MapId.Nullspace); + xform.MapUid = uid; + xform.MapID = mapComp.MapId; + } + else { - FindMapIdAndSet(uid, component, EntityManager, XformQuery, _mapManager); - component._mapIdInitialized = true; + xform.MapUid = null; + xform.MapID = MapId.Nullspace; } + xform._mapIdInitialized = true; + return (xform.MapUid, xform.MapID); + } + + private void OnCompInit(EntityUid uid, TransformComponent component, ComponentInit args) + { + InitializeMapUid(uid, component); + // Has to be done if _parent is set from ExposeData. if (component.ParentUid.IsValid()) { @@ -522,6 +509,8 @@ public void SetCoordinates( throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}"); } + InitializeMapUid(value.EntityId, newParent); + // Check for recursive/circular transform hierarchies. if (xform.MapUid == newParent.MapUid) { diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index aedb98f747e..3433c4ef66e 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -26,6 +26,7 @@ public abstract partial class SharedTransformSystem : EntitySystem [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + private EntityQuery _mapQuery; private EntityQuery _gridQuery; private EntityQuery _metaQuery; protected EntityQuery XformQuery; @@ -50,6 +51,7 @@ public override void Initialize() UpdatesOutsidePrediction = true; + _mapQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); _metaQuery = GetEntityQuery(); XformQuery = GetEntityQuery(); diff --git a/Robust.Shared/Map/Components/MapComponent.cs b/Robust.Shared/Map/Components/MapComponent.cs index fb07046bc43..d771a8c4754 100644 --- a/Robust.Shared/Map/Components/MapComponent.cs +++ b/Robust.Shared/Map/Components/MapComponent.cs @@ -12,35 +12,29 @@ namespace Robust.Shared.Map.Components public sealed partial class MapComponent : Component { [ViewVariables(VVAccess.ReadWrite)] - [DataField("lightingEnabled")] + [DataField] public bool LightingEnabled { get; set; } = true; [ViewVariables(VVAccess.ReadOnly)] public MapId MapId { get; internal set; } = MapId.Nullspace; - [ViewVariables(VVAccess.ReadOnly)] - public bool MapPaused { get; set; } = false; + [DataField, Access(typeof(SharedMapSystem), typeof(MapManager))] + public bool MapPaused; - //TODO replace MapPreInit with the map's entity life stage - [ViewVariables(VVAccess.ReadOnly)] - public bool MapPreInit { get; set; } = false; + [DataField, Access(typeof(SharedMapSystem), typeof(MapManager))] + public bool MapInitialized; } /// /// Serialized state of a . /// [Serializable, NetSerializable] - public sealed class MapComponentState : ComponentState + public sealed class MapComponentState(MapId mapId, bool lightingEnabled, bool paused, bool init) + : ComponentState { - public MapId MapId; - public bool LightingEnabled; - public bool MapPaused; - - public MapComponentState(MapId mapId, bool lightingEnabled, bool paused) - { - MapId = mapId; - LightingEnabled = lightingEnabled; - MapPaused = paused; - } + public MapId MapId = mapId; + public bool LightingEnabled = lightingEnabled; + public bool MapPaused = paused; + public bool Initialized = init; } } diff --git a/Robust.Shared/Map/IMapManager.cs b/Robust.Shared/Map/IMapManager.cs index 4a7913c8c81..5b18ed748e1 100644 --- a/Robust.Shared/Map/IMapManager.cs +++ b/Robust.Shared/Map/IMapManager.cs @@ -23,9 +23,6 @@ public interface IMapManager public const bool Approximate = false; public const bool IncludeMap = true; - [Obsolete("Use EntityQuery")] - IEnumerable GetAllGrids(); - /// /// Should the OnTileChanged event be suppressed? This is useful for initially loading the map /// so that you don't spam an event for each of the million station tiles. @@ -42,16 +39,7 @@ public interface IMapManager void Restart(); - /// - /// Creates a new map. - /// - /// - /// If provided, the new map will use this ID. If not provided, a new ID will be selected automatically. - /// - /// The new map. - /// - /// Throw if an explicit ID for the map or default grid is passed and a map or grid with the specified ID already exists, respectively. - /// + [Obsolete("Use MapSystem")] MapId CreateMap(MapId? mapId = null); /// @@ -59,24 +47,12 @@ public interface IMapManager /// /// The map ID to check existence of. /// True if the map exists, false otherwise. - bool MapExists(MapId mapId); + bool MapExists([NotNullWhen(true)] MapId? mapId); /// - /// Creates a new entity, then sets it as the map entity. - /// - /// Newly created entity. - EntityUid CreateNewMapEntity(MapId mapId); - - /// - /// Sets the MapEntity(root node) for a given map. If an entity is already set, it will be deleted - /// before the new one is set. - /// - /// Should we re-parent children from the old map to the new one, or delete them. - void SetMapEntity(MapId mapId, EntityUid newMapEntityId, bool updateChildren = true); - - /// - /// Returns the map entity ID for a given map. + /// Returns the map entity ID for a given map, or an invalid entity Id if the map does not exist. /// + [Obsolete("Use TryGetMap")] EntityUid GetMapEntityId(MapId mapId); /// @@ -93,6 +69,7 @@ public interface IMapManager MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options); MapGridComponent CreateGrid(MapId currentMapId); Entity CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null); + Entity CreateGridEntity(EntityUid map, GridCreateOptions? options = null); [Obsolete("Use GetComponent(uid)")] MapGridComponent GetGrid(EntityUid gridId); @@ -233,17 +210,13 @@ public IEnumerable FindGridsIntersecting(MapId mapId, Box2Rota #endregion - void DeleteGrid(EntityUid euid); - bool HasMapEntity(MapId mapId); + [Obsolete("Just delete the grid entity")] + void DeleteGrid(EntityUid euid); bool IsGrid(EntityUid uid); bool IsMap(EntityUid uid); - [Obsolete("Whatever this is used for, it is a terrible idea. Create a new map and get it's MapId.")] - MapId NextMapId(); - MapGridComponent GetGridComp(EntityUid euid); - // // Pausing functions // @@ -252,14 +225,15 @@ public IEnumerable FindGridsIntersecting(MapId mapId, Box2Rota void DoMapInitialize(MapId mapId); - // TODO rename this to actually be descriptive or just remove it. + [Obsolete("Use CreateMap's runMapInit argument")] void AddUninitializedMap(MapId mapId); - [Pure] + [Obsolete("Use MapSystem")] bool IsMapPaused(MapId mapId); - [Pure] + [Obsolete("Use MapSystem")] bool IsMapInitialized(MapId mapId); + } public struct GridCreateOptions diff --git a/Robust.Shared/Map/IMapManagerInternal.cs b/Robust.Shared/Map/IMapManagerInternal.cs index 0e42ea41b0a..f3234ab315c 100644 --- a/Robust.Shared/Map/IMapManagerInternal.cs +++ b/Robust.Shared/Map/IMapManagerInternal.cs @@ -1,23 +1,15 @@ -using Robust.Shared.GameObjects; using Robust.Shared.Maths; -using Robust.Shared.Timing; namespace Robust.Shared.Map { /// internal interface IMapManagerInternal : IMapManager { - IGameTiming GameTiming { get; } - /// /// Raises the OnTileChanged event. /// /// A reference to the new tile. /// The old tile that got replaced. void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk); - - MapId CreateMap(MapId? mapId, EntityUid euid); - - void RemoveMapId(MapId mapId); } } diff --git a/Robust.Shared/Map/MapId.cs b/Robust.Shared/Map/MapId.cs index 6899d181a30..1a696e50143 100644 --- a/Robust.Shared/Map/MapId.cs +++ b/Robust.Shared/Map/MapId.cs @@ -53,5 +53,7 @@ public override string ToString() { return Value.ToString(); } + + public bool IsClientSide => Value < 0; } } diff --git a/Robust.Shared/Map/MapManager.GridCollection.cs b/Robust.Shared/Map/MapManager.GridCollection.cs index ac49ec69167..67d6393168b 100644 --- a/Robust.Shared/Map/MapManager.GridCollection.cs +++ b/Robust.Shared/Map/MapManager.GridCollection.cs @@ -12,32 +12,15 @@ namespace Robust.Shared.Map; internal partial class MapManager { - [Obsolete("Use GetComponent(uid)")] - public MapGridComponent GetGridComp(EntityUid euid) - { - return EntityManager.GetComponent(euid); - } - - [Obsolete("Use EntityQuery instead.")] - public IEnumerable GetAllGrids() - { - var compQuery = EntityManager.AllEntityQueryEnumerator(); - - while (compQuery.MoveNext(out var comp)) - { - yield return comp; - } - } - // ReSharper disable once MethodOverloadWithOptionalParameter public MapGridComponent CreateGrid(MapId currentMapId, ushort chunkSize = 16) { - return CreateGrid(currentMapId, chunkSize, default); + return CreateGrid(GetMapEntityIdOrThrow(currentMapId), chunkSize, default); } public MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options) { - return CreateGrid(currentMapId, options.ChunkSize, default); + return CreateGrid(GetMapEntityIdOrThrow(currentMapId), options.ChunkSize, default); } public MapGridComponent CreateGrid(MapId currentMapId) @@ -46,18 +29,19 @@ public MapGridComponent CreateGrid(MapId currentMapId) } public Entity CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null) + { + return CreateGridEntity(GetMapEntityIdOrThrow(currentMapId), options); + } + + public Entity CreateGridEntity(EntityUid map, GridCreateOptions? options = null) { options ??= GridCreateOptions.Default; - return CreateGrid(currentMapId, options.Value.ChunkSize, default); + return CreateGrid(map, options.Value.ChunkSize, default); } [Obsolete("Use GetComponent(uid)")] public MapGridComponent GetGrid(EntityUid gridId) - { - DebugTools.Assert(gridId.IsValid()); - - return GetGridComp(gridId); - } + => EntityManager.GetComponent(gridId); [Obsolete("Use HasComponent(uid)")] public bool IsGrid(EntityUid uid) @@ -108,10 +92,6 @@ public IEnumerable> GetAllGrids(MapId mapId) public virtual void DeleteGrid(EntityUid euid) { -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - // Possible the grid was already deleted / is invalid if (!EntityManager.TryGetComponent(euid, out var iGrid)) { @@ -141,10 +121,6 @@ public virtual void DeleteGrid(EntityUid euid) /// The old tile that got replaced. public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk) { -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - if (SuppressOnTileChanged) return; @@ -153,7 +129,7 @@ public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk) EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true); } - protected Entity CreateGrid(MapId currentMapId, ushort chunkSize, EntityUid forcedGridEuid) + protected Entity CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid) { var gridEnt = EntityManager.CreateEntityUninitialized(null, forcedGridEuid); @@ -166,8 +142,7 @@ protected Entity CreateGrid(MapId currentMapId, ushort chunkSi //are applied. After they are applied the parent may be different, but the MapId will //be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to //use in transform states anytime before the state parent is properly set. - var fallbackParentEuid = GetMapEntityIdOrThrow(currentMapId); - EntityManager.GetComponent(gridEnt).AttachParent(fallbackParentEuid); + EntityManager.GetComponent(gridEnt).AttachParent(map); var meta = EntityManager.GetComponent(gridEnt); EntityManager.System().SetEntityName(gridEnt, $"grid", meta); diff --git a/Robust.Shared/Map/MapManager.GridTrees.cs b/Robust.Shared/Map/MapManager.GridTrees.cs deleted file mode 100644 index 249e46798dc..00000000000 --- a/Robust.Shared/Map/MapManager.GridTrees.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Robust.Shared.Map; - -internal partial class MapManager -{ - public void RemoveMapId(MapId mapId) - { - if (mapId == MapId.Nullspace) - return; - - _mapEntities.Remove(mapId); - } -} diff --git a/Robust.Shared/Map/MapManager.MapCollection.cs b/Robust.Shared/Map/MapManager.MapCollection.cs index df59546bb8c..7eda32ccaa3 100644 --- a/Robust.Shared/Map/MapManager.MapCollection.cs +++ b/Robust.Shared/Map/MapManager.MapCollection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameObjects; using Robust.Shared.Map.Components; using Robust.Shared.Utility; @@ -27,131 +28,42 @@ public MapEventArgs(MapId map) internal partial class MapManager { - private readonly Dictionary _mapEntities = new(); - private MapId _highestMapId = MapId.Nullspace; + private Dictionary _mapEntities => _mapSystem.Maps; /// public virtual void DeleteMap(MapId mapId) { -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - if (!_mapEntities.TryGetValue(mapId, out var ent) || !ent.IsValid()) throw new InvalidOperationException($"Attempted to delete nonexistent map '{mapId}'"); EntityManager.DeleteEntity(ent); + DebugTools.Assert(!_mapEntities.ContainsKey(mapId)); } /// public MapId CreateMap(MapId? mapId = null) { - return ((IMapManagerInternal) this).CreateMap(mapId, default); - } + if (mapId != null) + { + _mapSystem.CreateMap(mapId.Value); + return mapId.Value; + } - /// - public bool MapExists(MapId mapId) - { - return _mapEntities.ContainsKey(mapId); + _mapSystem.CreateMap(out var map); + return map; } /// - public EntityUid CreateNewMapEntity(MapId mapId) + public bool MapExists([NotNullWhen(true)] MapId? mapId) { - DebugTools.Assert(mapId != MapId.Nullspace); -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - - var newEntity = EntityManager.CreateEntityUninitialized(null); - SetMapEntity(mapId, newEntity); - - EntityManager.InitializeComponents(newEntity); - EntityManager.StartComponents(newEntity); - - return newEntity; - } - - /// - public void SetMapEntity(MapId mapId, EntityUid newMapEntity, bool updateChildren = true) - { -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - - if (!_mapEntities.ContainsKey(mapId)) - throw new InvalidOperationException($"Map {mapId} does not exist."); - - foreach (var kvEntity in _mapEntities) - { - if (kvEntity.Value == newMapEntity) - { - if (mapId == kvEntity.Key) - return; - - throw new InvalidOperationException( - $"Entity {newMapEntity} is already the root node of another map {kvEntity.Key}."); - } - } - - MapComponent? mapComp; - // If this is being done as part of maploader then we want to copy the preinit state across mainly. - bool preInit = false; - bool paused = false; - - // remove existing graph - if (_mapEntities.TryGetValue(mapId, out var oldEntId)) - { - if (EntityManager.TryGetComponent(oldEntId, out mapComp)) - { - preInit = mapComp.MapPreInit; - paused = mapComp.MapPaused; - } - - EntityManager.System().ReparentChildren(oldEntId, newMapEntity); - - //Note: EntityUid.Invalid gets passed in here - //Note: This prevents setting a subgraph as the root, since the subgraph will be deleted - EntityManager.DeleteEntity(oldEntId); - } - - var raiseEvent = false; - - // re-use or add map component - if (!EntityManager.TryGetComponent(newMapEntity, out mapComp)) - mapComp = EntityManager.AddComponent(newMapEntity); - else - { - raiseEvent = true; - - if (mapComp.MapId != mapId) - { - _sawmill.Warning($"Setting map {mapId} root to entity {newMapEntity}, but entity thinks it is root node of map {mapComp.MapId}."); - } - } - - _sawmill.Debug($"Setting map {mapId} entity to {newMapEntity}"); - - // set as new map entity - mapComp.MapPreInit = preInit; - mapComp.MapPaused = paused; - - mapComp.MapId = mapId; - _mapEntities[mapId] = newMapEntity; - - // Yeah this sucks but I just want to save maps for now, deal. - if (raiseEvent) - { - var ev = new MapChangedEvent(newMapEntity, mapId, true); - EntityManager.EventBus.RaiseLocalEvent(newMapEntity, ev, true); - } + return _mapSystem.MapExists(mapId); } /// public EntityUid GetMapEntityId(MapId mapId) { - if (_mapEntities.TryGetValue(mapId, out var entId)) - return entId; + if (_mapSystem.TryGetMap(mapId, out var entId)) + return entId.Value; return EntityUid.Invalid; } @@ -161,13 +73,12 @@ public EntityUid GetMapEntityId(MapId mapId) /// public EntityUid GetMapEntityIdOrThrow(MapId mapId) { - return _mapEntities[mapId]; + return _mapSystem.GetMap(mapId); } - /// - public bool HasMapEntity(MapId mapId) + public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid) { - return _mapEntities.ContainsKey(mapId); + return _mapSystem.TryGetMap(mapId, out uid); } /// @@ -181,67 +92,4 @@ public bool IsMap(EntityUid uid) { return EntityManager.HasComponent(uid); } - - /// - public MapId NextMapId() - { - return _highestMapId = new MapId(_highestMapId.Value + 1); - } - - MapId IMapManagerInternal.CreateMap(MapId? mapId, EntityUid entityUid) - { - if (mapId == MapId.Nullspace) - throw new InvalidOperationException("Attempted to create a null-space map."); - -#if DEBUG - DebugTools.Assert(_dbgGuardRunning); -#endif - - var actualId = mapId ?? new MapId(_highestMapId.Value + 1); - - if (MapExists(actualId)) - throw new InvalidOperationException($"A map with ID {actualId} already exists"); - - if (_highestMapId.Value < actualId.Value) - _highestMapId = actualId; - - _sawmill.Info($"Creating new map {actualId}"); - - if (actualId != MapId.Nullspace) // nullspace isn't bound to an entity - { - Entity result = default; - var query = EntityManager.AllEntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var map)) - { - if (map.MapId != actualId) - continue; - - result = (uid, map); - break; - } - - if (result != default) - { - DebugTools.Assert(mapId != null); - _mapEntities.Add(actualId, result); - _sawmill.Debug($"Rebinding map {actualId} to entity {result.Owner}"); - } - else - { - var newEnt = EntityManager.CreateEntityUninitialized(null, entityUid); - _mapEntities.Add(actualId, newEnt); - - var mapComp = EntityManager.AddComponent(newEnt); - mapComp.MapId = actualId; - var meta = EntityManager.GetComponent(newEnt); - EntityManager.System().SetEntityName(newEnt, $"map {actualId}", meta); - EntityManager.Dirty(newEnt, mapComp, meta); - EntityManager.InitializeComponents(newEnt, meta); - EntityManager.StartComponents(newEnt); - _sawmill.Debug($"Binding map {actualId} to entity {newEnt}"); - } - } - - return actualId; - } } diff --git a/Robust.Shared/Map/MapManager.Pause.cs b/Robust.Shared/Map/MapManager.Pause.cs index 221c92f1f99..9cd232f4c89 100644 --- a/Robust.Shared/Map/MapManager.Pause.cs +++ b/Robust.Shared/Map/MapManager.Pause.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.Map.Components; @@ -8,145 +7,44 @@ namespace Robust.Shared.Map { internal partial class MapManager { - /// public void SetMapPaused(MapId mapId, bool paused) { - if(mapId == MapId.Nullspace) - return; - - if(!MapExists(mapId)) - throw new ArgumentException("That map does not exist."); - - var mapUid = GetMapEntityId(mapId); - var mapComp = EntityManager.GetComponent(mapUid); - - if (mapComp.MapPaused == paused) - return; - - mapComp.MapPaused = paused; - EntityManager.Dirty(mapUid, mapComp); - - var xformQuery = EntityManager.GetEntityQuery(); - var metaQuery = EntityManager.GetEntityQuery(); - var metaSystem = EntityManager.EntitySysManager.GetEntitySystem(); - - RecursiveSetPaused(mapUid, paused, in xformQuery, in metaQuery, in metaSystem); + _mapSystem.SetPaused(mapId, paused); } - private static void RecursiveSetPaused(EntityUid entity, bool paused, - in EntityQuery xformQuery, - in EntityQuery metaQuery, - in MetaDataSystem system) + public void SetMapPaused(EntityUid uid, bool paused) { - system.SetEntityPaused(entity, paused, metaQuery.GetComponent(entity)); - var xform = xformQuery.GetComponent(entity); - foreach (var child in xform._children) - { - RecursiveSetPaused(child, paused, in xformQuery, in metaQuery, in system); - } + _mapSystem.SetPaused(uid, paused); } - /// public void DoMapInitialize(MapId mapId) { - if(!MapExists(mapId)) - throw new ArgumentException("That map does not exist."); - - if (IsMapInitialized(mapId)) - throw new ArgumentException("That map is already initialized."); - - var mapEnt = GetMapEntityId(mapId); - var mapComp = EntityManager.GetComponent(mapEnt); - var xformQuery = EntityManager.GetEntityQuery(); - var metaQuery = EntityManager.GetEntityQuery(); - var metaSystem = EntityManager.EntitySysManager.GetEntitySystem(); - - mapComp.MapPreInit = false; - mapComp.MapPaused = false; - EntityManager.Dirty(mapEnt, mapComp); - - RecursiveDoMapInit(mapEnt, in xformQuery, in metaQuery, in metaSystem); + _mapSystem.InitializeMap(mapId); } - private void RecursiveDoMapInit(EntityUid entity, - in EntityQuery xformQuery, - in EntityQuery metaQuery, - in MetaDataSystem system) + public bool IsMapInitialized(MapId mapId) { - // RunMapInit can modify the TransformTree - // ToArray caches deleted euids, we check here if they still exist. - if(!metaQuery.TryGetComponent(entity, out var meta)) - return; - - EntityManager.RunMapInit(entity, meta); - system.SetEntityPaused(entity, false, meta); - - foreach (var child in xformQuery.GetComponent(entity)._children.ToArray()) - { - RecursiveDoMapInit(child, in xformQuery, in metaQuery, in system); - } + return _mapSystem.IsInitialized(mapId); } - /// public void AddUninitializedMap(MapId mapId) { - SetMapPreInit(mapId); - } - - private bool CheckMapPause(MapId mapId) - { - if(mapId == MapId.Nullspace) - return false; - - var mapEuid = GetMapEntityId(mapId); - - if (!EntityManager.TryGetComponent(mapEuid, out var map)) - return false; - - return map.MapPaused; - } - - private void SetMapPreInit(MapId mapId) - { - if(mapId == MapId.Nullspace) - return; - - var mapEuid = GetMapEntityId(mapId); - var mapComp = EntityManager.GetComponent(mapEuid); - mapComp.MapPreInit = true; - } - - private bool CheckMapPreInit(MapId mapId) - { - if(mapId == MapId.Nullspace) - return false; - - var mapEuid = GetMapEntityId(mapId); - - if (!EntityManager.TryGetComponent(mapEuid, out var map)) - return false; - - return map.MapPreInit; + var ent = GetMapEntityId(mapId); + EntityManager.GetComponent(ent).MapInitialized = false; + var meta = EntityManager.GetComponent(ent); + ((EntityManager)EntityManager).SetLifeStage(meta, EntityLifeStage.Initialized); } /// public bool IsMapPaused(MapId mapId) { - if(mapId == MapId.Nullspace) - return false; - - var mapEuid = GetMapEntityId(mapId); - - if (!EntityManager.TryGetComponent(mapEuid, out var map)) - return false; - - return map.MapPaused || map.MapPreInit; + return _mapSystem.IsPaused(mapId); } /// - public bool IsMapInitialized(MapId mapId) + public bool IsMapPaused(EntityUid uid) { - return !CheckMapPreInit(mapId); + return _mapSystem.IsPaused(uid); } /// diff --git a/Robust.Shared/Map/MapManager.cs b/Robust.Shared/Map/MapManager.cs index 8d3bd6a5e30..c8d01b26a7e 100644 --- a/Robust.Shared/Map/MapManager.cs +++ b/Robust.Shared/Map/MapManager.cs @@ -3,13 +3,9 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map.Components; -using Robust.Shared.Maths; -using Robust.Shared.Physics; using Robust.Shared.Physics.Collision; -using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Robust.Shared.Map; @@ -25,58 +21,37 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber private ISawmill _sawmill = default!; - private FixtureSystem _fixtureSystem = default!; private SharedMapSystem _mapSystem = default!; private SharedPhysicsSystem _physics = default!; private SharedTransformSystem _transformSystem = default!; - private EntityQuery _fixturesQuery; private EntityQuery _gridTreeQuery; private EntityQuery _gridQuery; - private EntityQuery _physicsQuery; - private EntityQuery _xformQuery; /// public void Initialize() { - _fixturesQuery = EntityManager.GetEntityQuery(); _gridTreeQuery = EntityManager.GetEntityQuery(); _gridQuery = EntityManager.GetEntityQuery(); - _physicsQuery = EntityManager.GetEntityQuery(); - _xformQuery = EntityManager.GetEntityQuery(); _sawmill = Logger.GetSawmill("map"); -#if DEBUG - DebugTools.Assert(!_dbgGuardInit); - DebugTools.Assert(!_dbgGuardRunning); - _dbgGuardInit = true; -#endif InitializeMapPausing(); } /// public void Startup() { - _fixtureSystem = EntityManager.System(); _physics = EntityManager.System(); _transformSystem = EntityManager.System(); _mapSystem = EntityManager.System(); -#if DEBUG - DebugTools.Assert(_dbgGuardInit); - _dbgGuardRunning = true; -#endif - _sawmill.Debug("Starting..."); } /// public void Shutdown() { -#if DEBUG - DebugTools.Assert(_dbgGuardInit); -#endif _sawmill.Debug("Stopping..."); // TODO: AllEntityQuery instead??? @@ -102,9 +77,4 @@ public void Restart() EntityManager.DeleteEntity(uid); } } - -#if DEBUG - private bool _dbgGuardInit; - private bool _dbgGuardRunning; -#endif } diff --git a/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs b/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs index 02990b61c59..0f5a3e3c289 100644 --- a/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs +++ b/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs @@ -13,22 +13,18 @@ namespace Robust.UnitTesting.Client.GameObjects.Components [TestOf(typeof(TransformComponent))] public sealed class TransformComponentTests { - private static readonly MapId TestMapId = new(1); - private static (ISimulation, EntityUid gridA, EntityUid gridB) SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() .InitializeInstance(); + var mapId = sim.Resolve().System().CreateMap(); var mapManager = sim.Resolve(); - // Adds the map with id 1, and spawns entity 1 as the map entity. - mapManager.CreateMap(TestMapId); - // Adds two grids to use in tests. - var gridA = mapManager.CreateGridEntity(TestMapId); - var gridB = mapManager.CreateGridEntity(TestMapId); + var gridA = mapManager.CreateGridEntity(mapId); + var gridB = mapManager.CreateGridEntity(mapId); return (sim, gridA, gridB); } diff --git a/Robust.UnitTesting/Server/GameObjects/ComponentMapInitTest.cs b/Robust.UnitTesting/Server/GameObjects/ComponentMapInitTest.cs index 1d1e07af010..25ed08d6814 100644 --- a/Robust.UnitTesting/Server/GameObjects/ComponentMapInitTest.cs +++ b/Robust.UnitTesting/Server/GameObjects/ComponentMapInitTest.cs @@ -27,7 +27,7 @@ public void ComponentMapInit() var sim = simFactory.InitializeInstance(); var entManager = sim.Resolve(); var mapManager = sim.Resolve(); - var mapId = mapManager.CreateMap(); + sim.Resolve().System().CreateMap(out var mapId); var ent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); Assert.That(entManager.GetComponent(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized)); diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs index 1286d091c46..434ec44fd52 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Numerics; using NUnit.Framework; using Robust.Server.Containers; using Robust.Shared.Containers; @@ -19,14 +18,15 @@ namespace Robust.UnitTesting.Server.GameObjects.Components [TestFixture, Parallelizable] public sealed partial class ContainerTest { + private static EntityCoordinates _coords; + private static ISimulation SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() .InitializeInstance(); - - // Adds the map with id 1, and spawns entity 1 as the map entity. - sim.AddMap(1); + var map = sim.CreateMap(); + _coords = new EntityCoordinates(map.Item1, default); return sim; } @@ -37,8 +37,7 @@ public void TestCreation() var sim = SimulationFactory(); var entManager = sim.Resolve(); var containerSys = sim.Resolve().GetEntitySystem(); - - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(entity, "dummy"); @@ -74,9 +73,8 @@ public void TestInsertion() var sim = SimulationFactory(); var entManager = sim.Resolve(); var containerSys = sim.Resolve().GetEntitySystem(); - - var owner = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); - var inserted = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var owner = sim.SpawnEntity(null,_coords); + var inserted = sim.SpawnEntity(null,_coords); var transform = entManager.GetComponent(inserted); var container = containerSys.MakeContainer(owner, "dummy"); @@ -104,11 +102,10 @@ public void TestNestedRemoval() var sim = SimulationFactory(); var entManager = sim.Resolve(); var containerSys = sim.Resolve().GetEntitySystem(); - - var owner = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); - var inserted = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var owner = sim.SpawnEntity(null,_coords); + var inserted = sim.SpawnEntity(null,_coords); var transform = entManager.GetComponent(inserted); - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(owner, "dummy"); Assert.That(containerSys.Insert(inserted, container), Is.True); @@ -132,8 +129,7 @@ public void TestNestedRemovalWithDenial() var sim = SimulationFactory(); var entMan = sim.Resolve(); var containerSys = sim.Resolve().GetEntitySystem(); - - var coordinates = new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)); + var coordinates =_coords; var entityOne = sim.SpawnEntity(null, coordinates); var entityTwo = sim.SpawnEntity(null, coordinates); var entityThree = sim.SpawnEntity(null, coordinates); @@ -165,8 +161,7 @@ public void BaseContainer_SelfInsert_False() { var sim = SimulationFactory(); var containerSys = sim.Resolve().GetEntitySystem(); - - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(containerSys.Insert(entity, container), Is.False); @@ -178,9 +173,8 @@ public void BaseContainer_InsertMap_False() { var sim = SimulationFactory(); var containerSys = sim.Resolve().GetEntitySystem(); - - var mapEnt = EntityUid.FirstUid; - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var mapEnt = new EntityUid(1); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(containerSys.Insert(mapEnt, container), Is.False); @@ -194,7 +188,7 @@ public void BaseContainer_InsertGrid_False() var containerSys = sim.Resolve().GetEntitySystem(); var grid = sim.Resolve().CreateGridEntity(new MapId(1)).Owner; - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(containerSys.Insert(grid, container), Is.False); @@ -207,10 +201,9 @@ public void BaseContainer_Insert_True() var sim = SimulationFactory(); var entManager = sim.Resolve(); var containerSys = sim.Resolve().GetEntitySystem(); - - var containerEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var containerEntity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(containerEntity, "dummy"); - var insertEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var insertEntity = sim.SpawnEntity(null,_coords); var result = containerSys.Insert(insertEntity, container); @@ -230,10 +223,9 @@ public void BaseContainer_RemoveNotAdded_False() { var sim = SimulationFactory(); var containerSys = sim.Resolve().GetEntitySystem(); - - var containerEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var containerEntity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(containerEntity, "dummy"); - var insertEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var insertEntity = sim.SpawnEntity(null,_coords); var result = containerSys.Remove(insertEntity, container); @@ -245,12 +237,11 @@ public void BaseContainer_Transfer_True() { var sim = SimulationFactory(); var containerSys = sim.Resolve().GetEntitySystem(); - - var entity1 = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity1 = sim.SpawnEntity(null,_coords); var container1 = containerSys.MakeContainer(entity1, "dummy"); - var entity2 = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity2 = sim.SpawnEntity(null,_coords); var container2 = containerSys.MakeContainer(entity2, "dummy"); - var transferEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var transferEntity = sim.SpawnEntity(null,_coords); containerSys.Insert(transferEntity, container1); var result = containerSys.Insert(transferEntity, container2); @@ -266,10 +257,9 @@ public void Container_Serialize() var sim = SimulationFactory(); var entManager = sim.Resolve(); var containerSys = entManager.System(); - - var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var entity = sim.SpawnEntity(null,_coords); var container = containerSys.MakeContainer(entity, "dummy"); - var childEnt = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0))); + var childEnt = sim.SpawnEntity(null,_coords); container.OccludesLight = true; container.ShowContents = true; diff --git a/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs index 9da5438ef64..2977025d011 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/TransformIntegration_Test.cs @@ -20,12 +20,9 @@ public void WorldPositionContainerSet() var sim = factory.InitializeInstance(); var entManager = sim.Resolve(); - var mapManager = sim.Resolve(); var containerSystem = entManager.System(); var xformSystem = entManager.System(); - - var map1Id = mapManager.CreateMap(); - var map1 = mapManager.GetMapEntityId(map1Id); + var map1 = sim.CreateMap().Uid; var ent1 = entManager.SpawnEntity(null, new EntityCoordinates(map1, Vector2.Zero)); var ent2 = entManager.SpawnEntity(null, new EntityCoordinates(map1, Vector2.Zero)); diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs index bb91e6128e8..97e312d4b60 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs @@ -49,7 +49,6 @@ public void Setup() EntityManager = IoCManager.Resolve(); MapManager = IoCManager.Resolve(); - MapManager.CreateMap(); IoCManager.Resolve().Initialize(); var manager = IoCManager.Resolve(); @@ -57,11 +56,12 @@ public void Setup() manager.LoadFromStream(new StringReader(Prototypes)); manager.ResolveResults(); + var mapSys = EntityManager.System(); // build the net dream - MapA = MapManager.CreateMap(); - GridA = MapManager.CreateGridEntity(MapA); + mapSys.CreateMap(out MapA); + mapSys.CreateMap(out MapB); - MapB = MapManager.CreateMap(); + GridA = MapManager.CreateGridEntity(MapA); GridB = MapManager.CreateGridEntity(MapB); //NOTE: The grids have not moved, so we can assert worldpos == localpos for the test diff --git a/Robust.UnitTesting/Server/GameObjects/ThrowingEntityDeletion_Test.cs b/Robust.UnitTesting/Server/GameObjects/ThrowingEntityDeletion_Test.cs index c1ce9fc4342..2bff79e80d5 100644 --- a/Robust.UnitTesting/Server/GameObjects/ThrowingEntityDeletion_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/ThrowingEntityDeletion_Test.cs @@ -37,8 +37,6 @@ public void Setup() .RegisterEntitySystems(f => f.LoadExtraSystemType()) .RegisterPrototypes(protoMan => protoMan.LoadString(PROTOTYPES)) .InitializeInstance(); - - _sim.AddMap(1); } [TestCase("throwInAdd")] @@ -47,8 +45,9 @@ public void Setup() public void Test(string prototypeName) { var entMan = _sim.Resolve(); + _sim.Resolve().System().CreateMap(out var map); - Assert.That(() => entMan.SpawnEntity(prototypeName, new MapCoordinates(0, 0, new MapId(1))), + Assert.That(() => entMan.SpawnEntity(prototypeName, new MapCoordinates(0, 0, map)), Throws.TypeOf()); Assert.That(entMan.GetEntities().Where(p => entMan.GetComponent(p).EntityPrototype?.ID == prototypeName), Is.Empty); diff --git a/Robust.UnitTesting/Server/GameStates/DefaultEntityTest.cs b/Robust.UnitTesting/Server/GameStates/DefaultEntityTest.cs index 5c4186abd83..235c5b1fad7 100644 --- a/Robust.UnitTesting/Server/GameStates/DefaultEntityTest.cs +++ b/Robust.UnitTesting/Server/GameStates/DefaultEntityTest.cs @@ -75,8 +75,7 @@ await server.WaitPost(() => EntityCoordinates coords = default!; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - var map = mapMan.GetMapEntityId(mapId); + var map = server.System().CreateMap(); coords = new(map, default); var playerUid = sEntMan.SpawnEntity(null, coords); player = sEntMan.GetNetEntity(playerUid); diff --git a/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs b/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs index 233e25cb263..c3fc09efa05 100644 --- a/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs +++ b/Robust.UnitTesting/Server/GameStates/MissingParentTest.cs @@ -71,15 +71,13 @@ public async Task TestMissingParent() } // Set up map and spawn player - EntityUid map = default; NetEntity player = default; NetEntity entity = default; EntityCoordinates coords = default; NetCoordinates nCoords = default; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - map = mapMan.GetMapEntityId(mapId); + var map = server.System().CreateMap(); coords = new(map, default); var playerUid = sEntMan.SpawnEntity(null, coords); diff --git a/Robust.UnitTesting/Server/GameStates/PvsChunkTest.cs b/Robust.UnitTesting/Server/GameStates/PvsChunkTest.cs index f3a009d790d..8aa0ecaa292 100644 --- a/Robust.UnitTesting/Server/GameStates/PvsChunkTest.cs +++ b/Robust.UnitTesting/Server/GameStates/PvsChunkTest.cs @@ -69,14 +69,11 @@ public async Task TestGridMapChange() EntityCoordinates mapCoords = default; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - map1 = mapMan.GetMapEntityId(mapId); + map1 = server.System().CreateMap(); mapCoords = new(map1, default); - var map2Id = mapMan.CreateMap(); - map2 = mapMan.GetMapEntityId(map2Id); - - var gridComp = mapMan.CreateGridEntity(map2Id); + map2 = server.System().CreateMap(); + var gridComp = mapMan.CreateGridEntity(map2); grid = gridComp.Owner; mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1)); var gridCoords = new EntityCoordinates(grid, .5f, .5f); diff --git a/Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs b/Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs index c5c3534f461..7fea6330c78 100644 --- a/Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs +++ b/Robust.UnitTesting/Server/GameStates/PvsReEntryTest.cs @@ -74,8 +74,7 @@ public async Task TestLossyReEntry() EntityCoordinates coords = default; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - map = mapMan.GetMapEntityId(mapId); + map = server.System().CreateMap(); coords = new(map, default); var playerUid = sEntMan.SpawnEntity(null, coords); diff --git a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs index 84fa99d82a2..f08e7c5630a 100644 --- a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs +++ b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs @@ -50,8 +50,7 @@ public async Task TestMultipleIndexChange() EntityUid map = default; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - map = mapMan.GetMapEntityId(mapId); + map = server.System().CreateMap(out var mapId); var gridComp = mapMan.CreateGridEntity(mapId); gridComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); grid = gridComp.Owner; diff --git a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs index 515e4709235..68ad2b2202e 100644 --- a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs +++ b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs @@ -11,6 +11,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; namespace Robust.UnitTesting.Server.Maps { @@ -78,15 +79,10 @@ public void Setup() public void TestDataLoadPriority() { // TODO: Fix after serv3 - var map = IoCManager.Resolve(); + // fix what? var entMan = IoCManager.Resolve(); - - var mapId = map.CreateMap(); - // Yay test bullshit - var mapUid = map.GetMapEntityId(mapId); - entMan.EnsureComponent(mapUid); - entMan.EnsureComponent(mapUid); + entMan.System().CreateMap(out var mapId); var traversal = entMan.System(); traversal.Enabled = false; diff --git a/Robust.UnitTesting/Server/RobustServerSimulation.cs b/Robust.UnitTesting/Server/RobustServerSimulation.cs index 8022debcdf9..78b0734030f 100644 --- a/Robust.UnitTesting/Server/RobustServerSimulation.cs +++ b/Robust.UnitTesting/Server/RobustServerSimulation.cs @@ -71,8 +71,7 @@ public interface ISimulation /// /// Adds a new map directly to the map manager. /// - EntityUid AddMap(int mapId); - EntityUid AddMap(MapId mapId); + (EntityUid Uid, MapId MapId) CreateMap(); EntityUid SpawnEntity(string? protoId, EntityCoordinates coordinates); EntityUid SpawnEntity(string? protoId, MapCoordinates coordinates); } @@ -99,18 +98,10 @@ public T Resolve() return Collection.Resolve(); } - public EntityUid AddMap(int mapId) + public (EntityUid Uid, MapId MapId) CreateMap() { - var mapMan = Collection.Resolve(); - mapMan.CreateMap(new MapId(mapId)); - return mapMan.GetMapEntityId(new MapId(mapId)); - } - - public EntityUid AddMap(MapId mapId) - { - var mapMan = Collection.Resolve(); - mapMan.CreateMap(mapId); - return mapMan.GetMapEntityId(mapId); + var uid = Collection.Resolve().System().CreateMap(out var mapId); + return (uid, mapId); } public EntityUid SpawnEntity(string? protoId, EntityCoordinates coordinates) diff --git a/Robust.UnitTesting/Shared/EntityLookup_Test.cs b/Robust.UnitTesting/Shared/EntityLookup_Test.cs index 8994cd86ced..e4deb6d1203 100644 --- a/Robust.UnitTesting/Shared/EntityLookup_Test.cs +++ b/Robust.UnitTesting/Shared/EntityLookup_Test.cs @@ -21,7 +21,7 @@ public void AnyIntersecting() var entManager = server.Resolve(); var mapManager = server.Resolve(); - var mapId = mapManager.CreateMap(); + var mapId = server.CreateMap().MapId; var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One); @@ -43,7 +43,7 @@ public void TestAnchoring() var entManager = server.Resolve(); var mapManager = server.Resolve(); - var mapId = mapManager.CreateMap(); + var mapId = server.CreateMap().MapId; var grid = mapManager.CreateGridEntity(mapId); var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One); diff --git a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs index 907950f785c..51c353d16a9 100644 --- a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs @@ -62,7 +62,7 @@ public async Task TestContainerNonexistantItems() await server.WaitAssertion(() => { - mapId = sMapManager.CreateMap(); + sEntManager.System().CreateMap(out mapId); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); entityUid = sEntManager.SpawnEntity(null, mapPos); @@ -190,7 +190,7 @@ await client.WaitPost(() => await server.WaitAssertion(() => { - mapId = sMapManager.CreateMap(); + sEntManager.System().CreateMap(out mapId); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); sEntityUid = sEntManager.SpawnEntity(null, mapPos); @@ -300,7 +300,7 @@ public async Task Container_DeserializeGrid_IsStillContained() await server.WaitAssertion(() => { // build the map - var mapIdOne = mapManager.CreateMap(); + sEntManager.System().CreateMap(out var mapIdOne); Assert.That(mapManager.IsMapInitialized(mapIdOne), Is.True); var containerEnt = sEntManager.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne)); @@ -327,7 +327,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { var mapLoader = sEntManager.System(); - var mapIdTwo = mapManager.CreateMap(); + sEntManager.System().CreateMap(out var mapIdTwo); // load the map mapLoader.Load(mapIdTwo, "container_test.yml"); diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.OrderedEvents.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.OrderedEvents.cs index 17a6d8354b2..5b655d24015 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.OrderedEvents.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.OrderedEvents.cs @@ -30,8 +30,7 @@ public void TestDifferentComponentsOrderedSameKeySub() .RegisterComponents(factory => factory.RegisterClass()) .InitializeInstance(); - var map = new MapId(1); - simulation.AddMap(map); + var map = simulation.CreateMap().MapId; var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map)); simulation.Resolve().AddComponent(entity); diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.RefDirectedEvents.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.RefDirectedEvents.cs index 2878d59e8a5..c2f1ef3dd90 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.RefDirectedEvents.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.RefDirectedEvents.cs @@ -20,9 +20,7 @@ public void SubscribeCompRefDirectedEvent() .RegisterEntitySystems(factory => factory.LoadExtraSystemType()) .InitializeInstance(); - var map = new MapId(1); - simulation.AddMap(map); - + var map = simulation.CreateMap().MapId; var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map)); IoCManager.Resolve().AddComponent(entity); @@ -86,9 +84,7 @@ public void SortedDirectedRefEvents() }) .InitializeInstance(); - var map = new MapId(1); - simulation.AddMap(map); - + var map = simulation.CreateMap().MapId; var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map)); IoCManager.Resolve().AddComponent(entity); IoCManager.Resolve().AddComponent(entity); diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs index 46064fdeb85..2fa43f4c700 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs @@ -16,8 +16,6 @@ namespace Robust.UnitTesting.Shared.GameObjects [TestFixture, Parallelizable ,TestOf(typeof(EntityManager))] public sealed partial class EntityManager_Components_Tests { - private static readonly EntityCoordinates DefaultCoords = new(EntityUid.FirstUid, Vector2.Zero); - private const string DummyLoad = @" - type: entity id: DummyLoad @@ -35,12 +33,12 @@ public void AddRegistryComponentTest() .RegisterPrototypes(fac => fac.LoadString(DummyLoad)) .InitializeInstance(); - sim.AddMap(1); - var entMan = sim.Resolve(); var protoManager = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var map = sim.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); + var entity = entMan.SpawnEntity(null, coords); Assert.That(!entMan.HasComponent(entity)); var proto = protoManager.Index("DummyLoad"); @@ -60,12 +58,12 @@ public void RemoveRegistryComponentTest() .RegisterPrototypes(fac => fac.LoadString(DummyLoad)) .InitializeInstance(); - sim.AddMap(1); - var entMan = sim.Resolve(); var protoManager = sim.Resolve(); - var entity = entMan.SpawnEntity("DummyLoad", DefaultCoords); + var map = sim.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); + var entity = entMan.SpawnEntity("DummyLoad", coords); var proto = protoManager.Index("DummyLoad"); entMan.RemoveComponents(entity, proto); @@ -80,9 +78,9 @@ public void RemoveRegistryComponentTest() public void AddComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = new DummyComponent() { Owner = entity @@ -100,9 +98,9 @@ public void AddComponentTest() public void AddComponentOverwriteTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = new DummyComponent() { Owner = entity @@ -120,9 +118,9 @@ public void AddComponentOverwriteTest() public void AddComponent_ExistingDeleted() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var firstComp = new DummyComponent {Owner = entity}; entMan.AddComponent(entity, firstComp); entMan.RemoveComponent(entity); @@ -140,9 +138,9 @@ public void AddComponent_ExistingDeleted() public void HasComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); entMan.AddComponent(entity); // Act @@ -156,9 +154,9 @@ public void HasComponentTest() public void HasComponentNoGenericTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); entMan.AddComponent(entity); // Act @@ -172,13 +170,13 @@ public void HasComponentNoGenericTest() public void HasNetComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var factory = sim.Resolve(); var netId = factory.GetRegistration().NetID!; var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); entMan.AddComponent(entity); // Act @@ -192,13 +190,13 @@ public void HasNetComponentTest() public void GetNetComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var factory = sim.Resolve(); var netId = factory.GetRegistration().NetID!; var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -212,9 +210,9 @@ public void GetNetComponentTest() public void TryGetComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -229,13 +227,13 @@ public void TryGetComponentTest() public void TryGetNetComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var factory = sim.Resolve(); var netId = factory.GetRegistration().NetID!; var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -250,9 +248,9 @@ public void TryGetNetComponentTest() public void RemoveComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -266,9 +264,9 @@ public void RemoveComponentTest() [Test] public void EnsureQueuedComponentDeletion() { - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); Assert.That(component.LifeStage, Is.LessThanOrEqualTo(ComponentLifeStage.Running)); @@ -284,13 +282,13 @@ public void EnsureQueuedComponentDeletion() public void RemoveNetComponentTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var factory = sim.Resolve(); var netId = factory.GetRegistration().NetID!; var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -305,9 +303,9 @@ public void RemoveNetComponentTest() public void GetComponentsTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -323,9 +321,9 @@ public void GetComponentsTest() public void GetAllComponentsTest() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -341,10 +339,10 @@ public void GetAllComponentsTest() public void GetAllComponentInstances() { // Arrange - var sim = SimulationFactory(); + var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); var fac = sim.Resolve(); - var entity = entMan.SpawnEntity(null, DefaultCoords); + var entity = entMan.SpawnEntity(null, coords); var component = entMan.AddComponent(entity); // Act @@ -356,17 +354,16 @@ public void GetAllComponentInstances() Assert.That(list[0], Is.EqualTo(component)); } - private static ISimulation SimulationFactory() + private static (ISimulation, EntityCoordinates) SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() .RegisterComponents(factory => factory.RegisterClass()) .InitializeInstance(); - // Adds the map with id 1, and spawns entity 1 as the map entity. - sim.AddMap(1); - - return sim; + var map = sim.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); + return (sim, coords); } [NetworkedComponent()] diff --git a/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs b/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs index e8975b32228..28dc8c5c2f7 100644 --- a/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs @@ -1,7 +1,6 @@ using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared.GameObjects @@ -9,19 +8,12 @@ namespace Robust.UnitTesting.Shared.GameObjects [TestFixture, Parallelizable] sealed class EntityManagerTests { - private static readonly MapId TestMapId = new(1); - private static ISimulation SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() .InitializeInstance(); - var mapManager = sim.Resolve(); - - // Adds the map with id 1, and spawns entity 1 as the map entity. - mapManager.CreateMap(TestMapId); - return sim; } @@ -32,9 +24,10 @@ private static ISimulation SimulationFactory() public void SpawnEntity_PrototypeTransform_Works() { var sim = SimulationFactory(); + var map = sim.CreateMap().MapId; var entMan = sim.Resolve(); - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, TestMapId)); + var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map)); Assert.That(newEnt, Is.Not.EqualTo(EntityUid.Invalid)); } @@ -48,7 +41,7 @@ public void ComponentCount_Works() Assert.That(entManager.Count(), Is.EqualTo(0)); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; Assert.That(entManager.Count(), Is.EqualTo(1)); mapManager.DeleteMap(mapId); Assert.That(entManager.Count(), Is.EqualTo(0)); diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs index c34c679b2a2..e260e50da78 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs @@ -20,8 +20,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems [TestFixture, Parallelizable] public sealed partial class AnchoredSystemTests { - private static readonly MapId TestMapId = new(1); - private sealed class Subscriber : IEntityEventSubscriber { } private const string Prototypes = @" @@ -32,7 +30,7 @@ private sealed class Subscriber : IEntityEventSubscriber { } - type: Transform anchored: true"; - private static (ISimulation, EntityUid gridId) SimulationFactory() + private static (ISimulation, EntityUid gridId, MapCoordinates) SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() @@ -46,12 +44,12 @@ private static (ISimulation, EntityUid gridId) SimulationFactory() var mapManager = sim.Resolve(); // Adds the map with id 1, and spawns entity 1 as the map entity. - mapManager.CreateMap(TestMapId); - + var testMapId = sim.CreateMap().MapId; + var coords = new MapCoordinates(new Vector2(7, 7), testMapId); // Add grid 1, as the default grid to anchor things to. - var grid = mapManager.CreateGridEntity(TestMapId); + var grid = mapManager.CreateGridEntity(testMapId); - return (sim, grid); + return (sim, grid, coords); } // An entity is anchored to the tile it is over on the target grid. @@ -70,11 +68,9 @@ private static (ISimulation, EntityUid gridId) SimulationFactory() [Test] public void OnAnchored_WorldPosition_TileCenter() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - // can only be anchored to a tile var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); @@ -169,9 +165,9 @@ public void OnInitAnchored_AddedToLookup() var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - mapMan.CreateMap(TestMapId); - var grid = mapMan.CreateGrid(TestMapId); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); + var mapId = sim.CreateMap().MapId; + var grid = mapMan.CreateGrid(mapId); + var coordinates = new MapCoordinates(new Vector2(7, 7), mapId); var pos = grid.TileIndicesFor(coordinates); grid.SetTile(pos, new Tile(1)); @@ -194,11 +190,9 @@ public void OnInitAnchored_AddedToLookup() [Test] public void OnAnchored_Parent_SetToGrid() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - // can only be anchored to a tile var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); @@ -221,11 +215,11 @@ public void OnAnchored_Parent_SetToGrid() [Test] public void OnAnchored_EmptyTile_Nop() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, Tile.Empty); @@ -243,11 +237,11 @@ public void OnAnchored_EmptyTile_Nop() [Test] public void OnAnchored_NonEmptyTile_Anchors() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -269,11 +263,11 @@ public void OnAnchored_NonEmptyTile_Anchors() [Test] public void Anchored_SetPosition_Nop() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); // coordinates are already tile centered to prevent snapping and MoveEvent - var coordinates = new MapCoordinates(new Vector2(7.5f, 7.5f), TestMapId); + coordinates = coordinates.Offset(new Vector2(0.5f, 0.5f)); // can only be anchored to a tile var grid = entMan.GetComponent(gridId); @@ -297,12 +291,10 @@ public void Anchored_SetPosition_Nop() [Test] public void Anchored_ChangeParent_Unanchors() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, coordinates); @@ -311,7 +303,7 @@ public void Anchored_ChangeParent_Unanchors() entMan.GetComponent(ent1).Anchored = true; // Act - entMan.EntitySysManager.GetEntitySystem().SetParent(ent1, mapMan.GetMapEntityId(TestMapId)); + entMan.EntitySysManager.GetEntitySystem().SetParent(ent1, mapMan.GetMapEntityId(coordinates.MapId)); Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); @@ -326,11 +318,11 @@ public void Anchored_ChangeParent_Unanchors() [Test] public void Anchored_SetParentSame_Nop() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); entMan.GetComponent(ent1).Anchored = true; @@ -348,11 +340,11 @@ public void Anchored_SetParentSame_Nop() [Test] public void Anchored_TileToSpace_Unanchors() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); grid.SetTile(new Vector2i(100, 100), new Tile(1)); // Prevents the grid from being deleted when the Act happens @@ -376,11 +368,11 @@ public void Anchored_TileToSpace_Unanchors() [Test] public void Anchored_AddToContainer_Unanchors() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); entMan.GetComponent(ent1).Anchored = true; @@ -404,11 +396,11 @@ public void Anchored_AddToContainer_Unanchors() [Test] public void Anchored_AddPhysComp_IsStaticBody() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); entMan.GetComponent(ent1).Anchored = true; @@ -426,12 +418,10 @@ public void Anchored_AddPhysComp_IsStaticBody() [Test] public void OnAnchored_HasPhysicsComp_IsStaticBody() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); var physSystem = sim.Resolve().GetEntitySystem(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - // can only be anchored to a tile var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); @@ -452,11 +442,11 @@ public void OnAnchored_HasPhysicsComp_IsStaticBody() [Test] public void OnUnanchored_HasPhysicsComp_IsDynamicBody() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); var physComp = entMan.AddComponent(ent1); @@ -474,13 +464,13 @@ public void OnUnanchored_HasPhysicsComp_IsDynamicBody() [Test] public void SpawnAnchored_EmptyTile_Unanchors() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); // Act - var ent1 = entMan.SpawnEntity("anchoredEnt", new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity("anchoredEnt", coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); @@ -494,11 +484,11 @@ public void SpawnAnchored_EmptyTile_Unanchors() [Test] public void OnAnchored_InContainer_Nop() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coords) = SimulationFactory(); var entMan = sim.Resolve(); var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId)); + var ent1 = entMan.SpawnEntity(null, coords); var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); grid.SetTile(tileIndices, new Tile(1)); @@ -522,12 +512,10 @@ public void OnAnchored_InContainer_Nop() [Test] public void Unanchored_Unanchor_Nop() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - // can only be anchored to a tile var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); @@ -539,7 +527,7 @@ public void Unanchored_Unanchor_Nop() // Act entMan.System().FailOnMove = true; entMan.GetComponent(ent1).Anchored = false; - Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(mapMan.GetMapEntityId(TestMapId))); + Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(mapMan.GetMapEntityId(coordinates.MapId))); entMan.System().FailOnMove = false; traversal.Enabled = true; } @@ -550,11 +538,9 @@ public void Unanchored_Unanchor_Nop() [Test] public void Anchored_Unanchored_ParentUnchanged() { - var (sim, gridId) = SimulationFactory(); + var (sim, gridId, coordinates) = SimulationFactory(); var entMan = sim.Resolve(); - var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId); - // can only be anchored to a tile var grid = entMan.GetComponent(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs index 8af756effe8..e4431ec888a 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs @@ -17,9 +17,6 @@ private static ISimulation SimulationFactory() .RegisterEntitySystems(f => f.LoadExtraSystemType()) .InitializeInstance(); - // Adds the map with id 1, and spawns entity 1 as the map entity. - sim.AddMap(1); - return sim; } @@ -31,7 +28,8 @@ public void OnMove_LocalPosChanged_RaiseMoveEvent() { var sim = SimulationFactory(); var entMan = sim.Resolve(); - var ent1 = entMan.SpawnEntity(null, new MapCoordinates(Vector2.Zero, new MapId(1))); + var map = sim.CreateMap().MapId; + var ent1 = entMan.SpawnEntity(null, new MapCoordinates(Vector2.Zero, map)); entMan.System().ResetCounters(); IoCManager.Resolve().GetComponent(ent1).LocalPosition = Vector2.One; @@ -47,7 +45,7 @@ public void MoverCoordinatesCorrect() var sim = SimulationFactory(); var entManager = sim.Resolve(); var xformSystem = sim.Resolve().GetEntitySystem(); - var mapId = new MapId(1); + var mapId = sim.CreateMap().MapId; var parent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId)); var parentXform = entManager.GetComponent(parent); @@ -83,7 +81,7 @@ public void DetachMapRecursive() var sim = SimulationFactory(); var entManager = sim.Resolve(); var xformSystem = sim.Resolve().GetEntitySystem(); - var mapId = new MapId(1); + var mapId = sim.CreateMap().MapId; var parent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId)); var parentXform = entManager.GetComponent(parent); diff --git a/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs index 7039f8ff726..336c9e02e78 100644 --- a/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs @@ -20,9 +20,7 @@ public void TestGetWorldMatches() var server = RobustServerSimulation.NewSimulation().InitializeInstance(); var entManager = server.Resolve(); - var mapManager = server.Resolve(); - - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); var ent2 = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(100f, 0f), mapId)); @@ -56,7 +54,7 @@ public void AttachToGridOrMap() var entManager = server.Resolve(); var mapManager = server.Resolve(); - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); grid.Comp.SetTile(new Vector2i(0, 0), new Tile(1)); var gridXform = entManager.GetComponent(grid); diff --git a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs index 0aa713f7ca8..2d0d303b29b 100644 --- a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs +++ b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs @@ -38,12 +38,7 @@ public async Task UnknownEntityTest() server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true)); // Set up map. - EntityUid map = default; - await server.WaitPost(() => - { - var mapId = server.MapMan.CreateMap(); - map = server.MapMan.GetMapEntityId(mapId); - }); + var map = server.System().CreateMap(); await RunTicks(); @@ -162,12 +157,7 @@ public async Task UnknownEntityDeleteTest() server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true)); // Set up map. - EntityUid map = default; - await server.WaitPost(() => - { - var mapId = server.MapMan.CreateMap(); - map = server.MapMan.GetMapEntityId(mapId); - }); + var map = server.System().CreateMap(); await RunTicks(); diff --git a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs index d73656ea04e..4185e9524b7 100644 --- a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs +++ b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs @@ -58,11 +58,10 @@ async Task RunTicks() EntityUid grid2 = default; NetEntity grid1Net = default; NetEntity grid2Net = default; + server.System().CreateMap(out var mapId); await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - mapMan.GetMapEntityId(mapId); var gridComp = mapMan.CreateGridEntity(mapId); gridComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); grid1 = gridComp.Owner; diff --git a/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs b/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs index eecee8159d7..e0829283f7d 100644 --- a/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs +++ b/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs @@ -37,12 +37,7 @@ public async Task ReferencesAreNotShared() client.Post(() => netMan.ClientConnect(null!, 0, null!)); // Set up map. - EntityUid map = default; - await server.WaitPost(() => - { - var mapId = server.MapMan.CreateMap(); - map = server.MapMan.GetMapEntityId(mapId); - }); + var map = server.System().CreateMap(); await RunTicks(); diff --git a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs index d1613b1e477..79f8efdb914 100644 --- a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs @@ -48,8 +48,7 @@ public void IsValid_EntityDeleted_False() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); - var mapEntity = mapManager.CreateNewMapEntity(mapId); + var mapEntity = entityManager.System().CreateMap(out var mapId); var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId)); var coords = IoCManager.Resolve().GetComponent(newEnt).Coordinates; @@ -74,10 +73,7 @@ public void IsValid_EntityDeleted_False() public void IsValid_NonFiniteVector_False(float x, float y) { var entityManager = IoCManager.Resolve(); - var mapManager = IoCManager.Resolve(); - - var mapId = mapManager.CreateMap(); - var mapEntity = mapManager.CreateNewMapEntity(mapId); + entityManager.System().CreateMap(out var mapId); var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(new Vector2(x, y), mapId)); var coords = IoCManager.Resolve().GetComponent(newEnt).Coordinates; @@ -88,11 +84,7 @@ public void IsValid_NonFiniteVector_False(float x, float y) [Test] public void EntityCoordinates_Map() { - var mapManager = IoCManager.Resolve(); - - var mapId = mapManager.CreateMap(); - var mapEntity = mapManager.CreateNewMapEntity(mapId); - + var mapEntity = IoCManager.Resolve().System().CreateMap(); Assert.That(IoCManager.Resolve().GetComponent(mapEntity).ParentUid.IsValid(), Is.False); Assert.That(IoCManager.Resolve().GetComponent(mapEntity).Coordinates.EntityId, Is.EqualTo(mapEntity)); } @@ -104,12 +96,10 @@ public void EntityCoordinates_Map() [Test] public void NoParent_OffsetZero() { - var mapManager = IoCManager.Resolve(); var entMan = IoCManager.Resolve(); var uid = entMan.SpawnEntity(null, MapCoordinates.Nullspace); var xform = entMan.GetComponent(uid); Assert.That(xform.Coordinates.Position, Is.EqualTo(Vector2.Zero)); - xform.LocalPosition = Vector2.One; Assert.That(xform.Coordinates.Position, Is.EqualTo(Vector2.Zero)); } @@ -120,8 +110,7 @@ public void GetGridId_Map() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); - var mapEnt = mapManager.CreateNewMapEntity(mapId); + var mapEnt = entityManager.System().CreateMap(out var mapId); var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId)); Assert.That(IoCManager.Resolve().GetComponent(mapEnt).Coordinates.GetGridUid(entityManager), Is.Null); @@ -135,7 +124,7 @@ public void GetGridId_Grid() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); + entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(gridEnt, Vector2.Zero)); @@ -152,8 +141,7 @@ public void GetMapId_Map() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); - var mapEnt = mapManager.CreateNewMapEntity(mapId); + var mapEnt = entityManager.System().CreateMap(out var mapId); var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId)); Assert.That(IoCManager.Resolve().GetComponent(mapEnt).Coordinates.GetMapId(entityManager), Is.EqualTo(mapId)); @@ -166,7 +154,7 @@ public void GetMapId_Grid() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); + entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(gridEnt, Vector2.Zero)); @@ -181,9 +169,8 @@ public void GetParent() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); + var mapEnt = entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); - var mapEnt = mapManager.GetMapEntityId(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, Vector2.Zero)); @@ -203,9 +190,8 @@ public void TryGetParent() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); + var mapEnt = entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); - var mapEnt = mapManager.GetMapEntityId(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, Vector2.Zero)); @@ -256,7 +242,7 @@ public void ToMap_MoveGrid(float x1, float y1, float x2, float y2) var transformSystem = entityManager.System(); - var mapId = mapManager.CreateMap(); + entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, entPos)); @@ -274,8 +260,7 @@ public void WithEntityId() var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); - var mapId = mapManager.CreateMap(); - var mapEnt = mapManager.GetMapEntityId(mapId); + var mapEnt = entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridEnt = grid.Owner; var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, Vector2.Zero)); diff --git a/Robust.UnitTesting/Shared/Map/GridCollision_Test.cs b/Robust.UnitTesting/Shared/Map/GridCollision_Test.cs index da5ff311b8f..59b7318fe6e 100644 --- a/Robust.UnitTesting/Shared/Map/GridCollision_Test.cs +++ b/Robust.UnitTesting/Shared/Map/GridCollision_Test.cs @@ -33,7 +33,7 @@ public async Task TestGridsCollide() await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out mapId); gridId1 = mapManager.CreateGridEntity(mapId); gridId2 = mapManager.CreateGridEntity(mapId); gridEnt1 = gridId1.Value.Owner; diff --git a/Robust.UnitTesting/Shared/Map/GridContraction_Test.cs b/Robust.UnitTesting/Shared/Map/GridContraction_Test.cs index 0fcb868dbe8..3d1d348d42b 100644 --- a/Robust.UnitTesting/Shared/Map/GridContraction_Test.cs +++ b/Robust.UnitTesting/Shared/Map/GridContraction_Test.cs @@ -21,7 +21,7 @@ public async Task TestGridDeletes() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridEntity = grid.Owner; @@ -59,7 +59,7 @@ public async Task TestGridNoDeletes() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); for (var i = 0; i < 10; i++) diff --git a/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs b/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs index fa66557e500..cb27151cbde 100644 --- a/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs @@ -30,7 +30,7 @@ public async Task TestGridFixtures() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); // Should be nothing if grid empty diff --git a/Robust.UnitTesting/Shared/Map/GridMerge_Tests.cs b/Robust.UnitTesting/Shared/Map/GridMerge_Tests.cs index 0e46950fdfc..90345283457 100644 --- a/Robust.UnitTesting/Shared/Map/GridMerge_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridMerge_Tests.cs @@ -45,7 +45,7 @@ public void Merge(Vector2i offset, Angle angle, Box2 bounds) var mapSystem = entMan.System(); var gridFixtures = entMan.System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid1 = mapManager.CreateGridEntity(mapId); var grid2 = mapManager.CreateGridEntity(mapId); var tiles = new List<(Vector2i, Tile)>(); diff --git a/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs b/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs index 958329d9601..ed8fb7dc796 100644 --- a/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridRotation_Tests.cs @@ -28,7 +28,7 @@ public async Task TestLocalWorldConversions() await server.WaitAssertion(() => { - var mapId = mapMan.CreateMap(); + entMan.System().CreateMap(out var mapId); var grid = mapMan.CreateGridEntity(mapId); var gridEnt = grid.Owner; var coordinates = new EntityCoordinates(gridEnt, new Vector2(10, 0)); @@ -67,7 +67,7 @@ public async Task TestChunkRotations() await server.WaitAssertion(() => { - var mapId = mapMan.CreateMap(); + entMan.System().CreateMap(out var mapId); var grid = mapMan.CreateGridEntity(mapId); var gridEnt = grid.Owner; diff --git a/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs b/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs index e5768eb1b65..3439f45fd9a 100644 --- a/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridSplit_Tests.cs @@ -34,7 +34,7 @@ public void NoSplit() var mapManager = sim.Resolve(); var mapSystem = sim.Resolve().System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var gridEnt = mapManager.CreateGridEntity(mapId); var grid = gridEnt.Comp; grid.CanSplit = false; @@ -62,7 +62,7 @@ public void SimpleSplit() var sim = GetSim(); var mapManager = sim.Resolve(); var mapSystem = sim.Resolve().System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var gridEnt = mapManager.CreateGridEntity(mapId); for (var x = 0; x < 3; x++) @@ -84,7 +84,7 @@ public void DonutSplit() var sim = GetSim(); var mapManager = sim.Resolve(); var mapSystem = sim.Resolve().System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var gridEnt = mapManager.CreateGridEntity(mapId); for (var x = 0; x < 3; x++) @@ -115,7 +115,7 @@ public void TriSplit() var sim = GetSim(); var mapManager = sim.Resolve(); var mapSystem = sim.Resolve().System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var gridEnt = mapManager.CreateGridEntity(mapId); for (var x = 0; x < 3; x++) @@ -143,7 +143,7 @@ public void ReparentSplit() var entManager = sim.Resolve(); var mapManager = sim.Resolve(); var mapSystem = sim.Resolve().System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var gridEnt = mapManager.CreateGridEntity(mapId); var grid = gridEnt.Comp; diff --git a/Robust.UnitTesting/Shared/Map/MapGridMap_Tests.cs b/Robust.UnitTesting/Shared/Map/MapGridMap_Tests.cs index 5ebd9cc62d7..9c7743d5143 100644 --- a/Robust.UnitTesting/Shared/Map/MapGridMap_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapGridMap_Tests.cs @@ -22,7 +22,7 @@ public void FindGrids() var entManager = sim.Resolve(); var mapManager = sim.Resolve(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; Assert.That(!mapManager.FindGridsIntersecting(mapId, Box2.UnitCentered).Any()); entManager.AddComponent(mapManager.GetMapEntityId(mapId)); @@ -40,7 +40,7 @@ public void AddGridCompToMap() var entManager = sim.Resolve(); var mapManager = sim.Resolve(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; mapManager.CreateGridEntity(mapId); Assert.DoesNotThrow(() => diff --git a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs index 3f2e20ba06f..e5233cb7cdb 100644 --- a/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapGrid_Tests.cs @@ -31,7 +31,7 @@ public void GetTileRefCoords() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1)); @@ -52,7 +52,7 @@ public void BoundsExpansion() var sim = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); var gridXform = entMan.GetComponent(grid.Owner); gridXform.WorldPosition = new Vector2(3, 5); @@ -78,7 +78,7 @@ public void BoundsContract() var sim = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); var gridXform = entMan.GetComponent(grid.Owner); @@ -103,7 +103,7 @@ public void GridTileToChunkIndices() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); var result = grid.GridTileToChunkIndices(new Vector2i(-9, -1)); @@ -119,7 +119,7 @@ public void ToLocalCentered() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); var result = grid.GridTileToLocal(new Vector2i(0, 0)).Position; @@ -133,7 +133,7 @@ public void TryGetTileRefNoTile() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef); @@ -148,7 +148,7 @@ public void TryGetTileRefTileExists() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1)); @@ -166,7 +166,7 @@ public void PointCollidesWithGrid() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); grid.SetTile(new Vector2i(19, 23), new Tile(1)); @@ -181,7 +181,7 @@ public void PointNotCollideWithGrid() { var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapMan.CreateGrid(mapId, 8); grid.SetTile(new Vector2i(19, 23), new Tile(1)); diff --git a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs index 16ba206069f..218df558425 100644 --- a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs @@ -28,8 +28,7 @@ public void Restart_ExistingMap_IsRemoved() var sim = SimulationFactory(); var mapMan = sim.Resolve(); - var mapID = new MapId(11); - mapMan.CreateMap(mapID); + var mapID = sim.CreateMap().MapId; mapMan.Restart(); @@ -46,8 +45,7 @@ public void Restart_ExistingGrid_IsRemoved() var mapMan = sim.Resolve(); var entMan = sim.Resolve(); - var mapID = new MapId(11); - mapMan.CreateMap(mapID); + var mapID = sim.CreateMap().MapId; var grid = mapMan.CreateGridEntity(mapID); mapMan.Restart(); @@ -69,60 +67,13 @@ public void Restart_NullspaceMap_IsEmptied() Assert.That(entMan.Deleted(oldEntity), Is.True); } - /// - /// When using SetMapEntity, the existing entities on the map are removed, and the new map entity gets a MapComponent. - /// - [Test] - public void SetMapEntity_WithExistingEntity_ExistingEntityDeleted() - { - // Arrange - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - - var mapID = new MapId(11); - - mapMan.CreateMap(new MapId(7)); - mapMan.CreateMap(mapID); - var oldMapEntity = mapMan.GetMapEntityId(mapID); - var newMapEntity = entMan.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, new MapId(7))); - - // Act - mapMan.SetMapEntity(mapID, newMapEntity); - - // Assert - Assert.That(entMan.Deleted(oldMapEntity)); - Assert.That(entMan.HasComponent(newMapEntity)); - - var mapComp = entMan.GetComponent(newMapEntity); - Assert.That(mapComp.MapId == mapID); - } - - /// - /// After creating a new map entity for nullspace, you can spawn entities into nullspace like any other map. - /// - [Test] - public void SpawnEntityAt_IntoNullspace_Success() - { - // Arrange - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - - // Act - var newEntity = entMan.SpawnEntity(null, MapCoordinates.Nullspace); - - // Assert - Assert.That(entMan.GetComponent(newEntity).MapID, Is.EqualTo(MapId.Nullspace)); - } - [Test] public void Restart_MapEntity_IsRemoved() { var sim = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var map = mapMan.CreateMap(); - var entity = mapMan.GetMapEntityId(map); + var entity = entMan.System().CreateMap(); mapMan.Restart(); Assert.That((!entMan.EntityExists(entity) ? EntityLifeStage.Deleted : entMan.GetComponent(entity).EntityLifeStage) >= EntityLifeStage.Deleted, Is.True); } diff --git a/Robust.UnitTesting/Shared/Map/MapPauseTests.cs b/Robust.UnitTesting/Shared/Map/MapPauseTests.cs index 7ece14055e4..0f4fdbc1467 100644 --- a/Robust.UnitTesting/Shared/Map/MapPauseTests.cs +++ b/Robust.UnitTesting/Shared/Map/MapPauseTests.cs @@ -31,10 +31,9 @@ public void Paused_NotIncluded_NotInQuery() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, true); - - entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, true); + entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); var query = entMan.EntityQuery(false).ToList(); @@ -53,10 +52,9 @@ public void UnPaused_NotIncluded_InQuery() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, false); - - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, false); + entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); var query = entMan.EntityQuery(false).ToList(); @@ -75,10 +73,9 @@ public void Paused_Included_InQuery() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, true); - - entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, true); + entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); var query = entMan.EntityQuery(true).ToList(); @@ -97,10 +94,9 @@ public void Paused_AddEntity_IsPaused() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, true); - - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, true); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.True); @@ -117,10 +113,9 @@ public void UnPaused_AddEntity_IsNotPaused() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, false); - - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, false); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.False); @@ -137,8 +132,8 @@ public void Paused_AddGrid_GridPaused() var mapMan = sim.Resolve(); // arrange - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, true); + var mapId = sim.CreateMap().MapId; + entMan.System().SetPaused(mapId, true); // act var newGrid = mapMan.CreateGridEntity(mapId); @@ -160,17 +155,16 @@ public void Paused_TeleportBetweenMaps_Unpaused() var mapMan = sim.Resolve(); // arrange - var map1 = mapMan.CreateMap(); - mapMan.SetMapPaused(map1, true); - - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1)); + var map1 = sim.CreateMap().Uid; + entMan.System().SetPaused(map1, true); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default)); var xform = entMan.GetComponent(newEnt); - var map2 = mapMan.CreateMap(); - mapMan.SetMapPaused(map2, false); + var map2 = sim.CreateMap().Uid; + entMan.System().SetPaused(map2, false); // Act - entMan.EntitySysManager.GetEntitySystem().SetParent(xform.Owner, mapMan.GetMapEntityId(map2)); + entMan.EntitySysManager.GetEntitySystem().SetParent(xform.Owner, map2); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.False); @@ -188,17 +182,16 @@ public void Unpaused_TeleportBetweenMaps_IsPaused() var mapMan = sim.Resolve(); // arrange - var map1 = mapMan.CreateMap(); - mapMan.SetMapPaused(map1, false); - - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1)); + var map1 = sim.CreateMap().Uid; + entMan.System().SetPaused(map1, false); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(map1, default)); var xform = entMan.GetComponent(newEnt); - var map2 = mapMan.CreateMap(); - mapMan.SetMapPaused(map2, true); + var map2 = sim.CreateMap().Uid; + entMan.System().SetPaused(map2, true); // Act - entMan.EntitySysManager.GetEntitySystem().SetParent(xform.Owner, mapMan.GetMapEntityId(map2)); + entMan.EntitySysManager.GetEntitySystem().SetParent(xform.Owner, map2); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.True); @@ -214,11 +207,11 @@ public void Paused_UnpauseMap_UnpausedEntities() var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, true); - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, true); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); - mapMan.SetMapPaused(mapId, false); + entMan.System().SetPaused(mapId, false); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.False); @@ -234,75 +227,13 @@ public void Unpaused_PauseMap_PausedEntities() var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var mapId = mapMan.CreateMap(); - mapMan.SetMapPaused(mapId, false); - var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var mapId = sim.CreateMap().Uid; + entMan.System().SetPaused(mapId, false); + var newEnt = entMan.SpawnEntity(null, new EntityCoordinates(mapId, default)); - mapMan.SetMapPaused(mapId, true); + entMan.System().SetPaused(mapId, true); var metaData = entMan.GetComponent(newEnt); Assert.That(metaData.EntityPaused, Is.True); } - - /// - /// An unallocated MapId is always unpaused. - /// - [Test] - public void UnallocatedMap_IsUnPaused() - { - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = (IMapManagerInternal)sim.Resolve(); - - // some random unallocated MapId - var paused = mapMan.IsMapPaused(new MapId(12)); - - Assert.That(paused, Is.False); - } - - /// - /// Nullspace is always unpaused, and setting it is a no-op. - /// - [Test] - public void Nullspace_Pause_IsUnPaused() - { - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = (IMapManagerInternal)sim.Resolve(); - - mapMan.SetMapPaused(MapId.Nullspace, true); - - var paused = mapMan.IsMapPaused(MapId.Nullspace); - Assert.That(paused, Is.False); - } - - /// - /// An allocated MapId without an allocated entity (Nullspace) is always unpaused. - /// - [Test] - public void Nullspace_IsUnPaused() - { - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = (IMapManagerInternal)sim.Resolve(); - - var paused = mapMan.IsMapPaused(MapId.Nullspace); - - Assert.That(paused, Is.False); - } - - /// - /// A freed MapId is always unpaused. - /// - [Test] - public void Unpaused_Freed_IsUnPaused() - { - var sim = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = (IMapManagerInternal)sim.Resolve(); - - var paused = mapMan.IsMapPaused(MapId.Nullspace); - - Assert.That(paused, Is.False); - } } diff --git a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs index 5ca7704eb9b..0e0db47d147 100644 --- a/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs +++ b/Robust.UnitTesting/Shared/Map/SingleTileRemoveTest.cs @@ -75,9 +75,7 @@ public async Task TestRemoveSingleTile() var sys = sEntMan.System(); await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - sMap = mapMan.GetMapEntityId(mapId); - + 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)); diff --git a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs index e3a33cc6377..b8d83181008 100644 --- a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs +++ b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs @@ -60,8 +60,7 @@ public async Task TestBroadphaseNetworking() EntityUid map1 = default; await server.WaitPost(() => { - var mapId = mapMan.CreateMap(); - map1 = mapMan.GetMapEntityId(mapId); + map1 = sEntMan.System().CreateMap(out var mapId); var gridEnt = mapMan.CreateGridEntity(mapId); gridEnt.Comp.SetTile(Vector2i.Zero, new Tile(1)); grid1 = gridEnt.Owner; @@ -132,8 +131,7 @@ await client.WaitPost(() => await server.WaitPost(() => { // Create grid - var mapId = mapMan.CreateMap(); - map2 = mapMan.GetMapEntityId(mapId); + map2 = sEntMan.System().CreateMap(out var mapId); var gridEnt = mapMan.CreateGridEntity(mapId); gridEnt.Comp.SetTile(Vector2i.Zero, new Tile(1)); grid2 = gridEnt.Owner; diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index 5143efb8f4d..0f0118b0871 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -26,8 +26,7 @@ public void ReparentSundries() var entManager = sim.Resolve(); var mapManager = sim.Resolve(); - var mapId = mapManager.CreateMap(); - var mapEnt = mapManager.GetMapEntityId(mapId); + var (mapEnt, mapId) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); @@ -61,8 +60,7 @@ public void ReparentBroadphase() var fixturesSystem = entManager.EntitySysManager.GetEntitySystem(); var physicsSystem = entManager.EntitySysManager.GetEntitySystem(); - var mapId = mapManager.CreateMap(); - var mapEnt = mapManager.GetMapEntityId(mapId); + var (mapEnt, mapId) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); var gridUid = grid.Owner; @@ -110,8 +108,8 @@ public void GridMapUpdate() var entManager = sim.Resolve(); var mapManager = sim.Resolve(); - var mapId1 = mapManager.CreateMap(); - var mapId2 = mapManager.CreateMap(); + var mapId1 = sim.CreateMap().MapId; + var mapId2 = sim.CreateMap().MapId; var grid = mapManager.CreateGridEntity(mapId1); var xform = entManager.GetComponent(grid); @@ -143,7 +141,7 @@ public void BroadphaseRecursiveUpdate() var physicsSystem = system.GetEntitySystem(); var lookup = system.GetEntitySystem(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapManager.CreateGridEntity(mapId); grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); @@ -195,10 +193,8 @@ public void EntMapChangeRecursiveUpdate() var fixtures = system.GetEntitySystem(); // setup maps - var mapAId = mapManager.CreateMap(); - var mapA = mapManager.GetMapEntityId(mapAId); - var mapBId = mapManager.CreateMap(); - var mapB = mapManager.GetMapEntityId(mapBId); + var (mapA, mapAId) = sim.CreateMap(); + var (mapB, mapBId) = sim.CreateMap(); // setup grids var gridAComp = mapManager.CreateGridEntity(mapAId); @@ -323,10 +319,8 @@ public void BroadphaseRecursiveNullspaceUpdate() var physSystem = system.GetEntitySystem(); var lookup = system.GetEntitySystem(); var fixtures = system.GetEntitySystem(); - var mapManager = sim.Resolve(); + var (mapUid, mapId) = sim.CreateMap(); - var mapId = mapManager.CreateMap(); - var mapUid = mapManager.GetMapEntityId(mapId); var mapBroadphase = entManager.GetComponent(mapUid); Assert.That(entManager.EntityQuery(true).Count(), Is.EqualTo(1)); diff --git a/Robust.UnitTesting/Shared/Physics/CollisionWake_Test.cs b/Robust.UnitTesting/Shared/Physics/CollisionWake_Test.cs index d2d7aa246d4..ed15497a39d 100644 --- a/Robust.UnitTesting/Shared/Physics/CollisionWake_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/CollisionWake_Test.cs @@ -51,7 +51,7 @@ public async Task TestCollisionWakeGrid() await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out mapId); grid = mapManager.CreateGridEntity(mapId); grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); diff --git a/Robust.UnitTesting/Shared/Physics/Collision_Test.cs b/Robust.UnitTesting/Shared/Physics/Collision_Test.cs index 08bc6b80782..7a5dc3c08ed 100644 --- a/Robust.UnitTesting/Shared/Physics/Collision_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Collision_Test.cs @@ -102,8 +102,8 @@ public void CrossMapContacts() var fixtures = entManager.System(); var physics = entManager.System(); var xformSystem = entManager.System(); - var mapId = mapManager.CreateMap(); - var mapId2 = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; + var mapId2 = sim.CreateMap().MapId; var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); var ent2 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); diff --git a/Robust.UnitTesting/Shared/Physics/Fixtures_Test.cs b/Robust.UnitTesting/Shared/Physics/Fixtures_Test.cs index 443830fcd3a..9c25a17c596 100644 --- a/Robust.UnitTesting/Shared/Physics/Fixtures_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Fixtures_Test.cs @@ -24,7 +24,7 @@ public void SetDensity() var sysManager = sim.Resolve(); var fixturesSystem = sysManager.GetEntitySystem(); var physicsSystem = sysManager.GetEntitySystem(); - var map = mapManager.CreateMap(); + var map = sim.CreateMap().MapId; var ent = sim.SpawnEntity(null, new MapCoordinates(Vector2.Zero, map)); var body = entManager.AddComponent(ent); diff --git a/Robust.UnitTesting/Shared/Physics/GridDeletion_Test.cs b/Robust.UnitTesting/Shared/Physics/GridDeletion_Test.cs index 27d3121c6a3..5bc66d892b0 100644 --- a/Robust.UnitTesting/Shared/Physics/GridDeletion_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/GridDeletion_Test.cs @@ -36,7 +36,7 @@ public async Task GridDeletionTest() await server.WaitAssertion(() => { - mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out mapId); grid = mapManager.CreateGridEntity(mapId); physics = entManager.GetComponent(grid); diff --git a/Robust.UnitTesting/Shared/Physics/GridMovement_Test.cs b/Robust.UnitTesting/Shared/Physics/GridMovement_Test.cs index d8ccf5896ba..62d23b0043d 100644 --- a/Robust.UnitTesting/Shared/Physics/GridMovement_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/GridMovement_Test.cs @@ -31,7 +31,7 @@ public async Task TestFindGridContacts() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); // Setup 1 body on grid, 1 body off grid, and assert that it's all gucci. diff --git a/Robust.UnitTesting/Shared/Physics/JointDeletion_Test.cs b/Robust.UnitTesting/Shared/Physics/JointDeletion_Test.cs index 5a3833a4e94..099ad25694f 100644 --- a/Robust.UnitTesting/Shared/Physics/JointDeletion_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/JointDeletion_Test.cs @@ -39,7 +39,7 @@ public async Task JointDeletionTest() await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out mapId); ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); ent2 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId)); diff --git a/Robust.UnitTesting/Shared/Physics/Joints_Test.cs b/Robust.UnitTesting/Shared/Physics/Joints_Test.cs index 7c106b851db..cd68b31bf9a 100644 --- a/Robust.UnitTesting/Shared/Physics/Joints_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Joints_Test.cs @@ -27,7 +27,7 @@ public void JointsRelayTest() var mapManager = sim.Resolve(); var jointSystem = entManager.System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var uidA = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId)); var uidB = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId)); @@ -71,7 +71,7 @@ public void JointsCollidableTest() var broadphaseSystem = entManager.EntitySysManager.GetEntitySystem(); var physicsSystem = server.Resolve().GetEntitySystem(); - var mapId = mapManager.CreateMap(); + var mapId = server.CreateMap().MapId; var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); var ent2 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); diff --git a/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs b/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs index 84633036c93..a843a7ac035 100644 --- a/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs @@ -43,7 +43,7 @@ public async Task TestMapVelocities() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var grid2 = mapManager.CreateGridEntity(mapId); var gridUidA = grid.Owner; @@ -115,7 +115,7 @@ public async Task TestNestedParentVelocities() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entityManager.System().CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var gridUid = grid.Owner; diff --git a/Robust.UnitTesting/Shared/Physics/PhysicsComponent_Test.cs b/Robust.UnitTesting/Shared/Physics/PhysicsComponent_Test.cs index afe7b769c31..291329efe72 100644 --- a/Robust.UnitTesting/Shared/Physics/PhysicsComponent_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/PhysicsComponent_Test.cs @@ -32,7 +32,7 @@ public async Task TestPointLinearImpulse() await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + entManager.System().CreateMap(out var mapId); var boxEnt = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); var box = entManager.AddComponent(boxEnt); var poly = new PolygonShape(); diff --git a/Robust.UnitTesting/Shared/Physics/PhysicsMap_Test.cs b/Robust.UnitTesting/Shared/Physics/PhysicsMap_Test.cs index def98fbb789..5f3bbbb3572 100644 --- a/Robust.UnitTesting/Shared/Physics/PhysicsMap_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/PhysicsMap_Test.cs @@ -29,8 +29,8 @@ public void RecursiveMapChange() var fixtureSystem = system.GetEntitySystem(); var xformSystem = system.GetEntitySystem(); - var mapId = mapManager.CreateMap(); - var mapId2 = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; + var mapId2 = sim.CreateMap().MapId; var mapUid = mapManager.GetMapEntityId(mapId); var mapUid2 = mapManager.GetMapEntityId(mapId2); diff --git a/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs b/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs index 484ad22101a..8dcccbe3880 100644 --- a/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs +++ b/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs @@ -27,7 +27,7 @@ public void ContainerRecursiveUpdateTest() var xforms = entManager.System(); var containers = entManager.System(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var grid = mapManager.CreateGridEntity(mapId); var guid = grid.Owner; grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); @@ -166,7 +166,7 @@ public void RecursiveMoveTest() var transforms = entManager.EntitySysManager.GetEntitySystem(); var lookup = entManager.EntitySysManager.GetEntitySystem(); - var mapId = mapManager.CreateMap(); + var mapId = sim.CreateMap().MapId; var map = mapManager.GetMapEntityId(mapId); var mapBroadphase = entManager.GetComponent(map); diff --git a/Robust.UnitTesting/Shared/Physics/Stack_Test.cs b/Robust.UnitTesting/Shared/Physics/Stack_Test.cs index fb843d50084..9412df334bb 100644 --- a/Robust.UnitTesting/Shared/Physics/Stack_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Stack_Test.cs @@ -66,9 +66,7 @@ public async Task TestBoxStack() await server.WaitPost(() => { - mapId = mapManager.CreateMap(); - - var mapUid = mapManager.GetMapEntityId(mapId); + var mapUid = entityManager.System().CreateMap(out mapId); gravSystem.SetGravity(mapUid, new Vector2(0f, -9.8f)); var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); @@ -173,8 +171,7 @@ public async Task TestCircleStack() await server.WaitPost(() => { - mapId = mapManager.CreateMap(); - var mapUid = mapManager.GetMapEntityId(mapId); + var mapUid = entityManager.System().CreateMap(out mapId); gravSystem.SetGravity(mapUid, new Vector2(0f, -9.8f)); var groundUid = entityManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); diff --git a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs index 62bbc69261a..387f891a23c 100644 --- a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs +++ b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs @@ -55,7 +55,7 @@ public void Setup() [Test] public void TestHotReload() { - var id = IoCManager.Resolve().CreateMap(); + IoCManager.Resolve().System().CreateMap(out var id); var entity = _entities.SpawnEntity(DummyId, new MapCoordinates(default, id)); var entityComponent = IoCManager.Resolve().GetComponent(entity); diff --git a/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs b/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs index 3fc8251ac33..d64b083a126 100644 --- a/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs @@ -59,12 +59,7 @@ public void Test() prototypeManager.ResolveResults(); var entityManager = IoCManager.Resolve(); - - var mapManager = IoCManager.Resolve(); - - var mapId = new MapId(1); - - mapManager.CreateMap(mapId); + entityManager.System().CreateMap(out var mapId); var coordinates = new MapCoordinates(0, 0, mapId); diff --git a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs index c1cd947d70d..f2f3d952d90 100644 --- a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs +++ b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs @@ -49,8 +49,7 @@ protected async Task Setup() // Set up map and spawn several nested containers await Server.WaitPost(() => { - MapId = MapMan.CreateMap(); - Map = MapMan.GetMapEntityId(MapId); + Map = Server.System().CreateMap(out MapId); Parent = EntMan.SpawnEntity(null, new EntityCoordinates(Map, new(1,2))); ChildA = EntMan.SpawnEntity(null, new EntityCoordinates(Map, default)); ChildB = EntMan.SpawnEntity(null, new EntityCoordinates(Map, default)); diff --git a/Robust.UnitTesting/Shared/Spawning/SpawnNextToOrDropTest.cs b/Robust.UnitTesting/Shared/Spawning/SpawnNextToOrDropTest.cs index fa1cd9f4bd1..67c3d3fe70f 100644 --- a/Robust.UnitTesting/Shared/Spawning/SpawnNextToOrDropTest.cs +++ b/Robust.UnitTesting/Shared/Spawning/SpawnNextToOrDropTest.cs @@ -107,6 +107,20 @@ await Server.WaitPost(() => Assert.That(xform.GridUid, Is.Null); }); + // Spawning next to an entity on a pre-init map does not initialize the entity. + // Previously the intermediate step of spawning the entity into nullspace would cause it to get initialized. + await Server.WaitPost(() => + { + var preInitMap = EntMan.System().CreateMap(out var mapId, runMapInit: false); + var ent = EntMan.Spawn(null, new MapCoordinates(default, mapId)); + + Assert.That(EntMan.GetComponent(preInitMap).EntityLifeStage, Is.LessThan(EntityLifeStage.MapInitialized)); + Assert.That(EntMan.GetComponent(ent).EntityLifeStage, Is.LessThan(EntityLifeStage.MapInitialized)); + + var uid = EntMan.SpawnNextToOrDrop(null, ent); + Assert.That(EntMan.GetComponent(uid).EntityLifeStage, Is.LessThan(EntityLifeStage.MapInitialized)); + }); + await Server.WaitPost(() =>MapMan.DeleteMap(MapId)); Server.Dispose(); } diff --git a/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs index c14761e4970..e9edf633c38 100644 --- a/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs +++ b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs @@ -28,9 +28,7 @@ public async Task TestSpawnTraversal() Vector2 gridMapPos = default; await server.WaitPost(() => { - mapId = mapMan.CreateMap(); - map = mapMan.GetMapEntityId(mapId); - + map = sEntMan.System().CreateMap(out mapId); var gridComp = mapMan.CreateGridEntity(mapId); grid = gridComp.Owner; mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1)); From f5a6e52c7fefa568296d52aab9b894e1a8bd9122 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 18 Apr 2024 14:16:40 +1000 Subject: [PATCH 101/130] Version: 219.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 6963955a275..63a7f5ee7fd 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 218.2.0 + 219.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a84601b1408..50865c0509f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 219.0.0 + +### Breaking changes + +* Move most IMapManager functionality to SharedMapSystem. + + ## 218.2.0 ### New features From c5aa7355064f8324471eca6487d427d1dd44f238 Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Thu, 18 Apr 2024 00:25:07 -0400 Subject: [PATCH 102/130] Adds rotation to Map Position spawns (#5047) * add angle to rotation * fixes inheritance * proxy * Fixes maprot * changes angle to default and uses set coords overload --- Robust.Shared/GameObjects/EntityManager.Spawn.cs | 5 +++-- Robust.Shared/GameObjects/EntityManager.cs | 5 +++-- Robust.Shared/GameObjects/EntitySystem.Proxy.cs | 6 ++++-- Robust.Shared/GameObjects/IEntityManager.Spawn.cs | 3 ++- Robust.Shared/GameObjects/IEntityManager.cs | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Spawn.cs b/Robust.Shared/GameObjects/EntityManager.Spawn.cs index a5ba665f653..20d2a74363b 100644 --- a/Robust.Shared/GameObjects/EntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/EntityManager.Spawn.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Robust.Shared.Containers; +using Robust.Shared.Maths; namespace Robust.Shared.GameObjects; @@ -90,9 +91,9 @@ public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = return entity; } - public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null) + public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!) { - var entity = CreateEntityUninitialized(protoName, coordinates, overrides); + var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation); InitializeAndStartEntity(entity, coordinates.MapId); return entity; } diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index dbd0f870485..144e5ea40a5 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -9,6 +9,7 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Map.Components; +using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Profiling; @@ -297,7 +298,7 @@ public virtual EntityUid CreateEntityUninitialized(string? prototypeName, Entity } /// - public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null) + public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!) { var newEntity = CreateEntity(prototypeName, out _, overrides); var transform = TransformQuery.GetComponent(newEntity); @@ -322,7 +323,7 @@ public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoo else { coords = new EntityCoordinates(mapEnt, coordinates.Position); - _xforms.SetCoordinates(newEntity, transform, coords, null, newParent: mapXform); + _xforms.SetCoordinates(newEntity, transform, coords, rotation, newParent: mapXform); } return newEntity; diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 662b2a8f5ea..9c4266dc03c 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -5,8 +5,10 @@ using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using TerraFX.Interop.Windows; namespace Robust.Shared.GameObjects; @@ -708,8 +710,8 @@ protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected EntityUid Spawn(string? prototype, MapCoordinates coordinates) - => EntityManager.Spawn(prototype, coordinates); + protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, Angle rotation = default!) + => EntityManager.Spawn(prototype, coordinates, rotation: rotation); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs index 2bcd44ce436..29f744366a4 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Spawn.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using Robust.Shared.Containers; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Prototypes; namespace Robust.Shared.GameObjects; @@ -32,7 +33,7 @@ EntityUid[] SpawnEntities(EntityCoordinates coordinates, List protoName /// /// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid. /// - EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null); + EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!); /// /// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to. diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs index ea9acf23414..3d1210277d4 100644 --- a/Robust.Shared/GameObjects/IEntityManager.cs +++ b/Robust.Shared/GameObjects/IEntityManager.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using Prometheus; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -76,7 +77,7 @@ public partial interface IEntityManager EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null); - EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null); + EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!); void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null); From ea5892449591f5091246733ee281cbf8fdf26985 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:36:29 +1000 Subject: [PATCH 103/130] Audio stuff (#5048) Better overlay debug and uses the adjusted distance for cutoff instead. --- Robust.Client/Audio/AudioOverlay.cs | 6 ++++-- Robust.Client/Audio/AudioSystem.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Robust.Client/Audio/AudioOverlay.cs b/Robust.Client/Audio/AudioOverlay.cs index 73ef2d24e2c..9cf129d0a21 100644 --- a/Robust.Client/Audio/AudioOverlay.cs +++ b/Robust.Client/Audio/AudioOverlay.cs @@ -74,11 +74,13 @@ protected internal override void Draw(in OverlayDrawArgs args) output.Clear(); output.AppendLine("Audio Source"); output.AppendLine("Runtime:"); + output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}"); output.AppendLine($"- Occlusion: {posOcclusion:0.0000}"); output.AppendLine("Params:"); + output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}"); output.AppendLine($"- Volume: {comp.Volume:0.0000}"); - output.AppendLine($"- Reference distance: {comp.ReferenceDistance}"); - output.AppendLine($"- Max distance: {comp.MaxDistance}"); + output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}"); + output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}"); var outputText = output.ToString().Trim(); var dimensions = screenHandle.GetDimensions(_font, outputText, 1f); var buffer = new Vector2(3f, 3f); diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index 1838d5655de..c52d99b134a 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -388,7 +388,7 @@ private void ProcessStream(EntityUid entity, AudioComponent component, Transform var distance = delta.Length(); // Out of range so just clip it for us. - if (distance > component.MaxDistance) + if (GetAudioDistance(distance) > component.MaxDistance) { // Still keeps the source playing, just with no volume. component.Gain = 0f; From ec37d1c137de35e1fc371adbb18ec9e616dc2bfc Mon Sep 17 00:00:00 2001 From: Vasilis Date: Thu, 18 Apr 2024 17:51:25 +0200 Subject: [PATCH 104/130] Auth is now required by default (#5050) --- Robust.Shared/CVars.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index b0988a27fe3..01ac866d4b8 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -851,7 +851,7 @@ protected CVars() /// See the documentation of the enum for values. /// public static readonly CVarDef AuthMode = - CVarDef.Create("auth.mode", (int) Network.AuthMode.Optional, CVar.SERVERONLY); + CVarDef.Create("auth.mode", (int) Network.AuthMode.Required, CVar.SERVERONLY); /// /// Allow unauthenticated localhost connections, even if the auth mode is set to required. From 9f913cd2d984627e052454fbcf03f126c6b6a06e Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:15:42 +1200 Subject: [PATCH 105/130] Fix RecursiveMapInit (#5052) * Fix RecursiveMapInit * re-use sawmill --- RELEASE-NOTES.md | 2 +- .../Systems/SharedMapSystem.MapInit.cs | 16 +++++----------- Robust.Shared/Map/MapManager.cs | 5 +---- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 50865c0509f..02a08255e17 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixes map initialisation not always initialising all entities on a map. ### Other diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs index f6122e0c1fa..e7b326e039c 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs @@ -8,8 +8,6 @@ namespace Robust.Shared.GameObjects; public abstract partial class SharedMapSystem { - private List _toInitialize = new(); - public bool IsInitialized(MapId mapId) { if (mapId == MapId.Nullspace) @@ -67,23 +65,19 @@ public void InitializeMap(Entity map, bool unpause = true) private void RecursiveMapInit(EntityUid entity) { - _toInitialize.Clear(); - _toInitialize.Add(entity); - - for (var i = 0; i < _toInitialize.Count; i++) + var toInitialize = new List {entity}; + for (var i = 0; i < toInitialize.Count; i++) { - var uid = _toInitialize[i]; - // _toInitialize might contain deleted entities. + var uid = toInitialize[i]; + // toInitialize might contain deleted entities. if(!_metaQuery.TryComp(uid, out var meta)) continue; if (meta.EntityLifeStage == EntityLifeStage.MapInitialized) continue; - _toInitialize.AddRange(Transform(uid)._children); + toInitialize.AddRange(Transform(uid)._children); EntityManager.RunMapInit(uid, meta); } - - _toInitialize.Clear(); } } diff --git a/Robust.Shared/Map/MapManager.cs b/Robust.Shared/Map/MapManager.cs index c8d01b26a7e..ac896902d26 100644 --- a/Robust.Shared/Map/MapManager.cs +++ b/Robust.Shared/Map/MapManager.cs @@ -19,7 +19,7 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber [Dependency] private readonly IConsoleHost _conhost = default!; - private ISawmill _sawmill = default!; + private ISawmill _sawmill => _mapSystem.Log; private SharedMapSystem _mapSystem = default!; private SharedPhysicsSystem _physics = default!; @@ -33,9 +33,6 @@ public void Initialize() { _gridTreeQuery = EntityManager.GetEntityQuery(); _gridQuery = EntityManager.GetEntityQuery(); - - _sawmill = Logger.GetSawmill("map"); - InitializeMapPausing(); } From 68e5b6924d8cdba5ce10e9591055c2f3878f2696 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:16:17 +1200 Subject: [PATCH 106/130] Add ComponentRegistry overrides to more entity spawn methods (#5051) --- .../GameObjects/EntityManager.Spawn.cs | 4 ++-- .../GameObjects/EntitySystem.Proxy.cs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Spawn.cs b/Robust.Shared/GameObjects/EntityManager.Spawn.cs index 20d2a74363b..ca96a731661 100644 --- a/Robust.Shared/GameObjects/EntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/EntityManager.Spawn.cs @@ -11,11 +11,11 @@ namespace Robust.Shared.GameObjects; public partial class EntityManager { - // This method will soon be marked as obsolete. + // This method will soon(TM) be marked as obsolete. public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null) => SpawnAttachedTo(protoName, coordinates, overrides); - // This method will soon be marked as obsolete. + // This method will soon(TM) be marked as obsolete. public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null) => Spawn(protoName, coordinates, overrides); diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 9c4266dc03c..c70bb05a362 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -701,32 +701,32 @@ protected void QueueDel(EntityUid? uid) #region Entity Spawning - // This method will be obsoleted soon. + // This method will be obsoleted soon(TM). [MethodImpl(MethodImplOptions.AggressiveInlining)] protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates) { return ((IEntityManager)EntityManager).SpawnEntity(prototype, coordinates); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, Angle rotation = default!) - => EntityManager.Spawn(prototype, coordinates, rotation: rotation); + protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default) + => EntityManager.Spawn(prototype, coordinates, overrides, rotation); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected EntityUid Spawn(string? prototype = null, ComponentRegistry? overrides = null, bool doMapInit = true) => EntityManager.Spawn(prototype, overrides, doMapInit); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates) - => EntityManager.SpawnAttachedTo(prototype, coordinates); + protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null) + => EntityManager.SpawnAttachedTo(prototype, coordinates, overrides); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates) - => EntityManager.SpawnAtPosition(prototype, coordinates); + protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null) + => EntityManager.SpawnAtPosition(prototype, coordinates, overrides); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] From 19f87dfbb3dc0992e49b612ffe034c684a6aa96b Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sat, 20 Apr 2024 16:52:47 -0400 Subject: [PATCH 107/130] Version: 219.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 63a7f5ee7fd..00b80ce39b0 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.0.0 + 219.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 02a08255e17..ba6f8c1b3ae 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixes map initialisation not always initialising all entities on a map. +*None yet* ### Other @@ -54,6 +54,21 @@ END TEMPLATE--> *None yet* +## 219.1.0 + +### New features + +* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation. + +### Bugfixes + +* Fixes map initialisation not always initialising all entities on a map. + +### Other + +* The default value of the `auth.mode` cvar has changed + + ## 219.0.0 ### Breaking changes From 68888c4370f6deb748cb1d7a04a9f0d85a375cc9 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:39:54 -0700 Subject: [PATCH 108/130] Make remaining IPrototypes partial (#5053) --- Robust.Client/Graphics/Shaders/ShaderPrototype.cs | 2 +- Robust.Client/UserInterface/RichText/FontPrototype.cs | 2 +- Robust.Client/UserInterface/Themes/UiTheme.cs | 4 +--- Robust.Shared/Audio/AudioMetadataPrototype.cs | 2 +- Robust.Shared/Audio/AudioPresetPrototype.cs | 2 +- Robust.Shared/Audio/SoundCollectionPrototype.cs | 4 ++-- Robust.Shared/Prototypes/EntityCategoryPrototype.cs | 4 ++-- Robust.Shared/Prototypes/TileAliasPrototype.cs | 2 +- Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs | 2 +- 9 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Robust.Client/Graphics/Shaders/ShaderPrototype.cs b/Robust.Client/Graphics/Shaders/ShaderPrototype.cs index 0aed10c6130..8c8f255ae55 100644 --- a/Robust.Client/Graphics/Shaders/ShaderPrototype.cs +++ b/Robust.Client/Graphics/Shaders/ShaderPrototype.cs @@ -17,7 +17,7 @@ namespace Robust.Client.Graphics { [Prototype("shader")] - public sealed class ShaderPrototype : IPrototype, ISerializationHooks + public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks { [ViewVariables] [IdDataField] diff --git a/Robust.Client/UserInterface/RichText/FontPrototype.cs b/Robust.Client/UserInterface/RichText/FontPrototype.cs index af058dbac62..4c549e2fd3d 100644 --- a/Robust.Client/UserInterface/RichText/FontPrototype.cs +++ b/Robust.Client/UserInterface/RichText/FontPrototype.cs @@ -5,7 +5,7 @@ namespace Robust.Client.UserInterface.RichText; [Prototype("font")] -public sealed class FontPrototype : IPrototype +public sealed partial class FontPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Robust.Client/UserInterface/Themes/UiTheme.cs b/Robust.Client/UserInterface/Themes/UiTheme.cs index f719c7ad4b3..50e16a1611c 100644 --- a/Robust.Client/UserInterface/Themes/UiTheme.cs +++ b/Robust.Client/UserInterface/Themes/UiTheme.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Frozen; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.ContentPack; -using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; @@ -18,7 +16,7 @@ namespace Robust.Client.UserInterface.Themes; [Prototype("uiTheme")] -public sealed class UITheme : IPrototype +public sealed partial class UITheme : IPrototype { private IResourceCache? _cache; private IUserInterfaceManager? _uiMan; diff --git a/Robust.Shared/Audio/AudioMetadataPrototype.cs b/Robust.Shared/Audio/AudioMetadataPrototype.cs index 49188c4d82e..65a67840fad 100644 --- a/Robust.Shared/Audio/AudioMetadataPrototype.cs +++ b/Robust.Shared/Audio/AudioMetadataPrototype.cs @@ -10,7 +10,7 @@ namespace Robust.Shared.Audio; /// to allow the server to know audio lengths without shipping the large audio files themselves. /// [Prototype(ProtoName)] -public sealed class AudioMetadataPrototype : IPrototype +public sealed partial class AudioMetadataPrototype : IPrototype { public const string ProtoName = "audioMetadata"; diff --git a/Robust.Shared/Audio/AudioPresetPrototype.cs b/Robust.Shared/Audio/AudioPresetPrototype.cs index 9e5fe916d3c..64cee87d631 100644 --- a/Robust.Shared/Audio/AudioPresetPrototype.cs +++ b/Robust.Shared/Audio/AudioPresetPrototype.cs @@ -9,7 +9,7 @@ namespace Robust.Shared.Audio; /// This can be used by to apply an audio preset. /// [Prototype("audioPreset")] -public sealed class AudioPresetPrototype : IPrototype +public sealed partial class AudioPresetPrototype : IPrototype { [IdDataField] public string ID { get; } = default!; diff --git a/Robust.Shared/Audio/SoundCollectionPrototype.cs b/Robust.Shared/Audio/SoundCollectionPrototype.cs index 574dc56366c..350bfb85dcb 100644 --- a/Robust.Shared/Audio/SoundCollectionPrototype.cs +++ b/Robust.Shared/Audio/SoundCollectionPrototype.cs @@ -1,13 +1,13 @@ +using System.Collections.Generic; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using System.Collections.Generic; namespace Robust.Shared.Audio; [Prototype("soundCollection")] -public sealed class SoundCollectionPrototype : IPrototype +public sealed partial class SoundCollectionPrototype : IPrototype { [ViewVariables] [IdDataField] diff --git a/Robust.Shared/Prototypes/EntityCategoryPrototype.cs b/Robust.Shared/Prototypes/EntityCategoryPrototype.cs index e907e8b7340..3f2331fa5fe 100644 --- a/Robust.Shared/Prototypes/EntityCategoryPrototype.cs +++ b/Robust.Shared/Prototypes/EntityCategoryPrototype.cs @@ -6,7 +6,7 @@ namespace Robust.Shared.Prototypes; /// Prototype that represents game entities. /// [Prototype("entityCategory")] -public sealed class EntityCategoryPrototype : IPrototype +public sealed partial class EntityCategoryPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; @@ -22,4 +22,4 @@ public sealed class EntityCategoryPrototype : IPrototype /// [DataField("description")] public string? Description { get; private set; } -} \ No newline at end of file +} diff --git a/Robust.Shared/Prototypes/TileAliasPrototype.cs b/Robust.Shared/Prototypes/TileAliasPrototype.cs index f887af59dae..de75ba09f4f 100644 --- a/Robust.Shared/Prototypes/TileAliasPrototype.cs +++ b/Robust.Shared/Prototypes/TileAliasPrototype.cs @@ -8,7 +8,7 @@ namespace Robust.Shared.Prototypes; /// Tile alias prototypes, unlike tile prototypes, are implemented here, as they're really just fed to TileDefinitionManager. /// [Prototype("tileAlias")] -public sealed class TileAliasPrototype : IPrototype +public sealed partial class TileAliasPrototype : IPrototype { /// /// The target tile ID to alias to. diff --git a/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs b/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs index 43c0085c10b..78d2574f152 100644 --- a/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs +++ b/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs @@ -142,7 +142,7 @@ string GenerateCircleTestPrototype(string id, string parent) } [Prototype("circle")] - private sealed class CircleTestPrototype : IPrototype, IInheritingPrototype + private sealed partial class CircleTestPrototype : IPrototype, IInheritingPrototype { [IdDataField()] public string ID { get; private set; } = default!; From 15f94bd094bc51f0dc3c865492ee4c827ce40a82 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:48:39 +1200 Subject: [PATCH 109/130] Fix mapinit persistence when overwriting existing maps (#5057) * Fix mapinit persistence when overwriting existing maps * Hours wasted chasing fucking chickens in a crate --- .../GameObjects/EntitySystems/MapLoaderSystem.cs | 15 +++++++-------- Robust.Server/Maps/MapLoadOptions.cs | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs index f8d11219917..436efb76651 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs @@ -49,7 +49,6 @@ public sealed class MapLoaderSystem : EntitySystem private ISawmill _logLoader = default!; private ISawmill _logWriter = default!; - private static readonly MapLoadOptions DefaultLoadOptions = new(); private const int MapFormatVersion = 6; private const int BackwardsVersion = 2; @@ -132,7 +131,7 @@ public void Load(MapId mapId, string path, MapLoadOptions? options = null) public bool TryLoad(MapId mapId, string path, [NotNullWhen(true)] out IReadOnlyList? rootUids, MapLoadOptions? options = null) { - options ??= DefaultLoadOptions; + options ??= new(); var resPath = new ResPath(path).ToRootedPath(); @@ -663,6 +662,7 @@ private void SwapRootNode(MapData data) // If map exists swap out if (_mapSystem.TryGetMap(data.TargetMap, out var existing)) { + data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap); data.MapIsPaused = _mapSystem.IsPaused(existing.Value); // Map exists but we also have a map file with stuff on it soooo swap out the old map. if (data.Options.LoadMap) @@ -887,8 +887,7 @@ private void StartupEntity(EntityUid uid, MetaDataComponent metadata, MapData da { EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized); } - // TODO MAP LOAD cache this - else if (_mapManager.IsMapInitialized(data.TargetMap)) + else if (data.Options.DoMapInit) { _serverEntityManager.RunMapInit(uid, metadata); } @@ -1090,17 +1089,17 @@ private void PopulateEntityList(EntityUid uid, List entities, Diction } } - private bool IsSaveable(EntityUid uid, EntityQuery metaQuery, EntityQuery transformQuery) + private bool IsSaveable(EntityUid uid) { // Don't serialize things parented to un savable things. // For example clothes inside a person. while (uid.IsValid()) { - var meta = metaQuery.GetComponent(uid); + var meta = MetaData(uid); if (meta.EntityDeleted || meta.EntityPrototype?.MapSavable == false) break; - uid = transformQuery.GetComponent(uid).ParentUid; + uid = Transform(uid).ParentUid; } // If we manage to get up to the map (root node) then it's saveable. @@ -1115,7 +1114,7 @@ private void RecursivePopulate(EntityUid uid, EntityQuery transformQuery, EntityQuery saveCompQuery) { - if (!IsSaveable(uid, metaQuery, transformQuery)) + if (!IsSaveable(uid)) return; entities.Add(uid); diff --git a/Robust.Server/Maps/MapLoadOptions.cs b/Robust.Server/Maps/MapLoadOptions.cs index d04dde8130f..85f9034b176 100644 --- a/Robust.Server/Maps/MapLoadOptions.cs +++ b/Robust.Server/Maps/MapLoadOptions.cs @@ -53,5 +53,7 @@ public Angle Rotation /// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity. /// public bool LoadMap { get; set; } = true; + + public bool DoMapInit = false; } } From 8e8470ac7eca4b63df4f146d3dcdc2b0f01d11cb Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sun, 21 Apr 2024 02:50:34 -0400 Subject: [PATCH 110/130] Version: 219.1.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 00b80ce39b0..4f1715099e3 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.1.0 + 219.1.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ba6f8c1b3ae..7099bb4dee7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 219.1.1 + +### Bugfixes + +* Fix map-loader not map-initialising maps when overwriting a post-init map. + + ## 219.1.0 ### New features From 0ab59d70b15bbbdc8de856513136df6aad6125dc Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 21 Apr 2024 20:57:20 +1200 Subject: [PATCH 111/130] More mapinit fixes (#5058) --- Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs index 436efb76651..b1467071d68 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs @@ -722,6 +722,7 @@ private void SwapRootNode(MapData data) mapNode = _mapSystem.CreateMap(data.TargetMap, false); } + data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap); data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value); // If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map. From 73da147b8811c8d032e0f119ef507a1c11ff4073 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sun, 21 Apr 2024 04:58:44 -0400 Subject: [PATCH 112/130] Version: 219.1.2 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 4f1715099e3..468af0180d6 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.1.1 + 219.1.2 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7099bb4dee7..f5f2664b6b7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 219.1.2 + +### Bugfixes + +* Fix map-loader not map-initialising grids when loading into a post-init map. + + ## 219.1.1 ### Bugfixes From 1031ae4cc5df4346891891da8505a12f7588cfa1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:25:40 +1200 Subject: [PATCH 113/130] Fix mapping not pausing maps (#5063) --- RELEASE-NOTES.md | 2 +- Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f5f2664b6b7..555ee0fb301 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map. ### Other diff --git a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs index b1467071d68..d39f90d4cd6 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapLoaderSystem.cs @@ -706,6 +706,7 @@ private void SwapRootNode(MapData data) } else { + data.MapIsPaused = !data.MapIsPostInit; mapComp.MapId = data.TargetMap; DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing); EnsureComp(rootNode); From 4e87d93009748778c27ae835b16297596e5b1563 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 23 Apr 2024 00:26:17 -0400 Subject: [PATCH 114/130] Version: 219.1.3 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 468af0180d6..c352697c92e 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.1.2 + 219.1.3 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 555ee0fb301..25aa35ba558 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map. +*None yet* ### Other @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 219.1.3 + +### Bugfixes + +* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map. + + ## 219.1.2 ### Bugfixes From 9e3e1cc929c592d69999d996aabe04433ce2368c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:36:40 +1000 Subject: [PATCH 115/130] Optimise physics networking a lot (#5064) Avoids unnecessarily dirtying every single tick when a mob is moving. Also avoids the getcomponent every time which is nice. --- .../Systems/SharedPhysicsSystem.Components.cs | 25 +++++++++++-------- .../Systems/SharedPhysicsSystem.Island.cs | 16 ++++++------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 675e8d38fa9..b9537b4d03a 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -313,55 +313,60 @@ public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, Phys Dirty(uid, body); } - public void SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null) + public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null) { if (!Resolve(uid, ref body)) - return; + return false; if (body.BodyType == BodyType.Static) - return; + return false; if (value * value > 0.0f) { if (!WakeBody(uid, manager: manager, body: body)) - return; + return false; } // CloseToPercent tolerance needs to be small enough such that an angular velocity just above // sleep-tolerance can damp down to sleeping. if (MathHelper.CloseToPercent(body.AngularVelocity, value, 0.00001f)) - return; + return false; body.AngularVelocity = value; if (dirty) Dirty(uid, body); + + return true; } /// /// Attempts to set the body to collidable, wake it, then move it. /// - public void SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null) + public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null) { if (!Resolve(uid, ref body)) - return; + return false; - if (body.BodyType == BodyType.Static) return; + if (body.BodyType == BodyType.Static) + return false; if (wakeBody && Vector2.Dot(velocity, velocity) > 0.0f) { if (!WakeBody(uid, manager: manager, body: body)) - return; + return false; } if (body.LinearVelocity.EqualsApprox(velocity, 0.0000001f)) - return; + return false; body.LinearVelocity = velocity; if (dirty) Dirty(uid, body); + + return true; } public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs index 0e4fb5a7288..7ad4e121df6 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs @@ -660,13 +660,11 @@ private void SolveIslands(EntityUid uid, PhysicsMapComponent component, List(); - for (var i = 0; i < actualIslands.Length; i++) { var island = actualIslands[i]; - UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery, metaQuery); + UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery); SleepBodies(in island, sleepStatus); } @@ -1001,8 +999,7 @@ private void UpdateBodies( float[] angles, Vector2[] linearVelocities, float[] angularVelocities, - EntityQuery xformQuery, - EntityQuery metaQuery) + EntityQuery xformQuery) { foreach (var (joint, error) in island.BrokenJoints) { @@ -1035,21 +1032,22 @@ private void UpdateBodies( } var linVelocity = linearVelocities[offset + i]; + var physicsDirtied = false; if (!float.IsNaN(linVelocity.X) && !float.IsNaN(linVelocity.Y)) { - SetLinearVelocity(uid, linVelocity, false, body: body); + physicsDirtied |= SetLinearVelocity(uid, linVelocity, false, body: body); } var angVelocity = angularVelocities[offset + i]; if (!float.IsNaN(angVelocity)) { - SetAngularVelocity(uid, angVelocity, false, body: body); + physicsDirtied |= SetAngularVelocity(uid, angVelocity, false, body: body); } - // TODO: Should check if the values update. - Dirty(uid, body, metaQuery.GetComponent(uid)); + if (physicsDirtied) + Dirty(uid, body); } } From 6566a7658a30f13b4515611ef75937767aa69138 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 23 Apr 2024 20:34:29 +0200 Subject: [PATCH 116/130] Fix DebugCoordsPanel freezing when hovering a UI control. It would bail out of the entire update logic if you aren't hovering over a map position, which isn't great when the control displays far more than in-simulation mouse position info. --- Robust.Client/BaseClient.cs | 16 ++++ .../DebugMonitorControls/DebugCoordsPanel.cs | 86 +++++++++++-------- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/Robust.Client/BaseClient.cs b/Robust.Client/BaseClient.cs index d96537ba08f..146dd1cc212 100644 --- a/Robust.Client/BaseClient.cs +++ b/Robust.Client/BaseClient.cs @@ -285,6 +285,7 @@ void IPostInjectInit.PostInject() /// /// Enumeration of the run levels of the BaseClient. /// + /// public enum ClientRunLevel : byte { Error = 0, @@ -315,6 +316,21 @@ public enum ClientRunLevel : byte SinglePlayerGame, } + /// + /// Helper functions for working with . + /// + public static class ClientRunLevelExt + { + /// + /// Check if a is + /// or . + /// + public static bool IsInGameLike(this ClientRunLevel runLevel) + { + return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame; + } + } + /// /// Event arguments for when something changed with the player. /// diff --git a/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugCoordsPanel.cs b/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugCoordsPanel.cs index a8415381918..3c6b728ffd8 100644 --- a/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugCoordsPanel.cs +++ b/Robust.Client/UserInterface/CustomControls/DebugMonitorControls/DebugCoordsPanel.cs @@ -20,6 +20,7 @@ internal sealed class DebugCoordsPanel : PanelContainer [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IClyde _displayManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IBaseClient _baseClient = default!; private readonly StringBuilder _textBuilder = new(); private readonly char[] _textBuffer = new char[1024]; @@ -58,30 +59,36 @@ protected override void FrameUpdate(FrameEventArgs args) _textBuilder.Clear(); + var isInGame = _baseClient.RunLevel.IsInGameLike(); var mouseScreenPos = _inputManager.MouseScreenPosition; var screenSize = _displayManager.ScreenSize; var screenScale = _displayManager.MainWindow.ContentScale; - EntityCoordinates mouseGridPos; - TileRef tile; + EntityCoordinates mouseGridPos = default; + TileRef tile = default; + MapCoordinates mouseWorldMap = default; - var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos); - if (mouseWorldMap == MapCoordinates.Nullspace) - return; - - var mapSystem = _entityManager.System(); - var xformSystem = _entityManager.System(); - - if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid)) - { - mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap); - tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos); - } - else + if (isInGame) { - mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId), - mouseWorldMap.Position); - tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty); + mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos); + if (mouseWorldMap != MapCoordinates.Nullspace) + { + var mapSystem = _entityManager.System(); + var xformSystem = _entityManager.System(); + + if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid)) + { + mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap); + tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos); + } + else + { + mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId), + mouseWorldMap.Position); + tile = new TileRef(EntityUid.Invalid, + mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty); + } + } } var controlHovered = UserInterfaceManager.CurrentlyHovered; @@ -95,32 +102,37 @@ protected override void FrameUpdate(FrameEventArgs args) {tile} GUI: {controlHovered}"); - _textBuilder.AppendLine("\nAttached NetEntity:"); - var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid; - - if (controlledEntity == EntityUid.Invalid) - { - _textBuilder.AppendLine("No attached netentity."); - } - else + if (isInGame) { - var entityTransform = _entityManager.GetComponent(controlledEntity); - var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform); - var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position); - - var playerCoordinates = entityTransform.Coordinates; - var playerRotation = xformSystem.GetWorldRotation(entityTransform); - var gridRotation = entityTransform.GridUid != null - ? xformSystem.GetWorldRotation(entityTransform.GridUid.Value) - : Angle.Zero; - - _textBuilder.Append($@" Screen: {playerScreen} + var xformSystem = _entityManager.System(); + + _textBuilder.AppendLine("\nAttached NetEntity:"); + var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid; + + if (controlledEntity == EntityUid.Invalid) + { + _textBuilder.AppendLine("No attached netentity."); + } + else + { + var entityTransform = _entityManager.GetComponent(controlledEntity); + var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform); + var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position); + + var playerCoordinates = entityTransform.Coordinates; + var playerRotation = xformSystem.GetWorldRotation(entityTransform); + var gridRotation = entityTransform.GridUid != null + ? xformSystem.GetWorldRotation(entityTransform.GridUid.Value) + : Angle.Zero; + + _textBuilder.Append($@" Screen: {playerScreen} {playerWorldOffset} {_entityManager.GetNetCoordinates(playerCoordinates)} Rotation: {playerRotation.Degrees:F2}° NEntId: {_entityManager.GetNetEntity(controlledEntity)} Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)} Grid Rotation: {gridRotation.Degrees:F2}°"); + } } _contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer); From b624f5b70f2b865a7d70164f0ead531fe6aae337 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 24 Apr 2024 08:55:58 -0400 Subject: [PATCH 117/130] Add SetMapCoordinates (#5065) * add set map coordinates function * mmmmmmm yes * Update Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../SharedTransformSystem.Component.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 29a69391852..7ef2ca10cd6 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -870,6 +870,28 @@ public MapCoordinates GetMapCoordinates(Entity entity) return GetMapCoordinates(entity.Comp); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMapCoordinates(EntityUid entity, MapCoordinates coordinates) + { + var xform = XformQuery.GetComponent(entity); + SetMapCoordinates((entity, xform), coordinates); + } + + public void SetMapCoordinates(Entity entity, MapCoordinates coordinates) + { + var mapUid = _map.GetMap(coordinates.MapId); + if (!_gridQuery.HasComponent(entity) && + _mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _)) + { + var invWorldMatrix = GetInvWorldMatrix(targetGrid); + SetCoordinates(entity, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(coordinates.Position))); + } + else + { + SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position)); + } + } + [Pure] public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid) { From 4c3c74865ccce47a67e900a0b029c1a32febfd3f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 25 Apr 2024 02:27:02 +1200 Subject: [PATCH 118/130] Fix yaml linter & improve validation of static fields (#5056) --- Robust.Shared/Prototypes/IPrototypeManager.cs | 11 +- .../PrototypeManager.ValidateFields.cs | 191 ++++++++++-------- .../PrototypeManager.YamlValidate.cs | 54 +++-- .../Prototypes/YamlValidationContext.cs | 62 ++++++ .../ValidatePrototypeIdAttribute.cs | 5 +- .../Definition/DataDefinition.Emitters.cs | 2 +- .../RequiredFieldNotMappedException.cs | 2 +- .../Custom/Prototype/PrototypeIdSerializer.cs | 2 +- 8 files changed, 220 insertions(+), 109 deletions(-) create mode 100644 Robust.Shared/Prototypes/YamlValidationContext.cs diff --git a/Robust.Shared/Prototypes/IPrototypeManager.cs b/Robust.Shared/Prototypes/IPrototypeManager.cs index e070d978495..68f1d0b2be3 100644 --- a/Robust.Shared/Prototypes/IPrototypeManager.cs +++ b/Robust.Shared/Prototypes/IPrototypeManager.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using Robust.Shared.Random; +using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown; @@ -272,7 +273,8 @@ Dictionary> ValidateDirectory(ResPath path, out Dictionary> prototypes); /// - /// This method uses reflection to validate that prototype id fields correspond to valid prototypes. + /// This method uses reflection to validate that all static prototype id fields correspond to valid prototypes. + /// This will validate all known to /// /// /// This will validate any field that has either a attribute, or a @@ -280,7 +282,12 @@ Dictionary> ValidateDirectory(ResPath path, /// /// A collection prototypes to use for validation. Any prototype not in this collection /// will be considered invalid. - List ValidateFields(Dictionary> prototypes); + List ValidateStaticFields(Dictionary> prototypes); + + /// + /// This is a variant of that only validates a single type. + /// + List ValidateStaticFields(Type type, Dictionary> prototypes); /// /// This method will serialize all loaded prototypes into yaml and then validate them. This can be used to ensure diff --git a/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs b/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs index cd10bbe0d8b..682b0b1f0ae 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Utility; using BindingFlags = System.Reflection.BindingFlags; @@ -13,35 +12,41 @@ namespace Robust.Shared.Prototypes; public partial class PrototypeManager { /// - public List ValidateFields(Dictionary> prototypes) + public List ValidateStaticFields(Dictionary> prototypes) { var errors = new List(); foreach (var type in _reflectionManager.FindAllTypes()) { // TODO validate public static fields on abstract classes that have no implementations? if (!type.IsAbstract) - ValidateType(type, errors, prototypes); + ValidateStaticFieldsInternal(type, errors, prototypes); } return errors; } + /// + public List ValidateStaticFields(Type type, Dictionary> prototypes) + { + var errors = new List(); + ValidateStaticFieldsInternal(type, errors, prototypes); + return errors; + } + /// - /// Validate all fields defined on this type and all base types. + /// Validate all static fields defined on this type and all base types. /// - private void ValidateType(Type type, List errors, Dictionary> prototypes) + private void ValidateStaticFieldsInternal(Type type, List errors, Dictionary> prototypes) { - object? instance = null; - Type? baseType = type; - - var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | - BindingFlags.DeclaredOnly; + var baseType = type; + var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly; while (baseType != null) { foreach (var field in baseType.GetFields(flags)) { - ValidateField(field, type, ref instance, errors, prototypes); + DebugTools.Assert(field.IsStatic); + ValidateStaticField(field, type, errors, prototypes); } // We need to get the fields on the base type separately in order to get the private fields @@ -49,92 +54,110 @@ private void ValidateType(Type type, List errors, Dictionary errors, Dictionary> prototypes) { - // Is this even a prototype id related field? - if (!TryGetFieldPrototype(field, out var proto, out var canBeNull, out var canBeEmpty)) - return; + DebugTools.Assert(field.IsStatic); + DebugTools.Assert(!field.HasCustomAttribute(), "Datafields should not be static"); - if (!TryGetFieldValue(field, type, ref instance, errors, out var value)) + // Is this even a prototype id related field? + if (!TryGetFieldPrototype(field, out var proto)) return; - var id = value?.ToString(); - - if (id == null) + if (!prototypes.TryGetValue(proto, out var validIds)) { - if (!canBeNull) - errors.Add($"Prototype id field failed validation. Fields should not be null. Field: {field.Name} in {type.FullName}"); + errors.Add($"Prototype id field failed validation. Unknown prototype kind {proto.Name}. Field: {field.Name} in {type.FullName}"); return; } - if (string.IsNullOrWhiteSpace(id)) + if (!TryGetIds(field, proto, out var ids)) { - if (!canBeEmpty) - errors.Add($"Prototype id field failed validation. Non-optional non-nullable data-fields must have a default value. Field: {field.Name} in {type.FullName}"); + TryGetIds(field, proto, out _); + DebugTools.Assert($"Failed to get ids, despite resolving the field into a prototype kind?"); return; } - if (!prototypes.TryGetValue(proto, out var ids)) + foreach (var id in ids) { - errors.Add($"Prototype id field failed validation. Unknown prototype kind. Field: {field.Name} in {type.FullName}"); - return; - } - - if (!ids.Contains(id)) - { - errors.Add($"Prototype id field failed validation. Unknown prototype: {id}. Field: {field.Name} in {type.FullName}"); + if (!validIds.Contains(id)) + errors.Add($"Prototype id field failed validation. Unknown prototype: {id} of type {proto.Name}. Field: {field.Name} in {type.FullName}"); } } /// - /// Get the value of some field. If this is not a static field, this will create instance of the object in order to - /// validate default field values. + /// Extract prototype ids from a string, IEnumerable{string}, EntProtoId, IEnumerable{EntProtoId}, ProtoId{T}, or IEnumerable{ProtoId{T}} field. /// - private bool TryGetFieldValue(FieldInfo field, Type type, ref object? instance, List errors, out object? value) + private bool TryGetIds(FieldInfo field, Type proto, [NotNullWhen(true)] out string[]? ids) { - value = null; + ids = null; + var value = field.GetValue(null); + if (value == null) + return false; - if (field.IsStatic || instance != null) + if (value is string str) { - value = field.GetValue(instance); + ids = [str]; return true; } - var constructor = type.GetConstructor( - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, - Type.EmptyTypes); + if (value is IEnumerable strEnum) + { + ids = strEnum.ToArray(); + return true; + } - // TODO handle parameterless record constructors. - // Figure out how ISerializationManager does it, or just re-use that code somehow. - // In the meantime, record data fields need an explicit parameterless ctor. + if (value is EntProtoId protoId) + { + ids = [protoId]; + return true; + } - if (constructor == null) + if (value is IEnumerable protoIdEnum) { - errors.Add($"Prototype id field failed validation. Could not create instance to validate default value. Field: {field.Name} in {type.FullName}"); - return false; + ids = protoIdEnum.Select(x=> x.Id).ToArray(); + return true; + } + + if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>)) + { + ids = [value.ToString()!]; + return true; } - instance = constructor.Invoke(Array.Empty()); - value = field.GetValue(instance); + foreach (var iface in field.FieldType.GetInterfaces()) + { + if (!iface.IsGenericType) + continue; + + if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>)) + continue; + + var enumType = iface.GetGenericArguments().Single(); + if (!enumType.IsGenericType) + continue; + + if (enumType.GetGenericTypeDefinition() != typeof(ProtoId<>)) + continue; + + ids = GetIdsMethod.MakeGenericMethod(proto).Invoke(null, [value]) as string[]; + return ids != null; + } - return true; + return false; } - private bool TryGetFieldPrototype( - FieldInfo field, - [NotNullWhen(true)] out Type? proto, - out bool canBeNull, - out bool canBeEmpty) + private static MethodInfo GetIdsMethod = typeof(PrototypeManager).GetMethod(nameof(GetIds), BindingFlags.NonPublic | BindingFlags.Static)!; + private static string[] GetIds(IEnumerable> enumerable) where T : class, IPrototype { - proto = null; - canBeNull = false; - canBeEmpty = false; + return enumerable.Select(x => x.Id).ToArray(); + } + private bool TryGetFieldPrototype(FieldInfo field, [NotNullWhen(true)] out Type? proto) + { + // Validate anything with the attribute var attrib = field.GetCustomAttribute(typeof(ValidatePrototypeIdAttribute<>), false); if (attrib != null) { @@ -142,46 +165,40 @@ private bool TryGetFieldPrototype( return true; } - if (!field.TryGetCustomAttribute(out DataFieldAttribute? dataField)) - return false; - - var fieldType = field.FieldType; - canBeEmpty = dataField.Required; - DebugTools.Assert(!field.IsStatic); + if (TryGetPrototypeFromType(field.FieldType, out proto)) + return true; - // Resolve nullable structs - if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) + // Allow validating arrays or lists. + foreach (var iface in field.FieldType.GetInterfaces().Where(x => x.IsGenericType)) { - fieldType = fieldType.GetGenericArguments().Single(); - canBeNull = true; + if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>)) + continue; + + var enumType = iface.GetGenericArguments().Single(); + if (TryGetPrototypeFromType(enumType, out proto)) + return true; } - if (fieldType == typeof(EntProtoId)) + proto = null; + return false; + } + + private bool TryGetPrototypeFromType(Type type, [NotNullWhen(true)] out Type? proto) + { + if (type == typeof(EntProtoId)) { proto = typeof(EntityPrototype); return true; } - if (fieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>)) + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>)) { - proto = field.FieldType.GetGenericArguments().Single(); + proto = type.GetGenericArguments().Single(); + DebugTools.Assert(proto != typeof(EntityPrototype), "Use EntProtoId instead of ProtoId"); return true; } - // As far as I know there is no way to check for the nullability of a string field, so we will assume that null - // values imply that the field itself is properly marked as nullable. - canBeNull = true; - - if (dataField.CustomTypeSerializer == null) - return false; - - if (!dataField.CustomTypeSerializer.IsGenericType) - return false; - - if (dataField.CustomTypeSerializer.GetGenericTypeDefinition() != typeof(PrototypeIdSerializer<>)) - return false; - - proto = dataField.CustomTypeSerializer.GetGenericArguments().First(); - return true; + proto = null; + return false; } } diff --git a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs index 8ddc9fd6f0c..65f6411755b 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs @@ -53,7 +53,7 @@ public Dictionary> ValidateDirectory(ResPath path, var mapping = node.ToDataNodeCast(); var id = mapping.Get("id").Value; - var data = new PrototypeValidationData(mapping, resourcePath.ToString()); + var data = new PrototypeValidationData(id, mapping, resourcePath.ToString()); mapping.Remove("type"); if (prototypes.GetOrNew(type).TryAdd(id, data)) @@ -65,10 +65,14 @@ public Dictionary> ValidateDirectory(ResPath path, } } + var ctx = new YamlValidationContext(); + var errors = new List(); foreach (var (type, instances) in prototypes) { - foreach (var data in instances.Values) + var defaultErrorOccurred = false; + foreach (var (id, data) in instances) { + errors.Clear(); EnsurePushed(data, instances, type); if (data.Mapping.TryGet("abstract", out ValueDataNode? abstractNode) && bool.Parse(abstractNode.Value)) @@ -76,9 +80,25 @@ public Dictionary> ValidateDirectory(ResPath path, continue; } - var result = _serializationManager.ValidateNode(type, data.Mapping).GetErrors().ToHashSet(); - if (result.Count > 0) - dict.GetOrNew(data.File).UnionWith(result); + // Validate yaml directly + errors.AddRange(_serializationManager.ValidateNode(type, data.Mapping).GetErrors()); + if (errors.Count > 0) + dict.GetOrNew(data.File).UnionWith(errors); + + // Create instance & re-serialize it, to validate the default values of data-fields. We still validate + // the yaml directly just in case reading & writing the fields somehow modifies their values. + try + { + var instance = _serializationManager.Read(type, data.Mapping, ctx); + var mapping = _serializationManager.WriteValue(type, instance, alwaysWrite: true, ctx); + errors.AddRange(_serializationManager.ValidateNode(type, mapping, ctx).GetErrors()); + if (errors.Count > 0) + dict.GetOrNew(data.File).UnionWith(errors); + } + catch (Exception ex) + { + errors.Add(new ErrorNode(new ValueDataNode(), $"Caught Exception while validating {type} prototype {id}. Exception: {ex}")); + } } } @@ -152,12 +172,17 @@ private HashSet ValidateProto(Type type, IPrototype instance, ISerial private sealed class PrototypeValidationData { + public readonly string Id; public MappingDataNode Mapping; public readonly string File; public bool Pushed; - public PrototypeValidationData(MappingDataNode mapping, string file) + public string[]? Parents; + public MappingDataNode[]? ParentMappings; + + public PrototypeValidationData(string id, MappingDataNode mapping, string file) { + Id = id; File = file; Mapping = mapping; } @@ -176,23 +201,22 @@ private void EnsurePushed( if (!data.Mapping.TryGet(ParentDataFieldAttribute.Name, out var parentNode)) return; - var parents = _serializationManager.Read(parentNode, notNullableOverride: true); - var parentNodes = new MappingDataNode[parents.Length]; + DebugTools.AssertNull(data.Parents); + DebugTools.AssertNull(data.ParentMappings); + data.Parents = _serializationManager.Read(parentNode, notNullableOverride: true); + data.ParentMappings = new MappingDataNode[data.Parents.Length]; - foreach (var parentId in parents) + var i = 0; + foreach (var parentId in data.Parents) { var parent = prototypes[parentId]; EnsurePushed(parent, prototypes, type); - - for (var i = 0; i < parents.Length; i++) - { - parentNodes[i] = parent.Mapping; - } + data.ParentMappings[i++] = parent.Mapping; } data.Mapping = _serializationManager.PushCompositionWithGenericNode( type, - parentNodes, + data.ParentMappings, data.Mapping); } } diff --git a/Robust.Shared/Prototypes/YamlValidationContext.cs b/Robust.Shared/Prototypes/YamlValidationContext.cs new file mode 100644 index 00000000000..1838f9e8807 --- /dev/null +++ b/Robust.Shared/Prototypes/YamlValidationContext.cs @@ -0,0 +1,62 @@ +using System.Globalization; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Robust.Shared.Prototypes; + +internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer +{ + public SerializationManager.SerializerProvider SerializerProvider { get; } = new(); + public bool WritingReadingPrototypes => true; + + public YamlValidationContext() + { + SerializerProvider.RegisterSerializer(this); + } + + ValidationNode ITypeValidator.Validate(ISerializationManager serializationManager, + ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context) + { + if (node.Value == "null" || node.Value == "invalid") + return new ValidatedValueNode(node); + + return new ErrorNode(node, "Prototypes should not contain EntityUids", true); + } + + public DataNode Write(ISerializationManager serializationManager, EntityUid value, + IDependencyCollection dependencies, bool alwaysWrite = false, + ISerializationContext? context = null) + { + if (!value.Valid) + return new ValueDataNode("invalid"); + + return new ValueDataNode(value.Id.ToString(CultureInfo.InvariantCulture)); + } + + EntityUid ITypeReader.Read(ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context, ISerializationManager.InstantiationDelegate? _) + { + if (node.Value == "invalid") + return EntityUid.Invalid; + + return EntityUid.Parse(node.Value); + } + + [MustUseReturnValue] + public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target, + bool skipHook, + ISerializationContext? context = null) + { + return new((int)source); + } +} diff --git a/Robust.Shared/Serialization/Manager/Attributes/ValidatePrototypeIdAttribute.cs b/Robust.Shared/Serialization/Manager/Attributes/ValidatePrototypeIdAttribute.cs index cff51e6ee87..de1ac428049 100644 --- a/Robust.Shared/Serialization/Manager/Attributes/ValidatePrototypeIdAttribute.cs +++ b/Robust.Shared/Serialization/Manager/Attributes/ValidatePrototypeIdAttribute.cs @@ -4,8 +4,9 @@ namespace Robust.Shared.Serialization.Manager.Attributes; /// -/// This attribute should be used on string fields to validate that they correspond to a valid YAML prototype id. -/// If the field needs to be have a default value. +/// This attribute should be used on static string or string collection fields to validate that they correspond to +/// valid YAML prototype ids. This attribute is not required for static and +/// fields, as they automatically get validated. /// [AttributeUsage(AttributeTargets.Field)] public sealed class ValidatePrototypeIdAttribute : Attribute where T : IPrototype diff --git a/Robust.Shared/Serialization/Manager/Definition/DataDefinition.Emitters.cs b/Robust.Shared/Serialization/Manager/Definition/DataDefinition.Emitters.cs index f273e901b68..4d506bcb3cb 100644 --- a/Robust.Shared/Serialization/Manager/Definition/DataDefinition.Emitters.cs +++ b/Robust.Shared/Serialization/Manager/Definition/DataDefinition.Emitters.cs @@ -143,7 +143,7 @@ private PopulateDelegateSignature EmitPopulateDelegate(SerializationManager mana nodeVariable), call, dfa.Required - ? ExpressionUtils.ThrowExpression(fieldDefinition.FieldType, tagConst) + ? ExpressionUtils.ThrowExpression(fieldDefinition.FieldType, tagConst, typeof(T)) : AssignIfNotDefaultExpression(i, targetParam, Expression.Constant(DefaultValues[i], fieldDefinition.FieldType)) ))); } diff --git a/Robust.Shared/Serialization/Manager/Exceptions/RequiredFieldNotMappedException.cs b/Robust.Shared/Serialization/Manager/Exceptions/RequiredFieldNotMappedException.cs index a427a6e26ba..cd1e706a827 100644 --- a/Robust.Shared/Serialization/Manager/Exceptions/RequiredFieldNotMappedException.cs +++ b/Robust.Shared/Serialization/Manager/Exceptions/RequiredFieldNotMappedException.cs @@ -4,7 +4,7 @@ namespace Robust.Shared.Serialization.Manager.Exceptions; public sealed class RequiredFieldNotMappedException : Exception { - public RequiredFieldNotMappedException(Type type, string field) : base($"Required field {field} of type {type} wasn't mapped.") + public RequiredFieldNotMappedException(Type type, string field, Type dataDef) : base($"Required field {field} of type {type} in {dataDef} wasn't mapped.") { } } diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/PrototypeIdSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/PrototypeIdSerializer.cs index 19fb2bf8b27..fc421539cfd 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/PrototypeIdSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/Prototype/PrototypeIdSerializer.cs @@ -21,7 +21,7 @@ public override ValidationNode Validate(ISerializationManager serializationManag /// /// Checks that a string corresponds to a valid prototype id. Note that any data fields using this serializer will - /// also be validated by + /// also be validated by /// [Virtual] public class PrototypeIdSerializer : ITypeValidator where TPrototype : class, IPrototype From eb638099999dce3a43d90772ca976ae010d649c0 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 25 Apr 2024 00:31:04 +1000 Subject: [PATCH 119/130] Version: 219.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index c352697c92e..dfba26116f1 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.1.3 + 219.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 25aa35ba558..0fbc0eba8c0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,22 @@ END TEMPLATE--> *None yet* +## 219.2.0 + +### New features + +* Add SetMapCoordinates to TransformSystem. +* Improve YAML Linter and validation of static fields. + +### Bugfixes + +* Fix DebugCoordsPanel freezing when hovering a control. + +### Other + +* Optimise physics networking to not dirty every tick of movement. + + ## 219.1.3 ### Bugfixes From 0fdba836eeea9ec80798b66bef9122deabbdc567 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 26 Apr 2024 00:47:09 -0400 Subject: [PATCH 120/130] Remove debug assert for Fixture.Owner equality (#5066) * Removed debug assert for Fixture owner equality * Blah --- Robust.Shared/Physics/Dynamics/Fixture.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Robust.Shared/Physics/Dynamics/Fixture.cs b/Robust.Shared/Physics/Dynamics/Fixture.cs index e8c35848a83..3d95d65653c 100644 --- a/Robust.Shared/Physics/Dynamics/Fixture.cs +++ b/Robust.Shared/Physics/Dynamics/Fixture.cs @@ -181,9 +181,6 @@ public bool Equals(Fixture? other) { if (other == null) return false; - // Owner field shouldn't be required, fixtures on other entities shouldn't be getting compared to each other. - // This is mainly here because it might've intruded some physics bugs, so this is here just in case. - DebugTools.Assert(Owner == other.Owner); return Equivalent(other) && Owner == other.Owner; } } From d72de032fa74a3b2984e3a65428404d8633e380d Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:12:55 +1000 Subject: [PATCH 121/130] Predicted BUIs (#5059) * Add TryGetOpenBUI Avoids having to get the component and openinterfaces separately. * Couple more helpers * entityquery * reviews * Shared BUIs * zawehdo * More boilerplate * Bunch more work * Building * Stuff * More state handling * API cleanup * Slight tweak * Tweaks * gabriel * Disposies * Active UI support * Lots of fixes - Fix states not applying properly, fix predicted messages, remove redundant message type, add RaiseUiMessage for an easy way to do it from shared, add the old BUI state change events back. * Fix test failures * weh * Remove unncessary closes. * release note --- RELEASE-NOTES.md | 6 +- .../EntitySystems/UserInterfaceSystem.cs | 82 +- .../EntitySystems/UserInterfaceSystem.cs | 415 +------- .../UserInterface/BoundUserInterface.cs | 22 +- .../UserInterface/IgnoreUIRangeComponent.cs | 6 +- .../UserInterface/PlayerBoundUserInterface.cs | 53 - .../ServerBoundUserInterfaceMessage.cs | 37 +- .../UserInterface/UserInterfaceComponent.cs | 115 +-- .../UserInterfaceUserComponent.cs | 25 + .../Systems/SharedUserInterfaceSystem.cs | 934 ++++++++++++++++-- Robust.Shared/Map/EntityCoordinates.cs | 3 + .../Player/SharedPlayerManager.Sessions.cs | 5 +- .../Server/Maps/MapLoaderTest.cs | 1 + 13 files changed, 939 insertions(+), 765 deletions(-) rename {Robust.Server => Robust.Shared}/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs (67%) delete mode 100644 Robust.Shared/GameObjects/Components/UserInterface/PlayerBoundUserInterface.cs create mode 100644 Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceUserComponent.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0fbc0eba8c0..b9a6b33b990 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,11 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* Refactor UserInterfaceSystem. + - The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate. + - Interface data is now stored via key rather than as a flat list which is a breaking change for YAML. + - BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before. + - BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly. ### New features diff --git a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs index f50db2892a8..20a0225a6a9 100644 --- a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -1,84 +1,8 @@ -using Robust.Client.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Reflection; -using System; -using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent; -namespace Robust.Client.GameObjects -{ - public sealed class UserInterfaceSystem : SharedUserInterfaceSystem - { - [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IReflectionManager _reflectionManager = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeNetworkEvent(MessageReceived); - } - - private void MessageReceived(BoundUIWrapMessage ev) - { - var uid = GetEntity(ev.Entity); - - if (!TryComp(uid, out var cmp)) - return; - - var uiKey = ev.UiKey; - var message = ev.Message; - message.Session = _playerManager.LocalSession!; - message.Entity = GetNetEntity(uid); - message.UiKey = uiKey; - - // Raise as object so the correct type is used. - RaiseLocalEvent(uid, (object)message, true); - - switch (message) - { - case OpenBoundInterfaceMessage _: - TryOpenUi(uid, uiKey, cmp); - break; - - case CloseBoundInterfaceMessage _: - TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp); - break; +namespace Robust.Client.GameObjects; - default: - if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui)) - bui.InternalReceiveMessage(message); - - break; - } - } - - private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null) - { - if (!Resolve(uid, ref uiComp)) - return false; - - if (uiComp.OpenInterfaces.ContainsKey(uiKey)) - return false; - - var data = uiComp.MappedInterfaceData[uiKey]; - - // TODO: This type should be cached, but I'm too lazy. - var type = _reflectionManager.LooseGetType(data.ClientType); - var boundInterface = - (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey}); - - boundInterface.Open(); - uiComp.OpenInterfaces[uiKey] = boundInterface; - - if (_playerManager.LocalSession is { } playerSession) - { - uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession); - RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true); - } +public sealed class UserInterfaceSystem : SharedUserInterfaceSystem +{ - return true; - } - } } diff --git a/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs index 3f87ff3fe0a..07fffe099df 100644 --- a/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -1,416 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Robust.Server.Player; -using Robust.Shared.Enums; +using System.Collections; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Player; -using Robust.Shared.Utility; -namespace Robust.Server.GameObjects -{ - public sealed class UserInterfaceSystem : SharedUserInterfaceSystem - { - [Dependency] private readonly IPlayerManager _playerMan = default!; - [Dependency] private readonly TransformSystem _xformSys = default!; - - private EntityQuery _ignoreUIRangeQuery; - - private readonly List _sessionCache = new(); - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeNetworkEvent(OnMessageReceived); - _playerMan.PlayerStatusChanged += OnPlayerStatusChanged; - - _ignoreUIRangeQuery = GetEntityQuery(); - } - - public override void Shutdown() - { - base.Shutdown(); - - _playerMan.PlayerStatusChanged -= OnPlayerStatusChanged; - } - - private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args) - { - if (args.NewStatus != SessionStatus.Disconnected) - return; - - if (!OpenInterfaces.TryGetValue(args.Session, out var buis)) - return; - - foreach (var bui in buis.ToArray()) - { - CloseShared(bui, args.Session); - } - } - - /// - public override void Update(float frameTime) - { - var xformQuery = GetEntityQuery(); - var query = AllEntityQuery(); - - while (query.MoveNext(out var uid, out var activeUis, out var xform)) - { - foreach (var ui in activeUis.Interfaces) - { - CheckRange(uid, activeUis, ui, xform, xformQuery); - - if (!ui.StateDirty) - continue; - - ui.StateDirty = false; - - foreach (var (player, state) in ui.PlayerStateOverrides) - { - RaiseNetworkEvent(state, player.Channel); - } - - if (ui.LastStateMsg == null) - continue; - - foreach (var session in ui.SubscribedSessions) - { - if (!ui.PlayerStateOverrides.ContainsKey(session)) - RaiseNetworkEvent(ui.LastStateMsg, session.Channel); - } - } - } - } - - /// - /// Verify that the subscribed clients are still in range of the interface. - /// - private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery query) - { - if (ui.InteractionRange <= 0) - return; - - // We have to cache the set of sessions because Unsubscribe modifies the original. - _sessionCache.Clear(); - _sessionCache.AddRange(ui.SubscribedSessions); - - var uiPos = _xformSys.GetWorldPosition(transform, query); - var uiMap = transform.MapID; - - foreach (var session in _sessionCache) - { - // The component manages the set of sessions, so this invalid session should be removed soon. - if (!query.TryGetComponent(session.AttachedEntity, out var xform)) - continue; - - if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity)) - continue; - - // Handle pluggable BoundUserInterfaceCheckRangeEvent - var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session); - RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true); - if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass) - continue; - - if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail) - { - CloseUi(ui, session, activeUis); - continue; - } - - DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default); - - if (uiMap != xform.MapID) - { - CloseUi(ui, session, activeUis); - continue; - } - - var distanceSquared = (uiPos - _xformSys.GetWorldPosition(xform, query)).LengthSquared(); - if (distanceSquared > ui.InteractionRangeSqrd) - CloseUi(ui, session, activeUis); - } - } - - #region Get BUI - - public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) - { - if (!Resolve(uid, ref ui)) - return false; - - return ui.Interfaces.ContainsKey(uiKey); - } - - public PlayerBoundUserInterface GetUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) - { - if (!Resolve(uid, ref ui)) - throw new InvalidOperationException($"Cannot get {typeof(PlayerBoundUserInterface)} from an entity without {typeof(UserInterfaceComponent)}!"); - - return ui.Interfaces[uiKey]; - } - - public PlayerBoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) - { - return TryGetUi(uid, uiKey, out var bui, ui) - ? bui - : null; - } - - /// - /// Return UIs a session has open. - /// Null if empty. - /// - public List? GetAllUIsForSession(ICommonSession session) - { - OpenInterfaces.TryGetValue(session, out var value); - return value; - } - #endregion - - public bool IsUiOpen(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; +namespace Robust.Server.GameObjects; - return bui.SubscribedSessions.Count > 0; - } - - public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; - - return bui.SubscribedSessions.Contains(session); - } - - /// - /// Sets a state. This can be used for stateful UI updating. - /// This state is sent to all clients, and automatically sent to all new clients when they open the UI. - /// Pretty much how NanoUI did it back in ye olde BYOND. - /// - /// - /// The state object that will be sent to all current and future client. - /// This can be null. - /// - /// - /// The player session to send this new state to. - /// Set to null for sending it to every subscribed player session. - /// - public bool TrySetUiState(EntityUid uid, - Enum uiKey, - BoundUserInterfaceState state, - ICommonSession? session = null, - UserInterfaceComponent? ui = null, - bool clearOverrides = true) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; - - SetUiState(bui, state, session, clearOverrides); - return true; - } - - /// - /// Sets a state. This can be used for stateful UI updating. - /// This state is sent to all clients, and automatically sent to all new clients when they open the UI. - /// Pretty much how NanoUI did it back in ye olde BYOND. - /// - /// - /// The state object that will be sent to all current and future client. - /// This can be null. - /// - /// - /// The player session to send this new state to. - /// Set to null for sending it to every subscribed player session. - /// - public void SetUiState(PlayerBoundUserInterface bui, BoundUserInterfaceState state, ICommonSession? session = null, bool clearOverrides = true) - { - var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey); - if (session == null) - { - bui.LastStateMsg = msg; - if (clearOverrides) - bui.PlayerStateOverrides.Clear(); - } - else - { - bui.PlayerStateOverrides[session] = msg; - } - - bui.StateDirty = true; - } - - #region Close - protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null) - { - var owner = bui.Owner; - bui._subscribedSessions.Remove(session); - bui.PlayerStateOverrides.Remove(session); - - if (OpenInterfaces.TryGetValue(session, out var buis)) - buis.Remove(bui); - - RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session)); - - if (bui._subscribedSessions.Count == 0) - DeactivateInterface(bui.Owner, bui, activeUis); - } - - /// - /// Closes this all interface for any clients that have any open. - /// - public bool TryCloseAll(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent? aui = null) - { - if (!Resolve(uid, ref aui, false)) - return false; - - foreach (var ui in aui.Interfaces) - { - CloseAll(ui); - } - - return true; - } - - /// - /// Closes this specific interface for any clients that have it open. - /// - public bool TryCloseAll(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; - - CloseAll(bui); - return true; - } - - /// - /// Closes this interface for any clients that have it open. - /// - public void CloseAll(PlayerBoundUserInterface bui) - { - foreach (var session in bui.SubscribedSessions.ToArray()) - { - CloseUi(bui, session); - } - } - - #endregion - - #region SendMessage - - /// - /// Send a BUI message to all connected player sessions. - /// - public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, UserInterfaceComponent? ui = null) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; - - SendUiMessage(bui, message); - return true; - } - - /// - /// Send a BUI message to all connected player sessions. - /// - public void SendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message) - { - var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey); - foreach (var session in bui.SubscribedSessions) - { - RaiseNetworkEvent(msg, session.Channel); - } - } - - /// - /// Send a BUI message to a specific player session. - /// - public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, ICommonSession session, UserInterfaceComponent? ui = null) - { - if (!TryGetUi(uid, uiKey, out var bui, ui)) - return false; - - return TrySendUiMessage(bui, message, session); - } - - /// - /// Send a BUI message to a specific player session. - /// - public bool TrySendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message, ICommonSession session) - { - if (!bui.SubscribedSessions.Contains(session)) - return false; - - RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel); - return true; - } - - #endregion - } - - /// - /// Raised by to check whether an interface is still accessible by its user. - /// - [ByRefEvent] - [PublicAPI] - public struct BoundUserInterfaceCheckRangeEvent - { - /// - /// The entity owning the UI being checked for. - /// - public readonly EntityUid Target; - - /// - /// The UI itself. - /// - /// - public readonly PlayerBoundUserInterface UserInterface; - - /// - /// The player for which the UI is being checked. - /// - public readonly ICommonSession Player; - - /// - /// The result of the range check. - /// - public BoundUserInterfaceRangeResult Result; - - public BoundUserInterfaceCheckRangeEvent( - EntityUid target, - PlayerBoundUserInterface userInterface, - ICommonSession player) - { - Target = target; - UserInterface = userInterface; - Player = player; - } - } - - /// - /// Possible results for a . - /// - public enum BoundUserInterfaceRangeResult : byte - { - /// - /// Run built-in range check. - /// - Default, - - /// - /// Range check passed, UI is accessible. - /// - Pass, - - /// - /// Range check failed, UI is inaccessible. - /// - Fail - } +public sealed class UserInterfaceSystem : SharedUserInterfaceSystem +{ } diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index b6c6fb6bd3c..00aa9f33a53 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -41,14 +41,14 @@ protected internal virtual void Open() /// /// Invoked when the server uses SetState. /// - protected virtual void UpdateState(BoundUserInterfaceState state) + protected internal virtual void UpdateState(BoundUserInterfaceState state) { } /// /// Invoked when the server sends an arbitrary message. /// - protected virtual void ReceiveMessage(BoundUserInterfaceMessage message) + protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message) { } @@ -57,7 +57,7 @@ protected virtual void ReceiveMessage(BoundUserInterfaceMessage message) /// public void Close() { - UiSystem.TryCloseUi(_playerManager.LocalSession, Owner, UiKey); + UiSystem.CloseUi(Owner, UiKey, _playerManager.LocalEntity, predicted: true); } /// @@ -65,7 +65,7 @@ public void Close() /// public void SendMessage(BoundUserInterfaceMessage message) { - UiSystem.SendUiMessage(this, message); + UiSystem.ClientSendUiMessage(Owner, UiKey, message); } public void SendPredictedMessage(BoundUserInterfaceMessage message) @@ -73,20 +73,6 @@ public void SendPredictedMessage(BoundUserInterfaceMessage message) UiSystem.SendPredictedUiMessage(this, message); } - internal void InternalReceiveMessage(BoundUserInterfaceMessage message) - { - switch (message) - { - case UpdateBoundStateMessage updateBoundStateMessage: - State = updateBoundStateMessage.State; - UpdateState(State); - break; - default: - ReceiveMessage(message); - break; - } - } - ~BoundUserInterface() { Dispose(false); diff --git a/Robust.Server/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs similarity index 67% rename from Robust.Server/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs rename to Robust.Shared/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs index db00a0c068c..b6fa9aaf91a 100644 --- a/Robust.Server/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/IgnoreUIRangeComponent.cs @@ -1,12 +1,12 @@ -using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; -namespace Robust.Server.GameObjects; +namespace Robust.Shared.GameObjects; /// /// Lets any entities with this component ignore user interface range checks that would normally /// close the UI automatically. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class IgnoreUIRangeComponent : Component { } diff --git a/Robust.Shared/GameObjects/Components/UserInterface/PlayerBoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/PlayerBoundUserInterface.cs deleted file mode 100644 index f8b0790b31d..00000000000 --- a/Robust.Shared/GameObjects/Components/UserInterface/PlayerBoundUserInterface.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Robust.Shared.Player; -using Robust.Shared.ViewVariables; - -namespace Robust.Shared.GameObjects; - -/// -/// Represents an entity-bound interface that can be opened by multiple players at once. -/// -[PublicAPI] -public sealed class PlayerBoundUserInterface -{ - [ViewVariables] - public float InteractionRange; - - [ViewVariables] - public float InteractionRangeSqrd => InteractionRange * InteractionRange; - - [ViewVariables] - public Enum UiKey { get; } - [ViewVariables] - public EntityUid Owner { get; } - - internal readonly HashSet _subscribedSessions = new(); - [ViewVariables] - internal BoundUIWrapMessage? LastStateMsg; - [ViewVariables(VVAccess.ReadWrite)] - public bool RequireInputValidation; - - [ViewVariables] - internal bool StateDirty; - - [ViewVariables] - internal readonly Dictionary PlayerStateOverrides = - new(); - - /// - /// All of the sessions currently subscribed to this UserInterface. - /// - [ViewVariables] - public IReadOnlySet SubscribedSessions => _subscribedSessions; - - public PlayerBoundUserInterface(PrototypeData data, EntityUid owner) - { - RequireInputValidation = data.RequireInputValidation; - UiKey = data.UiKey; - Owner = owner; - - InteractionRange = data.InteractionRange; - } -} diff --git a/Robust.Shared/GameObjects/Components/UserInterface/ServerBoundUserInterfaceMessage.cs b/Robust.Shared/GameObjects/Components/UserInterface/ServerBoundUserInterfaceMessage.cs index f89fcd13e00..27afadc4425 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/ServerBoundUserInterfaceMessage.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/ServerBoundUserInterfaceMessage.cs @@ -1,29 +1,26 @@ -using System.Collections.Generic; using JetBrains.Annotations; +using Robust.Shared.GameStates; using Robust.Shared.Player; using Robust.Shared.ViewVariables; -namespace Robust.Shared.GameObjects +namespace Robust.Shared.GameObjects; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ActiveUserInterfaceComponent : Component { - [RegisterComponent] - public sealed partial class ActiveUserInterfaceComponent : Component - { - [ViewVariables] - public HashSet Interfaces = new(); - } +} - [PublicAPI] - public sealed class ServerBoundUserInterfaceMessage - { - [ViewVariables] - public BoundUserInterfaceMessage Message { get; } - [ViewVariables] - public ICommonSession Session { get; } +[PublicAPI] +public sealed class ServerBoundUserInterfaceMessage +{ + [ViewVariables] + public BoundUserInterfaceMessage Message { get; } + [ViewVariables] + public ICommonSession Session { get; } - public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session) - { - Message = message; - Session = session; - } + public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session) + { + Message = message; + Session = session; } } diff --git a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs index 2e8808e8b42..42cc789c21e 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs @@ -8,36 +8,51 @@ namespace Robust.Shared.GameObjects { - [RegisterComponent, NetworkedComponent] + [RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))] public sealed partial class UserInterfaceComponent : Component { - // TODO: Obviously clean this shit up, I just moved it into shared. - - [ViewVariables] public readonly Dictionary OpenInterfaces = new(); + /// + /// The currently open interfaces. Used clientside to store the UI. + /// + [ViewVariables, Access(Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.ReadWriteExecute)] + public readonly Dictionary ClientOpenInterfaces = new(); - [ViewVariables] public readonly Dictionary Interfaces = new(); + [DataField] + internal Dictionary Interfaces = new(); - public Dictionary MappedInterfaceData = new(); + /// + /// Actors that currently have interfaces open. + /// + [DataField] + public Dictionary> Actors = new(); /// - /// Loaded on Init from serialized data. + /// Legacy data, new BUIs should be using comp states. /// - [DataField("interfaces")] internal List InterfaceData = new(); + public Dictionary States = new(); + + [Serializable, NetSerializable] + internal sealed class UserInterfaceComponentState( + Dictionary> actors, + Dictionary states) + : IComponentState + { + public Dictionary> Actors = actors; + + public Dictionary States = states; + } } [DataDefinition] - public sealed partial class PrototypeData + public sealed partial class InterfaceData { - [DataField("key", required: true)] - public Enum UiKey { get; private set; } = default!; - [DataField("type", required: true)] public string ClientType { get; private set; } = default!; /// /// Maximum range before a BUI auto-closes. A non-positive number means there is no limit. /// - [DataField("range")] + [DataField] public float InteractionRange = 2f; // TODO BUI move to content? @@ -48,7 +63,7 @@ public sealed partial class PrototypeData /// /// Avoids requiring each system to individually validate client inputs. However, perhaps some BUIs are supposed to be bypass accessibility checks /// - [DataField("requireInputValidation")] + [DataField] public bool RequireInputValidation = true; } @@ -56,18 +71,12 @@ public sealed partial class PrototypeData /// Raised whenever the server receives a BUI message from a client relating to a UI that requires input /// validation. /// - public sealed class BoundUserInterfaceMessageAttempt : CancellableEntityEventArgs + public sealed class BoundUserInterfaceMessageAttempt(EntityUid actor, EntityUid target, Enum uiKey) + : CancellableEntityEventArgs { - public readonly ICommonSession Sender; - public readonly EntityUid Target; - public readonly Enum UiKey; - - public BoundUserInterfaceMessageAttempt(ICommonSession sender, EntityUid target, Enum uiKey) - { - Sender = sender; - Target = target; - UiKey = uiKey; - } + public readonly EntityUid Actor = actor; + public readonly EntityUid Target = target; + public readonly Enum UiKey = uiKey; } [NetSerializable, Serializable] @@ -104,7 +113,7 @@ public abstract class BaseBoundUserInterfaceEvent : EntityEventArgs /// Only set when the message is raised as a directed event. /// [NonSerialized] - public ICommonSession Session = default!; + public EntityUid Actor = default!; } /// @@ -120,17 +129,6 @@ public abstract class BoundUserInterfaceMessage : BaseBoundUserInterfaceEvent public NetEntity Entity { get; set; } = NetEntity.Invalid; } - [NetSerializable, Serializable] - internal sealed class UpdateBoundStateMessage : BoundUserInterfaceMessage - { - public readonly BoundUserInterfaceState State; - - public UpdateBoundStateMessage(BoundUserInterfaceState state) - { - State = state; - } - } - [NetSerializable, Serializable] internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage { @@ -142,59 +140,38 @@ internal sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage } [Serializable, NetSerializable] - internal abstract class BaseBoundUIWrapMessage : EntityEventArgs + internal abstract class BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) + : EntityEventArgs { - public readonly NetEntity Entity; - public readonly BoundUserInterfaceMessage Message; - public readonly Enum UiKey; - - public BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) - { - Message = message; - UiKey = uiKey; - Entity = entity; - } + public readonly NetEntity Entity = entity; + public readonly BoundUserInterfaceMessage Message = message; + public readonly Enum UiKey = uiKey; } /// /// Helper message raised from client to server. /// [Serializable, NetSerializable] - internal sealed class BoundUIWrapMessage : BaseBoundUIWrapMessage - { - public BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey) - { - } - } - - /// - /// Helper message raised from client to server. - /// - [Serializable, NetSerializable] - internal sealed class PredictedBoundUIWrapMessage : BaseBoundUIWrapMessage - { - public PredictedBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey) - { - } - } + internal sealed class BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) + : BaseBoundUIWrapMessage(entity, message, uiKey); public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent { - public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, ICommonSession session) + public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, EntityUid actor) { UiKey = uiKey; Entity = uid; - Session = session; + Actor = actor; } } public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent { - public BoundUIClosedEvent(Enum uiKey, EntityUid uid, ICommonSession session) + public BoundUIClosedEvent(Enum uiKey, EntityUid uid, EntityUid actor) { UiKey = uiKey; Entity = uid; - Session = session; + Actor = actor; } } } diff --git a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceUserComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceUserComponent.cs new file mode 100644 index 00000000000..f2e09def547 --- /dev/null +++ b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceUserComponent.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.GameObjects; + +/// +/// Stores data about this entity and what BUIs they have open. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class UserInterfaceUserComponent : Component +{ + public override bool SessionSpecific => true; + + [DataField] + public Dictionary> OpenInterfaces = new(); +} + +[Serializable, NetSerializable] +internal sealed class UserInterfaceUserComponentState : IComponentState +{ + public Dictionary> OpenInterfaces = new(); +} diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 5ad50f9d995..4bbd6da6adc 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -1,78 +1,100 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Robust.Shared.Enums; +using System.Linq; +using System.Runtime.InteropServices; +using JetBrains.Annotations; +using Robust.Shared.Collections; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Reflection; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; public abstract class SharedUserInterfaceSystem : EntitySystem { - protected readonly Dictionary> OpenInterfaces = new(); + [Dependency] private readonly IDynamicTypeFactory _factory = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IReflectionManager _reflection = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly SharedTransformSystem _transforms = default!; + + private EntityQuery _ignoreUIRangeQuery; + private EntityQuery _xformQuery; + private EntityQuery _uiQuery; + private EntityQuery _userQuery; public override void Initialize() { base.Initialize(); - SubscribeAllEvent(OnMessageReceived); - SubscribeLocalEvent(OnUserInterfaceInit); - SubscribeLocalEvent(OnUserInterfaceShutdown); - } - private void OnUserInterfaceInit(EntityUid uid, UserInterfaceComponent component, ComponentInit args) - { - component.Interfaces.Clear(); + _ignoreUIRangeQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); + _uiQuery = GetEntityQuery(); + _userQuery = GetEntityQuery(); - foreach (var prototypeData in component.InterfaceData) + SubscribeAllEvent((msg, args) => { - AddUi((uid, component), prototypeData); - } - } + if (args.SenderSession.AttachedEntity is not { } player) + return; - private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args) - { - if (!TryComp(uid, out ActiveUserInterfaceComponent? activeUis)) - return; + OnMessageReceived(msg, player); + }); - foreach (var bui in activeUis.Interfaces) - { - DeactivateInterface(uid, bui, activeUis); - } + SubscribeLocalEvent(OnUserInterfaceOpen); + SubscribeLocalEvent(OnUserInterfaceClosed); + SubscribeLocalEvent(OnUserInterfaceShutdown); + SubscribeLocalEvent(OnUserInterfaceGetState); + SubscribeLocalEvent(OnUserInterfaceHandleState); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnGetStateAttempt); + SubscribeLocalEvent(OnActorGetState); + SubscribeLocalEvent(OnActorHandleState); } /// - /// Validates the received message, and then pass it onto systems/components + /// Validates the received message, and then pass it onto systems/components /// - internal void OnMessageReceived(BaseBoundUIWrapMessage msg, EntitySessionEventArgs args) + private void OnMessageReceived(BoundUIWrapMessage msg, EntityUid sender) { - var uid = GetEntity(msg.Entity); + // This is more or less the main BUI method that handles all messages. - if (!TryComp(uid, out UserInterfaceComponent? uiComp) || args.SenderSession is not { } session) - return; + var uid = GetEntity(msg.Entity); - if (!uiComp.Interfaces.TryGetValue(msg.UiKey, out var ui)) + if (!_uiQuery.TryComp(uid, out var uiComp)) { - Log.Debug($"Got BoundInterfaceMessageWrapMessage for unknown UI key: {msg.UiKey}"); return; } - if (!ui.SubscribedSessions.Contains(session)) + if (!uiComp.Interfaces.TryGetValue(msg.UiKey, out var ui)) { - Log.Debug($"UI {msg.UiKey} got BoundInterfaceMessageWrapMessage from a client who was not subscribed: {session}"); + Log.Debug($"Got BoundInterfaceMessageWrapMessage for unknown UI key: {msg.UiKey}"); return; } - // if they want to close the UI, we can go home early. - if (msg.Message is CloseBoundInterfaceMessage) + // If it's not an open message check we're even a subscriber. + if (msg.Message is not OpenBoundInterfaceMessage && + (!uiComp.Actors.TryGetValue(msg.UiKey, out var actors) || + !actors.Contains(sender))) { - CloseShared(ui, session); + Log.Debug($"UI {msg.UiKey} got BoundInterfaceMessageWrapMessage from a client who was not subscribed: {ToPrettyString(sender)}"); return; } // verify that the user is allowed to press buttons on this UI: - if (ui.RequireInputValidation) + // If it's a close message something else might try to cancel it but we want to force it. + if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation) { - var attempt = new BoundUserInterfaceMessageAttempt(args.SenderSession, uid, msg.UiKey); + var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey); RaiseLocalEvent(attempt); if (attempt.Cancelled) return; @@ -80,172 +102,866 @@ internal void OnMessageReceived(BaseBoundUIWrapMessage msg, EntitySessionEventAr // get the wrapped message and populate it with the sender & UI key information. var message = msg.Message; - message.Session = args.SenderSession; + message.Actor = sender; message.Entity = msg.Entity; message.UiKey = msg.UiKey; + if (uiComp.ClientOpenInterfaces.TryGetValue(msg.UiKey, out var cBui)) + { + cBui.ReceiveMessage(message); + } + // Raise as object so the correct type is used. RaiseLocalEvent(uid, (object)message, true); } - protected void DeactivateInterface(EntityUid entityUid, PlayerBoundUserInterface ui, - ActiveUserInterfaceComponent? activeUis = null) + #region User + + private void OnGetStateAttempt(Entity ent, ref ComponentGetStateAttemptEvent args) + { + if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner) + args.Cancelled = true; + } + + private void OnActorGetState(Entity ent, ref ComponentGetState args) + { + var interfaces = new Dictionary>(); + + foreach (var (buid, data) in ent.Comp.OpenInterfaces) + { + interfaces[GetNetEntity(buid)] = data; + } + + args.State = new UserInterfaceUserComponentState() + { + OpenInterfaces = interfaces, + }; + } + + private void OnActorHandleState(Entity ent, ref ComponentHandleState args) + { + if (args.Current is not UserInterfaceUserComponentState state) + return; + + // TODO: Allocate less. + ent.Comp.OpenInterfaces.Clear(); + + foreach (var (nent, data) in state.OpenInterfaces) + { + var openEnt = EnsureEntity(nent, ent.Owner); + ent.Comp.OpenInterfaces[openEnt] = data; + } + } + + #endregion + + private void OnPlayerAttached(PlayerAttachedEvent ev) + { + if (!_userQuery.TryGetComponent(ev.Entity, out var actor)) + return; + + // Open BUIs upon attachment + foreach (var (uid, keys) in actor.OpenInterfaces) + { + if (!_uiQuery.TryGetComponent(uid, out var uiComp)) + continue; + + foreach (var key in keys) + { + if (!uiComp.Interfaces.TryGetValue(key, out var data)) + continue; + + EnsureClientBui((uid, uiComp), key, data); + } + } + } + + private void OnPlayerDetached(PlayerDetachedEvent ev) { - if (!Resolve(entityUid, ref activeUis, false)) + if (!_userQuery.TryGetComponent(ev.Entity, out var actor)) return; - activeUis.Interfaces.Remove(ui); - if (activeUis.Interfaces.Count == 0) - RemCompDeferred(entityUid, activeUis); + // Close BUIs open detachment. + foreach (var (uid, keys) in actor.OpenInterfaces) + { + if (!_uiQuery.TryGetComponent(uid, out var uiComp)) + continue; + + foreach (var key in keys) + { + if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) + continue; + + cBui.Dispose(); + uiComp.ClientOpenInterfaces.Remove(key); + } + } + } + + private void OnUserInterfaceClosed(Entity ent, ref CloseBoundInterfaceMessage args) + { + // This handles all of the actually closing BUI. + // This is because CloseUi just relays the event so client sending message to server vs just server + // go through the same path. + + var actor = args.Actor; + + var actors = ent.Comp.Actors[args.UiKey]; + actors.Remove(actor); + + if (actors.Count == 0) + ent.Comp.Actors.Remove(args.UiKey); + + Dirty(ent); + + // If the actor is also deleting then don't worry about updating what they have open. + if (!TerminatingOrDeleted(actor)) + { + var actorComp = EnsureComp(actor); + + if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys)) + { + keys.Remove(args.UiKey); + + if (keys.Count == 0) + actorComp.OpenInterfaces.Remove(ent.Owner); + + Dirty(actor, actorComp); + } + } + + // If we're client we want this handled immediately. + if (ent.Comp.ClientOpenInterfaces.Remove(args.UiKey, out var cBui)) + { + cBui.Dispose(); + } + + if (ent.Comp.Actors.Count == 0) + RemCompDeferred(ent.Owner); + + var ev = new BoundUIClosedEvent(args.UiKey, ent.Owner, args.Actor); + RaiseLocalEvent(ent.Owner, ev); + } + + private void OnUserInterfaceOpen(Entity ent, ref OpenBoundInterfaceMessage args) + { + // Similar to the close method this handles actually opening a UI, it just gets relayed here + EnsureComp(ent.Owner); + + var actor = args.Actor; + var actorComp = EnsureComp(actor); + + // Let state handling open the UI clientside. + actorComp.OpenInterfaces.GetOrNew(ent.Owner).Add(args.UiKey); + ent.Comp.Actors.GetOrNew(args.UiKey).Add(actor); + Dirty(ent); + Dirty(actor, actorComp); + + var ev = new BoundUIOpenedEvent(args.UiKey, ent.Owner, args.Actor); + RaiseLocalEvent(ent.Owner, ev); + + // If we're client we want this handled immediately. + EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]); } - protected virtual void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, - ActiveUserInterfaceComponent? activeUis = null) + private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args) { + foreach (var bui in component.ClientOpenInterfaces.Values) + { + bui.Dispose(); + } + + component.ClientOpenInterfaces.Clear(); + } + + private void OnUserInterfaceGetState(Entity ent, ref ComponentGetState args) + { + var actors = new Dictionary>(); + var states = new Dictionary(); + + foreach (var (key, acts) in ent.Comp.Actors) + { + actors[key] = GetNetEntityList(acts); + } + + foreach (var (key, state) in ent.Comp.States) + { + states[key] = state; + } + + args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, states); + } + + private void OnUserInterfaceHandleState(Entity ent, ref ComponentHandleState args) + { + if (args.Current is not UserInterfaceComponent.UserInterfaceComponentState state) + return; + + var toRemove = new ValueList(); + + foreach (var (key, actors) in state.Actors) + { + ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(ent.Comp.Actors, key, out _); + + existing ??= new List(); + + existing.Clear(); + existing.AddRange(EnsureEntityList(actors, ent.Owner)); + } + + foreach (var key in ent.Comp.Actors.Keys) + { + if (state.Actors.ContainsKey(key)) + continue; + + toRemove.Add(key); + } + + foreach (var key in toRemove) + { + ent.Comp.Actors.Remove(key); + } + + toRemove.Clear(); + + // State handling + foreach (var key in ent.Comp.States.Keys) + { + if (state.States.ContainsKey(key)) + continue; + + toRemove.Add(key); + } + + foreach (var key in toRemove) + { + ent.Comp.States.Remove(key); + } + + toRemove.Clear(); + + // Check if the UI is still open, otherwise call close. + foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces) + { + if (ent.Comp.Actors.ContainsKey(key)) + continue; + + bui.Dispose(); + toRemove.Add(key); + } + + foreach (var key in toRemove) + { + ent.Comp.ClientOpenInterfaces.Remove(key); + } + + // update any states we have open + foreach (var (key, buiState) in state.States) + { + if (ent.Comp.States.TryGetValue(key, out var existing) && + existing.Equals(buiState)) + { + continue; + } + + ent.Comp.States[key] = buiState; + + if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) + continue; + + cBui.UpdateState(buiState); + } + + // If UI not open then open it + var attachedEnt = _player.LocalEntity; + + if (attachedEnt != null) + { + foreach (var (key, value) in ent.Comp.Interfaces) + { + EnsureClientBui(ent, key, value); + } + } } /// - /// Add a UI after an entity has been created. - /// It cannot be added already. + /// Opens a client's BUI if not already open and applies the state to it. /// - public void AddUi(Entity ent, PrototypeData data) + private void EnsureClientBui(Entity entity, Enum key, InterfaceData data) { - if (!Resolve(ent, ref ent.Comp)) + // If it's out BUI open it up and apply the state, otherwise do nothing. + var player = _player.LocalEntity; + + if (player == null || + !entity.Comp.Actors.TryGetValue(key, out var actors) || + !actors.Contains(player.Value)) + { return; + } + + DebugTools.Assert(_netManager.IsClient); - ent.Comp.Interfaces[data.UiKey] = new PlayerBoundUserInterface(data, ent); - ent.Comp.MappedInterfaceData[data.UiKey] = data; + if (entity.Comp.ClientOpenInterfaces.ContainsKey(key)) + { + return; + } + + var type = _reflection.LooseGetType(data.ClientType); + var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]); + + entity.Comp.ClientOpenInterfaces[key] = boundUserInterface; + boundUserInterface.Open(); + + if (entity.Comp.States.TryGetValue(key, out var buiState)) + { + boundUserInterface.UpdateState(buiState); + } } - public bool TryGetUi(EntityUid uid, Enum uiKey, [NotNullWhen(true)] out PlayerBoundUserInterface? bui, UserInterfaceComponent? ui = null) + /// + /// Yields all the entities + keys currently open by this entity. + /// + public IEnumerable<(EntityUid Entity, Enum Key)> GetActorUis(Entity entity) { - bui = null; + if (!_userQuery.Resolve(entity.Owner, ref entity.Comp, false)) + yield break; + + foreach (var berry in entity.Comp.OpenInterfaces) + { + foreach (var key in berry.Value) + { + yield return (berry.Key, key); + } + } + } + + /// + /// Gets the actors that have the specified key attached to this entity open. + /// + public IEnumerable GetActors(Entity entity, Enum key) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false) || !entity.Comp.Actors.TryGetValue(key, out var actors)) + yield break; + + foreach (var actorUid in actors) + { + yield return actorUid; + } + } + + /// + /// Closes the attached UI for all entities. + /// + public void CloseUi(Entity entity, Enum key) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors)) + return; + + for (var i = actors.Count - 1; i >= 0; i--) + { + var actor = actors[i]; + CloseUi(entity, key, actor); + } + + DebugTools.Assert(actors.Count == 0); + } + + /// + /// Closes the attached UI only for the specified actor. + /// + public void CloseUi(Entity entity, Enum key, ICommonSession? actor, bool predicted = false) + { + var actorEnt = actor?.AttachedEntity; + + if (actorEnt == null) + return; + + CloseUi(entity, key, actorEnt.Value, predicted); + } + + /// + /// Closes the attached Ui only for the specified actor. + /// + public void CloseUi(Entity entity, Enum key, EntityUid? actor, bool predicted = false) + { + if (actor == null) + return; + + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + // Short-circuit if no UI. + if (!entity.Comp.Interfaces.ContainsKey(key)) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors) || !actors.Contains(actor.Value)) + return; - return Resolve(uid, ref ui, false) && ui.Interfaces.TryGetValue(uiKey, out bui); + // Rely upon the client telling us. + if (predicted) + { + if (_timing.IsFirstTimePredicted) + { + // Not guaranteed to open so rely upon the event handling it. + // Also lets client request it to be opened remotely too. + EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key)); + } + } + else + { + OnMessageReceived(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key), actor.Value); + } } /// - /// Switches between closed and open for a specific client. + /// Tries to call OpenUi and return false if it isn't open. /// - public virtual bool TryToggleUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null) + public bool TryOpenUi(Entity entity, Enum key, EntityUid actor, bool predicted = false) { - if (!TryGetUi(uid, uiKey, out var bui, ui)) + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) return false; - ToggleUi(bui, session); + OpenUi(entity, key, actor, predicted); + + // Due to the event actually handling the UI open / closed we can't + if (!entity.Comp.Actors.TryGetValue(key, out var actors) || + !actors.Contains(actor)) + { + return false; + } + return true; } + public void OpenUi(Entity entity, Enum key, EntityUid? actor, bool predicted = false) + { + if (actor == null || !_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + // No implementation for that UI key on this ent so short-circuit. + if (!entity.Comp.Interfaces.ContainsKey(key)) + return; + + if (entity.Comp.Actors.TryGetValue(key, out var actors) && actors.Contains(actor.Value)) + return; + + if (predicted) + { + if (_timing.IsFirstTimePredicted) + { + // Not guaranteed to open so rely upon the event handling it. + // Also lets client request it to be opened remotely too. + EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new OpenBoundInterfaceMessage(), key)); + } + } + else + { + OnMessageReceived(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new OpenBoundInterfaceMessage(), key), actor.Value); + } + } + + public void OpenUi(Entity entity, Enum key, ICommonSession actor, bool predicted = false) + { + var actorEnt = actor.AttachedEntity; + + if (actorEnt == null) + return; + + OpenUi(entity, key, actorEnt.Value, predicted); + } + /// - /// Switches between closed and open for a specific client. + /// Sets a BUI state and networks it to all clients. /// - public void ToggleUi(PlayerBoundUserInterface bui, ICommonSession session) + public void SetUiState(Entity entity, Enum key, BoundUserInterfaceState? state) { - if (bui._subscribedSessions.Contains(session)) - CloseUi(bui, session); + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + if (!entity.Comp.Interfaces.ContainsKey(key)) + return; + + // Null state + if (state == null) + { + if (!entity.Comp.States.Remove(key)) + return; + + Dirty(entity); + } + // Non-null state, check if it matches existing. else - OpenUi(bui, session); + { + ref var stateRef = ref CollectionsMarshal.GetValueRefOrAddDefault(entity.Comp.States, key, out var exists); + + if (exists && stateRef?.Equals(state) == true) + return; + + stateRef = state; + } + + Dirty(entity); } - public bool TryOpen(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null) + /// + /// Returns true if this entity has the specified Ui key available, even if not currently open. + /// + public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null) { - if (!TryGetUi(uid, uiKey, out var bui, ui)) + if (!Resolve(uid, ref ui, false)) return false; - return OpenUi(bui, session); + return ui.Interfaces.ContainsKey(uiKey); } /// - /// Opens this interface for a specific client. + /// Returns true if the specified UI key is open for this entity by anyone. /// - public bool OpenUi(PlayerBoundUserInterface bui, ICommonSession session) + public bool IsUiOpen(Entity entity, Enum uiKey) { - if (session.Status == SessionStatus.Connecting || session.Status == SessionStatus.Disconnected) + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) return false; - if (!bui._subscribedSessions.Add(session)) + if (!entity.Comp.Actors.TryGetValue(uiKey, out var actors)) return false; - OpenInterfaces.GetOrNew(session).Add(bui); - RaiseLocalEvent(bui.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Owner, session)); - if (!bui._subscribedSessions.Contains(session)) - { - // This can happen if Content closed a BUI from inside the event handler. - // This will already have caused a redundant close event to be sent to the client, but whatever. - // Just avoid doing the rest to avoid any state corruption shit. + DebugTools.Assert(actors.Count > 0); + return actors.Count > 0; + } + + public bool IsUiOpen(Entity entity, Enum uiKey, EntityUid actor) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) return false; + + if (!entity.Comp.Actors.TryGetValue(uiKey, out var actors)) + return false; + + return actors.Contains(actor); + } + + /// + /// Raises a BUI message locally (on client or server) without networking it. + /// + [PublicAPI] + public void RaiseUiMessage(Entity entity, Enum key, BoundUserInterfaceMessage message) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors)) + return; + + OnMessageReceived(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key), message.Actor); + } + + #region Server messages + + /// + /// Sends a BUI message to any actors who have the specified Ui key open. + /// + public void ServerSendUiMessage(Entity entity, Enum key, BoundUserInterfaceMessage message) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors)) + return; + + var filter = Filter.Entities(actors.ToArray()); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key), filter); + } + + /// + /// Sends a Bui message to the specified actor only. + /// + public void ServerSendUiMessage(Entity entity, Enum key, BoundUserInterfaceMessage message, EntityUid actor) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors) || !actors.Contains(actor)) + return; + + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key), actor); + } + + /// + /// Sends a Bui message to the specified actor only. + /// + public void ServerSendUiMessage(Entity entity, Enum key, BoundUserInterfaceMessage message, ICommonSession actor) + { + if (!_netManager.IsClient) + return; + + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false) || actor.AttachedEntity is not { } attachedEntity) + return; + + if (!entity.Comp.Actors.TryGetValue(key, out var actors) || !actors.Contains(attachedEntity)) + return; + + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key), actor); + } + + #endregion + + /// + /// Raises a BUI message from the client to the server. + /// + public void ClientSendUiMessage(Entity entity, Enum key, BoundUserInterfaceMessage message) + { + var player = _player.LocalEntity; + + // Don't send it if we're not a valid actor for it just in case. + if (player == null || + !_uiQuery.Resolve(entity.Owner, ref entity.Comp, false) || + !entity.Comp.Actors.TryGetValue(key, out var actors) || + !actors.Contains(player.Value)) + { + return; } - RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new OpenBoundInterfaceMessage(), bui.UiKey), session.Channel); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key)); + } - // Fun fact, clients needs to have BUIs open before they can receive the state..... - if (bui.LastStateMsg != null) - RaiseNetworkEvent(bui.LastStateMsg, session.Channel); + /// + /// Closes all Uis for the entity. + /// + public void CloseUis(Entity entity) + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; - ActivateInterface(bui); - return true; + entity.Comp.Actors.Clear(); + entity.Comp.States.Clear(); + Dirty(entity); } - private void ActivateInterface(PlayerBoundUserInterface ui) + /// + /// Closes all Uis for the entity that the specified actor has open. + /// + public void CloseUis(Entity entity, EntityUid actor) { - EnsureComp(ui.Owner).Interfaces.Add(ui); + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; + + foreach (var key in entity.Comp.Interfaces.Keys) + { + CloseUi(entity, key, actor); + } } - internal bool TryCloseUi(ICommonSession? session, EntityUid uid, Enum uiKey, bool remoteCall = false, UserInterfaceComponent? uiComp = null) + /// + /// Closes all Uis for the entity that the specified actor has open. + /// + public void CloseUis(Entity entity, ICommonSession actor) { - if (!Resolve(uid, ref uiComp)) - return false; + if (actor.AttachedEntity is not { } attachedEnt || !_uiQuery.Resolve(entity.Owner, ref entity.Comp, false)) + return; - if (!uiComp.OpenInterfaces.TryGetValue(uiKey, out var boundUserInterface)) - return false; + CloseUis(entity, attachedEnt); + } - if (!remoteCall) - SendUiMessage(boundUserInterface, new CloseBoundInterfaceMessage()); + /// + /// Tries to get the BUI if it is currently open. + /// + public bool TryGetOpenUi(Entity entity, Enum uiKey, [NotNullWhen(true)] out BoundUserInterface? bui) + { + bui = null; - uiComp.OpenInterfaces.Remove(uiKey); - boundUserInterface.Dispose(); + return _uiQuery.Resolve(entity.Owner, ref entity.Comp, false) && entity.Comp.ClientOpenInterfaces.TryGetValue(uiKey, out bui); + } - if (session != null) - RaiseLocalEvent(uid, new BoundUIClosedEvent(uiKey, uid, session), true); + /// + /// Tries to get the BUI if it is currently open. + /// + public bool TryGetOpenUi(Entity entity, Enum uiKey, [NotNullWhen(true)] out T? bui) where T : BoundUserInterface + { + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false) || !entity.Comp.ClientOpenInterfaces.TryGetValue(uiKey, out var cBui)) + { + bui = null; + return false; + } + bui = (T)cBui; return true; } - public bool TryClose(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null) + public bool TryToggleUi(Entity entity, Enum uiKey, ICommonSession actor) { - if (!TryGetUi(uid, uiKey, out var bui, ui)) + if (actor.AttachedEntity is not { } attachedEntity) return false; - return CloseUi(bui, session); + return TryToggleUi(entity, uiKey, attachedEntity); } /// - /// Close this interface for a specific client. + /// Switches between closed and open for a specific client. /// - public bool CloseUi(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null) + public bool TryToggleUi(Entity entity, Enum uiKey, EntityUid actor) { - if (!bui._subscribedSessions.Remove(session)) + if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false) || + !entity.Comp.Interfaces.ContainsKey(uiKey)) + { return false; + } + + if (entity.Comp.Actors.TryGetValue(uiKey, out var actors) && actors.Contains(actor)) + { + CloseUi(entity, uiKey, actor); + } + else + { + OpenUi(entity, uiKey, actor); + } - RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new CloseBoundInterfaceMessage(), bui.UiKey), session.Channel); - CloseShared(bui, session, activeUis); return true; } /// - /// Raised by client-side UIs to send to server. + /// Raised by client-side UIs to send predicted messages to server. /// - internal void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg) + public void SendPredictedUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg) { - RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey)); + RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey)); } + /// + public override void Update(float frameTime) + { + var query = AllEntityQuery(); + + // Handles closing the BUI if actors move out of range of them. + while (query.MoveNext(out var uid, out _, out var uiComp)) + { + foreach (var (key, actors) in uiComp.Actors) + { + DebugTools.Assert(actors.Count > 0); + var data = uiComp.Interfaces[key]; + + // Short-circuit + if (data.InteractionRange <= 0f || actors.Count == 0) + continue; + + // Okay so somehow UISystem is high up on the server profile + // If that's actually still a problem turn this into an IParallelRobustJob and slam all the UIs in parallel. + var xform = _xformQuery.GetComponent(uid); + var coordinates = xform.Coordinates; + var mapId = xform.MapID; + + for (var i = actors.Count - 1; i >= 0; i--) + { + var actor = actors[i]; + + if (CheckRange(uid, key, data, actor, coordinates, mapId)) + continue; + + // Using the non-predicted one here seems fine? + CloseUi((uid, uiComp), key, actor); + } + } + } + } /// - /// Raised by client-side UIs to send predicted messages to server. + /// Verify that the subscribed clients are still in range of the interface. /// - internal void SendPredictedUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg) + private bool CheckRange( + EntityUid uid, + Enum key, + InterfaceData data, + EntityUid actor, + EntityCoordinates uiCoordinates, + MapId uiMap) { - RaisePredictiveEvent(new PredictedBoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey)); + if (_ignoreUIRangeQuery.HasComponent(actor)) + return true; + + if (!_xformQuery.TryGetComponent(actor, out var actorXform)) + return false; + + // Handle pluggable BoundUserInterfaceCheckRangeEvent + var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor); + RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true); + + if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass) + return true; + + if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail) + return false; + + DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default); + + if (uiMap != actorXform.MapID) + return false; + + return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange); } } + +/// +/// Raised by to check whether an interface is still accessible by its user. +/// +[ByRefEvent] +[PublicAPI] +public struct BoundUserInterfaceCheckRangeEvent +{ + /// + /// The entity owning the UI being checked for. + /// + public readonly EntityUid Target; + + /// + /// The UI itself. + /// + /// + public readonly Enum UiKey; + + public readonly InterfaceData Data; + + /// + /// The player for which the UI is being checked. + /// + public readonly EntityUid Actor; + + /// + /// The result of the range check. + /// + public BoundUserInterfaceRangeResult Result; + + public BoundUserInterfaceCheckRangeEvent( + EntityUid target, + Enum uiKey, + InterfaceData data, + EntityUid actor) + { + Target = target; + UiKey = uiKey; + Data = data; + Actor = actor; + } +} + +/// +/// Possible results for a . +/// +public enum BoundUserInterfaceRangeResult : byte +{ + /// + /// Run built-in range check. + /// + Default, + + /// + /// Range check passed, UI is accessible. + /// + Pass, + + /// + /// Range check failed, UI is inaccessible. + /// + Fail +} diff --git a/Robust.Shared/Map/EntityCoordinates.cs b/Robust.Shared/Map/EntityCoordinates.cs index 41c60c39aa3..9aaf8039224 100644 --- a/Robust.Shared/Map/EntityCoordinates.cs +++ b/Robust.Shared/Map/EntityCoordinates.cs @@ -344,6 +344,9 @@ public bool InRange( var mapCoordinates = ToMap(entityManager, transformSystem); var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem); + if (mapCoordinates.MapId != otherMapCoordinates.MapId) + return false; + return mapCoordinates.InRange(otherMapCoordinates, range); } diff --git a/Robust.Shared/Player/SharedPlayerManager.Sessions.cs b/Robust.Shared/Player/SharedPlayerManager.Sessions.cs index b615514814d..d0d958e57c0 100644 --- a/Robust.Shared/Player/SharedPlayerManager.Sessions.cs +++ b/Robust.Shared/Player/SharedPlayerManager.Sessions.cs @@ -200,12 +200,13 @@ private bool Attach(ICommonSession session, EntityUid uid, out ICommonSession? k if (EntManager.EnsureComponent(uid, out var actor)) { // component already existed. - DebugTools.AssertNotNull(actor.PlayerSession); if (!force) return false; kicked = actor.PlayerSession; - Detach(kicked); + + if (kicked != null) + Detach(kicked); } if (_netMan.IsServer) diff --git a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs index 68ad2b2202e..9f6130ef05f 100644 --- a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs +++ b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs @@ -12,6 +12,7 @@ using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; +using IgnoreUIRangeComponent = Robust.Shared.GameObjects.IgnoreUIRangeComponent; namespace Robust.UnitTesting.Server.Maps { From 123d0ae6acf397654c135fe2b8f5268ff80c9bcf Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 26 Apr 2024 18:15:47 +1000 Subject: [PATCH 122/130] Version: 220.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index dfba26116f1..5be0e8aca35 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 219.2.0 + 220.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b9a6b33b990..210414121c0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,11 +35,7 @@ END TEMPLATE--> ### Breaking changes -* Refactor UserInterfaceSystem. - - The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate. - - Interface data is now stored via key rather than as a flat list which is a breaking change for YAML. - - BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before. - - BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly. +*None yet* ### New features @@ -58,6 +54,17 @@ END TEMPLATE--> *None yet* +## 220.0.0 + +### Breaking changes + +* Refactor UserInterfaceSystem. + - The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate. + - Interface data is now stored via key rather than as a flat list which is a breaking change for YAML. + - BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before. + - BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly. + + ## 219.2.0 ### New features From 16bab1bc032c7e25faf5de2fed4c55706de7d881 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:21:46 +1000 Subject: [PATCH 123/130] Close UIs on disconnect (#5071) Engine handles it fine but content does not as the state gets handled before all comps are initialized. --- .../Systems/SharedUserInterfaceSystem.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 4bbd6da6adc..97cf4227200 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -59,6 +59,25 @@ public override void Initialize() SubscribeLocalEvent(OnGetStateAttempt); SubscribeLocalEvent(OnActorGetState); SubscribeLocalEvent(OnActorHandleState); + + _player.PlayerStatusChanged += OnStatusChange; + } + + private void OnStatusChange(object? sender, SessionStatusEventArgs e) + { + var attachedEnt = e.Session.AttachedEntity; + + if (attachedEnt == null) + return; + + // Content can't handle it yet sadly :( + CloseUserUis(attachedEnt.Value); + } + + public override void Shutdown() + { + base.Shutdown(); + _player.PlayerStatusChanged -= OnStatusChange; } /// @@ -727,6 +746,32 @@ public void ClientSendUiMessage(Entity entity, Enum key RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key)); } + /// + /// Closes all Uis for the actor. + /// + public void CloseUserUis(Entity actor) + { + if (!_userQuery.Resolve(actor.Owner, ref actor.Comp, false)) + return; + + if (actor.Comp.OpenInterfaces.Count == 0) + return; + + var copied = new Dictionary>(actor.Comp.OpenInterfaces); + var enumCopy = new ValueList(); + + foreach (var (uid, enums) in copied) + { + enumCopy.Clear(); + enumCopy.AddRange(enums); + + foreach (var key in enumCopy) + { + CloseUi(uid, key, actor.Owner); + } + } + } + /// /// Closes all Uis for the entity. /// From 2a102f048f5c904363e5916057f37cf991835e8a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:30:00 +1200 Subject: [PATCH 124/130] Fix client-side replay exception (#5068) Co-authored-by: metalgearsloth --- .../Loading/ReplayLoadManager.Checkpoints.cs | 23 +++++++++++++++---- .../ReplayPlaybackManager.Checkpoint.cs | 19 ++++++++------- .../GameObjects/ServerEntityManager.cs | 2 +- Robust.Shared/GameObjects/EntityManager.cs | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs index ee11dfd2652..551fbc95a8d 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs @@ -88,7 +88,6 @@ public sealed partial class ReplayLoadManager if (initMessages != null) UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true); UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true); - ProcessQueue(GameTick.MaxValue, detachQueue, detached); var entSpan = state0.EntityStates.Value; Dictionary entStates = new(entSpan.Count); @@ -98,6 +97,8 @@ public sealed partial class ReplayLoadManager entStates.Add(entState.NetEntity, modifiedState); } + ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates); + await callback(0, states.Count, LoadingState.ProcessingFiles, true); var playerSpan = state0.PlayerStates.Value; Dictionary playerStates = new(playerSpan.Count); @@ -144,7 +145,7 @@ TimeSpan GetTime(GameTick tick) UpdatePlayerStates(curState.PlayerStates.Span, playerStates); UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached); UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase); - ProcessQueue(curState.ToSequence, detachQueue, detached); + ProcessQueue(curState.ToSequence, detachQueue, detached, entStates); UpdateDeletions(curState.EntityDeletions, entStates, detached); serverTime[i] = GetTime(curState.ToSequence) - initialTime; ticksSinceLastCheckpoint++; @@ -176,14 +177,28 @@ TimeSpan GetTime(GameTick tick) private void ProcessQueue( GameTick curTick, Dictionary> detachQueue, - HashSet detached) + HashSet detached, + Dictionary entStates) { foreach (var (tick, ents) in detachQueue) { if (tick > curTick) continue; detachQueue.Remove(tick); - detached.UnionWith(ents); + + foreach (var e in ents) + { + if (entStates.ContainsKey(e)) + detached.Add(e); + else + { + // AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss + // I.e., entity was created on tick n, then leaves PVS range on the tick n+1 + // If the n-th tick gets dropped, the client only ever receives the pvs-leave message. + // In that case we should just ignore it. + _sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?"); + } + } } } diff --git a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs index adc8ed1248f..fa1cb57c46c 100644 --- a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs +++ b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs @@ -79,13 +79,14 @@ private void EnsureDetachedExist(CheckpointState checkpoint) if (checkpoint.DetachedStates == null) return; - DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ; - var metas = _entMan.GetEntityQuery(); + DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); foreach (var es in checkpoint.DetachedStates) { - var uid = _entMan.GetEntity(es.NetEntity); - if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted) + if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta)) + { + DebugTools.Assert(!meta.EntityDeleted); continue; + } var metaState = (MetaDataComponentState?)es.ComponentChanges.Value? .FirstOrDefault(c => c.NetID == _metaId).State; @@ -93,18 +94,16 @@ private void EnsureDetachedExist(CheckpointState checkpoint) if (metaState == null) throw new MissingMetadataException(es.NetEntity); - _entMan.CreateEntityUninitialized(metaState.PrototypeId, uid); - meta = metas.GetComponent(uid); + uid = _entMan.CreateEntity(metaState.PrototypeId, out meta); // Client creates a client-side net entity for the newly created entity. // We need to clear this mapping before assigning the real net id. // TODO NetEntity Jank: prevent the client from creating this in the first place. _entMan.ClearNetEntity(meta.NetEntity); + _entMan.SetNetEntity(uid.Value, es.NetEntity, meta); - _entMan.SetNetEntity(uid, es.NetEntity, meta); - - _entMan.InitializeEntity(uid, meta); - _entMan.StartEntity(uid); + _entMan.InitializeEntity(uid.Value, meta); + _entMan.StartEntity(uid.Value); meta.LastStateApplied = checkpoint.Tick; } } diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index 0e261de1218..7596e65e94b 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -86,7 +86,7 @@ void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity) StartEntity(entity); } - private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null) + internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null) { if (prototypeName == null) return base.CreateEntity(prototypeName, out metadata, context); diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 144e5ea40a5..a540494d157 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -776,7 +776,7 @@ private EntityUid AllocEntity(out MetaDataComponent metadata) /// /// Allocates an entity and loads components but does not do initialization. /// - private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null) + internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null) { if (prototypeName == null) return AllocEntity(out metadata); From 7cd95351c36a47e7aaa6f4adeb17bcf7894f9e34 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Fri, 26 Apr 2024 19:30:34 -0700 Subject: [PATCH 125/130] Remove IP address and HWId from ViewVariables (#5062) * Remove IP address from ViewVariables * Remove HWId from ViewVariables --- Robust.Shared/Network/NetManager.NetChannel.cs | 3 +-- Robust.Shared/Network/NetUserData.cs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Network/NetManager.NetChannel.cs b/Robust.Shared/Network/NetManager.NetChannel.cs index 2bf2cd0b122..ca5dc95baca 100644 --- a/Robust.Shared/Network/NetManager.NetChannel.cs +++ b/Robust.Shared/Network/NetManager.NetChannel.cs @@ -35,7 +35,6 @@ private sealed class NetChannel : INetChannel public bool IsConnected => _connection.Status == NetConnectionStatus.Connected; /// - [ViewVariables] public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint; /// @@ -100,7 +99,7 @@ public void Disconnect(string reason, bool sendBye) public override string ToString() { - return $"{RemoteEndPoint}/{UserId}"; + return $"{ConnectionId}/{UserId}"; } } } diff --git a/Robust.Shared/Network/NetUserData.cs b/Robust.Shared/Network/NetUserData.cs index 27ced6d04d3..b5ee8c33c90 100644 --- a/Robust.Shared/Network/NetUserData.cs +++ b/Robust.Shared/Network/NetUserData.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Text; using Robust.Shared.ViewVariables; namespace Robust.Shared.Network @@ -10,11 +11,13 @@ public sealed record NetUserData { [ViewVariables] public NetUserId UserId { get; } + [ViewVariables] public string UserName { get; } + [ViewVariables] public string? PatronTier { get; init; } - [ViewVariables] + public ImmutableArray HWId { get; init; } public NetUserData(NetUserId userId, string userName) @@ -22,5 +25,18 @@ public NetUserData(NetUserId userId, string userName) UserId = userId; UserName = userName; } + + public sealed override string ToString() + { + var stringBuilder = new StringBuilder(); + stringBuilder.Append("NetUserData"); // type name + stringBuilder.Append(" { "); + if ((this with { HWId = default }).PrintMembers(stringBuilder)) + { + stringBuilder.Append(' '); + } + stringBuilder.Append('}'); + return stringBuilder.ToString(); + } } } From 6e0205d1a80a97122ce66bf09e0feeb3cb034c3d Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 27 Apr 2024 12:33:45 +1000 Subject: [PATCH 126/130] Version: 220.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 5be0e8aca35..fee1965eafa 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 220.0.0 + 220.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 210414121c0..c981f4460e0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,18 @@ END TEMPLATE--> *None yet* +## 220.1.0 + +### Bugfixes + +* Fix client-side replay exceptions due to dropped states when recording. + +### Other + +* Remove IP + HWId from ViewVariables. +* Close BUIs upon disconnect. + + ## 220.0.0 ### Breaking changes From 4033d96327421ef5571c423b941510a0efd844b0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 27 Apr 2024 08:03:35 +0200 Subject: [PATCH 127/130] Engine changes for displacement maps. (#5023) * Add load parameter support to RSIs. Currently only supports turning sRGB off. RSIs with custom load parameters are not thrown into the meta-atlas. As part of this, TextureLoadParameters and TextureSampleParameters has been made to support equality. * Add UV2 channel to vertices. This is a bad hack to make displacement maps work in Robust. The UV2 channel goes from 0 -> 1 across the draw and can therefore be used by displacement maps to map a separate displacement map layer on top of the regular meta-atlas RSI texture. This creates float inaccuracy issues but they weren't bad enough to completely void the feature. I'm thinking I learn from this experience and completely re-do how UVs work with the renderer rewrite, so that hopefully won't happen anymore. This required dumping the optimized PadVerticesV2 because the changed struct size made it impractical. RIP. I don't like this approach at all but the renderer is slated for a rewrite anyways, and all shaders will need to be rewritten regardless. * Add CopyToShaderParameters for sprite layers. This effectively allows copying the parameters of a sprite layer into another layer's shader parameters. The use case is to copy texture coordinates for displacement maps, as the exact map used changes depending on orientation. It also enables animations to be used though I didn't use that personally. --- RELEASE-NOTES.md | 4 +- .../Components/Renderable/SpriteComponent.cs | 85 ++++++++++++++++--- .../Graphics/Clyde/Clyde.Constants.cs | 3 +- Robust.Client/Graphics/Clyde/Clyde.Layout.cs | 17 +++- .../Graphics/Clyde/Clyde.RenderHandle.cs | 13 ++- .../Graphics/Clyde/Clyde.Rendering.cs | 8 +- .../Graphics/Clyde/Clyde.Textures.cs | 2 +- .../Graphics/Clyde/Shaders/base-default.frag | 1 + .../Graphics/Clyde/Shaders/base-default.vert | 5 +- .../Graphics/Clyde/Shaders/base-raw.frag | 1 + .../Graphics/Clyde/Shaders/base-raw.vert | 5 +- .../Graphics/Drawing/DrawingHandleBase.cs | 41 ++------- .../ResourceCache.Preload.cs | 34 ++++++-- .../ResourceTypes/RSIResource.cs | 16 ++-- .../Components/Renderable/SpriteLayerData.cs | 41 +++++++++ .../Graphics/TextureLoadParameters.cs | 30 ++++++- .../Graphics/TextureSampleParameters.cs | 29 ++++++- Robust.Shared/Resources/RsiLoading.cs | 31 ++++--- 18 files changed, 278 insertions(+), 88 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c981f4460e0..da7acdce7b1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,9 @@ END TEMPLATE--> ### New features -*None yet* +* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported. +* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad. +* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers. ### Bugfixes diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f42d3a8afa6..f6467daa075 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Text; using Robust.Client.Graphics; +using Robust.Client.Graphics.Clyde; using Robust.Client.ResourceManagement; using Robust.Client.Utility; using Robust.Shared.Animations; @@ -28,6 +29,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth; using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer; using Direction = Robust.Shared.Maths.Direction; +using Vector4 = Robust.Shared.Maths.Vector4; namespace Robust.Client.GameObjects { @@ -770,15 +772,7 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) { foreach (var keyString in layerDatum.MapKeys) { - object key; - if (reflection.TryParseEnumReference(keyString, out var @enum)) - { - key = @enum; - } - else - { - key = keyString; - } + var key = ParseKey(keyString); if (LayerMap.TryGetValue(key, out var mappedIndex)) { @@ -804,9 +798,30 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) // If neither state: nor texture: were provided we assume that they want a blank invisible layer. layer.Visible = layerDatum.Visible ?? layer.Visible; + if (layerDatum.CopyToShaderParameters is { } copyParameters) + { + layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey)) + { + ParameterTexture = copyParameters.ParameterTexture, + ParameterUV = copyParameters.ParameterUV + }; + } + else + { + layer.CopyToShaderParameters = null; + } + RebuildBounds(); } + private object ParseKey(string keyString) + { + if (reflection.TryParseEnumReference(keyString, out var @enum)) + return @enum; + + return keyString; + } + public void LayerSetData(object layerKey, PrototypeLayerData data) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -1635,6 +1650,9 @@ public Vector2 Offset [ViewVariables] public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy; + [ViewVariables(VVAccess.ReadWrite)] + public CopyToShaderParameters? CopyToShaderParameters; + public Layer(SpriteComponent parent) { _parent = parent; @@ -2007,8 +2025,6 @@ internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, // Set the drawing transform for this layer GetLayerDrawMatrix(dir, out var layerMatrix); - Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix); - drawingHandle.SetTransform(in transformMatrix); // The direction used to draw the sprite can differ from the one that the angle would naively suggest, // due to direction overrides or offsets. @@ -2018,7 +2034,41 @@ internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, // Get the correct directional texture from the state, and draw it! var texture = GetRenderTexture(_actualState, dir); - RenderTexture(drawingHandle, texture); + + if (CopyToShaderParameters == null) + { + // Set the drawing transform for this layer + Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix); + drawingHandle.SetTransform(in transformMatrix); + + RenderTexture(drawingHandle, texture); + } + else + { + // Multiple atrocities to god being committed right here. + var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!]; + var otherLayer = _parent.Layers[otherLayerIdx]; + if (otherLayer.Shader is not { } shader) + { + // No shader set apparently..? + return; + } + + if (!shader.Mutable) + otherLayer.Shader = shader = shader.Duplicate(); + + var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr); + var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr); + + if (CopyToShaderParameters.ParameterTexture is { } paramTexture) + shader.SetParameter(paramTexture, clydeTexture); + + if (CopyToShaderParameters.ParameterUV is { } paramUV) + { + var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top); + shader.SetParameter(paramUV, uv); + } + } } private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture) @@ -2096,6 +2146,17 @@ internal void AdvanceFrameAnimation(RSI.State state) } } + /// + /// Instantiated version of . + /// Has actually resolved to a a real key. + /// + public sealed class CopyToShaderParameters(object layerKey) + { + public object LayerKey = layerKey; + public string? ParameterTexture; + public string? ParameterUV; + } + void IAnimationProperties.SetAnimatableProperty(string name, object value) { if (!name.StartsWith("layer/")) diff --git a/Robust.Client/Graphics/Clyde/Clyde.Constants.cs b/Robust.Client/Graphics/Clyde/Clyde.Constants.cs index 94b1fe3acf6..2ae93219693 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Constants.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Constants.cs @@ -6,7 +6,8 @@ private static readonly (string, uint)[] BaseShaderAttribLocations = { ("aPos", 0), ("tCoord", 1), - ("modulate", 2) + ("tCoord2", 2), + ("modulate", 3) }; private const int UniIModUV = 0; diff --git a/Robust.Client/Graphics/Clyde/Clyde.Layout.cs b/Robust.Client/Graphics/Clyde/Clyde.Layout.cs index 07a0f145f76..7f6ab086b71 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Layout.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Layout.cs @@ -23,9 +23,12 @@ private static unsafe void SetupVAOLayout() // Texture Coords. GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float)); GL.EnableVertexAttribArray(1); - // Colour Modulation. - GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float)); + // Texture Coords (2). + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float)); GL.EnableVertexAttribArray(2); + // Colour Modulation. + GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float)); + GL.EnableVertexAttribArray(3); } // NOTE: This is: @@ -37,6 +40,7 @@ private readonly struct Vertex2D { public readonly Vector2 Position; public readonly Vector2 TextureCoordinates; + public readonly Vector2 TextureCoordinates2; // Note that this color is in linear space. public readonly Color Modulate; @@ -48,6 +52,15 @@ public Vertex2D(Vector2 position, Vector2 textureCoordinates, Color modulate) Modulate = modulate; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate) + { + Position = position; + TextureCoordinates = textureCoordinates; + TextureCoordinates2 = textureCoordinates2; + Modulate = modulate; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a) : this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a)) diff --git a/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs b/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs index b3453a7574c..b51d70c0165 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs @@ -15,7 +15,7 @@ internal partial class Clyde { private RenderHandle _renderHandle = default!; - private sealed class RenderHandle : IRenderHandle + internal sealed class RenderHandle : IRenderHandle { private readonly Clyde _clyde; private readonly IEntityManager _entities; @@ -88,16 +88,21 @@ public void DrawTextureWorld(Texture texture, Vector2 bl, Vector2 br, Vector2 tl { var clydeTexture = ExtractTexture(texture, in subRegion, out var csr); - var (w, h) = clydeTexture.Size; - var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h); + var sr = WorldTextureBoundsToUV(clydeTexture, csr); _clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr); } + internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr) + { + var (w, h) = texture.Size; + return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h); + } + /// /// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas). /// - private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr) + internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr) { if (texture is AtlasTexture atlas) { diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index 8a58541e575..da77262b139 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -578,10 +578,10 @@ private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl // TODO: split batch if necessary. var vIdx = BatchVertexIndex; - BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate); - BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate); - BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate); - BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate); + BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate); + BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate); + BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate); + BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate); BatchVertexIndex += 4; QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx); diff --git a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs index 6b8180917cb..4dc90a723e0 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs @@ -601,7 +601,7 @@ private void FlushTextureDispose() } } - private sealed class ClydeTexture : OwnedTexture + internal sealed class ClydeTexture : OwnedTexture { private readonly Clyde _clyde; public readonly bool IsSrgb; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-default.frag b/Robust.Client/Graphics/Clyde/Shaders/base-default.frag index 15bdb5b1252..a0830f17f4b 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-default.frag +++ b/Robust.Client/Graphics/Clyde/Shaders/base-default.frag @@ -1,4 +1,5 @@ varying highp vec2 UV; +varying highp vec2 UV2; varying highp vec2 Pos; varying highp vec4 VtxModulate; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-default.vert b/Robust.Client/Graphics/Clyde/Shaders/base-default.vert index 28d21c4f137..51ba6649c94 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-default.vert +++ b/Robust.Client/Graphics/Clyde/Shaders/base-default.vert @@ -2,10 +2,12 @@ /*layout (location = 0)*/ attribute vec2 aPos; // Texture coordinates. /*layout (location = 1)*/ attribute vec2 tCoord; +/*layout (location = 2)*/ attribute vec2 tCoord2; // Colour modulation. -/*layout (location = 2)*/ attribute vec4 modulate; +/*layout (location = 3)*/ attribute vec4 modulate; varying vec2 UV; +varying vec2 UV2; varying vec2 Pos; varying vec4 VtxModulate; @@ -36,5 +38,6 @@ void main() gl_Position = vec4(VERTEX, 0.0, 1.0); Pos = (VERTEX + 1.0) / 2.0; UV = mix(modifyUV.xy, modifyUV.zw, tCoord); + UV2 = tCoord2; VtxModulate = zFromSrgb(modulate); } diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag b/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag index e72eaeebbf1..b62afbb8d56 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag +++ b/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag @@ -1,4 +1,5 @@ varying highp vec2 UV; +varying highp vec2 UV2; uniform sampler2D lightMap; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert b/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert index e5b8cf27fbc..11fee7a4f6c 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert +++ b/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert @@ -2,10 +2,12 @@ /*layout (location = 0)*/ attribute vec2 aPos; // Texture coordinates. /*layout (location = 1)*/ attribute vec2 tCoord; +/*layout (location = 2)*/ attribute vec2 tCoord2; // Colour modulation. -/*layout (location = 2)*/ attribute vec4 modulate; +/*layout (location = 3)*/ attribute vec4 modulate; varying vec2 UV; +varying vec2 UV2; // Maybe we should merge these CPU side. // idk yet. @@ -40,6 +42,7 @@ void main() vec2 VERTEX = aPos; UV = tCoord; + UV2 = tCoord2; // [SHADER_CODE] diff --git a/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs b/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs index 525fa741f43..56259861687 100644 --- a/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs +++ b/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs @@ -114,43 +114,12 @@ public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, ReadOnlySpan DrawPrimitives(primitiveTopology, White, indices, drawVertices); } - private static void PadVerticesV2(ReadOnlySpan input, Span output, Color color) + private void PadVerticesV2(ReadOnlySpan input, Span output, Color color) { - if (input.Length == 0) - return; - - if (input.Length != output.Length) - { - throw new InvalidOperationException("Invalid lengths!"); - } - - var colorLinear = Color.FromSrgb(color); - var colorVec = Unsafe.As>(ref colorLinear); - var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f); - var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle(); - - var simdVectors = (nuint)(input.Length / 2); - ref readonly var srcBase = ref Unsafe.As(ref Unsafe.AsRef(in input[0])); - ref var dstBase = ref Unsafe.As(ref output[0]); - - for (nuint i = 0; i < simdVectors; i++) - { - var positions = Vector128.LoadUnsafe(in srcBase, i * 4); - - var posColorLower = (positions & maskVec) | uvVec; - var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec; - - posColorLower.StoreUnsafe(ref dstBase, i * 16); - colorVec.StoreUnsafe(ref dstBase, i * 16 + 4); - posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8); - colorVec.StoreUnsafe(ref dstBase, i * 16 + 12); - } - - var lastPos = (int)simdVectors * 2; - if (lastPos != output.Length) + Color colorLinear = Color.FromSrgb(color); + for (var i = 0; i < output.Length; i++) { - // Odd number of vertices. Handle the last manually. - output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear); + output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear); } } @@ -268,6 +237,8 @@ public struct DrawVertexUV2DColor { public Vector2 Position; public Vector2 UV; + public Vector2 UV2; + /// /// Modulation colour for this vertex. /// Note that this color is in linear space. diff --git a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs index f698171adbf..cc567565ef5 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs @@ -10,6 +10,7 @@ using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; @@ -142,6 +143,26 @@ private void PreloadRsis(ISawmill sawmill) } }); + // Do not meta-atlas RSIs with custom load parameters. + var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray(); + var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray(); + + foreach (var data in nonAtlasList) + { + if (data.Bad) + continue; + + try + { + RSIResource.LoadTexture(Clyde, data); + } + catch (Exception e) + { + sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}"); + data.Bad = true; + } + } + // This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY // lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should // try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own @@ -155,7 +176,7 @@ private void PreloadRsis(ISawmill sawmill) // TODO allow RSIs to opt out (useful for very big & rare RSIs) // TODO combine with (non-rsi) texture atlas? - Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0)); + Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0)); // Each RSI sub atlas has a different size. // Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size. @@ -167,9 +188,9 @@ private void PreloadRsis(ISawmill sawmill) Vector2i offset = default; int finalized = -1; int atlasCount = 0; - for (int i = 0; i < rsiList.Length; i++) + for (int i = 0; i < atlasList.Length; i++) { - var rsi = rsiList[i]; + var rsi = atlasList[i]; if (rsi.Bad) continue; @@ -200,14 +221,14 @@ private void PreloadRsis(ISawmill sawmill) var height = offset.Y + deltaY; var croppedSheet = new Image(maxSize, height); sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default); - FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet); + FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet); void FinalizeMetaAtlas(int toIndex, Image sheet) { var atlas = Clyde.LoadTextureFromImage(sheet); for (int i = finalized + 1; i <= toIndex; i++) { - var rsi = rsiList[i]; + var rsi = atlasList[i]; rsi.AtlasTexture = atlas; } @@ -255,9 +276,10 @@ void FinalizeMetaAtlas(int toIndex, Image sheet) } sawmill.Debug( - "Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}", + "Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}", rsiList.Length, atlasCount, + nonAtlasList.Length, errors, sw.Elapsed); diff --git a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs index f207b583288..7f7b92525ca 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs @@ -40,17 +40,21 @@ public override void Load(IDependencyCollection dependencies, ResPath path) var loadStepData = new LoadStepData {Path = path}; var manager = dependencies.Resolve(); LoadPreTexture(manager, loadStepData); - - loadStepData.AtlasTexture = dependencies.Resolve().LoadTextureFromImage( - loadStepData.AtlasSheet, - loadStepData.Path.ToString()); - + LoadTexture(dependencies.Resolve(), loadStepData); LoadPostTexture(loadStepData); LoadFinish(dependencies.Resolve(), loadStepData); loadStepData.AtlasSheet.Dispose(); } + internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData) + { + loadStepData.AtlasTexture = clyde.LoadTextureFromImage( + loadStepData.AtlasSheet, + loadStepData.Path.ToString(), + loadStepData.LoadParameters); + } + internal static void LoadPreTexture(IResourceManager manager, LoadStepData data) { var manifestPath = data.Path / "meta.json"; @@ -178,6 +182,7 @@ internal static void LoadPreTexture(IResourceManager manager, LoadStepData data) data.FrameSize = frameSize; data.DimX = dimensionX; data.CallbackOffsets = callbackOffsets; + data.LoadParameters = metadata.LoadParameters; } internal static void LoadPostTexture(LoadStepData data) @@ -380,6 +385,7 @@ internal sealed class LoadStepData public Texture AtlasTexture = default!; public Vector2i AtlasOffset; public RSI Rsi = default!; + public TextureLoadParameters LoadParameters; } internal struct StateReg diff --git a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs index 68920261139..531a28a19c3 100644 --- a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs +++ b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs @@ -29,9 +29,50 @@ public sealed partial class PrototypeLayerData [DataField("map")] public HashSet? MapKeys; [DataField("renderingStrategy")] public LayerRenderingStrategy? RenderingStrategy; + /// + /// If set, indicates that this sprite layer should instead be used to copy into shader parameters on another layer. + /// + /// + /// + /// If set, this sprite layer is not rendered. Instead, the "result" of rendering it (exact sprite layer and such) + /// are copied into the shader parameters of another object, + /// specified by the . + /// + /// + /// The specified layer must have a shader set. When it does, the shader's + /// + /// + /// Note that sprite layers are processed in-order, so to avoid 1-frame delays, + /// the layer doing the copying should occur BEFORE the layer being copied into. + /// + /// + [DataField] public PrototypeCopyToShaderParameters? CopyToShaderParameters; + [DataField] public bool Cycle; } +/// +/// Stores parameters for . +/// +[Serializable, NetSerializable, DataDefinition] +public sealed partial class PrototypeCopyToShaderParameters +{ + /// + /// The map key of the layer that will have its shader modified. + /// + [DataField(required: true)] public string LayerKey; + + /// + /// The name of the shader parameter that will receive the actual selected texture. + /// + [DataField] public string? ParameterTexture; + + /// + /// The name of the shader parameter that will receive UVs to select the sprite in . + /// + [DataField] public string? ParameterUV; +} + [Serializable, NetSerializable] public enum LayerRenderingStrategy { diff --git a/Robust.Shared/Graphics/TextureLoadParameters.cs b/Robust.Shared/Graphics/TextureLoadParameters.cs index 2ec1449c73a..b1764dad6c0 100644 --- a/Robust.Shared/Graphics/TextureLoadParameters.cs +++ b/Robust.Shared/Graphics/TextureLoadParameters.cs @@ -1,3 +1,4 @@ +using System; using JetBrains.Annotations; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -8,7 +9,7 @@ namespace Robust.Shared.Graphics; /// Flags for loading of textures. /// [PublicAPI] -public struct TextureLoadParameters +public struct TextureLoadParameters : IEquatable { /// /// The default sampling parameters for the texture. @@ -41,4 +42,29 @@ public static TextureLoadParameters FromYaml(YamlMappingNode yaml) SampleParameters = TextureSampleParameters.Default, Srgb = true }; -} \ No newline at end of file + + public bool Equals(TextureLoadParameters other) + { + return SampleParameters.Equals(other.SampleParameters) && Srgb == other.Srgb; + } + + public override bool Equals(object? obj) + { + return obj is TextureLoadParameters other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(SampleParameters, Srgb); + } + + public static bool operator ==(TextureLoadParameters left, TextureLoadParameters right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureLoadParameters left, TextureLoadParameters right) + { + return !left.Equals(right); + } +} diff --git a/Robust.Shared/Graphics/TextureSampleParameters.cs b/Robust.Shared/Graphics/TextureSampleParameters.cs index 2e3eb3a5e0f..3ae52db8632 100644 --- a/Robust.Shared/Graphics/TextureSampleParameters.cs +++ b/Robust.Shared/Graphics/TextureSampleParameters.cs @@ -12,7 +12,7 @@ namespace Robust.Shared.Graphics; /// with different sampling parameters than the base texture. /// [PublicAPI] -public struct TextureSampleParameters +public struct TextureSampleParameters : IEquatable { // NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment. // See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering @@ -62,4 +62,29 @@ public static TextureSampleParameters FromYaml(YamlMappingNode node) Filter = false, WrapMode = TextureWrapMode.None }; -} \ No newline at end of file + + public bool Equals(TextureSampleParameters other) + { + return Filter == other.Filter && WrapMode == other.WrapMode; + } + + public override bool Equals(object? obj) + { + return obj is TextureSampleParameters other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Filter, (int) WrapMode); + } + + public static bool operator ==(TextureSampleParameters left, TextureSampleParameters right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureSampleParameters left, TextureSampleParameters right) + { + return !left.Equals(right); + } +} diff --git a/Robust.Shared/Resources/RsiLoading.cs b/Robust.Shared/Resources/RsiLoading.cs index 37779d66a3c..66a45396407 100644 --- a/Robust.Shared/Resources/RsiLoading.cs +++ b/Robust.Shared/Resources/RsiLoading.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text.Json; using JetBrains.Annotations; +using Robust.Shared.Graphics; using Robust.Shared.Maths; using Robust.Shared.Utility; @@ -93,7 +94,17 @@ internal static RsiMetadata LoadRsiMetadata(Stream manifestFile) states[stateI] = new StateMetadata(stateName, dirValue, delays); } - return new RsiMetadata(size, states); + var textureParams = TextureLoadParameters.Default; + if (manifestJson.Load is { } load) + { + textureParams = new TextureLoadParameters + { + SampleParameters = TextureSampleParameters.Default, + Srgb = load.Srgb + }; + } + + return new RsiMetadata(size, states, textureParams); } public static void Warmup() @@ -103,16 +114,11 @@ public static void Warmup() JsonSerializer.Deserialize(warmupJson, SerializerOptions); } - internal sealed class RsiMetadata + internal sealed class RsiMetadata(Vector2i size, StateMetadata[] states, TextureLoadParameters loadParameters) { - public readonly Vector2i Size; - public readonly StateMetadata[] States; - - public RsiMetadata(Vector2i size, StateMetadata[] states) - { - Size = size; - States = states; - } + public readonly Vector2i Size = size; + public readonly StateMetadata[] States = states; + public readonly TextureLoadParameters LoadParameters = loadParameters; } internal sealed class StateMetadata @@ -134,10 +140,13 @@ public StateMetadata(string stateId, int dirCount, float[][] delays) // To be directly deserialized. [UsedImplicitly] - private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States); + private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States, RsiJsonLoad? Load); [UsedImplicitly] private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays); + + [UsedImplicitly] + private sealed record RsiJsonLoad(bool Srgb = true); } [Serializable] From 3330d9617728534bfef313201de0327c4e7f0627 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 27 Apr 2024 16:05:51 +1000 Subject: [PATCH 128/130] Version: 220.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index fee1965eafa..d3d045acda2 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 220.1.0 + 220.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index da7acdce7b1..535730bb689 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,9 +39,7 @@ END TEMPLATE--> ### New features -* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported. -* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad. -* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers. +*None yet* ### Bugfixes @@ -56,6 +54,15 @@ END TEMPLATE--> *None yet* +## 220.2.0 + +### New features + +* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported. +* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad. +* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers. + + ## 220.1.0 ### Bugfixes From cee8d427763daf1ed6aa6545e924ea0e096519f1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 28 Apr 2024 04:23:56 +1200 Subject: [PATCH 129/130] Improve MergeImplicitData exception tolerance (#5075) --- .../GameStates/ClientGameStateManager.cs | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 8e66deafdea..472bf2b43a7 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -125,6 +125,8 @@ public sealed class ClientGameStateManager : IClientGameStateManager #endif private bool _resettingPredictedEntities; + private readonly List _brokenEnts = new(); + private readonly List<(EntityUid, NetEntity)> _toStart = new(); /// public void Initialize() @@ -667,7 +669,16 @@ private void MergeImplicitData(IEnumerable createdEntities) foreach (var netEntity in createdEntities) { +#if EXCEPTION_TOLERANCE + if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta)) + { + _sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}"); + continue; + } +#else var (_, meta) = _entityManager.GetEntityData(netEntity); +#endif + var compData = _compDataPool.Get(); _outputData.Add(netEntity, compData); @@ -1157,63 +1168,58 @@ private void Detach(GameTick maxTick, private void InitializeAndStart(Dictionary toCreate) { - var metaQuery = _entityManager.GetEntityQuery(); + _toStart.Clear(); -#if EXCEPTION_TOLERANCE - var brokenEnts = new List(); -#endif using (_prof.Group("Initialize Entity")) { + EntityUid entity = default; foreach (var netEntity in toCreate.Keys) { - var entity = _entityManager.GetEntity(netEntity); -#if EXCEPTION_TOLERANCE try { -#endif - _entities.InitializeEntity(entity, metaQuery.GetComponent(entity)); -#if EXCEPTION_TOLERANCE + (entity, var meta) = _entityManager.GetEntityData(netEntity); + _entities.InitializeEntity(entity, meta); + _toStart.Add((entity, netEntity)); } catch (Exception e) { - _sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}"); + _sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}"); _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); - brokenEnts.Add(entity); - toCreate.Remove(netEntity); - } + _toCreate.Remove(netEntity); + _brokenEnts.Add(entity); +#if !EXCEPTION_TOLERANCE + throw; #endif + } } } using (_prof.Group("Start Entity")) { - foreach (var netEntity in toCreate.Keys) + foreach (var (entity, netEntity) in _toStart) { - var entity = _entityManager.GetEntity(netEntity); -#if EXCEPTION_TOLERANCE try { -#endif - _entities.StartEntity(entity); -#if EXCEPTION_TOLERANCE + _entities.StartEntity(entity); } catch (Exception e) { - _sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}"); + _sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}"); _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); - brokenEnts.Add(entity); - toCreate.Remove(netEntity); - } + _toCreate.Remove(netEntity); + _brokenEnts.Add(entity); +#if !EXCEPTION_TOLERANCE + throw; #endif + } } } -#if EXCEPTION_TOLERANCE - foreach (var entity in brokenEnts) + foreach (var entity in _brokenEnts) { _entityManager.DeleteEntity(entity); } -#endif + _brokenEnts.Clear(); } private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState, From 40a90487043afa8eee4d9ae9d143aa6880bd14c6 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Sat, 27 Apr 2024 11:17:11 -0700 Subject: [PATCH 130/130] Add margin input value order as a comment (#5067) * Add margin input value order as a comment * Make a better comment and move value to remark --- Robust.Client/UserInterface/Control.Layout.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Robust.Client/UserInterface/Control.Layout.cs b/Robust.Client/UserInterface/Control.Layout.cs index 95904dda045..7ae06d795fa 100644 --- a/Robust.Client/UserInterface/Control.Layout.cs +++ b/Robust.Client/UserInterface/Control.Layout.cs @@ -52,6 +52,10 @@ public partial class Control [ViewVariables] public bool IsMeasureValid { get; private set; } [ViewVariables] public bool IsArrangeValid { get; private set; } + /// + /// Controls the amount of empty space in virtual pixels around the control. + /// + /// Values can be provided as "All" or "Horizontal, Vertical" or "Left, Top, Right, Bottom" [ViewVariables] public Thickness Margin {