Рефактор системы автодоступов (#2237)

This commit is contained in:
ThereDrD 2025-05-29 10:28:57 +03:00 committed by GitHub
parent ed936e8d99
commit 66f4bd1d75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 202 additions and 198 deletions

View file

@ -1,126 +1,7 @@
using Content.Shared.Access.Systems;
using Content.Shared.Access.Components;
using Content.Server.Station.Systems;
using Content.Server.AlertLevel;
using Content.Shared.Station.Components;
using Timer = Robust.Shared.Timing.Timer;
using Content.Server.Chat.Systems;
using System.Threading;
namespace Content.Server.Access.Systems; //Sunrise-edited
namespace Content.Server.Access.Systems;
public sealed class AccessSystem : SharedAccessSystem
{
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AlertAccessesEvent>(OnAlertLevelChanged);
}
private readonly CancellationTokenSource? _timerCancel;
/// <summary>
/// Запускает таймер и выводит объявление о смене доступов через 1 минуту
/// </summary>
private void OnAlertLevelChanged(AlertAccessesEvent ev)
{
if (!TryComp<AlertLevelComponent>(ev.Station, out var alert))
return;
if (alert.AlertLevels == null)
return;
var levels = new Dictionary<string, string>
{
{ "green", "access-system-accesses-delay-green" },
{ "blue", "access-system-accesses-delay-blue" },
{ "red", "access-system-accesses-delay-red" },
{ "yellow", "access-system-accesses-delay-yellow" },
{ "gamma", "access-system-accesses-delay-gamma" },
};
foreach (var announce in levels)
{
if (alert.CurrentLevel.Contains(announce.Key))
{
Timer.Spawn(TimeSpan.FromMinutes(1), () => AlertAccessesDelay(ev), _timerCancel?.Token ?? default);
_chatSystem.DispatchStationAnnouncement(ev.Station,
Loc.GetString(announce.Value),
playDefault: true,
colorOverride: Color.Yellow,
sender: Loc.GetString("access-system-sender"));
}
}
if (alert.CurrentLevel == "delta")
AlertAccessesDelay(ev);
}
/// <summary>
/// Устанавливает доступы спустя 1 минуту после начала таймера
/// </summary>
private void AlertAccessesDelay(AlertAccessesEvent ev)
{
_chatSystem.DispatchStationAnnouncement(ev.Station,
Loc.GetString("access-system-accesses-established"),
playDefault: true,
colorOverride: Color.Yellow,
sender: Loc.GetString("access-system-sender"));
var query = EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var reader, out var xform))
{
if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station != ev.Station)
continue;
if (!TryComp<AccessReaderComponent>(uid, out var comp))
return;
if (comp.AlertAccesses.Count == 0)
continue;
Update((uid, reader));
Dirty(uid, reader);
_timerCancel?.Cancel();
}
}
/// <summary>
/// Устанавливает значение из прототипа в зависимости от кода
/// </summary>
public void Update(Entity<AccessReaderComponent> entity)
{
if (!TryComp<AlertLevelComponent>(_station.GetOwningStation(entity.Owner), out var alerts))
return;
if (alerts.AlertLevels == null)
return;
var alertLevels = new Dictionary<string, AccessReaderComponent.CurrentAlertLevel>
{
{ "blue", AccessReaderComponent.CurrentAlertLevel.blue },
{ "red", AccessReaderComponent.CurrentAlertLevel.red },
{ "yellow", AccessReaderComponent.CurrentAlertLevel.yellow },
{ "gamma", AccessReaderComponent.CurrentAlertLevel.gamma },
{ "delta", AccessReaderComponent.CurrentAlertLevel.delta }
};
entity.Comp.Group = string.Empty; // Значение по умолчанию
foreach (var level in alertLevels)
{
if (alerts.CurrentLevel.Contains(level.Key))
{
if (entity.Comp.AlertAccesses.TryGetValue(level.Value, out var value))
{
entity.Comp.Group = value;
break;
}
}
}
}
}

View file

@ -1,3 +1,4 @@
using Content.Server._Sunrise.ExtendedAccess;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@ -72,8 +73,11 @@ public sealed partial class AlertLevelDetail
/// </summary>
[DataField("shuttleTime")] public TimeSpan ShuttleTime { get; private set; } = TimeSpan.FromMinutes(5);
// Sunrise-Start
[DataField("forceEndRound")] public bool ForceEndRound { get; private set; } = false;
// Sunrise-End
// Sunrise added start
[DataField] public bool ForceEndRound;
[DataField] public ExtendedAccessOptions? ExtendedAccessOptions;
// Sunrise added end
}

View file

@ -165,6 +165,9 @@ public sealed class AlertLevelSystem : EntitySystem
component.ActiveDelay = true;
}
// Sunrise added - добавил сохраненый прежний уровень для системы автодоступов
var previousLevel = component.CurrentLevel;
component.CurrentLevel = level;
component.IsLevelLocked = locked;
@ -212,7 +215,8 @@ public sealed class AlertLevelSystem : EntitySystem
}
// Sunrise-End
RaiseLocalEvent(new AlertLevelChangedEvent(station, level));
// Sunrise edit - добавил прежний уровень для системы автодоступов
RaiseLocalEvent(new AlertLevelChangedEvent(station, level, previousLevel));
}
}
@ -227,9 +231,12 @@ public sealed class AlertLevelChangedEvent : EntityEventArgs
public EntityUid Station { get; }
public string AlertLevel { get; }
public AlertLevelChangedEvent(EntityUid station, string alertLevel)
public string PreviousLevel; // Sunrise added - прежний уровень для системы автодоступов
public AlertLevelChangedEvent(EntityUid station, string alertLevel, string previousLevel)
{
Station = station;
AlertLevel = alertLevel;
PreviousLevel = previousLevel; // Sunrise added - прежний уровень для системы автодоступов
}
}

View file

@ -77,7 +77,6 @@ namespace Content.Server.AlertLevel.Commands
}
_entitySystems.GetEntitySystem<AlertLevelSystem>().SetLevel(stationUid.Value, level, true, true, true, locked);
_entManager.EventBus.RaiseLocalEvent(stationUid.Value, new AlertAccessesEvent(stationUid.Value), true); // Sunrise-added
}
private string[] GetStationLevelNames(EntityUid station)

View file

@ -223,7 +223,6 @@ namespace Content.Server.Communications
if (stationUid != null)
{
_alertLevelSystem.SetLevel(stationUid.Value, message.Level, true, true);
RaiseLocalEvent(new AlertAccessesEvent(stationUid.Value)); // Sunrise-added
}
}

View file

@ -477,10 +477,9 @@ public sealed class NukeSystem : EntitySystem
// The nuke may not be on a station, so it's more important to just
// let people know that a nuclear bomb was armed in their vicinity instead.
// Otherwise, you could set every station to whatever AlertLevelOnActivate is.
if (stationUid != null) // Sunrise-edited
if (stationUid != null)
{
_alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnActivate, true, true, true, true);
RaiseLocalEvent(new AlertAccessesEvent(stationUid.Value));
}
var pos = _transform.GetMapCoordinates(uid, xform: nukeXform);
@ -530,10 +529,9 @@ public sealed class NukeSystem : EntitySystem
return;
var stationUid = _station.GetOwningStation(uid);
if (stationUid != null) // Sunrise-edited
if (stationUid != null)
{
_alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true);
RaiseLocalEvent(new AlertAccessesEvent(stationUid.Value));
}
// warn a crew

View file

@ -19,6 +19,5 @@ public sealed class AlertLevelInterceptionRule : StationEventSystem<AlertLevelIn
return;
_alertLevelSystem.SetLevel(chosenStation.Value, component.AlertLevel, true, true, true);
RaiseLocalEvent(new AlertAccessesEvent(chosenStation.Value)); // Sunrise-added
}
}

View file

@ -410,10 +410,7 @@ public sealed class SupermatterSystem : EntitySystem
sb.Append(Loc.GetString("supermatter-announcement-delam-countdown", ("seconds", sm.DelamCountdownTimer)));
// make it cancellable in case there are crazy engineers that managed to contain the delam
if (stationUid != null)
{
_alert.SetLevel(stationUid.Value, alertLevel, true, true, true, false);
RaiseLocalEvent(new AlertAccessesEvent(stationUid.Value));
}
SupermatterAlert(uid, sb.ToString());

View file

@ -1,12 +0,0 @@
/// <summary>
/// Проставил этот ивент во всех удовлетворяющих случаях смены кода.
/// </summary>
public sealed class AlertAccessesEvent : EntityEventArgs
{
public EntityUid Station { get; }
public AlertAccessesEvent(EntityUid station)
{
Station = station;
}
}

View file

@ -0,0 +1,8 @@
namespace Content.Server._Sunrise.ExtendedAccess;
[DataDefinition]
public partial record struct ExtendedAccessOptions
{
[DataField] public string? Announcement;
[DataField] public TimeSpan Delay = TimeSpan.FromSeconds(60);
}

View file

@ -0,0 +1,114 @@
using System.Threading;
using Content.Server.AlertLevel;
using Content.Server.Chat.Systems;
using Content.Shared.Access.Components;
using Content.Shared.GameTicking;
using Content.Shared.Station.Components;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server._Sunrise.ExtendedAccess;
public sealed class ExtendedAccessSystem : EntitySystem
{
[Dependency] private readonly ChatSystem _chat = default!;
private static CancellationTokenSource _token = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
SubscribeLocalEvent<RoundRestartCleanupEvent>(_ => RecreateToken());
}
/// <summary>
/// Запускает таймер и выводит объявление о смене доступов через некоторое время
/// </summary>
private void OnAlertLevelChanged(AlertLevelChangedEvent ev)
{
// Это случай первичного установления кода(зеленый) по умолчанию
// Чтобы в начале раунда не слышать, что доступы изменились на зеленый
if (ev.PreviousLevel == string.Empty)
return;
if (!TryComp<AlertLevelComponent>(ev.Station, out var alert))
return;
if (alert.AlertLevels == null)
return;
if (!alert.AlertLevels.Levels.TryGetValue(alert.CurrentLevel, out var currentLevelDetail))
return;
var options = currentLevelDetail.ExtendedAccessOptions;
if (options == null)
return;
// Предотвращение стаканье смены доступов. Доступы должны сменяться только на последний код угрозы.
RecreateToken();
Timer.Spawn(options.Value.Delay, () => AfterDelay((ev.Station, alert)), _token.Token);
if (options.Value.Announcement != null)
{
// В строке локализации оповещения обязательно должно быть указан параметр для времени
var message = Loc.GetString(options.Value.Announcement, ("time", options.Value.Delay.TotalSeconds));
_chat.DispatchStationAnnouncement(ev.Station,
Loc.GetString(message),
colorOverride: Color.Yellow,
sender: Loc.GetString("access-system-sender"));
}
}
/// <summary>
/// Проходится по всем сущностям, считывающим доступ.
/// Заставляет пересмотреть свои доступы в соответствии с текущим кодом угрозы
/// </summary>
private void AfterDelay(Entity<AlertLevelComponent> station)
{
_chat.DispatchStationAnnouncement(station,
Loc.GetString("access-system-accesses-established"),
colorOverride: Color.Yellow,
sender: Loc.GetString("access-system-sender"));
var query = EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var reader, out var xform))
{
if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station != station)
continue;
if (reader.AlertAccesses.Count == 0)
continue;
UpdateAccess((uid, reader), station);
}
}
/// <summary>
/// Устанавливает новые доступы в соответствии с текущим кодом угрозы.
/// Сбрасывает аварийные доступы, если не нашлось доступов при текущем коде угрозы.
/// </summary>
private void UpdateAccess(Entity<AccessReaderComponent> ent, Entity<AlertLevelComponent> station)
{
if (station.Comp.AlertLevels == null)
return;
if (ent.Comp.AlertAccesses.TryGetValue(station.Comp.CurrentLevel, out var value))
ent.Comp.Group = value;
else
ent.Comp.Group = null;
Dirty(ent);
}
private static void RecreateToken()
{
_token.Cancel();
_token = new();
}
}

View file

@ -16,6 +16,21 @@ namespace Content.Shared.Access.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class AccessReaderComponent : Component
{
// Sunrise added start
#region ExtendedAccess
/// <summary>
/// Именно от Group происходит проверка аварийных доступов
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ProtoId<AccessGroupPrototype>? Group;
[DataField, ViewVariables]
public Dictionary<string, ProtoId<AccessGroupPrototype>> AlertAccesses = new();
#endregion
// Sunrise added end
/// <summary>
/// Whether or not the accessreader is enabled.
/// If not, it will always let people through.
@ -23,29 +38,6 @@ public sealed partial class AccessReaderComponent : Component
[DataField]
public bool Enabled = true;
// Sunrise-start
/// <summary>
/// Именно от Group происходит проверка аварийных доступов
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ProtoId<AccessGroupPrototype> Group = string.Empty;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public Dictionary<CurrentAlertLevel, ProtoId<AccessGroupPrototype>> AlertAccesses = new();
[Flags]
public enum CurrentAlertLevel : byte
{
blue,
red,
yellow,
gamma,
delta
}
// Sunrise-end
/// <summary>
/// The set of tags that will automatically deny an allowed check, if any of them are present.
/// </summary>
@ -126,7 +118,7 @@ public sealed class AccessReaderComponentState : ComponentState
public List<HashSet<ProtoId<AccessLevelPrototype>>> AccessLists;
public ProtoId<AccessGroupPrototype> Group; // Sunrise-alertAccesses, нужно для связывания клиента с сервером
public ProtoId<AccessGroupPrototype>? Group; // Sunrise-alertAccesses, нужно для связывания клиента с сервером
public List<(NetEntity, uint)> AccessKeys;
@ -136,10 +128,10 @@ public sealed class AccessReaderComponentState : ComponentState
public AccessReaderComponentState(bool enabled, HashSet<ProtoId<AccessLevelPrototype>> denyTags,
List<HashSet<ProtoId<AccessLevelPrototype>>> accessLists,
ProtoId<AccessGroupPrototype> group,
ProtoId<AccessGroupPrototype>? group, //Sunrise added
List<(NetEntity, uint)> accessKeys,
Queue<AccessRecord> accessLog,
int accessLogLimit) //Sunrise-edit
int accessLogLimit)
{
Enabled = enabled;
DenyTags = denyTags;
@ -147,7 +139,7 @@ public sealed class AccessReaderComponentState : ComponentState
AccessKeys = accessKeys;
AccessLog = accessLog;
AccessLogLimit = accessLogLimit;
Group = group; // Sunrise-alertAccesses
Group = group; // Sunrise added for alertAccesses
}
}

View file

@ -68,7 +68,7 @@ public sealed class AccessReaderSystem : EntitySystem
component.AccessLists = new(state.AccessLists);
component.DenyTags = new(state.DenyTags);
component.AccessLog = new(state.AccessLog);
component.Group = new(state.Group); // Sunrise-alertAccesses
component.Group = state.Group != null ? new (state.Group) : null; // Sunrise added - автодоступы по коду
component.AccessLogLimit = state.AccessLogLimit;
}
@ -167,8 +167,10 @@ public sealed class AccessReaderSystem : EntitySystem
if (!reader.Enabled)
return true;
if (AreAccessTagsAllowedAlert(access, reader))
// Sunrise added start
if (IsAccessAllowedByExtendedAccess(access, reader))
return true;
// Sunrise added end
if (reader.ContainerAccessProvider == null)
return IsAllowedInternal(access, stationKeys, reader);
@ -186,10 +188,10 @@ public sealed class AccessReaderSystem : EntitySystem
if (!TryComp(entity, out AccessReaderComponent? containedReader))
continue;
// Sunrise-start
if (AreAccessTagsAllowedAlert(access, containedReader))
// Sunrise added start
if (IsAccessAllowedByExtendedAccess(access, containedReader))
return true;
// Sunrise-end
// Sunrise added end
if (IsAllowed(access, stationKeys, entity, containedReader))
return true;
@ -238,26 +240,18 @@ public sealed class AccessReaderSystem : EntitySystem
/// <summary>
/// Сравнивает список аварийных доступов с доступами на карте.
/// </summary>
public bool AreAccessTagsAllowedAlert(ICollection<ProtoId<AccessLevelPrototype>> access, AccessReaderComponent reader)
public bool IsAccessAllowedByExtendedAccess(ICollection<ProtoId<AccessLevelPrototype>> access, AccessReaderComponent reader)
{
if (reader.Group == string.Empty)
return false;
if (!_prototype.TryIndex<AccessGroupPrototype>(reader.Group, out var accessTags))
return false;
if (accessTags == null)
if (!_prototype.TryIndex(reader.Group, out var accessTags))
return false;
if (accessTags.Tags.Count == 0)
return false;
foreach (var ent in accessTags.Tags)
{
if (access.Contains(ent))
return true;
}
return false;
if (!accessTags.Tags.Any(access.Contains))
return false;
return true;
}
// Sunrise-end

View file

@ -1,7 +1,7 @@
access-system-sender = Система доступов
access-system-accesses-established = Внимание! Доступы были перезаписаны.
access-system-accesses-delay-green = Зафиксировано установление зеленого кода! Запущен протокол автоматической перезаписи доступов. Все аварийные доступы будут стёрты через 1 минуту.
access-system-accesses-delay-blue = Зафиксировано установление синего кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат дополнительные доступы в технические помещения через 1 минуту.
access-system-accesses-delay-red = Зафиксировано установление красного кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат расширенный доступ через 1 минуту.
access-system-accesses-delay-yellow = Зафиксировано установление желтого кода! Запущен протокол автоматической перезаписи доступов. Атмосферные техники и Старший Инженер получат дополнительные доступы через 1 минуту.
access-system-accesses-delay-gamma = Зафиксировано установление гамма кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат расширенный доступ через 1 минуту.
access-system-accesses-delay-green = Зафиксировано установление зеленого кода! Запущен протокол автоматической перезаписи доступов. Все аварийные доступы будут стёрты через {$time} секунд.
access-system-accesses-delay-blue = Зафиксировано установление синего кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат дополнительные доступы в технические помещения через {$time} секунд.
access-system-accesses-delay-red = Зафиксировано установление красного кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат расширенный доступ через {$time} секунд.
access-system-accesses-delay-yellow = Зафиксировано установление желтого кода! Запущен протокол автоматической перезаписи доступов. Атмосферные техники и Старший Инженер получат дополнительные доступы через {$time} секунд.
access-system-accesses-delay-gamma = Зафиксировано установление гамма кода! Запущен протокол автоматической перезаписи доступов. Сотрудники службы безопасности получат расширенный доступ через {$time} секунд.

View file

@ -7,6 +7,10 @@
color: Green
emergencyLightColor: LawnGreen
shuttleTime: 600
# Sunrise edit start
extendedAccessOptions:
announcement: access-system-accesses-delay-green
# Sunrise edit end
blue:
announcement: alert-level-blue-announcement
sound: /Audio/Misc/bluealert.ogg
@ -14,6 +18,10 @@
forceEnableEmergencyLights: true
emergencyLightColor: DodgerBlue
shuttleTime: 600
# Sunrise edit start
extendedAccessOptions:
announcement: access-system-accesses-delay-blue
# Sunrise edit end
violet:
announcement: alert-level-violet-announcement
sound: /Audio/Misc/notice1.ogg
@ -28,6 +36,10 @@
emergencyLightColor: Goldenrod
forceEnableEmergencyLights: true
shuttleTime: 600
# Sunrise edit start
extendedAccessOptions:
announcement: access-system-accesses-delay-yellow
# Sunrise edit end
red:
announcement: alert-level-red-announcement
sound: /Audio/Misc/redalert.ogg
@ -35,6 +47,10 @@
emergencyLightColor: Red
forceEnableEmergencyLights: true
shuttleTime: 600 #No reduction in time as we don't have swiping for red alert like in /tg/. Shuttle times are intended to create friction, so having a way to brainlessly bypass that would be dumb.
# Sunrise edit start
extendedAccessOptions:
announcement: access-system-accesses-delay-red
# Sunrise edit end
gamma:
announcement: alert-level-gamma-announcement
selectable: false
@ -46,6 +62,10 @@
color: PaleVioletRed
emergencyLightColor: PaleVioletRed
forceEnableEmergencyLights: true
# Sunrise edit start
extendedAccessOptions:
announcement: access-system-accesses-delay-gamma
# Sunrise edit end
delta:
announcement: alert-level-delta-announcement
selectable: false
@ -58,6 +78,10 @@
emergencyLightColor: Orange
forceEnableEmergencyLights: true
shuttleTime: 1200
# Sunrise edit start
extendedAccessOptions:
delay: 0
# Sunrise edit end
epsilon:
announcement: alert-level-epsilon-announcement
selectable: false