Extended rework & ramping event scheduler (#11362)
This commit is contained in:
@@ -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!;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the next check for an event runs
|
||||
/// </summary>
|
||||
/// 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<RoundRestartCleanupEvent>(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() { }
|
||||
|
||||
/// <summary>
|
||||
/// Randomly run a valid event <b>immediately</b>, ignoring earlieststart or whether the event is enabled
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string RunRandomEvent()
|
||||
public override void Ended()
|
||||
{
|
||||
var randomEvent = PickRandomEvent();
|
||||
|
||||
if (randomEvent == null
|
||||
|| !_prototype.TryIndex<GameRulePrototype>(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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly picks a valid event.
|
||||
/// </summary>
|
||||
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<GameRulePrototype>(stationEvent.Id, out var proto))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameTicker.AddGameRule(proto);
|
||||
_event.RunRandomEvent();
|
||||
ResetTimer();
|
||||
_sawmill.Info($"Started event {proto.ID}. Next event in {_timeUntilNextEvent} seconds");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a random event from the available events at this time, also considering their weightings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private StationEventRuleConfiguration? FindEvent(List<StationEventRuleConfiguration> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the events that have met their player count, time-until start, etc.
|
||||
/// </summary>
|
||||
/// <param name="ignoreEarliestStart"></param>
|
||||
/// <returns></returns>
|
||||
private List<StationEventRuleConfiguration> 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<StationEventRuleConfiguration>();
|
||||
|
||||
foreach (var stationEvent in AllEvents())
|
||||
{
|
||||
if (CanRun(stationEvent, playerCount, currentTime))
|
||||
{
|
||||
result.Add(stationEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<StationEventRuleConfiguration> AllEvents()
|
||||
{
|
||||
return _prototype.EnumeratePrototypes<GameRulePrototype>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
197
Content.Server/StationEvents/EventManagerSystem.cs
Normal file
197
Content.Server/StationEvents/EventManagerSystem.cs
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly runs a valid event.
|
||||
/// </summary>
|
||||
public string RunRandomEvent()
|
||||
{
|
||||
var randomEvent = PickRandomEvent();
|
||||
|
||||
if (randomEvent == null
|
||||
|| !_prototype.TryIndex<GameRulePrototype>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly picks a valid event.
|
||||
/// </summary>
|
||||
public StationEventRuleConfiguration? PickRandomEvent()
|
||||
{
|
||||
var availableEvents = AvailableEvents();
|
||||
_sawmill.Info($"Picking from {availableEvents.Count} total available events");
|
||||
return FindEvent(availableEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a random event from the available events at this time, also considering their weightings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private StationEventRuleConfiguration? FindEvent(List<StationEventRuleConfiguration> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the events that have met their player count, time-until start, etc.
|
||||
/// </summary>
|
||||
/// <param name="ignoreEarliestStart"></param>
|
||||
/// <returns></returns>
|
||||
private List<StationEventRuleConfiguration> 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<StationEventRuleConfiguration>();
|
||||
|
||||
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<StationEventRuleConfiguration> AllEvents()
|
||||
{
|
||||
return _prototype.EnumeratePrototypes<GameRulePrototype>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,15 +15,18 @@ public sealed class MouseMigration : StationEventSystem
|
||||
{
|
||||
base.Started();
|
||||
|
||||
var modifier = GetSeverityModifier();
|
||||
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().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;
|
||||
|
||||
@@ -18,10 +18,11 @@ public sealed class RandomSentience : StationEventSystem
|
||||
base.Started();
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
|
||||
var mod = GetSeverityModifier();
|
||||
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||
RobustRandom.Shuffle(targetList);
|
||||
|
||||
var toMakeSentient = RobustRandom.Next(2, 5);
|
||||
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
|
||||
var groups = new HashSet<string>();
|
||||
|
||||
foreach (var target in targetList)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class GetSeverityModifierEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Should be multiplied/added to rather than set, for commutativity.
|
||||
/// </summary>
|
||||
public float Modifier = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GasVentPumpComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ public sealed class VentCritters : StationEventSystem
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().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)
|
||||
{
|
||||
|
||||
@@ -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<GetSeverityModifierEvent>(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);
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,8 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<string> StatusMoMMIPassword =
|
||||
CVarDef.Create("status.mommipassword", "", CVar.SERVERONLY | CVar.CONFIDENTIAL);
|
||||
|
||||
|
||||
/*
|
||||
* Game
|
||||
* Events
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
@@ -107,6 +106,24 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool>
|
||||
EventsEnabled = CVarDef.Create("events.enabled", true, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
EventsRampingAverageEndTime = CVarDef.Create("events.ramping_average_end_time", 40f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Average ending chaos modifier for the ramping event scheduler.
|
||||
/// Max chaos chosen for a round will deviate from this
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 6f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Game
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Disables most functionality in the GameTicker.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
extended-title = Extended
|
||||
extended-description = No antagonists, have fun!
|
||||
extended-description = A calm experience. Admin intervention required.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
survival-title = Survival
|
||||
survival-description = No internal threats, but how long can the station survive increasingly chaotic and frequent events?
|
||||
@@ -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
|
||||
|
||||
@@ -72,3 +72,9 @@
|
||||
config:
|
||||
!type:GenericGameRuleConfiguration
|
||||
id: BasicStationEventScheduler
|
||||
|
||||
- type: gameRule
|
||||
id: RampingStationEventScheduler
|
||||
config:
|
||||
!type:GenericGameRuleConfiguration
|
||||
id: RampingStationEventScheduler
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
- type: gamePreset
|
||||
id: Survival
|
||||
alias:
|
||||
- survival
|
||||
name: survival-title
|
||||
showInVote: false # secret
|
||||
description: survival-description
|
||||
rules:
|
||||
- RampingStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Extended
|
||||
alias:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
id: Welcome
|
||||
sound: /Audio/Announcements/welcome.ogg
|
||||
presets:
|
||||
- Extended
|
||||
- Survival
|
||||
- Sandbox
|
||||
- Secret
|
||||
- Traitor
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: weightedRandom
|
||||
id: Secret
|
||||
weights:
|
||||
Extended: 0.25
|
||||
Survival: 0.25
|
||||
Nukeops: 0.25
|
||||
Traitor: 0.75
|
||||
Zombie: 0.05
|
||||
|
||||
Reference in New Issue
Block a user