From 1804b6e983525e36427e85b766a139c42bd17858 Mon Sep 17 00:00:00 2001 From: TNE Date: Thu, 30 Jul 2026 21:47:22 +0200 Subject: [PATCH 1/2] Port spare ID cabinet, port automaticspareidsystem, add unlockspareidcabinet command DCO: https://github.com/DeltaV-Station/Acceleration/issues/1#issuecomment-5073880660 DCO: https://github.com/DeltaV-Station/Acceleration/issues/1#issuecomment-5074161135 Other content reverse engineered, entirely rewritten or licensed under a compatible license. Compared to original, adds two extra functions: ForceUnlock (public API), UnlockCabinet (private helper) Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> --- .../Station/Systems/StationJobsSystem.cs | 19 +- .../_DVA/Cabinet/DVSpareIDSafeComponent.cs | 7 + .../Components/DVAutomaticSpareIdComponent.cs | 95 +++++++ .../Station/Events/PlayerJobAddedEvent.cs | 9 + .../Station/Events/PlayerJobsRemovedEvent.cs | 11 + .../DVAutomaticSpareIdSystem.Commands.cs | 39 +++ .../Systems/DVAutomaticSpareIdSystem.cs | 244 ++++++++++++++++++ Content.Shared/_DVA/CCVars/DCCVars.cs | 26 ++ .../Locale/en-US/_DVA/job/captain-state.ftl | 14 + .../Catalog/Fills/Lockers/heads.yml | 2 +- .../Entities/Stations/nanotrasen.yml | 3 + .../_DVA/Entities/Stations/base.yml | 5 + .../Wallmounts/spare_id_cabinet.yml | 41 +++ .../cabinet-empty-closed.png | Bin 0 -> 827 bytes .../idcard_cabinet.rsi/cabinet-empty-open.png | Bin 0 -> 808 bytes .../cabinet-filled-closed.png | Bin 0 -> 929 bytes .../cabinet-filled-open.png | Bin 0 -> 930 bytes .../Wallmounts/idcard_cabinet.rsi/cabinet.png | Bin 0 -> 778 bytes .../Wallmounts/idcard_cabinet.rsi/card.png | Bin 0 -> 694 bytes .../Wallmounts/idcard_cabinet.rsi/glass-1.png | Bin 0 -> 684 bytes .../Wallmounts/idcard_cabinet.rsi/glass-2.png | Bin 0 -> 792 bytes .../Wallmounts/idcard_cabinet.rsi/glass-3.png | Bin 0 -> 869 bytes .../Wallmounts/idcard_cabinet.rsi/glass-4.png | Bin 0 -> 799 bytes .../idcard_cabinet.rsi/glass-up.png | Bin 0 -> 548 bytes .../Wallmounts/idcard_cabinet.rsi/glass.png | Bin 0 -> 628 bytes .../Wallmounts/idcard_cabinet.rsi/locked.png | Bin 0 -> 521 bytes .../Wallmounts/idcard_cabinet.rsi/meta.json | 53 ++++ .../idcard_cabinet.rsi/unlocked.png | Bin 0 -> 524 bytes 28 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 Content.Server/_DVA/Cabinet/DVSpareIDSafeComponent.cs create mode 100644 Content.Server/_DVA/Station/Components/DVAutomaticSpareIdComponent.cs create mode 100644 Content.Server/_DVA/Station/Events/PlayerJobAddedEvent.cs create mode 100644 Content.Server/_DVA/Station/Events/PlayerJobsRemovedEvent.cs create mode 100644 Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs create mode 100644 Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs create mode 100644 Resources/Locale/en-US/_DVA/job/captain-state.ftl create mode 100644 Resources/Prototypes/_DVA/Entities/Stations/base.yml create mode 100644 Resources/Prototypes/_DVA/Entities/Structures/Wallmounts/spare_id_cabinet.yml create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-closed.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-open.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-filled-closed.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-filled-open.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/card.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-1.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-2.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-3.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-4.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-up.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/locked.png create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/meta.json create mode 100644 Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/unlocked.png diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index 0790ffdf8fb..f7b866beb63 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -14,6 +14,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Content.Server._DVA.Station.Events; // DeltaV - AutomaticSpareIdSystem namespace Content.Server.Station.Systems; @@ -111,6 +112,12 @@ public bool TryAssignJob(EntityUid station, string jobPrototypeId, NetUserId net stationJobs.PlayerJobs.TryAdd(netUserId, new()); stationJobs.PlayerJobs[netUserId].Add(jobPrototypeId); + + // DeltaV START - AutomaticSpareIdSystem: Raise an event when a player takes a job + var jobAddedEvent = new PlayerJobAddedEvent(netUserId, jobPrototypeId); + RaiseLocalEvent(station, ref jobAddedEvent, false); + // DeltaV END + return true; } @@ -207,7 +214,17 @@ public bool TryRemovePlayerJobs(EntityUid station, if (!Resolve(station, ref jobsComponent, false)) return false; - return jobsComponent.PlayerJobs.Remove(userId); + // DeltaV START - AutomaticSpareIdSystem: Raise an event when a player loses jobs + if (jobsComponent.PlayerJobs.Remove(userId, out var jobs)) + { + var jobsRemovedEvent = new PlayerJobsRemovedEvent(userId, jobs); + RaiseLocalEvent(station, ref jobsRemovedEvent, false); + return true; + } + return false; + + // return jobsComponent.PlayerJobs.Remove(userId); + // DeltaV END } /// diff --git a/Content.Server/_DVA/Cabinet/DVSpareIDSafeComponent.cs b/Content.Server/_DVA/Cabinet/DVSpareIDSafeComponent.cs new file mode 100644 index 00000000000..b6e6dc04405 --- /dev/null +++ b/Content.Server/_DVA/Cabinet/DVSpareIDSafeComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._DVA.Cabinet; + +/// +/// Component that marks an entity as a spare ID safe. Is interacted with by DVAutomaticSpareIdSystem to unlock the safe when there is no captain present. +/// +[RegisterComponent] +public sealed partial class DVSpareIDSafeComponent : Component; \ No newline at end of file diff --git a/Content.Server/_DVA/Station/Components/DVAutomaticSpareIdComponent.cs b/Content.Server/_DVA/Station/Components/DVAutomaticSpareIdComponent.cs new file mode 100644 index 00000000000..849babfd07c --- /dev/null +++ b/Content.Server/_DVA/Station/Components/DVAutomaticSpareIdComponent.cs @@ -0,0 +1,95 @@ +using Content.Server._DVA.Station.Systems; +using Content.Shared.Access; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server._DVA.Station.Components; + +public enum AutomaticSpareIdState +{ + RoundStart, + Alerted, + AwaitingUnlock, + Unlocked, + CaptainPresent, + WarOps +} + +[RegisterComponent, Access(typeof(DVAutomaticSpareIdSystem)), AutoGenerateComponentPause] +public sealed partial class DVAutomaticSpareIdComponent : Component +{ + /// + /// The current state of the automatic spare ID system + /// + [DataField] + public AutomaticSpareIdState State = AutomaticSpareIdState.RoundStart; + + /// + /// Timeout before an action is taken if the state doesn't change + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan? Timeout; + + /// + /// The job considered as Captain for the automatic spare ID system + /// + [DataField] + public ProtoId CaptainJob = "Captain"; + + /// + /// The access that the spare ID safe will be extended to have if it is automatically unlocked + /// if there is no captain + /// + [DataField] + public ProtoId GrantAccessToCommand = "Command"; + + /// + /// The access that the spare ID safe will be extended to have if it is automatically unlocked + /// if there is a captain + /// + [DataField] + public ProtoId GrantAccessToCaptain = "Captain"; + + /// + /// Message for when a Captain joins after the system has alerted about their absence + /// + [DataField] + public LocId CaptainPresentAfterAlertsMessage = "captain-arrived-revoke-aco-announcement"; + + /// + /// Message for when the system alerts but isn't going to automatically unlock + /// + [DataField] + public LocId AlertedMessage = "no-captain-request-aco-vote-announcement"; + + /// + /// Message for when the system alerts and will automatically unlock + /// + [DataField] + public LocId AwaitingUnlockMessage = "no-captain-request-aco-vote-with-aa-announcement"; + + /// + /// Message for when the system alerts automatically unlock + /// + [DataField] + public LocId UnlockedMessage = "no-captain-aa-unlocked-announcement"; + + /// + /// The amount of time in which that the spare ID will unlock after nuclear operatives declare war. + /// + [DataField] + public TimeSpan WarOpsUnlockDelay = TimeSpan.FromSeconds(15); + + /// + /// Message that will be displayed to the station when there is no captain and war ops is declared. + /// + [DataField] + public LocId WarOpsUnlockedMessageACO = "spare-id-warops-no-captain"; + + /// + /// Message that will be displayed to the station when there is a captain and war ops is declared. + /// + [DataField] + public LocId WarOpsUnlockedMessageCaptain = "spare-id-warops-captain"; +} diff --git a/Content.Server/_DVA/Station/Events/PlayerJobAddedEvent.cs b/Content.Server/_DVA/Station/Events/PlayerJobAddedEvent.cs new file mode 100644 index 00000000000..56b3786fdd3 --- /dev/null +++ b/Content.Server/_DVA/Station/Events/PlayerJobAddedEvent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Network; + +namespace Content.Server._DVA.Station.Events; + +/// +/// Event is raised when a player takes a job. +/// +[ByRefEvent] +public record struct PlayerJobAddedEvent(NetUserId Player, string JobPrototypeId); \ No newline at end of file diff --git a/Content.Server/_DVA/Station/Events/PlayerJobsRemovedEvent.cs b/Content.Server/_DVA/Station/Events/PlayerJobsRemovedEvent.cs new file mode 100644 index 00000000000..c858d5b083d --- /dev/null +++ b/Content.Server/_DVA/Station/Events/PlayerJobsRemovedEvent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Content.Shared.Roles; + +namespace Content.Server._DVA.Station.Events; + +/// +/// Event is raised when a player loses jobs. +/// +[ByRefEvent] +public record struct PlayerJobsRemovedEvent(NetUserId Player, List> PlayerJobs); \ No newline at end of file diff --git a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs new file mode 100644 index 00000000000..f2ac0098e33 --- /dev/null +++ b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs @@ -0,0 +1,39 @@ +using Content.Shared.Administration; +using Content.Shared.Commands; +using Robust.Shared.Console; +using Content.Server._DVA.Station.Systems; + +namespace Content.Server.Administration.Commands +{ + [AdminCommand(AdminFlags.Spawn)] + public sealed partial class UnlockSpareIdCommand : LocalizedEntityCommands + { + [Dependency] private DVAutomaticSpareIdSystem _automaticSpareIdSystem = default!; + + public override string Command => "unlockspareid"; + public override string Description => Loc.GetString("cmd-unlockspareid-desc"); + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (!CommandHelper.CheckExactlyOneArgument(Loc, shell, args)) + return; + + if (!CommandHelper.ParseArgumentBoolean(Loc, shell, args[0], out var boolean)) + return; + + _automaticSpareIdSystem.ForceUnlock(null, boolean ? "cmd-unlockspareid-announcement" : null); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + CompletionHelper.Booleans, + Loc.GetString("cmd-unlockspareid-arg-state")); + } + + return CompletionResult.Empty; + } + } +} \ No newline at end of file diff --git a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs new file mode 100644 index 00000000000..eb580f4254c --- /dev/null +++ b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs @@ -0,0 +1,244 @@ +using System.Linq; +using Content.Server._DVA.Cabinet; +using Content.Server._DVA.Station.Components; +using Content.Server._DVA.Station.Events; +using Content.Server.Chat.Systems; +using Content.Server.NukeOps; +using Content.Server.Station.Components; +using Content.Shared._DVA.CCVars; +using Content.Shared.Access.Components; +using Content.Shared.Access; +using Content.Shared.NukeOps; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Robust.Shared.Prototypes; + +namespace Content.Server._DVA.Station.Systems; + +public sealed partial class DVAutomaticSpareIdSystem : EntitySystem +{ + [Dependency] private ChatSystem _chat = default!; + [Dependency] private IConfigurationManager _cfg = default!; + [Dependency] private IGameTiming _timing = default!; + + private bool _autoUnlock; + private TimeSpan _alertDelay; + private TimeSpan _unlockDelay; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPlayerJobAdded); + SubscribeLocalEvent(OnPlayerJobsRemoved); + SubscribeLocalEvent(OnWarDeclared); + + Subs.CVar(_cfg, DCCVars.SpareIdAutoUnlock, a => _autoUnlock = a, true); + Subs.CVar(_cfg, DCCVars.SpareIdAlertDelay, a => _alertDelay = a, true); + Subs.CVar(_cfg, DCCVars.SpareIdUnlockDelay, a => _unlockDelay = a, true); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var spareId)) + { + if (spareId.Timeout is { } timeout && _timing.CurTime > timeout) + { + Timeout((station, spareId)); + } + } + } + + private void Timeout(Entity ent) + { + if (ent.Comp.State is AutomaticSpareIdState.RoundStart) + { + if (HasCaptain(ent)) + RoundStartCaptain(ent); + else + RoundStartNoCaptain(ent); + } + else if (ent.Comp.State is AutomaticSpareIdState.AwaitingUnlock) + { + MoveToUnlocked(ent); + } + else if (ent.Comp.State is AutomaticSpareIdState.WarOps) + { + // Default to these, then check if there is a captain + var message = ent.Comp.WarOpsUnlockedMessageACO; + var accessGranted = ent.Comp.GrantAccessToCommand; + if (HasCaptain(ent)) + { + message = ent.Comp.WarOpsUnlockedMessageCaptain; + accessGranted = ent.Comp.GrantAccessToCaptain; + } + ent.Comp.State = AutomaticSpareIdState.AwaitingUnlock; + MoveToUnlocked(ent, accessGranted, message); + } + else + { + DebugTools.Assert($"Spare ID state timed out with unexpected state {ent.Comp.State}"); + } + } + + private void RoundStartNoCaptain(Entity ent) + { + if (_autoUnlock) + MoveToAwaitingUnlock(ent); + else + MoveToAlerted(ent); + } + + private static void RoundStartCaptain(Entity ent) + { + ent.Comp.State = AutomaticSpareIdState.CaptainPresent; + ent.Comp.Timeout = null; + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.Timeout = _timing.CurTime + _alertDelay; + } + + private void OnPlayerJobAdded(Entity ent, ref PlayerJobAddedEvent args) + { + if (args.JobPrototypeId == ent.Comp.CaptainJob) + MoveToCaptainPresent(ent); + } + + private void OnPlayerJobsRemoved(Entity ent, ref PlayerJobsRemovedEvent args) + { + if (!args.PlayerJobs.Contains(ent.Comp.CaptainJob) || HasCaptain(ent)) + return; + + MoveToAlerted(ent); + } + + private void OnWarDeclared(ref WarDeclaredEvent args) + { + if (args.Status == WarConditionStatus.YesWar) + { + foreach (var spareId in EntityQuery()) + { + spareId.Timeout = _timing.CurTime + spareId.WarOpsUnlockDelay; + spareId.State = AutomaticSpareIdState.WarOps; + } + } + } + + private bool HasCaptain(Entity ent) + { + if (!TryComp(ent, out var stationJobs)) + return false; + + return stationJobs.PlayerJobs.Any(playerJobs => playerJobs.Value.Contains(ent.Comp.CaptainJob)); + } + + private void MoveToAwaitingUnlock(Entity ent) + { + DebugTools.Assert(ent.Comp.State is AutomaticSpareIdState.RoundStart, $"Spare ID state has unexpected state {ent.Comp.State} on awaiting to unlock"); + + ent.Comp.State = AutomaticSpareIdState.AwaitingUnlock; + ent.Comp.Timeout = _timing.CurTime + _unlockDelay; + + _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.AwaitingUnlockMessage, ("minutes", _unlockDelay.TotalMinutes)), colorOverride: Color.Gold); + } + + /// + /// Unlocks all spare ID cabinets, giving access to a certain access prototype and displays a message to the station. + /// + /// The station entity that has the . + /// The access to give to the spare ID cabinet. If not specified or null, will default to ent.Comp.GrantAccessToCommand + /// The message to display to the station upon unlocking the spare ID. If not specified or null, will default to ent.Comp.UnlockedMessage + private void MoveToUnlocked(Entity ent, ProtoId? newSpareIdAccess = null, LocId? unlockMessageLocId = null) + { + DebugTools.Assert(ent.Comp.State is AutomaticSpareIdState.AwaitingUnlock, $"Spare ID state has unexpected state {ent.Comp.State} on unlocking"); + + ent.Comp.State = AutomaticSpareIdState.Unlocked; + ent.Comp.Timeout = null; + + UnlockCabinet(ent, newSpareIdAccess); + + if (!unlockMessageLocId.HasValue) + unlockMessageLocId = ent.Comp.UnlockedMessage; // Default message if nothing is specified + + _chat.DispatchStationAnnouncement(ent, Loc.GetString(unlockMessageLocId), colorOverride: Color.Red); + } + + private void MoveToCaptainPresent(Entity ent) + { + if (!(ent.Comp.State is AutomaticSpareIdState.Alerted or AutomaticSpareIdState.AwaitingUnlock or AutomaticSpareIdState.Unlocked)) + { + return; + } + + ent.Comp.State = AutomaticSpareIdState.CaptainPresent; + ent.Comp.Timeout = null; + + _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.CaptainPresentAfterAlertsMessage), colorOverride: Color.Gold); + } + + private void MoveToAlerted(Entity ent) + { + DebugTools.Assert(ent.Comp.State is AutomaticSpareIdState.RoundStart or AutomaticSpareIdState.CaptainPresent, $"Spare ID state has unexpected state {ent.Comp.State} on moving to alerted"); + + ent.Comp.State = AutomaticSpareIdState.Alerted; + ent.Comp.Timeout = null; + + _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.AlertedMessage), colorOverride: Color.Gold); + } + + /// + /// Unlocks all spare ID cabinets, giving access to a certain access prototype. + /// + /// The station entity that has the . + /// The access to give to the spare ID cabinet. If not specified or null, will default to ent.Comp.GrantAccessToCommand + private void UnlockCabinet(Entity ent, ProtoId? newSpareIdAccess = null) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var accessReader)) + { + var accesses = accessReader.AccessLists; + if (accesses.Count <= 0) + continue; + + if (!newSpareIdAccess.HasValue) + newSpareIdAccess = ent.Comp.GrantAccessToCommand; // Default to command if no access is specified + + accesses.Add([newSpareIdAccess.Value]); + Dirty(uid, accessReader); + RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent()); + } + } + + /// + /// Force unlocks the spare ID cabinets and optionally displays an announcement. + /// + /// The access to give to the spare ID cabinet. If not specified or null, will default to ent.Comp.GrantAccessToCommand + /// The message to display to the station upon unlocking the spare ID. If not specified or null, no announcement will be made + public void ForceUnlock(ProtoId? newSpareIdAccess = null, LocId? unlockMessageLocId = null) + { + var query = EntityQueryEnumerator(); + if (!query.MoveNext(out var uid, out var spareId)) + return; + + Entity ent = (uid, spareId); + + UnlockCabinet(ent, newSpareIdAccess); + + // Make sure the state is set to the expected state given the current game state. Prevents errouneous announcements. + if (HasCaptain(ent)) + ent.Comp.State = AutomaticSpareIdState.CaptainPresent; + else + ent.Comp.State = AutomaticSpareIdState.Unlocked; + ent.Comp.Timeout = null; + + if (unlockMessageLocId.HasValue) + _chat.DispatchStationAnnouncement(ent, Loc.GetString(unlockMessageLocId), colorOverride: Color.Red); + } +} diff --git a/Content.Shared/_DVA/CCVars/DCCVars.cs b/Content.Shared/_DVA/CCVars/DCCVars.cs index ffdef39e659..d5d0774e7a3 100644 --- a/Content.Shared/_DVA/CCVars/DCCVars.cs +++ b/Content.Shared/_DVA/CCVars/DCCVars.cs @@ -9,6 +9,32 @@ namespace Content.Shared._DVA.CCVars; // ReSharper disable once InconsistentNaming - Shush you public sealed partial class DCCVars { + /* + * Auto ACO + */ + + /// + /// How long after the announcement before the spare ID is unlocked + /// + public static readonly CVarDef SpareIdUnlockDelay = + CVarDef.Create("game.spare_id.unlock_delay", TimeSpan.FromMinutes(5), CVar.SERVERONLY | CVar.ARCHIVE); + + /// + /// How long to wait before checking for a captain after roundstart + /// + public static readonly CVarDef SpareIdAlertDelay = + CVarDef.Create("game.spare_id.alert_delay", TimeSpan.FromMinutes(15), CVar.SERVERONLY | CVar.ARCHIVE); + + /// + /// Determines if the automatic spare ID process should automatically unlock the cabinet + /// + public static readonly CVarDef SpareIdAutoUnlock = + CVarDef.Create("game.spare_id.auto_unlock", true, CVar.SERVERONLY | CVar.ARCHIVE); + + /* + * Misc. + */ + /// /// The total time a player has to be SSD to be considered cryoable (stage 3). /// Default is 20 minutes. Value should be bigger than . diff --git a/Resources/Locale/en-US/_DVA/job/captain-state.ftl b/Resources/Locale/en-US/_DVA/job/captain-state.ftl new file mode 100644 index 00000000000..0175e89a111 --- /dev/null +++ b/Resources/Locale/en-US/_DVA/job/captain-state.ftl @@ -0,0 +1,14 @@ +# Announcements related to captain presence and ACO state + +captain-arrived-revoke-aco-announcement = The Acting Commanding Officer's position is revoked due to the arrival of a NanoTrasen-appointed captain. All personnel are to return to the standard Chain of Command. +no-captain-request-aco-vote-with-aa-announcement = Station records indicate that no captain is currently present. Command personnel are requested to nominate an Acting Commanding Officer and report the results to Central Command in accordance with Standard Operating Procedure. The spare captain ID cabinet will be unlocked in {$minutes} minutes to ensure continued operational efficiency. +no-captain-request-aco-vote-announcement = Station records indicate that no captain is currently present. Command personnel are requested to nominate an Acting Commanding Officer and report the results to Central Command in accordance with Standard Operating Procedure. +no-captain-aa-unlocked-announcement = Command access authority has been granted to the Spare ID cabinet for use by the Acting Commanding Officer. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. + +spare-id-warops-no-captain = Due to the current circumstances, command has been granted to the Spare ID cabinet for use by the Acting Commanding Officer. Ensure the spare ID remains secure. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. +spare-id-warops-captain = Due to the current circumstances, access has been granted to the Spare ID cabinet for use by the Captain. Ensure the spare ID remains secure. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. + +cmd-unlockspareid-desc = Force unlocks the spare ID cabinet, optionally making an announcement. +cmd-unlockspareid-usage = unlockspareid +cmd-unlockspareid-arg-state = Announcement +cmd-unlockspareid-announcement = Command access authority has been manually granted to the Spare ID cabinet by Central Command. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 46915bad263..c3cd90970c2 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -30,7 +30,7 @@ id: LockerFillCaptainNoLaser table: !type:AllSelector children: - - id: CaptainIDCard + #- id: CaptainIDCard # DeltaV - Replaced by the spare ID system - id: CigarGoldCase prob: 0.25 - id: ClothingBeltSheathFilled diff --git a/Resources/Prototypes/Entities/Stations/nanotrasen.yml b/Resources/Prototypes/Entities/Stations/nanotrasen.yml index 03155c8197d..d2da2afa21f 100644 --- a/Resources/Prototypes/Entities/Stations/nanotrasen.yml +++ b/Resources/Prototypes/Entities/Stations/nanotrasen.yml @@ -26,6 +26,9 @@ - BaseStationAllEventsEligible - BaseStationNanotrasen - BaseStationDeliveries + # Begin DeltaV - Station additions + - BaseStationAutomaticSpareId + # End DeltaV - Station additions categories: [ HideSpawnMenu ] components: - type: Transform diff --git a/Resources/Prototypes/_DVA/Entities/Stations/base.yml b/Resources/Prototypes/_DVA/Entities/Stations/base.yml new file mode 100644 index 00000000000..380013e932b --- /dev/null +++ b/Resources/Prototypes/_DVA/Entities/Stations/base.yml @@ -0,0 +1,5 @@ +- type: entity + id: BaseStationAutomaticSpareId + abstract: true + components: + - type: DVAutomaticSpareId \ No newline at end of file diff --git a/Resources/Prototypes/_DVA/Entities/Structures/Wallmounts/spare_id_cabinet.yml b/Resources/Prototypes/_DVA/Entities/Structures/Wallmounts/spare_id_cabinet.yml new file mode 100644 index 00000000000..4ba512998da --- /dev/null +++ b/Resources/Prototypes/_DVA/Entities/Structures/Wallmounts/spare_id_cabinet.yml @@ -0,0 +1,41 @@ +- type: entity + parent: BaseWallmountCabinetGlass + id: SpareIdCabinet + name: spare id cabinet + description: There is a small label that reads "For authorized personnel only". + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _DVA/Structures/Wallmounts/idcard_cabinet.rsi + layers: + - state: cabinet + - state: card + map: ["enum.ItemCabinetVisuals.Layer"] + visible: true + - state: glass + map: ["enum.OpenableVisuals.Layer"] + - state: locked + shader: unshaded + map: ["enum.LockVisualLayers.Lock"] + - type: ItemSlots + slots: + ItemCabinet: + ejectOnInteract: true + whitelist: + components: + - IdCard + - type: Lock + - type: AccessReader + access: [["CentralCommand"]] + - type: DVSpareIDSafe + +- type: entity + parent: SpareIdCabinet + id: SpareIdCabinetFilled + suffix: Filled + components: + - type: ContainerFill + containers: + ItemCabinet: + - CaptainIDCard \ No newline at end of file diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-closed.png b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-closed.png new file mode 100644 index 0000000000000000000000000000000000000000..e196f37c3f4d7cb421eb24e4ba4529c1a28fae25 GIT binary patch literal 827 zcmV-B1H}A^P)!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}aY;l$R9Fe^R8336KoEV3U`ksd+7>}b z3xYQVL9d?tAO3h3W0I~4Da;bpv;4RZa+39&< zWIy$gg;YRRVZCvhwH&Y2&wlslg6f9y67r3zH3zphm+{g?%j37AOScO5?i#hKgOA=Y zwI@rI0_Y#mdIt8lKS49_-tL3QX~g)T8=uSH$4fMZkFOY&`-aL&1#(~9l;0JyXi+bm!VdDS*002ovPDHLk FV1i*(e~JJA literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-open.png b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/cabinet-empty-open.png new file mode 100644 index 0000000000000000000000000000000000000000..e7a20d2b61b1cc969d6244a4529738f43122f95e GIT binary patch literal 808 zcmV+@1K0eCP)!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}UP(kjR9FdPU>F6XAut*OqaiRF0wX;H z7>Nl0QMLI0#Hd48^ZM38g60!c0J5B$SBT-oqs!>#XD* z9tKVxKCq?7E;2HJ9EA)(mYHe)W%&K~FT?k*pBPSG`pH0yBUq3P!NvypTtV;|&`=qM zM|W?5l?a2#�<7fZV%RA!4EuGGILq9x*WR3QD6YMB_1`@o{p$efx$}4qbv2wM07r z-4bf?@jBr1`=@dX0|*bxsNNipy;K(o{*xTmo)KELMxmI08$3!mj6%{X0#Y%(Y$h)GZ|JutO| mEQhHdrAI?xGz3O=2mk=)-*{i<&J}C`0000!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}*GWV{R9Fe^R830)Q4~ERL828D1x8sy zkOh503swR)$v-|+jfl%^35o0rQsq`$#NS{>M1y8R zjfCD@kgsnqb3|z4fUo}Gtfpg(W$)<-*0v(R2#H9bxwT7!(I+;2p4{?||RSME#J@v*RFl$N>>#Kh2PZ;Wr?flescKg447!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}*hxe|R9Fe^R830)Q4~E!iyEC^5~B~0 z7SadgA}WFwEh_o}t%EidR11j~t)gGhCeWZ3(Xw^8k*I|bgtSQ)A}&PigCqqfhuUb~ zXAj15-psyt;c?%4bM8IIQ}mufk=F*et>FWr+E%35l(Eyn6nC_pbi{jqR2 zG>QAH&KB)CIsF|Zf*jyM_EQg0*uDe2_#ywnEWWC4U^jHeu8Ev%CvwVx^$2r-us}C2 z6=uf*GOkgq9Mv#3af{1X9NP;6Ks4KI-pG-g7x!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}KuJVFR9Fe^RKZHaKoETv4<#i+TLPtu zcxq1`{R97_zvD&xhWSsezKo>XiF8)RQUAr+!Qu3&NW?7si>8J>2!kE z)l%wZa)fDf2h=g_Mvr?Qr6Jy`F_cco#55t_cmTg2KiNX$QOA%9Q^1IblQBXZMrtdX zU=l^%{d)ucA1{rZz@oA`1v%kzp}2;zHgd-MuXn&ZaNZ950>3|Spcli<@c;k-07*qo IM6N<$f<*pjH~;_u literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/card.png b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/card.png new file mode 100644 index 0000000000000000000000000000000000000000..c53c3d276375d3b57261b9e709939f259306fb0a GIT binary patch literal 694 zcmV;n0!jUeP)!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy|>`6pHR9FdPU>F6XAut*OqaiRF0;3@? z(nElelmLh{7X5E5$HQQ*!i!bg$vdAJ&OZ9Wu>9<6qRnN&Y7!Pfkmbv^mV;$_GpB;- z;_2BaDs6zge>aXXFccsDPpl)DP)xwX0r?zcILLBP2!Jq%4H5%e3bp*b&MXFyT4Djw z4geX*q+x~CT#z`(F(6v-N*Y!*xCBXZ0LYNSd_x9MT!S!(4YM2+9Uw=5LVzX!lm`AD z2>K5)97Kcg{}(+dG)xYpmRLs+om!C%1X+;fB#A7k literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-1.png b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/glass-1.png new file mode 100644 index 0000000000000000000000000000000000000000..450de1004bb40bb8534b7de38a94721a5fb87da1 GIT binary patch literal 684 zcmV;d0#p5oP)!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy|;z>k7R9FdPU>F6XAut*OqaiRF0;3@? z(nEle*Z|mf{e>~Q+SeaGqYE*-dJhrH^)fn(A&EtR35ytp5H`!deEegJp%6)6^0spr z4n~rOGl_BlTs0Eo`~UBUnSZmjfrMZ*%=bvj2r|ib0LbLOtiP&&md}RL83b*^qJRa9 z7(pRW+ygBJSttjzJQbwj`Iomd2x`QlfGh_v|6#QST1=LO=x!j{0pOI+!f-g=FY^qt zVciS6Opy7+vPsT{AP0afmypv%vE#*~%M8elAT}GK7&6Kk4S~@R7!83D83F+Dtw#g6 SUu?br0000!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}PDw;TR9FdPU>F6XAut*OqaiRF0;3@? z(nEle*Z|mf{e>||?U#>#Z2$fJugxdKI^*?+&zRbOuw=aY{(eTTm(f{FNlX!<9I)Z& zWFa!3(NpQ^oQ5KS1~a&>;tI-IR<1o z(9%_ZS$~BA>DfR!_0@YUAws}lCIa$cX^@2iqO4*6{xTRdF*9EM^zL^CNCIm436SN` z5Fo}l6g5OS0H%Y5jd2kVKg;2tOh0CTEQE%D1xOwk^i^Vo!3%g7}sb7el&-UHVPlu7^ z@Ue-?1t0@JIRTgkoPZ7p16eL1r;B3li$|9kK#qV35|aa9YDekO5Eu=C(GVEfApijX W8GUUr-KakR0000!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}n@L1LR9FdPU>F6XAut*OqaiRF0;3@? z(nEle*Z|mY^zm#yA=VjRKK`-&_xHaxi2eG*XRvnW-)tFwS%1v{bAGU7y!!rrMy{99 zSz^p4$^q*RJw7KO%9@I7;Kw(=+kUb9sDgPwU_`G`mnL&u1&z6OWSsQ8|iledFK~xCr zyZ*u$q#5Xl*(_{~i$GEE_s{>hV0$@skoekzj~)UYaRcakc!+?+sTKkt!$1IJIS)U} zVPM=JW@coQ16uqGXz`W*4F7k5f?kqKc+2x|Z+$>&CAfszfRsCkjSmnN1uz3ZmV-ip znSt#!Fv!8a1tmZR#{XAAYX1KJ2TBJFVw{2*|Nj4f1Y#2cBsl!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy}RY^oaR9FdPU>F6XAut*OqaiRF0;3@? z(nEle*Z|mY^zrP!fBvU{)HAa%&fybco$>m^X9kvE>}UQn{-5)g_16rrSwC1ZUVVQ* zBiGC5EHP#i<$!gE9-k8sWlaSd2siZOo8Jt-Sbi{sTdIQXS$pu&e-Pu@*Ec3$0U$w) zBUoUH@zG4o3}@fJ`gJDQUJj>&VR9gc{Q38H4%9r5qX!18KJefmF*Xww0zhAF0{YMo zn}KT%Jp>Cs|MHeWl1rH3`M0+)Wj|mHd^8h2Nthfq%R!EU$uls5qo3jL|9@a1F-}2- zfB*kKg3IG!;EYprJs=Ai85wQ<|Ns9FnCjQZ1Sf-Y1Weu1HS;!u912wT>fir=*I#^l zS46c$2(kp2^FAIoUue9a0Q&wVF_xnkFv=MXfzc2c d4S^9G0sv*6fr`KLtvCPx002ovPDHLkV1mHGX}b`6)-Anr(f3mCjysv+Ed4v604lEIg^vExqF85^&gc>vbx%5ytuk|T{4eI$ZW=ac8?;{3fn8@S?qlp(e%B;J;GB@ zV7`F6rR;DcL-H3UJ|xqwO6Ea?9!!+ zwVwT}pSNw=jE}q8Cpo-d{kqPN>0s@GxZ=C_gRifh&)6vb;8xDh(FkHnjHS#zWL|QIJfjbk)9$q=L8cL e6k!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy|s!2paR9FdPU>F6XAut*OqaiRF0;3@? z(nEle*Z|mf{e>~Q+SeaGqYE*-dJhrH^)fn(A&EtR35ytp5H`y(6rl-B-gXYd!Dvdr zT%sI+O(Q!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy|KS@MER9Fe^(6JE!AP56MbuXr8FSVtn zvGA$Vklg(kjsO7y1PBlyK!Ctwf!W>P5vTsH%yw7pkqHnW@Sng1Kvo0-VkdoF00000 LNkvXXu0mjfF*oEM literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/meta.json b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/meta.json new file mode 100644 index 00000000000..920cf6679cb --- /dev/null +++ b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/meta.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Original work by TJohnson.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "cabinet-empty-closed" + }, + { + "name": "cabinet-empty-open" + }, + { + "name": "cabinet-filled-open" + }, + { + "name": "cabinet-filled-closed" + }, + { + "name": "cabinet" + }, + { + "name": "card" + }, + { + "name": "glass" + }, + { + "name": "glass-1" + }, + { + "name": "glass-2" + }, + { + "name": "glass-3" + }, + { + "name": "glass-4" + }, + { + "name": "glass-up" + }, + { + "name": "unlocked" + }, + { + "name": "locked" + } + ] +} diff --git a/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/unlocked.png b/Resources/Textures/_DVA/Structures/Wallmounts/idcard_cabinet.rsi/unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..22d718bb96f264d5de18738954c76e4578fbea42 GIT binary patch literal 524 zcmV+n0`vWeP)!i35h|@JuQxt>)LH z;5&R6qLsje%xr5ZyO=}Q_<5viiieo!)xEA`3=~wG0lq%sS!QvEc%C@D!pk_*UqO$8Um*9)Ahuxy%CK7d@q78!K(hO57lxCXN_-g*DG3ZW2<>l`Q2k z+T*a1K?+GoP*A}pbU27nHfd&^ees5G8D;V?`dX-hlcR_dd84FOsH;j@BVDDXqblb9 zf1b9MnDM%Yl0fgP>wZoG!F`}!bKT!l*R7ucffwM~Z28MHr@3#^Yb`Bu41{-p%bS*_ z8~|6l!059jbGab}X!|t{__zQD^T6;u(7*1@s`(%1Pe5*)p6CuZJOZYuKD_YK$Gl00009a7bBm000XT000XT0n*)m`~Uy|LP;>0APfUQ2(QKP7!0o^ zV5esg+*Hllh` O0000 Date: Fri, 31 Jul 2026 00:53:50 +0200 Subject: [PATCH 2/2] Convert unlockspareid to toolshed -> spareid:unlock Common usage: stations:get | spareid:unlock true --- .../DVAutomaticSpareIdSystem.Commands.cs | 64 ++++++++++--------- .../Systems/DVAutomaticSpareIdSystem.cs | 9 +-- .../Locale/en-US/_DVA/job/captain-state.ftl | 6 +- 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs index f2ac0098e33..6356d0dc10d 100644 --- a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs +++ b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.Commands.cs @@ -1,39 +1,41 @@ -using Content.Shared.Administration; -using Content.Shared.Commands; -using Robust.Shared.Console; +using System.Diagnostics; +using Content.Server._DVA.Station.Components; using Content.Server._DVA.Station.Systems; +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; -namespace Content.Server.Administration.Commands -{ - [AdminCommand(AdminFlags.Spawn)] - public sealed partial class UnlockSpareIdCommand : LocalizedEntityCommands - { - [Dependency] private DVAutomaticSpareIdSystem _automaticSpareIdSystem = default!; +namespace Robust.Shared._DVA.Station.Systems; - public override string Command => "unlockspareid"; - public override string Description => Loc.GetString("cmd-unlockspareid-desc"); +[ToolshedCommand(Name = "spareid"), AdminCommand(AdminFlags.Spawn)] +public sealed partial class DVAutomaticSpareIdSystemCommand : ToolshedCommand +{ + [Dependency] private IEntityManager _entityManager = default!; + private DVAutomaticSpareIdSystem? _automaticSpareIdSystem; - public override void Execute(IConsoleShell shell, string argStr, string[] args) + [CommandImplementation("unlock")] + public void Unlock(IInvocationContext ctx, [PipedArgument] EntityUid stationUid, bool doAnnouncement) + { + _automaticSpareIdSystem ??= GetSys(); + if (!_entityManager.TryGetComponent(stationUid, out var spareId)) { - if (!CommandHelper.CheckExactlyOneArgument(Loc, shell, args)) - return; - - if (!CommandHelper.ParseArgumentBoolean(Loc, shell, args[0], out var boolean)) - return; - - _automaticSpareIdSystem.ForceUnlock(null, boolean ? "cmd-unlockspareid-announcement" : null); + ctx.ReportError(new AutomaticSpareIdSystemMissing()); + return; } + _automaticSpareIdSystem.ForceUnlock((stationUid, spareId), null, doAnnouncement ? "command-spareid-unlock-announcement" : null); + } +} - public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) - { - if (args.Length == 1) - { - return CompletionResult.FromHintOptions( - CompletionHelper.Booleans, - Loc.GetString("cmd-unlockspareid-arg-state")); - } - - return CompletionResult.Empty; - } +public record struct AutomaticSpareIdSystemMissing : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkupOrThrow("This command doesn't function if there is no automatic spare ID system. Common usage: stations:get | spareid:unlock true"); } -} \ No newline at end of file + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs index eb580f4254c..e2eebb22b28 100644 --- a/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs +++ b/Content.Server/_DVA/Station/Systems/DVAutomaticSpareIdSystem.cs @@ -219,16 +219,11 @@ private void UnlockCabinet(Entity ent, ProtoId /// Force unlocks the spare ID cabinets and optionally displays an announcement. /// + /// The station entity that has the . /// The access to give to the spare ID cabinet. If not specified or null, will default to ent.Comp.GrantAccessToCommand /// The message to display to the station upon unlocking the spare ID. If not specified or null, no announcement will be made - public void ForceUnlock(ProtoId? newSpareIdAccess = null, LocId? unlockMessageLocId = null) + public void ForceUnlock(Entity ent, ProtoId? newSpareIdAccess = null, LocId? unlockMessageLocId = null) { - var query = EntityQueryEnumerator(); - if (!query.MoveNext(out var uid, out var spareId)) - return; - - Entity ent = (uid, spareId); - UnlockCabinet(ent, newSpareIdAccess); // Make sure the state is set to the expected state given the current game state. Prevents errouneous announcements. diff --git a/Resources/Locale/en-US/_DVA/job/captain-state.ftl b/Resources/Locale/en-US/_DVA/job/captain-state.ftl index 0175e89a111..1e1091f370d 100644 --- a/Resources/Locale/en-US/_DVA/job/captain-state.ftl +++ b/Resources/Locale/en-US/_DVA/job/captain-state.ftl @@ -8,7 +8,5 @@ no-captain-aa-unlocked-announcement = Command access authority has been granted spare-id-warops-no-captain = Due to the current circumstances, command has been granted to the Spare ID cabinet for use by the Acting Commanding Officer. Ensure the spare ID remains secure. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. spare-id-warops-captain = Due to the current circumstances, access has been granted to the Spare ID cabinet for use by the Captain. Ensure the spare ID remains secure. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. -cmd-unlockspareid-desc = Force unlocks the spare ID cabinet, optionally making an announcement. -cmd-unlockspareid-usage = unlockspareid -cmd-unlockspareid-arg-state = Announcement -cmd-unlockspareid-announcement = Command access authority has been manually granted to the Spare ID cabinet by Central Command. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. \ No newline at end of file +command-description-spareid-unlock = Force unlocks the spare ID cabinet, optionally making an announcement. +command-spareid-unlock-announcement = Command access authority has been manually granted to the Spare ID cabinet by Central Command. Unauthorized possession of the spare captain ID is punishable under Grand Felony Offense [307]: Grand Larceny. \ No newline at end of file