Event refactor (#9589)
* Station event refactor * Remove clientside `IStationEventManager` we can just use prototypes * Basic API idea * Cruft * first attempt at epicness * okay yeah this shit is really clean * sort out minor stuff * Convert `BreakerFlip` * `BureaucraticError` + general cleanup * `DiseaseOutbreak` * `FalseAlarm` * `GasLeak` * `KudzuGrowth` * `MeteorSwarm` * `MouseMigration` * misc errors * `PowerGridCheck` * `RandomSentience` * `VentClog` * `VentCritters` * `ZombieOutbreak` * Rewrite basic event scheduler * Minor fixes and logging * ooooops * errors + fix * linter * completions, `RuleStarted` property, update loop fixes * Tweaks * Fix #9462 * Basic scheduler update fix, and fixes #8174 * Add test * UI cleanup * really this was just for testing
This commit is contained in:
@@ -12,6 +12,5 @@
|
||||
<Button Name="LoadGamePrototypeButton" Text="{Loc 'load-game-prototype'}"/>
|
||||
<cc:UICommandButton Name="LoadBlueprintsButton" Command="loadbp" Text="{Loc 'load-blueprints'}" WindowType="{x:Type abt:LoadBlueprintsWindow}"/>
|
||||
<cc:CommandButton Command="deleteewc Singularity" Name="DeleteSingulos" Text="{Loc 'delete-singularities'}"/>
|
||||
<cc:UICommandButton Command="events" Text="{Loc 'open-station-events'}" WindowType="{x:Type abt:StationEventsWindow}" />
|
||||
</GridContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io" Title="{Loc Events}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc Event}" MinSize="100 0" />
|
||||
<Control MinSize="50 0" />
|
||||
<OptionButton Name="EventsOptions" MinSize="100 0" HorizontalExpand="True" Disabled="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="PauseButton" Text="{Loc Pause}" Disabled="True" />
|
||||
<Button Name="ResumeButton" Text="{Loc Resume}" Disabled="True" />
|
||||
<Button Name="SubmitButton" Text="{Loc Run}" Disabled="True" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,86 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.StationEvents.Managers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.AdminbusTab
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
[UsedImplicitly]
|
||||
public sealed partial class StationEventsWindow : DefaultWindow
|
||||
{
|
||||
private List<string>? _data;
|
||||
|
||||
[Dependency]
|
||||
private readonly IStationEventManager _eventManager = default!;
|
||||
|
||||
public StationEventsWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
MinSize = SetSize = (300, 200);
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
_eventManager.OnStationEventsReceived += OnStationEventsReceived;
|
||||
_eventManager.RequestEvents();
|
||||
|
||||
EventsOptions.AddItem(Loc.GetString("station-events-window-not-loaded-text"));
|
||||
}
|
||||
|
||||
private void OnStationEventsReceived()
|
||||
{
|
||||
// fill events dropdown
|
||||
_data = _eventManager.StationEvents.ToList();
|
||||
EventsOptions.Clear();
|
||||
foreach (var stationEvent in _data)
|
||||
{
|
||||
EventsOptions.AddItem(stationEvent);
|
||||
}
|
||||
EventsOptions.AddItem(Loc.GetString("station-events-window-random-text"));
|
||||
|
||||
// Enable all UI elements
|
||||
EventsOptions.Disabled = false;
|
||||
PauseButton.Disabled = false;
|
||||
ResumeButton.Disabled = false;
|
||||
SubmitButton.Disabled = false;
|
||||
|
||||
// Subscribe to UI events
|
||||
EventsOptions.OnItemSelected += eventArgs => EventsOptions.SelectId(eventArgs.Id);
|
||||
PauseButton.OnPressed += PauseButtonOnOnPressed;
|
||||
ResumeButton.OnPressed += ResumeButtonOnOnPressed;
|
||||
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
|
||||
}
|
||||
|
||||
private static void PauseButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand("events pause");
|
||||
}
|
||||
|
||||
private static void ResumeButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand("events resume");
|
||||
}
|
||||
|
||||
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// random is always last option
|
||||
var id = EventsOptions.SelectedId;
|
||||
var selectedEvent = id < _data.Count ? _data[id] : "random";
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand($"events run \"{selectedEvent}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,10 @@ using Content.Client.MobState.Overlays;
|
||||
using Content.Client.Parallax;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Radiation;
|
||||
using Content.Client.Sandbox;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Singularity;
|
||||
using Content.Client.StationEvents;
|
||||
using Content.Client.StationEvents.Managers;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
@@ -192,7 +191,6 @@ namespace Content.Client.Entry
|
||||
|
||||
IoCManager.Resolve<IChatManager>().Initialize();
|
||||
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
||||
IoCManager.Resolve<IStationEventManager>().Initialize();
|
||||
IoCManager.Resolve<EuiManager>().Initialize();
|
||||
IoCManager.Resolve<IVoteManager>().Initialize();
|
||||
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();
|
||||
|
||||
@@ -13,7 +13,6 @@ using Content.Client.Module;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.StationEvents.Managers;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
@@ -37,7 +36,6 @@ namespace Content.Client.IoC
|
||||
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
||||
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
||||
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
||||
IoCManager.Register<IStationEventManager, StationEventManager>();
|
||||
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
|
||||
IoCManager.Register<EuiManager, EuiManager>();
|
||||
IoCManager.Register<IVoteManager, VoteManager>();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using Content.Shared.Radiation;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.StationEvents
|
||||
namespace Content.Client.Radiation
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedRadiationPulseComponent))]
|
||||
@@ -1,16 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.StationEvents
|
||||
namespace Content.Client.Radiation
|
||||
{
|
||||
public sealed class RadiationPulseOverlay : Overlay
|
||||
{
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Client.StationEvents.Managers
|
||||
{
|
||||
public interface IStationEventManager
|
||||
{
|
||||
public IReadOnlyList<string> StationEvents { get; }
|
||||
public void Initialize();
|
||||
public event Action OnStationEventsReceived;
|
||||
public void RequestEvents();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.StationEvents;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.StationEvents.Managers
|
||||
{
|
||||
internal sealed class StationEventManager : IStationEventManager
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
|
||||
private readonly List<string> _events = new();
|
||||
public IReadOnlyList<string> StationEvents => _events;
|
||||
public event Action? OnStationEventsReceived;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgRequestStationEvents>();
|
||||
_netManager.RegisterNetMessage<MsgStationEvents>(RxStationEvents);
|
||||
_netManager.Disconnect += OnNetManagerOnDisconnect;
|
||||
}
|
||||
|
||||
private void OnNetManagerOnDisconnect(object? sender, NetDisconnectedArgs msg)
|
||||
{
|
||||
_events.Clear();
|
||||
}
|
||||
|
||||
private void RxStationEvents(MsgStationEvents msg)
|
||||
{
|
||||
_events.Clear();
|
||||
_events.AddRange(msg.Events);
|
||||
OnStationEventsReceived?.Invoke();
|
||||
}
|
||||
|
||||
public void RequestEvents()
|
||||
{
|
||||
_netManager.ClientSendMessage(_netManager.CreateNetMessage<MsgRequestStationEvents>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameRules;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that all game rules can be added/started/ended at the same time without exceptions.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class StartEndGameRulesTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient();
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var gameTicker = EntitySystem.Get<GameTicker>();
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
var rules = protoMan.EnumeratePrototypes<GameRulePrototype>().ToArray();
|
||||
|
||||
// Start all rules
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
gameTicker.StartGameRule(rule);
|
||||
}
|
||||
|
||||
Assert.That(gameTicker.AddedGameRules.Count == rules.Length);
|
||||
});
|
||||
|
||||
// Wait three ticks for any random update loops that might happen
|
||||
await server.WaitRunTicks(3);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var gameTicker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
// End all rules
|
||||
gameTicker.ClearGameRules();
|
||||
Assert.That(!gameTicker.AddedGameRules.Any());
|
||||
});
|
||||
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.StationEvents;
|
||||
using Content.Shared.GameTicking;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.StationEvents
|
||||
{
|
||||
[TestFixture]
|
||||
public sealed class StationEventsSystemTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient();
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Idle each event
|
||||
var stationEventsSystem = EntitySystem.Get<StationEventSystem>();
|
||||
var dummyFrameTime = (float) IoCManager.Resolve<IGameTiming>().TickPeriod.TotalSeconds;
|
||||
|
||||
foreach (var stationEvent in stationEventsSystem.StationEvents)
|
||||
{
|
||||
stationEvent.Announce();
|
||||
stationEvent.Update(dummyFrameTime);
|
||||
stationEvent.Startup();
|
||||
stationEvent.Update(dummyFrameTime);
|
||||
stationEvent.Running = false;
|
||||
stationEvent.Shutdown();
|
||||
// Due to timings some events might startup twice when in reality they wouldn't.
|
||||
Assert.That(stationEvent.Occurrences > 0);
|
||||
}
|
||||
|
||||
stationEventsSystem.Reset(new RoundRestartCleanupEvent());
|
||||
|
||||
foreach (var stationEvent in stationEventsSystem.StationEvents)
|
||||
{
|
||||
Assert.That(stationEvent.Occurrences, Is.EqualTo(0));
|
||||
}
|
||||
});
|
||||
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,9 @@ public partial class AtmosphereSystem
|
||||
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, IMapGridComponent? mapGridComp = null)
|
||||
{
|
||||
var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
|
||||
// If nothing handled the event, it'll default to true.
|
||||
return ev.Result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
@@ -10,10 +11,26 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
// No duplicates.
|
||||
[ViewVariables] private readonly HashSet<GameRulePrototype> _addedGameRules = new();
|
||||
public IEnumerable<GameRulePrototype> AddedGameRules => _addedGameRules;
|
||||
|
||||
/// <summary>
|
||||
/// Holds all currently added game rules.
|
||||
/// </summary>
|
||||
public IReadOnlySet<GameRulePrototype> AddedGameRules => _addedGameRules;
|
||||
|
||||
[ViewVariables] private readonly HashSet<GameRulePrototype> _startedGameRules = new();
|
||||
public IEnumerable<GameRulePrototype> StartedGameRules => _startedGameRules;
|
||||
|
||||
/// <summary>
|
||||
/// Holds all currently started game rules.
|
||||
/// </summary>
|
||||
public IReadOnlySet<GameRulePrototype> StartedGameRules => _startedGameRules;
|
||||
|
||||
[ViewVariables] private readonly List<(TimeSpan, GameRulePrototype)> _allPreviousGameRules = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list storing the start times of all game rules that have been started this round.
|
||||
/// Game rules can be started and stopped at any time, including midround.
|
||||
/// </summary>
|
||||
public IReadOnlyList<(TimeSpan, GameRulePrototype)> AllPreviousGameRules => _allPreviousGameRules;
|
||||
|
||||
private void InitializeGameRules()
|
||||
{
|
||||
@@ -21,13 +38,15 @@ namespace Content.Server.GameTicking
|
||||
_consoleHost.RegisterCommand("addgamerule",
|
||||
string.Empty,
|
||||
"addgamerule <rules>",
|
||||
AddGameRuleCommand);
|
||||
AddGameRuleCommand,
|
||||
AddGameRuleCompletions);
|
||||
|
||||
// End game rule command.
|
||||
_consoleHost.RegisterCommand("endgamerule",
|
||||
string.Empty,
|
||||
"endgamerule <rules>",
|
||||
EndGameRuleCommand);
|
||||
EndGameRuleCommand,
|
||||
EndGameRuleCompletions);
|
||||
|
||||
// Clear game rules command.
|
||||
_consoleHost.RegisterCommand("cleargamerules",
|
||||
@@ -49,50 +68,55 @@ namespace Content.Server.GameTicking
|
||||
/// </summary>
|
||||
public void StartGameRule(GameRulePrototype rule)
|
||||
{
|
||||
if (!GameRuleAdded(rule))
|
||||
if (!IsGameRuleAdded(rule))
|
||||
AddGameRule(rule);
|
||||
|
||||
_allPreviousGameRules.Add((RoundDuration(), rule));
|
||||
_sawmill.Info($"Started game rule {rule.ID}");
|
||||
|
||||
if (_startedGameRules.Add(rule))
|
||||
RaiseLocalEvent(new GameRuleStartedEvent(rule));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends a game rule.
|
||||
/// This always includes removing it (removing it from added game rules) so that behavior
|
||||
/// This always includes removing it (from added game rules) so that behavior
|
||||
/// is not separate from this.
|
||||
/// </summary>
|
||||
/// <param name="rule"></param>
|
||||
public void EndGameRule(GameRulePrototype rule)
|
||||
{
|
||||
if (!GameRuleAdded(rule))
|
||||
if (!IsGameRuleAdded(rule))
|
||||
return;
|
||||
|
||||
_addedGameRules.Remove(rule);
|
||||
_sawmill.Info($"Ended game rule {rule.ID}");
|
||||
|
||||
if (GameRuleStarted(rule))
|
||||
if (IsGameRuleStarted(rule))
|
||||
_startedGameRules.Remove(rule);
|
||||
RaiseLocalEvent(new GameRuleEndedEvent(rule));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a game rule to the list, but does not
|
||||
/// start it yet, instead waiting until roundstart.
|
||||
/// start it yet, instead waiting until the rule is actually started by other code (usually roundstart)
|
||||
/// </summary>
|
||||
public bool AddGameRule(GameRulePrototype rule)
|
||||
{
|
||||
if (!_addedGameRules.Add(rule))
|
||||
return false;
|
||||
|
||||
_sawmill.Info($"Added game rule {rule.ID}");
|
||||
RaiseLocalEvent(new GameRuleAddedEvent(rule));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GameRuleAdded(GameRulePrototype rule)
|
||||
public bool IsGameRuleAdded(GameRulePrototype rule)
|
||||
{
|
||||
return _addedGameRules.Contains(rule);
|
||||
}
|
||||
|
||||
public bool GameRuleAdded(string rule)
|
||||
public bool IsGameRuleAdded(string rule)
|
||||
{
|
||||
foreach (var ruleProto in _addedGameRules)
|
||||
{
|
||||
@@ -103,12 +127,12 @@ namespace Content.Server.GameTicking
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool GameRuleStarted(GameRulePrototype rule)
|
||||
public bool IsGameRuleStarted(GameRulePrototype rule)
|
||||
{
|
||||
return _startedGameRules.Contains(rule);
|
||||
}
|
||||
|
||||
public bool GameRuleStarted(string rule)
|
||||
public bool IsGameRuleStarted(string rule)
|
||||
{
|
||||
foreach (var ruleProto in _startedGameRules)
|
||||
{
|
||||
@@ -142,12 +166,19 @@ namespace Content.Server.GameTicking
|
||||
|
||||
AddGameRule(rule);
|
||||
|
||||
// Start rule if we're already in the middle of a round.
|
||||
// Start rule if we're already in the middle of a round
|
||||
if(RunLevel == GameRunLevel.InRound)
|
||||
StartGameRule(rule);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletionResult AddGameRuleCompletions(IConsoleShell shell, string[] args)
|
||||
{
|
||||
var activeIds = _addedGameRules.Select(c => c.ID);
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<GameRulePrototype>().Where(p => !activeIds.Contains(p.Value)),
|
||||
"<rule>");
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
@@ -163,6 +194,12 @@ namespace Content.Server.GameTicking
|
||||
}
|
||||
}
|
||||
|
||||
private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(_addedGameRules.Select(c => new CompletionOption(c.ID)),
|
||||
"<added rule>");
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
|
||||
@@ -425,6 +425,7 @@ namespace Content.Server.GameTicking
|
||||
ClearGameRules();
|
||||
|
||||
_addedGameRules.Clear();
|
||||
_allPreviousGameRules.Clear();
|
||||
|
||||
// Round restart cleanup event, so entity systems can reset.
|
||||
var ev = new RoundRestartCleanupEvent();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using Content.Shared.Sound;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a configuration for a given station event game rule, since all station events are just
|
||||
/// game rules.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class StationEventRuleConfiguration : GameRuleConfiguration
|
||||
{
|
||||
[DataField("id", required: true)]
|
||||
private string _id = default!;
|
||||
public override string Id => _id;
|
||||
|
||||
public const float WeightVeryLow = 0.0f;
|
||||
public const float WeightLow = 5.0f;
|
||||
public const float WeightNormal = 10.0f;
|
||||
public const float WeightHigh = 15.0f;
|
||||
public const float WeightVeryHigh = 20.0f;
|
||||
|
||||
[DataField("weight")]
|
||||
public float Weight = WeightNormal;
|
||||
|
||||
[DataField("startAnnouncement")]
|
||||
public string? StartAnnouncement;
|
||||
|
||||
[DataField("endAnnouncement")]
|
||||
public string? EndAnnouncement;
|
||||
|
||||
[DataField("startAudio")]
|
||||
public SoundSpecifier? StartAudio;
|
||||
|
||||
[DataField("endAudio")]
|
||||
public SoundSpecifier? EndAudio;
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, when is the first round time this event can start
|
||||
/// </summary>
|
||||
[DataField("earliestStart")]
|
||||
public int EarliestStart = 5;
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, the amount of time before the same event can occur again
|
||||
/// </summary>
|
||||
[DataField("reoccurrenceDelay")]
|
||||
public int ReoccurrenceDelay = 30;
|
||||
|
||||
/// <summary>
|
||||
/// When in the lifetime to start the event.
|
||||
/// </summary>
|
||||
[DataField("startAfter")]
|
||||
public float StartAfter;
|
||||
|
||||
/// <summary>
|
||||
/// When in the lifetime to end the event..
|
||||
/// </summary>
|
||||
[DataField("endAfter")]
|
||||
public float EndAfter = float.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// How many players need to be present on station for the event to run
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid running deadly events with low-pop
|
||||
/// </remarks>
|
||||
[DataField("minimumPlayers")]
|
||||
public int MinimumPlayers;
|
||||
|
||||
/// <summary>
|
||||
/// How many times this even can occur in a single round
|
||||
/// </summary>
|
||||
[DataField("maxOccurrences")]
|
||||
public int? MaxOccurrences;
|
||||
}
|
||||
@@ -34,14 +34,14 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
||||
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
_deadCheckTimer = null;
|
||||
_restartTimer = null;
|
||||
@@ -64,7 +64,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
private void RunDelayedCheck()
|
||||
{
|
||||
if (!Enabled || _deadCheckTimer != null)
|
||||
if (!RuleAdded || _deadCheckTimer != null)
|
||||
return;
|
||||
|
||||
_deadCheckTimer = DeadCheckDelay;
|
||||
@@ -72,7 +72,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
|
||||
|
||||
@@ -9,10 +9,16 @@ public abstract class GameRuleSystem : EntitySystem
|
||||
[Dependency] protected GameTicker GameTicker = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this GameRule is currently enabled or not.
|
||||
/// Whether this GameRule is currently added or not.
|
||||
/// Be sure to check this before doing anything rule-specific.
|
||||
/// </summary>
|
||||
public bool Enabled { get; protected set; } = false;
|
||||
public bool RuleAdded { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this game rule has been started after being added.
|
||||
/// You probably want to check this before doing any update loop stuff.
|
||||
/// </summary>
|
||||
public bool RuleStarted { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the GameRule prototype with this ID is added, this system will be enabled.
|
||||
@@ -20,6 +26,12 @@ public abstract class GameRuleSystem : EntitySystem
|
||||
/// </summary>
|
||||
public new abstract string Prototype { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current configuration after the event has been added.
|
||||
/// This should not be getting accessed before the event is enabled, as usual.
|
||||
/// </summary>
|
||||
public GameRuleConfiguration Configuration = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -35,7 +47,10 @@ public abstract class GameRuleSystem : EntitySystem
|
||||
if (ev.Rule.Configuration.Id != Prototype)
|
||||
return;
|
||||
|
||||
Enabled = true;
|
||||
Configuration = ev.Rule.Configuration;
|
||||
RuleAdded = true;
|
||||
|
||||
Added();
|
||||
}
|
||||
|
||||
private void OnGameRuleStarted(GameRuleStartedEvent ev)
|
||||
@@ -43,7 +58,9 @@ public abstract class GameRuleSystem : EntitySystem
|
||||
if (ev.Rule.Configuration.Id != Prototype)
|
||||
return;
|
||||
|
||||
Started(ev.Rule.Configuration);
|
||||
RuleStarted = true;
|
||||
|
||||
Started();
|
||||
}
|
||||
|
||||
private void OnGameRuleEnded(GameRuleEndedEvent ev)
|
||||
@@ -51,17 +68,27 @@ public abstract class GameRuleSystem : EntitySystem
|
||||
if (ev.Rule.Configuration.Id != Prototype)
|
||||
return;
|
||||
|
||||
Enabled = false;
|
||||
Ended(ev.Rule.Configuration);
|
||||
RuleAdded = false;
|
||||
RuleStarted = false;
|
||||
Ended();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the game rule has been started..
|
||||
/// Called when the game rule has been added.
|
||||
/// You should avoid using this in favor of started--they are not the same thing.
|
||||
/// </summary>
|
||||
public abstract void Started(GameRuleConfiguration configuration);
|
||||
/// <remarks>
|
||||
/// This is virtual because it doesn't actually have to be used, and most of the time shouldn't be.
|
||||
/// </remarks>
|
||||
public virtual void Added() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the game rule has ended..
|
||||
/// Called when the game rule has been started.
|
||||
/// </summary>
|
||||
public abstract void Ended(GameRuleConfiguration configuration);
|
||||
public abstract void Started();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the game rule has ended.
|
||||
/// </summary>
|
||||
public abstract void Ended();
|
||||
}
|
||||
|
||||
@@ -25,16 +25,16 @@ public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration config)
|
||||
public override void Started()
|
||||
{
|
||||
if (config is not InactivityGameRuleConfiguration inactivityConfig)
|
||||
if (Configuration is not InactivityGameRuleConfiguration inactivityConfig)
|
||||
return;
|
||||
InactivityMaxTime = inactivityConfig.InactivityMaxTime;
|
||||
RoundEndDelay = inactivityConfig.RoundEndDelay;
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
switch (args.New)
|
||||
|
||||
@@ -23,10 +23,11 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration config)
|
||||
public override void Started()
|
||||
{
|
||||
if (config is not MaxTimeRestartRuleConfiguration maxTimeRestartConfig)
|
||||
if (Configuration is not MaxTimeRestartRuleConfiguration maxTimeRestartConfig)
|
||||
return;
|
||||
|
||||
RoundMaxTime = maxTimeRestartConfig.RoundMaxTime;
|
||||
RoundEndDelay = maxTimeRestartConfig.RoundEndDelay;
|
||||
|
||||
@@ -34,7 +35,7 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
||||
RestartTimer();
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
StopTimer();
|
||||
}
|
||||
@@ -62,7 +63,7 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
switch (args.New)
|
||||
|
||||
@@ -56,7 +56,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnNukeExploded(NukeExplodedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
_opsWon = true;
|
||||
@@ -65,7 +65,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
ev.AddLine(_opsWon ? Loc.GetString("nukeops-ops-won") : Loc.GetString("nukeops-crew-won"));
|
||||
@@ -78,7 +78,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
if (!_aliveNukeops.TryFirstOrNull(x => x.Key.OwnedEntity == ev.Entity, out var op)) return;
|
||||
@@ -93,7 +93,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnPlayersSpawning(RulePlayerSpawningEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
_aliveNukeops.Clear();
|
||||
@@ -292,7 +292,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.NukeopsMinPlayers);
|
||||
@@ -311,11 +311,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
_opsWon = false;
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _) { }
|
||||
public override void Ended() { }
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
if (Deleted(_pirateShip))
|
||||
@@ -120,14 +120,14 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
||||
}
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration _) { }
|
||||
public override void Started() { }
|
||||
|
||||
public override void Ended(GameRuleConfiguration _) { }
|
||||
public override void Ended() { }
|
||||
|
||||
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
|
||||
{
|
||||
// Forgive me for copy-pasting nukies.
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +225,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
||||
|
||||
@@ -9,12 +9,12 @@ public sealed class SandboxRuleSystem : GameRuleSystem
|
||||
|
||||
public override string Prototype => "Sandbox";
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
_sandbox.IsSandboxEnabled = true;
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
_sandbox.IsSandboxEnabled = false;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public sealed class SecretRuleSystem : GameRuleSystem
|
||||
|
||||
public override string Prototype => "Secret";
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
PickRule();
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
// noop
|
||||
// Preset should already handle it.
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
|
||||
@@ -119,7 +119,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var minTraitors = _cfg.GetCVar(CCVars.SuspicionMinTraitors);
|
||||
@@ -203,7 +203,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
}
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
|
||||
@@ -269,7 +269,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
|
||||
EndTime = null;
|
||||
@@ -288,7 +288,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
|
||||
private void CheckWinConditions()
|
||||
{
|
||||
if (!Enabled || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||
if (!RuleAdded || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||
return;
|
||||
|
||||
var traitorsAlive = 0;
|
||||
@@ -457,7 +457,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
ev.Disallow();
|
||||
|
||||
@@ -63,7 +63,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var session = ev.Player;
|
||||
@@ -144,7 +144,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnGhostAttempt(GhostAttemptHandleEvent ev)
|
||||
{
|
||||
if (!Enabled || ev.Handled)
|
||||
if (!RuleAdded || ev.Handled)
|
||||
return;
|
||||
|
||||
ev.Handled = true;
|
||||
@@ -181,7 +181,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var lines = new List<string>();
|
||||
@@ -200,14 +200,14 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
ev.AddLine(string.Join('\n', lines));
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration _)
|
||||
public override void Started()
|
||||
{
|
||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
||||
_restarter.RestartTimer();
|
||||
_safeToEndRound = true;
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -49,16 +49,16 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration _) {}
|
||||
public override void Started() {}
|
||||
|
||||
public override void Ended(GameRuleConfiguration _)
|
||||
public override void Ended()
|
||||
{
|
||||
_traitors.Clear();
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
// If the current preset doesn't explicitly contain the traitor game rule, just carry on and remove self.
|
||||
@@ -86,7 +86,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var playersPerTraitor = _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
||||
@@ -197,7 +197,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var result = Loc.GetString("traitor-round-end-result", ("traitorCount", _traitors.Count));
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
//this is just the general condition thing used for determining the win/lose text
|
||||
@@ -113,7 +113,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
_initialInfectedNames = new();
|
||||
@@ -127,14 +127,14 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
||||
/// </remarks>
|
||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
CheckRoundEnd(ev.Entity);
|
||||
}
|
||||
|
||||
private void OnEntityZombified(EntityZombifiedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
CheckRoundEnd(ev.Target);
|
||||
}
|
||||
@@ -158,7 +158,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (!RuleAdded)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
||||
@@ -177,13 +177,13 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
||||
}
|
||||
}
|
||||
|
||||
public override void Started(GameRuleConfiguration configuration)
|
||||
public override void Started()
|
||||
{
|
||||
//this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly
|
||||
InfectInitialPlayers();
|
||||
}
|
||||
|
||||
public override void Ended(GameRuleConfiguration configuration) { }
|
||||
public override void Ended() { }
|
||||
|
||||
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Content.Server.Spawners.EntitySystems
|
||||
|
||||
foreach (var rule in component.GameRules)
|
||||
{
|
||||
if (!_ticker.GameRuleStarted(rule)) continue;
|
||||
if (!_ticker.IsGameRuleStarted(rule)) continue;
|
||||
Spawn(component);
|
||||
return;
|
||||
}
|
||||
|
||||
247
Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs
Normal file
247
Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.StationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// The basic event scheduler rule, loosely based off of /tg/ events, which most
|
||||
/// game presets use.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem
|
||||
{
|
||||
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!;
|
||||
|
||||
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
|
||||
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, value => RuleAdded = value, true);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
if (!_configurationManager.GetCVar(CCVars.EventsEnabled))
|
||||
RuleAdded = false;
|
||||
}
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (_timeUntilNextEvent > 0)
|
||||
{
|
||||
_timeUntilNextEvent -= frameTime;
|
||||
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);
|
||||
ResetTimer();
|
||||
_sawmill.Info($"Started event {proto.ID}. Next event in {_timeUntilNextEvent} seconds");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the event timer once the event is done.
|
||||
/// </summary>
|
||||
private void ResetTimer()
|
||||
{
|
||||
// 5 - 15 minutes. TG does 3-10 but that's pretty frequent
|
||||
_timeUntilNextEvent = _random.Next(300, 900);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,34 +7,34 @@ using Robust.Shared.Random;
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BreakerFlip : StationEvent
|
||||
public sealed class BreakerFlip : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||
|
||||
public override string Name => "BreakerFlip";
|
||||
public override string? StartAnnouncement =>
|
||||
Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}"))));
|
||||
public override float Weight => WeightNormal;
|
||||
protected override float EndAfter => 1.0f;
|
||||
public override int? MaxOccurrences => 5;
|
||||
public override int MinimumPlayers => 15;
|
||||
public override string Prototype => "BreakerFlip";
|
||||
|
||||
public override void Startup()
|
||||
public override void Added()
|
||||
{
|
||||
base.Startup();
|
||||
base.Added();
|
||||
|
||||
var apcSys = EntitySystem.Get<ApcSystem>();
|
||||
var allApcs = _entityManager.EntityQuery<ApcComponent>().ToList();
|
||||
var toDisable = Math.Min(_random.Next(3, 7), allApcs.Count);
|
||||
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
|
||||
ChatSystem.DispatchGlobalAnnouncement(str, playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
var allApcs = EntityQuery<ApcComponent>().ToList();
|
||||
var toDisable = Math.Min(RobustRandom.Next(3, 7), allApcs.Count);
|
||||
if (toDisable == 0)
|
||||
return;
|
||||
|
||||
_random.Shuffle(allApcs);
|
||||
RobustRandom.Shuffle(allApcs);
|
||||
|
||||
for (var i = 0; i < toDisable; i++)
|
||||
{
|
||||
apcSys.ApcToggleBreaker(allApcs[i].Owner, allApcs[i]);
|
||||
_apcSystem.ApcToggleBreaker(allApcs[i].Owner, allApcs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,55 +6,44 @@ using Robust.Shared.Random;
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BureaucraticError : StationEvent
|
||||
public sealed class BureaucraticError : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
public override string StartAnnouncement =>
|
||||
Loc.GetString("station-event-bureaucratic-error-announcement");
|
||||
public override string Name => "BureaucraticError";
|
||||
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||
|
||||
public override int MinimumPlayers => 25;
|
||||
public override string Prototype => "BureaucraticError";
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 2;
|
||||
|
||||
protected override float EndAfter => 1f;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
var stationSystem = EntitySystem.Get<StationSystem>();
|
||||
var stationJobsSystem = EntitySystem.Get<StationJobsSystem>();
|
||||
if (stationSystem.Stations.Count == 0) return; // No stations
|
||||
var chosenStation = _random.Pick(stationSystem.Stations.ToList());
|
||||
var jobList = stationJobsSystem.GetJobs(chosenStation).Keys.ToList();
|
||||
base.Started();
|
||||
|
||||
if (StationSystem.Stations.Count == 0) return; // No stations
|
||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||
var jobList = _stationJobs.GetJobs(chosenStation).Keys.ToList();
|
||||
|
||||
// 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 (_random.Prob(0.25f))
|
||||
if (RobustRandom.Prob(0.25f))
|
||||
{
|
||||
var chosenJob = _random.PickAndTake(jobList);
|
||||
stationJobsSystem.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
||||
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||
_stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
||||
foreach (var job in jobList)
|
||||
{
|
||||
if (stationJobsSystem.IsJobUnlimited(chosenStation, job))
|
||||
if (_stationJobs.IsJobUnlimited(chosenStation, job))
|
||||
continue;
|
||||
stationJobsSystem.TrySetJobSlot(chosenStation, job, 0);
|
||||
_stationJobs.TrySetJobSlot(chosenStation, job, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Changing every role is maybe a bit too chaotic so instead change 20-30% of them.
|
||||
for (var i = 0; i < _random.Next((int)(jobList.Count * 0.20), (int)(jobList.Count * 0.30)); i++)
|
||||
for (var i = 0; i < RobustRandom.Next((int)(jobList.Count * 0.20), (int)(jobList.Count * 0.30)); i++)
|
||||
{
|
||||
var chosenJob = _random.PickAndTake(jobList);
|
||||
if (stationJobsSystem.IsJobUnlimited(chosenStation, chosenJob))
|
||||
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||
if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob))
|
||||
continue;
|
||||
|
||||
stationJobsSystem.TryAdjustJobSlot(chosenStation, chosenJob, _random.Next(-3, 6));
|
||||
_stationJobs.TryAdjustJobSlot(chosenStation, chosenJob, RobustRandom.Next(-3, 6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ namespace Content.Server.StationEvents.Events;
|
||||
/// Infects a couple people
|
||||
/// with a random disease that isn't super deadly
|
||||
/// </summary>
|
||||
public sealed class DiseaseOutbreak : StationEvent
|
||||
public sealed class DiseaseOutbreak : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
||||
|
||||
public override string Prototype => "DiseaseOutbreak";
|
||||
|
||||
/// <summary>
|
||||
/// Disease prototypes I decided were not too deadly for a random event
|
||||
@@ -33,62 +33,43 @@ public sealed class DiseaseOutbreak : StationEvent
|
||||
"BirdFlew",
|
||||
"TongueTwister"
|
||||
};
|
||||
public override string Name => "DiseaseOutbreak";
|
||||
public override float Weight => WeightNormal;
|
||||
|
||||
public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/outbreak7.ogg");
|
||||
protected override float EndAfter => 1.0f;
|
||||
|
||||
public override bool AnnounceEvent => false;
|
||||
|
||||
/// <summary>
|
||||
/// Finds 2-5 random, alive entities that can host diseases
|
||||
/// and gives them a randomly selected disease.
|
||||
/// They all get the same disease.
|
||||
/// </summary>
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
List<DiseaseCarrierComponent> aliveList = new();
|
||||
foreach (var (carrier, mobState) in _entityManager.EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
||||
foreach (var (carrier, mobState) in EntityManager.EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
||||
{
|
||||
if (!mobState.IsDead())
|
||||
aliveList.Add(carrier);
|
||||
}
|
||||
_random.Shuffle(aliveList);
|
||||
/// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
||||
RobustRandom.Shuffle(aliveList);
|
||||
|
||||
var toInfect = _random.Next(2, 5);
|
||||
// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
||||
var toInfect = RobustRandom.Next(2, 5);
|
||||
|
||||
var diseaseName = _random.Pick(NotTooSeriousDiseases);
|
||||
var diseaseName = RobustRandom.Pick(NotTooSeriousDiseases);
|
||||
|
||||
if (!_prototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
|
||||
if (!PrototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
|
||||
return;
|
||||
|
||||
var diseaseSystem = EntitySystem.Get<DiseaseSystem>();
|
||||
var entSysMgr = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var stationSystem = entSysMgr.GetEntitySystem<StationSystem>();
|
||||
var chatSystem = entSysMgr.GetEntitySystem<ChatSystem>();
|
||||
// Now we give it to people in the list of living disease carriers earlier
|
||||
foreach (var target in aliveList)
|
||||
{
|
||||
if (toInfect-- == 0)
|
||||
break;
|
||||
|
||||
diseaseSystem.TryAddDisease(target.Owner, disease, target);
|
||||
_diseaseSystem.TryAddDisease(target.Owner, disease, target);
|
||||
|
||||
var station = stationSystem.GetOwningStation(target.Owner);
|
||||
var station = StationSystem.GetOwningStation(target.Owner);
|
||||
if(station == null) continue;
|
||||
stationsToNotify.Add((EntityUid) station);
|
||||
}
|
||||
|
||||
if (!AnnounceEvent)
|
||||
return;
|
||||
foreach (var station in stationsToNotify)
|
||||
{
|
||||
chatSystem.DispatchStationAnnouncement(station, Loc.GetString("station-event-disease-outbreak-announcement"),
|
||||
playDefaultSound: false, colorOverride: Color.YellowGreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
using JetBrains.Annotations;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class FalseAlarm : StationEvent
|
||||
public sealed class FalseAlarm : StationEventSystem
|
||||
{
|
||||
public override string Name => "FalseAlarm";
|
||||
public override float Weight => WeightHigh;
|
||||
protected override float EndAfter => 1.0f;
|
||||
public override int? MaxOccurrences => 5;
|
||||
public override string Prototype => "FalseAlarm";
|
||||
|
||||
public override void Announce()
|
||||
public override void Started()
|
||||
{
|
||||
var stationEventSystem = EntitySystem.Get<StationEventSystem>();
|
||||
var randomEvent = stationEventSystem.PickRandomEvent();
|
||||
base.Started();
|
||||
|
||||
if (randomEvent != null)
|
||||
var ev = GetRandomEventUnweighted(PrototypeManager, RobustRandom);
|
||||
|
||||
if (ev.Configuration is not StationEventRuleConfiguration cfg)
|
||||
return;
|
||||
|
||||
if (cfg.StartAnnouncement != null)
|
||||
{
|
||||
StartAnnouncement = randomEvent.StartAnnouncement;
|
||||
StartAudio = randomEvent.StartAudio;
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(cfg.StartAnnouncement), playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
base.Announce();
|
||||
if (cfg.StartAudio != null)
|
||||
{
|
||||
SoundSystem.Play(cfg.StartAudio.GetSound(), Filter.Broadcast(), cfg.StartAudio.Params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
@@ -7,17 +9,11 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
internal sealed class GasLeak : StationEvent
|
||||
internal sealed class GasLeak : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
public override string Name => "GasLeak";
|
||||
|
||||
public override string StartAnnouncement => Loc.GetString("station-event-gas-leak-start-announcement");
|
||||
|
||||
|
||||
protected override string EndAnnouncement => Loc.GetString("station-event-gas-leak-end-announcement");
|
||||
public override string Prototype => "GasLeak";
|
||||
|
||||
private static readonly Gas[] LeakableGases = {
|
||||
Gas.Miasma,
|
||||
@@ -25,24 +21,6 @@ namespace Content.Server.StationEvents.Events
|
||||
Gas.Tritium,
|
||||
};
|
||||
|
||||
public override int EarliestStart => 10;
|
||||
|
||||
public override int MinimumPlayers => 5;
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 1;
|
||||
|
||||
/// <summary>
|
||||
/// Give people time to get their internals on.
|
||||
/// </summary>
|
||||
protected override float StartAfter => 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Don't know how long the event will be until we calculate the leak amount.
|
||||
/// </summary>
|
||||
protected override float EndAfter { get; set; } = float.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Running cooldown of how much time until another leak.
|
||||
/// </summary>
|
||||
@@ -53,23 +31,18 @@ namespace Content.Server.StationEvents.Events
|
||||
/// </summary>
|
||||
private const float LeakCooldown = 1.0f;
|
||||
|
||||
|
||||
// Event variables
|
||||
|
||||
private EntityUid _targetStation;
|
||||
|
||||
private EntityUid _targetGrid;
|
||||
|
||||
private Vector2i _targetTile;
|
||||
|
||||
private EntityCoordinates _targetCoords;
|
||||
|
||||
private bool _foundTile;
|
||||
|
||||
private Gas _leakGas;
|
||||
|
||||
private float _molesPerSecond;
|
||||
|
||||
private const int MinimumMolesPerSecond = 20;
|
||||
private float _endAfter = float.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Don't want to make it too fast to give people time to flee.
|
||||
@@ -77,26 +50,25 @@ namespace Content.Server.StationEvents.Events
|
||||
private const int MaximumMolesPerSecond = 50;
|
||||
|
||||
private const int MinimumGas = 250;
|
||||
|
||||
private const int MaximumGas = 1000;
|
||||
|
||||
private const float SparkChance = 0.05f;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
|
||||
// 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))
|
||||
{
|
||||
_foundTile = true;
|
||||
|
||||
_leakGas = _robustRandom.Pick(LeakableGases);
|
||||
_leakGas = RobustRandom.Pick(LeakableGases);
|
||||
// Was 50-50 on using normal distribution.
|
||||
var totalGas = (float) _robustRandom.Next(MinimumGas, MaximumGas);
|
||||
_molesPerSecond = _robustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
||||
EndAfter = totalGas / _molesPerSecond + StartAfter;
|
||||
Logger.InfoS("stationevents", $"Leaking {totalGas} of {_leakGas} over {EndAfter - StartAfter} seconds at {_targetTile}");
|
||||
var totalGas = (float) RobustRandom.Next(MinimumGas, MaximumGas);
|
||||
var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
|
||||
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
||||
_endAfter = totalGas / _molesPerSecond + startAfter;
|
||||
Sawmill.Info($"Leaking {totalGas} of {_leakGas} over {_endAfter - startAfter} seconds at {_targetTile}");
|
||||
}
|
||||
|
||||
// Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
|
||||
@@ -107,32 +79,37 @@ namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!Started || !Running) return;
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (Elapsed > _endAfter)
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
_timeUntilLeak -= frameTime;
|
||||
|
||||
if (_timeUntilLeak > 0f) return;
|
||||
_timeUntilLeak += LeakCooldown;
|
||||
|
||||
var atmosphereSystem = _entityManager.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
|
||||
|
||||
if (!_foundTile ||
|
||||
_targetGrid == default ||
|
||||
_entityManager.Deleted(_targetGrid) ||
|
||||
!atmosphereSystem.IsSimulatedGrid(_targetGrid))
|
||||
EntityManager.Deleted(_targetGrid) ||
|
||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||
{
|
||||
Running = false;
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
var environment = atmosphereSystem.GetTileMixture(_targetGrid, null, _targetTile, true);
|
||||
var environment = _atmosphere.GetTileMixture(_targetGrid, null, _targetTile, true);
|
||||
|
||||
environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
public override void Ended()
|
||||
{
|
||||
base.Shutdown();
|
||||
base.Ended();
|
||||
|
||||
Spark();
|
||||
|
||||
@@ -141,25 +118,24 @@ namespace Content.Server.StationEvents.Events
|
||||
_targetTile = default;
|
||||
_targetCoords = default;
|
||||
_leakGas = Gas.Oxygen;
|
||||
EndAfter = float.MaxValue;
|
||||
_endAfter = float.MaxValue;
|
||||
}
|
||||
|
||||
private void Spark()
|
||||
{
|
||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
if (_robustRandom.NextFloat() <= SparkChance)
|
||||
if (RobustRandom.NextFloat() <= SparkChance)
|
||||
{
|
||||
if (!_foundTile ||
|
||||
_targetGrid == default ||
|
||||
(!_entityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : _entityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||
!atmosphereSystem.IsSimulatedGrid(_targetGrid))
|
||||
(!EntityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
||||
// it COULD start potentially start a bigger fire.
|
||||
atmosphereSystem.HotspotExpose(_targetGrid, _targetTile, 700f, 50f, true);
|
||||
_atmosphere.HotspotExpose(_targetGrid, _targetTile, 700f, 50f, true);
|
||||
SoundSystem.Play("/Audio/Effects/sparks4.ogg", Filter.Pvs(_targetCoords), _targetCoords);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,51 +3,26 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class KudzuGrowth : StationEvent
|
||||
public sealed class KudzuGrowth : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public override string Name => "KudzuGrowth";
|
||||
|
||||
public override string? StartAnnouncement =>
|
||||
Loc.GetString("station-event-kudzu-growth-start-announcement");
|
||||
|
||||
public override int EarliestStart => 15;
|
||||
|
||||
public override int MinimumPlayers => 15;
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 2;
|
||||
|
||||
// Get players to scatter a bit looking for it.
|
||||
protected override float StartAfter => 50f;
|
||||
|
||||
// Give crew at least 9 minutes to either have it gone, or to suffer another event. Kudzu is not actually required to be dead for another event to roll.
|
||||
protected override float EndAfter => 60*4;
|
||||
|
||||
public override bool AnnounceEvent => false;
|
||||
public override string Prototype => "KudzuGrowth";
|
||||
|
||||
private EntityUid _targetGrid;
|
||||
|
||||
private Vector2i _targetTile;
|
||||
|
||||
private EntityCoordinates _targetCoords;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
|
||||
// Pick a place to plant the kudzu.
|
||||
if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords, _robustRandom, _entityManager))
|
||||
if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords))
|
||||
{
|
||||
_entityManager.SpawnEntity("Kudzu", _targetCoords);
|
||||
Logger.InfoS("stationevents", $"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
|
||||
EntityManager.SpawnEntity("Kudzu", _targetCoords);
|
||||
Sawmill.Info($"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
|
||||
}
|
||||
|
||||
// If the kudzu tile selection fails we just let the announcement happen anyways because it's funny and people
|
||||
// will be hunting the non-existent, dangerous plant.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Projectiles.Components;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Spawners.Components;
|
||||
@@ -7,26 +8,9 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class MeteorSwarm : StationEvent
|
||||
public sealed class MeteorSwarm : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
public override string Name => "MeteorSwarm";
|
||||
|
||||
public override int EarliestStart => 30;
|
||||
public override float Weight => WeightLow;
|
||||
public override int? MaxOccurrences => 2;
|
||||
public override int MinimumPlayers => 20;
|
||||
|
||||
public override string StartAnnouncement => Loc.GetString("station-event-meteor-swarm-start-announcement");
|
||||
protected override string EndAnnouncement => Loc.GetString("station-event-meteor-swarm-ebd-announcement");
|
||||
|
||||
public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/meteors.ogg");
|
||||
|
||||
protected override float StartAfter => 30f;
|
||||
protected override float EndAfter => float.MaxValue;
|
||||
public override string Prototype => "MeteorSwarm";
|
||||
|
||||
private float _cooldown;
|
||||
|
||||
@@ -46,53 +30,53 @@ namespace Content.Server.StationEvents.Events
|
||||
private const float MaxAngularVelocity = 0.25f;
|
||||
private const float MinAngularVelocity = -0.25f;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
_waveCounter = robustRandom.Next(MinimumWaves, MaximumWaves);
|
||||
base.Started();
|
||||
_waveCounter = RobustRandom.Next(MinimumWaves, MaximumWaves);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
public override void Ended()
|
||||
{
|
||||
base.Shutdown();
|
||||
base.Ended();
|
||||
_waveCounter = 0;
|
||||
_cooldown = 0f;
|
||||
EndAfter = float.MaxValue;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!Started) return;
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (_waveCounter <= 0)
|
||||
{
|
||||
Running = false;
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
_cooldown -= frameTime;
|
||||
|
||||
if (_cooldown > 0f) return;
|
||||
|
||||
_waveCounter--;
|
||||
|
||||
_cooldown += (MaximumCooldown - MinimumCooldown) * _robustRandom.NextFloat() + MinimumCooldown;
|
||||
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() + MinimumCooldown;
|
||||
|
||||
Box2? playableArea = null;
|
||||
var mapId = EntitySystem.Get<GameTicker>().DefaultMap;
|
||||
var mapId = GameTicker.DefaultMap;
|
||||
|
||||
foreach (var grid in _mapManager.GetAllGrids())
|
||||
foreach (var grid in MapManager.GetAllGrids())
|
||||
{
|
||||
if (grid.ParentMapId != mapId || !_entityManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? gridBody)) continue;
|
||||
if (grid.ParentMapId != mapId || !EntityManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? gridBody)) continue;
|
||||
var aabb = gridBody.GetWorldAABB();
|
||||
playableArea = playableArea?.Union(aabb) ?? aabb;
|
||||
}
|
||||
|
||||
if (playableArea == null)
|
||||
{
|
||||
EndAfter = float.MinValue;
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,21 +87,21 @@ namespace Content.Server.StationEvents.Events
|
||||
|
||||
for (var i = 0; i < MeteorsPerWave; i++)
|
||||
{
|
||||
var angle = new Angle(_robustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * _robustRandom.NextFloat() + minimumDistance, 0));
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
var spawnPosition = new MapCoordinates(center + offset, mapId);
|
||||
var meteor = _entityManager.SpawnEntity("MeteorLarge", spawnPosition);
|
||||
var physics = _entityManager.GetComponent<PhysicsComponent>(meteor);
|
||||
var meteor = EntityManager.SpawnEntity("MeteorLarge", spawnPosition);
|
||||
var physics = EntityManager.GetComponent<PhysicsComponent>(meteor);
|
||||
physics.BodyStatus = BodyStatus.InAir;
|
||||
physics.LinearDamping = 0f;
|
||||
physics.AngularDamping = 0f;
|
||||
physics.ApplyLinearImpulse(-offset.Normalized * MeteorVelocity * physics.Mass);
|
||||
physics.ApplyAngularImpulse(
|
||||
// Get a random angular velocity.
|
||||
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * _robustRandom.NextFloat() +
|
||||
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * RobustRandom.NextFloat() +
|
||||
MinAngularVelocity));
|
||||
// TODO: God this disgusts me but projectile needs a refactor.
|
||||
IoCManager.Resolve<IEntityManager>().EnsureComponent<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,49 +4,31 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class MouseMigration : StationEvent
|
||||
public sealed class MouseMigration : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public static List<string> SpawnedPrototypeChoices = new List<string>() //we double up for that ez fake probability
|
||||
{"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
|
||||
|
||||
public override string Name => "MouseMigration";
|
||||
public override string Prototype => "MouseMigration";
|
||||
|
||||
public override string? StartAnnouncement =>
|
||||
Loc.GetString("station-event-mouse-migration-announcement");
|
||||
|
||||
public override int EarliestStart => 30;
|
||||
|
||||
public override int MinimumPlayers => 35; //this just ensures that it doesn't spawn on lowpop maps.
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 1;
|
||||
|
||||
public override bool AnnounceEvent => false;
|
||||
|
||||
protected override float StartAfter => 30f;
|
||||
|
||||
protected override float EndAfter => 60;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
|
||||
var spawnLocations = _entityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||
_random.Shuffle(spawnLocations);
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var spawnAmount = _random.Next(7, 15); // A small colony of critters.
|
||||
var spawnAmount = RobustRandom.Next(7, 15); // A small colony of critters.
|
||||
|
||||
for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||
{
|
||||
var spawnChoice = _random.Pick(SpawnedPrototypeChoices);
|
||||
if (_random.Prob(0.01f) || i == 0) //small chance for multiple, but always at least 1
|
||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||
if (RobustRandom.Prob(0.01f) || i == 0) //small chance for multiple, but always at least 1
|
||||
spawnChoice = "SpawnPointGhostRatKing";
|
||||
|
||||
_entityManager.SpawnEntity(spawnChoice, spawnLocations[i].Item2.Coordinates);
|
||||
var coords = spawnLocations[i].Item2.Coordinates;
|
||||
Sawmill.Info($"Spawning mouse {spawnChoice} at {coords}");
|
||||
EntityManager.SpawnEntity(spawnChoice, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,9 @@ using Timer = Robust.Shared.Timing.Timer;
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PowerGridCheck : StationEvent
|
||||
public sealed class PowerGridCheck : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override string Name => "PowerGridCheck";
|
||||
public override float Weight => WeightNormal;
|
||||
public override int? MaxOccurrences => 3;
|
||||
public override string StartAnnouncement => Loc.GetString("station-event-power-grid-check-start-announcement");
|
||||
protected override string EndAnnouncement => Loc.GetString("station-event-power-grid-check-end-announcement");
|
||||
public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/power_off.ogg");
|
||||
|
||||
// If you need EndAudio it's down below. Not set here because we can't play it at the normal time without spamming sounds.
|
||||
|
||||
protected override float StartAfter => 12.0f;
|
||||
public override string Prototype => "PowerGridCheck";
|
||||
|
||||
private CancellationTokenSource? _announceCancelToken;
|
||||
|
||||
@@ -37,34 +25,44 @@ namespace Content.Server.StationEvents.Events
|
||||
private int _numberPerSecond = 0;
|
||||
private float UpdateRate => 1.0f / _numberPerSecond;
|
||||
private float _frameTimeAccumulator = 0.0f;
|
||||
private float _endAfter = 0.0f;
|
||||
|
||||
public override void Announce()
|
||||
public override void Added()
|
||||
{
|
||||
base.Announce();
|
||||
EndAfter = IoCManager.Resolve<IRobustRandom>().Next(60, 120);
|
||||
base.Added();
|
||||
_endAfter = RobustRandom.Next(60, 120);
|
||||
}
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
foreach (var component in _entityManager.EntityQuery<ApcPowerReceiverComponent>(true))
|
||||
foreach (var component in EntityManager.EntityQuery<ApcPowerReceiverComponent>(true))
|
||||
{
|
||||
if (!component.PowerDisabled)
|
||||
_powered.Add(component.Owner);
|
||||
}
|
||||
|
||||
_random.Shuffle(_powered);
|
||||
RobustRandom.Shuffle(_powered);
|
||||
|
||||
_numberPerSecond = Math.Max(1, (int)(_powered.Count / SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
|
||||
|
||||
base.Startup();
|
||||
base.Started();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
_frameTimeAccumulator += frameTime;
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (Elapsed > _endAfter)
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
var updates = 0;
|
||||
_frameTimeAccumulator += frameTime;
|
||||
if (_frameTimeAccumulator > UpdateRate)
|
||||
{
|
||||
updates = (int) (_frameTimeAccumulator / UpdateRate);
|
||||
@@ -77,8 +75,8 @@ namespace Content.Server.StationEvents.Events
|
||||
break;
|
||||
|
||||
var selected = _powered.Pop();
|
||||
if (_entityManager.Deleted(selected)) continue;
|
||||
if (_entityManager.TryGetComponent<ApcPowerReceiverComponent>(selected, out var powerReceiverComponent))
|
||||
if (EntityManager.Deleted(selected)) continue;
|
||||
if (EntityManager.TryGetComponent<ApcPowerReceiverComponent>(selected, out var powerReceiverComponent))
|
||||
{
|
||||
powerReceiverComponent.PowerDisabled = true;
|
||||
}
|
||||
@@ -86,13 +84,13 @@ namespace Content.Server.StationEvents.Events
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
public override void Ended()
|
||||
{
|
||||
foreach (var entity in _unpowered)
|
||||
{
|
||||
if (_entityManager.Deleted(entity)) continue;
|
||||
if (EntityManager.Deleted(entity)) continue;
|
||||
|
||||
if (_entityManager.TryGetComponent(entity, out ApcPowerReceiverComponent? powerReceiverComponent))
|
||||
if (EntityManager.TryGetComponent(entity, out ApcPowerReceiverComponent? powerReceiverComponent))
|
||||
{
|
||||
powerReceiverComponent.PowerDisabled = false;
|
||||
}
|
||||
@@ -103,11 +101,11 @@ namespace Content.Server.StationEvents.Events
|
||||
_announceCancelToken = new CancellationTokenSource();
|
||||
Timer.Spawn(3000, () =>
|
||||
{
|
||||
SoundSystem.Play("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), AudioParams);
|
||||
SoundSystem.Play("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), AudioParams.Default);
|
||||
}, _announceCancelToken.Token);
|
||||
_unpowered.Clear();
|
||||
|
||||
base.Shutdown();
|
||||
base.Ended();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,19 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class RandomSentience : StationEvent
|
||||
public sealed class RandomSentience : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
public override string Prototype => "RandomSentience";
|
||||
|
||||
public override string Name => "RandomSentience";
|
||||
|
||||
public override float Weight => WeightNormal;
|
||||
|
||||
protected override float EndAfter => 1.0f;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
|
||||
var targetList = _entityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||
_random.Shuffle(targetList);
|
||||
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||
RobustRandom.Shuffle(targetList);
|
||||
|
||||
var toMakeSentient = _random.Next(2, 5);
|
||||
var toMakeSentient = RobustRandom.Next(2, 5);
|
||||
var groups = new HashSet<string>();
|
||||
|
||||
foreach (var target in targetList)
|
||||
@@ -36,10 +29,10 @@ public sealed class RandomSentience : StationEvent
|
||||
if (toMakeSentient-- == 0)
|
||||
break;
|
||||
|
||||
MakeSentientCommand.MakeSentient(target.Owner, _entityManager);
|
||||
_entityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
||||
var comp = _entityManager.AddComponent<GhostTakeoverAvailableComponent>(target.Owner);
|
||||
comp.RoleName = _entityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
||||
MakeSentientCommand.MakeSentient(target.Owner, EntityManager);
|
||||
EntityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
||||
var comp = EntityManager.AddComponent<GhostTakeoverAvailableComponent>(target.Owner);
|
||||
comp.RoleName = EntityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
||||
comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName));
|
||||
groups.Add(target.FlavorKind);
|
||||
}
|
||||
@@ -67,8 +60,8 @@ public sealed class RandomSentience : StationEvent
|
||||
(EntityUid) station,
|
||||
Loc.GetString("station-event-random-sentience-announcement",
|
||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||
("data", Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")),
|
||||
("strength", Loc.GetString($"random-sentience-event-strength-{_random.Next(1, 8)}"))),
|
||||
("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")),
|
||||
("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}"))),
|
||||
playDefaultSound: false,
|
||||
colorOverride: Color.Gold
|
||||
);
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public abstract class StationEvent
|
||||
{
|
||||
public const float WeightVeryLow = 0.0f;
|
||||
public const float WeightLow = 5.0f;
|
||||
public const float WeightNormal = 10.0f;
|
||||
public const float WeightHigh = 15.0f;
|
||||
public const float WeightVeryHigh = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// If the event has started and is currently running.
|
||||
/// </summary>
|
||||
public bool Running { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time when this event last ran.
|
||||
/// </summary>
|
||||
public TimeSpan LastRun { get; set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name for the event.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The weight this event has in the random-selection process.
|
||||
/// </summary>
|
||||
public virtual float Weight => WeightNormal;
|
||||
|
||||
/// <summary>
|
||||
/// What should be said in chat when the event starts (if anything).
|
||||
/// </summary>
|
||||
public virtual string? StartAnnouncement { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// What should be said in chat when the event ends (if anything).
|
||||
/// </summary>
|
||||
protected virtual string? EndAnnouncement { get; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Starting audio of the event.
|
||||
/// </summary>
|
||||
public virtual SoundSpecifier? StartAudio { get; set; } = new SoundPathSpecifier("/Audio/Announcements/attention.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Ending audio of the event.
|
||||
/// </summary>
|
||||
public virtual SoundSpecifier? EndAudio { get; } = null;
|
||||
|
||||
public virtual AudioParams AudioParams { get; } = AudioParams.Default.WithVolume(-10f);
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, when is the first round time this event can start
|
||||
/// </summary>
|
||||
public virtual int EarliestStart { get; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, the amount of time before the same event can occur again
|
||||
/// </summary>
|
||||
public virtual int ReoccurrenceDelay { get; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// When in the lifetime to call Start().
|
||||
/// </summary>
|
||||
protected virtual float StartAfter { get; } = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// When in the lifetime the event should end.
|
||||
/// </summary>
|
||||
protected virtual float EndAfter { get; set; } = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How long has the event existed. Do not change this.
|
||||
/// </summary>
|
||||
private float Elapsed { get; set; } = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How many players need to be present on station for the event to run
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid running deadly events with low-pop
|
||||
/// </remarks>
|
||||
public virtual int MinimumPlayers { get; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// How many times this event has run this round
|
||||
/// </summary>
|
||||
public int Occurrences { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// How many times this even can occur in a single round
|
||||
/// </summary>
|
||||
public virtual int? MaxOccurrences { get; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the event is announced when it is run
|
||||
/// </summary>
|
||||
public virtual bool AnnounceEvent { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Has the startup time elapsed?
|
||||
/// </summary>
|
||||
protected bool Started { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Has this event commenced (announcement may or may not be used)?
|
||||
/// </summary>
|
||||
private bool Announced { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Called once to setup the event after StartAfter has elapsed.
|
||||
/// </summary>
|
||||
public virtual void Startup()
|
||||
{
|
||||
Started = true;
|
||||
Occurrences += 1;
|
||||
LastRun = EntitySystem.Get<GameTicker>().RoundDuration();
|
||||
|
||||
IoCManager.Resolve<IAdminLogManager>()
|
||||
.Add(LogType.EventStarted, LogImpact.High, $"Event startup: {Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once as soon as an event is active.
|
||||
/// Can also be used for some initial setup.
|
||||
/// </summary>
|
||||
public virtual void Announce()
|
||||
{
|
||||
IoCManager.Resolve<IAdminLogManager>()
|
||||
.Add(LogType.EventAnnounced, $"Event announce: {Name}");
|
||||
|
||||
if (AnnounceEvent && StartAnnouncement != null)
|
||||
{
|
||||
var chatSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
|
||||
chatSystem.DispatchGlobalAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (AnnounceEvent && StartAudio != null)
|
||||
{
|
||||
SoundSystem.Play(StartAudio.GetSound(), Filter.Broadcast(), AudioParams);
|
||||
}
|
||||
|
||||
Announced = true;
|
||||
Running = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once when the station event ends for any reason.
|
||||
/// </summary>
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
IoCManager.Resolve<IAdminLogManager>()
|
||||
.Add(LogType.EventStopped, $"Event shutdown: {Name}");
|
||||
|
||||
if (AnnounceEvent && EndAnnouncement != null)
|
||||
{
|
||||
var chatSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
|
||||
chatSystem.DispatchGlobalAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (AnnounceEvent && EndAudio != null)
|
||||
{
|
||||
SoundSystem.Play(EndAudio.GetSound(), Filter.Broadcast(), AudioParams);
|
||||
}
|
||||
|
||||
Started = false;
|
||||
Announced = false;
|
||||
Elapsed = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick when this event is running.
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
public virtual void Update(float frameTime)
|
||||
{
|
||||
Elapsed += frameTime;
|
||||
|
||||
if (!Started && Elapsed >= StartAfter)
|
||||
{
|
||||
Startup();
|
||||
}
|
||||
|
||||
if (EndAfter <= Elapsed)
|
||||
{
|
||||
Running = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords, IRobustRandom? robustRandom = null, IEntityManager? entityManager = null, IMapManager? mapManager = null, StationSystem? stationSystem = null)
|
||||
{
|
||||
tile = default;
|
||||
IoCManager.Resolve(ref robustRandom, ref entityManager, ref mapManager);
|
||||
entityManager.EntitySysManager.Resolve(ref stationSystem);
|
||||
|
||||
targetCoords = EntityCoordinates.Invalid;
|
||||
if (stationSystem.Stations.Count == 0)
|
||||
{
|
||||
targetStation = EntityUid.Invalid;
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
targetStation = robustRandom.Pick(stationSystem.Stations);
|
||||
var possibleTargets = entityManager.GetComponent<StationDataComponent>(targetStation).Grids;
|
||||
if (possibleTargets.Count == 0)
|
||||
{
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
targetGrid = robustRandom.Pick(possibleTargets);
|
||||
|
||||
if (!entityManager.TryGetComponent<IMapGridComponent>(targetGrid, out var gridComp)
|
||||
|| !entityManager.TryGetComponent<TransformComponent>(targetGrid, out var transform))
|
||||
return false;
|
||||
var grid = gridComp.Grid;
|
||||
|
||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
var found = false;
|
||||
var gridBounds = grid.WorldAABB;
|
||||
var gridPos = grid.WorldPosition;
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var randomX = robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right);
|
||||
var randomY = robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
|
||||
|
||||
tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
|
||||
if (atmosphereSystem.IsTileSpace(grid.GridEntityId, transform.MapUid, tile, mapGridComp:gridComp)
|
||||
|| atmosphereSystem.IsTileAirBlocked(grid.GridEntityId, tile, mapGridComp:gridComp))
|
||||
continue;
|
||||
found = true;
|
||||
targetCoords = grid.GridTileToLocal(tile);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Content.Server/StationEvents/Events/StationEventSystem.cs
Normal file
184
Content.Server/StationEvents/Events/StationEventSystem.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract entity system inherited by all station events for their behavior.
|
||||
/// </summary>
|
||||
public abstract class StationEventSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||
[Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
||||
[Dependency] protected readonly StationSystem StationSystem = default!;
|
||||
|
||||
protected ISawmill Sawmill = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How long has the event existed. Do not change this.
|
||||
/// </summary>
|
||||
protected float Elapsed { get; set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Sawmill = Logger.GetSawmill("stationevents");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once to setup the event after StartAfter has elapsed, or if an event is forcibly started.
|
||||
/// </summary>
|
||||
public override void Started()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventStarted, LogImpact.High, $"Event started: {Configuration.Id}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once as soon as an event is added, for announcements.
|
||||
/// Can also be used for some initial setup.
|
||||
/// </summary>
|
||||
public override void Added()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {Configuration.Id}");
|
||||
|
||||
if (Configuration is not StationEventRuleConfiguration ev)
|
||||
return;
|
||||
|
||||
if (ev.StartAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.StartAnnouncement), playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (ev.StartAudio != null)
|
||||
{
|
||||
SoundSystem.Play(ev.StartAudio.GetSound(), Filter.Broadcast(), ev.StartAudio.Params);
|
||||
}
|
||||
|
||||
Elapsed = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once when the station event ends for any reason.
|
||||
/// </summary>
|
||||
public override void Ended()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventStopped, $"Event ended: {Configuration.Id}");
|
||||
|
||||
if (Configuration is not StationEventRuleConfiguration ev)
|
||||
return;
|
||||
|
||||
if (ev.EndAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.EndAnnouncement), playDefaultSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (ev.EndAudio != null)
|
||||
{
|
||||
SoundSystem.Play(ev.EndAudio.GetSound(), Filter.Broadcast(), ev.EndAudio.Params);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick when this event is running.
|
||||
/// Events are responsible for their own lifetime, so this handles starting and ending after time.
|
||||
/// </summary>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!RuleAdded || Configuration is not StationEventRuleConfiguration data)
|
||||
return;
|
||||
|
||||
Elapsed += frameTime;
|
||||
|
||||
if (!RuleStarted && Elapsed >= data.StartAfter)
|
||||
{
|
||||
GameTicker.StartGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
|
||||
if (RuleStarted && Elapsed >= data.EndAfter)
|
||||
{
|
||||
GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
protected void ForceEndSelf()
|
||||
{
|
||||
GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
|
||||
protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
|
||||
{
|
||||
tile = default;
|
||||
|
||||
targetCoords = EntityCoordinates.Invalid;
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
{
|
||||
targetStation = EntityUid.Invalid;
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
targetStation = RobustRandom.Pick(StationSystem.Stations);
|
||||
var possibleTargets = Comp<StationDataComponent>(targetStation).Grids;
|
||||
if (possibleTargets.Count == 0)
|
||||
{
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
targetGrid = RobustRandom.Pick(possibleTargets);
|
||||
|
||||
if (!TryComp<IMapGridComponent>(targetGrid, out var gridComp))
|
||||
return false;
|
||||
var grid = gridComp.Grid;
|
||||
|
||||
var atmosphereSystem = Get<AtmosphereSystem>();
|
||||
var found = false;
|
||||
var gridBounds = grid.WorldAABB;
|
||||
var gridPos = grid.WorldPosition;
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var randomX = RobustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right);
|
||||
var randomY = RobustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
|
||||
|
||||
tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
|
||||
if (atmosphereSystem.IsTileSpace(grid.GridEntityId, Transform(targetGrid).MapUid, tile, mapGridComp:gridComp)
|
||||
|| atmosphereSystem.IsTileAirBlocked(grid.GridEntityId, tile, mapGridComp:gridComp)) continue;
|
||||
found = true;
|
||||
targetCoords = grid.GridTileToLocal(tile);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static GameRulePrototype GetRandomEventUnweighted(IPrototypeManager? prototypeManager = null, IRobustRandom? random = null)
|
||||
{
|
||||
IoCManager.Resolve(ref prototypeManager, ref random);
|
||||
|
||||
return random.Pick(prototypeManager.EnumeratePrototypes<GameRulePrototype>()
|
||||
.Where(p => p.Configuration is StationEventRuleConfiguration).ToArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -11,29 +11,9 @@ using Robust.Shared.Random;
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class VentClog : StationEvent
|
||||
public sealed class VentClog : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override string Name => "VentClog";
|
||||
|
||||
public override string? StartAnnouncement =>
|
||||
Loc.GetString("station-event-vent-clog-start-announcement");
|
||||
|
||||
public override int EarliestStart => 15;
|
||||
|
||||
public override int MinimumPlayers => 15;
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 2;
|
||||
|
||||
// Give players time to reach cover.
|
||||
protected override float StartAfter => 50f;
|
||||
|
||||
protected override float EndAfter => 51.0f; // This can, surprisingly, cause the event to end before it starts.
|
||||
public override string Prototype => "VentClog";
|
||||
|
||||
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
||||
{
|
||||
@@ -41,33 +21,36 @@ public sealed class VentClog : StationEvent
|
||||
"Nutriment", "Sugar", "SpaceLube", "Ethanol", "Mercury", "Ephedrine", "WeldingFuel", "VentCrud"
|
||||
};
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
base.Started();
|
||||
|
||||
// TODO: "safe random" for chems. Right now this includes admin chemicals.
|
||||
var allReagents = _prototypeManager.EnumeratePrototypes<ReagentPrototype>()
|
||||
var allReagents = PrototypeManager.EnumeratePrototypes<ReagentPrototype>()
|
||||
.Where(x => !x.Abstract)
|
||||
.Select(x => x.ID).ToList();
|
||||
|
||||
// This is gross, but not much can be done until event refactor, which needs Dynamic.
|
||||
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
|
||||
|
||||
foreach (var (_, transform) in _entityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
||||
foreach (var (_, transform) in EntityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
||||
{
|
||||
var solution = new Solution();
|
||||
|
||||
if (_random.Prob(0.05f))
|
||||
if (!RobustRandom.Prob(0.33f))
|
||||
continue;
|
||||
|
||||
if (RobustRandom.Prob(0.05f))
|
||||
{
|
||||
solution.AddReagent(_random.Pick(allReagents), 100);
|
||||
solution.AddReagent(RobustRandom.Pick(allReagents), 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
solution.AddReagent(_random.Pick(SafeishVentChemicals), 100);
|
||||
solution.AddReagent(RobustRandom.Pick(SafeishVentChemicals), 100);
|
||||
}
|
||||
|
||||
FoamAreaReactionEffect.SpawnFoam("Foam", transform.Coordinates, solution, _random.Next(2, 6), 20, 1,
|
||||
1, sound, _entityManager);
|
||||
FoamAreaReactionEffect.SpawnFoam("Foam", transform.Coordinates, solution, RobustRandom.Next(2, 6), 20, 1,
|
||||
1, sound, EntityManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,51 +5,30 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class VentCritters : StationEvent
|
||||
public sealed class VentCritters : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public static List<string> SpawnedPrototypeChoices = new List<string>()
|
||||
{"MobGiantSpiderAngry", "MobMouse", "MobMouse1", "MobMouse2"};
|
||||
|
||||
public override string Name => "VentCritters";
|
||||
public override string Prototype => "VentCritters";
|
||||
|
||||
public override string? StartAnnouncement =>
|
||||
Loc.GetString("station-event-vent-spiders-start-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}"))));
|
||||
|
||||
public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/aliens.ogg");
|
||||
|
||||
public override int EarliestStart => 15;
|
||||
|
||||
public override int MinimumPlayers => 15;
|
||||
|
||||
public override float Weight => WeightLow;
|
||||
|
||||
public override int? MaxOccurrences => 2;
|
||||
|
||||
protected override float StartAfter => 30f;
|
||||
|
||||
protected override float EndAfter => 60;
|
||||
|
||||
public override bool AnnounceEvent => false;
|
||||
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
var spawnChoice = _random.Pick(SpawnedPrototypeChoices);
|
||||
var spawnLocations = _entityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
_random.Shuffle(spawnLocations);
|
||||
base.Started();
|
||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var spawnAmount = _random.Next(4, 12); // A small colony of critters.
|
||||
var spawnAmount = RobustRandom.Next(4, 12); // A small colony of critters.
|
||||
Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var coords = _entityManager.GetComponent<TransformComponent>(location.Owner);
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
|
||||
|
||||
_entityManager.SpawnEntity(spawnChoice, coords.Coordinates);
|
||||
EntityManager.SpawnEntity(spawnChoice, coords.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
using Content.Server.Chat;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Server.Zombies;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
@@ -12,62 +6,35 @@ namespace Content.Server.StationEvents.Events
|
||||
/// <summary>
|
||||
/// Revives several dead entities as zombies
|
||||
/// </summary>
|
||||
public sealed class ZombieOutbreak : StationEvent
|
||||
public sealed class ZombieOutbreak : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
||||
|
||||
public override string Name => "ZombieOutbreak";
|
||||
public override int EarliestStart => 50;
|
||||
public override float Weight => WeightLow / 2;
|
||||
public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/bloblarm.ogg");
|
||||
protected override float EndAfter => 1.0f;
|
||||
public override int? MaxOccurrences => 1;
|
||||
public override bool AnnounceEvent => false;
|
||||
public override string Prototype => "ZombieOutbreak";
|
||||
|
||||
/// <summary>
|
||||
/// Finds 1-3 random, dead entities accross the station
|
||||
/// Finds 1-3 random, dead entities across the station
|
||||
/// and turns them into zombies.
|
||||
/// </summary>
|
||||
public override void Startup()
|
||||
public override void Started()
|
||||
{
|
||||
base.Startup();
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
base.Started();
|
||||
List<MobStateComponent> deadList = new();
|
||||
foreach (var mobState in _entityManager.EntityQuery<MobStateComponent>())
|
||||
foreach (var mobState in EntityManager.EntityQuery<MobStateComponent>())
|
||||
{
|
||||
if (mobState.IsDead() || mobState.IsCritical())
|
||||
deadList.Add(mobState);
|
||||
}
|
||||
_random.Shuffle(deadList);
|
||||
RobustRandom.Shuffle(deadList);
|
||||
|
||||
var toInfect = _random.Next(1, 3);
|
||||
|
||||
var zombifysys = _entityManager.EntitySysManager.GetEntitySystem<ZombifyOnDeathSystem>();
|
||||
|
||||
// Now we give it to people in the list of dead entities earlier.
|
||||
var entSysMgr = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var stationSystem = entSysMgr.GetEntitySystem<StationSystem>();
|
||||
var chatSystem = entSysMgr.GetEntitySystem<ChatSystem>();
|
||||
var toInfect = RobustRandom.Next(1, 3);
|
||||
|
||||
foreach (var target in deadList)
|
||||
{
|
||||
if (toInfect-- == 0)
|
||||
break;
|
||||
|
||||
zombifysys.ZombifyEntity(target.Owner);
|
||||
|
||||
var station = stationSystem.GetOwningStation(target.Owner);
|
||||
if(station == null) continue;
|
||||
stationsToNotify.Add((EntityUid) station);
|
||||
}
|
||||
|
||||
if (!AnnounceEvent)
|
||||
return;
|
||||
foreach (var station in stationsToNotify)
|
||||
{
|
||||
chatSystem.DispatchStationAnnouncement(station, Loc.GetString("station-event-zombie-outbreak-announcement"),
|
||||
playDefaultSound: false, colorOverride: Color.DarkMagenta);
|
||||
_zombify.ZombifyEntity(target.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Content.Server.StationEvents
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
public sealed class StationEventCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "events";
|
||||
public string Description => Loc.GetString("cmd-events-desc");
|
||||
public string Help => Loc.GetString("cmd-events-help");
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player as IPlayerSession;
|
||||
if (!args.Any())
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-wrong-arguments-number") + $"\n{Help}");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args.First())
|
||||
{
|
||||
case "list":
|
||||
List(shell, player);
|
||||
break;
|
||||
case "running":
|
||||
Running(shell, player);
|
||||
break;
|
||||
// Didn't use a "toggle" so it's explicit
|
||||
case "pause":
|
||||
Pause(shell, player);
|
||||
break;
|
||||
case "resume":
|
||||
Resume(shell, player);
|
||||
break;
|
||||
case "stop":
|
||||
Stop(shell, player);
|
||||
break;
|
||||
case "run":
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||
("properAmount", 2),
|
||||
("currentAmount", args.Length))
|
||||
+ $"\n{Help}");
|
||||
break;
|
||||
}
|
||||
|
||||
Run(shell, player, args[1]);
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine(Loc.GetString($"shell-invalid-command-specific.", ("commandName", "events")) + $"\n{Help}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Run(IConsoleShell shell, IPlayerSession? player, string eventName)
|
||||
{
|
||||
var stationSystem = EntitySystem.Get<StationEventSystem>();
|
||||
|
||||
var resultText = eventName == "random"
|
||||
? stationSystem.RunRandomEvent()
|
||||
: stationSystem.RunEvent(eventName);
|
||||
|
||||
shell.WriteLine(resultText);
|
||||
}
|
||||
|
||||
private void Running(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var eventName = EntitySystem.Get<StationEventSystem>().CurrentEvent?.Name;
|
||||
if (!string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
shell.WriteLine(eventName);
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-events-none-running"));
|
||||
}
|
||||
}
|
||||
|
||||
private void List(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var events = EntitySystem.Get<StationEventSystem>();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(Loc.GetString("cmd-events-list-random"));
|
||||
|
||||
foreach (var stationEvents in events.StationEvents)
|
||||
{
|
||||
sb.AppendLine(stationEvents.Name);
|
||||
}
|
||||
|
||||
shell.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
private void Pause(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var stationEventSystem = EntitySystem.Get<StationEventSystem>();
|
||||
|
||||
if (!stationEventSystem.Enabled)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-events-already-paused"));
|
||||
}
|
||||
else
|
||||
{
|
||||
stationEventSystem.Enabled = false;
|
||||
shell.WriteLine(Loc.GetString("cmd-events-paused"));
|
||||
}
|
||||
}
|
||||
|
||||
private void Resume(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var stationEventSystem = EntitySystem.Get<StationEventSystem>();
|
||||
|
||||
if (stationEventSystem.Enabled)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-events-already-running"));
|
||||
}
|
||||
else
|
||||
{
|
||||
stationEventSystem.Enabled = true;
|
||||
shell.WriteLine(Loc.GetString("cmd-events-resumed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void Stop(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var resultText = EntitySystem.Get<StationEventSystem>().StopEvent();
|
||||
shell.WriteLine(resultText);
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var options = new[]
|
||||
{
|
||||
"list",
|
||||
"running",
|
||||
"pause",
|
||||
"resume",
|
||||
"stop",
|
||||
"run"
|
||||
};
|
||||
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-events-arg-subcommand"));
|
||||
}
|
||||
|
||||
var command = args[0];
|
||||
|
||||
if (args.Length != 2)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
if (command == "run")
|
||||
{
|
||||
var system = EntitySystem.Get<StationEventSystem>();
|
||||
var options = new[] { "random" }.Concat(
|
||||
system.StationEvents.Select(e => e.Name).OrderBy(e => e));
|
||||
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-events-arg-run-eventName"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.StationEvents;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Server.StationEvents
|
||||
{
|
||||
[UsedImplicitly]
|
||||
// Somewhat based off of TG's implementation of events
|
||||
public sealed class StationEventSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IConGroupController _conGroupController = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
public StationEvent? CurrentEvent { get; private set; }
|
||||
public IReadOnlyCollection<StationEvent> StationEvents => _stationEvents;
|
||||
|
||||
private readonly List<StationEvent> _stationEvents = new();
|
||||
|
||||
private const float MinimumTimeUntilFirstEvent = 300;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the next check for an event runs
|
||||
/// </summary>
|
||||
/// Default value is how long until first event is allowed
|
||||
private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Whether random events can run
|
||||
/// </summary>
|
||||
/// If disabled while an event is running (even if admin run) it will disable it
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = value;
|
||||
CurrentEvent?.Shutdown();
|
||||
CurrentEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Admins can forcibly run events by passing in the Name
|
||||
/// </summary>
|
||||
/// <param name="name">The exact string for Name, without localization</param>
|
||||
/// <returns></returns>
|
||||
public string RunEvent(string name)
|
||||
{
|
||||
_adminLogger.Add(LogType.EventRan, LogImpact.High, $"Event run: {name}");
|
||||
|
||||
// Could use a dictionary but it's such a minor thing, eh.
|
||||
// Wasn't sure on whether to localize this given it's a command
|
||||
var upperName = name.ToUpperInvariant();
|
||||
|
||||
foreach (var stationEvent in _stationEvents)
|
||||
{
|
||||
if (stationEvent.Name.ToUpperInvariant() != upperName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CurrentEvent?.Shutdown();
|
||||
CurrentEvent = stationEvent;
|
||||
stationEvent.Announce();
|
||||
return Loc.GetString("station-event-system-run-event", ("eventName", stationEvent.Name));
|
||||
}
|
||||
|
||||
// I had string interpolation but lord it made it hard to read
|
||||
return Loc.GetString("station-event-system-run-event-no-event-name", ("eventName", name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly run a valid event <b>immediately</b>, ignoring earlieststart
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string RunRandomEvent()
|
||||
{
|
||||
var randomEvent = PickRandomEvent();
|
||||
|
||||
if (randomEvent == null)
|
||||
{
|
||||
return Loc.GetString("station-event-system-run-random-event-no-valid-events");
|
||||
}
|
||||
|
||||
CurrentEvent?.Shutdown();
|
||||
CurrentEvent = randomEvent;
|
||||
CurrentEvent.Startup();
|
||||
|
||||
return Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly picks a valid event.
|
||||
/// </summary>
|
||||
public StationEvent? PickRandomEvent()
|
||||
{
|
||||
var availableEvents = AvailableEvents(true);
|
||||
return FindEvent(availableEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Admins can stop the currently running event (if applicable) and reset the timer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string StopEvent()
|
||||
{
|
||||
string resultText;
|
||||
|
||||
if (CurrentEvent == null)
|
||||
{
|
||||
resultText = Loc.GetString("station-event-system-stop-event-no-running-event");
|
||||
}
|
||||
else
|
||||
{
|
||||
resultText = Loc.GetString("station-event-system-stop-event", ("eventName", CurrentEvent.Name));
|
||||
CurrentEvent.Shutdown();
|
||||
CurrentEvent = null;
|
||||
}
|
||||
|
||||
ResetTimer();
|
||||
return resultText;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactory>();
|
||||
|
||||
foreach (var type in reflectionManager.GetAllChildren(typeof(StationEvent)))
|
||||
{
|
||||
if (type.IsAbstract) continue;
|
||||
|
||||
var stationEvent = (StationEvent) typeFactory.CreateInstance(type);
|
||||
IoCManager.InjectDependencies(stationEvent);
|
||||
_stationEvents.Add(stationEvent);
|
||||
}
|
||||
|
||||
// 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, value => Enabled = value, true);
|
||||
|
||||
_netManager.RegisterNetMessage<MsgRequestStationEvents>(RxRequest);
|
||||
_netManager.RegisterNetMessage<MsgStationEvents>();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
}
|
||||
|
||||
private void RxRequest(MsgRequestStationEvents msg)
|
||||
{
|
||||
if (_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var player))
|
||||
SendEvents(player);
|
||||
}
|
||||
|
||||
private void SendEvents(IPlayerSession player)
|
||||
{
|
||||
if (!_conGroupController.CanCommand(player, "events"))
|
||||
return;
|
||||
|
||||
var newMsg = new MsgStationEvents();
|
||||
newMsg.Events = StationEvents.Select(e => e.Name).ToArray();
|
||||
_netManager.ServerSendMessage(newMsg, player.ConnectedClient);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!Enabled && CurrentEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop events from happening in lobby and force active event to end if the round ends
|
||||
if (Get<GameTicker>().RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
if (CurrentEvent != null)
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep running the current event
|
||||
if (CurrentEvent != null)
|
||||
{
|
||||
CurrentEvent.Update(frameTime);
|
||||
|
||||
// Shutdown the event and set the timer for the next event
|
||||
if (!CurrentEvent.Running)
|
||||
{
|
||||
CurrentEvent.Shutdown();
|
||||
CurrentEvent = null;
|
||||
ResetTimer();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we only count down when no event is running.
|
||||
if (_timeUntilNextEvent > 0 && CurrentEvent == null)
|
||||
{
|
||||
_timeUntilNextEvent -= frameTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// No point hammering this trying to find events if none are available
|
||||
var stationEvent = FindEvent(AvailableEvents());
|
||||
if (stationEvent == null)
|
||||
{
|
||||
ResetTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentEvent = stationEvent;
|
||||
CurrentEvent.Announce();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the event timer once the event is done.
|
||||
/// </summary>
|
||||
private void ResetTimer()
|
||||
{
|
||||
// 5 - 15 minutes. TG does 3-10 but that's pretty frequent
|
||||
_timeUntilNextEvent = _random.Next(300, 900);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a random event from the available events at this time, also considering their weightings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private StationEvent? FindEvent(List<StationEvent> 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<StationEvent> 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<StationEvent>();
|
||||
|
||||
foreach (var stationEvent in _stationEvents)
|
||||
{
|
||||
if (CanRun(stationEvent, playerCount, currentTime))
|
||||
{
|
||||
result.Add(stationEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool CanRun(StationEvent stationEvent, int playerCount, TimeSpan currentTime)
|
||||
{
|
||||
if (stationEvent.MaxOccurrences.HasValue && stationEvent.Occurrences >= stationEvent.MaxOccurrences.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (playerCount < stationEvent.MinimumPlayers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTime != TimeSpan.Zero && currentTime.TotalMinutes < stationEvent.EarliestStart)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stationEvent.LastRun != TimeSpan.Zero && currentTime.TotalMinutes <
|
||||
stationEvent.ReoccurrenceDelay + stationEvent.LastRun.TotalMinutes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CurrentEvent?.Shutdown();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
if (CurrentEvent?.Running == true)
|
||||
{
|
||||
CurrentEvent.Shutdown();
|
||||
CurrentEvent = null;
|
||||
}
|
||||
|
||||
foreach (var stationEvent in _stationEvents)
|
||||
{
|
||||
stationEvent.Occurrences = 0;
|
||||
stationEvent.LastRun = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
_timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.StationEvents
|
||||
{
|
||||
public sealed class MsgRequestStationEvents : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.StationEvents
|
||||
{
|
||||
public sealed class MsgStationEvents : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||
|
||||
public string[] Events = Array.Empty<string>();
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
serializer.DeserializeDirect(stream, out Events);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
serializer.SerializeDirect(stream, Events);
|
||||
buffer.WriteVariableInt32((int)stream.Length);
|
||||
stream.TryGetBuffer(out var segment);
|
||||
buffer.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
station-events-window-not-loaded-text = Not loaded
|
||||
station-events-window-random-text = Random
|
||||
@@ -1,2 +1,2 @@
|
||||
station-event-meteor-swarm-start-announcement = Meteors are on a collision course with the station. Brace for impact.
|
||||
station-event-meteor-swarm-ebd-announcement = The meteor swarm has passed. Please return to your stations.
|
||||
station-event-meteor-swarm-end-announcement = The meteor swarm has passed. Please return to your stations.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
### Localization for events console commands
|
||||
|
||||
## 'events' command
|
||||
cmd-events-desc = Provides admin control to station events
|
||||
cmd-events-help = events <running/list/pause/resume/stop/run <eventName/random>>
|
||||
running: return the current running event
|
||||
list: return all event names that can be run
|
||||
pause: stop all random events from running and any one currently running
|
||||
resume: allow random events to run again
|
||||
run <eventName/random>: start a particular event now; <eventName> is case-insensitive and not localized
|
||||
cmd-events-arg-subcommand = <subcommand>
|
||||
cmd-events-arg-run-eventName = <eventName>
|
||||
|
||||
cmd-events-none-running = No station event running
|
||||
cmd-events-list-random = Random
|
||||
cmd-events-paused = Station events paused
|
||||
cmd-events-already-paused = Station events are already paused
|
||||
cmd-events-resumed = Station events resumed
|
||||
cmd-events-already-running = Station events are already running
|
||||
@@ -1,7 +1,4 @@
|
||||
## StationEventSystem
|
||||
## BasicStationEventSchedulerSystem
|
||||
|
||||
station-event-system-run-event = Running event {$eventName}
|
||||
station-event-system-run-event-no-event-name = No event named: {$eventName}
|
||||
station-event-system-run-random-event-no-valid-events = No valid events available
|
||||
station-event-system-stop-event-no-running-event = No event running currently
|
||||
station-event-system-stop-event = Stopped event {$eventName}
|
||||
149
Resources/Prototypes/GameRules/events.yml
Normal file
149
Resources/Prototypes/GameRules/events.yml
Normal file
@@ -0,0 +1,149 @@
|
||||
- type: gameRule
|
||||
id: BreakerFlip
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: BreakerFlip
|
||||
weight: 10
|
||||
endAfter: 1
|
||||
maxOccurrences: 5
|
||||
minimumPlayers: 15
|
||||
|
||||
- type: gameRule
|
||||
id: BureaucraticError
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: BureaucraticError
|
||||
startAnnouncement: station-event-bureaucratic-error-announcement
|
||||
minimumPlayers: 25
|
||||
weight: 5
|
||||
maxOccurrences: 2
|
||||
endAfter: 1
|
||||
|
||||
- type: gameRule
|
||||
id: DiseaseOutbreak
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: DiseaseOutbreak
|
||||
weight: 10
|
||||
endAfter: 1
|
||||
|
||||
- type: gameRule
|
||||
id: FalseAlarm
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: FalseAlarm
|
||||
weight: 15
|
||||
endAfter: 1
|
||||
maxOccurrences: 5
|
||||
|
||||
- type: gameRule
|
||||
id: GasLeak
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: GasLeak
|
||||
startAnnouncement: station-event-gas-leak-start-announcement
|
||||
startAudio:
|
||||
path: /Audio/Announcements/attention.ogg
|
||||
endAnnouncement: station-event-gas-leak-end-announcement
|
||||
earliestStart: 10
|
||||
minimumPlayers: 5
|
||||
weight: 5
|
||||
maxOccurrences: 1
|
||||
startAfter: 20
|
||||
|
||||
- type: gameRule
|
||||
id: KudzuGrowth
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: KudzuGrowth
|
||||
earliestStart: 15
|
||||
minimumPlayers: 15
|
||||
weight: 5
|
||||
maxOccurrences: 2
|
||||
startAfter: 50
|
||||
endAfter: 240
|
||||
|
||||
- type: gameRule
|
||||
id: MeteorSwarm
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: MeteorSwarm
|
||||
earliestStart: 30
|
||||
weight: 5
|
||||
maxOccurrences: 2
|
||||
minimumPlayers: 20
|
||||
startAnnouncement: station-event-meteor-swarm-start-announcement
|
||||
endAnnouncement: station-event-meteor-swarm-end-announcement
|
||||
startAudio:
|
||||
path: /Audio/Announcements/meteors.ogg
|
||||
startAfter: 30
|
||||
|
||||
- type: gameRule
|
||||
id: MouseMigration
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: MouseMigration
|
||||
earliestStart: 30
|
||||
minimumPlayers: 35
|
||||
weightLow: 5
|
||||
maxOccurrences: 1
|
||||
endAfter: 50
|
||||
|
||||
- type: gameRule
|
||||
id: PowerGridCheck
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: PowerGridCheck
|
||||
weight: 10
|
||||
maxOccurrences: 3
|
||||
startAnnouncement: station-event-power-grid-check-start-announcement
|
||||
endAnnouncement: station-event-power-grid-check-end-announcement
|
||||
startAudio:
|
||||
path: /Audio/Announcements/power_off.ogg
|
||||
startAfter: 12
|
||||
|
||||
- type: gameRule
|
||||
id: RandomSentience
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: RandomSentience
|
||||
weight: 10
|
||||
endAfter: 1
|
||||
startAudio:
|
||||
path: /Audio/Announcements/attention.ogg
|
||||
|
||||
- type: gameRule
|
||||
id: VentClog
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: VentClog
|
||||
startAnnouncement: station-event-vent-clog-start-announcement
|
||||
startAudio:
|
||||
path: /Audio/Announcements/attention.ogg
|
||||
earliestStart: 15
|
||||
minimumPlayers: 15
|
||||
weight: 5
|
||||
maxOccurrences: 2
|
||||
startAfter: 50
|
||||
endAfter: 60
|
||||
|
||||
- type: gameRule
|
||||
id: VentCritters
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: VentCritters
|
||||
earliestStart: 15
|
||||
minimumPlayers: 15
|
||||
weight: 5
|
||||
maxOccurrences: 2
|
||||
endAfter: 60
|
||||
|
||||
- type: gameRule
|
||||
id: ZombieOutbreak
|
||||
config:
|
||||
!type:StationEventRuleConfiguration
|
||||
id: ZombieOutbreak
|
||||
earliestStart: 50
|
||||
weight: 2.5
|
||||
endAfter: 1
|
||||
maxOccurrences: 1
|
||||
@@ -65,3 +65,10 @@
|
||||
config:
|
||||
!type:GenericGameRuleConfiguration
|
||||
id: Zombie
|
||||
|
||||
# event schedulers
|
||||
- type: gameRule
|
||||
id: BasicStationEventScheduler
|
||||
config:
|
||||
!type:GenericGameRuleConfiguration
|
||||
id: BasicStationEventScheduler
|
||||
@@ -6,6 +6,8 @@
|
||||
name: extended-title
|
||||
showInVote: false #2boring2vote
|
||||
description: extended-description
|
||||
rules:
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Secret
|
||||
@@ -38,6 +40,7 @@
|
||||
showInVote: false
|
||||
rules:
|
||||
- Traitor
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Suspicion
|
||||
@@ -79,6 +82,7 @@
|
||||
showInVote: false
|
||||
rules:
|
||||
- Nukeops
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Zombie
|
||||
@@ -93,6 +97,7 @@
|
||||
showInVote: false
|
||||
rules:
|
||||
- Zombie
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Pirates
|
||||
@@ -103,3 +108,4 @@
|
||||
showInVote: false
|
||||
rules:
|
||||
- Pirates
|
||||
- BasicStationEventScheduler
|
||||
|
||||
Reference in New Issue
Block a user