From c30dc030c502dcfded2659325c865818b1af9ffc Mon Sep 17 00:00:00 2001 From: Letter N <24603524+LetterN@users.noreply.github.com> Date: Mon, 18 Jan 2021 18:14:53 +0800 Subject: [PATCH] Adds false alarm and updates events code (#2577) * oops accedentaly ports how ss13 deals with event randomness. Also renames FakeEvent to FalseAlarm! * thing * greytide but it's implemented badly * fixes&changies, also greytide! * rng actualy exists now * resync * Naming Schemes * Startup not init * areas are dead * very cool vsudio * this does not exist, wtf * Cleanup * Nullables, fixables, and timings Co-authored-by: Metal Gear Sloth --- .../StationEvents/StationEventsSystemTest.cs | 12 +- .../StationEvents/StationEventCommand.cs | 20 ++- .../StationEvents/StationEventSystem.cs | 29 ++-- Content.Server/StationEvents/FalseAlarm.cs | 30 ++++ .../StationEvents/PowerGridCheck.cs | 67 +++----- .../StationEvents/RadiationStorm.cs | 62 ++------ Content.Server/StationEvents/StationEvent.cs | 146 ++++++++++++++---- 7 files changed, 226 insertions(+), 140 deletions(-) create mode 100644 Content.Server/StationEvents/FalseAlarm.cs diff --git a/Content.IntegrationTests/Tests/StationEvents/StationEventsSystemTest.cs b/Content.IntegrationTests/Tests/StationEvents/StationEventsSystemTest.cs index 04b60218c7..cce62afc06 100644 --- a/Content.IntegrationTests/Tests/StationEvents/StationEventsSystemTest.cs +++ b/Content.IntegrationTests/Tests/StationEvents/StationEventsSystemTest.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.GameObjects.EntitySystems.StationEvents; using NUnit.Framework; using Robust.Shared.GameObjects.Systems; @@ -17,23 +17,27 @@ namespace Content.IntegrationTests.Tests.StationEvents server.Assert(() => { - // Idle each event once + // Idle each event var stationEventsSystem = EntitySystem.Get(); var dummyFrameTime = (float) IoCManager.Resolve().TickPeriod.TotalSeconds; foreach (var stationEvent in stationEventsSystem.StationEvents) { + stationEvent.Announce(); + stationEvent.Update(dummyFrameTime); stationEvent.Startup(); stationEvent.Update(dummyFrameTime); + stationEvent.Running = false; stationEvent.Shutdown(); - Assert.That(stationEvent.Occurrences == 1); + // Due to timings some events might startup twice when in reality they wouldn't. + Assert.That(stationEvent.Occurrences > 0); } stationEventsSystem.Reset(); foreach (var stationEvent in stationEventsSystem.StationEvents) { - Assert.That(stationEvent.Occurrences == 0); + Assert.That(stationEvent.Occurrences, Is.EqualTo(0)); } }); diff --git a/Content.Server/Commands/StationEvents/StationEventCommand.cs b/Content.Server/Commands/StationEvents/StationEventCommand.cs index 2dbcb806fa..95c8b910fd 100644 --- a/Content.Server/Commands/StationEvents/StationEventCommand.cs +++ b/Content.Server/Commands/StationEvents/StationEventCommand.cs @@ -14,7 +14,9 @@ namespace Content.Server.Commands.StationEvents { public string Command => "events"; public string Description => "Provides admin control to station events"; - public string Help => $"events >\n{ListHelp}\n{PauseHelp}\n{ResumeHelp}\n{RunHelp}"; + public string Help => $"events >\n{RunningHelp}\n{ListHelp}\n{PauseHelp}\n{ResumeHelp}\n{RunHelp}"; + + private const string RunningHelp = "running: return the current running event"; private const string ListHelp = "list: return all event names that can be run"; @@ -38,6 +40,9 @@ namespace Content.Server.Commands.StationEvents case "list": List(shell, player); break; + case "running": + Running(shell, player); + break; // Didn't use a "toggle" so it's explicit case "pause": Pause(shell, player); @@ -74,6 +79,19 @@ namespace Content.Server.Commands.StationEvents shell.SendText(player, resultText); } + private void Running(IConsoleShell shell, IPlayerSession? player) + { + var eventName = EntitySystem.Get().CurrentEvent?.Name; + if (!string.IsNullOrEmpty(eventName)) + { + shell.SendText(player, eventName); + } + else + { + shell.SendText(player, Loc.GetString("No station event running")); + } + } + private void List(IConsoleShell shell, IPlayerSession? player) { var resultText = "Random\n" + EntitySystem.Get().GetEventNames(); diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs index c7403f55b8..0d5e79c6d8 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs @@ -1,10 +1,11 @@ +#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; using Content.Server.GameTicking; -using Content.Server.Interfaces.GameTicking; using Content.Server.StationEvents; +using Content.Server.Interfaces.GameTicking; using Content.Shared; using Content.Shared.GameTicking; using Content.Shared.Network.NetMessages; @@ -34,7 +35,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IRobustRandom _random = default!; - public StationEvent CurrentEvent { get; private set; } + public StationEvent? CurrentEvent { get; private set; } public IReadOnlyCollection StationEvents => _stationEvents; private readonly List _stationEvents = new(); @@ -105,7 +106,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents CurrentEvent?.Shutdown(); CurrentEvent = stationEvent; - stationEvent.Startup(); + stationEvent.Announce(); return Loc.GetString("Running event ") + stationEvent.Name; } @@ -114,13 +115,12 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents } /// - /// Randomly run a valid event immediately, ignoring earlieststart + /// Randomly run a valid event immediately, ignoring earlieststart /// /// public string RunRandomEvent() { - var availableEvents = AvailableEvents(true); - var randomEvent = FindEvent(availableEvents); + var randomEvent = PickRandomEvent(); if (randomEvent == null) { @@ -134,6 +134,15 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents return Loc.GetString("Running ") + randomEvent.Name; } + /// + /// Randomly picks a valid event. + /// + public StationEvent? PickRandomEvent() + { + var availableEvents = AvailableEvents(true); + return FindEvent(availableEvents); + } + /// /// Admins can stop the currently running event (if applicable) and reset the timer /// @@ -246,7 +255,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents else { CurrentEvent = stationEvent; - CurrentEvent.Startup(); + CurrentEvent.Announce(); } } @@ -263,7 +272,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents /// Pick a random event from the available events at this time, also considering their weightings. /// /// - private StationEvent FindEvent(List availableEvents) + private StationEvent? FindEvent(List availableEvents) { if (availableEvents.Count == 0) { @@ -347,13 +356,13 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents public override void Shutdown() { - base.Shutdown(); CurrentEvent?.Shutdown(); + base.Shutdown(); } public void Reset() { - if (CurrentEvent != null && CurrentEvent.Running) + if (CurrentEvent?.Running == true) { CurrentEvent.Shutdown(); CurrentEvent = null; diff --git a/Content.Server/StationEvents/FalseAlarm.cs b/Content.Server/StationEvents/FalseAlarm.cs new file mode 100644 index 0000000000..70db0cc7bb --- /dev/null +++ b/Content.Server/StationEvents/FalseAlarm.cs @@ -0,0 +1,30 @@ +#nullable enable +using JetBrains.Annotations; +using Content.Server.GameObjects.EntitySystems.StationEvents; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.StationEvents +{ + [UsedImplicitly] + public sealed class FalseAlarm : StationEvent + { + public override string Name => "FalseAlarm"; + public override float Weight => WeightHigh; + protected override float EndAfter => 1.0f; + public override int? MaxOccurrences => 5; + + public override void Announce() + { + var stationEventSystem = EntitySystem.Get(); + var randomEvent = stationEventSystem.PickRandomEvent(); + + if (randomEvent != null) + { + StartAnnouncement = randomEvent.StartAnnouncement; + StartAudio = randomEvent.StartAudio; + } + + base.Announce(); + } + } +} diff --git a/Content.Server/StationEvents/PowerGridCheck.cs b/Content.Server/StationEvents/PowerGridCheck.cs index eb422b0d5a..31b000b2e7 100644 --- a/Content.Server/StationEvents/PowerGridCheck.cs +++ b/Content.Server/StationEvents/PowerGridCheck.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Threading; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using JetBrains.Annotations; @@ -16,59 +17,54 @@ namespace Content.Server.StationEvents public sealed class PowerGridCheck : StationEvent { public override string Name => "PowerGridCheck"; - - public override StationEventWeight Weight => StationEventWeight.Normal; - - public override int? MaxOccurrences => 2; - - protected override string StartAnnouncement => Loc.GetString( + public override float Weight => WeightNormal; + public override int? MaxOccurrences => 3; + public override string StartAnnouncement => Loc.GetString( "Abnormal activity detected in the station's powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration."); - protected override string EndAnnouncement => Loc.GetString( "Power has been restored to the station. We apologize for the inconvenience."); + public override string? StartAudio => "/Audio/Announcements/power_off.ogg"; - private float _elapsedTime; - private int _failDuration; + // If you need EndAudio it's down below. Not set here because we can't play it at the normal time without spamming sounds. - /// - /// So we don't overlap the announcement with power-down sounds we'll delay it a few seconds. - /// - private bool _announced; + protected override float StartAfter => 12.0f; - private CancellationTokenSource _announceCancelToken; + private CancellationTokenSource? _announceCancelToken; private readonly List _powered = new(); + public override void Announce() + { + base.Announce(); + EndAfter = IoCManager.Resolve().Next(60, 120); + } + public override void Startup() { - base.Startup(); - - _announced = false; - _elapsedTime = 0.0f; - _failDuration = IoCManager.Resolve().Next(60, 120); var componentManager = IoCManager.Resolve(); - foreach (PowerReceiverComponent component in componentManager.EntityQuery()) + foreach (var component in componentManager.EntityQuery()) { component.PowerDisabled = true; _powered.Add(component.Owner); } + + base.Startup(); } public override void Shutdown() { - base.Shutdown(); - foreach (var entity in _powered) { if (entity.Deleted) continue; - if (entity.TryGetComponent(out PowerReceiverComponent powerReceiverComponent)) + if (entity.TryGetComponent(out PowerReceiverComponent? powerReceiverComponent)) { powerReceiverComponent.PowerDisabled = false; } } + // Can't use the default EndAudio _announceCancelToken?.Cancel(); _announceCancelToken = new CancellationTokenSource(); Timer.Spawn(3000, () => @@ -76,29 +72,8 @@ namespace Content.Server.StationEvents EntitySystem.Get().PlayGlobal("/Audio/Announcements/power_on.ogg"); }, _announceCancelToken.Token); _powered.Clear(); - } - public override void Update(float frameTime) - { - if (!Running) - { - return; - } - - if (!_announced && _elapsedTime > 3.0f) - { - EntitySystem.Get().PlayGlobal("/Audio/Announcements/power_off.ogg"); - _announced = true; - } - - _elapsedTime += frameTime; - - if (_elapsedTime < _failDuration) - { - return; - } - - Running = false; + base.Shutdown(); } } } diff --git a/Content.Server/StationEvents/RadiationStorm.cs b/Content.Server/StationEvents/RadiationStorm.cs index 5b6a8f6fda..ee88eb47cb 100644 --- a/Content.Server/StationEvents/RadiationStorm.cs +++ b/Content.Server/StationEvents/RadiationStorm.cs @@ -1,12 +1,11 @@ +#nullable enable +using JetBrains.Annotations; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.StationEvents; using Content.Server.Interfaces.GameTicking; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.Utility; -using JetBrains.Annotations; -using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.Timing; -using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; @@ -26,24 +25,14 @@ namespace Content.Server.StationEvents [Dependency] private IRobustRandom _robustRandom = default!; public override string Name => "RadiationStorm"; - - protected override string StartAnnouncement => Loc.GetString( + public override string StartAnnouncement => Loc.GetString( "High levels of radiation detected near the station. Evacuate any areas containing abnormal green energy fields."); - protected override string EndAnnouncement => Loc.GetString( "The radiation threat has passed. Please return to your workplaces."); + public override string StartAudio => "/Audio/Announcements/radiation.ogg"; + protected override float StartAfter => 10.0f; - /// - /// How long until the radiation storm starts - /// - private const float StartupTime = 5; - - /// - /// How long the radiation storm has been running for - /// - private float _timeElapsed; - - private int _pulsesRemaining; + // Event specific details private float _timeUntilPulse; private const float MinPulseDelay = 0.2f; private const float MaxPulseDelay = 0.8f; @@ -53,15 +42,15 @@ namespace Content.Server.StationEvents _timeUntilPulse = _robustRandom.NextFloat() * (MaxPulseDelay - MinPulseDelay) + MinPulseDelay; } + public override void Announce() + { + base.Announce(); + EndAfter = _robustRandom.Next(30, 80) + StartAfter; // We want to be forgiving about the radstorm. + } + public override void Startup() { - base.Startup(); - EntitySystem.Get().PlayGlobal("/Audio/Announcements/radiation.ogg"); - IoCManager.InjectDependencies(this); - ResetTimeUntilPulse(); - _timeElapsed = 0.0f; - _pulsesRemaining = _robustRandom.Next(30, 100); var componentManager = IoCManager.Resolve(); @@ -69,42 +58,26 @@ namespace Content.Server.StationEvents { overlay.AddOverlay(SharedOverlayID.RadiationPulseOverlay); } + + base.Startup(); } public override void Shutdown() { - base.Shutdown(); - - // IOC uninject? - _entityManager = null; - _robustRandom = null; - var componentManager = IoCManager.Resolve(); foreach (var overlay in componentManager.EntityQuery()) { overlay.RemoveOverlay(SharedOverlayID.RadiationPulseOverlay); } + base.Shutdown(); } public override void Update(float frameTime) { - _timeElapsed += frameTime; + base.Update(frameTime); - if (_pulsesRemaining == 0) - { - Running = false; - } - - if (!Running) - { - return; - } - - if (_timeElapsed < StartupTime) - { - return; - } + if (!Started || !Running) return; _timeUntilPulse -= frameTime; @@ -129,7 +102,6 @@ namespace Content.Server.StationEvents var pulse = _entityManager.SpawnEntity("RadiationPulse", coordinates); pulse.GetComponent().DoPulse(); ResetTimeUntilPulse(); - _pulsesRemaining -= 1; } private bool TryFindRandomGrid(IMapGrid mapGrid, out EntityCoordinates coordinates) diff --git a/Content.Server/StationEvents/StationEvent.cs b/Content.Server/StationEvents/StationEvent.cs index 244cd2a3ff..b4deed65ac 100644 --- a/Content.Server/StationEvents/StationEvent.cs +++ b/Content.Server/StationEvents/StationEvent.cs @@ -1,76 +1,135 @@ +#nullable enable using Content.Server.Interfaces.Chat; +using Robust.Shared.GameObjects.Systems; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.Audio; using Robust.Shared.IoC; namespace Content.Server.StationEvents { public abstract class StationEvent { + public const float WeightVeryLow = 0.0f; + public const float WeightLow = 5.0f; + public const float WeightNormal = 10.0f; + public const float WeightHigh = 15.0f; + public const float WeightVeryHigh = 20.0f; + /// - /// If the event has started and is currently running + /// If the event has started and is currently running. /// - public bool Running { get; protected set; } - + public bool Running { get; set; } + /// - /// Human-readable name for the event + /// Human-readable name for the event. /// public abstract string Name { get; } - public virtual StationEventWeight Weight { get; } = StationEventWeight.Normal; + /// + /// The weight this event has in the random-selection process. + /// + public virtual float Weight => WeightNormal; /// - /// What should be said in chat when the event starts (if anything). + /// What should be said in chat when the event starts (if anything). /// - protected virtual string StartAnnouncement { get; } = null; + public virtual string? StartAnnouncement { get; set; } = null; /// - /// What should be said in chat when the event end (if anything). + /// What should be said in chat when the event ends (if anything). /// - protected virtual string EndAnnouncement { get; } = null; + protected virtual string? EndAnnouncement { get; } = null; /// - /// In minutes, when is the first time this event can start + /// Starting audio of the event. + /// + public virtual string? StartAudio { get; set; } = null; + + /// + /// Ending audio of the event. + /// + public virtual string? EndAudio { get; } = null; + + /// + /// In minutes, when is the first round time this event can start /// - /// public virtual int EarliestStart { get; } = 5; /// - /// How many players need to be present on station for the event to run + /// When in the lifetime to call Start(). /// - /// To avoid running deadly events with low-pop + protected virtual float StartAfter { get; } = 0.0f; + + /// + /// When in the lifetime the event should end. + /// + protected virtual float EndAfter { get; set; } = 0.0f; + + /// + /// How long has the event existed. Do not change this. + /// + private float Elapsed { get; set; } = 0.0f; + + /// + /// How many players need to be present on station for the event to run + /// + /// + /// To avoid running deadly events with low-pop + /// public virtual int MinimumPlayers { get; } = 0; /// - /// How many times this event has run this round + /// How many times this event has run this round /// public int Occurrences { get; set; } = 0; /// - /// How many times this even can occur in a single round + /// How many times this even can occur in a single round /// public virtual int? MaxOccurrences { get; } = null; /// - /// Called once when the station event starts + /// Has the startup time elapsed? + /// + protected bool Started { get; set; } = false; + + /// + /// Has this event commenced (announcement may or may not be used)? + /// + private bool Announced { get; set; } = false; + + /// + /// Called once to setup the event after StartAfter has elapsed. /// public virtual void Startup() { - Running = true; + Started = true; Occurrences += 1; + } + + /// + /// Called once as soon as an event is active. + /// Can also be used for some initial setup. + /// + public virtual void Announce() + { if (StartAnnouncement != null) { var chatManager = IoCManager.Resolve(); chatManager.DispatchStationAnnouncement(StartAnnouncement); } + + if (StartAudio != null) + { + EntitySystem.Get().PlayGlobal(StartAudio, AudioParams.Default.WithVolume(-10f)); + } + + Announced = true; + Running = true; } /// - /// Called every tick when this event is active - /// - /// - public abstract void Update(float frameTime); - - /// - /// Called once when the station event ends + /// Called once when the station event ends for any reason. /// public virtual void Shutdown() { @@ -79,15 +138,34 @@ namespace Content.Server.StationEvents var chatManager = IoCManager.Resolve(); chatManager.DispatchStationAnnouncement(EndAnnouncement); } + + if (EndAudio != null) + { + EntitySystem.Get().PlayGlobal(EndAudio, AudioParams.Default.WithVolume(-10f)); + } + + Started = false; + Announced = false; + Elapsed = 0; + } + + /// + /// Called every tick when this event is running. + /// + /// + public virtual void Update(float frameTime) + { + Elapsed += frameTime; + + if (!Started && Elapsed >= StartAfter) + { + Startup(); + } + + if (EndAfter <= Elapsed) + { + Running = false; + } } } - - public enum StationEventWeight - { - VeryLow = 0, - Low = 5, - Normal = 10, - High = 15, - VeryHigh = 20, - } -} \ No newline at end of file +}