From 86ed33b28b1a8407bebea7c6d6c6226619199c0f Mon Sep 17 00:00:00 2001 From: Ende Date: Mon, 7 Sep 2026 16:49:47 +0200 Subject: [PATCH 01/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BE=D0=B4=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=83=D0=B3=D1=80=D0=BE=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommunicationsConsoleBoundUserInterface.cs | 9 + .../UI/CommunicationsConsoleMenu.xaml | 13 ++ .../UI/CommunicationsConsoleMenu.xaml.cs | 36 ++++ Content.Client/PDA/PdaMenu.xaml.cs | 23 +- .../AlertLevel/AdditionalAlertLevelTest.cs | 136 ++++++++++++ .../AlertLevel/AlertLevelComponent.cs | 11 + .../AlertLevel/AlertLevelPrototype.cs | 5 + Content.Server/AlertLevel/AlertLevelSystem.cs | 36 ++-- .../Commands/SetAlertLevelCommand.cs | 22 +- .../CommunicationsConsoleSystem.cs | 65 +++++- Content.Server/PDA/PdaSystem.cs | 20 ++ Content.Server/RoundEnd/RoundEndSystem.cs | 16 +- .../Ranged/Conditions/AlertLevelCondition.cs | 12 +- .../AlertLevelComponent.Additional.cs | 11 + .../AlertLevel/AlertLevelSystem.Additional.cs | 196 ++++++++++++++++++ .../SharedCommunicationsConsoleComponent.cs | 34 ++- Content.Shared/PDA/PdaComponent.cs | 1 + Content.Shared/PDA/PdaUpdateState.cs | 16 ++ .../_sunrise/communications/codes.ftl | 2 + .../alert-levels/alert-level-command.ftl | 6 +- .../_sunrise/communications/codes.ftl | 2 + .../alert-levels/alert-level-command.ftl | 6 +- .../Prototypes/AlertLevels/alert_levels.yml | 4 + 23 files changed, 641 insertions(+), 41 deletions(-) create mode 100644 Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs create mode 100644 Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs create mode 100644 Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs create mode 100644 Resources/Locale/en-US/_strings/_sunrise/communications/codes.ftl create mode 100644 Resources/Locale/ru-RU/_strings/_sunrise/communications/codes.ftl diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs index a6bb84c6449..ad2adc4b0b5 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs @@ -26,10 +26,18 @@ protected override void Open() _menu.OnAnnounce += AnnounceButtonPressed; _menu.OnBroadcast += BroadcastButtonPressed; _menu.OnAlertLevel += AlertLevelSelected; + _menu.OnAdditionalAlertLevel += AdditionalAlertLevelSelected; // Sunrise-Edit _menu.OnEmergencyLevel += EmergencyShuttleButtonPressed; _menu.OnToggleRelay += ToggleRelayPressed; // Sunrise-Edit } + // Sunrise added start - дополнительные коды переключаются независимо + private void AdditionalAlertLevelSelected(string level, bool enabled) + { + SendMessage(new CommunicationsConsoleSetAdditionalAlertLevelMessage(level, enabled)); + } + // Sunrise added end + public void AlertLevelSelected(string level) { if (_menu!.AlertLevelSelectable) @@ -95,6 +103,7 @@ protected override void UpdateState(BoundUserInterfaceState state) _menu.UpdateCountdown(); _menu.UpdateAlertLevels(commsState.AlertLevels, _menu.CurrentLevel); + _menu.UpdateAdditionalAlertLevels(commsState.AdditionalAlertLevels); // Sunrise-Edit _menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable; _menu.EmergencyShuttleButton.Disabled = !_menu.CanCall; _menu.AnnounceButton.Disabled = !_menu.CanAnnounce; diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml index 0c276d0a997..1fbe5bf7d12 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml @@ -57,6 +57,19 @@ Access="Public" ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}" StyleClasses="OpenRight"/> + + + + + + + + ? OnAlertLevel; + public event Action? OnAdditionalAlertLevel; // Sunrise-Edit public event Action? OnAnnounce; public event Action? OnBroadcast; public event Action? OnToggleRelay; // Sunrise-Edit @@ -130,6 +134,38 @@ public void UpdateAlertLevels(List? alerts, string currentAlert) } } + // Sunrise added start - независимые переключатели дополнительных кодов + public void UpdateAdditionalAlertLevels(List alerts) + { + AdditionalAlertLevelsContainer.RemoveAllChildren(); + foreach (var alert in alerts) + { + var name = alert.Level; + if (_loc.TryGetString($"alert-level-{alert.Level}", out var localizedName)) + name = localizedName; + + var checkBox = new CheckBox + { + Text = name, + ToggleMode = true, + Pressed = alert.Enabled, + Disabled = !alert.Selectable, + HorizontalExpand = true, + Margin = new Thickness(2), + }; + + checkBox.OnToggled += args => + { + checkBox.Disabled = true; + OnAdditionalAlertLevel?.Invoke(alert.Level, args.Pressed); + }; + AdditionalAlertLevelsContainer.AddChild(checkBox); + } + + AdditionalAlertLevelsSection.Visible = alerts.Count > 0; + } + // Sunrise added end + public void UpdateCountdown() { if (!CountdownStarted) diff --git a/Content.Client/PDA/PdaMenu.xaml.cs b/Content.Client/PDA/PdaMenu.xaml.cs index 58d631d8117..818eac34c39 100644 --- a/Content.Client/PDA/PdaMenu.xaml.cs +++ b/Content.Client/PDA/PdaMenu.xaml.cs @@ -218,15 +218,32 @@ public void UpdateState(PdaUpdateState state) var alertLevel = state.PdaOwnerInfo.StationAlertLevel; var alertColor = state.PdaOwnerInfo.StationAlertColor; - var alertLevelKey = alertLevel != null ? $"alert-level-{alertLevel}" : "alert-level-unknown"; - _alertLevel = Loc.GetString(alertLevelKey); + // Sunrise edit start - КПК показывает основной и дополнительные коды одновременно + IReadOnlyList activeAlertLevels = state.PdaOwnerInfo.StationAlertLevels is { } stationAlertLevels + ? stationAlertLevels + : Array.Empty(); + if (activeAlertLevels.Count == 0 && alertLevel != null) + activeAlertLevels = new[] { new PdaAlertLevelInfo(alertLevel, alertColor) }; + + var localizedAlertLevels = new List(activeAlertLevels.Count); + var instructions = new List(activeAlertLevels.Count); + foreach (var activeAlertLevel in activeAlertLevels) + { + var activeAlertLevelKey = $"alert-level-{activeAlertLevel.Level}"; + var localizedAlertLevel = FormattedMessage.EscapeText(Loc.GetString(activeAlertLevelKey)); + localizedAlertLevels.Add($"[color={activeAlertLevel.Color.ToHex()}]{localizedAlertLevel}[/color]"); + instructions.Add(Loc.GetString($"{activeAlertLevelKey}-instructions")); + } + + _alertLevel = string.Join(", ", localizedAlertLevels); + // Sunrise edit end StationAlertLevelLabel.SetMarkup(Loc.GetString( "comp-pda-ui-station-alert-level", ("color", alertColor), ("level", _alertLevel) )); - _instructions = Loc.GetString($"{alertLevelKey}-instructions"); + _instructions = string.Join("\n", instructions); // Sunrise-Edit StationAlertLevelInstructions.SetMarkup(Loc.GetString( "comp-pda-ui-station-alert-level-instructions", ("instructions", _instructions)) diff --git a/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs b/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs new file mode 100644 index 00000000000..1603e90bf0b --- /dev/null +++ b/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs @@ -0,0 +1,136 @@ +#nullable enable + +using Content.Server.AlertLevel; +using Content.Shared.Access; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests._Sunrise.AlertLevel; + +[TestFixture] +[TestOf(typeof(AlertLevelSystem))] +public sealed class AdditionalAlertLevelTest +{ + [Test] + public async Task AdditionalLevelsDoNotReplacePrimaryLevel() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + var entityManager = server.EntMan; + var prototypeManager = server.ResolveDependency(); + var alertLevelSystem = server.System(); + + EntityUid station = default; + AlertLevelComponent alertLevel = null!; + var yellowEnabled = false; + var primaryStayedGreen = false; + var additionalIgnoredPrimaryCooldown = false; + var violetEnabled = false; + var yellowDisabled = false; + var disablingDidNotStartCooldown = false; + + await server.WaitPost(() => + { + station = entityManager.SpawnEntity(null, MapCoordinates.Nullspace); + alertLevel = entityManager.AddComponent(station); + alertLevel.AlertLevels = prototypeManager.Index(AlertLevelSystem.DefaultAlertLevelSet); + alertLevel.CurrentLevel = "green"; + alertLevel.CurrentDelay = 30; + + alertLevelSystem.SetLevel(station, "yellow", false, false, component: alertLevel); + yellowEnabled = alertLevel.ActiveAdditionalLevels.Contains("yellow"); + primaryStayedGreen = alertLevel.CurrentLevel == "green"; + additionalIgnoredPrimaryCooldown = alertLevel.CurrentDelay == 30; + violetEnabled = alertLevelSystem.TrySetAdditionalLevel( + station, + "violet", + true, + playSound: false, + announce: false, + component: alertLevel); + alertLevel.CurrentDelay = 0; + alertLevelSystem.SetLevel(station, "red", false, false, true, component: alertLevel); + yellowDisabled = alertLevelSystem.TrySetAdditionalLevel( + station, + "yellow", + false, + playSound: false, + announce: false, + component: alertLevel); + disablingDidNotStartCooldown = alertLevel.CurrentDelay == 0; + }); + + await server.WaitAssertion(() => + { + Assert.Multiple(() => + { + Assert.That(yellowEnabled, Is.True); + Assert.That(primaryStayedGreen, Is.True); + Assert.That(additionalIgnoredPrimaryCooldown, Is.True); + Assert.That(violetEnabled, Is.True); + Assert.That(yellowDisabled, Is.True); + Assert.That(disablingDidNotStartCooldown, Is.True); + Assert.That(alertLevel!.CurrentLevel, Is.EqualTo("red")); + Assert.That(alertLevel.ActiveAdditionalLevels, Is.EquivalentTo(new[] { "violet" })); + var stationAlertLevel = new Entity(station, alertLevel); + Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "red"), Is.True); + Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "violet"), Is.True); + Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "yellow"), Is.False); + }); + }); + + await pair.CleanReturnAsync(); + } + + [Test] + public async Task AdditionalLevelKeepsPrimaryEmergencyAccesses() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + var entityManager = server.EntMan; + var prototypeManager = server.ResolveDependency(); + var alertLevelSystem = server.System(); + var accessReaderSystem = server.System(); + + AlertLevelComponent alertLevel = null!; + AccessReaderComponent accessReader = null!; + var additionalLevelEnabled = false; + + await server.WaitPost(() => + { + var station = entityManager.SpawnEntity(null, MapCoordinates.Nullspace); + alertLevel = entityManager.AddComponent(station); + alertLevel.AlertLevels = prototypeManager.Index(AlertLevelSystem.DefaultAlertLevelSet); + alertLevel.CurrentLevel = "red"; + + var reader = entityManager.SpawnEntity("DoorElectronicsLawyer", MapCoordinates.Nullspace); + accessReader = entityManager.GetComponent(reader); + accessReaderSystem.UpdateAccess((reader, accessReader), alertLevel.CurrentLevel); + + additionalLevelEnabled = alertLevelSystem.TrySetAdditionalLevel( + station, + "yellow", + true, + playSound: false, + announce: false, + force: true, + component: alertLevel); + }); + + await server.WaitAssertion(() => + { + Assert.Multiple(() => + { + Assert.That(additionalLevelEnabled, Is.True); + Assert.That(alertLevel.CurrentLevel, Is.EqualTo("red")); + Assert.That(accessReader.Group, + Is.EqualTo(new ProtoId("RedAlertAccesses"))); + }); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/AlertLevel/AlertLevelComponent.cs b/Content.Server/AlertLevel/AlertLevelComponent.cs index cb45c6b40e6..3a30935e14b 100644 --- a/Content.Server/AlertLevel/AlertLevelComponent.cs +++ b/Content.Server/AlertLevel/AlertLevelComponent.cs @@ -46,6 +46,17 @@ public bool IsSelectable return false; } + // Sunrise edit start - принудительный дополнительный код может заблокировать ручной выбор + foreach (var additionalLevel in ActiveAdditionalLevels) + { + if (AlertLevels.Levels.TryGetValue(additionalLevel, out var additionalDetail) + && additionalDetail.DisableSelection) + { + return false; + } + } + // Sunrise edit end + return level.Selectable && !level.DisableSelection && !IsLevelLocked; } } diff --git a/Content.Server/AlertLevel/AlertLevelPrototype.cs b/Content.Server/AlertLevel/AlertLevelPrototype.cs index 5fc97d693bd..087117524f2 100644 --- a/Content.Server/AlertLevel/AlertLevelPrototype.cs +++ b/Content.Server/AlertLevel/AlertLevelPrototype.cs @@ -30,6 +30,11 @@ public sealed partial class AlertLevelPrototype : IPrototype [DataDefinition] public sealed partial class AlertLevelDetail { + /// + /// Определяет код как дополнительный протокол, который может действовать одновременно с основным кодом. + /// + [DataField] public bool IsAdditional { get; private set; } // Sunrise-Edit + /// /// What is announced upon this alert level change. Can be a localized string. /// diff --git a/Content.Server/AlertLevel/AlertLevelSystem.cs b/Content.Server/AlertLevel/AlertLevelSystem.cs index cbc8c798a5d..b8abba311aa 100644 --- a/Content.Server/AlertLevel/AlertLevelSystem.cs +++ b/Content.Server/AlertLevel/AlertLevelSystem.cs @@ -12,7 +12,7 @@ namespace Content.Server.AlertLevel; -public sealed class AlertLevelSystem : EntitySystem +public sealed partial class AlertLevelSystem : EntitySystem // Sunrise-Edit { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -82,6 +82,7 @@ private void OnPrototypeReload(PrototypesReloadedEventArgs args) while (query.MoveNext(out var uid, out var comp)) { comp.AlertLevels = alerts; + PruneAdditionalLevels((uid, comp)); // Sunrise-Edit if (!comp.AlertLevels.Levels.ContainsKey(comp.CurrentLevel)) { var defaultLevel = comp.AlertLevels.DefaultLevel; @@ -138,16 +139,26 @@ public void SetLevel(EntityUid station, string level, bool playSound, bool annou { if (!Resolve(station, ref component, ref dataComponent) || component.AlertLevels == null - || !component.AlertLevels.Levels.TryGetValue(level, out var detail) - || component.CurrentLevel == level) + || !component.AlertLevels.Levels.TryGetValue(level, out var detail)) { return; } + + // Sunrise edit start - дополнительные коды не заменяют основной + if (detail.IsAdditional) + { + TrySetAdditionalLevel(station, level, true, playSound, announce, force, component); + return; + } + + if (component.CurrentLevel == level) + return; + // Sunrise edit end if (!force) { if (!detail.Selectable || component.CurrentDelay > 0 - || component.IsLevelLocked) + || !component.IsSelectable) // Sunrise-Edit { return; } @@ -187,22 +198,7 @@ public void SetLevel(EntityUid station, string level, bool playSound, bool annou colorOverride: detail.Color, sender: stationName); } - // Sunrise-Start - // Handle special alert level behaviors - if (detail.ForceEndRound) - { - _roundEnd.EndRound(); - } - // Handle Epsilon alert level - if (level == EpsilonAlertLevel) - { - var eventEnt = _gameTicker.AddGameRule(EpsilonBorgLawChanges); - // Use the system to set the station - var epsilonRule = EntityManager.System(); - epsilonRule.StartEvent(eventEnt, station); - _gameTicker.StartGameRule(eventEnt); - } - // Sunrise-End + ApplySpecialAlertLevelBehavior(station, level, detail); // Sunrise-Edit // Sunrise edit - добавил прежний уровень для системы автодоступов // Raise event with previous level for auto access system RaiseLocalEvent(new AlertLevelChangedEvent(station, level, previousLevel)); diff --git a/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs b/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs index 2a8e02fadb7..cb5695bcfab 100644 --- a/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs +++ b/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs @@ -37,14 +37,14 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg public override void Execute(IConsoleShell shell, string argStr, string[] args) { - if (args.Length < 1) + if (args.Length is < 1 or > 2) { shell.WriteError(LocalizationManager.GetString("shell-wrong-arguments-number")); return; } - var locked = false; - if (args.Length > 1 && !bool.TryParse(args[1], out locked)) + var option = false; + if (args.Length > 1 && !bool.TryParse(args[1], out option)) { shell.WriteLine(LocalizationManager.GetString("shell-argument-must-be-boolean")); return; @@ -65,14 +65,24 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) } var level = args[0]; - var levelNames = GetStationLevelNames(stationUid.Value); - if (!levelNames.Contains(level)) + if (!EntityManager.TryGetComponent(stationUid.Value, out var alertLevelComp) + || alertLevelComp.AlertLevels == null + || !alertLevelComp.AlertLevels.Levels.TryGetValue(level, out var detail)) { shell.WriteLine(LocalizationManager.GetString("cmd-setalertlevel-invalid-level")); return; } - _alertLevelSystem.SetLevel(stationUid.Value, level, true, true, true, locked); + // Sunrise edit start - второй параметр управляет состоянием дополнительного кода + if (detail.IsAdditional) + { + var enabled = args.Length == 1 || option; + _alertLevelSystem.TrySetAdditionalLevel(stationUid.Value, level, enabled, true, true, true, alertLevelComp); + return; + } + + _alertLevelSystem.SetLevel(stationUid.Value, level, true, true, true, option); + // Sunrise edit end } private string[] GetStationLevelNames(EntityUid station) diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index ad392d20a91..4d469ac9221 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -47,11 +47,13 @@ public override void Initialize() { // All events that refresh the BUI SubscribeLocalEvent(OnAlertLevelChanged); + SubscribeLocalEvent(OnAdditionalAlertLevelChanged); // Sunrise-Edit SubscribeLocalEvent(_ => OnGenericBroadcastEvent()); SubscribeLocalEvent(_ => OnGenericBroadcastEvent()); // Messages from the BUI SubscribeLocalEvent(OnSelectAlertLevelMessage); + SubscribeLocalEvent(OnSetAdditionalAlertLevelMessage); // Sunrise-Edit SubscribeLocalEvent(OnAnnounceMessage); SubscribeLocalEvent(OnBroadcastMessage); SubscribeLocalEvent(OnCallShuttleMessage); @@ -141,6 +143,18 @@ private void OnAlertLevelChanged(AlertLevelChangedEvent args) } } + // Sunrise added start - дополнительные коды обновляют те же консоли + private void OnAdditionalAlertLevelChanged(AdditionalAlertLevelChangedEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (args.Station == _stationSystem.GetOwningStation(uid)) + UpdateCommsConsoleInterface(uid, comp); + } + } + // Sunrise added end + /// /// Updates the UI for all comms consoles. /// @@ -162,6 +176,7 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp List? levels = null; string currentLevel = default!; float currentDelay = 0; + var additionalLevels = new List(); // Sunrise-Edit if (stationUid != null) { @@ -173,13 +188,27 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp levels = new(); foreach (var (id, detail) in alertComp.AlertLevels.Levels) { - if (detail.Selectable) + if (detail.Selectable && !detail.IsAdditional) // Sunrise-Edit { levels.Add(id); } } } + // Sunrise added start - обычная консоль показывает только доступные экипажу дополнительные коды + foreach (var (id, detail) in alertComp.AlertLevels.Levels) + { + if (!detail.IsAdditional || !detail.Selectable) + continue; + + var enabled = alertComp.ActiveAdditionalLevels.Contains(id); + additionalLevels.Add(new CommunicationsConsoleAdditionalAlertLevelState( + id, + enabled, + _alertLevelSystem.CanSetAdditionalLevel((stationUid.Value, alertComp), id, !enabled))); + } + // Sunrise added end + currentLevel = alertComp.CurrentLevel; currentDelay = _alertLevelSystem.GetAlertLevelDelay(stationUid.Value, alertComp); } @@ -197,7 +226,8 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp canRelay, comp.IsRelaying, MathF.Max(0f, comp.RelayCooldownRemaining), - MathF.Max(0f, comp.RelayTimeRemaining) + MathF.Max(0f, comp.RelayTimeRemaining), + additionalAlertLevels: additionalLevels // Sunrise-Edit // Sunrise-End )); } @@ -259,6 +289,37 @@ private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleCompo } } + // Sunrise added start - явное включение или выключение дополнительного кода + private void OnSetAdditionalAlertLevelMessage( + EntityUid uid, + CommunicationsConsoleComponent comp, + CommunicationsConsoleSetAdditionalAlertLevelMessage message) + { + if (message.Actor is not { Valid: true } mob) + return; + + if (!CanUse(mob, uid)) + { + _popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), message.Actor, PopupType.Medium); + return; + } + + var stationUid = _stationSystem.GetOwningStation(uid); + if (stationUid == null) + return; + + if (!_alertLevelSystem.TrySetAdditionalLevel( + stationUid.Value, + message.Level, + message.Enabled, + playSound: true, + announce: true)) + { + UpdateCommsConsoleInterface(uid, comp); + } + } + // Sunrise added end + private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleAnnounceMessage message) { diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index b2955507e22..c4e16ffe343 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -47,6 +47,7 @@ public sealed class PdaSystem : SharedPdaSystem [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttleSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AlertLevelSystem _alertLevel = default!; // Sunrise-Edit public override void Initialize() { @@ -68,6 +69,7 @@ public override void Initialize() SubscribeLocalEvent(OnStationRenamed); SubscribeLocalEvent(OnEntityRenamed, after: new[] { typeof(IdCardSystem) }); SubscribeLocalEvent(OnAlertLevelChanged); + SubscribeLocalEvent(OnAdditionalAlertLevelChanged); // Sunrise-Edit SubscribeLocalEvent>(OnRelayedEventToIdCard); SubscribeLocalEvent>(OnRelayedEventToIdCard); } @@ -155,6 +157,12 @@ private void OnAlertLevelChanged(AlertLevelChangedEvent args) UpdateAllPdaUisOnStation(); } + // Sunrise-Edit + private void OnAdditionalAlertLevelChanged(AdditionalAlertLevelChangedEvent args) + { + UpdateAllPdaUisOnStation(); + } + private void UpdateAllPdaUisOnStation() { var query = AllEntityQuery(); @@ -225,6 +233,7 @@ public override void UpdatePdaUi(EntityUid uid, PdaComponent? pda = null) IdOwner = id?.FullName, JobTitle = id?.LocalizedJobTitle, StationAlertLevel = pda.StationAlertLevel, + StationAlertLevels = pda.StationAlertLevels, // Sunrise-Edit StationAlertColor = pda.StationAlertColor, EvacShuttleStatus = pda.ShuttleStatus, // Sunrise-edit EvacShuttleTime = pda.ShuttleTime // Sunrise-edit @@ -353,6 +362,17 @@ private void UpdateAlertLevel(EntityUid uid, PdaComponent pda) alertComp.AlertLevels == null) return; pda.StationAlertLevel = alertComp.CurrentLevel; + // Sunrise edit start - КПК получает отдельный цвет каждого активного кода + var activeLevels = _alertLevel.GetActiveLevels((station!.Value, alertComp)); + var stationAlertLevels = new List(activeLevels.Count); + foreach (var activeLevel in activeLevels) + { + if (alertComp.AlertLevels.Levels.TryGetValue(activeLevel, out var detail)) + stationAlertLevels.Add(new PdaAlertLevelInfo(activeLevel, detail.Color)); + } + + pda.StationAlertLevels = stationAlertLevels; + // Sunrise edit end if (alertComp.AlertLevels.Levels.TryGetValue(alertComp.CurrentLevel, out var details)) pda.StationAlertColor = details.Color; } diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 1a38994ee42..797489d5221 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -148,9 +148,21 @@ public void RequestRoundEnd(EntityUid? requester = null, EntityUid? machine = nu var stationUid = _stationSystem.GetOwningStation(requester.Value); if (TryComp(stationUid, out var alertLevel)) { - duration = _protoManager + var alertLevels = _protoManager .Index(AlertLevelSystem.DefaultAlertLevelSet) - .Levels[alertLevel.CurrentLevel].ShuttleTime; + .Levels; + duration = alertLevels[alertLevel.CurrentLevel].ShuttleTime; + + // Sunrise edit start - дополнительные коды сохраняют свои ограничения эвакуации + foreach (var additionalLevel in alertLevel.ActiveAdditionalLevels) + { + if (alertLevels.TryGetValue(additionalLevel, out var detail) + && detail.ShuttleTime > duration) + { + duration = detail.ShuttleTime; + } + } + // Sunrise edit end } } diff --git a/Content.Server/Weapons/Ranged/Conditions/AlertLevelCondition.cs b/Content.Server/Weapons/Ranged/Conditions/AlertLevelCondition.cs index c1d17bc85a8..8c2e4374d91 100644 --- a/Content.Server/Weapons/Ranged/Conditions/AlertLevelCondition.cs +++ b/Content.Server/Weapons/Ranged/Conditions/AlertLevelCondition.cs @@ -25,10 +25,16 @@ public override bool Condition(FireModeConditionConditionArgs args) if (entityManager.TryGetComponent(transformComp.ParentUid, out var stationMember) && entityManager.TryGetComponent(stationMember.Station, out var alertLevelComp)) { - var currentAlertLevel = alertSystem.GetLevel(stationMember.Station, alertLevelComp); - return AlertLevels.Contains(currentAlertLevel); + // Sunrise-Edit - условия учитывают как основной, так и дополнительные коды + foreach (var alertLevel in AlertLevels) + { + if (alertSystem.IsLevelActive((stationMember.Station, alertLevelComp), alertLevel)) + return true; + } + + return false; } return false; } -} \ No newline at end of file +} diff --git a/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs b/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs new file mode 100644 index 00000000000..f1b0a7aad61 --- /dev/null +++ b/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs @@ -0,0 +1,11 @@ +#pragma warning disable IDE0130 // Пространство имён соответствует расширяемому upstream-компоненту. +namespace Content.Server.AlertLevel; + +public sealed partial class AlertLevelComponent +{ + /// + /// Дополнительные коды, действующие одновременно с основным кодом станции. + /// + [ViewVariables(VVAccess.ReadOnly)] + public readonly HashSet ActiveAdditionalLevels = []; +} diff --git a/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs new file mode 100644 index 00000000000..fa0d5702232 --- /dev/null +++ b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs @@ -0,0 +1,196 @@ +using Content.Server._Sunrise.StationEvents.Events; + +#pragma warning disable IDE0130 // Пространство имён соответствует расширяемой upstream-системе. +namespace Content.Server.AlertLevel; + +public sealed partial class AlertLevelSystem +{ + /// + /// Проверяет, можно ли изменить дополнительный код станции. + /// + public bool CanSetAdditionalLevel( + Entity station, + string level, + bool enabled, + bool force = false) + { + if (!Resolve(station, ref station.Comp) + || station.Comp.AlertLevels == null + || !station.Comp.AlertLevels.Levels.TryGetValue(level, out var detail) + || !detail.IsAdditional + || station.Comp.ActiveAdditionalLevels.Contains(level) == enabled) + { + return false; + } + + if (force) + return true; + + if (!station.Comp.AlertLevels.Levels.TryGetValue(station.Comp.CurrentLevel, out var currentDetail)) + return false; + + if (!currentDetail.Selectable || currentDetail.DisableSelection) + return false; + + foreach (var additionalLevel in station.Comp.ActiveAdditionalLevels) + { + if (additionalLevel == level) + continue; + + if (station.Comp.AlertLevels.Levels.TryGetValue(additionalLevel, out var additionalDetail) + && additionalDetail.DisableSelection) + { + return false; + } + } + + return detail.Selectable + && !detail.DisableSelection + && !station.Comp.IsLevelLocked; + } + + /// + /// Пытается явно включить или выключить дополнительный код станции. + /// + public bool TrySetAdditionalLevel( + EntityUid station, + string level, + bool enabled, + bool playSound, + bool announce, + bool force = false, + AlertLevelComponent? component = null) + { + var stationEntity = new Entity(station, component); + if (!CanSetAdditionalLevel(stationEntity, level, enabled, force)) + return false; + + Resolve(stationEntity, ref stationEntity.Comp); + DoSetAdditionalLevel((station, stationEntity.Comp!), level, enabled, playSound, announce); + return true; + } + + /// + /// Возвращает, действует ли указанный основной или дополнительный код. + /// + public bool IsLevelActive(Entity station, string level) + { + if (!Resolve(station, ref station.Comp)) + return false; + + return station.Comp.CurrentLevel == level || station.Comp.ActiveAdditionalLevels.Contains(level); + } + + /// + /// Возвращает основной код и все активные дополнительные коды в порядке прототипа. + /// + public List GetActiveLevels(Entity station) + { + var result = new List(); + if (!Resolve(station, ref station.Comp) || station.Comp.AlertLevels == null) + return result; + + result.Add(station.Comp.CurrentLevel); + foreach (var level in station.Comp.AlertLevels.Levels.Keys) + { + if (station.Comp.ActiveAdditionalLevels.Contains(level)) + result.Add(level); + } + + return result; + } + + private void DoSetAdditionalLevel( + Entity station, + string level, + bool enabled, + bool playSound, + bool announce) + { + var detail = station.Comp.AlertLevels!.Levels[level]; + + if (enabled) + station.Comp.ActiveAdditionalLevels.Add(level); + else + station.Comp.ActiveAdditionalLevels.Remove(level); + + if (announce) + AnnounceAdditionalLevel(station, level, detail, enabled, playSound); + + if (enabled) + ApplySpecialAlertLevelBehavior(station, level, detail); + + RaiseLocalEvent(new AdditionalAlertLevelChangedEvent(station, level, enabled)); + } + + private void AnnounceAdditionalLevel( + EntityUid station, + string level, + AlertLevelDetail detail, + bool enabled, + bool playSound) + { + var name = level.ToLowerInvariant(); + if (Loc.TryGetString($"alert-level-{level}", out var localizedName)) + name = localizedName.ToLowerInvariant(); + + string announcement; + if (enabled) + { + announcement = detail.Announcement; + if (Loc.TryGetString(detail.Announcement, out var localizedAnnouncement)) + announcement = localizedAnnouncement; + + announcement = Loc.GetString("alert-level-announcement", ("name", name), ("announcement", announcement)); + } + else + { + announcement = Loc.GetString("alert-level-additional-disabled-announcement", ("name", name)); + } + + _chatSystem.DispatchStationAnnouncement( + station, + announcement, + announcementSound: enabled && playSound ? detail.Sound : null, + playDefault: enabled && playSound && detail.Sound == null, + colorOverride: detail.Color, + sender: MetaData(station).EntityName); + } + + private void ApplySpecialAlertLevelBehavior(EntityUid station, string level, AlertLevelDetail detail) + { + if (detail.ForceEndRound) + _roundEnd.EndRound(); + + if (level != EpsilonAlertLevel) + return; + + var eventEnt = _gameTicker.AddGameRule(EpsilonBorgLawChanges); + var epsilonRule = EntityManager.System(); + epsilonRule.StartEvent(eventEnt, station); + _gameTicker.StartGameRule(eventEnt); + } + + private static void PruneAdditionalLevels(Entity station) + { + station.Comp.ActiveAdditionalLevels.RemoveWhere(level => + !station.Comp.AlertLevels!.Levels.TryGetValue(level, out var detail) || !detail.IsAdditional); + } +} + +/// +/// Вызывается после включения или выключения дополнительного кода станции. +/// +public sealed class AdditionalAlertLevelChangedEvent : EntityEventArgs +{ + public EntityUid Station { get; } + public string AlertLevel { get; } + public bool Enabled { get; } + + public AdditionalAlertLevelChangedEvent(EntityUid station, string alertLevel, bool enabled) + { + Station = station; + AlertLevel = alertLevel; + Enabled = enabled; + } +} diff --git a/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs b/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs index b71f4dfc8d9..848c49b0441 100644 --- a/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs +++ b/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs @@ -18,6 +18,7 @@ public sealed class CommunicationsConsoleInterfaceState : BoundUserInterfaceStat public List? AlertLevels; public string CurrentAlert; public float CurrentAlertDelay; + public readonly List AdditionalAlertLevels; // Sunrise-Edit // Sunrise-Start public readonly bool CanRelay; public readonly bool IsRelaying; @@ -25,7 +26,7 @@ public sealed class CommunicationsConsoleInterfaceState : BoundUserInterfaceStat public readonly float RelayTimeRemaining; // Sunrise-End - public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null, bool canRelay = false, bool isRelaying = false, float relayCooldownRemaining = 0f, float relayTimeRemaining = 0f) // Sunrise-Edit + public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null, bool canRelay = false, bool isRelaying = false, float relayCooldownRemaining = 0f, float relayTimeRemaining = 0f, List? additionalAlertLevels = null) // Sunrise-Edit { CanAnnounce = canAnnounce; CanCall = canCall; @@ -34,6 +35,7 @@ public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List< AlertLevels = alertLevels; CurrentAlert = currentAlert; CurrentAlertDelay = currentAlertDelay; + AdditionalAlertLevels = additionalAlertLevels ?? []; // Sunrise-Edit // Sunrise-Start CanRelay = canRelay; IsRelaying = isRelaying; @@ -43,6 +45,36 @@ public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List< } } + // Sunrise added start - сетевой контракт дополнительных кодов + [Serializable, NetSerializable] + public sealed class CommunicationsConsoleAdditionalAlertLevelState + { + public readonly string Level; + public readonly bool Enabled; + public readonly bool Selectable; + + public CommunicationsConsoleAdditionalAlertLevelState(string level, bool enabled, bool selectable) + { + Level = level; + Enabled = enabled; + Selectable = selectable; + } + } + + [Serializable, NetSerializable] + public sealed class CommunicationsConsoleSetAdditionalAlertLevelMessage : BoundUserInterfaceMessage + { + public readonly string Level; + public readonly bool Enabled; + + public CommunicationsConsoleSetAdditionalAlertLevelMessage(string level, bool enabled) + { + Level = level; + Enabled = enabled; + } + } + // Sunrise added end + [Serializable, NetSerializable] public sealed class CommunicationsConsoleSelectAlertLevelMessage : BoundUserInterfaceMessage { diff --git a/Content.Shared/PDA/PdaComponent.cs b/Content.Shared/PDA/PdaComponent.cs index 94b4c689dfc..7b62234de62 100644 --- a/Content.Shared/PDA/PdaComponent.cs +++ b/Content.Shared/PDA/PdaComponent.cs @@ -40,6 +40,7 @@ public sealed partial class PdaComponent : Component [ViewVariables(VVAccess.ReadWrite)] public EntityUid? PdaOwner; [ViewVariables] public string? StationName; [ViewVariables] public string? StationAlertLevel; + [ViewVariables] public List StationAlertLevels = []; // Sunrise-Edit [ViewVariables] public Color StationAlertColor = Color.White; [ViewVariables] public TimeSpan? ShuttleTime; // Sunrise-edit [ViewVariables] public EvacShuttleStatus ShuttleStatus; // Sunrise-edit diff --git a/Content.Shared/PDA/PdaUpdateState.cs b/Content.Shared/PDA/PdaUpdateState.cs index 726433d6601..637a187d0b9 100644 --- a/Content.Shared/PDA/PdaUpdateState.cs +++ b/Content.Shared/PDA/PdaUpdateState.cs @@ -48,11 +48,27 @@ public struct PdaIdInfoText public string? IdOwner; public string? JobTitle; public string? StationAlertLevel; + public List? StationAlertLevels; // Sunrise-Edit public Color StationAlertColor; public TimeSpan? EvacShuttleTime; // Sunrise-edit public EvacShuttleStatus EvacShuttleStatus; // Sunrise-edit } + // Sunrise added start - цвет каждого активного кода для интерфейса КПК + [Serializable, NetSerializable] + public sealed class PdaAlertLevelInfo + { + public readonly string Level; + public readonly Color Color; + + public PdaAlertLevelInfo(string level, Color color) + { + Level = level; + Color = color; + } + } + // Sunrise added end + // Sunrise-start public enum EvacShuttleStatus { diff --git a/Resources/Locale/en-US/_strings/_sunrise/communications/codes.ftl b/Resources/Locale/en-US/_strings/_sunrise/communications/codes.ftl new file mode 100644 index 00000000000..6af96fe1792 --- /dev/null +++ b/Resources/Locale/en-US/_strings/_sunrise/communications/codes.ftl @@ -0,0 +1,2 @@ +comms-console-menu-additional-alert-level-header = Additional alert codes +alert-level-additional-disabled-announcement = Additional alert code "{ $name }" has been lifted. diff --git a/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl b/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl index dda4c0cbc64..de7ac900bc9 100644 --- a/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl +++ b/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl @@ -1,7 +1,9 @@ cmd-setalertlevel-desc = Set current station alert level for grid on which the player is standing. -cmd-setalertlevel-help = Usage: setalertlevel [locked] +# Sunrise edit start - описание отдельной семантики основного и дополнительного кодов. +cmd-setalertlevel-help = Usage: setalertlevel [locked/enabled]. For a primary alert level, the optional boolean locks crew selection. For an additional alert level, omit it or use true to enable the code, and use false to disable it. cmd-setalertlevel-invalid-grid = You must be on grid of station code that you are going to change. cmd-setalertlevel-invalid-level = Specified alert level does not exist on that grid. cmd-setalertlevel-hint-1 = -cmd-setalertlevel-hint-2 = [locked] +cmd-setalertlevel-hint-2 = [locked/enabled] +# Sunrise edit end diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/communications/codes.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/communications/codes.ftl new file mode 100644 index 00000000000..de36a9c1f48 --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/_sunrise/communications/codes.ftl @@ -0,0 +1,2 @@ +comms-console-menu-additional-alert-level-header = Дополнительные коды угрозы +alert-level-additional-disabled-announcement = Дополнительный код угрозы «{ $name }» отменён. diff --git a/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl b/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl index 342bc7b1b42..127428b1c59 100644 --- a/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl +++ b/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl @@ -1,6 +1,8 @@ cmd-setalertlevel-desc = Изменяет уровень угрозы на станции, на сетке которой находится игрок. -cmd-setalertlevel-help = Использование: setalertlevel [locked] +# Sunrise edit start - описание отдельной семантики основного и дополнительного кодов. +cmd-setalertlevel-help = Использование: setalertlevel <уровень> [блокировка/включён]. Для основного кода необязательное логическое значение блокирует смену кода экипажем. Для дополнительного кода отсутствие значения или true включает код, а false снимает его. cmd-setalertlevel-invalid-grid = Вы должны находиться на сетке станции, код которой собираетесь изменить. cmd-setalertlevel-invalid-level = Указанный уровень угрозы не существует на этой сетке. cmd-setalertlevel-hint-1 = -cmd-setalertlevel-hint-2 = [locked] +cmd-setalertlevel-hint-2 = [блокировка/включён] +# Sunrise edit end diff --git a/Resources/Prototypes/AlertLevels/alert_levels.yml b/Resources/Prototypes/AlertLevels/alert_levels.yml index dfd6b488913..ac20136a1bc 100644 --- a/Resources/Prototypes/AlertLevels/alert_levels.yml +++ b/Resources/Prototypes/AlertLevels/alert_levels.yml @@ -23,6 +23,7 @@ announcement: access-system-accesses-delay-blue # Sunrise edit end violet: + isAdditional: true # Sunrise-Edit announcement: alert-level-violet-announcement sound: /Audio/Misc/notice1.ogg color: Violet @@ -30,6 +31,7 @@ forceEnableEmergencyLights: true shuttleTime: 600 yellow: + isAdditional: true # Sunrise-Edit announcement: alert-level-yellow-announcement sound: /Audio/Misc/notice1.ogg color: Yellow @@ -82,6 +84,7 @@ announcement: access-system-accesses-delay-gamma # Sunrise edit end delta: + isAdditional: true # Sunrise-Edit announcement: alert-level-delta-announcement selectable: false sound: @@ -98,6 +101,7 @@ delay: 0 # Sunrise edit end epsilon: + isAdditional: true # Sunrise-Edit announcement: alert-level-epsilon-announcement selectable: false sound: From 667a04a9fe3709638d408beabd673c80464849eb Mon Sep 17 00:00:00 2001 From: Ende Date: Tue, 8 Sep 2026 19:31:05 +0200 Subject: [PATCH 02/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BE=D0=B4=D0=BE=D0=B2=20?= =?UTF-8?q?=D1=83=D0=B3=D1=80=D0=BE=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommunicationsConsoleBoundUserInterface.cs | 29 ++- .../UI/CommunicationsConsoleMenu.xaml.cs | 6 +- .../AlertLevel/AdditionalAlertLevelTest.cs | 223 ++++++++++++++---- .../AlertLevel/AlertLevelDisplaySystem.cs | 27 ++- .../AlertLevel/AlertLevelPrototype.cs | 16 +- .../Commands/SetAlertLevelCommand.cs | 13 +- .../CommunicationsConsoleComponent.cs | 14 ++ .../CommunicationsConsoleSystem.cs | 87 ++++++- .../EntitySystems/EmergencyLightSystem.cs | 45 ++-- Content.Server/Nuke/NukeSystem.cs | 47 +++- Content.Server/RoundEnd/RoundEndSystem.cs | 15 +- .../AlertLevelComponent.Additional.cs | 2 +- .../AlertLevel/AlertLevelSystem.Additional.cs | 57 ++++- .../ExtendedAccess/ExtendedAccessStuff.cs | 15 ++ .../ExtendedAccess/ExtendedAccessSystem.cs | 93 ++++++-- .../Components/AccessReaderComponent.cs | 11 +- .../Access/Systems/AccessReaderSystem.cs | 75 +++++- .../alert-levels/alert-level-command.ftl | 1 + .../alert-levels/alert-level-command.ftl | 1 + .../Prototypes/AlertLevels/alert_levels.yml | 10 + .../Entities/Mobs/Player/silicon.yml | 1 + .../Machines/Computers/computers.yml | 4 + .../Access/AccessGroup/access_group.yml | 1 + .../Machines/Computers/starlight_compat.yml | 6 + .../Structures/Machines/computers.yml | 4 +- 25 files changed, 677 insertions(+), 126 deletions(-) diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs index ad2adc4b0b5..6bbc91444b4 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs @@ -1,6 +1,8 @@ -using Content.Shared.CCVar; +using Content.Shared.Access.Systems; +using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Communications; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Shared.Configuration; using Robust.Shared.Timing; @@ -11,11 +13,16 @@ public sealed class CommunicationsConsoleBoundUserInterface : BoundUserInterface { [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IPlayerManager _player = default!; // Sunrise-Edit + + private readonly AccessReaderSystem _accessReader; // Sunrise-Edit + [ViewVariables] private CommunicationsConsoleMenu? _menu; public CommunicationsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { + _accessReader = EntMan.System(); // Sunrise-Edit } protected override void Open() @@ -34,13 +41,16 @@ protected override void Open() // Sunrise added start - дополнительные коды переключаются независимо private void AdditionalAlertLevelSelected(string level, bool enabled) { + if (!HasAccess()) + return; + SendMessage(new CommunicationsConsoleSetAdditionalAlertLevelMessage(level, enabled)); } // Sunrise added end public void AlertLevelSelected(string level) { - if (_menu!.AlertLevelSelectable) + if (_menu!.AlertLevelSelectable && HasAccess()) // Sunrise-Edit { _menu.CurrentLevel = level; SendMessage(new CommunicationsConsoleSelectAlertLevelMessage(level)); @@ -93,17 +103,21 @@ protected override void UpdateState(BoundUserInterfaceState state) if (_menu != null) { + var hasAccess = HasAccess(); // Sunrise-Edit _menu.CanAnnounce = commsState.CanAnnounce; _menu.CanBroadcast = commsState.CanBroadcast; _menu.CanCall = commsState.CanCall; _menu.CountdownStarted = commsState.CountdownStarted; - _menu.AlertLevelSelectable = commsState.AlertLevels != null && !float.IsNaN(commsState.CurrentAlertDelay) && commsState.CurrentAlertDelay <= 0; + _menu.AlertLevelSelectable = hasAccess + && commsState.AlertLevels != null + && !float.IsNaN(commsState.CurrentAlertDelay) + && commsState.CurrentAlertDelay <= 0; // Sunrise-Edit _menu.CurrentLevel = commsState.CurrentAlert; _menu.CountdownEnd = commsState.ExpectedCountdownEnd; _menu.UpdateCountdown(); _menu.UpdateAlertLevels(commsState.AlertLevels, _menu.CurrentLevel); - _menu.UpdateAdditionalAlertLevels(commsState.AdditionalAlertLevels); // Sunrise-Edit + _menu.UpdateAdditionalAlertLevels(commsState.AdditionalAlertLevels, hasAccess); // Sunrise-Edit _menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable; _menu.EmergencyShuttleButton.Disabled = !_menu.CanCall; _menu.AnnounceButton.Disabled = !_menu.CanAnnounce; @@ -118,5 +132,12 @@ protected override void UpdateState(BoundUserInterfaceState state) // Sunrise-End } } + + // Sunrise added start - сразу блокируем управление кодами без требуемого доступа + private bool HasAccess() + { + return _player.LocalEntity is { } player && _accessReader.IsAllowed(player, Owner); + } + // Sunrise added end } } diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs index f9566951efd..b3864094546 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs @@ -135,7 +135,9 @@ public void UpdateAlertLevels(List? alerts, string currentAlert) } // Sunrise added start - независимые переключатели дополнительных кодов - public void UpdateAdditionalAlertLevels(List alerts) + public void UpdateAdditionalAlertLevels( + List alerts, + bool hasAccess) { AdditionalAlertLevelsContainer.RemoveAllChildren(); foreach (var alert in alerts) @@ -149,7 +151,7 @@ public void UpdateAdditionalAlertLevels(List { @@ -40,19 +42,43 @@ await server.WaitPost(() => alertLevel.CurrentLevel = "green"; alertLevel.CurrentDelay = 30; - alertLevelSystem.SetLevel(station, "yellow", false, false, component: alertLevel); - yellowEnabled = alertLevel.ActiveAdditionalLevels.Contains("yellow"); - primaryStayedGreen = alertLevel.CurrentLevel == "green"; - additionalIgnoredPrimaryCooldown = alertLevel.CurrentDelay == 30; - violetEnabled = alertLevelSystem.TrySetAdditionalLevel( + blockedByPrimaryCooldown = !alertLevelSystem.TrySetAdditionalLevel( + station, + "yellow", + true, + playSound: false, + announce: false, + component: alertLevel); + + alertLevel.CurrentDelay = 0; + alertLevel.ActiveDelay = false; + yellowEnabled = alertLevelSystem.TrySetAdditionalLevel( + station, + "yellow", + true, + playSound: false, + announce: false, + component: alertLevel); + + forcedVioletEnabled = alertLevelSystem.TrySetAdditionalLevel( station, "violet", true, playSound: false, announce: false, + force: true, + component: alertLevel); + + yellowDisableBlockedByCooldown = !alertLevelSystem.TrySetAdditionalLevel( + station, + "yellow", + false, + playSound: false, + announce: false, component: alertLevel); + alertLevel.CurrentDelay = 0; - alertLevelSystem.SetLevel(station, "red", false, false, true, component: alertLevel); + alertLevel.ActiveDelay = false; yellowDisabled = alertLevelSystem.TrySetAdditionalLevel( station, "yellow", @@ -60,25 +86,29 @@ await server.WaitPost(() => playSound: false, announce: false, component: alertLevel); - disablingDidNotStartCooldown = alertLevel.CurrentDelay == 0; + + alertLevel.ActiveAdditionalLevels.Add("epsilon"); + epsilonDisableBlocked = !alertLevelSystem.CanSetAdditionalLevel( + (station, alertLevel), + "epsilon", + false, + force: true); }); await server.WaitAssertion(() => { Assert.Multiple(() => { + Assert.That(blockedByPrimaryCooldown, Is.True); Assert.That(yellowEnabled, Is.True); - Assert.That(primaryStayedGreen, Is.True); - Assert.That(additionalIgnoredPrimaryCooldown, Is.True); - Assert.That(violetEnabled, Is.True); + Assert.That(forcedVioletEnabled, Is.True); + Assert.That(yellowDisableBlockedByCooldown, Is.True); Assert.That(yellowDisabled, Is.True); - Assert.That(disablingDidNotStartCooldown, Is.True); - Assert.That(alertLevel!.CurrentLevel, Is.EqualTo("red")); - Assert.That(alertLevel.ActiveAdditionalLevels, Is.EquivalentTo(new[] { "violet" })); - var stationAlertLevel = new Entity(station, alertLevel); - Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "red"), Is.True); - Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "violet"), Is.True); - Assert.That(alertLevelSystem.IsLevelActive(stationAlertLevel, "yellow"), Is.False); + Assert.That(epsilonDisableBlocked, Is.True); + Assert.That(alertLevel.CurrentLevel, Is.EqualTo("green")); + Assert.That(alertLevel.ActiveDelay, Is.True); + Assert.That(alertLevel.CurrentDelay, Is.GreaterThan(0)); + Assert.That(alertLevel.ActiveAdditionalLevels, Is.EquivalentTo(new[] { "violet", "epsilon" })); }); }); @@ -86,48 +116,161 @@ await server.WaitAssertion(() => } [Test] - public async Task AdditionalLevelKeepsPrimaryEmergencyAccesses() + public async Task VisualPrioritySelectsHighestActiveLevel() { await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.EntMan; var prototypeManager = server.ResolveDependency(); var alertLevelSystem = server.System(); - var accessReaderSystem = server.System(); + EntityUid station = default; AlertLevelComponent alertLevel = null!; - AccessReaderComponent accessReader = null!; - var additionalLevelEnabled = false; + var effectiveLevels = new List(); await server.WaitPost(() => { - var station = entityManager.SpawnEntity(null, MapCoordinates.Nullspace); + station = entityManager.SpawnEntity(null, MapCoordinates.Nullspace); alertLevel = entityManager.AddComponent(station); alertLevel.AlertLevels = prototypeManager.Index(AlertLevelSystem.DefaultAlertLevelSet); + alertLevel.CurrentLevel = "green"; + + alertLevel.ActiveAdditionalLevels.Add("yellow"); + effectiveLevels.Add(alertLevelSystem.TryGetVisualAlertLevel((station, alertLevel), out var level, out _) + ? level + : string.Empty); + alertLevel.CurrentLevel = "red"; + effectiveLevels.Add(alertLevelSystem.TryGetVisualAlertLevel((station, alertLevel), out level, out _) + ? level + : string.Empty); + + alertLevel.ActiveAdditionalLevels.Add("delta"); + effectiveLevels.Add(alertLevelSystem.TryGetVisualAlertLevel((station, alertLevel), out level, out _) + ? level + : string.Empty); + + alertLevel.ActiveAdditionalLevels.Add("epsilon"); + effectiveLevels.Add(alertLevelSystem.TryGetVisualAlertLevel((station, alertLevel), out level, out _) + ? level + : string.Empty); + }); + + await server.WaitAssertion(() => + { + Assert.That(effectiveLevels, Is.EqualTo(new[] { "yellow", "red", "delta", "epsilon" })); + }); + + await pair.CleanReturnAsync(); + } + [Test] + public async Task AdditionalLevelKeepsPrimaryEmergencyAccesses() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + var entityManager = server.EntMan; + var accessReaderSystem = server.System(); + + AccessReaderComponent accessReader = null!; + var securityAllowed = false; + var atmosphericsAllowed = false; + var engineeringAllowed = false; + + await server.WaitPost(() => + { var reader = entityManager.SpawnEntity("DoorElectronicsLawyer", MapCoordinates.Nullspace); accessReader = entityManager.GetComponent(reader); - accessReaderSystem.UpdateAccess((reader, accessReader), alertLevel.CurrentLevel); - additionalLevelEnabled = alertLevelSystem.TrySetAdditionalLevel( - station, - "yellow", - true, - playSound: false, - announce: false, - force: true, - component: alertLevel); + accessReaderSystem.UpdateAccess( + (reader, accessReader), + new[] { "red", "yellow" }, + new HashSet> + { + new("YellowAlertAccesses"), + }); + + securityAllowed = accessReaderSystem.IsAccessAllowedByExtendedAccess( + new HashSet> { new("Security") }, + accessReader); + atmosphericsAllowed = accessReaderSystem.IsAccessAllowedByExtendedAccess( + new HashSet> { new("Atmospherics") }, + accessReader); + engineeringAllowed = accessReaderSystem.IsAccessAllowedByExtendedAccess( + new HashSet> { new("Engineering") }, + accessReader); }); await server.WaitAssertion(() => { Assert.Multiple(() => { - Assert.That(additionalLevelEnabled, Is.True); - Assert.That(alertLevel.CurrentLevel, Is.EqualTo("red")); - Assert.That(accessReader.Group, - Is.EqualTo(new ProtoId("RedAlertAccesses"))); + Assert.That(accessReader.Group, Is.EqualTo(new ProtoId("RedAlertAccesses"))); + Assert.That(accessReader.AdditionalGroups, + Does.Contain(new ProtoId("YellowAlertAccesses"))); + Assert.That(securityAllowed, Is.True); + Assert.That(atmosphericsAllowed, Is.True); + Assert.That(engineeringAllowed, Is.True); + }); + }); + + await pair.CleanReturnAsync(); + } + + [Test] + public async Task ConsoleAlertLevelAllowlistIsEnforced() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + var prototypeManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var levels = prototypeManager.Index(AlertLevelSystem.DefaultAlertLevelSet).Levels; + var engineeringConsole = new CommunicationsConsoleComponent + { + AllowedAlertLevels = ["yellow"], + }; + var disabledConsole = new CommunicationsConsoleComponent + { + AllowedAlertLevels = [], + }; + var centCommConsole = new CommunicationsConsoleComponent + { + AllowedAlertLevels = ["green", "blue", "violet", "yellow", "red", "gamma", "epsilon"], + ForceAlertLevelChanges = true, + }; + + Assert.Multiple(() => + { + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + engineeringConsole, + "yellow", + levels["yellow"]), Is.True); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + engineeringConsole, + "violet", + levels["violet"]), Is.False); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + engineeringConsole, + "green", + levels["green"]), Is.False); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + disabledConsole, + "red", + levels["red"]), Is.False); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + centCommConsole, + "gamma", + levels["gamma"]), Is.True); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + centCommConsole, + "epsilon", + levels["epsilon"]), Is.True); + Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( + centCommConsole, + "delta", + levels["delta"]), Is.False); }); }); diff --git a/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs b/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs index 3dd216c5dce..525ff6f48fe 100644 --- a/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs +++ b/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs @@ -7,33 +7,54 @@ namespace Content.Server.AlertLevel; public sealed class AlertLevelDisplaySystem : EntitySystem { + [Dependency] private readonly AlertLevelSystem _alertLevel = default!; // Sunrise-Edit [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; public override void Initialize() { SubscribeLocalEvent(OnAlertChanged); + SubscribeLocalEvent(OnAdditionalAlertChanged); // Sunrise-Edit SubscribeLocalEvent(OnDisplayInit); SubscribeLocalEvent(OnPowerChanged); } private void OnAlertChanged(AlertLevelChangedEvent args) { + UpdateDisplays(args.Station); // Sunrise-Edit + } + + // Sunrise added start - дисплеи показывают активный код с наивысшим визуальным приоритетом + private void OnAdditionalAlertChanged(AdditionalAlertLevelChangedEvent args) + { + UpdateDisplays(args.Station); + } + + private void UpdateDisplays(EntityUid station) + { + if (!_alertLevel.TryGetVisualAlertLevel((station, null), out var level, out _)) + return; + var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out _, out var appearance)) { - _appearance.SetData(uid, AlertLevelDisplay.CurrentLevel, args.AlertLevel, appearance); + if (_stationSystem.GetOwningStation(uid) != station) + continue; + + _appearance.SetData(uid, AlertLevelDisplay.CurrentLevel, level, appearance); } } + // Sunrise added end private void OnDisplayInit(EntityUid uid, AlertLevelDisplayComponent alertLevelDisplay, ComponentInit args) { if (TryComp(uid, out AppearanceComponent? appearance)) { var stationUid = _stationSystem.GetOwningStation(uid); - if (stationUid != null && TryComp(stationUid, out AlertLevelComponent? alert)) + if (stationUid != null + && _alertLevel.TryGetVisualAlertLevel((stationUid.Value, null), out var level, out _)) { - _appearance.SetData(uid, AlertLevelDisplay.CurrentLevel, alert.CurrentLevel, appearance); + _appearance.SetData(uid, AlertLevelDisplay.CurrentLevel, level, appearance); } } } diff --git a/Content.Server/AlertLevel/AlertLevelPrototype.cs b/Content.Server/AlertLevel/AlertLevelPrototype.cs index 087117524f2..4a434a78242 100644 --- a/Content.Server/AlertLevel/AlertLevelPrototype.cs +++ b/Content.Server/AlertLevel/AlertLevelPrototype.cs @@ -31,10 +31,24 @@ public sealed partial class AlertLevelPrototype : IPrototype public sealed partial class AlertLevelDetail { /// - /// Определяет код как дополнительный протокол, который может действовать одновременно с основным кодом. + /// Whether this level is an additional protocol that can be active alongside the primary alert level. /// [DataField] public bool IsAdditional { get; private set; } // Sunrise-Edit + // Sunrise added start - правила дополнительных кодов и выбор визуального приоритета + /// + /// Whether an active additional alert level can be disabled. + /// + [DataField] + public bool CanBeDisabled { get; private set; } = true; + + /// + /// Determines which active alert level controls single-state visuals such as emergency lights. + /// + [DataField] + public int VisualPriority { get; private set; } + // Sunrise added end + /// /// What is announced upon this alert level change. Can be a localized string. /// diff --git a/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs b/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs index cb5695bcfab..55865191fc8 100644 --- a/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs +++ b/Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs @@ -77,7 +77,18 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) if (detail.IsAdditional) { var enabled = args.Length == 1 || option; - _alertLevelSystem.TrySetAdditionalLevel(stationUid.Value, level, enabled, true, true, true, alertLevelComp); + if (!_alertLevelSystem.TrySetAdditionalLevel( + stationUid.Value, + level, + enabled, + true, + true, + true, + alertLevelComp)) + { + shell.WriteError(LocalizationManager.GetString("cmd-setalertlevel-change-failed")); + } + return; } diff --git a/Content.Server/Communications/CommunicationsConsoleComponent.cs b/Content.Server/Communications/CommunicationsConsoleComponent.cs index 2606d584c60..4397b0bb7b8 100644 --- a/Content.Server/Communications/CommunicationsConsoleComponent.cs +++ b/Content.Server/Communications/CommunicationsConsoleComponent.cs @@ -80,6 +80,20 @@ public sealed partial class CommunicationsConsoleComponent : SharedCommunication [DataField] public bool AnnounceSentBy = false; + // Sunrise added start - ограничения управления кодами для разных типов консолей + /// + /// Alert levels this console may set. A null value allows every crew-selectable level. + /// + [DataField] + public HashSet? AllowedAlertLevels; + + /// + /// Whether this console may set allowed non-selectable levels and bypass alert selection locks. + /// + [DataField] + public bool ForceAlertLevelChanges; + // Sunrise added end + // Sunrise-Start [DataField] public ProtoId? AnnounceVoice = "Hanson"; diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index 4d469ac9221..217508ef9a9 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -183,29 +183,36 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp if (TryComp(stationUid.Value, out AlertLevelComponent? alertComp) && alertComp.AlertLevels != null) { - if (alertComp.IsSelectable) + if (alertComp.IsSelectable || comp.ForceAlertLevelChanges) // Sunrise-Edit { levels = new(); foreach (var (id, detail) in alertComp.AlertLevels.Levels) { - if (detail.Selectable && !detail.IsAdditional) // Sunrise-Edit + if (!detail.IsAdditional && IsAlertLevelAllowed(comp, id, detail)) // Sunrise-Edit { levels.Add(id); } } + + if (levels.Count == 0) + levels = null; } - // Sunrise added start - обычная консоль показывает только доступные экипажу дополнительные коды + // Sunrise added start - консоль показывает только разрешённые для неё дополнительные коды foreach (var (id, detail) in alertComp.AlertLevels.Levels) { - if (!detail.IsAdditional || !detail.Selectable) + if (!detail.IsAdditional || !IsAlertLevelAllowed(comp, id, detail)) continue; var enabled = alertComp.ActiveAdditionalLevels.Contains(id); additionalLevels.Add(new CommunicationsConsoleAdditionalAlertLevelState( id, enabled, - _alertLevelSystem.CanSetAdditionalLevel((stationUid.Value, alertComp), id, !enabled))); + alertComp.CurrentDelay <= 0 && _alertLevelSystem.CanSetAdditionalLevel( + (stationUid.Value, alertComp), + id, + !enabled, + comp.ForceAlertLevelChanges))); } // Sunrise added end @@ -246,6 +253,17 @@ private bool CanUse(EntityUid user, EntityUid console) return true; } + /// + /// Checks whether this console is configured to control the specified alert level. + /// + public static bool IsAlertLevelAllowed( + CommunicationsConsoleComponent console, + string level, + AlertLevelDetail detail) + { + return console.AllowedAlertLevels?.Contains(level) ?? detail.Selectable; + } + private bool CanCallOrRecall(CommunicationsConsoleComponent comp) { // Defer to what the round end system thinks we should be able to do. @@ -283,10 +301,29 @@ private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleCompo } var stationUid = _stationSystem.GetOwningStation(uid); - if (stationUid != null) + if (stationUid == null + || !TryComp(stationUid.Value, out var alert) + || alert.AlertLevels == null + || !alert.AlertLevels.Levels.TryGetValue(message.Level, out var detail) + || detail.IsAdditional + || !IsAlertLevelAllowed(comp, message.Level, detail) + || alert.CurrentLevel == message.Level + || alert.CurrentDelay > 0) { - _alertLevelSystem.SetLevel(stationUid.Value, message.Level, true, true); + return; } + + // Привилегированная консоль обходит запрет выбора кода, но не общий cooldown ручных изменений. + if (comp.ForceAlertLevelChanges) + StartAlertLevelCooldown(alert); + + _alertLevelSystem.SetLevel( + stationUid.Value, + message.Level, + true, + true, + comp.ForceAlertLevelChanges, + component: alert); } // Sunrise added start - явное включение или выключение дополнительного кода @@ -308,16 +345,50 @@ private void OnSetAdditionalAlertLevelMessage( if (stationUid == null) return; + if (!TryComp(stationUid.Value, out var alert) + || alert.AlertLevels == null + || !alert.AlertLevels.Levels.TryGetValue(message.Level, out var detail) + || !detail.IsAdditional + || !IsAlertLevelAllowed(comp, message.Level, detail)) + { + return; + } + + var canChange = comp.ForceAlertLevelChanges + ? alert.CurrentDelay <= 0 && _alertLevelSystem.CanSetAdditionalLevel( + (stationUid.Value, alert), + message.Level, + message.Enabled, + force: true) + : true; + + if (!canChange) + { + UpdateCommsConsoleInterface(uid, comp); + return; + } + + if (comp.ForceAlertLevelChanges) + StartAlertLevelCooldown(alert); + if (!_alertLevelSystem.TrySetAdditionalLevel( stationUid.Value, message.Level, message.Enabled, playSound: true, - announce: true)) + announce: true, + force: comp.ForceAlertLevelChanges, + component: alert)) { UpdateCommsConsoleInterface(uid, comp); } } + + private void StartAlertLevelCooldown(AlertLevelComponent alert) + { + alert.CurrentDelay = _cfg.GetCVar(CCVars.GameAlertLevelChangeDelay); + alert.ActiveDelay = true; + } // Sunrise added end private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent comp, diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index 824afbc7890..1a871695471 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -18,6 +18,7 @@ namespace Content.Server.Light.EntitySystems; public sealed class EmergencyLightSystem : SharedEmergencyLightSystem { [Dependency] private readonly AmbientSoundSystem _ambient = default!; + [Dependency] private readonly AlertLevelSystem _alertLevel = default!; // Sunrise-Edit [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly PointLightSystem _pointLight = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -29,6 +30,7 @@ public override void Initialize() SubscribeLocalEvent(OnEmergencyLightEvent); SubscribeLocalEvent(OnAlertLevelChanged); + SubscribeLocalEvent(OnAdditionalAlertLevelChanged); // Sunrise-Edit SubscribeLocalEvent(OnEmergencyExamine); SubscribeLocalEvent(OnEmergencyPower); } @@ -57,22 +59,15 @@ private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component Loc.GetString(component.BatteryStateText[component.State])))); // Show alert level on the light itself. - if (!TryComp(_station.GetOwningStation(uid), out var alerts)) + var station = _station.GetOwningStation(uid); + if (!TryComp(station, out var alerts) + || !_alertLevel.TryGetVisualAlertLevel((station!.Value, alerts), out var name, out var details)) return; - if (alerts.AlertLevels == null) - return; - - var name = alerts.CurrentLevel; - - var color = Color.White; - if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) - color = details.Color; - args.PushMarkup( Loc.GetString("emergency-light-component-on-examine-alert", - ("color", color.ToHex()), - ("level", Loc.GetString($"alert-level-{name.ToString().ToLower()}")))); + ("color", details.Color.ToHex()), + ("level", Loc.GetString($"alert-level-{name.ToLowerInvariant()}")))); } } @@ -95,16 +90,24 @@ private void OnEmergencyLightEvent(EntityUid uid, EmergencyLightComponent compon private void OnAlertLevelChanged(AlertLevelChangedEvent ev) { - if (!TryComp(ev.Station, out var alert)) - return; + UpdateStationLights(ev.Station); + } - if (alert.AlertLevels == null || !alert.AlertLevels.Levels.TryGetValue(ev.AlertLevel, out var details)) + // Sunrise added start - дополнительные коды участвуют в выборе состояния аварийного освещения + private void OnAdditionalAlertLevelChanged(AdditionalAlertLevelChangedEvent ev) + { + UpdateStationLights(ev.Station); + } + + private void UpdateStationLights(EntityUid station) + { + if (!_alertLevel.TryGetVisualAlertLevel((station, null), out _, out var details)) return; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var light, out var pointLight, out var appearance, out var xform)) { - if (CompOrNull(xform.GridUid)?.Station != ev.Station) + if (CompOrNull(xform.GridUid)?.Station != station) continue; _pointLight.SetColor(uid, details.EmergencyLightColor, pointLight); @@ -123,6 +126,7 @@ private void OnAlertLevelChanged(AlertLevelChangedEvent ev) } } } + // Sunrise added end public void SetState(EntityUid uid, EmergencyLightComponent component, EmergencyLightState state) { @@ -174,10 +178,11 @@ public void UpdateState(Entity entity) if (!TryComp(entity.Owner, out var receiver)) return; - if (!TryComp(_station.GetOwningStation(entity.Owner), out var alerts)) + var station = _station.GetOwningStation(entity.Owner); + if (!TryComp(station, out var alerts)) return; - if (alerts.AlertLevels == null || !alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) + if (!_alertLevel.TryGetVisualAlertLevel((station!.Value, alerts), out _, out var details)) { TurnOff(entity, Color.Red); // if no alert, default to off red state return; @@ -186,7 +191,7 @@ public void UpdateState(Entity entity) if (receiver.Powered && !entity.Comp.ForciblyEnabled) // Green alert { receiver.Load = (int) Math.Abs(entity.Comp.Wattage); - TurnOff(entity, details.Color); + TurnOff(entity, details.EmergencyLightColor); // Sunrise-Edit SetState(entity.Owner, entity.Comp, EmergencyLightState.Charging); } else if (!receiver.Powered) // If internal battery runs out it will end in off red state @@ -196,7 +201,7 @@ public void UpdateState(Entity entity) } else // Powered and enabled { - TurnOn(entity, details.Color); + TurnOn(entity, details.EmergencyLightColor); // Sunrise-Edit SetState(entity.Owner, entity.Comp, EmergencyLightState.On); } } diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 6f7d20abca4..2022a291b4d 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -548,10 +548,30 @@ public void DisarmBomb(EntityUid uid, NukeComponent? component = null) if (component.Status != NukeStatus.ARMED) return; - var stationUid = _station.GetOwningStation(uid); - if (stationUid != null) + var stationUid = _station.GetStationInMap(Transform(uid).MapID); // Sunrise-Edit - совпадает с поиском станции при взводе + if (stationUid != null + && !HasOtherArmedNuke(uid, stationUid.Value, component.AlertLevelOnActivate)) { - _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true); + // Sunrise edit start - дополнительный Delta снимается без замены основного кода + if (TryComp(stationUid.Value, out var alert) + && alert.AlertLevels != null + && alert.AlertLevels.Levels.TryGetValue(component.AlertLevelOnActivate, out var activeDetail) + && activeDetail.IsAdditional) + { + _alertLevel.TrySetAdditionalLevel( + stationUid.Value, + component.AlertLevelOnActivate, + false, + true, + true, + true, + alert); + } + else + { + _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true); + } + // Sunrise edit end } // warn a crew @@ -584,6 +604,27 @@ public void DisarmBomb(EntityUid uid, NukeComponent? component = null) UpdateAppearance(uid, component); } + // Sunrise added start - код Delta остаётся активным, пока на станции взведена хотя бы одна бомба + private bool HasOtherArmedNuke(EntityUid excluded, EntityUid station, string alertLevel) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var nuke)) + { + if (uid == excluded + || nuke.Status != NukeStatus.ARMED + || nuke.AlertLevelOnActivate != alertLevel) + { + continue; + } + + if (_station.GetStationInMap(Transform(uid).MapID) == station) + return true; + } + + return false; + } + // Sunrise added end + /// /// Toggle bomb arm button /// diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 797489d5221..2ea5a0ac649 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -16,7 +16,6 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Content.Shared.DeviceNetwork.Components; using Content.Shared.Station.Components; @@ -39,7 +38,6 @@ public sealed partial class RoundEndSystem : EntitySystem [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly EmergencyShuttleSystem _shuttle = default!; [Dependency] private readonly StationSystem _stationSystem = default!; private bool _autoCalledBefore = false; @@ -146,17 +144,16 @@ public void RequestRoundEnd(EntityUid? requester = null, EntityUid? machine = nu if (requester != null) { var stationUid = _stationSystem.GetOwningStation(requester.Value); - if (TryComp(stationUid, out var alertLevel)) + if (TryComp(stationUid, out var alertLevel) + && alertLevel.AlertLevels is { } alertLevels) { - var alertLevels = _protoManager - .Index(AlertLevelSystem.DefaultAlertLevelSet) - .Levels; - duration = alertLevels[alertLevel.CurrentLevel].ShuttleTime; + // Sunrise edit start - используем фактический набор кодов станции + if (alertLevels.Levels.TryGetValue(alertLevel.CurrentLevel, out var currentDetail)) + duration = currentDetail.ShuttleTime; - // Sunrise edit start - дополнительные коды сохраняют свои ограничения эвакуации foreach (var additionalLevel in alertLevel.ActiveAdditionalLevels) { - if (alertLevels.TryGetValue(additionalLevel, out var detail) + if (alertLevels.Levels.TryGetValue(additionalLevel, out var detail) && detail.ShuttleTime > duration) { duration = detail.ShuttleTime; diff --git a/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs b/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs index f1b0a7aad61..ae8aa35004e 100644 --- a/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs +++ b/Content.Server/_Sunrise/AlertLevel/AlertLevelComponent.Additional.cs @@ -4,7 +4,7 @@ namespace Content.Server.AlertLevel; public sealed partial class AlertLevelComponent { /// - /// Дополнительные коды, действующие одновременно с основным кодом станции. + /// Additional alert levels currently active alongside the station's primary alert level. /// [ViewVariables(VVAccess.ReadOnly)] public readonly HashSet ActiveAdditionalLevels = []; diff --git a/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs index fa0d5702232..ee5c0fb1c00 100644 --- a/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs +++ b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs @@ -1,4 +1,5 @@ using Content.Server._Sunrise.StationEvents.Events; +using Content.Shared.CCVar; #pragma warning disable IDE0130 // Пространство имён соответствует расширяемой upstream-системе. namespace Content.Server.AlertLevel; @@ -6,7 +7,7 @@ namespace Content.Server.AlertLevel; public sealed partial class AlertLevelSystem { /// - /// Проверяет, можно ли изменить дополнительный код станции. + /// Checks whether an additional station alert level can be enabled or disabled. /// public bool CanSetAdditionalLevel( Entity station, @@ -18,6 +19,7 @@ public bool CanSetAdditionalLevel( || station.Comp.AlertLevels == null || !station.Comp.AlertLevels.Levels.TryGetValue(level, out var detail) || !detail.IsAdditional + || (!enabled && !detail.CanBeDisabled) || station.Comp.ActiveAdditionalLevels.Contains(level) == enabled) { return false; @@ -32,6 +34,9 @@ public bool CanSetAdditionalLevel( if (!currentDetail.Selectable || currentDetail.DisableSelection) return false; + if (station.Comp.CurrentDelay > 0) + return false; + foreach (var additionalLevel in station.Comp.ActiveAdditionalLevels) { if (additionalLevel == level) @@ -50,7 +55,7 @@ public bool CanSetAdditionalLevel( } /// - /// Пытается явно включить или выключить дополнительный код станции. + /// Attempts to explicitly enable or disable an additional station alert level. /// public bool TrySetAdditionalLevel( EntityUid station, @@ -66,12 +71,19 @@ public bool TrySetAdditionalLevel( return false; Resolve(stationEntity, ref stationEntity.Comp); + + if (!force) + { + stationEntity.Comp!.CurrentDelay = _cfg.GetCVar(CCVars.GameAlertLevelChangeDelay); + stationEntity.Comp.ActiveDelay = true; + } + DoSetAdditionalLevel((station, stationEntity.Comp!), level, enabled, playSound, announce); return true; } /// - /// Возвращает, действует ли указанный основной или дополнительный код. + /// Returns whether the specified primary or additional alert level is active. /// public bool IsLevelActive(Entity station, string level) { @@ -82,7 +94,7 @@ public bool IsLevelActive(Entity station, string level) } /// - /// Возвращает основной код и все активные дополнительные коды в порядке прототипа. + /// Returns the primary level followed by all active additional levels in prototype order. /// public List GetActiveLevels(Entity station) { @@ -100,6 +112,41 @@ public List GetActiveLevels(Entity station) return result; } + /// + /// Gets the active alert level with the highest visual priority. + /// + public bool TryGetVisualAlertLevel( + Entity station, + out string level, + out AlertLevelDetail detail) + { + level = string.Empty; + detail = default!; + + if (!Resolve(station, ref station.Comp) + || station.Comp.AlertLevels == null + || !station.Comp.AlertLevels.Levels.TryGetValue(station.Comp.CurrentLevel, out var currentDetail)) + { + return false; + } + + detail = currentDetail; + level = station.Comp.CurrentLevel; + foreach (var additionalLevel in station.Comp.ActiveAdditionalLevels) + { + if (!station.Comp.AlertLevels.Levels.TryGetValue(additionalLevel, out var additionalDetail) + || additionalDetail.VisualPriority <= detail.VisualPriority) + { + continue; + } + + level = additionalLevel; + detail = additionalDetail; + } + + return true; + } + private void DoSetAdditionalLevel( Entity station, string level, @@ -179,7 +226,7 @@ private static void PruneAdditionalLevels(Entity station) } /// -/// Вызывается после включения или выключения дополнительного кода станции. +/// Raised after an additional station alert level is enabled or disabled. /// public sealed class AdditionalAlertLevelChangedEvent : EntityEventArgs { diff --git a/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessStuff.cs b/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessStuff.cs index cbfdddcf6f2..4dda85a4cab 100644 --- a/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessStuff.cs +++ b/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessStuff.cs @@ -1,8 +1,23 @@ +using Content.Shared.Access; +using Robust.Shared.Prototypes; + namespace Content.Server._Sunrise.ExtendedAccess; [DataDefinition] public partial record struct ExtendedAccessOptions { + /// + /// Announcement played before the access update. + /// [DataField] public string? Announcement; + + /// + /// Delay before the access update is applied. + /// [DataField] public TimeSpan Delay = TimeSpan.FromSeconds(60); + + /// + /// Access group globally granted to readers participating in alert-level access changes. + /// + [DataField] public ProtoId? AccessGroup; } diff --git a/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessSystem.cs b/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessSystem.cs index 901f7b5a646..6a21fade61f 100644 --- a/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessSystem.cs +++ b/Content.Server/_Sunrise/ExtendedAccess/ExtendedAccessSystem.cs @@ -1,10 +1,12 @@ using System.Threading; using Content.Server.AlertLevel; using Content.Server.Chat.Systems; +using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.GameTicking; using Content.Shared.Station.Components; +using Robust.Shared.Prototypes; using Timer = Robust.Shared.Timing.Timer; namespace Content.Server._Sunrise.ExtendedAccess; @@ -13,21 +15,23 @@ public sealed class ExtendedAccessSystem : EntitySystem { [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly AlertLevelSystem _alertLevel = default!; - private static CancellationTokenSource _token = new(); + private readonly Dictionary _tokens = []; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAlertLevelChanged); + SubscribeLocalEvent(OnAdditionalAlertLevelChanged); - SubscribeLocalEvent(_ => RecreateToken()); + SubscribeLocalEvent(_ => CancelAllUpdates()); } /// - /// Запускает таймер и выводит объявление о смене доступов через некоторое время + /// Schedules an update of temporary access groups after an alert level change. /// private void OnAlertLevelChanged(AlertLevelChangedEvent ev) { @@ -45,42 +49,77 @@ private void OnAlertLevelChanged(AlertLevelChangedEvent ev) if (!alert.AlertLevels.Levels.TryGetValue(alert.CurrentLevel, out var currentLevelDetail)) return; - var options = currentLevelDetail.ExtendedAccessOptions; + if (currentLevelDetail.ExtendedAccessOptions is not { } options) + return; + + ScheduleAccessUpdate((ev.Station, alert), options); + } - if (options == null) + private void OnAdditionalAlertLevelChanged(AdditionalAlertLevelChangedEvent ev) + { + if (!TryComp(ev.Station, out var alert) + || alert.AlertLevels == null + || !alert.AlertLevels.Levels.TryGetValue(ev.AlertLevel, out var detail) + || detail.ExtendedAccessOptions is not { } options) + { return; + } - // Предотвращение стаканье смены доступов. Доступы должны сменяться только на последний код угрозы. - RecreateToken(); + ScheduleAccessUpdate((ev.Station, alert), options); + } + + private void ScheduleAccessUpdate(Entity station, ExtendedAccessOptions options) + { + // Отменяем отложенное изменение только на этой станции: применяется последнее состояние всех кодов. + CancelUpdate(station); + var token = new CancellationTokenSource(); + _tokens[station] = token; - Timer.Spawn(options.Value.Delay, () => AfterDelay((ev.Station, alert)), _token.Token); + Timer.Spawn(options.Delay, () => AfterDelay(station, token), token.Token); - if (options.Value.Announcement != null) + if (options.Announcement != null) { // В строке локализации оповещения обязательно должно быть указан параметр для времени - var message = Loc.GetString(options.Value.Announcement, ("time", options.Value.Delay.TotalSeconds)); + var message = Loc.GetString(options.Announcement, ("time", options.Delay.TotalSeconds)); - _chat.DispatchStationAnnouncement(ev.Station, - Loc.GetString(message), + _chat.DispatchStationAnnouncement(station, + message, colorOverride: Color.Yellow, sender: Loc.GetString("access-system-sender")); } } /// - /// Проходится по всем сущностям, считывающим доступ. - /// Заставляет пересмотреть свои доступы в соответствии с текущим кодом угрозы + /// Applies the combined temporary access groups from the primary and additional alert levels. /// - private void AfterDelay(Entity station) + private void AfterDelay(Entity station, CancellationTokenSource token) { - if (TerminatingOrDeleted(station)) + if (TerminatingOrDeleted(station) + || !_tokens.TryGetValue(station, out var currentToken) + || currentToken != token) + { return; + } + + _tokens.Remove(station); + token.Dispose(); _chat.DispatchStationAnnouncement(station, Loc.GetString("access-system-accesses-established"), colorOverride: Color.Yellow, sender: Loc.GetString("access-system-sender")); + var activeLevels = _alertLevel.GetActiveLevels(station.AsNullable()); + var globalGroups = new HashSet>(); + foreach (var level in activeLevels) + { + if (station.Comp.AlertLevels!.Levels.TryGetValue(level, out var detail) + && detail.ExtendedAccessOptions?.AccessGroup is { } group) + { + globalGroups.Add(group); + } + } + var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var reader, out var xform)) { @@ -90,13 +129,27 @@ private void AfterDelay(Entity station) if (reader.AlertAccesses.Count == 0) continue; - _accessReader.UpdateAccess((uid, reader), station.Comp.CurrentLevel); + _accessReader.UpdateAccess((uid, reader), activeLevels, globalGroups); } } - private static void RecreateToken() + private void CancelUpdate(EntityUid station) { - _token.Cancel(); - _token = new(); + if (!_tokens.Remove(station, out var token)) + return; + + token.Cancel(); + token.Dispose(); + } + + private void CancelAllUpdates() + { + foreach (var token in _tokens.Values) + { + token.Cancel(); + token.Dispose(); + } + + _tokens.Clear(); } } diff --git a/Content.Shared/Access/Components/AccessReaderComponent.cs b/Content.Shared/Access/Components/AccessReaderComponent.cs index 6e9fa964ad6..5d9fd662ff8 100644 --- a/Content.Shared/Access/Components/AccessReaderComponent.cs +++ b/Content.Shared/Access/Components/AccessReaderComponent.cs @@ -21,11 +21,17 @@ public sealed partial class AccessReaderComponent : Component #region ExtendedAccess /// - /// Именно от Group происходит проверка аварийных доступов + /// The primary access group temporarily granted by the current alert level. /// [ViewVariables(VVAccess.ReadWrite)] public ProtoId? Group; + /// + /// Additional access groups granted by simultaneously active alert levels. + /// + [ViewVariables(VVAccess.ReadWrite)] + public HashSet> AdditionalGroups = []; + [DataField, ViewVariables] public Dictionary> AlertAccesses = new(); @@ -135,6 +141,7 @@ public sealed class AccessReaderComponentState : ComponentState public List>> AccessLists; public List>>? AccessListsOriginal; public ProtoId? Group; // Sunrise-alertAccesses, нужно для связывания клиента с сервером + public HashSet> AdditionalGroups; // Sunrise-Edit public List<(NetEntity, uint)> AccessKeys; public Queue AccessLog; public int AccessLogLimit; @@ -145,6 +152,7 @@ public AccessReaderComponentState( List>> accessLists, List>>? accessListsOriginal, ProtoId? group, //Sunrise added + HashSet> additionalGroups, // Sunrise-Edit List<(NetEntity, uint)> accessKeys, Queue accessLog, int accessLogLimit) @@ -157,6 +165,7 @@ public AccessReaderComponentState( AccessLog = accessLog; AccessLogLimit = accessLogLimit; Group = group; // Sunrise added for alertAccesses + AdditionalGroups = additionalGroups; // Sunrise-Edit } } diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 38413f40dd8..f35b067a90a 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -110,6 +110,7 @@ private void OnGetState(EntityUid uid, AccessReaderComponent component, ref Comp component.AccessLists, component.AccessListsOriginal, component.Group, //Sunrise added + component.AdditionalGroups, // Sunrise-Edit _recordsSystem.Convert(component.AccessKeys), component.AccessLog, component.AccessLogLimit); // Sunrise-edit @@ -135,6 +136,7 @@ private void OnHandleState(EntityUid uid, AccessReaderComponent component, ref C component.DenyTags = new(state.DenyTags); component.AccessLog = new(state.AccessLog); component.Group = state.Group != null ? new (state.Group) : null; // Sunrise added - автодоступы по коду + component.AdditionalGroups = new(state.AdditionalGroups); // Sunrise-Edit component.AccessLogLimit = state.AccessLogLimit; // Sunrise added start - уведомляем client-side UI/overlays после изменения replicated access settings @@ -340,20 +342,36 @@ public bool AreAccessTagsAllowed(ICollection> acce // Sunrise-start /// - /// Сравнивает список аварийных доступов с доступами на карте. + /// Checks whether any alert-level access group grants access to the supplied tags. /// public bool IsAccessAllowedByExtendedAccess(ICollection> access, AccessReaderComponent reader) { - if (!_prototype.TryIndex(reader.Group, out var accessTags)) - return false; + if (reader.Group is { } group && IsAccessGroupAllowed(access, group)) + return true; - if (accessTags.Tags.Count == 0) - return false; + foreach (var additionalGroup in reader.AdditionalGroups) + { + if (IsAccessGroupAllowed(access, additionalGroup)) + return true; + } - if (!accessTags.Tags.Any(access.Contains)) + return false; + } + + private bool IsAccessGroupAllowed( + ICollection> access, + ProtoId group) + { + if (!_prototype.TryIndex(group, out var accessGroup)) return false; - return true; + foreach (var tag in accessGroup.Tags) + { + if (access.Contains(tag)) + return true; + } + + return false; } // Sunrise-end @@ -983,6 +1001,7 @@ public void LogAccess(Entity ent, string name, TimeSpan? public void UpdateAccess(Entity ent, string currentLevel) { + ent.Comp.AdditionalGroups.Clear(); if (ent.Comp.AlertAccesses.TryGetValue(currentLevel, out var value)) ent.Comp.Group = value; else @@ -991,6 +1010,48 @@ public void UpdateAccess(Entity ent, string currentLevel) Dirty(ent); } + /// + /// Updates the temporary access groups for all simultaneously active alert levels. + /// + public void UpdateAccess( + Entity ent, + IReadOnlyList activeLevels, + IReadOnlyCollection>? globalGroups = null) + { + ent.Comp.Group = null; + ent.Comp.AdditionalGroups.Clear(); + + foreach (var level in activeLevels) + { + if (!ent.Comp.AlertAccesses.TryGetValue(level, out var group)) + continue; + + AddExtendedAccessGroup(ent.Comp, group); + } + + if (globalGroups != null) + { + foreach (var group in globalGroups) + { + AddExtendedAccessGroup(ent.Comp, group); + } + } + + Dirty(ent); + } + + private static void AddExtendedAccessGroup(AccessReaderComponent reader, ProtoId group) + { + if (reader.Group == null) + { + reader.Group = group; + return; + } + + if (reader.Group != group) + reader.AdditionalGroups.Add(group); + } + private List GetLocalizedAccessNames(List>> accessLists) { var localizedNames = new List(); diff --git a/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl b/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl index de7ac900bc9..397dcd354ce 100644 --- a/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl +++ b/Resources/Locale/en-US/_strings/alert-levels/alert-level-command.ftl @@ -3,6 +3,7 @@ cmd-setalertlevel-desc = Set current station alert level for grid on which the p cmd-setalertlevel-help = Usage: setalertlevel [locked/enabled]. For a primary alert level, the optional boolean locks crew selection. For an additional alert level, omit it or use true to enable the code, and use false to disable it. cmd-setalertlevel-invalid-grid = You must be on grid of station code that you are going to change. cmd-setalertlevel-invalid-level = Specified alert level does not exist on that grid. +cmd-setalertlevel-change-failed = The requested alert level change is not allowed or has already been applied. cmd-setalertlevel-hint-1 = cmd-setalertlevel-hint-2 = [locked/enabled] diff --git a/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl b/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl index 127428b1c59..e0880485e65 100644 --- a/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl +++ b/Resources/Locale/ru-RU/_strings/alert-levels/alert-level-command.ftl @@ -3,6 +3,7 @@ cmd-setalertlevel-desc = Изменяет уровень угрозы на ст cmd-setalertlevel-help = Использование: setalertlevel <уровень> [блокировка/включён]. Для основного кода необязательное логическое значение блокирует смену кода экипажем. Для дополнительного кода отсутствие значения или true включает код, а false снимает его. cmd-setalertlevel-invalid-grid = Вы должны находиться на сетке станции, код которой собираетесь изменить. cmd-setalertlevel-invalid-level = Указанный уровень угрозы не существует на этой сетке. +cmd-setalertlevel-change-failed = Запрошенное изменение кода запрещено или уже применено. cmd-setalertlevel-hint-1 = cmd-setalertlevel-hint-2 = [блокировка/включён] # Sunrise edit end diff --git a/Resources/Prototypes/AlertLevels/alert_levels.yml b/Resources/Prototypes/AlertLevels/alert_levels.yml index ac20136a1bc..5eb610c86b3 100644 --- a/Resources/Prototypes/AlertLevels/alert_levels.yml +++ b/Resources/Prototypes/AlertLevels/alert_levels.yml @@ -3,6 +3,7 @@ defaultLevel: green levels: green: + visualPriority: 0 # Sunrise-Edit announcement: alert-level-green-announcement color: Green emergencyLightColor: LawnGreen @@ -12,6 +13,7 @@ announcement: access-system-accesses-delay-green # Sunrise edit end blue: + visualPriority: 10 # Sunrise-Edit announcement: alert-level-blue-announcement sound: /Audio/Misc/bluealert.ogg color: DodgerBlue @@ -24,6 +26,7 @@ # Sunrise edit end violet: isAdditional: true # Sunrise-Edit + visualPriority: 21 # Sunrise-Edit announcement: alert-level-violet-announcement sound: /Audio/Misc/notice1.ogg color: Violet @@ -32,6 +35,7 @@ shuttleTime: 600 yellow: isAdditional: true # Sunrise-Edit + visualPriority: 20 # Sunrise-Edit announcement: alert-level-yellow-announcement sound: /Audio/Misc/notice1.ogg color: Yellow @@ -41,8 +45,10 @@ # Sunrise edit start extendedAccessOptions: announcement: access-system-accesses-delay-yellow + accessGroup: YellowAlertAccesses # Sunrise-Edit # Sunrise edit end red: + visualPriority: 30 # Sunrise-Edit announcement: alert-level-red-announcement sound: /Audio/Misc/redalert.ogg color: Red @@ -69,6 +75,7 @@ announcement: access-system-accesses-delay-sierra #Fish-end gamma: + visualPriority: 40 # Sunrise-Edit announcement: alert-level-gamma-announcement selectable: false sound: @@ -85,6 +92,7 @@ # Sunrise edit end delta: isAdditional: true # Sunrise-Edit + visualPriority: 50 # Sunrise-Edit announcement: alert-level-delta-announcement selectable: false sound: @@ -102,6 +110,8 @@ # Sunrise edit end epsilon: isAdditional: true # Sunrise-Edit + canBeDisabled: false # Sunrise-Edit - протокол необратим после раскрытия цели + visualPriority: 60 # Sunrise-Edit announcement: alert-level-epsilon-announcement selectable: false sound: diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index a93e30fd2f4..a37f03ffb8c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -86,6 +86,7 @@ color: "#5ed7aa" announceVoice: PortalGlados # Sunrise-Edit sound: /Audio/Announcements/announce.ogg # Fish-Edit + allowedAlertLevels: [] # Sunrise-Edit - ИИ не управляет кодами через встроенную консоль - type: ShowJobIcons - type: ShowCrewIcons - type: DamagedSiliconAccent diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 5f4743937ba..5aa22990a0a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -818,6 +818,7 @@ global: true #announce to everyone they're about to fuck shit up announceSentBy: false # The title already says who they are. sound: /Audio/Announcements/war.ogg + allowedAlertLevels: [] # Sunrise-Edit - вражеская консоль не управляет кодами станции - type: Computer board: SyndicateCommsComputerCircuitboard - type: PointLight @@ -852,6 +853,7 @@ global: true #announce to everyone they're about to fuck shit up announceSentBy: false sound: /Audio/Announcements/war.ogg + allowedAlertLevels: [] # Sunrise-Edit - консоль мага не управляет кодами станции - type: Computer board: WizardCommsComputerCircuitboard - type: PointLight @@ -884,6 +886,8 @@ color: "#1d8bad" canShuttle: false global: true + allowedAlertLevels: [green, blue, violet, yellow, red, gamma, epsilon] # Sunrise-Edit + forceAlertLevelChanges: true # Sunrise-Edit - type: Computer board: CentcommCommsComputerCircuitboard - type: PointLight diff --git a/Resources/Prototypes/_Sunrise/Access/AccessGroup/access_group.yml b/Resources/Prototypes/_Sunrise/Access/AccessGroup/access_group.yml index 4df7cbcbcce..b7df80b8c77 100644 --- a/Resources/Prototypes/_Sunrise/Access/AccessGroup/access_group.yml +++ b/Resources/Prototypes/_Sunrise/Access/AccessGroup/access_group.yml @@ -23,6 +23,7 @@ id: YellowAlertAccesses tags: - ChiefEngineer + - Engineering # Sunrise-Edit - Atmospherics - type: accessGroup diff --git a/Resources/Prototypes/_Sunrise/Entities/Structures/Machines/Computers/starlight_compat.yml b/Resources/Prototypes/_Sunrise/Entities/Structures/Machines/Computers/starlight_compat.yml index 49405bc5093..694925e938f 100644 --- a/Resources/Prototypes/_Sunrise/Entities/Structures/Machines/Computers/starlight_compat.yml +++ b/Resources/Prototypes/_Sunrise/Entities/Structures/Machines/Computers/starlight_compat.yml @@ -57,6 +57,7 @@ sound: /Audio/Announcements/announce.ogg # Starlight used /Audio/_Starlight/Announcements/announce2.ogg. color: "#b48b57" canShuttle: false + allowedAlertLevels: [] # Sunrise-Edit - грузовая консоль не управляет кодами - type: Computer # Starlight department comms boards are not ported. board: CommsComputerCircuitboard @@ -90,6 +91,7 @@ sound: /Audio/Announcements/announce.ogg color: "#f37746" canShuttle: false + allowedAlertLevels: [yellow] # Sunrise-Edit - type: Computer board: CommsComputerCircuitboard - type: PointLight @@ -122,6 +124,7 @@ sound: /Audio/Announcements/announce.ogg color: "#ff178b" canShuttle: false + allowedAlertLevels: [] # Sunrise-Edit - юридическая консоль не управляет кодами - type: Computer board: CommsComputerCircuitboard - type: PointLight @@ -154,6 +157,7 @@ sound: /Audio/Announcements/announce.ogg color: "#52b4e9" canShuttle: false + allowedAlertLevels: [violet] # Sunrise-Edit - type: Computer board: CommsComputerCircuitboard - type: PointLight @@ -186,6 +190,7 @@ sound: /Audio/Announcements/announce.ogg color: "#c68cfa" canShuttle: false + allowedAlertLevels: [] # Sunrise-Edit - научная консоль не управляет кодами - type: Computer board: CommsComputerCircuitboard - type: PointLight @@ -250,6 +255,7 @@ sound: /Audio/Announcements/announce.ogg color: "#539c00" canShuttle: false + allowedAlertLevels: [] # Sunrise-Edit - сервисная консоль не управляет кодами - type: Computer board: CommsComputerCircuitboard - type: PointLight diff --git a/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml b/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml index 726d69ce8c7..3ebd4cf0f15 100644 --- a/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml +++ b/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml @@ -505,11 +505,13 @@ name: centcom communications computer components: - type: AccessReader - access: [[ "Command" ]] + access: [[ "CentralCommand" ]] # Sunrise-Edit - доступ только ЦК и спецоперациям - type: CommunicationsConsole title: comms-console-announcement-title-centcom global: true announceVoice: NecoArcTwo + allowedAlertLevels: [green, blue, violet, yellow, red, gamma, epsilon] # Sunrise-Edit + forceAlertLevelChanges: true # Sunrise-Edit - type: entity parent: BaseComputerShuttle From 8ff157466b5edb5f4929d33c7c976d18fe9a6a7c Mon Sep 17 00:00:00 2001 From: Ende Date: Tue, 8 Sep 2026 23:29:42 +0200 Subject: [PATCH 03/12] =?UTF-8?q?=D0=A3=D1=82=D0=BE=D1=87=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=B4=D1=8B?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE=D0=BB=D0=B8=20=D0=A6=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AlertLevel/AdditionalAlertLevelTest.cs | 25 ++++------ .../AlertLevel/AlertLevelPrototype.cs | 8 +--- Content.Server/Nuke/NukeSystem.cs | 47 ++----------------- .../AlertLevel/AlertLevelSystem.Additional.cs | 1 - .../Prototypes/AlertLevels/alert_levels.yml | 2 +- .../Machines/Computers/computers.yml | 2 +- .../Structures/Machines/computers.yml | 2 +- 7 files changed, 16 insertions(+), 71 deletions(-) diff --git a/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs b/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs index 0ba35aa9952..2495e200cf4 100644 --- a/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs +++ b/Content.IntegrationTests/_Sunrise/AlertLevel/AdditionalAlertLevelTest.cs @@ -17,7 +17,7 @@ namespace Content.IntegrationTests._Sunrise.AlertLevel; public sealed class AdditionalAlertLevelTest { [Test] - public async Task AdditionalLevelsRespectSharedCooldownAndIrreversibleLevels() + public async Task AdditionalLevelsRespectSharedCooldown() { await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; @@ -32,7 +32,6 @@ public async Task AdditionalLevelsRespectSharedCooldownAndIrreversibleLevels() var forcedVioletEnabled = false; var yellowDisableBlockedByCooldown = false; var yellowDisabled = false; - var epsilonDisableBlocked = false; await server.WaitPost(() => { @@ -86,13 +85,6 @@ await server.WaitPost(() => playSound: false, announce: false, component: alertLevel); - - alertLevel.ActiveAdditionalLevels.Add("epsilon"); - epsilonDisableBlocked = !alertLevelSystem.CanSetAdditionalLevel( - (station, alertLevel), - "epsilon", - false, - force: true); }); await server.WaitAssertion(() => @@ -104,11 +96,10 @@ await server.WaitAssertion(() => Assert.That(forcedVioletEnabled, Is.True); Assert.That(yellowDisableBlockedByCooldown, Is.True); Assert.That(yellowDisabled, Is.True); - Assert.That(epsilonDisableBlocked, Is.True); Assert.That(alertLevel.CurrentLevel, Is.EqualTo("green")); Assert.That(alertLevel.ActiveDelay, Is.True); Assert.That(alertLevel.CurrentDelay, Is.GreaterThan(0)); - Assert.That(alertLevel.ActiveAdditionalLevels, Is.EquivalentTo(new[] { "violet", "epsilon" })); + Assert.That(alertLevel.ActiveAdditionalLevels, Is.EquivalentTo(new[] { "violet" })); }); }); @@ -237,7 +228,7 @@ await server.WaitAssertion(() => }; var centCommConsole = new CommunicationsConsoleComponent { - AllowedAlertLevels = ["green", "blue", "violet", "yellow", "red", "gamma", "epsilon"], + AllowedAlertLevels = ["green", "blue", "violet", "yellow", "red", "gamma", "delta"], ForceAlertLevelChanges = true, }; @@ -263,14 +254,16 @@ await server.WaitAssertion(() => centCommConsole, "gamma", levels["gamma"]), Is.True); + Assert.That(levels["gamma"].IsAdditional, Is.True); Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( centCommConsole, - "epsilon", - levels["epsilon"]), Is.True); + "delta", + levels["delta"]), Is.True); + Assert.That(levels["delta"].IsAdditional, Is.True); Assert.That(CommunicationsConsoleSystem.IsAlertLevelAllowed( centCommConsole, - "delta", - levels["delta"]), Is.False); + "epsilon", + levels["epsilon"]), Is.False); }); }); diff --git a/Content.Server/AlertLevel/AlertLevelPrototype.cs b/Content.Server/AlertLevel/AlertLevelPrototype.cs index 4a434a78242..477ec67c1c0 100644 --- a/Content.Server/AlertLevel/AlertLevelPrototype.cs +++ b/Content.Server/AlertLevel/AlertLevelPrototype.cs @@ -35,13 +35,7 @@ public sealed partial class AlertLevelDetail /// [DataField] public bool IsAdditional { get; private set; } // Sunrise-Edit - // Sunrise added start - правила дополнительных кодов и выбор визуального приоритета - /// - /// Whether an active additional alert level can be disabled. - /// - [DataField] - public bool CanBeDisabled { get; private set; } = true; - + // Sunrise added start - выбор визуального приоритета активных кодов /// /// Determines which active alert level controls single-state visuals such as emergency lights. /// diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 2022a291b4d..6f7d20abca4 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -548,30 +548,10 @@ public void DisarmBomb(EntityUid uid, NukeComponent? component = null) if (component.Status != NukeStatus.ARMED) return; - var stationUid = _station.GetStationInMap(Transform(uid).MapID); // Sunrise-Edit - совпадает с поиском станции при взводе - if (stationUid != null - && !HasOtherArmedNuke(uid, stationUid.Value, component.AlertLevelOnActivate)) + var stationUid = _station.GetOwningStation(uid); + if (stationUid != null) { - // Sunrise edit start - дополнительный Delta снимается без замены основного кода - if (TryComp(stationUid.Value, out var alert) - && alert.AlertLevels != null - && alert.AlertLevels.Levels.TryGetValue(component.AlertLevelOnActivate, out var activeDetail) - && activeDetail.IsAdditional) - { - _alertLevel.TrySetAdditionalLevel( - stationUid.Value, - component.AlertLevelOnActivate, - false, - true, - true, - true, - alert); - } - else - { - _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true); - } - // Sunrise edit end + _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true); } // warn a crew @@ -604,27 +584,6 @@ public void DisarmBomb(EntityUid uid, NukeComponent? component = null) UpdateAppearance(uid, component); } - // Sunrise added start - код Delta остаётся активным, пока на станции взведена хотя бы одна бомба - private bool HasOtherArmedNuke(EntityUid excluded, EntityUid station, string alertLevel) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var nuke)) - { - if (uid == excluded - || nuke.Status != NukeStatus.ARMED - || nuke.AlertLevelOnActivate != alertLevel) - { - continue; - } - - if (_station.GetStationInMap(Transform(uid).MapID) == station) - return true; - } - - return false; - } - // Sunrise added end - /// /// Toggle bomb arm button /// diff --git a/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs index ee5c0fb1c00..d2bd5b7995c 100644 --- a/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs +++ b/Content.Server/_Sunrise/AlertLevel/AlertLevelSystem.Additional.cs @@ -19,7 +19,6 @@ public bool CanSetAdditionalLevel( || station.Comp.AlertLevels == null || !station.Comp.AlertLevels.Levels.TryGetValue(level, out var detail) || !detail.IsAdditional - || (!enabled && !detail.CanBeDisabled) || station.Comp.ActiveAdditionalLevels.Contains(level) == enabled) { return false; diff --git a/Resources/Prototypes/AlertLevels/alert_levels.yml b/Resources/Prototypes/AlertLevels/alert_levels.yml index 5eb610c86b3..f7503c0a907 100644 --- a/Resources/Prototypes/AlertLevels/alert_levels.yml +++ b/Resources/Prototypes/AlertLevels/alert_levels.yml @@ -75,6 +75,7 @@ announcement: access-system-accesses-delay-sierra #Fish-end gamma: + isAdditional: true # Sunrise-Edit visualPriority: 40 # Sunrise-Edit announcement: alert-level-gamma-announcement selectable: false @@ -110,7 +111,6 @@ # Sunrise edit end epsilon: isAdditional: true # Sunrise-Edit - canBeDisabled: false # Sunrise-Edit - протокол необратим после раскрытия цели visualPriority: 60 # Sunrise-Edit announcement: alert-level-epsilon-announcement selectable: false diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 5aa22990a0a..3b6cb7bc77a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -886,7 +886,7 @@ color: "#1d8bad" canShuttle: false global: true - allowedAlertLevels: [green, blue, violet, yellow, red, gamma, epsilon] # Sunrise-Edit + allowedAlertLevels: [green, blue, violet, yellow, red, gamma, delta] # Sunrise-Edit forceAlertLevelChanges: true # Sunrise-Edit - type: Computer board: CentcommCommsComputerCircuitboard diff --git a/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml b/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml index 3ebd4cf0f15..51842a0b00a 100644 --- a/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml +++ b/Resources/Prototypes/_Sunrise/Structures/Machines/computers.yml @@ -510,7 +510,7 @@ title: comms-console-announcement-title-centcom global: true announceVoice: NecoArcTwo - allowedAlertLevels: [green, blue, violet, yellow, red, gamma, epsilon] # Sunrise-Edit + allowedAlertLevels: [green, blue, violet, yellow, red, gamma, delta] # Sunrise-Edit forceAlertLevelChanges: true # Sunrise-Edit - type: entity From 1b28df4a4ae3c2253691d7c24b2dedfcf7640488 Mon Sep 17 00:00:00 2001 From: Ende Date: Wed, 9 Sep 2026 00:19:32 +0200 Subject: [PATCH 04/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=81=D0=BE=D0=BB=D0=B8=20=D0=A6=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommunicationsConsoleBoundUserInterface.cs | 15 +++ .../UI/CommunicationsConsoleMenu.xaml | 13 ++ .../UI/CommunicationsConsoleMenu.xaml.cs | 41 ++++++ .../AlertLevel/AdditionalAlertLevelTest.cs | 64 ++++++++++ .../CommunicationsConsoleComponent.cs | 12 ++ .../CommunicationsConsoleSystem.cs | 119 +++++++++++++++++- .../SharedCommunicationsConsoleComponent.cs | 43 ++++++- .../_sunrise/communications/codes.ftl | 1 + .../_sunrise/communications/codes.ftl | 1 + .../Machines/Computers/computers.yml | 1 + .../Structures/Machines/computers.yml | 1 + 11 files changed, 304 insertions(+), 7 deletions(-) diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs index 6bbc91444b4..78d8ca09947 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs @@ -34,6 +34,7 @@ protected override void Open() _menu.OnBroadcast += BroadcastButtonPressed; _menu.OnAlertLevel += AlertLevelSelected; _menu.OnAdditionalAlertLevel += AdditionalAlertLevelSelected; // Sunrise-Edit + _menu.OnAlertStation += AlertStationSelected; // Sunrise-Edit _menu.OnEmergencyLevel += EmergencyShuttleButtonPressed; _menu.OnToggleRelay += ToggleRelayPressed; // Sunrise-Edit } @@ -46,6 +47,16 @@ private void AdditionalAlertLevelSelected(string level, bool enabled) SendMessage(new CommunicationsConsoleSetAdditionalAlertLevelMessage(level, enabled)); } + + private void AlertStationSelected(NetEntity station) + { + if (!HasAccess() || _menu == null) + return; + + // Ждём подтверждённое состояние выбранной станции, чтобы команда не ушла на предыдущую. + _menu.DisableAlertLevelControls(); + SendMessage(new CommunicationsConsoleSelectAlertStationMessage(station)); + } // Sunrise added end public void AlertLevelSelected(string level) @@ -116,6 +127,10 @@ protected override void UpdateState(BoundUserInterfaceState state) _menu.CountdownEnd = commsState.ExpectedCountdownEnd; _menu.UpdateCountdown(); + _menu.UpdateAlertStations( + commsState.AlertStations, + commsState.SelectedAlertStation, + hasAccess); // Sunrise-Edit _menu.UpdateAlertLevels(commsState.AlertLevels, _menu.CurrentLevel); _menu.UpdateAdditionalAlertLevels(commsState.AdditionalAlertLevels, hasAccess); // Sunrise-Edit _menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable; diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml index 1fbe5bf7d12..360ac16fd4d 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml @@ -45,6 +45,19 @@ + + + + +