Extended rework & ramping event scheduler (#11362)
This commit is contained in:
@@ -20,77 +20,30 @@ namespace Content.Server.StationEvents
|
|||||||
{
|
{
|
||||||
public override string Prototype => "BasicStationEventScheduler";
|
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 IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||||
|
|
||||||
private const float MinimumTimeUntilFirstEvent = 300;
|
private const float MinimumTimeUntilFirstEvent = 300;
|
||||||
private ISawmill _sawmill = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How long until the next check for an event runs
|
/// How long until the next check for an event runs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// Default value is how long until first event is allowed
|
/// Default value is how long until first event is allowed
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
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 Started() { }
|
||||||
public override void Ended() { }
|
|
||||||
|
|
||||||
/// <summary>
|
public override void Ended()
|
||||||
/// Randomly run a valid event <b>immediately</b>, ignoring earlieststart or whether the event is enabled
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string RunRandomEvent()
|
|
||||||
{
|
{
|
||||||
var randomEvent = PickRandomEvent();
|
_timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
if (!RuleStarted || !EventsEnabled)
|
if (!RuleStarted || !_event.EventsEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_timeUntilNextEvent > 0)
|
if (_timeUntilNextEvent > 0)
|
||||||
@@ -99,17 +52,8 @@ namespace Content.Server.StationEvents
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No point hammering this trying to find events if none are available
|
_event.RunRandomEvent();
|
||||||
var stationEvent = FindEvent(AvailableEvents());
|
|
||||||
if (stationEvent == null
|
|
||||||
|| !_prototype.TryIndex<GameRulePrototype>(stationEvent.Id, out var proto))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameTicker.AddGameRule(proto);
|
|
||||||
ResetTimer();
|
ResetTimer();
|
||||||
_sawmill.Info($"Started event {proto.ID}. Next event in {_timeUntilNextEvent} seconds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,132 +64,5 @@ namespace Content.Server.StationEvents
|
|||||||
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
||||||
_timeUntilNextEvent = _random.Next(300, 1500);
|
_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();
|
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 chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||||
var jobList = _stationJobs.GetJobs(chosenStation).Keys.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.
|
// 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.
|
// 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);
|
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||||
_stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
_stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
||||||
@@ -35,8 +38,10 @@ public sealed class BureaucraticError : StationEventSystem
|
|||||||
}
|
}
|
||||||
else
|
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.
|
// 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);
|
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||||
if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob))
|
if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob))
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ namespace Content.Server.StationEvents.Events
|
|||||||
{
|
{
|
||||||
base.Started();
|
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.
|
// 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))
|
if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
|
||||||
{
|
{
|
||||||
@@ -66,7 +68,7 @@ namespace Content.Server.StationEvents.Events
|
|||||||
|
|
||||||
_leakGas = RobustRandom.Pick(LeakableGases);
|
_leakGas = RobustRandom.Pick(LeakableGases);
|
||||||
// Was 50-50 on using normal distribution.
|
// 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;
|
var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
|
||||||
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
||||||
_endAfter = totalGas / _molesPerSecond + startAfter;
|
_endAfter = totalGas / _molesPerSecond + startAfter;
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ namespace Content.Server.StationEvents.Events
|
|||||||
public override void Started()
|
public override void Started()
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started();
|
||||||
_waveCounter = RobustRandom.Next(MinimumWaves, MaximumWaves);
|
var mod = Math.Sqrt(GetSeverityModifier());
|
||||||
|
_waveCounter = (int) (RobustRandom.Next(MinimumWaves, MaximumWaves) * mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
public override void Ended()
|
||||||
@@ -53,13 +54,16 @@ namespace Content.Server.StationEvents.Events
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mod = GetSeverityModifier();
|
||||||
|
|
||||||
_cooldown -= frameTime;
|
_cooldown -= frameTime;
|
||||||
|
|
||||||
if (_cooldown > 0f) return;
|
if (_cooldown > 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
_waveCounter--;
|
_waveCounter--;
|
||||||
|
|
||||||
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() + MinimumCooldown;
|
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() / mod + MinimumCooldown;
|
||||||
|
|
||||||
Box2? playableArea = null;
|
Box2? playableArea = null;
|
||||||
var mapId = GameTicker.DefaultMap;
|
var mapId = GameTicker.DefaultMap;
|
||||||
|
|||||||
@@ -15,15 +15,18 @@ public sealed class MouseMigration : StationEventSystem
|
|||||||
{
|
{
|
||||||
base.Started();
|
base.Started();
|
||||||
|
|
||||||
|
var modifier = GetSeverityModifier();
|
||||||
|
|
||||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||||
RobustRandom.Shuffle(spawnLocations);
|
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++)
|
for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||||
{
|
{
|
||||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
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";
|
spawnChoice = "SpawnPointGhostRatKing";
|
||||||
|
|
||||||
var coords = spawnLocations[i].Item2.Coordinates;
|
var coords = spawnLocations[i].Item2.Coordinates;
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ public sealed class RandomSentience : StationEventSystem
|
|||||||
base.Started();
|
base.Started();
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
HashSet<EntityUid> stationsToNotify = new();
|
||||||
|
|
||||||
|
var mod = GetSeverityModifier();
|
||||||
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||||
RobustRandom.Shuffle(targetList);
|
RobustRandom.Shuffle(targetList);
|
||||||
|
|
||||||
var toMakeSentient = RobustRandom.Next(2, 5);
|
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
|
||||||
var groups = new HashSet<string>();
|
var groups = new HashSet<string>();
|
||||||
|
|
||||||
foreach (var target in targetList)
|
foreach (var target in targetList)
|
||||||
|
|||||||
@@ -179,6 +179,26 @@ namespace Content.Server.StationEvents.Events
|
|||||||
.Where(p => p.Configuration is StationEventRuleConfiguration).ToArray());
|
.Where(p => p.Configuration is StationEventRuleConfiguration).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float GetSeverityModifier()
|
||||||
|
{
|
||||||
|
var ev = new GetSeverityModifierEvent();
|
||||||
|
RaiseLocalEvent(ev);
|
||||||
|
return ev.Modifier;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#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.
|
// This is gross, but not much can be done until event refactor, which needs Dynamic.
|
||||||
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
|
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
|
||||||
|
var mod = (float) Math.Sqrt(GetSeverityModifier());
|
||||||
|
|
||||||
foreach (var (_, transform) in EntityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
foreach (var (_, transform) in EntityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
||||||
{
|
{
|
||||||
var solution = new Solution();
|
var solution = new Solution();
|
||||||
|
|
||||||
if (!RobustRandom.Prob(0.33f))
|
if (!RobustRandom.Prob(Math.Min(0.33f * mod, 1.0f)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (RobustRandom.Prob(0.05f))
|
if (RobustRandom.Prob(Math.Min(0.05f * mod, 1.0f)))
|
||||||
{
|
{
|
||||||
solution.AddReagent(RobustRandom.Pick(allReagents), 100);
|
solution.AddReagent(RobustRandom.Pick(allReagents), 100);
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ public sealed class VentClog : StationEventSystem
|
|||||||
solution.AddReagent(RobustRandom.Pick(SafeishVentChemicals), 100);
|
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);
|
1, sound, EntityManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ public sealed class VentCritters : StationEventSystem
|
|||||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||||
RobustRandom.Shuffle(spawnLocations);
|
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}");
|
Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
|
||||||
foreach (var location in spawnLocations)
|
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 =
|
public static readonly CVarDef<string> StatusMoMMIPassword =
|
||||||
CVarDef.Create("status.mommipassword", "", CVar.SERVERONLY | CVar.CONFIDENTIAL);
|
CVarDef.Create("status.mommipassword", "", CVar.SERVERONLY | CVar.CONFIDENTIAL);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Game
|
* Events
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -107,6 +106,24 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<bool>
|
public static readonly CVarDef<bool>
|
||||||
EventsEnabled = CVarDef.Create("events.enabled", true, CVar.ARCHIVE | CVar.SERVERONLY);
|
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>
|
/// <summary>
|
||||||
/// Disables most functionality in the GameTicker.
|
/// Disables most functionality in the GameTicker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
extended-title = Extended
|
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
|
## BasicStationEventSchedulerSystem
|
||||||
|
|
||||||
station-event-system-run-event = Running event {$eventName}
|
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:
|
config:
|
||||||
!type:GenericGameRuleConfiguration
|
!type:GenericGameRuleConfiguration
|
||||||
id: BasicStationEventScheduler
|
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
|
- type: gamePreset
|
||||||
id: Extended
|
id: Extended
|
||||||
alias:
|
alias:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id: Welcome
|
id: Welcome
|
||||||
sound: /Audio/Announcements/welcome.ogg
|
sound: /Audio/Announcements/welcome.ogg
|
||||||
presets:
|
presets:
|
||||||
- Extended
|
- Survival
|
||||||
- Sandbox
|
- Sandbox
|
||||||
- Secret
|
- Secret
|
||||||
- Traitor
|
- Traitor
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- type: weightedRandom
|
- type: weightedRandom
|
||||||
id: Secret
|
id: Secret
|
||||||
weights:
|
weights:
|
||||||
Extended: 0.25
|
Survival: 0.25
|
||||||
Nukeops: 0.25
|
Nukeops: 0.25
|
||||||
Traitor: 0.75
|
Traitor: 0.75
|
||||||
Zombie: 0.05
|
Zombie: 0.05
|
||||||
|
|||||||
Reference in New Issue
Block a user