Spare ID always unlocks during War Ops (#5104)

* Spare ID will now unlock during war ops

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Increased to 15 seconds.

* summary docs fixes

* Added some docs to MoveToUnlocked. Reworked things a tiny bit.

* Added some docs to MoveToUnlocked. Reworked things a tiny bit.

* Buh

* Remove blank lines that seem to have come from nowhere

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tobias Berger <toby@tobot.dev>
This commit is contained in:
Vanessa 2026-01-11 09:40:02 -06:00 committed by GitHub
parent eecaeb9e59
commit 210b8a4d53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 72 additions and 4 deletions

View File

@ -13,6 +13,7 @@ public enum AutomaticSpareIdState
AwaitingUnlock, AwaitingUnlock,
Unlocked, Unlocked,
CaptainPresent, CaptainPresent,
WarOps
} }
[RegisterComponent, Access(typeof(AutomaticSpareIdSystem)), AutoGenerateComponentPause] [RegisterComponent, Access(typeof(AutomaticSpareIdSystem)), AutoGenerateComponentPause]
@ -38,9 +39,17 @@ public sealed partial class AutomaticSpareIdComponent : Component
/// <summary> /// <summary>
/// The access that the spare ID safe will be extended to have if it is automatically unlocked /// The access that the spare ID safe will be extended to have if it is automatically unlocked
/// if there is no captain
/// </summary> /// </summary>
[DataField] [DataField]
public ProtoId<AccessLevelPrototype> GrantAccessTo = "Command"; public ProtoId<AccessLevelPrototype> GrantAccessToCommand = "Command";
/// <summary>
/// The access that the spare ID safe will be extended to have if it is automatically unlocked
/// if there is a captain
/// </summary>
[DataField]
public ProtoId<AccessLevelPrototype> GrantAccessToCaptain = "Captain";
/// <summary> /// <summary>
/// Message for when a Captain joins after the system has alerted about their absence /// Message for when a Captain joins after the system has alerted about their absence
@ -65,4 +74,19 @@ public sealed partial class AutomaticSpareIdComponent : Component
/// </summary> /// </summary>
[DataField] [DataField]
public LocId UnlockedMessage = "no-captain-aa-unlocked-announcement"; public LocId UnlockedMessage = "no-captain-aa-unlocked-announcement";
/// <summary>
/// The amount of time in which that the spare ID will unlock after nuclear operatives declare war.
/// </summary>
public TimeSpan WarOpsUnlockDelay = TimeSpan.FromSeconds(15);
/// <summary>
/// Message that will be displayed to the station when there is no captain and war ops is declared.
/// </summary>
public LocId WarOpsUnlockedMessageACO = "spare-id-warops-no-captain";
/// <summary>
/// Message that will be displayed to the station when there is a captain and war ops is declared.
/// </summary>
public LocId WarOpsUnlockedMessageCaptain = "spare-id-warops-captain";
} }

View File

@ -3,13 +3,16 @@ using Content.Server._DV.Cabinet;
using Content.Server._DV.Station.Components; using Content.Server._DV.Station.Components;
using Content.Server._DV.Station.Events; using Content.Server._DV.Station.Events;
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.NukeOps;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Shared._DV.CCVars; using Content.Shared._DV.CCVars;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access; using Content.Shared.Access;
using Content.Shared.NukeOps;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.Station.Systems; namespace Content.Server._DV.Station.Systems;
@ -30,6 +33,7 @@ public sealed class AutomaticSpareIdSystem : EntitySystem
SubscribeLocalEvent<AutomaticSpareIdComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<AutomaticSpareIdComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AutomaticSpareIdComponent, PlayerJobAddedEvent>(OnPlayerJobAdded); SubscribeLocalEvent<AutomaticSpareIdComponent, PlayerJobAddedEvent>(OnPlayerJobAdded);
SubscribeLocalEvent<AutomaticSpareIdComponent, PlayerJobsRemovedEvent>(OnPlayerJobsRemoved); SubscribeLocalEvent<AutomaticSpareIdComponent, PlayerJobsRemovedEvent>(OnPlayerJobsRemoved);
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
Subs.CVar(_cfg, DCCVars.SpareIdAutoUnlock, a => _autoUnlock = a, true); Subs.CVar(_cfg, DCCVars.SpareIdAutoUnlock, a => _autoUnlock = a, true);
Subs.CVar(_cfg, DCCVars.SpareIdAlertDelay, a => _alertDelay = a, true); Subs.CVar(_cfg, DCCVars.SpareIdAlertDelay, a => _alertDelay = a, true);
@ -63,6 +67,19 @@ public sealed class AutomaticSpareIdSystem : EntitySystem
{ {
MoveToUnlocked(ent); 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 else
{ {
DebugTools.Assert($"Spare ID state timed out with unexpected state {ent.Comp.State}"); DebugTools.Assert($"Spare ID state timed out with unexpected state {ent.Comp.State}");
@ -102,6 +119,18 @@ public sealed class AutomaticSpareIdSystem : EntitySystem
MoveToAlerted(ent); MoveToAlerted(ent);
} }
private void OnWarDeclared(ref WarDeclaredEvent args)
{
if (args.Status == WarConditionStatus.YesWar)
{
foreach (var spareId in EntityQuery<AutomaticSpareIdComponent>())
{
spareId.Timeout = _timing.CurTime + spareId.WarOpsUnlockDelay;
spareId.State = AutomaticSpareIdState.WarOps;
}
}
}
private bool HasCaptain(Entity<AutomaticSpareIdComponent> ent) private bool HasCaptain(Entity<AutomaticSpareIdComponent> ent)
{ {
if (!TryComp<StationJobsComponent>(ent, out var stationJobs)) if (!TryComp<StationJobsComponent>(ent, out var stationJobs))
@ -120,7 +149,13 @@ public sealed class AutomaticSpareIdSystem : EntitySystem
_chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.AwaitingUnlockMessage, ("minutes", _unlockDelay.TotalMinutes)), colorOverride: Color.Gold); _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.AwaitingUnlockMessage, ("minutes", _unlockDelay.TotalMinutes)), colorOverride: Color.Gold);
} }
private void MoveToUnlocked(Entity<AutomaticSpareIdComponent> ent) /// <summary>
/// Unlocks all spare ID cabinets, giving access to a certain access prototype and displays a message to the station.
/// </summary>
/// <param name="ent">The station entity that has the <see cref="AutomaticSpareIdComponent"/>.</param>
/// <param name="newSpareIdAccess">The access to give to the spare ID cabinet. If not specified or null, will default to ent.Comp.GrantAccessToCommand</param>
/// <param name="unlockMessageLocId">The message to display to the station upon unlocking the spare ID. If not specified or null, will default to ent.Comp.UnlockedMessage</param>
private void MoveToUnlocked(Entity<AutomaticSpareIdComponent> ent, ProtoId<AccessLevelPrototype>? newSpareIdAccess = null, LocId? unlockMessageLocId = null)
{ {
DebugTools.Assert(ent.Comp.State is AutomaticSpareIdState.AwaitingUnlock, $"Spare ID state has unexpected state {ent.Comp.State} on unlocking"); DebugTools.Assert(ent.Comp.State is AutomaticSpareIdState.AwaitingUnlock, $"Spare ID state has unexpected state {ent.Comp.State} on unlocking");
@ -134,12 +169,18 @@ public sealed class AutomaticSpareIdSystem : EntitySystem
if (accesses.Count <= 0) if (accesses.Count <= 0)
continue; continue;
accesses.Add([ent.Comp.GrantAccessTo]); if (!newSpareIdAccess.HasValue)
newSpareIdAccess = ent.Comp.GrantAccessToCommand; // Default to command if no access is specified
accesses.Add([newSpareIdAccess.Value]);
Dirty(uid, accessReader); Dirty(uid, accessReader);
RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent()); RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent());
} }
_chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.UnlockedMessage), colorOverride: Color.Red); 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<AutomaticSpareIdComponent> ent) private void MoveToCaptainPresent(Entity<AutomaticSpareIdComponent> ent)

View File

@ -4,3 +4,6 @@ captain-arrived-revoke-aco-announcement = The Acting Commanding Officer's positi
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. Emergency AA will be unlocked in {$minutes} minutes to ensure continued operational efficiency. 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. Emergency AA 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-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 Emergency AA is punishable under Grand Felony Offense [307]: Grand Larceny. 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 Emergency AA 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 Emergency AA 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 Emergency AA is punishable under Grand Felony Offense [307]: Grand Larceny.