diff --git a/Content.Server/StationEvents/Components/GreytideVirusComponent.cs b/Content.Server/StationEvents/Components/GreytideVirusComponent.cs new file mode 100644 index 000000000000..307f131db11d --- /dev/null +++ b/Content.Server/StationEvents/Components/GreytideVirusComponent.cs @@ -0,0 +1,38 @@ +using Content.Server.StationEvents.Events; +using Content.Shared.Access; +using Content.Shared.Destructible.Thresholds; +using Robust.Shared.Prototypes; + +namespace Content.Server.StationEvents.Components; + +/// +/// Greytide Virus event specific configuration +/// +[RegisterComponent, Access(typeof(GreytideVirusRule))] +public sealed partial class GreytideVirusRuleComponent : Component +{ + /// + /// Range from which the severity is randomly picked from. + /// + [DataField] + public MinMax SeverityRange = new(1, 3); + + /// + /// Severity corresponding to the number of access groups affected. + /// Will pick randomly from the SeverityRange if not specified. + /// + [DataField] + public int? Severity; + + /// + /// Access groups to pick from. + /// + [DataField] + public List> AccessGroups = new(); + + /// + /// Entities with this access level will be ignored. + /// + [DataField] + public List> Blacklist = new(); +} diff --git a/Content.Server/StationEvents/Events/GreytideVirusRule.cs b/Content.Server/StationEvents/Events/GreytideVirusRule.cs new file mode 100644 index 000000000000..f60d80ba9c57 --- /dev/null +++ b/Content.Server/StationEvents/Events/GreytideVirusRule.cs @@ -0,0 +1,96 @@ +using Content.Server.StationEvents.Components; +using Content.Shared.Access; +using Content.Shared.Access.Systems; +using Content.Shared.Access.Components; +using Content.Shared.Doors.Components; +using Content.Shared.Doors.Systems; +using Content.Shared.Lock; +using Content.Shared.GameTicking.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + + +/// +/// Greytide Virus event +/// This will open and bolt airlocks and unlock lockers from randomly selected access groups. +/// +public sealed class GreytideVirusRule : StationEventSystem +{ + [Dependency] private readonly AccessReaderSystem _access = default!; + [Dependency] private readonly SharedDoorSystem _door = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Added(EntityUid uid, GreytideVirusRuleComponent virusComp, GameRuleComponent gameRule, GameRuleAddedEvent args) + { + if (!TryComp(uid, out var stationEvent)) + return; + + // pick severity randomly from range if not specified otherwise + virusComp.Severity ??= virusComp.SeverityRange.Next(_random); + virusComp.Severity = Math.Min(virusComp.Severity.Value, virusComp.AccessGroups.Count); + + stationEvent.StartAnnouncement = Loc.GetString("station-event-greytide-virus-start-announcement", ("severity", virusComp.Severity.Value)); + base.Added(uid, virusComp, gameRule, args); + } + protected override void Started(EntityUid uid, GreytideVirusRuleComponent virusComp, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, virusComp, gameRule, args); + + if (virusComp.Severity == null) + return; + + // pick random access groups + var chosen = _random.GetItems(virusComp.AccessGroups, virusComp.Severity.Value, allowDuplicates: false); + + // combine all the selected access groups + var accessIds = new HashSet>(); + foreach (var group in chosen) + { + if (_prototype.TryIndex(group, out var proto)) + accessIds.UnionWith(proto.Tags); + } + + var firelockQuery = GetEntityQuery(); + var accessQuery = GetEntityQuery(); + + var lockQuery = AllEntityQuery(); + while (lockQuery.MoveNext(out var lockUid, out var lockComp)) + { + if (!accessQuery.TryComp(lockUid, out var accessComp)) + continue; + + // check access + // the AreAccessTagsAllowed function is a little weird because it technically has support for certain tags to be locked out of opening something + // which might have unintened side effects (see the comments in the function itself) + // but no one uses that yet, so it is fine for now + if (!_access.AreAccessTagsAllowed(accessIds, accessComp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessComp)) + continue; + + // open lockers + _lock.Unlock(lockUid, null, lockComp); + } + + var airlockQuery = AllEntityQuery(); + while (airlockQuery.MoveNext(out var airlockUid, out var airlockComp, out var doorComp)) + { + // don't space everything + if (firelockQuery.HasComp(airlockUid)) + continue; + + // use the access reader from the door electronics if they exist + if (!_access.GetMainAccessReader(airlockUid, out var accessComp)) + continue; + + // check access + if (!_access.AreAccessTagsAllowed(accessIds, accessComp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessComp)) + continue; + + // open and bolt airlocks + _door.TryOpenAndBolt(airlockUid, doorComp, airlockComp); + } + } +} diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 835adb31c05b..69905d1bd6b6 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -396,6 +396,25 @@ public void OnPartialOpen(EntityUid uid, DoorComponent? door = null) Dirty(uid, door); } + + /// + /// Opens and then bolts a door. + /// Different from emagging this does not remove the access reader, so it can be repaired by simply unbolting the door. + /// + public bool TryOpenAndBolt(EntityUid uid, DoorComponent? door = null, AirlockComponent? airlock = null) + { + if (!Resolve(uid, ref door, ref airlock)) + return false; + + if (IsBolted(uid) || !airlock.Powered || door.State != DoorState.Closed) + { + return false; + } + + SetState(uid, DoorState.Emagging, door); + + return true; + } #endregion #region Closing diff --git a/Resources/Locale/en-US/station-events/events/greytide-virus.ftl b/Resources/Locale/en-US/station-events/events/greytide-virus.ftl new file mode 100644 index 000000000000..7e6f5e32ca6b --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/greytide-virus.ftl @@ -0,0 +1 @@ +station-event-greytide-virus-start-announcement = Gr3y.T1d3 virus detected in the station's secure locking encryption subroutines. Severity level of { $severity }. Recommend station AI involvement. diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 08218accede4..98b6690ebb42 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -10,6 +10,7 @@ - id: ClericalError - id: CockroachMigration - id: GasLeak + - id: GreytideVirus - id: IonStorm # its calm like 90% of the time smh - id: KudzuGrowth - id: MassHallucinations @@ -540,3 +541,24 @@ maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it weight: 5 - type: MobReplacementRule + +- type: entity + id: GreytideVirus + parent: BaseStationEventShortDelay + components: + - type: StationEvent + startAudio: + path: /Audio/Announcements/attention.ogg + weight: 5 + minimumPlayers: 25 + reoccurrenceDelay: 20 + - type: GreytideVirusRule + accessGroups: + - Cargo + - Command + - Engineering + - Research + - Security + - Service + blacklist: + - External # don't space everything