]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Round event frequency simulation command (#27718)
authorKara <lunarautomaton6@gmail.com>
Mon, 6 May 2024 07:33:30 +0000 (00:33 -0700)
committerGitHub <noreply@github.com>
Mon, 6 May 2024 07:33:30 +0000 (00:33 -0700)
Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs
Content.Server/StationEvents/EventManagerSystem.cs

index efa9cc096dd5821826e9c96c46361856f16da00b..7e32f546bed1b45fa734a9fee1b9e995523650f4 100644 (file)
@@ -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
         /// </summary>
         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;
+
+        /// <summary>
+        ///     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.
+        /// </summary>
+        /// <remarks>
+        ///     This isn't perfect. Code path eventually goes into <see cref="EventManagerSystem.CanRun"/>, which requires
+        ///     state from <see cref="GameTicker"/>. 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.
+        /// </remarks>
+        [CommandImplementation("simulate")]
+        public IEnumerable<(string, float)> Simulate([CommandArgument] int rounds, [CommandArgument] int playerCount, [CommandArgument] float roundEndMean, [CommandArgument] float roundEndStdDev)
+        {
+            _stationEvent ??= GetSys<EventManagerSystem>();
+            _basicScheduler ??= GetSys<BasicStationEventSchedulerSystem>();
+            _random ??= IoCManager.Resolve<IRobustRandom>();
+
+            var occurrences = new Dictionary<string, int>();
+
+            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()
index 1a26417b0575e9d9903d634e0fdf8e6598025373..c8552895afff72e67677a4d4829573f662524854 100644 (file)
@@ -61,7 +61,7 @@ public sealed class EventManagerSystem : EntitySystem
     /// Pick a random event from the available events at this time, also considering their weightings.
     /// </summary>
     /// <returns></returns>
-    private string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
+    public string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
     {
         if (availableEvents.Count == 0)
         {
@@ -95,16 +95,20 @@ public sealed class EventManagerSystem : EntitySystem
     /// <summary>
     /// Gets the events that have met their player count, time-until start, etc.
     /// </summary>
-    /// <param name="ignoreEarliestStart"></param>
+    /// <param name="playerCountOverride">Override for player count, if using this to simulate events rather than in an actual round.</param>
+    /// <param name="currentTimeOverride">Override for round time, if using this to simulate events rather than in an actual round.</param>
     /// <returns></returns>
-    private Dictionary<EntityPrototype, StationEventComponent> AvailableEvents(bool ignoreEarliestStart = false)
+    public Dictionary<EntityPrototype, StationEventComponent> 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<EntityPrototype, StationEventComponent>();
 
@@ -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);
             }
         }