diff --git a/Content.Client/Access/AccessOverlay.cs b/Content.Client/Access/AccessOverlay.cs new file mode 100644 index 0000000000..74273d4102 --- /dev/null +++ b/Content.Client/Access/AccessOverlay.cs @@ -0,0 +1,79 @@ +using System.Text; +using Content.Client.Resources; +using Content.Shared.Access.Components; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; + +namespace Content.Client.Access; + +public sealed class AccessOverlay : Overlay +{ + private readonly IEntityManager _entityManager; + private readonly EntityLookupSystem _lookup; + private readonly Font _font; + + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + + public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup) + { + _entityManager = entManager; + _lookup = lookup; + + _font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.ViewportControl == null) + return; + + var readerQuery = _entityManager.GetEntityQuery(); + var xformQuery = _entityManager.GetEntityQuery(); + + foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB, + LookupFlags.Anchored | LookupFlags.Approximate)) + { + if (!readerQuery.TryGetComponent(ent, out var reader) || + !xformQuery.TryGetComponent(ent, out var xform)) + { + continue; + } + + var text = new StringBuilder(); + var index = 0; + var a = $"{_entityManager.ToPrettyString(ent)}"; + text.Append(a); + + foreach (var list in reader.AccessLists) + { + a = $"Tag {index}"; + text.AppendLine(a); + + foreach (var entry in list) + { + a = $"- {entry}"; + text.AppendLine(a); + } + + index++; + } + + string textStr; + + if (text.Length >= 2) + { + textStr = text.ToString(); + textStr = textStr[..^2]; + } + else + { + textStr = ""; + } + + var screenPos = args.ViewportControl.WorldToScreen(xform.WorldPosition); + + args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold); + } + } +} diff --git a/Content.Client/Access/AccessSystem.cs b/Content.Client/Access/AccessSystem.cs new file mode 100644 index 0000000000..a5680ab937 --- /dev/null +++ b/Content.Client/Access/AccessSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Access.Systems; + +namespace Content.Client.Access; + +public sealed class AccessSystem : SharedAccessSystem {} diff --git a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs new file mode 100644 index 0000000000..fa26df1944 --- /dev/null +++ b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs @@ -0,0 +1,33 @@ +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Console; + +namespace Content.Client.Access.Commands; + +public sealed class ShowAccessReadersCommand : IConsoleCommand +{ + public string Command => "showaccessreaders"; + public string Description => "Shows all access readers in the viewport"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var collection = IoCManager.Instance; + + if (collection == null) return; + + var overlay = collection.Resolve(); + + if (overlay.RemoveOverlay()) + { + shell.WriteLine($"Set access reader debug overlay to false"); + return; + } + + var entManager = collection.Resolve(); + var cache = collection.Resolve(); + var system = entManager.EntitySysManager.GetEntitySystem(); + + overlay.AddOverlay(new AccessOverlay(entManager, cache, system)); + shell.WriteLine($"Set access reader debug overlay to true"); + } +} diff --git a/Content.Server/Access/Systems/AccessSystem.cs b/Content.Server/Access/Systems/AccessSystem.cs new file mode 100644 index 0000000000..8a6ad24411 --- /dev/null +++ b/Content.Server/Access/Systems/AccessSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Access.Systems; + +namespace Content.Server.Access.Systems; + +public sealed class AccessSystem : SharedAccessSystem +{ + +} diff --git a/Content.Server/Access/Systems/PresetIdCardSystem.cs b/Content.Server/Access/Systems/PresetIdCardSystem.cs index 39c65ace86..f253d0e064 100644 --- a/Content.Server/Access/Systems/PresetIdCardSystem.cs +++ b/Content.Server/Access/Systems/PresetIdCardSystem.cs @@ -12,7 +12,7 @@ namespace Content.Server.Access.Systems { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!; - [Dependency] private readonly AccessSystem _accessSystem = default!; + [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!; public override void Initialize() diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index 8536da79cf..c866a8bdd4 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -40,7 +40,7 @@ public sealed partial class AdminVerbSystem { [Dependency] private readonly AirlockSystem _airlockSystem = default!; [Dependency] private readonly StackSystem _stackSystem = default!; - [Dependency] private readonly AccessSystem _accessSystem = default!; + [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly QuickDialogSystem _quickDialog = default!; [Dependency] private readonly AdminTestArenaSystem _adminTestArenaSystem = default!; diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 7f93902a6d..baa773e269 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -5,7 +5,6 @@ using Content.Server.Maps; using Content.Server.Mind; using Content.Server.Players; using Content.Shared.CCVar; -using Content.Shared.Coordinates; using Content.Shared.GameTicking; using Content.Shared.Preferences; using JetBrains.Annotations; @@ -20,7 +19,6 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using System.Threading.Tasks; -using Robust.Shared.Players; namespace Content.Server.GameTicking { diff --git a/Content.Server/Sandbox/SandboxSystem.cs b/Content.Server/Sandbox/SandboxSystem.cs index 08ce673698..7f7f3eac0f 100644 --- a/Content.Server/Sandbox/SandboxSystem.cs +++ b/Content.Server/Sandbox/SandboxSystem.cs @@ -23,7 +23,7 @@ namespace Content.Server.Sandbox [Dependency] private readonly IPlacementManager _placementManager = default!; [Dependency] private readonly IConGroupController _conGroupController = default!; [Dependency] private readonly IServerConsoleHost _host = default!; - [Dependency] private readonly AccessSystem _access = default!; + [Dependency] private readonly SharedAccessSystem _access = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly GameTicker _ticker = default!; diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 2de0866f3d..e068797def 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -38,7 +38,7 @@ public sealed class StationSpawningSystem : EntitySystem [Dependency] private readonly IdCardSystem _cardSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly PDASystem _pdaSystem = default!; - [Dependency] private readonly AccessSystem _accessSystem = default!; + [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; /// diff --git a/Content.Shared/Access/Components/AccessComponent.cs b/Content.Shared/Access/Components/AccessComponent.cs index 8cd5146ed8..cc971d713d 100644 --- a/Content.Shared/Access/Components/AccessComponent.cs +++ b/Content.Shared/Access/Components/AccessComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Access.Systems; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; namespace Content.Shared.Access.Components @@ -6,12 +7,12 @@ namespace Content.Shared.Access.Components /// /// Simple mutable access provider found on ID cards and such. /// - [RegisterComponent] - [Access(typeof(AccessSystem))] + [RegisterComponent, NetworkedComponent] + [Access(typeof(SharedAccessSystem))] public sealed class AccessComponent : Component { [DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - [Access(typeof(AccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends + [Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends public HashSet Tags = new(); /// diff --git a/Content.Shared/Access/Systems/AccessSystem.cs b/Content.Shared/Access/Systems/SharedAccessSystem.cs similarity index 68% rename from Content.Shared/Access/Systems/AccessSystem.cs rename to Content.Shared/Access/Systems/SharedAccessSystem.cs index 56e2fcc031..872e1190df 100644 --- a/Content.Shared/Access/Systems/AccessSystem.cs +++ b/Content.Shared/Access/Systems/SharedAccessSystem.cs @@ -1,10 +1,12 @@ using Content.Shared.Access.Components; using Content.Shared.Roles; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Access.Systems { - public sealed class AccessSystem : EntitySystem + public abstract class SharedAccessSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -13,6 +15,28 @@ namespace Content.Shared.Access.Systems base.Initialize(); SubscribeLocalEvent(OnAccessInit); + SubscribeLocalEvent(OnAccessGetState); + SubscribeLocalEvent(OnAccessHandleState); + } + + private void OnAccessHandleState(EntityUid uid, AccessComponent component, ref ComponentHandleState args) + { + if (args.Current is not AccessComponentState state) return; + + // Don't do = because prediction and refs + component.Tags.Clear(); + component.Groups.Clear(); + component.Tags.UnionWith(state.Tags); + component.Groups.UnionWith(state.Groups); + } + + private void OnAccessGetState(EntityUid uid, AccessComponent component, ref ComponentGetState args) + { + args.State = new AccessComponentState() + { + Tags = component.Tags, + Groups = component.Groups, + }; } private void OnAccessInit(EntityUid uid, AccessComponent component, MapInitEvent args) @@ -38,6 +62,7 @@ namespace Content.Shared.Access.Systems access.Tags.Clear(); access.Tags.UnionWith(newTags); + Dirty(access); return true; } @@ -55,6 +80,7 @@ namespace Content.Shared.Access.Systems access.Tags.UnionWith(proto.Tags); } + Dirty(access); return true; } @@ -85,5 +111,12 @@ namespace Content.Shared.Access.Systems TryAddGroups(uid, prototype.ExtendedAccessGroups, access); } } + + [Serializable, NetSerializable] + private sealed class AccessComponentState : ComponentState + { + public HashSet Tags = new(); + public HashSet Groups = new(); + } } }