* works, still has testing values, im sure I did stupid shit.
* shitvent crapfactor
* snap extra word out of existence
* shit I died of old
* remove useless inaccurate design comments
* Oopsie, handle requirement params in RandomRuleSystem too
* I'm a slash slinging hasher
* Address reviews, add admin alerts I forgor
* EntityMan saves the day
* address reviews 1
* eh, I actually don't care about the cargo gifts thing.
* started
* Do reviews
* you actually meant 1.2 lmao
* dependency inheritance is a fickle bitch
* I have no idea.
* Threads are for sheets not computers.
* fix traitor rule test
* fix round type tattling
* break things
* It worky
* Toolshed makes we want to drink depresso.
* Finished?
* remove debug values
* timings
* use defaults
* alphabetize
* bobby drop tables
* Float required fr fr
* continue
* more continence
* uno mas
* obsolution
* cleanup and documentations
* Yell at self
* use the right value defaults
* housekeeping
-using Content.Server.GameTicking;
-using Content.Server.GameTicking.Commands;
+using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
-using Content.Shared.CCVar;
using Content.Shared.GameTicking.Components;
-using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
var sGameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
var sGameTiming = server.ResolveDependency<IGameTiming>();
- sGameTicker.StartGameRule("MaxTimeRestart", out var ruleEntity);
- Assert.That(entityManager.TryGetComponent<MaxTimeRestartRuleComponent>(ruleEntity, out var maxTime));
+ MaxTimeRestartRuleComponent maxTime = null;
+ await server.WaitPost(() =>
+ {
+ sGameTicker.StartGameRule("MaxTimeRestart", out var ruleEntity);
+ Assert.That(entityManager.TryGetComponent<MaxTimeRestartRuleComponent>(ruleEntity, out maxTime));
+ });
Assert.That(server.EntMan.Count<GameRuleComponent>(), Is.EqualTo(1));
Assert.That(server.EntMan.Count<ActiveGameRuleComponent>(), Is.EqualTo(1));
await pair.SetAntagPreference(TraitorAntagRoleName, true);
// Add the game rule
- var gameRuleEnt = ticker.AddGameRule(TraitorGameRuleProtoId);
- Assert.That(entMan.TryGetComponent<TraitorRuleComponent>(gameRuleEnt, out var traitorRule));
-
- // Ready up
- ticker.ToggleReadyAll(true);
- Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
-
- // Start the round
+ TraitorRuleComponent traitorRule = null;
await server.WaitPost(() =>
{
+ var gameRuleEnt = ticker.AddGameRule(TraitorGameRuleProtoId);
+ Assert.That(entMan.TryGetComponent<TraitorRuleComponent>(gameRuleEnt, out traitorRule));
+
+ // Ready up
+ ticker.ToggleReadyAll(true);
+ Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
+
+ // Start the round
ticker.StartRound();
// Force traitor mode to start (skip the delay)
ticker.StartGameRule(gameRuleEnt);
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
+using Robust.Shared.Localization;
namespace Content.Server.GameTicking;
var ruleEntity = Spawn(ruleId, MapCoordinates.Nullspace);
_sawmill.Info($"Added game rule {ToPrettyString(ruleEntity)}");
_adminLogger.Add(LogType.EventStarted, $"Added game rule {ToPrettyString(ruleEntity)}");
+ var str = Loc.GetString("station-event-system-run-event", ("eventName", ToPrettyString(ruleEntity)));
+#if DEBUG
+ _chatManager.SendAdminAlert(str);
+#else
+ if (RunLevel == GameRunLevel.InRound) // avoids telling admins the round type before it starts so that can be handled elsewhere.
+ {
+ _chatManager.SendAdminAlert(str);
+ }
+#endif
+ Log.Info(str);
var ev = new GameRuleAddedEvent(ruleEntity, ruleId);
RaiseLocalEvent(ruleEntity, ref ev, true);
using Content.Server.Administration;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
using Content.Shared.Administration;
+using Content.Shared.EntityTable;
using Content.Shared.GameTicking.Components;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
[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 Started(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
+ GameRuleStartedEvent args)
+ {
+ // A little starting variance so schedulers dont all proc at once.
+ component.TimeUntilNextEvent = RobustRandom.NextFloat(component.MinimumTimeUntilFirstEvent, component.MinimumTimeUntilFirstEvent + 120);
+ }
protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
GameRuleEndedEvent args)
{
- component.TimeUntilNextEvent = BasicStationEventSchedulerComponent.MinimumTimeUntilFirstEvent;
+ component.TimeUntilNextEvent = component.MinimumTimeUntilFirstEvent;
}
if (eventScheduler.TimeUntilNextEvent > 0)
{
eventScheduler.TimeUntilNextEvent -= frameTime;
- return;
+ continue;
}
- _event.RunRandomEvent();
+ _event.RunRandomEvent(eventScheduler.ScheduledGameRules);
ResetTimer(eventScheduler);
}
}
/// </summary>
private void ResetTimer(BasicStationEventSchedulerComponent component)
{
- component.TimeUntilNextEvent = _random.NextFloat(MinEventTime, MaxEventTime);
+ component.TimeUntilNextEvent = component.MinMaxEventTiming.Next(_random);
}
}
public sealed class StationEventCommand : ToolshedCommand
{
private EventManagerSystem? _stationEvent;
- private BasicStationEventSchedulerSystem? _basicScheduler;
+ private EntityTableSystem? _entityTable;
+ private IComponentFactory? _compFac;
private IRobustRandom? _random;
/// <summary>
/// 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)
+ public IEnumerable<(string, float)> Simulate([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] int rounds, [CommandArgument] int playerCount, [CommandArgument] float roundEndMean, [CommandArgument] float roundEndStdDev)
{
_stationEvent ??= GetSys<EventManagerSystem>();
- _basicScheduler ??= GetSys<BasicStationEventSchedulerSystem>();
+ _entityTable ??= GetSys<EntityTableSystem>();
+ _compFac ??= IoCManager.Resolve<IComponentFactory>();
_random ??= IoCManager.Resolve<IRobustRandom>();
var occurrences = new Dictionary<string, int>();
occurrences.Add(ev.Key.ID, 0);
}
+ if (!eventScheduler.TryGetComponent<BasicStationEventSchedulerComponent>(out var basicScheduler, _compFac))
+ {
+ return occurrences.Select(p => (p.Key, (float)p.Value)).OrderByDescending(p => p.Item2);
+ }
+
+ var compMinMax = basicScheduler.MinMaxEventTiming; // we gotta do this since we cant execute on comp w/o an ent.
+
for (var i = 0; i < rounds; i++)
{
var curTime = TimeSpan.Zero;
while (curTime.TotalSeconds < randomEndTime)
{
// sim an event
- curTime += TimeSpan.FromSeconds(_random.NextFloat(BasicStationEventSchedulerSystem.MinEventTime, BasicStationEventSchedulerSystem.MaxEventTime));
+ curTime += TimeSpan.FromSeconds(compMinMax.Next(_random));
+
+ if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, out var selectedEvents))
+ {
+ continue; // doesnt break because maybe the time is preventing events being available.
+ }
var available = _stationEvent.AvailableEvents(false, playerCount, curTime);
- var ev = _stationEvent.FindEvent(available);
+ var plausibleEvents = new Dictionary<EntityPrototype, StationEventComponent>(available.Intersect(selectedEvents)); // C# makes me sad
+
+ var ev = _stationEvent.FindEvent(plausibleEvents);
if (ev == null)
continue;
}
[CommandImplementation("lsprob")]
- public IEnumerable<(string, float)> LsProb()
+ public IEnumerable<(string, float)> LsProb([CommandArgument] EntityPrototype eventScheduler)
{
+ _compFac ??= IoCManager.Resolve<IComponentFactory>();
_stationEvent ??= GetSys<EventManagerSystem>();
- var events = _stationEvent.AllEvents();
- var totalWeight = events.Sum(x => x.Value.Weight);
+ if (!eventScheduler.TryGetComponent<BasicStationEventSchedulerComponent>(out var basicScheduler, _compFac))
+ yield break;
- foreach (var (proto, comp) in events)
+ if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, out var events))
+ yield break;
+
+ var totalWeight = events.Sum(x => x.Value.Weight); // Well this shit definitely isnt correct now, and I see no way to make it correct.
+ // Its probably *fine* but it wont be accurate if the EntityTableSelector does any subsetting.
+ foreach (var (proto, comp) in events) // The only solution I see is to do a simulation, and we already have that, so...!
{
yield return (proto.ID, comp.Weight / totalWeight);
}
}
[CommandImplementation("lsprobtime")]
- public IEnumerable<(string, float)> LsProbTime([CommandArgument] float time)
+ public IEnumerable<(string, float)> LsProbTime([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] float time)
{
+ _compFac ??= IoCManager.Resolve<IComponentFactory>();
_stationEvent ??= GetSys<EventManagerSystem>();
- var events = _stationEvent.AllEvents().Where(pair => pair.Value.EarliestStart <= time).ToList();
- var totalWeight = events.Sum(x => x.Value.Weight);
+ if (!eventScheduler.TryGetComponent<BasicStationEventSchedulerComponent>(out var basicScheduler, _compFac))
+ yield break;
+
+ if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, out var untimedEvents))
+ yield break;
+
+ var events = untimedEvents.Where(pair => pair.Value.EarliestStart <= time).ToList();
+
+ var totalWeight = events.Sum(x => x.Value.Weight); // same subsetting issue as lsprob.
foreach (var (proto, comp) in events)
{
}
[CommandImplementation("prob")]
- public float Prob([CommandArgument] string eventId)
+ public float Prob([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] string eventId)
{
+ _compFac ??= IoCManager.Resolve<IComponentFactory>();
_stationEvent ??= GetSys<EventManagerSystem>();
- var events = _stationEvent.AllEvents();
- var totalWeight = events.Sum(x => x.Value.Weight);
+ if (!eventScheduler.TryGetComponent<BasicStationEventSchedulerComponent>(out var basicScheduler, _compFac))
+ return 0f;
+
+ if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, out var events))
+ return 0f;
+
+ var totalWeight = events.Sum(x => x.Value.Weight); // same subsetting issue as lsprob.
var weight = 0f;
if (events.TryFirstOrNull(p => p.Key.ID == eventId, out var pair))
{
-namespace Content.Server.StationEvents.Components;
+using Content.Shared.Destructible.Thresholds;
+using Content.Shared.EntityTable.EntitySelectors;
+
+
+namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(BasicStationEventSchedulerSystem))]
public sealed partial class BasicStationEventSchedulerComponent : Component
{
- public const float MinimumTimeUntilFirstEvent = 300;
+ /// <summary>
+ /// How long the the scheduler waits to begin starting rules.
+ /// </summary>
+ [DataField]
+ public float MinimumTimeUntilFirstEvent = 200;
+
+ /// <summary>
+ /// The minimum and maximum time between rule starts in seconds.
+ /// </summary>
+ [DataField]
+ public MinMax MinMaxEventTiming = new(3 * 60, 10 * 60);
+
+ /// <summary>
+ /// How long until the next check for an event runs, is initially set based on MinimumTimeUntilFirstEvent & MinMaxEventTiming.
+ /// </summary>
+ [DataField]
+ public float TimeUntilNextEvent;
/// <summary>
- /// How long until the next check for an event runs
+ /// The gamerules that the scheduler can choose from
/// </summary>
- /// Default value is how long until first event is allowed
- [ViewVariables(VVAccess.ReadWrite)]
- public float TimeUntilNextEvent = MinimumTimeUntilFirstEvent;
+ /// Reminder that though we could do all selection via the EntityTableSelector, we also need to consider various <see cref="StationEventComponent"/> restrictions.
+ /// As such, we want to pass a list of acceptable game rules, which are then parsed for restrictions by the <see cref="EventManagerSystem"/>.
+ [DataField(required: true)]
+ public EntityTableSelector ScheduledGameRules = default!;
}
+++ /dev/null
-using Content.Shared.Random;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.StationEvents.Components;
-
-/// <summary>
-/// This is used for running meteor swarm events at regular intervals.
-/// </summary>
-[RegisterComponent, Access(typeof(MeteorSchedulerSystem)), AutoGenerateComponentPause]
-public sealed partial class MeteorSchedulerComponent : Component
-{
- /// <summary>
- /// The weights for which swarms will be selected.
- /// </summary>
- [DataField]
- public ProtoId<WeightedRandomEntityPrototype> Config = "DefaultConfig";
-
- /// <summary>
- /// The time at which the next swarm occurs.
- /// </summary>
- [DataField, AutoPausedField]
- public TimeSpan NextSwarmTime = TimeSpan.Zero;
-
-}
-namespace Content.Server.StationEvents.Components;
+using Content.Shared.EntityTable.EntitySelectors;
+
+namespace Content.Server.StationEvents.Components;
[RegisterComponent, Access(typeof(RampingStationEventSchedulerSystem))]
public sealed partial class RampingStationEventSchedulerComponent : Component
{
- [DataField("endTime"), ViewVariables(VVAccess.ReadWrite)]
+ /// <summary>
+ /// Average ending chaos modifier for the ramping event scheduler. Higher means faster.
+ /// Max chaos chosen for a round will deviate from this
+ /// </summary>
+ [DataField]
+ public float AverageChaos = 6f;
+
+ /// <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>
+ [DataField]
+ public float AverageEndTime = 40f;
+
+ [DataField]
public float EndTime;
- [DataField("maxChaos"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float MaxChaos;
- [DataField("startingChaos"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float StartingChaos;
- [DataField("timeUntilNextEvent"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float TimeUntilNextEvent;
+
+ /// <summary>
+ /// The gamerules that the scheduler can choose from
+ /// </summary>
+ /// Reminder that though we could do all selection via the EntityTableSelector, we also need to consider various <see cref="StationEventComponent"/> restrictions.
+ /// As such, we want to pass a list of acceptable game rules, which are then parsed for restrictions by the <see cref="EventManagerSystem"/>.
+ [DataField(required: true)]
+ public EntityTableSelector ScheduledGameRules = default!;
}
using System.Linq;
-using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.RoundEnd;
using Content.Server.StationEvents.Components;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Content.Shared.EntityTable.EntitySelectors;
+using Content.Shared.EntityTable;
namespace Content.Server.StationEvents;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly IChatManager _chat = default!;
+ [Dependency] private readonly EntityTableSystem _entityTable = default!;
[Dependency] public readonly GameTicker GameTicker = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
/// <summary>
/// Randomly runs a valid event.
/// </summary>
- public string RunRandomEvent()
+ [Obsolete("use overload taking EnityTableSelector instead or risk unexpected results")]
+ public void RunRandomEvent()
{
var randomEvent = PickRandomEvent();
{
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
Log.Error(errStr);
- return errStr;
+ return;
}
- var ent = GameTicker.AddGameRule(randomEvent);
- var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent)));
- _chat.SendAdminAlert(str);
- Log.Info(str);
- return str;
+ GameTicker.AddGameRule(randomEvent);
+ }
+
+ /// <summary>
+ /// Randomly runs an event from provided EntityTableSelector.
+ /// </summary>
+ public void RunRandomEvent(EntityTableSelector limitedEventsTable)
+ {
+ if (!TryBuildLimitedEvents(limitedEventsTable, out var limitedEvents))
+ {
+ Log.Warning("Provided event table could not build dict!");
+ return;
+ }
+
+ var randomLimitedEvent = FindEvent(limitedEvents); // this picks the event, It might be better to use the GetSpawns to do it, but that will be a major rebalancing fuck.
+ if (randomLimitedEvent == null)
+ {
+ Log.Warning("The selected random event is null!");
+ return;
+ }
+
+ if (!_prototype.TryIndex(randomLimitedEvent, out _))
+ {
+ Log.Warning("A requested event is not available!");
+ return;
+ }
+
+ GameTicker.AddGameRule(randomLimitedEvent);
+ }
+
+ /// <summary>
+ /// Returns true if the provided EntityTableSelector gives at least one prototype with a StationEvent comp.
+ /// </summary>
+ public bool TryBuildLimitedEvents(EntityTableSelector limitedEventsTable, out Dictionary<EntityPrototype, StationEventComponent> limitedEvents)
+ {
+ limitedEvents = new Dictionary<EntityPrototype, StationEventComponent>();
+
+ var availableEvents = AvailableEvents(); // handles the player counts and individual event restrictions
+
+ if (availableEvents.Count == 0)
+ {
+ Log.Warning("No events were available to run!");
+ return false;
+ }
+
+ var selectedEvents = _entityTable.GetSpawns(limitedEventsTable);
+
+ if (selectedEvents.Any() != true) // This is here so if you fuck up the table it wont die.
+ return false;
+
+ foreach (var eventid in selectedEvents)
+ {
+ if (!_prototype.TryIndex(eventid, out var eventproto))
+ {
+ Log.Warning("An event ID has no prototype index!");
+ continue;
+ }
+
+ if (limitedEvents.ContainsKey(eventproto)) // This stops it from dying if you add duplicate entries in a fucked table
+ continue;
+
+ if (eventproto.Abstract)
+ continue;
+
+ if (!eventproto.TryGetComponent<StationEventComponent>(out var stationEvent, EntityManager.ComponentFactory))
+ continue;
+
+ if (!availableEvents.ContainsKey(eventproto))
+ continue;
+
+ limitedEvents.Add(eventproto, stationEvent);
+ }
+
+ if (!limitedEvents.Any())
+ return false;
+
+ return true;
}
/// <summary>
+++ /dev/null
-using Content.Server.GameTicking.Rules;
-using Content.Server.StationEvents.Components;
-using Content.Shared.CCVar;
-using Content.Shared.GameTicking.Components;
-using Content.Shared.Random.Helpers;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.StationEvents;
-
-/// <summary>
-/// This handles scheduling and launching meteors at a station at regular intervals.
-/// TODO: there is 100% a world in which this is genericized and can be used for lots of basic event scheduling
-/// </summary>
-public sealed class MeteorSchedulerSystem : GameRuleSystem<MeteorSchedulerComponent>
-{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
-
- private TimeSpan _meteorMinDelay;
- private TimeSpan _meteorMaxDelay;
-
- public override void Initialize()
- {
- base.Initialize();
-
- _cfg.OnValueChanged(CCVars.MeteorSwarmMinTime, f => { _meteorMinDelay = TimeSpan.FromMinutes(f); }, true);
- _cfg.OnValueChanged(CCVars.MeteorSwarmMaxTime, f => { _meteorMaxDelay = TimeSpan.FromMinutes(f); }, true);
- }
-
- protected override void Started(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- base.Started(uid, component, gameRule, args);
-
- component.NextSwarmTime = Timing.CurTime + RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
- }
-
- protected override void ActiveTick(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, float frameTime)
- {
- base.ActiveTick(uid, component, gameRule, frameTime);
-
- if (Timing.CurTime < component.NextSwarmTime)
- return;
- RunSwarm((uid, component));
-
- component.NextSwarmTime += RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
- }
-
- private void RunSwarm(Entity<MeteorSchedulerComponent> ent)
- {
- var swarmWeights = _prototypeManager.Index(ent.Comp.Config);
- GameTicker.StartGameRule(swarmWeights.Pick(RobustRandom));
- }
-}
-using Content.Server.GameTicking;
+using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
-using Content.Server.StationEvents.Events;
-using Content.Shared.CCVar;
using Content.Shared.GameTicking.Components;
-using Robust.Shared.Configuration;
using Robust.Shared.Random;
namespace Content.Server.StationEvents;
public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingStationEventSchedulerComponent>
{
- [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EventManagerSystem _event = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
+ /// <summary>
+ /// Returns the ChaosModifier which increases as round time increases to a point.
+ /// </summary>
public float GetChaosModifier(EntityUid uid, RampingStationEventSchedulerComponent component)
{
var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
{
base.Started(uid, component, gameRule, args);
- var avgChaos = _cfg.GetCVar(CCVars.EventsRampingAverageChaos);
- var avgTime = _cfg.GetCVar(CCVars.EventsRampingAverageEndTime);
-
// Worlds shittiest probability distribution
// Got a complaint? Send them to
- component.MaxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4);
+ component.MaxChaos = _random.NextFloat(component.AverageChaos - component.AverageChaos / 4, component.AverageChaos + component.AverageChaos / 4);
// This is in minutes, so *60 for seconds (for the chaos calc)
- component.EndTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f;
+ component.EndTime = _random.NextFloat(component.AverageEndTime - component.AverageEndTime / 4, component.AverageEndTime + component.AverageEndTime / 4) * 60f;
component.StartingChaos = component.MaxChaos / 10;
PickNextEventTime(uid, component);
while (query.MoveNext(out var uid, out var scheduler, out var gameRule))
{
if (!GameTicker.IsGameRuleActive(uid, gameRule))
- return;
+ continue;
if (scheduler.TimeUntilNextEvent > 0f)
{
scheduler.TimeUntilNextEvent -= frameTime;
- return;
+ continue;
}
PickNextEventTime(uid, scheduler);
- _event.RunRandomEvent();
+ _event.RunRandomEvent(scheduler.ScheduledGameRules);
}
}
+ /// <summary>
+ /// Sets the timing of the next event addition.
+ /// </summary>
private void PickNextEventTime(EntityUid uid, RampingStationEventSchedulerComponent component)
{
var mod = GetChaosModifier(uid, component);
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);
-
- /// <summary>
- /// Minimum time between meteor swarms in minutes.
- /// </summary>
- public static readonly CVarDef<float>
- MeteorSwarmMinTime = CVarDef.Create("events.meteor_swarm_min_time", 12.5f, CVar.ARCHIVE | CVar.SERVERONLY);
-
- /// <summary>
- /// Maximum time between meteor swarms in minutes.
- /// </summary>
- public static readonly CVarDef<float>
- MeteorSwarmMaxTime = CVarDef.Create("events.meteor_swarm_max_time", 17.5f, CVar.ARCHIVE | CVar.SERVERONLY);
-
/*
* Game
*/
all-at-once-title = All at once
all-at-once-description = It's just not your day...
+
+aller-at-once-title = Aller at once
+aller-at-once-description = You have fucked up now. You *have* fucked up now.
\ No newline at end of file
survival-title = Survival
survival-description = No internal threats, but how long can the station survive increasingly chaotic and frequent events?
+
+kessler-syndrome-title = Kessler Syndrome
+kessler-syndrome-description = No internal threats, but the station is quickly falling into a belt of meteors!
\ No newline at end of file
zombie-title = Zombies
zombie-description = The undead have been unleashed on the station! Work with the crew to survive the outbreak and secure the station.
+zombieteors-title = Zombieteors
+zombieteors-description = The undead have been unleashed on the station amid a cataclysmic meteor shower! Work with your fellow crew and do your best to survive!
+
zombie-not-enough-ready-players = Not enough players readied up for the game! There were {$readyPlayersCount} players readied up out of {$minimumPlayers} needed. Can't start Zombies.
zombie-no-one-ready = No players readied up! Can't start Zombies.
+# Tables -- Add your new event to this Table if you want it to happen via the basic/ramping schedulers.
+
+- type: entityTable
+ id: CargoGiftsTable
+ table: !type:GroupSelector # ~~we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp~~ But we arent doing that shit yet, it picks a random one StationEventComp be damned.
+ children:
+ - id: GiftsEngineering
+ - id: GiftsFireProtection
+ - id: GiftsJanitor
+ - id: GiftsMedical
+ - id: GiftsPizzaPartyLarge
+ - id: GiftsPizzaPartySmall
+ - id: GiftsSecurityGuns
+ - id: GiftsSecurityRiot
+ - id: GiftsSpacingSupplies
+ - id: GiftsVendingRestock
+
+# Game Rules
+
- type: entity
id: CargoGiftsBase
parent: BaseGameRule
+- type: entityTable
+ id: BasicCalmEventsTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: AnomalySpawn
+ - id: BluespaceArtifact
+ - id: BluespaceLocker
+ - id: BreakerFlip
+ - id: BureaucraticError
+ - id: ClericalError
+ - id: CockroachMigration
+ - id: GasLeak
+ - id: IonStorm # its calm like 90% of the time smh
+ - id: KudzuGrowth
+ - id: MassHallucinations
+ - id: MimicVendorRule
+ - id: MouseMigration
+ - id: PowerGridCheck
+ - id: SlimesSpawn
+ - id: SolarFlare
+ - id: SpiderClownSpawn
+ - id: SpiderSpawn
+ - id: VentClog
+
+- type: entityTable
+ id: BasicAntagEventsTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: ClosetSkeleton
+ - id: DragonSpawn
+ - id: KingRatMigration
+ - id: NinjaSpawn
+ - id: RevenantSpawn
+ - id: SleeperAgents
+ - id: ZombieOutbreak
+
+
- type: entity
id: BaseStationEvent
parent: BaseGameRule
sounds:
collection: Paracusia
-- type: entity
- id: ImmovableRodSpawn
- parent: BaseGameRule
- components:
- - type: StationEvent
- startAnnouncement: station-event-immovable-rod-start-announcement
- startAudio:
- path: /Audio/Announcements/attention.ogg
- weight: 3.5
- duration: 1
- earliestStart: 30
- minimumPlayers: 20
- - type: ImmovableRodRule
- rodPrototypes:
- - id: ImmovableRodKeepTilesStill
- prob: 0.95
- orGroup: rodProto
- - id: ImmovableRodMop
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodShark
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodClown
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodBanana
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodHammer
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodThrongler
- prob: 0.0072
- orGroup: rodProto
- - id: ImmovableRodGibstick
- prob: 0.0072
- orGroup: rodProto
-
- type: entity
parent: BaseGameRule
id: IonStorm
-- type: entity
- parent: BaseGameRule
- id: GameRuleMeteorScheduler
- components:
- - type: GameRule
- minPlayers: 25
- cancelPresetOnTooFewPlayers: false
- - type: MeteorScheduler
+# Event Tables
-- type: weightedRandomEntity
- id: DefaultConfig
- weights:
- GameRuleSpaceDustMinor: 44
- GameRuleSpaceDustMajor: 22
- GameRuleMeteorSwarmSmall: 18
- GameRuleMeteorSwarmMedium: 10
- GameRuleMeteorSwarmLarge: 5
- GameRuleUristSwarm: 0.05
+- type: entityTable
+ id: MeteorSwarmDustEventsTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: GameRuleSpaceDustMinor
+ - id: GameRuleSpaceDustMajor
+
+- type: entityTable
+ id: MeteorSwarmSmallChanceEventsTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: GameRuleMeteorSwarmSmall
+ prob: 0.15
+
+- type: entityTable
+ id: MeteorSwarmMildTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - !type:NestedSelector
+ tableId: MeteorSwarmDustEventsTable
+ - !type:NestedSelector
+ tableId: MeteorSwarmSmallChanceEventsTable
+
+- type: entityTable
+ id: BasicMeteorSwarmEventsTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - !type:NestedSelector
+ tableId: MeteorSwarmDustEventsTable
+ - id: GameRuleMeteorSwarmSmall
+ - id: GameRuleMeteorSwarmMedium
+ - id: GameRuleMeteorSwarmLarge
+ - id: GameRuleUristSwarm
+ - id: ImmovableRodSpawn
- type: weightedRandomEntity
id: MeteorSpawnAsteroidWallTable
AsteroidRockPlasma: 2
AsteroidRockDiamond: 2
AsteroidRockUranium: 0.5
- AsteroidRockBananium: 0.5
+ AsteroidRockBananium: 0.5
+
+# Event Schedulers
+
+- type: entity
+ parent: BaseGameRule
+ id: MeteorSwarmScheduler
+ components:
+ - type: GameRule
+ - type: BasicStationEventScheduler
+ minimumTimeUntilFirstEvent: 300 # 5 min
+ minMaxEventTiming:
+ min: 750 # 12.5 min
+ max: 930 # 17.5 min
+ scheduledGameRules: !type:NestedSelector
+ tableId: BasicMeteorSwarmEventsTable
+
+- type: entity
+ parent: BaseGameRule
+ id: MeteorSwarmMildScheduler
+ components:
+ - type: GameRule
+ - type: BasicStationEventScheduler
+ minimumTimeUntilFirstEvent: 300 # 5 min
+ minMaxEventTiming:
+ min: 750 # 12.5 min
+ max: 930 # 17.5 min
+ scheduledGameRules: !type:NestedSelector
+ tableId: MeteorSwarmMildTable
+
+- type: entity
+ parent: BaseGameRule
+ id: KesslerSyndromeScheduler
+ components:
+ - type: GameRule
+ - type: RampingStationEventScheduler
+ scheduledGameRules: !type:NestedSelector
+ tableId: BasicMeteorSwarmEventsTable
+
+# Game Rules
- type: entity
parent: BaseGameRule
abstract: true
components:
- type: GameRule
+ - type: StationEvent
+ earliestStart: 12
+ minimumPlayers: 25
- type: MeteorSwarm
- type: entity
parent: GameRuleMeteorSwarm
id: GameRuleSpaceDustMinor
components:
+ - type: StationEvent
+ weight: 44
+ earliestStart: 2
+ minimumPlayers: 0
- type: MeteorSwarm
announcement: null
announcementSound: null
parent: GameRuleMeteorSwarm
id: GameRuleSpaceDustMajor
components:
+ - type: StationEvent
+ weight: 22
+ minimumPlayers: 0
- type: MeteorSwarm
announcement: station-event-space-dust-start-announcement
announcementSound: /Audio/Announcements/attention.ogg
parent: GameRuleMeteorSwarm
id: GameRuleMeteorSwarmSmall
components:
+ - type: StationEvent
+ weight: 18
+ minimumPlayers: 15
- type: MeteorSwarm
meteors:
MeteorSmall: 7
parent: GameRuleMeteorSwarm
id: GameRuleMeteorSwarmMedium
components:
+ - type: StationEvent
+ weight: 10
- type: MeteorSwarm
meteors:
MeteorSmall: 3
parent: GameRuleMeteorSwarm
id: GameRuleMeteorSwarmLarge
components:
+ - type: StationEvent
+ weight: 5
- type: MeteorSwarm
meteors:
MeteorSmall: 2
parent: GameRuleMeteorSwarm
id: GameRuleUristSwarm
components:
+ - type: StationEvent
+ weight: 0.05
- type: MeteorSwarm
announcement: station-event-meteor-urist-start-announcement
announcementSound: /Audio/Announcements/attention.ogg
meteorsPerWave:
min: 10
max: 10
+
+- type: entity
+ id: ImmovableRodSpawn
+ parent: BaseGameRule
+ components:
+ - type: StationEvent
+ startAnnouncement: station-event-immovable-rod-start-announcement
+ startAudio:
+ path: /Audio/Announcements/attention.ogg
+ weight: 3.5
+ duration: 1
+ earliestStart: 30
+ minimumPlayers: 25
+ - type: ImmovableRodRule
+ rodPrototypes:
+ - id: ImmovableRodKeepTilesStill
+ prob: 0.95
+ orGroup: rodProto
+ - id: ImmovableRodMop
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodShark
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodClown
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodBanana
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodHammer
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodThrongler
+ prob: 0.0072
+ orGroup: rodProto
+ - id: ImmovableRodGibstick
+ prob: 0.0072
+ orGroup: rodProto
prototype: InitialInfected
# event schedulers
+
+- type: entityTable
+ id: BasicGameRulesTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - !type:NestedSelector
+ tableId: BasicCalmEventsTable
+ - !type:NestedSelector
+ tableId: BasicAntagEventsTable
+ - !type:NestedSelector
+ tableId: CargoGiftsTable
+
+- type: entityTable
+ id: SpaceTrafficControlTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - !type:NestedSelector
+ tableId: UnknownShuttlesFriendlyTable
+ - !type:NestedSelector
+ tableId: UnknownShuttlesFreelanceTable
+ - !type:NestedSelector
+ tableId: UnknownShuttlesHostileTable
+
- type: entity
id: BasicStationEventScheduler
parent: BaseGameRule
components:
- type: BasicStationEventScheduler
+ scheduledGameRules: !type:NestedSelector
+ tableId: BasicGameRulesTable
- type: entity
id: RampingStationEventScheduler
parent: BaseGameRule
components:
- type: RampingStationEventScheduler
+ scheduledGameRules: !type:NestedSelector
+ tableId: BasicGameRulesTable
+
+- type: entity
+ id: SpaceTrafficControlEventScheduler # iff we make a selector for EntityTables that can respect StationEventComp restrictions, or somehow impliment them otherwise in said tables,
+ parent: BaseGameRule # we can remerge this with the other schedulers, but it will silently fail due to that limitation without a separate scheduler to balance atm.
+ components:
+ - type: BasicStationEventScheduler
+ minimumTimeUntilFirstEvent: 1200 # 20 mins
+ minMaxEventTiming:
+ min: 600 # 10 mins
+ max: 1800 # 30 mins
+ scheduledGameRules: !type:NestedSelector
+ tableId: SpaceTrafficControlTable
+
+- type: entity
+ id: SpaceTrafficControlFriendlyEventScheduler
+ parent: BaseGameRule
+ components:
+ - type: BasicStationEventScheduler
+ minimumTimeUntilFirstEvent: 1200 # 20 mins
+ minMaxEventTiming:
+ min: 600 # 10 mins
+ max: 1800 # 30 mins
+ scheduledGameRules: !type:NestedSelector
+ tableId: UnknownShuttlesFriendlyTable
# variation passes
- type: entity
+# Shuttle Game Rule Tables -- If you dont add your rules to these they wont be used by the games schedulers.
+
+- type: entityTable
+ id: UnknownShuttlesFriendlyTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: UnknownShuttleCargoLost
+ - id: UnknownShuttleTravelingCuisine
+ - id: UnknownShuttleDisasterEvacPod
+ - id: UnknownShuttleHonki
+
+- type: entityTable
+ id: UnknownShuttlesFreelanceTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: UnknownShuttleSyndieEvacPod
+
+- type: entityTable
+ id: UnknownShuttlesHostileTable
+ table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
+ children:
+ - id: LoneOpsSpawn
+
+# Shuttle Game Rules
+
- type: entity
abstract: true
parent: BaseGameRule
showInVote: false # secret
description: survival-description
rules:
+ - MeteorSwarmScheduler
- RampingStationEventScheduler
- - GameRuleMeteorScheduler
+ - SpaceTrafficControlEventScheduler
+ - SpaceTrafficControlFriendlyEventScheduler
+ - BasicRoundstartVariation
+
+- type: gamePreset
+ id: KesslerSyndrome
+ alias:
+ - kessler
+ - junk
+ - meteorhell
+ name: kessler-syndrome-title
+ showInVote: false # secret
+ description: kessler-syndrome-description
+ rules:
+ - KesslerSyndromeScheduler
+ - RampingStationEventScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
- type: gamePreset
id: AllAtOnce
+ alias:
+ - all
name: all-at-once-title
description: all-at-once-description
showInVote: false
- Traitor
- Revolutionary
- Zombie
+ - KesslerSyndromeScheduler
+ - RampingStationEventScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
+
+- type: gamePreset
+ id: AllerAtOnce
+ alias:
+ - allall
+ - aller
+ - badidea
+ - punishment
+ name: aller-at-once-title
+ description: all-at-once-description
+ showInVote: false #Please god dont do this
+ rules:
+ - Nukeops
+ - Traitor
+ - Revolutionary
+ - Zombie
+ - BasicStationEventScheduler
+ - KesslerSyndromeScheduler
+ - MeteorSwarmMildScheduler
+ - MeteorSwarmScheduler
- RampingStationEventScheduler
- - GameRuleMeteorScheduler
+ - SpaceTrafficControlEventScheduler
+ - SpaceTrafficControlFriendlyEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: Extended
description: extended-description
rules:
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
- type: gamePreset
showInVote: false #4boring4vote
description: greenshift-description
rules:
+ - SpaceTrafficControlFriendlyEventScheduler
- BasicRoundstartVariation
- type: gamePreset
description: secret-description
rules:
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: SecretGreenshift #For Admin Use: Runs Greenshift but shows "Secret" in lobby.
name: secret-title
showInVote: false #Admin Use
description: secret-description
+ rules:
+ - SpaceTrafficControlFriendlyEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: Sandbox
id: Traitor
alias:
- traitor
+ - tator
name: traitor-title
description: traitor-description
showInVote: false
- Traitor
- SubGamemodesRule
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
- type: gamePreset
- Nukeops
- SubGamemodesRule
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
- type: gamePreset
- Revolutionary
- SubGamemodesRule
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
- type: gamePreset
rules:
- Zombie
- BasicStationEventScheduler
- - GameRuleMeteorScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
+
+- type: gamePreset
+ id: Zombieteors
+ alias:
+ - zombieteors
+ - zombombies
+ - meteombies
+ name: zombieteors-title
+ description: zombieteors-description
+ showInVote: false
+ rules:
+ - Zombie
+ - BasicStationEventScheduler
+ - KesslerSyndromeScheduler
+ - SpaceTrafficControlEventScheduler
- BasicRoundstartVariation
weights:
Nukeops: 0.20
Traitor: 0.60
- Zombie: 0.05
- Survival: 0.10
+ Zombie: 0.04
+ Zombieteors: 0.01
+ Survival: 0.09
+ KesslerSyndrome: 0.01
Revolutionary: 0.05