-
Notifications
You must be signed in to change notification settings - Fork 450
Oscillating Event Scheduler #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
VMSolidus
merged 6 commits into
Simple-Station:master
from
Mnemotechnician:feat/oscillating-event-scheduler
Sep 8, 2024
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
df2bd56
Basic stuff done
Mnemotechnician 7e569e6
Fixed stuff
Mnemotechnician 88d7050
Slightly higher limits
Mnemotechnician 87bf74d
Update Resources/Locale/en-US/game-ticking/game-presets/preset-irregu…
Mnemotechnician 16964e2
Update Content.Server/StationEvents/Components/OscillatingStationEven…
Mnemotechnician 7eb0915
Apply suggestions from code review
Mnemotechnician File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
63 changes: 63 additions & 0 deletions
63
Content.Server/StationEvents/Components/OscillatingStationEventSchedulerComponent.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| namespace Content.Server.StationEvents.Components; | ||
|
|
||
| /// <summary> | ||
| /// A station event scheduler that emits events at irregular intervals, with occasional chaos and occasional calmness. | ||
| /// </summary> | ||
| [RegisterComponent] | ||
| public sealed partial class OscillatingStationEventSchedulerComponent : Component | ||
| { | ||
| // TODO cvars? | ||
| [DataField] | ||
| public float MinChaos = 0.1f, MaxChaos = 15f; | ||
|
|
||
| /// <summary> | ||
| /// The amount of chaos at the beginning of the round. | ||
| /// </summary> | ||
| [DataField] | ||
| public float StartingChaosRatio = 0f; | ||
|
|
||
| /// <summary> | ||
| /// The value of the first derivative of the event delay function at the beginning of the shift. | ||
| /// Must be between 1 and -1. | ||
| /// </summary> | ||
| [DataField] | ||
| public float StartingSlope = 1f; | ||
|
|
||
| /// <summary> | ||
| /// Biases that determine how likely the event rate is to go up or down, and how fast it's going to happen. | ||
| /// </summary> | ||
| /// <remarks>Downwards bias must always be negative, and upwards must be positive. Otherwise, you'll get odd behavior or errors.</remarks> | ||
| [DataField] | ||
| public float DownwardsBias = -1f, UpwardsBias = 1f; | ||
|
|
||
| /// <summary> | ||
| /// Limits that define how large the chaos slope can become. | ||
| /// </summary> | ||
| /// <remarks>Downwards limit must always be negative, and upwards must be positive. Otherwise, you'll get odd behavior or errors.</remarks> | ||
| [DataField] | ||
| public float DownwardsLimit = -1f, UpwardsLimit = 1f; | ||
|
|
||
| /// <summary> | ||
| /// A value between 0 and 1 that determines how slowly the chaos and its first derivative change in time. | ||
| /// </summary> | ||
| /// <remarks>Changing these values will have a great impact on how fast the event rate changes.</remarks> | ||
| [DataField] | ||
| public float ChaosStickiness = 0.93f, SlopeStickiness = 0.96f; | ||
|
|
||
| /// <summary> | ||
| /// Actual chaos data at the current moment. Those are overridden at runtime. | ||
| /// </summary> | ||
| [DataField] | ||
| public float CurrentChaos, CurrentSlope, LastAcceleration; | ||
|
|
||
|
|
||
| [DataField] | ||
| public TimeSpan NextUpdate = TimeSpan.Zero, LastEventTime = TimeSpan.Zero; | ||
|
|
||
| /// <summary> | ||
| /// Update interval, which determines how often current chaos is recalculated. | ||
| /// Modifying this value does not directly impact the event rate, but changes how stable the slope is. | ||
| /// </summary> | ||
| [DataField] | ||
| public TimeSpan UpdateInterval = TimeSpan.FromSeconds(5f); | ||
| } | ||
102 changes: 102 additions & 0 deletions
102
Content.Server/StationEvents/OscillatingStationEventScheduler.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| using System.Diagnostics; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using Content.Server.GameTicking; | ||
| using Content.Server.GameTicking.Rules; | ||
| using Content.Server.GameTicking.Rules.Components; | ||
| using Content.Server.StationEvents.Components; | ||
| using Content.Shared.CCVar; | ||
| using Robust.Shared.Configuration; | ||
| using Robust.Shared.Random; | ||
| using Robust.Shared.Timing; | ||
| using Robust.Shared.Utility; | ||
|
|
||
| namespace Content.Server.StationEvents; | ||
|
|
||
| public sealed class OscillatingStationEventSchedulerSystem : GameRuleSystem<OscillatingStationEventSchedulerComponent> | ||
| { | ||
| [Dependency] private readonly IConfigurationManager _cfg = default!; | ||
| [Dependency] private readonly IRobustRandom _random = default!; | ||
| [Dependency] private readonly EventManagerSystem _event = default!; | ||
| [Dependency] private readonly GameTicker _gameTicker = default!; | ||
| [Dependency] private readonly IGameTiming _timing = default!; | ||
|
|
||
| [Conditional("DEBUG")] | ||
| private void DebugValidateParams(OscillatingStationEventSchedulerComponent c) | ||
| { | ||
| // This monstrousity is necessary because if someone fucks up one of these parameters, | ||
| // it will likely either crash the game (in debug), or cause the event scheduler to stop working and spam the server console (in prod) | ||
| DebugTools.Assert(c.DownwardsBias <= 0f && c.UpwardsBias >= 0f, "Fix your scheduler bias!"); | ||
| DebugTools.Assert(c.DownwardsLimit <= 0f && c.UpwardsLimit >= 0f, "Fix your scheduler slope limits!"); | ||
| DebugTools.Assert(c.UpdateInterval > TimeSpan.Zero, "scheduler update interval must be positive!"); | ||
| DebugTools.Assert(c.ChaosStickiness >= 0f && c.ChaosStickiness <= 1f, "Scheduler stickiness must be between 0 and 1!"); | ||
| DebugTools.Assert(c.SlopeStickiness >= 0f && c.SlopeStickiness <= 1f, "Scheduler stickiness must be between 0 and 1!"); | ||
| DebugTools.Assert(c.MinChaos < c.MaxChaos, "What did you expect??"); | ||
Mnemotechnician marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private TimeSpan CalculateAverageEventTime(OscillatingStationEventSchedulerComponent comp) | ||
| { | ||
| // TODO those cvars are bad | ||
Mnemotechnician marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var min = _cfg.GetCVar(CCVars.GameEventsOscillatingMinimumTime); | ||
| var max = _cfg.GetCVar(CCVars.GameEventsOscillatingAverageTime); | ||
|
|
||
| return TimeSpan.FromSeconds(min + (max - min) / comp.CurrentChaos); // Why does C# have no math.lerp?????????????? | ||
| } | ||
|
|
||
| protected override void Started(EntityUid uid, OscillatingStationEventSchedulerComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) | ||
| { | ||
| DebugValidateParams(comp); | ||
|
|
||
| comp.CurrentChaos = comp.MinChaos + comp.StartingChaosRatio * (comp.MaxChaos - comp.MinChaos); | ||
| comp.CurrentSlope = comp.StartingSlope; | ||
|
|
||
| comp.NextUpdate = _timing.CurTime + CalculateAverageEventTime(comp); | ||
| comp.LastEventTime = _timing.CurTime; // just so we don't run an event the very moment this scheduler gets added | ||
Mnemotechnician marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| protected override void ActiveTick(EntityUid uid, OscillatingStationEventSchedulerComponent comp, GameRuleComponent gameRule, float frameTime) | ||
| { | ||
| if (comp.NextUpdate > _timing.CurTime) | ||
| return; | ||
| comp.NextUpdate = _timing.CurTime + comp.UpdateInterval; | ||
| DebugValidateParams(comp); | ||
|
|
||
| // Slope is the first derivative of chaos, and acceleration is the second | ||
| // We randomize acceleration on each tick and simulate its effect on the slope and base function | ||
| // But we spread the effect across a longer time span to achieve a smooth and pleasant result | ||
| var delta = (float) comp.UpdateInterval.TotalSeconds; | ||
| var newAcceleration = _random.NextFloat(comp.DownwardsBias, comp.UpwardsBias); | ||
| var newSlope = | ||
| Math.Clamp(comp.CurrentSlope + newAcceleration * delta, comp.DownwardsLimit, comp.UpwardsLimit) * (1 - comp.SlopeStickiness) | ||
| + comp.CurrentSlope * comp.SlopeStickiness; | ||
| var newChaos = | ||
| Math.Clamp(comp.CurrentChaos + newSlope * delta, comp.MinChaos, comp.MaxChaos) * (1 - comp.ChaosStickiness) | ||
| + comp.CurrentChaos * comp.ChaosStickiness; | ||
|
|
||
| comp.CurrentChaos = newChaos; | ||
| comp.CurrentSlope = newSlope; | ||
| comp.LastAcceleration = newAcceleration; | ||
|
|
||
| // We do not use fixed "next event" times because that can cause us to skip over chaos spikes due to periods of low chaos | ||
| // Instead we recalculate the time until next event every time, so it can change before the event is even started | ||
| var targetDelay = CalculateAverageEventTime(comp); | ||
| if (_timing.CurTime > comp.LastEventTime + targetDelay && TryRunNextEvent(uid, comp, out _)) | ||
| { | ||
| #if DEBUG | ||
| var passed = _timing.CurTime - comp.LastEventTime; | ||
| Log.Debug($"Running an event after {passed.TotalSeconds} sec since last event. Next event scheduled in {CalculateAverageEventTime(comp).TotalSeconds} sec."); | ||
| #endif | ||
|
|
||
| comp.LastEventTime = _timing.CurTime; | ||
| } | ||
| } | ||
|
|
||
| public bool TryRunNextEvent(EntityUid uid, OscillatingStationEventSchedulerComponent comp, [NotNullWhen(true)] out string? runEvent) | ||
| { | ||
| runEvent = _event.PickRandomEvent(); | ||
| if (runEvent == null) | ||
| return false; | ||
|
|
||
| _gameTicker.AddGameRule(runEvent); | ||
| return true; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
Resources/Locale/en-US/game-ticking/game-presets/preset-irregular.ftl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| irregular-title = Irregular | ||
| irregular-description = Threat level varies throughout the shift. Sometimes it's a paradise, sometimes it's a disaster. | ||
|
|
||
| irregular-extended-title = Irregular Extended | ||
| irregular-extended-description = A rather calm experience with occasional spikes of threats. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.