diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index a6b64af00d..0304978ed6 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -20,77 +20,30 @@ namespace Content.Server.StationEvents { public override string Prototype => "BasicStationEventScheduler"; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly EventManagerSystem _event = default!; private const float MinimumTimeUntilFirstEvent = 300; - private ISawmill _sawmill = default!; /// /// How long until the next check for an event runs /// /// Default value is how long until first event is allowed + [ViewVariables(VVAccess.ReadWrite)] private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent; - public override void Initialize() - { - base.Initialize(); - - _sawmill = Logger.GetSawmill("basicevents"); - - // Can't just check debug / release for a default given mappers need to use release mode - // As such we'll always pause it by default. - _configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true); - - SubscribeLocalEvent(Reset); - } - - public override void Shutdown() - { - base.Shutdown(); - _configurationManager.UnsubValueChanged(CCVars.EventsEnabled, SetEnabled); - } - - public bool EventsEnabled { get; private set; } - private void SetEnabled(bool value) => EventsEnabled = value; - public override void Started() { } - public override void Ended() { } - /// - /// Randomly run a valid event immediately, ignoring earlieststart or whether the event is enabled - /// - /// - public string RunRandomEvent() + public override void Ended() { - var randomEvent = PickRandomEvent(); - - if (randomEvent == null - || !_prototype.TryIndex(randomEvent.Id, out var proto)) - { - return Loc.GetString("station-event-system-run-random-event-no-valid-events"); - } - - GameTicker.AddGameRule(proto); - return Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Id)); - } - - /// - /// Randomly picks a valid event. - /// - public StationEventRuleConfiguration? PickRandomEvent() - { - var availableEvents = AvailableEvents(true); - return FindEvent(availableEvents); + _timeUntilNextEvent = MinimumTimeUntilFirstEvent; } public override void Update(float frameTime) { base.Update(frameTime); - if (!RuleStarted || !EventsEnabled) + if (!RuleStarted || !_event.EventsEnabled) return; if (_timeUntilNextEvent > 0) @@ -99,17 +52,8 @@ namespace Content.Server.StationEvents return; } - // No point hammering this trying to find events if none are available - var stationEvent = FindEvent(AvailableEvents()); - if (stationEvent == null - || !_prototype.TryIndex(stationEvent.Id, out var proto)) - { - return; - } - - GameTicker.AddGameRule(proto); + _event.RunRandomEvent(); ResetTimer(); - _sawmill.Info($"Started event {proto.ID}. Next event in {_timeUntilNextEvent} seconds"); } /// @@ -120,132 +64,5 @@ namespace Content.Server.StationEvents // 5 - 25 minutes. TG does 3-10 but that's pretty frequent _timeUntilNextEvent = _random.Next(300, 1500); } - - /// - /// Pick a random event from the available events at this time, also considering their weightings. - /// - /// - private StationEventRuleConfiguration? FindEvent(List availableEvents) - { - if (availableEvents.Count == 0) - { - return null; - } - - var sumOfWeights = 0; - - foreach (var stationEvent in availableEvents) - { - sumOfWeights += (int) stationEvent.Weight; - } - - sumOfWeights = _random.Next(sumOfWeights); - - foreach (var stationEvent in availableEvents) - { - sumOfWeights -= (int) stationEvent.Weight; - - if (sumOfWeights <= 0) - { - return stationEvent; - } - } - - return null; - } - - /// - /// Gets the events that have met their player count, time-until start, etc. - /// - /// - /// - private List AvailableEvents(bool ignoreEarliestStart = false) - { - TimeSpan currentTime; - var playerCount = _playerManager.PlayerCount; - - // playerCount does a lock so we'll just keep the variable here - if (!ignoreEarliestStart) - { - currentTime = GameTicker.RoundDuration(); - } - else - { - currentTime = TimeSpan.Zero; - } - - var result = new List(); - - foreach (var stationEvent in AllEvents()) - { - if (CanRun(stationEvent, playerCount, currentTime)) - { - result.Add(stationEvent); - } - } - - return result; - } - - private IEnumerable AllEvents() - { - return _prototype.EnumeratePrototypes() - .Where(p => p.Configuration is StationEventRuleConfiguration) - .Select(p => (StationEventRuleConfiguration) p.Configuration); - } - - private int GetOccurrences(StationEventRuleConfiguration stationEvent) - { - return GameTicker.AllPreviousGameRules.Count(p => p.Item2.ID == stationEvent.Id); - } - - public TimeSpan TimeSinceLastEvent(StationEventRuleConfiguration? stationEvent) - { - foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse()) - { - if (rule.Configuration is not StationEventRuleConfiguration) - continue; - - if (stationEvent == null || rule.ID == stationEvent.Id) - return time; - } - - return TimeSpan.Zero; - } - - private bool CanRun(StationEventRuleConfiguration stationEvent, int playerCount, TimeSpan currentTime) - { - if (GameTicker.IsGameRuleStarted(stationEvent.Id)) - return false; - - if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(stationEvent) >= stationEvent.MaxOccurrences.Value) - { - return false; - } - - if (playerCount < stationEvent.MinimumPlayers) - { - return false; - } - - if (currentTime != TimeSpan.Zero && currentTime.TotalMinutes < stationEvent.EarliestStart) - { - return false; - } - - var lastRun = TimeSinceLastEvent(stationEvent); - if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes < - stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes) - { - return false; - } - - return true; - } - - private void Reset(RoundRestartCleanupEvent ev) - { - _timeUntilNextEvent = MinimumTimeUntilFirstEvent; - } } } diff --git a/Content.Server/StationEvents/EventManagerSystem.cs b/Content.Server/StationEvents/EventManagerSystem.cs new file mode 100644 index 0000000000..6a2b646602 --- /dev/null +++ b/Content.Server/StationEvents/EventManagerSystem.cs @@ -0,0 +1,197 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; +using Content.Server.GameTicking.Rules.Configurations; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents; + +public sealed class EventManagerSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] public readonly GameTicker GameTicker = default!; + + private ISawmill _sawmill = default!; + + public bool EventsEnabled { get; private set; } + private void SetEnabled(bool value) => EventsEnabled = value; + + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("events"); + + _configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true); + } + + public override void Shutdown() + { + base.Shutdown(); + _configurationManager.UnsubValueChanged(CCVars.EventsEnabled, SetEnabled); + } + + /// + /// Randomly runs a valid event. + /// + public string RunRandomEvent() + { + var randomEvent = PickRandomEvent(); + + if (randomEvent == null + || !_prototype.TryIndex(randomEvent.Id, out var proto)) + { + var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events"); + _sawmill.Error(errStr); + return errStr; + } + + GameTicker.AddGameRule(proto); + var str = Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Id)); + _sawmill.Info(str); + return str; + } + + /// + /// Randomly picks a valid event. + /// + public StationEventRuleConfiguration? PickRandomEvent() + { + var availableEvents = AvailableEvents(); + _sawmill.Info($"Picking from {availableEvents.Count} total available events"); + return FindEvent(availableEvents); + } + + /// + /// Pick a random event from the available events at this time, also considering their weightings. + /// + /// + private StationEventRuleConfiguration? FindEvent(List availableEvents) + { + if (availableEvents.Count == 0) + { + _sawmill.Warning("No events were available to run!"); + return null; + } + + var sumOfWeights = 0; + + foreach (var stationEvent in availableEvents) + { + sumOfWeights += (int) stationEvent.Weight; + } + + sumOfWeights = _random.Next(sumOfWeights); + + foreach (var stationEvent in availableEvents) + { + sumOfWeights -= (int) stationEvent.Weight; + + if (sumOfWeights <= 0) + { + return stationEvent; + } + } + + _sawmill.Error("Event was not found after weighted pick process!"); + return null; + } + + /// + /// Gets the events that have met their player count, time-until start, etc. + /// + /// + /// + private List AvailableEvents(bool ignoreEarliestStart = false) + { + TimeSpan currentTime; + var playerCount = _playerManager.PlayerCount; + + // playerCount does a lock so we'll just keep the variable here + if (!ignoreEarliestStart) + { + currentTime = GameTicker.RoundDuration(); + } + else + { + currentTime = TimeSpan.Zero; + } + + var result = new List(); + + foreach (var stationEvent in AllEvents()) + { + if (CanRun(stationEvent, playerCount, currentTime)) + { + _sawmill.Debug($"Adding event {stationEvent.Id} to possibilities"); + result.Add(stationEvent); + } + } + + return result; + } + + private IEnumerable AllEvents() + { + return _prototype.EnumeratePrototypes() + .Where(p => p.Configuration is StationEventRuleConfiguration) + .Select(p => (StationEventRuleConfiguration) p.Configuration); + } + + private int GetOccurrences(StationEventRuleConfiguration stationEvent) + { + return GameTicker.AllPreviousGameRules.Count(p => p.Item2.ID == stationEvent.Id); + } + + public TimeSpan TimeSinceLastEvent(StationEventRuleConfiguration? stationEvent) + { + foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse()) + { + if (rule.Configuration is not StationEventRuleConfiguration) + continue; + + if (stationEvent == null || rule.ID == stationEvent.Id) + return time; + } + + return TimeSpan.Zero; + } + + private bool CanRun(StationEventRuleConfiguration stationEvent, int playerCount, TimeSpan currentTime) + { + if (GameTicker.IsGameRuleStarted(stationEvent.Id)) + return false; + + if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(stationEvent) >= stationEvent.MaxOccurrences.Value) + { + return false; + } + + if (playerCount < stationEvent.MinimumPlayers) + { + return false; + } + + if (currentTime != TimeSpan.Zero && currentTime.TotalMinutes < stationEvent.EarliestStart) + { + return false; + } + + var lastRun = TimeSinceLastEvent(stationEvent); + if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes < + stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes) + { + return false; + } + + return true; + } +} diff --git a/Content.Server/StationEvents/Events/BureaucraticError.cs b/Content.Server/StationEvents/Events/BureaucraticError.cs index d765d387b4..3343fe83e0 100644 --- a/Content.Server/StationEvents/Events/BureaucraticError.cs +++ b/Content.Server/StationEvents/Events/BureaucraticError.cs @@ -16,13 +16,16 @@ public sealed class BureaucraticError : StationEventSystem { base.Started(); - if (StationSystem.Stations.Count == 0) return; // No stations + if (StationSystem.Stations.Count == 0) + return; // No stations var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList()); var jobList = _stationJobs.GetJobs(chosenStation).Keys.ToList(); + var mod = GetSeverityModifier(); + // Low chance to completely change up the late-join landscape by closing all positions except infinite slots. // Lower chance than the /tg/ equivalent of this event. - if (RobustRandom.Prob(0.25f)) + if (RobustRandom.Prob(Math.Min(0.25f * MathF.Sqrt(mod), 1.0f))) { var chosenJob = RobustRandom.PickAndTake(jobList); _stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos. @@ -35,8 +38,10 @@ public sealed class BureaucraticError : StationEventSystem } else { + var lower = (int) (jobList.Count * Math.Min(1.0f, 0.20 * mod)); + var upper = (int) (jobList.Count * Math.Min(1.0f, 0.30 * mod)); // Changing every role is maybe a bit too chaotic so instead change 20-30% of them. - for (var i = 0; i < RobustRandom.Next((int)(jobList.Count * 0.20), (int)(jobList.Count * 0.30)); i++) + for (var i = 0; i < RobustRandom.Next(lower, upper); i++) { var chosenJob = RobustRandom.PickAndTake(jobList); if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob)) diff --git a/Content.Server/StationEvents/Events/GasLeak.cs b/Content.Server/StationEvents/Events/GasLeak.cs index 5469407896..016e145c8b 100644 --- a/Content.Server/StationEvents/Events/GasLeak.cs +++ b/Content.Server/StationEvents/Events/GasLeak.cs @@ -59,6 +59,8 @@ namespace Content.Server.StationEvents.Events { base.Started(); + var mod = MathF.Sqrt(GetSeverityModifier()); + // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there. if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords)) { @@ -66,7 +68,7 @@ namespace Content.Server.StationEvents.Events _leakGas = RobustRandom.Pick(LeakableGases); // Was 50-50 on using normal distribution. - var totalGas = (float) RobustRandom.Next(MinimumGas, MaximumGas); + var totalGas = RobustRandom.Next(MinimumGas, MaximumGas) * mod; var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter; _molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond); _endAfter = totalGas / _molesPerSecond + startAfter; diff --git a/Content.Server/StationEvents/Events/MeteorSwarm.cs b/Content.Server/StationEvents/Events/MeteorSwarm.cs index 525dff4dc8..e11c4ae778 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarm.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarm.cs @@ -30,7 +30,8 @@ namespace Content.Server.StationEvents.Events public override void Started() { base.Started(); - _waveCounter = RobustRandom.Next(MinimumWaves, MaximumWaves); + var mod = Math.Sqrt(GetSeverityModifier()); + _waveCounter = (int) (RobustRandom.Next(MinimumWaves, MaximumWaves) * mod); } public override void Ended() @@ -53,13 +54,16 @@ namespace Content.Server.StationEvents.Events return; } + var mod = GetSeverityModifier(); + _cooldown -= frameTime; - if (_cooldown > 0f) return; + if (_cooldown > 0f) + return; _waveCounter--; - _cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() + MinimumCooldown; + _cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() / mod + MinimumCooldown; Box2? playableArea = null; var mapId = GameTicker.DefaultMap; diff --git a/Content.Server/StationEvents/Events/MouseMigration.cs b/Content.Server/StationEvents/Events/MouseMigration.cs index b6c4405065..e2f2a5b9b0 100644 --- a/Content.Server/StationEvents/Events/MouseMigration.cs +++ b/Content.Server/StationEvents/Events/MouseMigration.cs @@ -15,15 +15,18 @@ public sealed class MouseMigration : StationEventSystem { base.Started(); + var modifier = GetSeverityModifier(); + var spawnLocations = EntityManager.EntityQuery().ToList(); RobustRandom.Shuffle(spawnLocations); - var spawnAmount = RobustRandom.Next(7, 15); // A small colony of critters. + // sqrt so we dont get insane values for ramping events + var spawnAmount = (int) (RobustRandom.Next(7, 15) * Math.Sqrt(modifier)); // A small colony of critters. for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++) { var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices); - if (RobustRandom.Prob(0.01f) || i == 0) //small chance for multiple, but always at least 1 + if (RobustRandom.Prob(Math.Min(0.01f * modifier, 1.0f)) || i == 0) //small chance for multiple, but always at least 1 spawnChoice = "SpawnPointGhostRatKing"; var coords = spawnLocations[i].Item2.Coordinates; diff --git a/Content.Server/StationEvents/Events/RandomSentience.cs b/Content.Server/StationEvents/Events/RandomSentience.cs index e2c30a5933..275bf6097a 100644 --- a/Content.Server/StationEvents/Events/RandomSentience.cs +++ b/Content.Server/StationEvents/Events/RandomSentience.cs @@ -18,10 +18,11 @@ public sealed class RandomSentience : StationEventSystem base.Started(); HashSet stationsToNotify = new(); + var mod = GetSeverityModifier(); var targetList = EntityManager.EntityQuery().ToList(); RobustRandom.Shuffle(targetList); - var toMakeSentient = RobustRandom.Next(2, 5); + var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod)); var groups = new HashSet(); foreach (var target in targetList) diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index 7f8c5a3460..46eed70731 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -179,6 +179,26 @@ namespace Content.Server.StationEvents.Events .Where(p => p.Configuration is StationEventRuleConfiguration).ToArray()); } + public float GetSeverityModifier() + { + var ev = new GetSeverityModifierEvent(); + RaiseLocalEvent(ev); + return ev.Modifier; + } + #endregion } + + /// + /// Raised broadcast to determine what the severity modifier should be for an event, some positive number that can be multiplied with various things. + /// Handled by usually other game rules (like the ramping scheduler). + /// Most events should try and make use of this if possible. + /// + public sealed class GetSeverityModifierEvent : EntityEventArgs + { + /// + /// Should be multiplied/added to rather than set, for commutativity. + /// + public float Modifier = 1.0f; + } } diff --git a/Content.Server/StationEvents/Events/VentClog.cs b/Content.Server/StationEvents/Events/VentClog.cs index ba1aca3fde..5a1e80ca56 100644 --- a/Content.Server/StationEvents/Events/VentClog.cs +++ b/Content.Server/StationEvents/Events/VentClog.cs @@ -31,15 +31,16 @@ public sealed class VentClog : StationEventSystem // This is gross, but not much can be done until event refactor, which needs Dynamic. var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg"); + var mod = (float) Math.Sqrt(GetSeverityModifier()); foreach (var (_, transform) in EntityManager.EntityQuery()) { var solution = new Solution(); - if (!RobustRandom.Prob(0.33f)) + if (!RobustRandom.Prob(Math.Min(0.33f * mod, 1.0f))) continue; - if (RobustRandom.Prob(0.05f)) + if (RobustRandom.Prob(Math.Min(0.05f * mod, 1.0f))) { solution.AddReagent(RobustRandom.Pick(allReagents), 100); } @@ -48,7 +49,7 @@ public sealed class VentClog : StationEventSystem solution.AddReagent(RobustRandom.Pick(SafeishVentChemicals), 100); } - FoamAreaReactionEffect.SpawnFoam("Foam", transform.Coordinates, solution, RobustRandom.Next(2, 6), 20, 1, + FoamAreaReactionEffect.SpawnFoam("Foam", transform.Coordinates, solution, (int) (RobustRandom.Next(2, 6) * mod), 20, 1, 1, sound, EntityManager); } } diff --git a/Content.Server/StationEvents/Events/VentCritters.cs b/Content.Server/StationEvents/Events/VentCritters.cs index 4054ff4db4..d1569b07f1 100644 --- a/Content.Server/StationEvents/Events/VentCritters.cs +++ b/Content.Server/StationEvents/Events/VentCritters.cs @@ -18,7 +18,9 @@ public sealed class VentCritters : StationEventSystem var spawnLocations = EntityManager.EntityQuery().ToList(); RobustRandom.Shuffle(spawnLocations); - var spawnAmount = RobustRandom.Next(4, 12); // A small colony of critters. + var mod = Math.Sqrt(GetSeverityModifier()); + + var spawnAmount = (int) (RobustRandom.Next(4, 12) * mod); // A small colony of critters. Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}"); foreach (var location in spawnLocations) { diff --git a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs new file mode 100644 index 0000000000..d22e2d86dc --- /dev/null +++ b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs @@ -0,0 +1,104 @@ +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; +using Content.Server.StationEvents.Events; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents; + +public sealed class RampingStationEventSchedulerSystem : GameRuleSystem +{ + public override string Prototype => "RampingStationEventScheduler"; + + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EventManagerSystem _event = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + + [ViewVariables(VVAccess.ReadWrite)] + private float _endTime; + [ViewVariables(VVAccess.ReadWrite)] + private float _maxChaos; + [ViewVariables(VVAccess.ReadWrite)] + private float _startingChaos; + [ViewVariables(VVAccess.ReadWrite)] + private float _timeUntilNextEvent; + + [ViewVariables] + public float ChaosModifier + { + get + { + var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds; + if (roundTime > _endTime) + return _maxChaos; + + return (_maxChaos / _endTime) * roundTime + _startingChaos; + } + } + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetSeverityModifier); + } + + public override void Started() + { + var avgChaos = _cfg.GetCVar(CCVars.EventsRampingAverageChaos); + var avgTime = _cfg.GetCVar(CCVars.EventsRampingAverageEndTime); + + // Worlds shittiest probability distribution + // Got a complaint? Send them to + _maxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4); + // This is in minutes, so *60 for seconds (for the chaos calc) + _endTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f; + _startingChaos = _maxChaos / 10; + + PickNextEventTime(); + } + + public override void Ended() + { + _endTime = 0f; + _maxChaos = 0f; + _startingChaos = 0f; + _timeUntilNextEvent = 0f; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!RuleStarted || !_event.EventsEnabled) + return; + + if (_timeUntilNextEvent > 0f) + { + _timeUntilNextEvent -= frameTime; + return; + } + + PickNextEventTime(); + _event.RunRandomEvent(); + } + + private void OnGetSeverityModifier(GetSeverityModifierEvent ev) + { + if (!RuleStarted) + return; + + ev.Modifier *= ChaosModifier; + Logger.Info($"Ramping set modifier to {ev.Modifier}"); + } + + private void PickNextEventTime() + { + var mod = ChaosModifier; + + // 4-12 minutes baseline. Will get faster over time as the chaos mod increases. + _timeUntilNextEvent = _random.NextFloat(240f / mod, 720f / mod); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 4bbaf779da..a83eb7b422 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -96,9 +96,8 @@ namespace Content.Shared.CCVar public static readonly CVarDef StatusMoMMIPassword = CVarDef.Create("status.mommipassword", "", CVar.SERVERONLY | CVar.CONFIDENTIAL); - /* - * Game + * Events */ /// @@ -107,6 +106,24 @@ namespace Content.Shared.CCVar public static readonly CVarDef EventsEnabled = CVarDef.Create("events.enabled", true, CVar.ARCHIVE | CVar.SERVERONLY); + /// + /// Average time (in minutes) for when the ramping event scheduler should stop increasing the chaos modifier. + /// Close to how long you expect a round to last, so you'll probably have to tweak this on downstreams. + /// + public static readonly CVarDef + EventsRampingAverageEndTime = CVarDef.Create("events.ramping_average_end_time", 40f, CVar.ARCHIVE | CVar.SERVERONLY); + + /// + /// Average ending chaos modifier for the ramping event scheduler. + /// Max chaos chosen for a round will deviate from this + /// + public static readonly CVarDef + EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 6f, CVar.ARCHIVE | CVar.SERVERONLY); + + /* + * Game + */ + /// /// Disables most functionality in the GameTicker. /// diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-extended.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-extended.ftl index 56223aef40..a8c7f332ea 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-extended.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-extended.ftl @@ -1,2 +1,2 @@ extended-title = Extended -extended-description = No antagonists, have fun! +extended-description = A calm experience. Admin intervention required. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-survival.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-survival.ftl new file mode 100644 index 0000000000..231733eabf --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-survival.ftl @@ -0,0 +1,2 @@ +survival-title = Survival +survival-description = No internal threats, but how long can the station survive increasingly chaotic and frequent events? diff --git a/Resources/Locale/en-US/station-events/station-event-system.ftl b/Resources/Locale/en-US/station-events/station-event-system.ftl index 2e7843326e..0a117fbdd6 100644 --- a/Resources/Locale/en-US/station-events/station-event-system.ftl +++ b/Resources/Locale/en-US/station-events/station-event-system.ftl @@ -1,4 +1,4 @@ ## BasicStationEventSchedulerSystem station-event-system-run-event = Running event {$eventName} -station-event-system-run-random-event-no-valid-events = No valid events available +station-event-system-run-random-event-no-valid-events = No valid event was given diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index b7f49e5421..5a62ca2706 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -72,3 +72,9 @@ config: !type:GenericGameRuleConfiguration id: BasicStationEventScheduler + +- type: gameRule + id: RampingStationEventScheduler + config: + !type:GenericGameRuleConfiguration + id: RampingStationEventScheduler diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index afc0369517..f56452f20b 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -1,13 +1,23 @@ +- type: gamePreset + id: Survival + alias: + - survival + name: survival-title + showInVote: false # secret + description: survival-description + rules: + - RampingStationEventScheduler + - type: gamePreset id: Extended alias: - - extended - - shittersafari + - extended + - shittersafari name: extended-title showInVote: false #2boring2vote description: extended-description rules: - - BasicStationEventScheduler + - BasicStationEventScheduler - type: gamePreset id: Secret diff --git a/Resources/Prototypes/round_announcements.yml b/Resources/Prototypes/round_announcements.yml index 6c3d5d3a27..0496bb4b22 100644 --- a/Resources/Prototypes/round_announcements.yml +++ b/Resources/Prototypes/round_announcements.yml @@ -2,7 +2,7 @@ id: Welcome sound: /Audio/Announcements/welcome.ogg presets: - - Extended + - Survival - Sandbox - Secret - Traitor diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index fb64d74325..9c392a1549 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -1,7 +1,7 @@ - type: weightedRandom id: Secret weights: - Extended: 0.25 + Survival: 0.25 Nukeops: 0.25 Traitor: 0.75 Zombie: 0.05