diff --git a/Content.Client/Nuke/NukeMenu.xaml.cs b/Content.Client/Nuke/NukeMenu.xaml.cs index 4c80e8de17..092b869074 100644 --- a/Content.Client/Nuke/NukeMenu.xaml.cs +++ b/Content.Client/Nuke/NukeMenu.xaml.cs @@ -88,6 +88,11 @@ namespace Content.Client.Nuke secondMsg = Loc.GetString("nuke-user-interface-second-status-time", ("time", state.RemainingTime)); break; + case NukeStatus.COOLDOWN: + firstMsg = Loc.GetString("nuke-user-interface-first-status-device-cooldown"); + secondMsg = Loc.GetString("nuke-user-interface-second-status-cooldown-time", + ("time", state.CooldownTime)); + break; default: // shouldn't normally be here firstMsg = Loc.GetString("nuke-user-interface-status-error"); diff --git a/Content.Server/Nuke/Commands/ToggleNukeCommand.cs b/Content.Server/Nuke/Commands/ToggleNukeCommand.cs index 5df0922ee6..e1f29264c4 100644 --- a/Content.Server/Nuke/Commands/ToggleNukeCommand.cs +++ b/Content.Server/Nuke/Commands/ToggleNukeCommand.cs @@ -3,9 +3,6 @@ using Content.Server.Administration; using Content.Shared.Administration; using JetBrains.Annotations; using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; namespace Content.Server.Nuke.Commands { diff --git a/Content.Server/Nuke/NukeCodePaperComponent.cs b/Content.Server/Nuke/NukeCodePaperComponent.cs index 27a8d3e54e..c57f5b1885 100644 --- a/Content.Server/Nuke/NukeCodePaperComponent.cs +++ b/Content.Server/Nuke/NukeCodePaperComponent.cs @@ -1,5 +1,3 @@ -using Robust.Shared.GameObjects; - namespace Content.Server.Nuke { /// diff --git a/Content.Server/Nuke/NukeCodePaperSystem.cs b/Content.Server/Nuke/NukeCodePaperSystem.cs index 52ffea0f27..a99c42730a 100644 --- a/Content.Server/Nuke/NukeCodePaperSystem.cs +++ b/Content.Server/Nuke/NukeCodePaperSystem.cs @@ -1,7 +1,4 @@ using Content.Server.Paper; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; namespace Content.Server.Nuke { diff --git a/Content.Server/Nuke/NukeCodeSystem.cs b/Content.Server/Nuke/NukeCodeSystem.cs index c97072de01..70e6363759 100644 --- a/Content.Server/Nuke/NukeCodeSystem.cs +++ b/Content.Server/Nuke/NukeCodeSystem.cs @@ -1,9 +1,6 @@ using Content.Server.Chat.Managers; using Content.Server.Communications; using Content.Shared.GameTicking; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Random; namespace Content.Server.Nuke @@ -17,7 +14,7 @@ namespace Content.Server.Nuke [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IChatManager _chat = default!; - public const int CodeLength = 6; + private const int CodeLength = 6; public string Code { get; private set; } = default!; public override void Initialize() @@ -47,7 +44,7 @@ namespace Content.Server.Nuke public void GenerateNewCode() { var ret = ""; - for (int i = 0; i < CodeLength; i++) + for (var i = 0; i < CodeLength; i++) { var c = (char) _random.Next('0', '9' + 1); ret += c; diff --git a/Content.Server/Nuke/NukeComponent.cs b/Content.Server/Nuke/NukeComponent.cs index dcad5a56a4..230a86b339 100644 --- a/Content.Server/Nuke/NukeComponent.cs +++ b/Content.Server/Nuke/NukeComponent.cs @@ -1,17 +1,13 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Nuke; using Content.Shared.Sound; -using Robust.Shared.Analyzers; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; namespace Content.Server.Nuke { /// - /// Nuclear device that can devistate an entire station. - /// Basicaly a station self-destruction mechanism. + /// Nuclear device that can devastate an entire station. + /// Basically a station self-destruction mechanism. /// To activate it, user needs to insert an authorization disk and enter a secret code. /// [RegisterComponent] @@ -25,6 +21,13 @@ namespace Content.Server.Nuke [ViewVariables(VVAccess.ReadWrite)] public int Timer = 180; + /// + /// How long until the bomb can arm again after deactivation. + /// Used to prevent announcements spam. + /// + [DataField("cooldown")] + public int Cooldown = 30; + /// /// The that stores the nuclear disk. The entity whitelist, sounds, and some other /// behaviours are specified by this definition. Make sure the whitelist, is correct @@ -50,13 +53,13 @@ namespace Content.Server.Nuke public SoundSpecifier KeypadPressSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg"); [DataField("accessGrantedSound")] - public SoundSpecifier AccessGrantedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg"); + public SoundSpecifier AccessGrantedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/confirm_beep.ogg"); [DataField("accessDeniedSound")] public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg"); [DataField("alertSound")] - public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/Machines/alarm.ogg"); + public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/Machines/Nuke/nuke_alarm.ogg"); [DataField("armSound")] public SoundSpecifier ArmSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg"); @@ -71,7 +74,13 @@ namespace Content.Server.Nuke public float RemainingTime; /// - /// Curent nuclear code buffer. Entered manually by players. + /// Time until bomb cooldown will expire in seconds. + /// + [ViewVariables] + public float CooldownTime; + + /// + /// Current nuclear code buffer. Entered manually by players. /// If valid it will allow arm/disarm bomb. /// [ViewVariables] diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index be233e62f2..73bb1be55f 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,24 +1,15 @@ -using System.Collections.Generic; using Content.Server.Chat.Managers; using Content.Server.Construction.Components; using Content.Server.Coordinates.Helpers; using Content.Server.Popups; using Content.Server.UserInterface; -using Content.Shared.ActionBlocker; using Content.Shared.Audio; using Content.Shared.Body.Components; using Content.Shared.Containers.ItemSlots; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using Content.Shared.Nuke; using Content.Shared.Sound; -using Robust.Server.GameObjects; -using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Player; namespace Content.Server.Nuke @@ -31,14 +22,11 @@ namespace Content.Server.Nuke [Dependency] private readonly IEntityLookup _lookup = default!; [Dependency] private readonly IChatManager _chat = default!; - private readonly HashSet _tickingBombs = new(); - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); - SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnItemSlotChanged); SubscribeLocalEvent(OnItemSlotChanged); @@ -68,35 +56,24 @@ namespace Content.Server.Nuke public override void Update(float frameTime) { base.Update(frameTime); - foreach (var uid in _tickingBombs) + + var query = EntityQuery(); + foreach (var nuke in query) { - if (!EntityManager.TryGetComponent(uid, out NukeComponent nuke)) - continue; - - nuke.RemainingTime -= frameTime; - - // play alert sound if time is running out - if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound) + switch (nuke.Status) { - nuke.AlertAudioStream = SoundSystem.Play(Filter.Broadcast(), nuke.AlertSound.GetSound()); - nuke.PlayedAlertSound = true; - } - - if (nuke.RemainingTime <= 0) - { - nuke.RemainingTime = 0; - ActivateBomb(uid, nuke); - } - else - { - UpdateUserInterface(uid, nuke); + case NukeStatus.ARMED: + TickTimer(nuke.Owner, frameTime, nuke); + break; + case NukeStatus.COOLDOWN: + TickCooldown(nuke.Owner, frameTime, nuke); + break; } } } private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args) { - _tickingBombs.Remove(uid); _itemSlots.RemoveItemSlot(uid, component.DiskSlot); } @@ -111,18 +88,6 @@ namespace Content.Server.Nuke UpdateUserInterface(uid, component); } - private void OnActivate(EntityUid uid, NukeComponent component, ActivateInWorldEvent args) - { - if (args.Handled) - return; - - if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) - return; - - ShowUI(uid, actor.PlayerSession, component); - args.Handled = true; - } - #region Anchor private void OnAnchorAttempt(EntityUid uid, NukeComponent component, AnchorAttemptEvent args) { @@ -188,7 +153,7 @@ namespace Content.Server.Nuke private void OnKeypadButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadMessage args) { - PlaydSound(uid, component.KeypadPressSound, 0.125f, component); + PlaySound(uid, component.KeypadPressSound, 0.125f, component); if (component.Status != NukeStatus.AWAIT_CODE) return; @@ -202,7 +167,7 @@ namespace Content.Server.Nuke private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args) { - PlaydSound(uid, component.KeypadPressSound, 0f, component); + PlaySound(uid, component.KeypadPressSound, 0f, component); if (component.Status != NukeStatus.AWAIT_CODE) return; @@ -227,6 +192,48 @@ namespace Content.Server.Nuke } #endregion + private void TickCooldown(EntityUid uid, float frameTime, NukeComponent? nuke = null) + { + if (!Resolve(uid, ref nuke)) + return; + + nuke.CooldownTime -= frameTime; + if (nuke.CooldownTime <= 0) + { + // reset nuke to default state + nuke.CooldownTime = 0; + nuke.Status = NukeStatus.AWAIT_ARM; + UpdateStatus(uid, nuke); + } + + UpdateUserInterface(nuke.Owner, nuke); + } + + private void TickTimer(EntityUid uid, float frameTime, NukeComponent? nuke = null) + { + if (!Resolve(uid, ref nuke)) + return; + + nuke.RemainingTime -= frameTime; + + // play alert sound if time is running out + if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound) + { + nuke.AlertAudioStream = SoundSystem.Play(Filter.Broadcast(), nuke.AlertSound.GetSound()); + nuke.PlayedAlertSound = true; + } + + if (nuke.RemainingTime <= 0) + { + nuke.RemainingTime = 0; + ActivateBomb(uid, nuke); + } + else + { + UpdateUserInterface(uid, nuke); + } + } + private void UpdateStatus(EntityUid uid, NukeComponent? component = null) { if (!Resolve(uid, ref component)) @@ -252,12 +259,12 @@ namespace Content.Server.Nuke { component.Status = NukeStatus.AWAIT_ARM; component.RemainingTime = component.Timer; - PlaydSound(uid, component.AccessGrantedSound, 0, component); + PlaySound(uid, component.AccessGrantedSound, 0, component); } else { component.EnteredCode = ""; - PlaydSound(uid, component.AccessDeniedSound, 0, component); + PlaySound(uid, component.AccessDeniedSound, 0, component); } break; } @@ -270,17 +277,6 @@ namespace Content.Server.Nuke } } - private void ShowUI(EntityUid uid, IPlayerSession session, NukeComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - var ui = component.Owner.GetUIOrNull(NukeUiKey.Key); - ui?.Open(session); - - UpdateUserInterface(uid, component); - } - private void UpdateUserInterface(EntityUid uid, NukeComponent? component = null) { if (!Resolve(uid, ref component)) @@ -306,20 +302,21 @@ namespace Content.Server.Nuke IsAnchored = anchored, AllowArm = allowArm, EnteredCodeLength = component.EnteredCode.Length, - MaxCodeLength = _codes.Code.Length + MaxCodeLength = _codes.Code.Length, + CooldownTime = (int) component.CooldownTime }; ui.SetState(state); } - private void PlaydSound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f, + private void PlaySound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f, NukeComponent? component = null) { if (!Resolve(uid, ref component)) return; SoundSystem.Play(Filter.Pvs(uid), sound.GetSound(), - uid, AudioHelpers.WithVariation(varyPitch)); + uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f)); } #region Public API @@ -344,7 +341,6 @@ namespace Content.Server.Nuke SoundSystem.Play(Filter.Broadcast(), component.ArmSound.GetSound()); component.Status = NukeStatus.ARMED; - _tickingBombs.Add(uid); UpdateUserInterface(uid, component); } @@ -371,8 +367,10 @@ namespace Content.Server.Nuke component.PlayedAlertSound = false; component.AlertAudioStream?.Stop(); - component.Status = NukeStatus.AWAIT_ARM; - _tickingBombs.Remove(uid); + // start bomb cooldown + component.Status = NukeStatus.COOLDOWN; + component.CooldownTime = component.Cooldown; + UpdateUserInterface(uid, component); } @@ -407,7 +405,7 @@ namespace Content.Server.Nuke { var entUid = ent; if (!EntityManager.EntityExists(entUid)) - continue;; + continue; if (EntityManager.TryGetComponent(entUid, out SharedBodyComponent? body)) body.Gib(); diff --git a/Content.Shared/Nuke/SharedNuke.cs b/Content.Shared/Nuke/SharedNuke.cs index 6c0309bede..3be871804a 100644 --- a/Content.Shared/Nuke/SharedNuke.cs +++ b/Content.Shared/Nuke/SharedNuke.cs @@ -15,7 +15,8 @@ namespace Content.Shared.Nuke AWAIT_DISK, AWAIT_CODE, AWAIT_ARM, - ARMED + ARMED, + COOLDOWN } [Serializable, NetSerializable] @@ -24,6 +25,7 @@ namespace Content.Shared.Nuke public bool DiskInserted; public NukeStatus Status; public int RemainingTime; + public int CooldownTime; public bool IsAnchored; public int EnteredCodeLength; public int MaxCodeLength; diff --git a/Resources/Audio/Machines/Nuke/nuke_alarm.ogg b/Resources/Audio/Machines/Nuke/nuke_alarm.ogg new file mode 100644 index 0000000000..2aec35bd32 Binary files /dev/null and b/Resources/Audio/Machines/Nuke/nuke_alarm.ogg differ diff --git a/Resources/Locale/en-US/nuke/nuke-component.ftl b/Resources/Locale/en-US/nuke/nuke-component.ftl index 2b0d35b7ca..add3ede4f4 100644 --- a/Resources/Locale/en-US/nuke/nuke-component.ftl +++ b/Resources/Locale/en-US/nuke/nuke-component.ftl @@ -16,12 +16,13 @@ nuke-user-interface-first-status-input-code = INPUT CODE nuke-user-interface-first-status-input-time = INPUT TIME nuke-user-interface-first-status-device-ready = DEVICE READY nuke-user-interface-first-status-device-armed = DEVICE ARMED +nuke-user-interface-first-status-device-cooldown = DEACTIVATED nuke-user-interface-status-error = ERROR ## Lower status nuke-user-interface-second-status-await-disk = AWAIT DISK nuke-user-interface-second-status-time = TIME: {$time} nuke-user-interface-second-status-current-code = CODE: {$code} - +nuke-user-interface-second-status-cooldown-time = WAIT: {$time} diff --git a/Resources/Prototypes/Entities/Objects/Devices/nuke.yml b/Resources/Prototypes/Entities/Objects/Devices/nuke.yml index 9f8205a5b1..c8d9e04311 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/nuke.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/nuke.yml @@ -31,6 +31,8 @@ tags: - NukeDisk - type: InteractionOutline + - type: ActivatableUI + key: enum.NukeUiKey.Key - type: UserInterface interfaces: - key: enum.NukeUiKey.Key