From: Kara Date: Mon, 6 May 2024 07:33:30 +0000 (-0700) Subject: Round event frequency simulation command (#27718) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=c1aae2398b5e4510b9b07ea7af877fc5760dd5c3;p=space-station-14.git Round event frequency simulation command (#27718) --- diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index efa9cc096d..7e32f546be 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Administration; +using Content.Server.GameTicking; using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; @@ -22,6 +23,9 @@ namespace Content.Server.StationEvents [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EventManagerSystem _event = default!; + public const float MinEventTime = 60 * 3; + public const float MaxEventTime = 60 * 10; + protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) { @@ -58,7 +62,7 @@ namespace Content.Server.StationEvents /// private void ResetTimer(BasicStationEventSchedulerComponent component) { - component.TimeUntilNextEvent = _random.Next(3 * 60, 10 * 60); + component.TimeUntilNextEvent = _random.NextFloat(MinEventTime, MaxEventTime); } } @@ -66,6 +70,59 @@ namespace Content.Server.StationEvents public sealed class StationEventCommand : ToolshedCommand { private EventManagerSystem? _stationEvent; + private BasicStationEventSchedulerSystem? _basicScheduler; + private IRobustRandom? _random; + + /// + /// Estimates the expected number of times an event will run over the course of X rounds, taking into account weights and + /// how many events are expected to run over a given timeframe for a given playercount by repeatedly simulating rounds. + /// Effectively /100 (if you put 100 rounds) = probability an event will run per round. + /// + /// + /// This isn't perfect. Code path eventually goes into , which requires + /// state from . As a result, you should probably just run this locally and not doing + /// a real round (it won't pollute the state, but it will get contaminated by previously ran events in the actual round) + /// and things like `MaxOccurrences` and `ReoccurrenceDelay` won't be respected. + /// + /// I consider these to not be that relevant to the analysis here though (and I don't want most uses of them + /// to even exist) so I think it's fine. + /// + [CommandImplementation("simulate")] + public IEnumerable<(string, float)> Simulate([CommandArgument] int rounds, [CommandArgument] int playerCount, [CommandArgument] float roundEndMean, [CommandArgument] float roundEndStdDev) + { + _stationEvent ??= GetSys(); + _basicScheduler ??= GetSys(); + _random ??= IoCManager.Resolve(); + + var occurrences = new Dictionary(); + + foreach (var ev in _stationEvent.AllEvents()) + { + occurrences.Add(ev.Key.ID, 0); + } + + for (var i = 0; i < rounds; i++) + { + var curTime = TimeSpan.Zero; + var randomEndTime = _random.NextGaussian(roundEndMean, roundEndStdDev) * 60; // *60 = minutes to seconds + if (randomEndTime <= 0) + continue; + + while (curTime.TotalSeconds < randomEndTime) + { + // sim an event + curTime += TimeSpan.FromSeconds(_random.NextFloat(BasicStationEventSchedulerSystem.MinEventTime, BasicStationEventSchedulerSystem.MaxEventTime)); + var available = _stationEvent.AvailableEvents(false, playerCount, curTime); + var ev = _stationEvent.FindEvent(available); + if (ev == null) + continue; + + occurrences[ev] += 1; + } + } + + return occurrences.Select(p => (p.Key, (float) p.Value)).OrderByDescending(p => p.Item2); + } [CommandImplementation("lsprob")] public IEnumerable<(string, float)> LsProb() diff --git a/Content.Server/StationEvents/EventManagerSystem.cs b/Content.Server/StationEvents/EventManagerSystem.cs index 1a26417b05..c8552895af 100644 --- a/Content.Server/StationEvents/EventManagerSystem.cs +++ b/Content.Server/StationEvents/EventManagerSystem.cs @@ -61,7 +61,7 @@ public sealed class EventManagerSystem : EntitySystem /// Pick a random event from the available events at this time, also considering their weightings. /// /// - private string? FindEvent(Dictionary availableEvents) + public string? FindEvent(Dictionary availableEvents) { if (availableEvents.Count == 0) { @@ -95,16 +95,20 @@ public sealed class EventManagerSystem : EntitySystem /// /// Gets the events that have met their player count, time-until start, etc. /// - /// + /// Override for player count, if using this to simulate events rather than in an actual round. + /// Override for round time, if using this to simulate events rather than in an actual round. /// - private Dictionary AvailableEvents(bool ignoreEarliestStart = false) + public Dictionary AvailableEvents( + bool ignoreEarliestStart = false, + int? playerCountOverride = null, + TimeSpan? currentTimeOverride = null) { - var playerCount = _playerManager.PlayerCount; + var playerCount = playerCountOverride ?? _playerManager.PlayerCount; // playerCount does a lock so we'll just keep the variable here - var currentTime = !ignoreEarliestStart + var currentTime = currentTimeOverride ?? (!ignoreEarliestStart ? GameTicker.RoundDuration() - : TimeSpan.Zero; + : TimeSpan.Zero); var result = new Dictionary(); @@ -112,7 +116,6 @@ public sealed class EventManagerSystem : EntitySystem { if (CanRun(proto, stationEvent, playerCount, currentTime)) { - Log.Debug($"Adding event {proto.ID} to possibilities"); result.Add(proto, stationEvent); } }