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'}"/>
|
<Button Name="LoadGamePrototypeButton" Text="{Loc 'load-game-prototype'}"/>
|
||||||
<cc:UICommandButton Name="LoadBlueprintsButton" Command="loadbp" Text="{Loc 'load-blueprints'}" WindowType="{x:Type abt:LoadBlueprintsWindow}"/>
|
<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: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>
|
</GridContainer>
|
||||||
</Control>
|
</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;
|
||||||
using Content.Client.Parallax.Managers;
|
using Content.Client.Parallax.Managers;
|
||||||
using Content.Client.Preferences;
|
using Content.Client.Preferences;
|
||||||
|
using Content.Client.Radiation;
|
||||||
using Content.Client.Sandbox;
|
using Content.Client.Sandbox;
|
||||||
using Content.Client.Screenshot;
|
using Content.Client.Screenshot;
|
||||||
using Content.Client.Singularity;
|
using Content.Client.Singularity;
|
||||||
using Content.Client.StationEvents;
|
|
||||||
using Content.Client.StationEvents.Managers;
|
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
@@ -192,7 +191,6 @@ namespace Content.Client.Entry
|
|||||||
|
|
||||||
IoCManager.Resolve<IChatManager>().Initialize();
|
IoCManager.Resolve<IChatManager>().Initialize();
|
||||||
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
||||||
IoCManager.Resolve<IStationEventManager>().Initialize();
|
|
||||||
IoCManager.Resolve<EuiManager>().Initialize();
|
IoCManager.Resolve<EuiManager>().Initialize();
|
||||||
IoCManager.Resolve<IVoteManager>().Initialize();
|
IoCManager.Resolve<IVoteManager>().Initialize();
|
||||||
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();
|
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Content.Client.Module;
|
|||||||
using Content.Client.Parallax.Managers;
|
using Content.Client.Parallax.Managers;
|
||||||
using Content.Client.Preferences;
|
using Content.Client.Preferences;
|
||||||
using Content.Client.Screenshot;
|
using Content.Client.Screenshot;
|
||||||
using Content.Client.StationEvents.Managers;
|
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
@@ -37,7 +36,6 @@ namespace Content.Client.IoC
|
|||||||
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
||||||
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
||||||
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
||||||
IoCManager.Register<IStationEventManager, StationEventManager>();
|
|
||||||
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
|
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
|
||||||
IoCManager.Register<EuiManager, EuiManager>();
|
IoCManager.Register<EuiManager, EuiManager>();
|
||||||
IoCManager.Register<IVoteManager, VoteManager>();
|
IoCManager.Register<IVoteManager, VoteManager>();
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.Radiation;
|
using Content.Shared.Radiation;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.StationEvents
|
namespace Content.Client.Radiation
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(SharedRadiationPulseComponent))]
|
[ComponentReference(typeof(SharedRadiationPulseComponent))]
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.StationEvents
|
namespace Content.Client.Radiation
|
||||||
{
|
{
|
||||||
public sealed class RadiationPulseOverlay : Overlay
|
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)
|
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, IMapGridComponent? mapGridComp = null)
|
||||||
{
|
{
|
||||||
var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp);
|
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;
|
return ev.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Server.GameTicking.Rules.Configurations;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
@@ -10,10 +11,26 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
// No duplicates.
|
// No duplicates.
|
||||||
[ViewVariables] private readonly HashSet<GameRulePrototype> _addedGameRules = new();
|
[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();
|
[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()
|
private void InitializeGameRules()
|
||||||
{
|
{
|
||||||
@@ -21,13 +38,15 @@ namespace Content.Server.GameTicking
|
|||||||
_consoleHost.RegisterCommand("addgamerule",
|
_consoleHost.RegisterCommand("addgamerule",
|
||||||
string.Empty,
|
string.Empty,
|
||||||
"addgamerule <rules>",
|
"addgamerule <rules>",
|
||||||
AddGameRuleCommand);
|
AddGameRuleCommand,
|
||||||
|
AddGameRuleCompletions);
|
||||||
|
|
||||||
// End game rule command.
|
// End game rule command.
|
||||||
_consoleHost.RegisterCommand("endgamerule",
|
_consoleHost.RegisterCommand("endgamerule",
|
||||||
string.Empty,
|
string.Empty,
|
||||||
"endgamerule <rules>",
|
"endgamerule <rules>",
|
||||||
EndGameRuleCommand);
|
EndGameRuleCommand,
|
||||||
|
EndGameRuleCompletions);
|
||||||
|
|
||||||
// Clear game rules command.
|
// Clear game rules command.
|
||||||
_consoleHost.RegisterCommand("cleargamerules",
|
_consoleHost.RegisterCommand("cleargamerules",
|
||||||
@@ -49,50 +68,55 @@ namespace Content.Server.GameTicking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartGameRule(GameRulePrototype rule)
|
public void StartGameRule(GameRulePrototype rule)
|
||||||
{
|
{
|
||||||
if (!GameRuleAdded(rule))
|
if (!IsGameRuleAdded(rule))
|
||||||
AddGameRule(rule);
|
AddGameRule(rule);
|
||||||
|
|
||||||
|
_allPreviousGameRules.Add((RoundDuration(), rule));
|
||||||
|
_sawmill.Info($"Started game rule {rule.ID}");
|
||||||
|
|
||||||
if (_startedGameRules.Add(rule))
|
if (_startedGameRules.Add(rule))
|
||||||
RaiseLocalEvent(new GameRuleStartedEvent(rule));
|
RaiseLocalEvent(new GameRuleStartedEvent(rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ends a game rule.
|
/// 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.
|
/// is not separate from this.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rule"></param>
|
/// <param name="rule"></param>
|
||||||
public void EndGameRule(GameRulePrototype rule)
|
public void EndGameRule(GameRulePrototype rule)
|
||||||
{
|
{
|
||||||
if (!GameRuleAdded(rule))
|
if (!IsGameRuleAdded(rule))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_addedGameRules.Remove(rule);
|
_addedGameRules.Remove(rule);
|
||||||
|
_sawmill.Info($"Ended game rule {rule.ID}");
|
||||||
|
|
||||||
if (GameRuleStarted(rule))
|
if (IsGameRuleStarted(rule))
|
||||||
_startedGameRules.Remove(rule);
|
_startedGameRules.Remove(rule);
|
||||||
RaiseLocalEvent(new GameRuleEndedEvent(rule));
|
RaiseLocalEvent(new GameRuleEndedEvent(rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a game rule to the list, but does not
|
/// 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>
|
/// </summary>
|
||||||
public bool AddGameRule(GameRulePrototype rule)
|
public bool AddGameRule(GameRulePrototype rule)
|
||||||
{
|
{
|
||||||
if (!_addedGameRules.Add(rule))
|
if (!_addedGameRules.Add(rule))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
_sawmill.Info($"Added game rule {rule.ID}");
|
||||||
RaiseLocalEvent(new GameRuleAddedEvent(rule));
|
RaiseLocalEvent(new GameRuleAddedEvent(rule));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GameRuleAdded(GameRulePrototype rule)
|
public bool IsGameRuleAdded(GameRulePrototype rule)
|
||||||
{
|
{
|
||||||
return _addedGameRules.Contains(rule);
|
return _addedGameRules.Contains(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GameRuleAdded(string rule)
|
public bool IsGameRuleAdded(string rule)
|
||||||
{
|
{
|
||||||
foreach (var ruleProto in _addedGameRules)
|
foreach (var ruleProto in _addedGameRules)
|
||||||
{
|
{
|
||||||
@@ -103,12 +127,12 @@ namespace Content.Server.GameTicking
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GameRuleStarted(GameRulePrototype rule)
|
public bool IsGameRuleStarted(GameRulePrototype rule)
|
||||||
{
|
{
|
||||||
return _startedGameRules.Contains(rule);
|
return _startedGameRules.Contains(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GameRuleStarted(string rule)
|
public bool IsGameRuleStarted(string rule)
|
||||||
{
|
{
|
||||||
foreach (var ruleProto in _startedGameRules)
|
foreach (var ruleProto in _startedGameRules)
|
||||||
{
|
{
|
||||||
@@ -142,12 +166,19 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
AddGameRule(rule);
|
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)
|
if(RunLevel == GameRunLevel.InRound)
|
||||||
StartGameRule(rule);
|
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)]
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
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)]
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
|
private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -425,6 +425,7 @@ namespace Content.Server.GameTicking
|
|||||||
ClearGameRules();
|
ClearGameRules();
|
||||||
|
|
||||||
_addedGameRules.Clear();
|
_addedGameRules.Clear();
|
||||||
|
_allPreviousGameRules.Clear();
|
||||||
|
|
||||||
// Round restart cleanup event, so entity systems can reset.
|
// Round restart cleanup event, so entity systems can reset.
|
||||||
var ev = new RoundRestartCleanupEvent();
|
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);
|
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started(GameRuleConfiguration _)
|
public override void Started()
|
||||||
{
|
{
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||||
|
|
||||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
_deadCheckTimer = null;
|
_deadCheckTimer = null;
|
||||||
_restartTimer = null;
|
_restartTimer = null;
|
||||||
@@ -64,7 +64,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void RunDelayedCheck()
|
private void RunDelayedCheck()
|
||||||
{
|
{
|
||||||
if (!Enabled || _deadCheckTimer != null)
|
if (!RuleAdded || _deadCheckTimer != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_deadCheckTimer = DeadCheckDelay;
|
_deadCheckTimer = DeadCheckDelay;
|
||||||
@@ -72,7 +72,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
|
// 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!;
|
[Dependency] protected GameTicker GameTicker = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Be sure to check this before doing anything rule-specific.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// When the GameRule prototype with this ID is added, this system will be enabled.
|
/// When the GameRule prototype with this ID is added, this system will be enabled.
|
||||||
@@ -20,6 +26,12 @@ public abstract class GameRuleSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public new abstract string Prototype { get; }
|
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()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -35,7 +47,10 @@ public abstract class GameRuleSystem : EntitySystem
|
|||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (ev.Rule.Configuration.Id != Prototype)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Enabled = true;
|
Configuration = ev.Rule.Configuration;
|
||||||
|
RuleAdded = true;
|
||||||
|
|
||||||
|
Added();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGameRuleStarted(GameRuleStartedEvent ev)
|
private void OnGameRuleStarted(GameRuleStartedEvent ev)
|
||||||
@@ -43,7 +58,9 @@ public abstract class GameRuleSystem : EntitySystem
|
|||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (ev.Rule.Configuration.Id != Prototype)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Started(ev.Rule.Configuration);
|
RuleStarted = true;
|
||||||
|
|
||||||
|
Started();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGameRuleEnded(GameRuleEndedEvent ev)
|
private void OnGameRuleEnded(GameRuleEndedEvent ev)
|
||||||
@@ -51,17 +68,27 @@ public abstract class GameRuleSystem : EntitySystem
|
|||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (ev.Rule.Configuration.Id != Prototype)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Enabled = false;
|
RuleAdded = false;
|
||||||
Ended(ev.Rule.Configuration);
|
RuleStarted = false;
|
||||||
|
Ended();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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>
|
/// <summary>
|
||||||
/// Called when the game rule has ended..
|
/// Called when the game rule has been started.
|
||||||
/// </summary>
|
/// </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);
|
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;
|
return;
|
||||||
InactivityMaxTime = inactivityConfig.InactivityMaxTime;
|
InactivityMaxTime = inactivityConfig.InactivityMaxTime;
|
||||||
RoundEndDelay = inactivityConfig.RoundEndDelay;
|
RoundEndDelay = inactivityConfig.RoundEndDelay;
|
||||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (args.New)
|
switch (args.New)
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
|||||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
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;
|
return;
|
||||||
|
|
||||||
RoundMaxTime = maxTimeRestartConfig.RoundMaxTime;
|
RoundMaxTime = maxTimeRestartConfig.RoundMaxTime;
|
||||||
RoundEndDelay = maxTimeRestartConfig.RoundEndDelay;
|
RoundEndDelay = maxTimeRestartConfig.RoundEndDelay;
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
|||||||
RestartTimer();
|
RestartTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
StopTimer();
|
StopTimer();
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,7 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (args.New)
|
switch (args.New)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnNukeExploded(NukeExplodedEvent ev)
|
private void OnNukeExploded(NukeExplodedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_opsWon = true;
|
_opsWon = true;
|
||||||
@@ -65,7 +65,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ev.AddLine(_opsWon ? Loc.GetString("nukeops-ops-won") : Loc.GetString("nukeops-crew-won"));
|
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)
|
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_aliveNukeops.TryFirstOrNull(x => x.Key.OwnedEntity == ev.Entity, out var op)) 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)
|
private void OnPlayersSpawning(RulePlayerSpawningEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_aliveNukeops.Clear();
|
_aliveNukeops.Clear();
|
||||||
@@ -292,7 +292,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.NukeopsMinPlayers);
|
var minPlayers = _cfg.GetCVar(CCVars.NukeopsMinPlayers);
|
||||||
@@ -311,11 +311,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Started()
|
||||||
public override void Started(GameRuleConfiguration _)
|
|
||||||
{
|
{
|
||||||
_opsWon = false;
|
_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)
|
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Deleted(_pirateShip))
|
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)
|
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
|
||||||
{
|
{
|
||||||
// Forgive me for copy-pasting nukies.
|
// Forgive me for copy-pasting nukies.
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ public sealed class SandboxRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
public override string Prototype => "Sandbox";
|
public override string Prototype => "Sandbox";
|
||||||
|
|
||||||
public override void Started(GameRuleConfiguration _)
|
public override void Started()
|
||||||
{
|
{
|
||||||
_sandbox.IsSandboxEnabled = true;
|
_sandbox.IsSandboxEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
_sandbox.IsSandboxEnabled = false;
|
_sandbox.IsSandboxEnabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ public sealed class SecretRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
public override string Prototype => "Secret";
|
public override string Prototype => "Secret";
|
||||||
|
|
||||||
public override void Started(GameRuleConfiguration _)
|
public override void Started()
|
||||||
{
|
{
|
||||||
PickRule();
|
PickRule();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
// noop
|
// noop
|
||||||
// Preset should already handle it.
|
// Preset should already handle it.
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
|
private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
|
var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
|
||||||
@@ -119,7 +119,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
|
private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minTraitors = _cfg.GetCVar(CCVars.SuspicionMinTraitors);
|
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;
|
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
|
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
|
||||||
EndTime = null;
|
EndTime = null;
|
||||||
@@ -288,7 +288,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void CheckWinConditions()
|
private void CheckWinConditions()
|
||||||
{
|
{
|
||||||
if (!Enabled || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
if (!RuleAdded || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var traitorsAlive = 0;
|
var traitorsAlive = 0;
|
||||||
@@ -457,7 +457,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
|
private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ev.Disallow();
|
ev.Disallow();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var session = ev.Player;
|
var session = ev.Player;
|
||||||
@@ -144,7 +144,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnGhostAttempt(GhostAttemptHandleEvent ev)
|
private void OnGhostAttempt(GhostAttemptHandleEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled || ev.Handled)
|
if (!RuleAdded || ev.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ev.Handled = true;
|
ev.Handled = true;
|
||||||
@@ -181,7 +181,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var lines = new List<string>();
|
var lines = new List<string>();
|
||||||
@@ -200,14 +200,14 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
|||||||
ev.AddLine(string.Join('\n', lines));
|
ev.AddLine(string.Join('\n', lines));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started(GameRuleConfiguration _)
|
public override void Started()
|
||||||
{
|
{
|
||||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
||||||
_restarter.RestartTimer();
|
_restarter.RestartTimer();
|
||||||
_safeToEndRound = true;
|
_safeToEndRound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,16 +49,16 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started(GameRuleConfiguration _) {}
|
public override void Started() {}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration _)
|
public override void Ended()
|
||||||
{
|
{
|
||||||
_traitors.Clear();
|
_traitors.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If the current preset doesn't explicitly contain the traitor game rule, just carry on and remove self.
|
// 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)
|
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var playersPerTraitor = _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
var playersPerTraitor = _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
||||||
@@ -197,7 +197,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var result = Loc.GetString("traitor-round-end-result", ("traitorCount", _traitors.Count));
|
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)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//this is just the general condition thing used for determining the win/lose text
|
//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)
|
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_initialInfectedNames = new();
|
_initialInfectedNames = new();
|
||||||
@@ -127,14 +127,14 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
CheckRoundEnd(ev.Entity);
|
CheckRoundEnd(ev.Entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEntityZombified(EntityZombifiedEvent ev)
|
private void OnEntityZombified(EntityZombifiedEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
CheckRoundEnd(ev.Target);
|
CheckRoundEnd(ev.Target);
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!RuleAdded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
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
|
//this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly
|
||||||
InfectInitialPlayers();
|
InfectInitialPlayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended(GameRuleConfiguration configuration) { }
|
public override void Ended() { }
|
||||||
|
|
||||||
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace Content.Server.Spawners.EntitySystems
|
|||||||
|
|
||||||
foreach (var rule in component.GameRules)
|
foreach (var rule in component.GameRules)
|
||||||
{
|
{
|
||||||
if (!_ticker.GameRuleStarted(rule)) continue;
|
if (!_ticker.IsGameRuleStarted(rule)) continue;
|
||||||
Spawn(component);
|
Spawn(component);
|
||||||
return;
|
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;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class BreakerFlip : StationEvent
|
public sealed class BreakerFlip : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
|
|
||||||
public override string Name => "BreakerFlip";
|
public override string Prototype => "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 void Startup()
|
public override void Added()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Added();
|
||||||
|
|
||||||
var apcSys = EntitySystem.Get<ApcSystem>();
|
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
|
||||||
var allApcs = _entityManager.EntityQuery<ApcComponent>().ToList();
|
ChatSystem.DispatchGlobalAnnouncement(str, playDefaultSound: false, colorOverride: Color.Gold);
|
||||||
var toDisable = Math.Min(_random.Next(3, 7), allApcs.Count);
|
}
|
||||||
|
|
||||||
|
public override void Started()
|
||||||
|
{
|
||||||
|
base.Started();
|
||||||
|
|
||||||
|
var allApcs = EntityQuery<ApcComponent>().ToList();
|
||||||
|
var toDisable = Math.Min(RobustRandom.Next(3, 7), allApcs.Count);
|
||||||
if (toDisable == 0)
|
if (toDisable == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_random.Shuffle(allApcs);
|
RobustRandom.Shuffle(allApcs);
|
||||||
|
|
||||||
for (var i = 0; i < toDisable; i++)
|
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;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class BureaucraticError : StationEvent
|
public sealed class BureaucraticError : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||||
public override string StartAnnouncement =>
|
|
||||||
Loc.GetString("station-event-bureaucratic-error-announcement");
|
|
||||||
public override string Name => "BureaucraticError";
|
|
||||||
|
|
||||||
public override int MinimumPlayers => 25;
|
public override string Prototype => "BureaucraticError";
|
||||||
|
|
||||||
public override float Weight => WeightLow;
|
public override void Started()
|
||||||
|
|
||||||
public override int? MaxOccurrences => 2;
|
|
||||||
|
|
||||||
protected override float EndAfter => 1f;
|
|
||||||
|
|
||||||
public override void Startup()
|
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
var stationSystem = EntitySystem.Get<StationSystem>();
|
|
||||||
var stationJobsSystem = EntitySystem.Get<StationJobsSystem>();
|
if (StationSystem.Stations.Count == 0) return; // No stations
|
||||||
if (stationSystem.Stations.Count == 0) return; // No stations
|
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||||
var chosenStation = _random.Pick(stationSystem.Stations.ToList());
|
var jobList = _stationJobs.GetJobs(chosenStation).Keys.ToList();
|
||||||
var jobList = stationJobsSystem.GetJobs(chosenStation).Keys.ToList();
|
|
||||||
|
|
||||||
// Low chance to completely change up the late-join landscape by closing all positions except infinite slots.
|
// Low chance to completely change up the late-join landscape by closing all positions except infinite slots.
|
||||||
// Lower chance than the /tg/ equivalent of this event.
|
// Lower chance than the /tg/ equivalent of this event.
|
||||||
if (_random.Prob(0.25f))
|
if (RobustRandom.Prob(0.25f))
|
||||||
{
|
{
|
||||||
var chosenJob = _random.PickAndTake(jobList);
|
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||||
stationJobsSystem.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
_stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos.
|
||||||
foreach (var job in jobList)
|
foreach (var job in jobList)
|
||||||
{
|
{
|
||||||
if (stationJobsSystem.IsJobUnlimited(chosenStation, job))
|
if (_stationJobs.IsJobUnlimited(chosenStation, job))
|
||||||
continue;
|
continue;
|
||||||
stationJobsSystem.TrySetJobSlot(chosenStation, job, 0);
|
_stationJobs.TrySetJobSlot(chosenStation, job, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Changing every role is maybe a bit too chaotic so instead change 20-30% of them.
|
// Changing every role is maybe a bit too chaotic so instead change 20-30% of them.
|
||||||
for (var i = 0; i < _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);
|
var chosenJob = RobustRandom.PickAndTake(jobList);
|
||||||
if (stationJobsSystem.IsJobUnlimited(chosenStation, chosenJob))
|
if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob))
|
||||||
continue;
|
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
|
/// Infects a couple people
|
||||||
/// with a random disease that isn't super deadly
|
/// with a random disease that isn't super deadly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DiseaseOutbreak : StationEvent
|
public sealed class DiseaseOutbreak : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
public override string Prototype => "DiseaseOutbreak";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disease prototypes I decided were not too deadly for a random event
|
/// Disease prototypes I decided were not too deadly for a random event
|
||||||
@@ -33,62 +33,43 @@ public sealed class DiseaseOutbreak : StationEvent
|
|||||||
"BirdFlew",
|
"BirdFlew",
|
||||||
"TongueTwister"
|
"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>
|
/// <summary>
|
||||||
/// Finds 2-5 random, alive entities that can host diseases
|
/// Finds 2-5 random, alive entities that can host diseases
|
||||||
/// and gives them a randomly selected disease.
|
/// and gives them a randomly selected disease.
|
||||||
/// They all get the same disease.
|
/// They all get the same disease.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Startup()
|
public override void Started()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
HashSet<EntityUid> stationsToNotify = new();
|
||||||
List<DiseaseCarrierComponent> aliveList = 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())
|
if (!mobState.IsDead())
|
||||||
aliveList.Add(carrier);
|
aliveList.Add(carrier);
|
||||||
}
|
}
|
||||||
_random.Shuffle(aliveList);
|
RobustRandom.Shuffle(aliveList);
|
||||||
/// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
|
||||||
|
|
||||||
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;
|
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
|
// Now we give it to people in the list of living disease carriers earlier
|
||||||
foreach (var target in aliveList)
|
foreach (var target in aliveList)
|
||||||
{
|
{
|
||||||
if (toInfect-- == 0)
|
if (toInfect-- == 0)
|
||||||
break;
|
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;
|
if(station == null) continue;
|
||||||
stationsToNotify.Add((EntityUid) station);
|
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
|
namespace Content.Server.StationEvents.Events
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class FalseAlarm : StationEvent
|
public sealed class FalseAlarm : StationEventSystem
|
||||||
{
|
{
|
||||||
public override string Name => "FalseAlarm";
|
public override string Prototype => "FalseAlarm";
|
||||||
public override float Weight => WeightHigh;
|
|
||||||
protected override float EndAfter => 1.0f;
|
|
||||||
public override int? MaxOccurrences => 5;
|
|
||||||
|
|
||||||
public override void Announce()
|
public override void Started()
|
||||||
{
|
{
|
||||||
var stationEventSystem = EntitySystem.Get<StationEventSystem>();
|
base.Started();
|
||||||
var randomEvent = stationEventSystem.PickRandomEvent();
|
|
||||||
|
|
||||||
if (randomEvent != null)
|
var ev = GetRandomEventUnweighted(PrototypeManager, RobustRandom);
|
||||||
|
|
||||||
|
if (ev.Configuration is not StationEventRuleConfiguration cfg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cfg.StartAnnouncement != null)
|
||||||
{
|
{
|
||||||
StartAnnouncement = randomEvent.StartAnnouncement;
|
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(cfg.StartAnnouncement), playDefaultSound: false, colorOverride: Color.Gold);
|
||||||
StartAudio = randomEvent.StartAudio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Atmos.EntitySystems;
|
||||||
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Server.GameTicking.Rules.Configurations;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
@@ -7,17 +9,11 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
namespace Content.Server.StationEvents.Events
|
||||||
{
|
{
|
||||||
internal sealed class GasLeak : StationEvent
|
internal sealed class GasLeak : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public override string Name => "GasLeak";
|
public override string Prototype => "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");
|
|
||||||
|
|
||||||
private static readonly Gas[] LeakableGases = {
|
private static readonly Gas[] LeakableGases = {
|
||||||
Gas.Miasma,
|
Gas.Miasma,
|
||||||
@@ -25,24 +21,6 @@ namespace Content.Server.StationEvents.Events
|
|||||||
Gas.Tritium,
|
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>
|
/// <summary>
|
||||||
/// Running cooldown of how much time until another leak.
|
/// Running cooldown of how much time until another leak.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,23 +31,18 @@ namespace Content.Server.StationEvents.Events
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float LeakCooldown = 1.0f;
|
private const float LeakCooldown = 1.0f;
|
||||||
|
|
||||||
|
|
||||||
// Event variables
|
// Event variables
|
||||||
|
|
||||||
private EntityUid _targetStation;
|
private EntityUid _targetStation;
|
||||||
|
|
||||||
private EntityUid _targetGrid;
|
private EntityUid _targetGrid;
|
||||||
|
|
||||||
private Vector2i _targetTile;
|
private Vector2i _targetTile;
|
||||||
|
|
||||||
private EntityCoordinates _targetCoords;
|
private EntityCoordinates _targetCoords;
|
||||||
|
|
||||||
private bool _foundTile;
|
private bool _foundTile;
|
||||||
|
|
||||||
private Gas _leakGas;
|
private Gas _leakGas;
|
||||||
|
|
||||||
private float _molesPerSecond;
|
private float _molesPerSecond;
|
||||||
|
|
||||||
private const int MinimumMolesPerSecond = 20;
|
private const int MinimumMolesPerSecond = 20;
|
||||||
|
private float _endAfter = float.MaxValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Don't want to make it too fast to give people time to flee.
|
/// 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 MaximumMolesPerSecond = 50;
|
||||||
|
|
||||||
private const int MinimumGas = 250;
|
private const int MinimumGas = 250;
|
||||||
|
|
||||||
private const int MaximumGas = 1000;
|
private const int MaximumGas = 1000;
|
||||||
|
|
||||||
private const float SparkChance = 0.05f;
|
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.
|
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
|
||||||
if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
|
if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
|
||||||
{
|
{
|
||||||
_foundTile = true;
|
_foundTile = true;
|
||||||
|
|
||||||
_leakGas = _robustRandom.Pick(LeakableGases);
|
_leakGas = RobustRandom.Pick(LeakableGases);
|
||||||
// Was 50-50 on using normal distribution.
|
// Was 50-50 on using normal distribution.
|
||||||
var totalGas = (float) _robustRandom.Next(MinimumGas, MaximumGas);
|
var totalGas = (float) RobustRandom.Next(MinimumGas, MaximumGas);
|
||||||
_molesPerSecond = _robustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
|
||||||
EndAfter = totalGas / _molesPerSecond + StartAfter;
|
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
||||||
Logger.InfoS("stationevents", $"Leaking {totalGas} of {_leakGas} over {EndAfter - StartAfter} seconds at {_targetTile}");
|
_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
|
// 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);
|
base.Update(frameTime);
|
||||||
|
|
||||||
if (!Started || !Running) return;
|
if (!RuleStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Elapsed > _endAfter)
|
||||||
|
{
|
||||||
|
ForceEndSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_timeUntilLeak -= frameTime;
|
_timeUntilLeak -= frameTime;
|
||||||
|
|
||||||
if (_timeUntilLeak > 0f) return;
|
if (_timeUntilLeak > 0f) return;
|
||||||
_timeUntilLeak += LeakCooldown;
|
_timeUntilLeak += LeakCooldown;
|
||||||
|
|
||||||
var atmosphereSystem = _entityManager.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
|
|
||||||
|
|
||||||
if (!_foundTile ||
|
if (!_foundTile ||
|
||||||
_targetGrid == default ||
|
_targetGrid == default ||
|
||||||
_entityManager.Deleted(_targetGrid) ||
|
EntityManager.Deleted(_targetGrid) ||
|
||||||
!atmosphereSystem.IsSimulatedGrid(_targetGrid))
|
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||||
{
|
{
|
||||||
Running = false;
|
ForceEndSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var environment = atmosphereSystem.GetTileMixture(_targetGrid, null, _targetTile, true);
|
var environment = _atmosphere.GetTileMixture(_targetGrid, null, _targetTile, true);
|
||||||
|
|
||||||
environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
|
environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Ended()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Ended();
|
||||||
|
|
||||||
Spark();
|
Spark();
|
||||||
|
|
||||||
@@ -141,25 +118,24 @@ namespace Content.Server.StationEvents.Events
|
|||||||
_targetTile = default;
|
_targetTile = default;
|
||||||
_targetCoords = default;
|
_targetCoords = default;
|
||||||
_leakGas = Gas.Oxygen;
|
_leakGas = Gas.Oxygen;
|
||||||
EndAfter = float.MaxValue;
|
_endAfter = float.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Spark()
|
private void Spark()
|
||||||
{
|
{
|
||||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
if (RobustRandom.NextFloat() <= SparkChance)
|
||||||
if (_robustRandom.NextFloat() <= SparkChance)
|
|
||||||
{
|
{
|
||||||
if (!_foundTile ||
|
if (!_foundTile ||
|
||||||
_targetGrid == default ||
|
_targetGrid == default ||
|
||||||
(!_entityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : _entityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
(!EntityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||||
!atmosphereSystem.IsSimulatedGrid(_targetGrid))
|
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
// 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.
|
// 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);
|
SoundSystem.Play("/Audio/Effects/sparks4.ogg", Filter.Pvs(_targetCoords), _targetCoords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,51 +3,26 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class KudzuGrowth : StationEvent
|
public sealed class KudzuGrowth : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
public override string Prototype => "KudzuGrowth";
|
||||||
[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;
|
|
||||||
|
|
||||||
private EntityUid _targetGrid;
|
private EntityUid _targetGrid;
|
||||||
|
|
||||||
private Vector2i _targetTile;
|
private Vector2i _targetTile;
|
||||||
|
|
||||||
private EntityCoordinates _targetCoords;
|
private EntityCoordinates _targetCoords;
|
||||||
|
|
||||||
public override void Startup()
|
public override void Started()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
|
|
||||||
// Pick a place to plant the kudzu.
|
// 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);
|
EntityManager.SpawnEntity("Kudzu", _targetCoords);
|
||||||
Logger.InfoS("stationevents", $"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
|
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
|
// 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.
|
// will be hunting the non-existent, dangerous plant.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.Projectiles.Components;
|
using Content.Server.Projectiles.Components;
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using Content.Shared.Spawners.Components;
|
using Content.Shared.Spawners.Components;
|
||||||
@@ -7,26 +8,9 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
namespace Content.Server.StationEvents.Events
|
||||||
{
|
{
|
||||||
public sealed class MeteorSwarm : StationEvent
|
public sealed class MeteorSwarm : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
public override string Prototype => "MeteorSwarm";
|
||||||
[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;
|
|
||||||
|
|
||||||
private float _cooldown;
|
private float _cooldown;
|
||||||
|
|
||||||
@@ -46,53 +30,53 @@ namespace Content.Server.StationEvents.Events
|
|||||||
private const float MaxAngularVelocity = 0.25f;
|
private const float MaxAngularVelocity = 0.25f;
|
||||||
private const float MinAngularVelocity = -0.25f;
|
private const float MinAngularVelocity = -0.25f;
|
||||||
|
|
||||||
public override void Startup()
|
public override void Started()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
_waveCounter = RobustRandom.Next(MinimumWaves, MaximumWaves);
|
||||||
_waveCounter = robustRandom.Next(MinimumWaves, MaximumWaves);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Ended()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Ended();
|
||||||
_waveCounter = 0;
|
_waveCounter = 0;
|
||||||
_cooldown = 0f;
|
_cooldown = 0f;
|
||||||
EndAfter = float.MaxValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
if (!Started) return;
|
if (!RuleStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
if (_waveCounter <= 0)
|
if (_waveCounter <= 0)
|
||||||
{
|
{
|
||||||
Running = false;
|
ForceEndSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cooldown -= frameTime;
|
_cooldown -= frameTime;
|
||||||
|
|
||||||
if (_cooldown > 0f) return;
|
if (_cooldown > 0f) return;
|
||||||
|
|
||||||
_waveCounter--;
|
_waveCounter--;
|
||||||
|
|
||||||
_cooldown += (MaximumCooldown - MinimumCooldown) * _robustRandom.NextFloat() + MinimumCooldown;
|
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() + MinimumCooldown;
|
||||||
|
|
||||||
Box2? playableArea = null;
|
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();
|
var aabb = gridBody.GetWorldAABB();
|
||||||
playableArea = playableArea?.Union(aabb) ?? aabb;
|
playableArea = playableArea?.Union(aabb) ?? aabb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playableArea == null)
|
if (playableArea == null)
|
||||||
{
|
{
|
||||||
EndAfter = float.MinValue;
|
ForceEndSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,21 +87,21 @@ namespace Content.Server.StationEvents.Events
|
|||||||
|
|
||||||
for (var i = 0; i < MeteorsPerWave; i++)
|
for (var i = 0; i < MeteorsPerWave; i++)
|
||||||
{
|
{
|
||||||
var angle = new Angle(_robustRandom.NextFloat() * MathF.Tau);
|
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * _robustRandom.NextFloat() + minimumDistance, 0));
|
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||||
var spawnPosition = new MapCoordinates(center + offset, mapId);
|
var spawnPosition = new MapCoordinates(center + offset, mapId);
|
||||||
var meteor = _entityManager.SpawnEntity("MeteorLarge", spawnPosition);
|
var meteor = EntityManager.SpawnEntity("MeteorLarge", spawnPosition);
|
||||||
var physics = _entityManager.GetComponent<PhysicsComponent>(meteor);
|
var physics = EntityManager.GetComponent<PhysicsComponent>(meteor);
|
||||||
physics.BodyStatus = BodyStatus.InAir;
|
physics.BodyStatus = BodyStatus.InAir;
|
||||||
physics.LinearDamping = 0f;
|
physics.LinearDamping = 0f;
|
||||||
physics.AngularDamping = 0f;
|
physics.AngularDamping = 0f;
|
||||||
physics.ApplyLinearImpulse(-offset.Normalized * MeteorVelocity * physics.Mass);
|
physics.ApplyLinearImpulse(-offset.Normalized * MeteorVelocity * physics.Mass);
|
||||||
physics.ApplyAngularImpulse(
|
physics.ApplyAngularImpulse(
|
||||||
// Get a random angular velocity.
|
// Get a random angular velocity.
|
||||||
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * _robustRandom.NextFloat() +
|
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * RobustRandom.NextFloat() +
|
||||||
MinAngularVelocity));
|
MinAngularVelocity));
|
||||||
// TODO: God this disgusts me but projectile needs a refactor.
|
// 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;
|
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
|
public static List<string> SpawnedPrototypeChoices = new List<string>() //we double up for that ez fake probability
|
||||||
{"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
|
{"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
|
||||||
|
|
||||||
public override string Name => "MouseMigration";
|
public override string Prototype => "MouseMigration";
|
||||||
|
|
||||||
public override string? StartAnnouncement =>
|
public override void Started()
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
|
|
||||||
var spawnLocations = _entityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent, TransformComponent>().ToList();
|
||||||
_random.Shuffle(spawnLocations);
|
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++)
|
for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||||
{
|
{
|
||||||
var spawnChoice = _random.Pick(SpawnedPrototypeChoices);
|
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||||
if (_random.Prob(0.01f) || i == 0) //small chance for multiple, but always at least 1
|
if (RobustRandom.Prob(0.01f) || i == 0) //small chance for multiple, but always at least 1
|
||||||
spawnChoice = "SpawnPointGhostRatKing";
|
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
|
namespace Content.Server.StationEvents.Events
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class PowerGridCheck : StationEvent
|
public sealed class PowerGridCheck : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
public override string Prototype => "PowerGridCheck";
|
||||||
[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;
|
|
||||||
|
|
||||||
private CancellationTokenSource? _announceCancelToken;
|
private CancellationTokenSource? _announceCancelToken;
|
||||||
|
|
||||||
@@ -37,34 +25,44 @@ namespace Content.Server.StationEvents.Events
|
|||||||
private int _numberPerSecond = 0;
|
private int _numberPerSecond = 0;
|
||||||
private float UpdateRate => 1.0f / _numberPerSecond;
|
private float UpdateRate => 1.0f / _numberPerSecond;
|
||||||
private float _frameTimeAccumulator = 0.0f;
|
private float _frameTimeAccumulator = 0.0f;
|
||||||
|
private float _endAfter = 0.0f;
|
||||||
|
|
||||||
public override void Announce()
|
public override void Added()
|
||||||
{
|
{
|
||||||
base.Announce();
|
base.Added();
|
||||||
EndAfter = IoCManager.Resolve<IRobustRandom>().Next(60, 120);
|
_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)
|
if (!component.PowerDisabled)
|
||||||
_powered.Add(component.Owner);
|
_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.
|
_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)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
_frameTimeAccumulator += frameTime;
|
|
||||||
|
if (!RuleStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Elapsed > _endAfter)
|
||||||
|
{
|
||||||
|
ForceEndSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var updates = 0;
|
var updates = 0;
|
||||||
|
_frameTimeAccumulator += frameTime;
|
||||||
if (_frameTimeAccumulator > UpdateRate)
|
if (_frameTimeAccumulator > UpdateRate)
|
||||||
{
|
{
|
||||||
updates = (int) (_frameTimeAccumulator / UpdateRate);
|
updates = (int) (_frameTimeAccumulator / UpdateRate);
|
||||||
@@ -77,8 +75,8 @@ namespace Content.Server.StationEvents.Events
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
var selected = _powered.Pop();
|
var selected = _powered.Pop();
|
||||||
if (_entityManager.Deleted(selected)) continue;
|
if (EntityManager.Deleted(selected)) continue;
|
||||||
if (_entityManager.TryGetComponent<ApcPowerReceiverComponent>(selected, out var powerReceiverComponent))
|
if (EntityManager.TryGetComponent<ApcPowerReceiverComponent>(selected, out var powerReceiverComponent))
|
||||||
{
|
{
|
||||||
powerReceiverComponent.PowerDisabled = true;
|
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)
|
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;
|
powerReceiverComponent.PowerDisabled = false;
|
||||||
}
|
}
|
||||||
@@ -103,11 +101,11 @@ namespace Content.Server.StationEvents.Events
|
|||||||
_announceCancelToken = new CancellationTokenSource();
|
_announceCancelToken = new CancellationTokenSource();
|
||||||
Timer.Spawn(3000, () =>
|
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);
|
}, _announceCancelToken.Token);
|
||||||
_unpowered.Clear();
|
_unpowered.Clear();
|
||||||
|
|
||||||
base.Shutdown();
|
base.Ended();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,26 +9,19 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class RandomSentience : StationEvent
|
public sealed class RandomSentience : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
public override string Prototype => "RandomSentience";
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public override string Name => "RandomSentience";
|
public override void Started()
|
||||||
|
|
||||||
public override float Weight => WeightNormal;
|
|
||||||
|
|
||||||
protected override float EndAfter => 1.0f;
|
|
||||||
|
|
||||||
public override void Startup()
|
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
HashSet<EntityUid> stationsToNotify = new();
|
||||||
|
|
||||||
var targetList = _entityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||||
_random.Shuffle(targetList);
|
RobustRandom.Shuffle(targetList);
|
||||||
|
|
||||||
var toMakeSentient = _random.Next(2, 5);
|
var toMakeSentient = RobustRandom.Next(2, 5);
|
||||||
var groups = new HashSet<string>();
|
var groups = new HashSet<string>();
|
||||||
|
|
||||||
foreach (var target in targetList)
|
foreach (var target in targetList)
|
||||||
@@ -36,10 +29,10 @@ public sealed class RandomSentience : StationEvent
|
|||||||
if (toMakeSentient-- == 0)
|
if (toMakeSentient-- == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
MakeSentientCommand.MakeSentient(target.Owner, _entityManager);
|
MakeSentientCommand.MakeSentient(target.Owner, EntityManager);
|
||||||
_entityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
EntityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
||||||
var comp = _entityManager.AddComponent<GhostTakeoverAvailableComponent>(target.Owner);
|
var comp = EntityManager.AddComponent<GhostTakeoverAvailableComponent>(target.Owner);
|
||||||
comp.RoleName = _entityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
comp.RoleName = EntityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
||||||
comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName));
|
comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName));
|
||||||
groups.Add(target.FlavorKind);
|
groups.Add(target.FlavorKind);
|
||||||
}
|
}
|
||||||
@@ -67,8 +60,8 @@ public sealed class RandomSentience : StationEvent
|
|||||||
(EntityUid) station,
|
(EntityUid) station,
|
||||||
Loc.GetString("station-event-random-sentience-announcement",
|
Loc.GetString("station-event-random-sentience-announcement",
|
||||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||||
("data", Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")),
|
("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")),
|
||||||
("strength", Loc.GetString($"random-sentience-event-strength-{_random.Next(1, 8)}"))),
|
("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}"))),
|
||||||
playDefaultSound: false,
|
playDefaultSound: false,
|
||||||
colorOverride: Color.Gold
|
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;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class VentClog : StationEvent
|
public sealed class VentClog : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
public override string Prototype => "VentClog";
|
||||||
[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 readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
||||||
{
|
{
|
||||||
@@ -41,33 +21,36 @@ public sealed class VentClog : StationEvent
|
|||||||
"Nutriment", "Sugar", "SpaceLube", "Ethanol", "Mercury", "Ephedrine", "WeldingFuel", "VentCrud"
|
"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.
|
// 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)
|
.Where(x => !x.Abstract)
|
||||||
.Select(x => x.ID).ToList();
|
.Select(x => x.ID).ToList();
|
||||||
|
|
||||||
// This is gross, but not much can be done until event refactor, which needs Dynamic.
|
// This is gross, but not much can be done until event refactor, which needs Dynamic.
|
||||||
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
|
var sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
|
||||||
|
|
||||||
foreach (var (_, transform) in _entityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
foreach (var (_, transform) in EntityManager.EntityQuery<GasVentPumpComponent, TransformComponent>())
|
||||||
{
|
{
|
||||||
var solution = new Solution();
|
var solution = new Solution();
|
||||||
|
|
||||||
if (_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
|
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,
|
FoamAreaReactionEffect.SpawnFoam("Foam", transform.Coordinates, solution, RobustRandom.Next(2, 6), 20, 1,
|
||||||
1, sound, _entityManager);
|
1, sound, EntityManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,51 +5,30 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
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>()
|
public static List<string> SpawnedPrototypeChoices = new List<string>()
|
||||||
{"MobGiantSpiderAngry", "MobMouse", "MobMouse1", "MobMouse2"};
|
{"MobGiantSpiderAngry", "MobMouse", "MobMouse1", "MobMouse2"};
|
||||||
|
|
||||||
public override string Name => "VentCritters";
|
public override string Prototype => "VentCritters";
|
||||||
|
|
||||||
public override string? StartAnnouncement =>
|
public override void Started()
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
var spawnChoice = _random.Pick(SpawnedPrototypeChoices);
|
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||||
var spawnLocations = _entityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||||
_random.Shuffle(spawnLocations);
|
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)
|
foreach (var location in spawnLocations)
|
||||||
{
|
{
|
||||||
if (spawnAmount-- == 0)
|
if (spawnAmount-- == 0)
|
||||||
break;
|
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.MobState.Components;
|
||||||
using Content.Shared.Sound;
|
|
||||||
using Content.Server.Zombies;
|
using Content.Server.Zombies;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
namespace Content.Server.StationEvents.Events
|
||||||
@@ -12,62 +6,35 @@ namespace Content.Server.StationEvents.Events
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Revives several dead entities as zombies
|
/// Revives several dead entities as zombies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ZombieOutbreak : StationEvent
|
public sealed class ZombieOutbreak : StationEventSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public override string Name => "ZombieOutbreak";
|
public override string Prototype => "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;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds 1-3 random, dead entities accross the station
|
/// Finds 1-3 random, dead entities across the station
|
||||||
/// and turns them into zombies.
|
/// and turns them into zombies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Startup()
|
public override void Started()
|
||||||
{
|
{
|
||||||
base.Startup();
|
base.Started();
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
|
||||||
List<MobStateComponent> deadList = new();
|
List<MobStateComponent> deadList = new();
|
||||||
foreach (var mobState in _entityManager.EntityQuery<MobStateComponent>())
|
foreach (var mobState in EntityManager.EntityQuery<MobStateComponent>())
|
||||||
{
|
{
|
||||||
if (mobState.IsDead() || mobState.IsCritical())
|
if (mobState.IsDead() || mobState.IsCritical())
|
||||||
deadList.Add(mobState);
|
deadList.Add(mobState);
|
||||||
}
|
}
|
||||||
_random.Shuffle(deadList);
|
RobustRandom.Shuffle(deadList);
|
||||||
|
|
||||||
var toInfect = _random.Next(1, 3);
|
var toInfect = RobustRandom.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>();
|
|
||||||
|
|
||||||
foreach (var target in deadList)
|
foreach (var target in deadList)
|
||||||
{
|
{
|
||||||
if (toInfect-- == 0)
|
if (toInfect-- == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
zombifysys.ZombifyEntity(target.Owner);
|
_zombify.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-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 = 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-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:
|
config:
|
||||||
!type:GenericGameRuleConfiguration
|
!type:GenericGameRuleConfiguration
|
||||||
id: Zombie
|
id: Zombie
|
||||||
|
|
||||||
|
# event schedulers
|
||||||
|
- type: gameRule
|
||||||
|
id: BasicStationEventScheduler
|
||||||
|
config:
|
||||||
|
!type:GenericGameRuleConfiguration
|
||||||
|
id: BasicStationEventScheduler
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
name: extended-title
|
name: extended-title
|
||||||
showInVote: false #2boring2vote
|
showInVote: false #2boring2vote
|
||||||
description: extended-description
|
description: extended-description
|
||||||
|
rules:
|
||||||
|
- BasicStationEventScheduler
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Secret
|
id: Secret
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
showInVote: false
|
showInVote: false
|
||||||
rules:
|
rules:
|
||||||
- Traitor
|
- Traitor
|
||||||
|
- BasicStationEventScheduler
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Suspicion
|
id: Suspicion
|
||||||
@@ -79,6 +82,7 @@
|
|||||||
showInVote: false
|
showInVote: false
|
||||||
rules:
|
rules:
|
||||||
- Nukeops
|
- Nukeops
|
||||||
|
- BasicStationEventScheduler
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Zombie
|
id: Zombie
|
||||||
@@ -93,6 +97,7 @@
|
|||||||
showInVote: false
|
showInVote: false
|
||||||
rules:
|
rules:
|
||||||
- Zombie
|
- Zombie
|
||||||
|
- BasicStationEventScheduler
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Pirates
|
id: Pirates
|
||||||
@@ -103,3 +108,4 @@
|
|||||||
showInVote: false
|
showInVote: false
|
||||||
rules:
|
rules:
|
||||||
- Pirates
|
- Pirates
|
||||||
|
- BasicStationEventScheduler
|
||||||
|
|||||||
Reference in New Issue
Block a user