+++ /dev/null
-using System;
-using Content.Shared.Suspicion;
-using Robust.Shared.GameObjects;
-
-namespace Content.Client.Suspicion
-{
- public sealed class SuspicionEndTimerSystem : EntitySystem
- {
- public TimeSpan? EndTime { get; private set; }
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeNetworkEvent<SuspicionMessages.SetSuspicionEndTimerMessage>(RxTimerMessage);
- }
-
- private void RxTimerMessage(SuspicionMessages.SetSuspicionEndTimerMessage ev)
- {
- EndTime = ev.EndTime;
- }
- }
-}
+++ /dev/null
-<sus:SuspicionGui xmlns="https://spacestation14.io"
- xmlns:sus="clr-namespace:Content.Client.Suspicion">
- <BoxContainer Orientation="Vertical" SeparationOverride="0">
- <Button Name="RoleButton">
- <Label Name="TimerLabel" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
- </Button>
- </BoxContainer>
-</sus:SuspicionGui>
+++ /dev/null
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.Linq;
-using Content.Shared.Popups;
-using Robust.Client.AutoGenerated;
-using Robust.Client.Player;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
-using Robust.Shared.Timing;
-using static Robust.Client.UserInterface.Controls.BaseButton;
-
-namespace Content.Client.Suspicion
-{
- [GenerateTypedNameReferences]
- public sealed partial class SuspicionGui : UIWidget
- {
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
-
- private string? _previousRoleName;
- private bool _previousAntagonist;
-
- public SuspicionGui()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- RoleButton.OnPressed += RoleButtonPressed;
- RoleButton.MinSize = (200, 60);
- }
-
- private void RoleButtonPressed(ButtonEventArgs obj)
- {
- if (!TryGetComponent(out var role))
- {
- return;
- }
-
- if (!role.Antagonist ?? false)
- {
- return;
- }
-
- var allies = string.Join(", ", role.Allies.Select(tuple => tuple.name));
-
- role.Owner.PopupMessage(
- Loc.GetString(
- "suspicion-ally-count-display",
- ("allyCount", role.Allies.Count),
- ("allyNames", allies)
- )
- );
- }
-
- private bool TryGetComponent([NotNullWhen(true)] out SuspicionRoleComponent? suspicion)
- {
- suspicion = default;
- if (_playerManager.LocalPlayer?.ControlledEntity == null)
- {
- return false;
- }
-
- return _entManager.TryGetComponent(_playerManager.LocalPlayer.ControlledEntity, out suspicion);
- }
-
- public void UpdateLabel()
- {
- if (!TryGetComponent(out var suspicion))
- {
- Visible = false;
- return;
- }
-
- if (suspicion.Role == null || suspicion.Antagonist == null)
- {
- Visible = false;
- return;
- }
-
- var endTime = _entManager.System<SuspicionEndTimerSystem>().EndTime;
- if (endTime == null)
- {
- TimerLabel.Visible = false;
- }
- else
- {
- var diff = endTime.Value - _timing.CurTime;
- if (diff < TimeSpan.Zero)
- {
- diff = TimeSpan.Zero;
- }
- TimerLabel.Visible = true;
- TimerLabel.Text = $"{diff:mm\\:ss}";
- }
-
- if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist)
- {
- return;
- }
-
- _previousRoleName = suspicion.Role;
- _previousAntagonist = suspicion.Antagonist.Value;
-
- var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName);
- buttonText = Loc.GetString(buttonText);
-
- RoleButton.Text = buttonText;
- RoleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.LimeGreen;
-
- Visible = true;
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
- UpdateLabel();
- }
- }
-}
+++ /dev/null
-using Content.Shared.Suspicion;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using static Robust.Client.UserInterface.Controls.LayoutContainer;
-
-namespace Content.Client.Suspicion
-{
- [RegisterComponent]
- public sealed class SuspicionRoleComponent : SharedSuspicionRoleComponent
- {
- [Dependency] private readonly IOverlayManager _overlayManager = default!;
- [Dependency] private readonly IResourceCache _resourceCache = default!;
- [Dependency] private readonly IUserInterfaceManager _ui = default!;
-
- private SuspicionGui? _gui;
- private string? _role;
- private bool? _antagonist;
- private bool _overlayActive;
-
- public string? Role
- {
- get => _role;
- set
- {
- if (_role == value)
- {
- return;
- }
-
- _role = value;
- _gui?.UpdateLabel();
- Dirty();
- }
- }
-
- public bool? Antagonist
- {
- get => _antagonist;
- set
- {
- if (_antagonist == value)
- {
- return;
- }
-
- _antagonist = value;
- _gui?.UpdateLabel();
-
- if (value ?? false)
- {
- AddTraitorOverlay();
- }
-
- Dirty();
- }
- }
-
- [ViewVariables]
- public List<(string name, EntityUid uid)> Allies { get; } = new();
-
- private void AddTraitorOverlay()
- {
- if (_overlayManager.HasOverlay<TraitorOverlay>())
- {
- return;
- }
-
- _overlayActive = true;
- var entManager = IoCManager.Resolve<IEntityManager>();
- var overlay = new TraitorOverlay(entManager, IoCManager.Resolve<IPlayerManager>(), _resourceCache, entManager.System<EntityLookupSystem>());
- _overlayManager.AddOverlay(overlay);
- }
-
- private void RemoveTraitorOverlay()
- {
- if (!_overlayActive)
- {
- return;
- }
-
- _overlayManager.RemoveOverlay<TraitorOverlay>();
- }
-
- public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
- {
- base.HandleComponentState(curState, nextState);
-
- if (curState is not SuspicionRoleComponentState state)
- {
- return;
- }
-
- Role = state.Role;
- Antagonist = state.Antagonist;
- Allies.Clear();
- Allies.AddRange(state.Allies);
- }
-
- public void RemoveUI()
- {
- _gui?.Parent?.RemoveChild(_gui);
- RemoveTraitorOverlay();
- }
-
- public void AddUI()
- {
- // TODO move this out of the component
- _gui = _ui.ActiveScreen?.GetOrAddWidget<SuspicionGui>();
- _gui!.UpdateLabel();
- SetAnchorAndMarginPreset(_gui, LayoutPreset.BottomLeft);
-
- if (_antagonist ?? false)
- {
- AddTraitorOverlay();
- }
- }
-
- protected override void OnRemove()
- {
- base.OnRemove();
-
- _gui?.Dispose();
- RemoveTraitorOverlay();
- }
- }
-}
+++ /dev/null
-using Robust.Client.GameObjects;
-
-namespace Content.Client.Suspicion
-{
- sealed class SuspicionRoleSystem : EntitySystem
- {
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<SuspicionRoleComponent, ComponentAdd>((_, component, _) => component.AddUI());
- SubscribeLocalEvent<SuspicionRoleComponent, ComponentRemove>((_, component, _) => component.RemoveUI());
-
- SubscribeLocalEvent<SuspicionRoleComponent, PlayerAttachedEvent>((_, component, _) => component.AddUI());
- SubscribeLocalEvent<SuspicionRoleComponent, PlayerDetachedEvent>((_, component, _) => component.RemoveUI());
- }
- }
-}
+++ /dev/null
-using Content.Shared.Examine;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-using Robust.Client.ResourceManagement;
-using Robust.Shared.Containers;
-using Robust.Shared.Enums;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
-
-namespace Content.Client.Suspicion
-{
- public sealed class TraitorOverlay : Overlay
- {
- private readonly IEntityManager _entityManager;
- private readonly IPlayerManager _playerManager;
- private readonly EntityLookupSystem _lookup;
-
- public override OverlaySpace Space => OverlaySpace.ScreenSpace;
- private readonly Font _font;
-
- private readonly string _traitorText = Loc.GetString("traitor-overlay-traitor-text");
-
- public TraitorOverlay(
- IEntityManager entityManager,
- IPlayerManager playerManager,
- IResourceCache resourceCache,
- EntityLookupSystem lookup)
- {
- _playerManager = playerManager;
- _entityManager = entityManager;
- _lookup = lookup;
-
- _font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
- }
-
- protected override void Draw(in OverlayDrawArgs args)
- {
- var viewport = args.WorldAABB;
-
- var ent = _playerManager.LocalPlayer?.ControlledEntity;
- if (_entityManager.TryGetComponent(ent, out SuspicionRoleComponent? sus) != true)
- {
- return;
- }
-
- foreach (var (_, ally) in sus.Allies)
- {
- // Otherwise the entity can not exist yet
- if (!_entityManager.EntityExists(ally))
- {
- continue;
- }
-
- if (!_entityManager.TryGetComponent(ally, out PhysicsComponent? physics))
- {
- continue;
- }
-
- var allyXform = _entityManager.GetComponent<TransformComponent>(ally);
-
- var entPosition = _entityManager.GetComponent<TransformComponent>(ent.Value).MapPosition;
- var allyPosition = allyXform.MapPosition;
- if (!ExamineSystemShared.InRangeUnOccluded(entPosition, allyPosition, 15,
- entity => entity == ent || entity == ally))
- {
- continue;
- }
-
- // if not on the same map, continue
- if (allyXform.MapID != args.Viewport.Eye!.Position.MapId
- || physics.Owner.IsInContainer())
- {
- continue;
- }
-
- var (allyWorldPos, allyWorldRot) = allyXform.GetWorldPositionRotation();
-
- var worldBox = _lookup.GetWorldAABB(ally, allyXform);
-
- // if not on screen, or too small, continue
- if (!worldBox.Intersects(in viewport) || worldBox.IsEmpty())
- {
- continue;
- }
-
- var screenCoordinates = args.ViewportControl!.WorldToScreen(worldBox.TopLeft + (0, 0.5f));
- args.ScreenHandle.DrawString(_font, screenCoordinates, _traitorText, Color.OrangeRed);
- }
- }
- }
-}
using Content.Server.GameTicking;
using Content.Server.GameTicking.Commands;
using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.CCVar;
using NUnit.Framework;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.IntegrationTests.Tests.GameRules
await using var pairTracker = await PoolManager.GetServerClient();
var server = pairTracker.Pair.Server;
+ var entityManager = server.ResolveDependency<IEntityManager>();
var configManager = server.ResolveDependency<IConfigurationManager>();
await server.WaitPost(() =>
{
command.Execute(null, string.Empty, Array.Empty<string>());
});
-
var sGameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
- var maxTimeMaxTimeRestartRuleSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MaxTimeRestartRuleSystem>();
var sGameTiming = server.ResolveDependency<IGameTiming>();
+
+ sGameTicker.StartGameRule("MaxTimeRestart", out var ruleEntity);
+ Assert.That(entityManager.TryGetComponent<MaxTimeRestartRuleComponent>(ruleEntity, out var maxTime));
+
await server.WaitAssertion(() =>
{
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
-
- sGameTicker.StartGameRule(IoCManager.Resolve<IPrototypeManager>().Index<GameRulePrototype>(maxTimeMaxTimeRestartRuleSystem.Prototype));
- maxTimeMaxTimeRestartRuleSystem.RoundMaxTime = TimeSpan.FromSeconds(3);
-
+ maxTime.RoundMaxTime = TimeSpan.FromSeconds(3);
sGameTicker.StartRound();
});
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
});
- var ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeMaxTimeRestartRuleSystem.RoundMaxTime.TotalSeconds * 1.1f);
+ var ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTime.RoundMaxTime.TotalSeconds * 1.1f);
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
await server.WaitAssertion(() =>
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PostRound));
});
- ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeMaxTimeRestartRuleSystem.RoundEndDelay.TotalSeconds * 1.1f);
+ ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTime.RoundEndDelay.TotalSeconds * 1.1f);
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
await server.WaitAssertion(() =>
-using System;
-using System.Linq;
+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.Prototypes;
namespace Content.IntegrationTests.Tests.GameRules;
var server = pairTracker.Pair.Server;
await server.WaitIdleAsync();
- var protoMan = server.ResolveDependency<IPrototypeManager>();
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
await server.WaitAssertion(() =>
{
- gameTicker.StartGameRule(protoMan.Index<GameRulePrototype>("Secret"));
+ gameTicker.StartGameRule("Secret");
});
// Wait three ticks for any random update loops that might happen
await server.WaitAssertion(() =>
{
- foreach (var rule in gameTicker.AddedGameRules)
+ foreach (var rule in gameTicker.GetAddedGameRules())
{
- Assert.That(gameTicker.StartedGameRules.Contains(rule));
+ Assert.That(gameTicker.GetActiveGameRules().Contains(rule));
}
// End all rules
-using System;
+using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameTicking;
-using Content.Server.GameTicking.Rules;
using Content.Shared.CCVar;
using NUnit.Framework;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
-using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.GameRules;
-
[TestFixture]
public sealed class StartEndGameRulesTest
{
});
var server = pairTracker.Pair.Server;
await server.WaitIdleAsync();
- var protoMan = server.ResolveDependency<IPrototypeManager>();
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.DisableGridFill), Is.False);
await server.WaitAssertion(() =>
{
- var rules = protoMan.EnumeratePrototypes<GameRulePrototype>().ToList();
+ var rules = gameTicker.GetAllGameRulePrototypes().ToList();
rules.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
// Start all rules
foreach (var rule in rules)
{
- gameTicker.StartGameRule(rule);
+ gameTicker.StartGameRule(rule.ID);
}
-
- Assert.That(gameTicker.AddedGameRules, Has.Count.EqualTo(rules.Count));
- Assert.That(gameTicker.AddedGameRules, Has.Count.EqualTo(gameTicker.StartedGameRules.Count));
});
// Wait three ticks for any random update loops that might happen
{
// End all rules
gameTicker.ClearGameRules();
- Assert.That(!gameTicker.AddedGameRules.Any());
+ Assert.That(!gameTicker.GetAddedGameRules().Any());
});
await pairTracker.CleanReturnAsync();
--- /dev/null
+namespace Content.Server.Dragon;
+
+[RegisterComponent]
+public sealed class DragonRuleComponent : Component
+{
+
+}
using System.Linq;
using Content.Server.GameTicking;
-using Content.Server.StationEvents.Components;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Dragon;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
public sealed partial class DragonSystem
{
- public override string Prototype => "Dragon";
-
private int RiftsMet(DragonComponent component)
{
var finished = 0;
return finished;
}
- public override void Started()
+ protected override void Started(EntityUid uid, DragonRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- var spawnLocations = EntityManager.EntityQuery<MapGridComponent, TransformComponent>().ToList();
+ base.Started(uid, component, gameRule, args);
+
+ var spawnLocations = EntityQuery<MapGridComponent, TransformComponent>().ToList();
if (spawnLocations.Count == 0)
return;
Spawn("MobDragon", location.Item2.MapPosition);
}
- public override void Ended()
- {
- return;
- }
-
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
{
- if (!RuleAdded)
- return;
-
var dragons = EntityQuery<DragonComponent>(true).ToList();
if (dragons.Count == 0)
namespace Content.Server.Dragon
{
- public sealed partial class DragonSystem : GameRuleSystem
+ public sealed partial class DragonSystem : GameRuleSystem<DragonRuleComponent>
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
-using Content.Server.GameTicking.Events;
using Content.Server.GameTicking.Presets;
-using Content.Server.GameTicking.Rules;
using Content.Server.Ghost.Components;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using JetBrains.Annotations;
using Robust.Server.Player;
namespace Content.Server.GameTicking
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
- var oldPreset = Preset;
ClearGameRules();
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
AddGamePresetRules();
return prototype != null;
}
+ [PublicAPI]
private bool AddGamePresetRules()
{
if (DummyTicker || Preset == null)
foreach (var rule in Preset.Rules)
{
- if (!_prototypeManager.TryIndex(rule, out GameRulePrototype? ruleProto))
- continue;
-
- AddGameRule(ruleProto);
+ AddGameRule(rule);
}
return true;
private void StartGamePresetRules()
{
// May be touched by the preset during init.
- foreach (var rule in _addedGameRules.ToArray())
+ var rules = new List<EntityUid>(GetAddedGameRules());
+ foreach (var rule in rules)
{
StartGameRule(rule);
}
if (mind.PreventGhosting)
{
- if (mind.Session != null)
- // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
+ if (mind.Session != null) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
+ {
_chatManager.DispatchServerMessage(mind.Session, Loc.GetString("comp-mind-ghosting-prevented"),
true);
+ }
+
return false;
}
using System.Linq;
using Content.Server.Administration;
-using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Administration;
+using Content.Shared.Prototypes;
+using JetBrains.Annotations;
using Robust.Shared.Console;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
-namespace Content.Server.GameTicking
+namespace Content.Server.GameTicking;
+
+public sealed partial class GameTicker
{
- public sealed partial class GameTicker
- {
- // No duplicates.
- [ViewVariables] private readonly HashSet<GameRulePrototype> _addedGameRules = new();
+ [ViewVariables] private readonly List<(TimeSpan, string)> _allPreviousGameRules = new();
- /// <summary>
- /// Holds all currently added game rules.
- /// </summary>
- public IReadOnlySet<GameRulePrototype> AddedGameRules => _addedGameRules;
+ /// <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, string)> AllPreviousGameRules => _allPreviousGameRules;
- [ViewVariables] private readonly HashSet<GameRulePrototype> _startedGameRules = new();
+ private void InitializeGameRules()
+ {
+ // Add game rule command.
+ _consoleHost.RegisterCommand("addgamerule",
+ string.Empty,
+ "addgamerule <rules>",
+ AddGameRuleCommand,
+ AddGameRuleCompletions);
+
+ // End game rule command.
+ _consoleHost.RegisterCommand("endgamerule",
+ string.Empty,
+ "endgamerule <rules>",
+ EndGameRuleCommand,
+ EndGameRuleCompletions);
+
+ // Clear game rules command.
+ _consoleHost.RegisterCommand("cleargamerules",
+ string.Empty,
+ "cleargamerules",
+ ClearGameRulesCommand);
+ }
- /// <summary>
- /// Holds all currently started game rules.
- /// </summary>
- public IReadOnlySet<GameRulePrototype> StartedGameRules => _startedGameRules;
+ private void ShutdownGameRules()
+ {
+ _consoleHost.UnregisterCommand("addgamerule");
+ _consoleHost.UnregisterCommand("endgamerule");
+ _consoleHost.UnregisterCommand("cleargamerules");
+ }
- [ViewVariables] private readonly List<(TimeSpan, GameRulePrototype)> _allPreviousGameRules = new();
+ /// <summary>
+ /// Adds a game rule to the list, but does not
+ /// start it yet, instead waiting until the rule is actually started by other code (usually roundstart)
+ /// </summary>
+ /// <returns>The entity for the added gamerule</returns>
+ public EntityUid AddGameRule(string ruleId)
+ {
+ var ruleEntity = Spawn(ruleId, MapCoordinates.Nullspace);
+ _sawmill.Info($"Added game rule {ToPrettyString(ruleEntity)}");
- /// <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;
+ var ev = new GameRuleAddedEvent(ruleEntity, ruleId);
+ RaiseLocalEvent(ruleEntity, ref ev, true);
+ return ruleEntity;
+ }
- private void InitializeGameRules()
- {
- // Add game rule command.
- _consoleHost.RegisterCommand("addgamerule",
- string.Empty,
- "addgamerule <rules>",
- AddGameRuleCommand,
- AddGameRuleCompletions);
-
- // End game rule command.
- _consoleHost.RegisterCommand("endgamerule",
- string.Empty,
- "endgamerule <rules>",
- EndGameRuleCommand,
- EndGameRuleCompletions);
-
- // Clear game rules command.
- _consoleHost.RegisterCommand("cleargamerules",
- string.Empty,
- "cleargamerules",
- ClearGameRulesCommand);
- }
+ /// <summary>
+ /// Game rules can be 'started' separately from being added. 'Starting' them usually
+ /// happens at round start while they can be added and removed before then.
+ /// </summary>
+ public bool StartGameRule(string ruleId)
+ {
+ return StartGameRule(ruleId, out _);
+ }
- private void ShutdownGameRules()
- {
- _consoleHost.UnregisterCommand("addgamerule");
- _consoleHost.UnregisterCommand("endgamerule");
- _consoleHost.UnregisterCommand("cleargamerules");
- }
+ /// <summary>
+ /// Game rules can be 'started' separately from being added. 'Starting' them usually
+ /// happens at round start while they can be added and removed before then.
+ /// </summary>
+ public bool StartGameRule(string ruleId, out EntityUid ruleEntity)
+ {
+ ruleEntity = AddGameRule(ruleId);
+ return StartGameRule(ruleEntity);
+ }
- /// <summary>
- /// Game rules can be 'started' separately from being added. 'Starting' them usually
- /// happens at round start while they can be added and removed before then.
- /// </summary>
- public void StartGameRule(GameRulePrototype rule)
- {
- if (!IsGameRuleAdded(rule))
- AddGameRule(rule);
+ /// <summary>
+ /// Game rules can be 'started' separately from being added. 'Starting' them usually
+ /// happens at round start while they can be added and removed before then.
+ /// </summary>
+ public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = null)
+ {
+ if (!Resolve(ruleEntity, ref ruleData))
+ ruleData ??= EnsureComp<GameRuleComponent>(ruleEntity);
- _allPreviousGameRules.Add((RoundDuration(), rule));
- _sawmill.Info($"Started game rule {rule.ID}");
+ // can't start an already active rule
+ if (ruleData.Active || ruleData.Ended)
+ return false;
- if (_startedGameRules.Add(rule))
- RaiseLocalEvent(new GameRuleStartedEvent(rule));
- }
+ if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up
+ return false;
- /// <summary>
- /// Ends a game rule.
- /// This always includes removing it (from added game rules) so that behavior
- /// is not separate from this.
- /// </summary>
- /// <param name="rule"></param>
- public void EndGameRule(GameRulePrototype rule)
- {
- if (!IsGameRuleAdded(rule))
- return;
+ _allPreviousGameRules.Add((RoundDuration(), id));
+ _sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}");
- _addedGameRules.Remove(rule);
- _sawmill.Info($"Ended game rule {rule.ID}");
+ ruleData.Active = true;
+ var ev = new GameRuleStartedEvent(ruleEntity, id);
+ RaiseLocalEvent(ruleEntity, ref ev, true);
+ return true;
+ }
- if (IsGameRuleStarted(rule))
- _startedGameRules.Remove(rule);
- RaiseLocalEvent(new GameRuleEndedEvent(rule));
- }
+ /// <summary>
+ /// Ends a game rule.
+ /// </summary>
+ [PublicAPI]
+ public bool EndGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = null)
+ {
+ if (!Resolve(ruleEntity, ref ruleData))
+ return false;
- /// <summary>
- /// Adds a game rule to the list, but does not
- /// start it yet, instead waiting until the rule is actually started by other code (usually roundstart)
- /// </summary>
- public bool AddGameRule(GameRulePrototype rule)
- {
- if (!_addedGameRules.Add(rule))
- return false;
+ // don't end it multiple times
+ if (ruleData.Ended)
+ return false;
- _sawmill.Info($"Added game rule {rule.ID}");
- RaiseLocalEvent(new GameRuleAddedEvent(rule));
- return true;
- }
+ if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up
+ return false;
- public bool IsGameRuleAdded(GameRulePrototype rule)
- {
- return _addedGameRules.Contains(rule);
- }
+ ruleData.Active = false;
+ ruleData.Ended = true;
+ _sawmill.Info($"Ended game rule {ToPrettyString(ruleEntity)}");
- public bool IsGameRuleAdded(string rule)
- {
- foreach (var ruleProto in _addedGameRules)
- {
- if (ruleProto.ID.Equals(rule))
- return true;
- }
+ var ev = new GameRuleEndedEvent(ruleEntity, id);
+ RaiseLocalEvent(ruleEntity, ref ev, true);
+ return true;
+ }
- return false;
- }
+ public bool IsGameRuleAdded(EntityUid ruleEntity, GameRuleComponent? component = null)
+ {
+ return Resolve(ruleEntity, ref component) && !component.Ended;
+ }
- public bool IsGameRuleStarted(GameRulePrototype rule)
+ public bool IsGameRuleAdded(string rule)
+ {
+ foreach (var ruleEntity in GetAddedGameRules())
{
- return _startedGameRules.Contains(rule);
+ if (MetaData(ruleEntity).EntityPrototype?.ID == rule)
+ return true;
}
- public bool IsGameRuleStarted(string rule)
- {
- foreach (var ruleProto in _startedGameRules)
- {
- if (ruleProto.ID.Equals(rule))
- return true;
- }
+ return false;
+ }
- return false;
- }
+ public bool IsGameRuleActive(EntityUid ruleEntity, GameRuleComponent? component = null)
+ {
+ return Resolve(ruleEntity, ref component) && component.Active;
+ }
- public void ClearGameRules()
+ public bool IsGameRuleActive(string rule)
+ {
+ foreach (var ruleEntity in GetActiveGameRules())
{
- foreach (var rule in _addedGameRules.ToArray())
- {
- EndGameRule(rule);
- }
+ if (MetaData(ruleEntity).EntityPrototype?.ID == rule)
+ return true;
}
- #region Command Implementations
+ return false;
+ }
- [AdminCommand(AdminFlags.Fun)]
- private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
+ public void ClearGameRules()
+ {
+ foreach (var rule in GetAddedGameRules())
{
- if (args.Length == 0)
- return;
-
- foreach (var ruleId in args)
- {
- if (!_prototypeManager.TryIndex<GameRulePrototype>(ruleId, out var rule))
- continue;
-
- AddGameRule(rule);
-
- // Start rule if we're already in the middle of a round
- if(RunLevel == GameRunLevel.InRound)
- StartGameRule(rule);
- }
+ EndGameRule(rule);
}
+ }
- private CompletionResult AddGameRuleCompletions(IConsoleShell shell, string[] args)
+ /// <summary>
+ /// Gets all the gamerule entities which are currently active.
+ /// </summary>
+ public IEnumerable<EntityUid> GetAddedGameRules()
+ {
+ var query = EntityQueryEnumerator<GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var ruleData))
{
- var activeIds = _addedGameRules.Select(c => c.ID);
- return CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<GameRulePrototype>().Where(p => !activeIds.Contains(p.Value)),
- "<rule>");
+ if (IsGameRuleAdded(uid, ruleData))
+ yield return uid;
}
+ }
- [AdminCommand(AdminFlags.Fun)]
- private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
+ /// <summary>
+ /// Gets all the gamerule entities which are currently active.
+ /// </summary>
+ public IEnumerable<EntityUid> GetActiveGameRules()
+ {
+ var query = EntityQueryEnumerator<GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var ruleData))
{
- if (args.Length == 0)
- return;
-
- foreach (var ruleId in args)
- {
- if (!_prototypeManager.TryIndex<GameRulePrototype>(ruleId, out var rule))
- continue;
-
- EndGameRule(rule);
- }
+ if (ruleData.Active)
+ yield return uid;
}
+ }
- private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
+ /// <summary>
+ /// Gets all gamerule prototypes
+ /// </summary>
+ public IEnumerable<EntityPrototype> GetAllGameRulePrototypes()
+ {
+ foreach (var proto in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
- return CompletionResult.FromHintOptions(_addedGameRules.Select(c => new CompletionOption(c.ID)),
- "<added rule>");
+ if (proto.Abstract)
+ continue;
+
+ if (proto.HasComponent<GameRuleComponent>())
+ yield return proto;
}
+ }
+
+ #region Command Implementations
+
+ [AdminCommand(AdminFlags.Fun)]
+ private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
+ {
+ if (args.Length == 0)
+ return;
- [AdminCommand(AdminFlags.Fun)]
- private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
+ foreach (var rule in args)
{
- ClearGameRules();
+ var ent = AddGameRule(rule);
+
+ // Start rule if we're already in the middle of a round
+ if(RunLevel == GameRunLevel.InRound)
+ StartGameRule(ent);
}
+ }
- #endregion
+ private CompletionResult AddGameRuleCompletions(IConsoleShell shell, string[] args)
+ {
+ return CompletionResult.FromHintOptions(GetAllGameRulePrototypes().Select(p => p.ID), "<rule>");
}
- /// <summary>
- /// Raised broadcast when a game rule is selected, but not started yet.
- /// </summary>
- public sealed class GameRuleAddedEvent
+ [AdminCommand(AdminFlags.Fun)]
+ private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
{
- public GameRulePrototype Rule { get; }
+ if (args.Length == 0)
+ return;
- public GameRuleAddedEvent(GameRulePrototype rule)
+ foreach (var rule in args)
{
- Rule = rule;
+ if (!EntityUid.TryParse(rule, out var ruleEnt))
+ continue;
+
+ EndGameRule(ruleEnt);
}
}
- public sealed class GameRuleStartedEvent
+ private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
{
- public GameRulePrototype Rule { get; }
+ return CompletionResult.FromHintOptions(GetAddedGameRules().Select(u => u.ToString()), "<added rule>");
+ }
- public GameRuleStartedEvent(GameRulePrototype rule)
- {
- Rule = rule;
- }
+ [AdminCommand(AdminFlags.Fun)]
+ private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
+ {
+ ClearGameRules();
}
- public sealed class GameRuleEndedEvent
+ #endregion
+}
+
+/*
+/// <summary>
+/// Raised broadcast when a game rule is selected, but not started yet.
+/// </summary>
+public sealed class GameRuleAddedEvent
+{
+ public GameRulePrototype Rule { get; }
+
+ public GameRuleAddedEvent(GameRulePrototype rule)
{
- public GameRulePrototype Rule { get; }
+ Rule = rule;
+ }
+}
- public GameRuleEndedEvent(GameRulePrototype rule)
- {
- Rule = rule;
- }
+public sealed class GameRuleStartedEvent
+{
+ public GameRulePrototype Rule { get; }
+
+ public GameRuleStartedEvent(GameRulePrototype rule)
+ {
+ Rule = rule;
+ }
+}
+
+public sealed class GameRuleEndedEvent
+{
+ public GameRulePrototype Rule { get; }
+
+ public GameRuleEndedEvent(GameRulePrototype rule)
+ {
+ Rule = rule;
}
}
+*/
// Clear up any game rules.
ClearGameRules();
- _addedGameRules.Clear();
_allPreviousGameRules.Clear();
// Round restart cleanup event, so entity systems can reset.
-using Content.Server.GameTicking.Rules;
+
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
public string ID { get; } = default!;
[DataField("alias")]
- public string[] Alias { get; } = Array.Empty<string>();
+ public readonly string[] Alias = Array.Empty<string>();
[DataField("name")]
- public string ModeTitle { get; } = "????";
+ public readonly string ModeTitle = "????";
[DataField("description")]
- public string Description { get; } = string.Empty;
+ public readonly string Description = string.Empty;
[DataField("showInVote")]
- public bool ShowInVote { get; } = false;
+ public readonly bool ShowInVote;
[DataField("minPlayers")]
- public int? MinPlayers { get; } = null;
+ public readonly int? MinPlayers;
[DataField("maxPlayers")]
- public int? MaxPlayers { get; } = null;
+ public readonly int? MaxPlayers;
- [DataField("rules", customTypeSerializer:typeof(PrototypeIdListSerializer<GameRulePrototype>))]
+ [DataField("rules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public IReadOnlyList<string> Rules { get; } = Array.Empty<string>();
}
}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Simple GameRule that will do a free-for-all death match.
+/// Kill everybody else to win.
+/// </summary>
+[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
+public sealed class DeathMatchRuleComponent : Component
+{
+ /// <summary>
+ /// How long until the round restarts
+ /// </summary>
+ [DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
+ public float RestartDelay = 10f;
+
+ /// <summary>
+ /// How long after a person dies will the restart be checked
+ /// </summary>
+ [DataField("deadCheckDelay"), ViewVariables(VVAccess.ReadWrite)]
+ public float DeadCheckDelay = 5f;
+
+ /// <summary>
+ /// A timer for checking after a death
+ /// </summary>
+ [DataField("deadCheckTimer"), ViewVariables(VVAccess.ReadWrite)]
+ public float? DeadCheckTimer;
+
+ /// <summary>
+ /// A timer for the restart.
+ /// </summary>
+ [DataField("restartTimer"), ViewVariables(VVAccess.ReadWrite)]
+ public float? RestartTimer;
+}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Component attached to all gamerule entities.
+/// Used to both track the entity as well as store basic data
+/// </summary>
+[RegisterComponent]
+public sealed class GameRuleComponent : Component
+{
+ /// <summary>
+ /// Whether or not the rule is active.
+ /// Is enabled after <see cref="GameRuleStartedEvent"/> and disabled after <see cref="GameRuleEndedEvent"/>
+ /// </summary>
+ [DataField("active")]
+ public bool Active;
+
+ /// <summary>
+ /// Whether or not the gamerule finished.
+ /// Used for tracking whether a non-active gamerule has been started before.
+ /// </summary>
+ [DataField("ended")]
+ public bool Ended;
+}
+
+/// <summary>
+/// Raised when a rule is added but hasn't formally begun yet.
+/// Good for announcing station events and other such things.
+/// </summary>
+[ByRefEvent]
+public readonly record struct GameRuleAddedEvent(EntityUid RuleEntity, string RuleId);
+
+/// <summary>
+/// Raised when the rule actually begins.
+/// Player-facing logic should begin here.
+/// </summary>
+[ByRefEvent]
+public readonly record struct GameRuleStartedEvent(EntityUid RuleEntity, string RuleId);
+
+/// <summary>
+/// Raised when the rule ends.
+/// Do cleanup and other such things here.
+/// </summary>
+[ByRefEvent]
+public readonly record struct GameRuleEndedEvent(EntityUid RuleEntity, string RuleId);
--- /dev/null
+using System.Threading;
+
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Gamerule that ends the round after a period of inactivity.
+/// </summary>
+[RegisterComponent, Access(typeof(InactivityTimeRestartRuleSystem))]
+public sealed class InactivityRuleComponent : Component
+{
+ /// <summary>
+ /// How long the round must be inactive to restart
+ /// </summary>
+ [DataField("inactivityMaxTime", required: true)]
+ public TimeSpan InactivityMaxTime = TimeSpan.FromMinutes(10);
+
+ /// <summary>
+ /// The delay between announcing round end and the lobby.
+ /// </summary>
+ [DataField("roundEndDelay", required: true)]
+ public TimeSpan RoundEndDelay = TimeSpan.FromSeconds(10);
+
+ public CancellationTokenSource TimerCancel = new();
+}
--- /dev/null
+using System.Threading;
+
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
+/// </summary>
+[RegisterComponent]
+public sealed class MaxTimeRestartRuleComponent : Component
+{
+ /// <summary>
+ /// The max amount of time the round can last
+ /// </summary>
+ [DataField("roundMaxTime", required: true)]
+ public TimeSpan RoundMaxTime = TimeSpan.FromMinutes(5);
+
+ /// <summary>
+ /// The amount of time between the round completing and the lobby appearing.
+ /// </summary>
+ [DataField("roundEndDelay", required: true)]
+ public TimeSpan RoundEndDelay = TimeSpan.FromSeconds(10);
+
+ public CancellationTokenSource TimerCancel = new();
+}
/// TODO: Remove once systems can request spawns from the ghost role system directly.
/// </summary>
[RegisterComponent]
-[Access(typeof(NukeopsRuleSystem))]
public sealed class NukeOperativeSpawnerComponent : Component
{
[DataField("name")]
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.StationEvents.Events;
using Content.Shared.Dataset;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Roles;
+using Robust.Server.Player;
using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
+using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
-namespace Content.Server.GameTicking.Rules.Configurations;
+namespace Content.Server.GameTicking.Rules.Components;
-public sealed class NukeopsRuleConfiguration : GameRuleConfiguration
+[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))]
+public sealed class NukeopsRuleComponent : Component
{
- public override string Id => "Nukeops";
-
+ /// <summary>
+ /// The minimum needed amount of players
+ /// </summary>
[DataField("minPlayers")]
public int MinPlayers = 15;
[DataField("spawnOutpost")]
public bool SpawnOutpost = true;
- /// <summary>
- /// Whether or not loneops can spawn. Set to false if a normal nukeops round is occurring.
- /// </summary>
- [DataField("canLoneOpsSpawn")]
- public bool CanLoneOpsSpawn = true;
-
- [DataField("randomHumanoidSettings", customTypeSerializer: typeof(PrototypeIdSerializer<RandomHumanoidSettingsPrototype>))]
- public string RandomHumanoidSettingsPrototype = "NukeOp";
-
[DataField("spawnPointProto", customTypeSerializer: typeof(PrototypeIdSerializer<StartingGearPrototype>))]
public string SpawnPointPrototype = "SpawnPointNukies";
[DataField("greetingSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))]
public SoundSpecifier? GreetSound = new SoundPathSpecifier("/Audio/Misc/nukeops.ogg");
+
+ [DataField("winType")]
+ public WinType WinType = WinType.Neutral;
+
+ [DataField("winConditions")]
+ public List<WinCondition> WinConditions = new ();
+
+ public MapId? NukiePlanet;
+
+ // TODO: use components, don't just cache entity UIDs
+ // There have been (and probably still are) bugs where these refer to deleted entities from old rounds.
+ public EntityUid? NukieOutpost;
+ public EntityUid? NukieShuttle;
+ public EntityUid? TargetStation;
+
+ /// <summary>
+ /// Cached starting gear prototypes.
+ /// </summary>
+ [DataField("startingGearPrototypes")]
+ public readonly Dictionary<string, StartingGearPrototype> StartingGearPrototypes = new ();
+
+ /// <summary>
+ /// Cached operator name prototypes.
+ /// </summary>
+ [DataField("operativeNames")]
+ public readonly Dictionary<string, List<string>> OperativeNames = new();
+
+ /// <summary>
+ /// Data to be used in <see cref="OnMindAdded"/> for an operative once the Mind has been added.
+ /// </summary>
+ [DataField("operativeMindPendingData")]
+ public readonly Dictionary<EntityUid, string> OperativeMindPendingData = new();
+
+ /// <summary>
+ /// Players who played as an operative at some point in the round.
+ /// Stores the session as well as the entity name
+ /// </summary>
+ /// todo: don't store sessions, dingus
+ [DataField("operativePlayers")]
+ public readonly Dictionary<string, IPlayerSession> OperativePlayers = new();
+}
+
+public enum WinType : byte
+{
+ /// <summary>
+ /// Operative major win. This means they nuked the station.
+ /// </summary>
+ OpsMajor,
+ /// <summary>
+ /// Minor win. All nukies were alive at the end of the round.
+ /// Alternatively, some nukies were alive, but the disk was left behind.
+ /// </summary>
+ OpsMinor,
+ /// <summary>
+ /// Neutral win. The nuke exploded, but on the wrong station.
+ /// </summary>
+ Neutral,
+ /// <summary>
+ /// Crew minor win. The nuclear authentication disk escaped on the shuttle,
+ /// but some nukies were alive.
+ /// </summary>
+ CrewMinor,
+ /// <summary>
+ /// Crew major win. This means they either killed all nukies,
+ /// or the bomb exploded too far away from the station, or on the nukie moon.
+ /// </summary>
+ CrewMajor
+}
+
+public enum WinCondition : byte
+{
+ NukeExplodedOnCorrectStation,
+ NukeExplodedOnNukieOutpost,
+ NukeExplodedOnIncorrectLocation,
+ NukeActiveInStation,
+ NukeActiveAtCentCom,
+ NukeDiskOnCentCom,
+ NukeDiskNotOnCentCom,
+ NukiesAbandoned,
+ AllNukiesDead,
+ SomeNukiesAlive,
+ AllNukiesAlive
}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+[RegisterComponent, Access(typeof(PiratesRuleSystem))]
+public sealed class PiratesRuleComponent : Component
+{
+ [ViewVariables]
+ public List<Mind.Mind> Pirates = new();
+ [ViewVariables]
+ public EntityUid PirateShip = EntityUid.Invalid;
+ [ViewVariables]
+ public HashSet<EntityUid> InitialItems = new();
+ [ViewVariables]
+ public double InitialShipValue;
+
+}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+[RegisterComponent, Access(typeof(SandboxRuleSystem))]
+public sealed class SandboxRuleComponent : Component
+{
+
+}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+[RegisterComponent, Access(typeof(SecretRuleSystem))]
+public sealed class SecretRuleComponent : Component
+{
+ /// <summary>
+ /// The gamerules that get added by secret.
+ /// </summary>
+ [DataField("additionalGameRules")]
+ public HashSet<EntityUid> AdditionalGameRules = new();
+}
--- /dev/null
+using Content.Server.Traitor;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Robust.Server.Player;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.GameTicking.Rules.Components;
+
+[RegisterComponent, Access(typeof(TraitorRuleSystem))]
+public sealed class TraitorRuleComponent : Component
+{
+ public readonly SoundSpecifier AddedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
+ public List<TraitorRole> Traitors = new();
+
+ [DataField("traitorPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
+ public string TraitorPrototypeId = "Traitor";
+
+ public int TotalTraitors => Traitors.Count;
+ public string[] Codewords = new string[3];
+
+ public enum SelectionState
+ {
+ WaitingForSpawn = 0,
+ ReadyToSelect = 1,
+ SelectionMade = 2,
+ }
+
+ public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
+ public TimeSpan AnnounceAt = TimeSpan.Zero;
+ public Dictionary<IPlayerSession, HumanoidCharacterProfile> StartCandidates = new();
+}
--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+
+[RegisterComponent, Access(typeof(ZombieRuleSystem))]
+public sealed class ZombieRuleComponent : Component
+{
+ public Dictionary<string, string> InitialInfectedNames = new();
+
+ public string PatientZeroPrototypeID = "InitialInfected";
+ public string InitialZombieVirusPrototype = "PassiveZombieVirus";
+ public const string ZombifySelfActionPrototype = "TurnUndead";
+}
+++ /dev/null
-namespace Content.Server.GameTicking.Rules.Configurations;
-
-/// <summary>
-/// Configures a game rule, providing information like what maps to use or how long to run.
-/// </summary>
-[ImplicitDataDefinitionForInheritors]
-public abstract class GameRuleConfiguration
-{
- /// <summary>
- /// The game rule this configuration is intended for.
- /// </summary>
- public abstract string Id { get; }
-}
+++ /dev/null
-using JetBrains.Annotations;
-
-namespace Content.Server.GameTicking.Rules.Configurations;
-
-/// <summary>
-/// A generic configuration, for game rules that don't have special config data.
-/// </summary>
-[UsedImplicitly]
-public sealed class GenericGameRuleConfiguration : GameRuleConfiguration
-{
- [DataField("id", required: true)]
- private string _id = default!;
- public override string Id => _id;
-}
+++ /dev/null
-using JetBrains.Annotations;
-
-namespace Content.Server.GameTicking.Rules.Configurations;
-
-/// <summary>
-/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
-/// </summary>
-[UsedImplicitly]
-public sealed class InactivityGameRuleConfiguration : GameRuleConfiguration
-{
- public override string Id => "InactivityTimeRestart"; // The value for this in the system isn't static and can't be made static. RIP.
-
- [DataField("inactivityMaxTime", required: true)]
- public TimeSpan InactivityMaxTime { get; }
- [DataField("roundEndDelay", required: true)]
- public TimeSpan RoundEndDelay { get; }
-}
+++ /dev/null
-using JetBrains.Annotations;
-
-namespace Content.Server.GameTicking.Rules.Configurations;
-
-/// <summary>
-/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
-/// </summary>
-[UsedImplicitly]
-public sealed class MaxTimeRestartRuleConfiguration : GameRuleConfiguration
-{
- public override string Id => "MaxTimeRestart"; // The value for this in the system isn't static and can't be made static. RIP.
-
- [DataField("roundMaxTime", required: true)]
- public TimeSpan RoundMaxTime { get; }
- [DataField("roundEndDelay", required: true)]
- public TimeSpan RoundEndDelay { get; }
-}
using Content.Server.Chat.Managers;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Mobs.Components;
namespace Content.Server.GameTicking.Rules;
/// <summary>
-/// Simple GameRule that will do a free-for-all death match.
-/// Kill everybody else to win.
+/// Manages <see cref="DeathMatchRuleComponent"/>
/// </summary>
-public sealed class DeathMatchRuleSystem : GameRuleSystem
+public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponent>
{
- public override string Prototype => "DeathMatch";
-
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
- private const float RestartDelay = 10f;
- private const float DeadCheckDelay = 5f;
-
- private float? _deadCheckTimer = null;
- private float? _restartTimer = null;
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
+ _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
}
- public override void Started()
+ protected override void Started(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
- _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
- public override void Ended()
+ protected override void Ended(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
- _deadCheckTimer = null;
- _restartTimer = null;
+ base.Ended(uid, component, gameRule, args);
+
+ component.DeadCheckTimer = null;
+ component.RestartTimer = null;
- _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
}
private void OnHealthChanged(DamageChangedEvent _)
RunDelayedCheck();
}
- private void OnPlayerStatusChanged(object? _, SessionStatusEventArgs e)
+ private void OnPlayerStatusChanged(object? ojb, SessionStatusEventArgs e)
{
if (e.NewStatus == SessionStatus.Disconnected)
{
private void RunDelayedCheck()
{
- if (!RuleAdded || _deadCheckTimer != null)
- return;
+ var query = EntityQueryEnumerator<DeathMatchRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var deathMatch, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, gameRule) || deathMatch.DeadCheckTimer != null)
+ continue;
- _deadCheckTimer = DeadCheckDelay;
+ deathMatch.DeadCheckTimer = deathMatch.DeadCheckDelay;
+ }
}
- public override void Update(float frameTime)
+ protected override void ActiveTick(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
- if (!RuleAdded)
- return;
+ base.ActiveTick(uid, component, gameRule, frameTime);
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
// TODO: We probably want a sane, centralized round end thingie in GameTicker, RoundEndSystem is no good...
- if (_restartTimer != null)
+ if (component.RestartTimer != null)
{
- _restartTimer -= frameTime;
+ component.RestartTimer -= frameTime;
- if (_restartTimer > 0f)
+ if (component.RestartTimer > 0f)
return;
GameTicker.EndRound();
return;
}
- if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || _deadCheckTimer == null)
+ if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || component.DeadCheckTimer == null)
return;
- _deadCheckTimer -= frameTime;
+ component.DeadCheckTimer -= frameTime;
- if (_deadCheckTimer > 0)
+ if (component.DeadCheckTimer > 0)
return;
- _deadCheckTimer = null;
+ component.DeadCheckTimer = null;
IPlayerSession? winner = null;
foreach (var playerSession in _playerManager.ServerSessions)
{
- if (playerSession.AttachedEntity is not {Valid: true} playerEntity
+ if (playerSession.AttachedEntity is not { Valid: true } playerEntity
|| !TryComp(playerEntity, out MobStateComponent? state))
continue;
_chatManager.DispatchServerAnnouncement(winner == null
? Loc.GetString("rule-death-match-check-winner-stalemate")
- : Loc.GetString("rule-death-match-check-winner",("winner", winner)));
+ : Loc.GetString("rule-death-match-check-winner", ("winner", winner)));
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", RestartDelay)));
- _restartTimer = RestartDelay;
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",
+ ("seconds", component.RestartDelay)));
+ component.RestartTimer = component.RestartDelay;
}
}
-using Content.Server.GameTicking.Rules.Configurations;
-using Robust.Shared.Prototypes;
+
namespace Content.Server.GameTicking.Rules;
+/*
[Prototype("gameRule")]
public sealed class GameRulePrototype : IPrototype
{
[DataField("config", required: true)]
public GameRuleConfiguration Configuration { get; } = default!;
}
+*/
-using Content.Server.GameTicking.Rules.Configurations;
-using JetBrains.Annotations;
+using Content.Server.GameTicking.Rules.Components;
namespace Content.Server.GameTicking.Rules;
-[PublicAPI]
-public abstract class GameRuleSystem : EntitySystem
+public abstract class GameRuleSystem<T> : EntitySystem where T : Component
{
[Dependency] protected GameTicker GameTicker = default!;
- /// <summary>
- /// Whether this GameRule is currently added or not.
- /// Be sure to check this before doing anything rule-specific.
- /// </summary>
- public bool RuleAdded { get; protected set; }
-
- /// <summary>
- /// Whether this game rule has been started after being added.
- /// You probably want to check this before doing any update loop stuff.
- /// </summary>
- public bool RuleStarted { get; protected set; }
-
- /// <summary>
- /// When the GameRule prototype with this ID is added, this system will be enabled.
- /// When it gets removed, this system will be disabled.
- /// </summary>
- public new abstract string Prototype { get; }
-
- /// <summary>
- /// Holds the current configuration after the event has been added.
- /// This should not be getting accessed before the event is enabled, as usual.
- /// </summary>
- public GameRuleConfiguration Configuration = default!;
-
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<GameRuleAddedEvent>(OnGameRuleAdded);
-
- SubscribeLocalEvent<GameRuleStartedEvent>(OnGameRuleStarted);
- SubscribeLocalEvent<GameRuleEndedEvent>(OnGameRuleEnded);
+ SubscribeLocalEvent<T, GameRuleAddedEvent>(OnGameRuleAdded);
+ SubscribeLocalEvent<T, GameRuleStartedEvent>(OnGameRuleStarted);
+ SubscribeLocalEvent<T, GameRuleEndedEvent>(OnGameRuleEnded);
}
- private void OnGameRuleAdded(GameRuleAddedEvent ev)
+ private void OnGameRuleAdded(EntityUid uid, T component, ref GameRuleAddedEvent args)
{
- if (ev.Rule.Configuration.Id != Prototype)
+ if (!TryComp<GameRuleComponent>(uid, out var ruleData))
return;
-
- Configuration = ev.Rule.Configuration;
- RuleAdded = true;
-
- Added();
+ Added(uid, component, ruleData, args);
}
- private void OnGameRuleStarted(GameRuleStartedEvent ev)
+ private void OnGameRuleStarted(EntityUid uid, T component, ref GameRuleStartedEvent args)
{
- if (ev.Rule.Configuration.Id != Prototype)
+ if (!TryComp<GameRuleComponent>(uid, out var ruleData))
return;
-
- RuleStarted = true;
-
- Started();
+ Started(uid, component, ruleData, args);
}
- private void OnGameRuleEnded(GameRuleEndedEvent ev)
+ private void OnGameRuleEnded(EntityUid uid, T component, ref GameRuleEndedEvent args)
{
- if (ev.Rule.Configuration.Id != Prototype)
+ if (!TryComp<GameRuleComponent>(uid, out var ruleData))
return;
+ Ended(uid, component, ruleData, args);
+ }
+
+ /// <summary>
+ /// Called when the gamerule is added
+ /// </summary>
+ protected virtual void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args)
+ {
- RuleAdded = false;
- RuleStarted = false;
- Ended();
}
/// <summary>
- /// Called when the game rule has been added.
- /// You should avoid using this in favor of started--they are not the same thing.
+ /// Called when the gamerule begins
/// </summary>
- /// <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() { }
+ protected virtual void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+
+ }
/// <summary>
- /// Called when the game rule has been started.
+ /// Called when the gamerule ends
/// </summary>
- public abstract void Started();
+ protected virtual void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args)
+ {
+
+ }
/// <summary>
- /// Called when the game rule has ended.
+ /// Called on an active gamerule entity in the Update function
/// </summary>
- public abstract void Ended();
+ protected virtual void ActiveTick(EntityUid uid, T component, GameRuleComponent gameRule, float frameTime)
+ {
+
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<T, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var comp1, out var comp2))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, comp2))
+ continue;
+
+ ActiveTick(uid, comp1, comp2, frameTime);
+ }
+ }
}
using System.Threading;
using Content.Server.Chat.Managers;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Robust.Server.Player;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.GameTicking.Rules;
-public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem
+public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem<InactivityRuleComponent>
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
- public override string Prototype => "InactivityTimeRestart";
-
- private CancellationTokenSource _timerCancel = new();
-
- public TimeSpan InactivityMaxTime { get; set; } = TimeSpan.FromMinutes(10);
- public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
+ _playerManager.PlayerStatusChanged += PlayerStatusChanged;
}
- public override void Started()
+ public override void Shutdown()
{
- if (Configuration is not InactivityGameRuleConfiguration inactivityConfig)
- return;
- InactivityMaxTime = inactivityConfig.InactivityMaxTime;
- RoundEndDelay = inactivityConfig.RoundEndDelay;
- _playerManager.PlayerStatusChanged += PlayerStatusChanged;
+ base.Shutdown();
+ _playerManager.PlayerStatusChanged -= PlayerStatusChanged;
}
- public override void Ended()
+ protected override void Ended(EntityUid uid, InactivityRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
- _playerManager.PlayerStatusChanged -= PlayerStatusChanged;
+ base.Ended(uid, component, gameRule, args);
- StopTimer();
+ StopTimer(uid, component);
}
- public void RestartTimer()
+ public void RestartTimer(EntityUid uid, InactivityRuleComponent? component = null)
{
- _timerCancel.Cancel();
- _timerCancel = new CancellationTokenSource();
- Timer.Spawn(InactivityMaxTime, TimerFired, _timerCancel.Token);
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.TimerCancel.Cancel();
+ component.TimerCancel = new CancellationTokenSource();
+ Timer.Spawn(component.InactivityMaxTime, () => TimerFired(uid, component), component.TimerCancel.Token);
}
- public void StopTimer()
+ public void StopTimer(EntityUid uid, InactivityRuleComponent? component = null)
{
- _timerCancel.Cancel();
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.TimerCancel.Cancel();
}
- private void TimerFired()
+ private void TimerFired(EntityUid uid, InactivityRuleComponent? component = null)
{
+ if (!Resolve(uid, ref component))
+ return;
+
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) RoundEndDelay.TotalSeconds)));
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) component.RoundEndDelay.TotalSeconds)));
- Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
+ Timer.Spawn(component.RoundEndDelay, () => GameTicker.RestartRound());
}
private void RunLevelChanged(GameRunLevelChangedEvent args)
{
- if (!RuleAdded)
- return;
-
- switch (args.New)
+ var query = EntityQueryEnumerator<InactivityRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var inactivity, out var gameRule))
{
- case GameRunLevel.InRound:
- RestartTimer();
- break;
- case GameRunLevel.PreRoundLobby:
- case GameRunLevel.PostRound:
- StopTimer();
- break;
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
+
+ switch (args.New)
+ {
+ case GameRunLevel.InRound:
+ RestartTimer(uid, inactivity);
+ break;
+ case GameRunLevel.PreRoundLobby:
+ case GameRunLevel.PostRound:
+ StopTimer(uid, inactivity);
+ break;
+ }
}
}
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
- if (GameTicker.RunLevel != GameRunLevel.InRound)
- {
- return;
- }
-
- if (_playerManager.PlayerCount == 0)
- {
- RestartTimer();
- }
- else
+ var query = EntityQueryEnumerator<InactivityRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var inactivity, out var gameRule))
{
- StopTimer();
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
+
+ if (GameTicker.RunLevel != GameRunLevel.InRound)
+ {
+ return;
+ }
+
+ if (_playerManager.PlayerCount == 0)
+ {
+ RestartTimer(uid, inactivity);
+ }
+ else
+ {
+ StopTimer(uid, inactivity);
+ }
}
}
}
using System.Threading;
using Content.Server.Chat.Managers;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.GameTicking.Rules;
-public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
+public sealed class MaxTimeRestartRuleSystem : GameRuleSystem<MaxTimeRestartRuleComponent>
{
[Dependency] private readonly IChatManager _chatManager = default!;
- public override string Prototype => "MaxTimeRestart";
-
- private CancellationTokenSource _timerCancel = new();
-
- public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromMinutes(5);
- public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
}
- public override void Started()
+ protected override void Started(EntityUid uid, MaxTimeRestartRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- if (Configuration is not MaxTimeRestartRuleConfiguration maxTimeRestartConfig)
- return;
-
- RoundMaxTime = maxTimeRestartConfig.RoundMaxTime;
- RoundEndDelay = maxTimeRestartConfig.RoundEndDelay;
+ base.Started(uid, component, gameRule, args);
if(GameTicker.RunLevel == GameRunLevel.InRound)
- RestartTimer();
+ RestartTimer(component);
}
- public override void Ended()
+ protected override void Ended(EntityUid uid, MaxTimeRestartRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
- StopTimer();
+ base.Ended(uid, component, gameRule, args);
+
+ StopTimer(component);
}
- public void RestartTimer()
+ public void RestartTimer(MaxTimeRestartRuleComponent component)
{
- _timerCancel.Cancel();
- _timerCancel = new CancellationTokenSource();
- Timer.Spawn(RoundMaxTime, TimerFired, _timerCancel.Token);
+ component.TimerCancel.Cancel();
+ component.TimerCancel = new CancellationTokenSource();
+ Timer.Spawn(component.RoundMaxTime, () => TimerFired(component), component.TimerCancel.Token);
}
- public void StopTimer()
+ public void StopTimer(MaxTimeRestartRuleComponent component)
{
- _timerCancel.Cancel();
+ component.TimerCancel.Cancel();
}
- private void TimerFired()
+ private void TimerFired(MaxTimeRestartRuleComponent component)
{
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) RoundEndDelay.TotalSeconds)));
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) component.RoundEndDelay.TotalSeconds)));
- Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
+ Timer.Spawn(component.RoundEndDelay, () => GameTicker.RestartRound());
}
private void RunLevelChanged(GameRunLevelChangedEvent args)
{
- if (!RuleAdded)
- return;
-
- switch (args.New)
+ var query = EntityQueryEnumerator<MaxTimeRestartRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var timer, out var gameRule))
{
- case GameRunLevel.InRound:
- RestartTimer();
- break;
- case GameRunLevel.PreRoundLobby:
- case GameRunLevel.PostRound:
- StopTimer();
- break;
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
+
+ switch (args.New)
+ {
+ case GameRunLevel.InRound:
+ RestartTimer(timer);
+ break;
+ case GameRunLevel.PreRoundLobby:
+ case GameRunLevel.PostRound:
+ StopTimer(timer);
+ break;
+ }
}
}
}
using Content.Server.Administration.Commands;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.GameTicking.Rules.Configurations;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Events;
using Content.Server.Humanoid;
using Content.Server.Station.Systems;
using Content.Server.Traitor;
using Content.Shared.Dataset;
+using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
namespace Content.Server.GameTicking.Rules;
-public sealed class NukeopsRuleSystem : GameRuleSystem
+public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
- [Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
-
- private enum WinType
- {
- /// <summary>
- /// Operative major win. This means they nuked the station.
- /// </summary>
- OpsMajor,
- /// <summary>
- /// Minor win. All nukies were alive at the end of the round.
- /// Alternatively, some nukies were alive, but the disk was left behind.
- /// </summary>
- OpsMinor,
- /// <summary>
- /// Neutral win. The nuke exploded, but on the wrong station.
- /// </summary>
- Neutral,
- /// <summary>
- /// Crew minor win. The nuclear authentication disk escaped on the shuttle,
- /// but some nukies were alive.
- /// </summary>
- CrewMinor,
- /// <summary>
- /// Crew major win. This means they either killed all nukies,
- /// or the bomb exploded too far away from the station, or on the nukie moon.
- /// </summary>
- CrewMajor
- }
-
- private enum WinCondition
- {
- NukeExplodedOnCorrectStation,
- NukeExplodedOnNukieOutpost,
- NukeExplodedOnIncorrectLocation,
- NukeActiveInStation,
- NukeActiveAtCentCom,
- NukeDiskOnCentCom,
- NukeDiskNotOnCentCom,
- NukiesAbandoned,
- AllNukiesDead,
- SomeNukiesAlive,
- AllNukiesAlive
- }
-
- private WinType _winType = WinType.Neutral;
-
- private WinType RuleWinType
- {
- get => _winType;
- set
- {
- _winType = value;
-
- if (value == WinType.CrewMajor || value == WinType.OpsMajor)
- {
- _roundEndSystem.EndRound();
- }
- }
- }
- private List<WinCondition> _winConditions = new ();
-
- private MapId? _nukiePlanet;
-
- // TODO: use components, don't just cache entity UIDs
- // There have been (and probably still are) bugs where these refer to deleted entities from old rounds.
- private EntityUid? _nukieOutpost;
- private EntityUid? _nukieShuttle;
- private EntityUid? _targetStation;
-
- public override string Prototype => "Nukeops";
-
- private NukeopsRuleConfiguration _nukeopsRuleConfig = new();
-
- /// <summary>
- /// Cached starting gear prototypes.
- /// </summary>
- private readonly Dictionary<string, StartingGearPrototype> _startingGearPrototypes = new ();
-
- /// <summary>
- /// Cached operator name prototypes.
- /// </summary>
- private readonly Dictionary<string, List<string>> _operativeNames = new();
-
- /// <summary>
- /// Data to be used in <see cref="OnMindAdded"/> for an operative once the Mind has been added.
- /// </summary>
- private readonly Dictionary<EntityUid, string> _operativeMindPendingData = new();
-
- /// <summary>
- /// Players who played as an operative at some point in the round.
- /// Stores the session as well as the entity name
- /// </summary>
- private readonly Dictionary<string, IPlayerSession> _operativePlayers = new();
-
-
public override void Initialize()
{
base.Initialize();
private void OnComponentInit(EntityUid uid, NukeOperativeComponent component, ComponentInit args)
{
- // If entity has a prior mind attached, add them to the players list.
- if (!TryComp<MindComponent>(uid, out var mindComponent) || !RuleAdded)
- return;
+ var query = EntityQueryEnumerator<NukeopsRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var ruleEnt, out var nukeops, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleAdded(ruleEnt, gameRule))
+ continue;
+
+ // If entity has a prior mind attached, add them to the players list.
+ if (!TryComp<MindComponent>(uid, out var mindComponent))
+ continue;
- var session = mindComponent.Mind?.Session;
- var name = MetaData(uid).EntityName;
- if (session != null)
- _operativePlayers.Add(name, session);
+ var session = mindComponent.Mind?.Session;
+ var name = MetaData(uid).EntityName;
+ if (session != null)
+ nukeops.OperativePlayers.Add(name, session);
+ }
}
private void OnComponentRemove(EntityUid uid, NukeOperativeComponent component, ComponentRemove args)
private void OnNukeExploded(NukeExplodedEvent ev)
{
- if (!RuleAdded)
- return;
-
- if (ev.OwningStation != null)
+ var query = EntityQueryEnumerator<NukeopsRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var nukeops, out var gameRule))
{
- if (ev.OwningStation == _nukieOutpost)
- {
- _winConditions.Add(WinCondition.NukeExplodedOnNukieOutpost);
- RuleWinType = WinType.CrewMajor;
- return;
- }
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- if (TryComp(_targetStation, out StationDataComponent? data))
+ if (ev.OwningStation != null)
{
- foreach (var grid in data.Grids)
+ if (ev.OwningStation == nukeops.NukieOutpost)
{
- if (grid != ev.OwningStation)
+ nukeops.WinConditions.Add(WinCondition.NukeExplodedOnNukieOutpost);
+ SetWinType(uid, WinType.CrewMajor, nukeops);
+ continue;
+ }
+
+ if (TryComp(nukeops.TargetStation, out StationDataComponent? data))
+ {
+ var correctStation = false;
+ foreach (var grid in data.Grids)
{
- continue;
+ if (grid != ev.OwningStation)
+ {
+ continue;
+ }
+
+ nukeops.WinConditions.Add(WinCondition.NukeExplodedOnCorrectStation);
+ SetWinType(uid, WinType.OpsMajor, nukeops);
+ correctStation = true;
}
- _winConditions.Add(WinCondition.NukeExplodedOnCorrectStation);
- RuleWinType = WinType.OpsMajor;
- return;
+ if (correctStation)
+ continue;
}
+
+ nukeops.WinConditions.Add(WinCondition.NukeExplodedOnIncorrectLocation);
+ }
+ else
+ {
+ nukeops.WinConditions.Add(WinCondition.NukeExplodedOnIncorrectLocation);
}
- _winConditions.Add(WinCondition.NukeExplodedOnIncorrectLocation);
+ _roundEndSystem.EndRound();
}
- else
- {
- _winConditions.Add(WinCondition.NukeExplodedOnIncorrectLocation);
- }
-
- _roundEndSystem.EndRound();
}
private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
{
- switch (ev.New)
+ var query = EntityQueryEnumerator<NukeopsRuleComponent>();
+ while (query.MoveNext(out var uid, out var nukeops))
{
- case GameRunLevel.InRound:
- OnRoundStart();
- break;
- case GameRunLevel.PostRound:
- OnRoundEnd();
- break;
+ switch (ev.New)
+ {
+ case GameRunLevel.InRound:
+ OnRoundStart(uid, nukeops);
+ break;
+ case GameRunLevel.PostRound:
+ OnRoundEnd(uid, nukeops);
+ break;
+ }
}
}
- public void LoadLoneOpsConfig()
- {
- _nukeopsRuleConfig.SpawnOutpost = false;
- _nukeopsRuleConfig.EndsRound = false;
- }
-
+ /// <summary>
+ /// Loneops can only spawn if there is no nukeops active
+ /// </summary>
public bool CheckLoneOpsSpawn()
{
- return _nukeopsRuleConfig.CanLoneOpsSpawn;
+ return !EntityQuery<NukeopsRuleComponent>().Any();
}
- private void OnRoundStart()
+ private void OnRoundStart(EntityUid uid, NukeopsRuleComponent? component = null)
{
+ if (!Resolve(uid, ref component))
+ return;
+
// TODO: This needs to try and target a Nanotrasen station. At the very least,
// we can only currently guarantee that NT stations are the only station to
// exist in the base game.
- _targetStation = _stationSystem.Stations.FirstOrNull();
+ component.TargetStation = _stationSystem.Stations.FirstOrNull();
- if (_targetStation == null)
+ if (component.TargetStation == null)
{
return;
}
var filter = Filter.Empty();
- foreach (var nukie in EntityQuery<NukeOperativeComponent>())
+ var query = EntityQueryEnumerator<NukeOperativeComponent, ActorComponent>();
+ while (query.MoveNext(out _, out _, out var actor))
{
- if (!TryComp<ActorComponent>(nukie.Owner, out var actor))
- {
- continue;
- }
-
- _chatManager.DispatchServerMessage(actor.PlayerSession, Loc.GetString("nukeops-welcome", ("station", _targetStation.Value)));
+ _chatManager.DispatchServerMessage(actor.PlayerSession, Loc.GetString("nukeops-welcome", ("station", component.TargetStation.Value)));
filter.AddPlayer(actor.PlayerSession);
}
- _audioSystem.PlayGlobal(_nukeopsRuleConfig.GreetSound, filter, recordReplay: false);
+ _audioSystem.PlayGlobal(component.GreetSound, filter, recordReplay: false);
}
- private void OnRoundEnd()
+ private void OnRoundEnd(EntityUid uid, NukeopsRuleComponent? component = null)
{
+ if (!Resolve(uid, ref component))
+ return;
+
// If the win condition was set to operative/crew major win, ignore.
- if (RuleWinType == WinType.OpsMajor || RuleWinType == WinType.CrewMajor)
- {
+ if (component.WinType == WinType.OpsMajor || component.WinType == WinType.CrewMajor)
return;
- }
- foreach (var (nuke, nukeTransform) in EntityManager.EntityQuery<NukeComponent, TransformComponent>(true))
+ foreach (var (nuke, nukeTransform) in EntityQuery<NukeComponent, TransformComponent>(true))
{
if (nuke.Status != NukeStatus.ARMED)
- {
continue;
- }
// UH OH
if (nukeTransform.MapID == _emergency.CentComMap)
{
- _winConditions.Add(WinCondition.NukeActiveAtCentCom);
- RuleWinType = WinType.OpsMajor;
+ component.WinConditions.Add(WinCondition.NukeActiveAtCentCom);
+ SetWinType(uid, WinType.OpsMajor, component);
return;
}
- if (nukeTransform.GridUid == null || _targetStation == null)
- {
+ if (nukeTransform.GridUid == null || component.TargetStation == null)
continue;
- }
- if (!TryComp(_targetStation.Value, out StationDataComponent? data))
- {
+ if (!TryComp(component.TargetStation.Value, out StationDataComponent? data))
continue;
- }
foreach (var grid in data.Grids)
{
if (grid != nukeTransform.GridUid)
- {
continue;
- }
- _winConditions.Add(WinCondition.NukeActiveInStation);
- RuleWinType = WinType.OpsMajor;
+ component.WinConditions.Add(WinCondition.NukeActiveInStation);
+ SetWinType(uid, WinType.OpsMajor, component);
return;
}
}
foreach (var (_, state) in EntityQuery<NukeOperativeComponent, MobStateComponent>())
{
if (state.CurrentState is MobState.Alive)
- {
continue;
- }
allAlive = false;
break;
// running away the moment nuke ops appear.
if (allAlive)
{
- RuleWinType = WinType.OpsMinor;
- _winConditions.Add(WinCondition.AllNukiesAlive);
+ SetWinType(uid, WinType.OpsMinor, component);
+ component.WinConditions.Add(WinCondition.AllNukiesAlive);
return;
}
- _winConditions.Add(WinCondition.SomeNukiesAlive);
+ component.WinConditions.Add(WinCondition.SomeNukiesAlive);
var diskAtCentCom = false;
foreach (var (_, transform) in EntityManager.EntityQuery<NukeDiskComponent, TransformComponent>())
// This also implies that some nuclear operatives have died.
if (diskAtCentCom)
{
- RuleWinType = WinType.CrewMinor;
- _winConditions.Add(WinCondition.NukeDiskOnCentCom);
+ SetWinType(uid, WinType.CrewMinor, component);
+ component.WinConditions.Add(WinCondition.NukeDiskOnCentCom);
}
// Otherwise, the nuke ops win.
else
{
- RuleWinType = WinType.OpsMinor;
- _winConditions.Add(WinCondition.NukeDiskNotOnCentCom);
+ SetWinType(uid, WinType.OpsMinor, component);
+ component.WinConditions.Add(WinCondition.NukeDiskNotOnCentCom);
}
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
- if (!RuleAdded)
- return;
-
- var winText = Loc.GetString($"nukeops-{_winType.ToString().ToLower()}");
+ foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
+ {
+ var winText = Loc.GetString($"nukeops-{nukeops.WinType.ToString().ToLower()}");
- ev.AddLine(winText);
+ ev.AddLine(winText);
- foreach (var cond in _winConditions)
- {
- var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}");
+ foreach (var cond in nukeops.WinConditions)
+ {
+ var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}");
- ev.AddLine(text);
- }
+ ev.AddLine(text);
+ }
- ev.AddLine(Loc.GetString("nukeops-list-start"));
- foreach (var (name, session) in _operativePlayers)
- {
- var listing = Loc.GetString("nukeops-list-name", ("name", name), ("user", session.Name));
- ev.AddLine(listing);
+ ev.AddLine(Loc.GetString("nukeops-list-start"));
+ foreach (var (name, session) in nukeops.OperativePlayers)
+ {
+ var listing = Loc.GetString("nukeops-list-name", ("name", name), ("user", session.Name));
+ ev.AddLine(listing);
+ }
}
}
- private void CheckRoundShouldEnd()
+ private void SetWinType(EntityUid uid, WinType type, NukeopsRuleComponent? component = null)
{
- if (!RuleAdded || !_nukeopsRuleConfig.EndsRound || RuleWinType == WinType.CrewMajor || RuleWinType == WinType.OpsMajor)
+ if (!Resolve(uid, ref component))
return;
- // If there are any nuclear bombs that are active, immediately return. We're not over yet.
- foreach (var nuke in EntityQuery<NukeComponent>())
+ component.WinType = type;
+
+ if (type == WinType.CrewMajor || type == WinType.OpsMajor)
+ _roundEndSystem.EndRound();
+ }
+
+ private void CheckRoundShouldEnd()
+ {
+ var query = EntityQueryEnumerator<NukeopsRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var nukeops, out var gameRule))
{
- if (nuke.Status == NukeStatus.ARMED)
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
+
+ if (!nukeops.EndsRound || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor)
+ continue;
+
+ // If there are any nuclear bombs that are active, immediately return. We're not over yet.
+ var armed = false;
+ foreach (var nuke in EntityQuery<NukeComponent>())
{
- return;
+ if (nuke.Status == NukeStatus.ARMED)
+ {
+ armed = true;
+ break;
+ }
}
- }
-
- MapId? shuttleMapId = EntityManager.EntityExists(_nukieShuttle)
- ? Transform(_nukieShuttle!.Value).MapID
- : null;
+ if (armed)
+ continue;
- MapId? targetStationMap = null;
- if (_targetStation != null && TryComp(_targetStation, out StationDataComponent? data))
- {
- var grid = data.Grids.FirstOrNull();
- targetStationMap = grid != null
- ? Transform(grid.Value).MapID
+ MapId? shuttleMapId = Exists(nukeops.NukieShuttle)
+ ? Transform(nukeops.NukieShuttle.Value).MapID
: null;
- }
- // Check if there are nuke operatives still alive on the same map as the shuttle,
- // or on the same map as the station.
- // If there are, the round can continue.
- var operatives = EntityQuery<NukeOperativeComponent, MobStateComponent, TransformComponent>(true);
- var operativesAlive = operatives
- .Where(ent =>
- ent.Item3.MapID == shuttleMapId
- || ent.Item3.MapID == targetStationMap)
- .Any(ent => ent.Item2.CurrentState == MobState.Alive && ent.Item1.Running);
-
- if (operativesAlive)
- return; // There are living operatives than can access the shuttle, or are still on the station's map.
-
- // Check that there are spawns available and that they can access the shuttle.
- var spawnsAvailable = EntityQuery<NukeOperativeSpawnerComponent>(true).Any();
- if (spawnsAvailable && shuttleMapId == _nukiePlanet)
- return; // Ghost spawns can still access the shuttle. Continue the round.
-
- // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives,
- // and there are no nuclear operatives on the target station's map.
- if (spawnsAvailable)
- {
- _winConditions.Add(WinCondition.NukiesAbandoned);
- }
- else
- {
- _winConditions.Add(WinCondition.AllNukiesDead);
- }
+ MapId? targetStationMap = null;
+ if (nukeops.TargetStation != null && TryComp(nukeops.TargetStation, out StationDataComponent? data))
+ {
+ var grid = data.Grids.FirstOrNull();
+ targetStationMap = grid != null
+ ? Transform(grid.Value).MapID
+ : null;
+ }
+
+ // Check if there are nuke operatives still alive on the same map as the shuttle,
+ // or on the same map as the station.
+ // If there are, the round can continue.
+ var operatives = EntityQuery<NukeOperativeComponent, MobStateComponent, TransformComponent>(true);
+ var operativesAlive = operatives
+ .Where(ent =>
+ ent.Item3.MapID == shuttleMapId
+ || ent.Item3.MapID == targetStationMap)
+ .Any(ent => ent.Item2.CurrentState == MobState.Alive && ent.Item1.Running);
+
+ if (operativesAlive)
+ continue; // There are living operatives than can access the shuttle, or are still on the station's map.
+
+ // Check that there are spawns available and that they can access the shuttle.
+ var spawnsAvailable = EntityQuery<NukeOperativeSpawnerComponent>(true).Any();
+ if (spawnsAvailable && shuttleMapId == nukeops.NukiePlanet)
+ continue; // Ghost spawns can still access the shuttle. Continue the round.
- RuleWinType = WinType.CrewMajor;
+ // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives,
+ // and there are no nuclear operatives on the target station's map.
+ nukeops.WinConditions.Add(spawnsAvailable
+ ? WinCondition.NukiesAbandoned
+ : WinCondition.AllNukiesDead);
+
+ SetWinType(uid, WinType.CrewMajor, nukeops);
+ }
}
private void OnNukeDisarm(NukeDisarmSuccessEvent ev)
private void OnPlayersSpawning(RulePlayerSpawningEvent ev)
{
- if (!RuleAdded)
- return;
-
- if (!SpawnMap())
+ var query = EntityQueryEnumerator<NukeopsRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var nukeops, out var gameRule))
{
- Logger.InfoS("nukies", "Failed to load map for nukeops");
- return;
- }
-
- // Basically copied verbatim from traitor code
- var playersPerOperative = _nukeopsRuleConfig.PlayersPerOperative;
- var maxOperatives = _nukeopsRuleConfig.MaxOperatives;
-
- var everyone = new List<IPlayerSession>(ev.PlayerPool);
- var prefList = new List<IPlayerSession>();
- var cmdrPrefList = new List<IPlayerSession>();
- var operatives = new List<IPlayerSession>();
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- // The LINQ expression ReSharper keeps suggesting is completely unintelligible so I'm disabling it
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var player in everyone)
- {
- if (!ev.Profiles.ContainsKey(player.UserId))
+ if (!SpawnMap(uid, nukeops))
{
+ Logger.InfoS("nukies", "Failed to load map for nukeops");
continue;
}
- var profile = ev.Profiles[player.UserId];
- if (profile.AntagPreferences.Contains(_nukeopsRuleConfig.OperativeRoleProto))
- {
- prefList.Add(player);
- }
- if (profile.AntagPreferences.Contains(_nukeopsRuleConfig.CommanderRolePrototype))
+
+ // Basically copied verbatim from traitor code
+ var playersPerOperative = nukeops.PlayersPerOperative;
+ var maxOperatives = nukeops.MaxOperatives;
+
+ var everyone = new List<IPlayerSession>(ev.PlayerPool);
+ var prefList = new List<IPlayerSession>();
+ var cmdrPrefList = new List<IPlayerSession>();
+ var operatives = new List<IPlayerSession>();
+
+ // The LINQ expression ReSharper keeps suggesting is completely unintelligible so I'm disabling it
+ // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
+ foreach (var player in everyone)
{
- cmdrPrefList.Add(player);
+ if (!ev.Profiles.ContainsKey(player.UserId))
+ {
+ continue;
+ }
+
+ var profile = ev.Profiles[player.UserId];
+ if (profile.AntagPreferences.Contains(nukeops.OperativeRoleProto))
+ {
+ prefList.Add(player);
+ }
+
+ if (profile.AntagPreferences.Contains(nukeops.CommanderRolePrototype))
+ {
+ cmdrPrefList.Add(player);
+ }
}
- }
- var numNukies = MathHelper.Clamp(ev.PlayerPool.Count / playersPerOperative, 1, maxOperatives);
+ var numNukies = MathHelper.Clamp(ev.PlayerPool.Count / playersPerOperative, 1, maxOperatives);
- for (var i = 0; i < numNukies; i++)
- {
- IPlayerSession nukeOp;
- // Only one commander, so we do it at the start
- if (i == 0)
+ for (var i = 0; i < numNukies; i++)
{
- if (cmdrPrefList.Count == 0)
+ IPlayerSession nukeOp;
+ // Only one commander, so we do it at the start
+ if (i == 0)
{
- if (prefList.Count == 0)
+ if (cmdrPrefList.Count == 0)
{
- if (everyone.Count == 0)
+ if (prefList.Count == 0)
{
- Logger.InfoS("preset", "Insufficient ready players to fill up with nukeops, stopping the selection");
- break;
+ if (everyone.Count == 0)
+ {
+ Logger.InfoS("preset",
+ "Insufficient ready players to fill up with nukeops, stopping the selection");
+ break;
+ }
+
+ nukeOp = _random.PickAndTake(everyone);
+ Logger.InfoS("preset",
+ "Insufficient preferred nukeop commanders or nukies, picking at random.");
+ }
+ else
+ {
+ nukeOp = _random.PickAndTake(prefList);
+ everyone.Remove(nukeOp);
+ Logger.InfoS("preset",
+ "Insufficient preferred nukeop commanders, picking at random from regular op list.");
}
- nukeOp = _random.PickAndTake(everyone);
- Logger.InfoS("preset", "Insufficient preferred nukeop commanders or nukies, picking at random.");
}
else
{
- nukeOp = _random.PickAndTake(prefList);
+ nukeOp = _random.PickAndTake(cmdrPrefList);
everyone.Remove(nukeOp);
- Logger.InfoS("preset", "Insufficient preferred nukeop commanders, picking at random from regular op list.");
+ prefList.Remove(nukeOp);
+ Logger.InfoS("preset", "Selected a preferred nukeop commander.");
}
}
else
{
- nukeOp = _random.PickAndTake(cmdrPrefList);
- everyone.Remove(nukeOp);
- prefList.Remove(nukeOp);
- Logger.InfoS("preset", "Selected a preferred nukeop commander.");
- }
- }
- else
- {
- if (prefList.Count == 0)
- {
- if (everyone.Count == 0)
+ if (prefList.Count == 0)
+ {
+ if (everyone.Count == 0)
+ {
+ Logger.InfoS("preset",
+ "Insufficient ready players to fill up with nukeops, stopping the selection");
+ break;
+ }
+
+ nukeOp = _random.PickAndTake(everyone);
+ Logger.InfoS("preset", "Insufficient preferred nukeops, picking at random.");
+ }
+ else
{
- Logger.InfoS("preset", "Insufficient ready players to fill up with nukeops, stopping the selection");
- break;
+ nukeOp = _random.PickAndTake(prefList);
+ everyone.Remove(nukeOp);
+ Logger.InfoS("preset", "Selected a preferred nukeop.");
}
- nukeOp = _random.PickAndTake(everyone);
- Logger.InfoS("preset", "Insufficient preferred nukeops, picking at random.");
- }
- else
- {
- nukeOp = _random.PickAndTake(prefList);
- everyone.Remove(nukeOp);
- Logger.InfoS("preset", "Selected a preferred nukeop.");
}
+
+ operatives.Add(nukeOp);
}
- operatives.Add(nukeOp);
- }
- SpawnOperatives(numNukies, operatives, false);
+ SpawnOperatives(numNukies, operatives, false, nukeops);
- foreach(var session in operatives)
- {
- ev.PlayerPool.Remove(session);
- GameTicker.PlayerJoinGame(session);
- var name = session.AttachedEntity == null
- ? string.Empty
- : MetaData(session.AttachedEntity.Value).EntityName;
- // TODO: Fix this being able to have duplicates
- _operativePlayers[name] = session;
+ foreach (var session in operatives)
+ {
+ ev.PlayerPool.Remove(session);
+ GameTicker.PlayerJoinGame(session);
+ var name = session.AttachedEntity == null
+ ? string.Empty
+ : MetaData(session.AttachedEntity.Value).EntityName;
+ // TODO: Fix this being able to have duplicates
+ nukeops.OperativePlayers[name] = session;
+ }
}
}
if (TryComp(args.Spawned, out ActorComponent? actor))
profile = _prefs.GetPreferences(actor.PlayerSession.UserId).SelectedCharacter as HumanoidCharacterProfile;
- SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.OperativeStartingGear, profile);
+ // todo: this is kinda awful for multi-nukies
+ foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
+ {
+ SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.OperativeStartingGear, profile, nukeops);
- _operativeMindPendingData.Add(uid, nukeOpSpawner.OperativeRolePrototype);
+ nukeops.OperativeMindPendingData.Add(uid, nukeOpSpawner.OperativeRolePrototype);
+ }
}
private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args)
var mind = mindComponent.Mind;
- if (_operativeMindPendingData.TryGetValue(uid, out var role) || !_nukeopsRuleConfig.SpawnOutpost || !_nukeopsRuleConfig.EndsRound)
+ foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
{
- if (role == null)
- role = _nukeopsRuleConfig.OperativeRoleProto;
+ if (nukeops.OperativeMindPendingData.TryGetValue(uid, out var role) || !nukeops.SpawnOutpost || !nukeops.EndsRound)
+ {
+ role ??= nukeops.OperativeRoleProto;
- mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(role)));
- _operativeMindPendingData.Remove(uid);
- }
+ mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(role)));
+ nukeops.OperativeMindPendingData.Remove(uid);
+ }
- if (!mind.TryGetSession(out var playerSession))
- return;
- if (_operativePlayers.ContainsValue(playerSession))
- return;
+ if (!mind.TryGetSession(out var playerSession))
+ return;
+ if (nukeops.OperativePlayers.ContainsValue(playerSession))
+ return;
- var name = MetaData(uid).EntityName;
+ var name = MetaData(uid).EntityName;
- _operativePlayers.Add(name, playerSession);
+ nukeops.OperativePlayers.Add(name, playerSession);
- if (_ticker.RunLevel != GameRunLevel.InRound)
- return;
+ if (GameTicker.RunLevel != GameRunLevel.InRound)
+ return;
- if (_nukeopsRuleConfig.GreetSound != null)
- _audioSystem.PlayGlobal(_nukeopsRuleConfig.GreetSound, playerSession);
+ _audioSystem.PlayGlobal(nukeops.GreetSound, playerSession);
- if (_targetStation != null && !string.IsNullOrEmpty(Name(_targetStation.Value)))
- _chatManager.DispatchServerMessage(playerSession, Loc.GetString("nukeops-welcome", ("station", _targetStation.Value)));
+ if (nukeops.TargetStation != null && !string.IsNullOrEmpty(Name(nukeops.TargetStation.Value)))
+ _chatManager.DispatchServerMessage(playerSession, Loc.GetString("nukeops-welcome", ("station", nukeops.TargetStation.Value)));
+ }
}
- private bool SpawnMap()
+ private bool SpawnMap(EntityUid uid, NukeopsRuleComponent? component = null)
{
- if (_nukiePlanet != null)
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (component.NukiePlanet != null)
return true; // Map is already loaded.
- if (!_nukeopsRuleConfig.SpawnOutpost)
+ if (!component.SpawnOutpost)
return true;
- _nukeopsRuleConfig.CanLoneOpsSpawn = false;
-
- var path = _nukeopsRuleConfig.NukieOutpostMap;
- var shuttlePath = _nukeopsRuleConfig.NukieShuttleMap;
- if (path == null)
- {
- Logger.ErrorS("nukies", "No station map specified for nukeops!");
- return false;
- }
-
- if (shuttlePath == null)
- {
- Logger.ErrorS("nukies", "No shuttle map specified for nukeops!");
- return false;
- }
+ var path = component.NukieOutpostMap;
+ var shuttlePath = component.NukieShuttleMap;
var mapId = _mapManager.CreateMap();
- var options = new MapLoadOptions()
+ var options = new MapLoadOptions
{
LoadMap = true,
};
}
// Assume the first grid is the outpost grid.
- _nukieOutpost = outpostGrids[0];
+ component.NukieOutpost = outpostGrids[0];
// Listen I just don't want it to overlap.
if (!_map.TryLoad(mapId, shuttlePath.ToString(), out var grids, new MapLoadOptions {Offset = Vector2.One*1000f}) || !grids.Any())
if (TryComp<ShuttleComponent>(shuttleId, out var shuttle))
{
- _shuttle.TryFTLDock(shuttleId, shuttle, _nukieOutpost.Value);
+ _shuttle.TryFTLDock(shuttleId, shuttle, component.NukieOutpost.Value);
}
- _nukiePlanet = mapId;
- _nukieShuttle = shuttleId;
-
+ component.NukiePlanet = mapId;
+ component.NukieShuttle = shuttleId;
return true;
}
- private (string Name, string Role, string Gear) GetOperativeSpawnDetails(int spawnNumber)
+ private (string Name, string Role, string Gear) GetOperativeSpawnDetails(int spawnNumber, NukeopsRuleComponent component )
{
string name;
string role;
switch (spawnNumber)
{
case 0:
- name = Loc.GetString("nukeops-role-commander") + " " + _random.PickAndTake(_operativeNames[_nukeopsRuleConfig.EliteNames]);
- role = _nukeopsRuleConfig.CommanderRolePrototype;
- gear = _nukeopsRuleConfig.CommanderStartGearPrototype;
+ name = Loc.GetString("nukeops-role-commander") + " " + _random.PickAndTake(component.OperativeNames[component.EliteNames]);
+ role = component.CommanderRolePrototype;
+ gear = component.CommanderStartGearPrototype;
break;
case 1:
- name = Loc.GetString("nukeops-role-agent") + " " + _random.PickAndTake(_operativeNames[_nukeopsRuleConfig.NormalNames]);
- role = _nukeopsRuleConfig.OperativeRoleProto;
- gear = _nukeopsRuleConfig.MedicStartGearPrototype;
+ name = Loc.GetString("nukeops-role-agent") + " " + _random.PickAndTake(component.OperativeNames[component.NormalNames]);
+ role = component.OperativeRoleProto;
+ gear = component.MedicStartGearPrototype;
break;
default:
- name = Loc.GetString("nukeops-role-operator") + " " + _random.PickAndTake(_operativeNames[_nukeopsRuleConfig.NormalNames]);
- role = _nukeopsRuleConfig.OperativeRoleProto;
- gear = _nukeopsRuleConfig.OperativeStartGearPrototype;
+ name = Loc.GetString("nukeops-role-operator") + " " + _random.PickAndTake(component.OperativeNames[component.NormalNames]);
+ role = component.OperativeRoleProto;
+ gear = component.OperativeStartGearPrototype;
break;
}
/// <summary>
/// Adds missing nuke operative components, equips starting gear and renames the entity.
/// </summary>
- private void SetupOperativeEntity(EntityUid mob, string name, string gear, HumanoidCharacterProfile? profile)
+ private void SetupOperativeEntity(EntityUid mob, string name, string gear, HumanoidCharacterProfile? profile, NukeopsRuleComponent component)
{
MetaData(mob).EntityName = name;
- EntityManager.EnsureComponent<NukeOperativeComponent>(mob);
+ EnsureComp<NukeOperativeComponent>(mob);
if (profile != null)
{
_humanoidSystem.LoadProfile(mob, profile);
}
- if (_startingGearPrototypes.TryGetValue(gear, out var gearPrototype))
+ if (component.StartingGearPrototypes.TryGetValue(gear, out var gearPrototype))
_stationSpawningSystem.EquipStartingGear(mob, gearPrototype, profile);
_faction.RemoveFaction(mob, "NanoTrasen", false);
_faction.AddFaction(mob, "Syndicate");
}
- private void SpawnOperatives(int spawnCount, List<IPlayerSession> sessions, bool addSpawnPoints)
+ private void SpawnOperatives(int spawnCount, List<IPlayerSession> sessions, bool addSpawnPoints, NukeopsRuleComponent component)
{
- if (_nukieOutpost == null)
+ if (component.NukieOutpost == null)
return;
- var outpostUid = _nukieOutpost.Value;
+ var outpostUid = component.NukieOutpost.Value;
var spawns = new List<EntityCoordinates>();
// Forgive me for hardcoding prototypes
foreach (var (_, meta, xform) in EntityManager.EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
{
- if (meta.EntityPrototype?.ID != _nukeopsRuleConfig.SpawnPointPrototype)
+ if (meta.EntityPrototype?.ID != component.SpawnPointPrototype)
continue;
- if (xform.ParentUid != _nukieOutpost)
+ if (xform.ParentUid != component.NukieOutpost)
continue;
spawns.Add(xform.Coordinates);
// TODO: This should spawn the nukies in regardless and transfer if possible; rest should go to shot roles.
for(var i = 0; i < spawnCount; i++)
{
- var spawnDetails = GetOperativeSpawnDetails(i);
+ var spawnDetails = GetOperativeSpawnDetails(i, component);
var nukeOpsAntag = _prototypeManager.Index<AntagPrototype>(spawnDetails.Role);
if (sessions.TryGetValue(i, out var session))
{
var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
- if (!_prototypeManager.TryIndex(profile?.Species ?? HumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species))
+ if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species))
{
- species = _prototypeManager.Index<SpeciesPrototype>(HumanoidAppearanceSystem.DefaultSpecies);
+ species = _prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
}
var mob = EntityManager.SpawnEntity(species.Prototype, _random.Pick(spawns));
- SetupOperativeEntity(mob, spawnDetails.Name, spawnDetails.Gear, profile);
+ SetupOperativeEntity(mob, spawnDetails.Name, spawnDetails.Gear, profile, component);
var newMind = new Mind.Mind(session.UserId)
{
}
else if (addSpawnPoints)
{
- var spawnPoint = EntityManager.SpawnEntity(_nukeopsRuleConfig.GhostSpawnPointProto, _random.Pick(spawns));
+ var spawnPoint = EntityManager.SpawnEntity(component.GhostSpawnPointProto, _random.Pick(spawns));
var ghostRole = EnsureComp<GhostRoleComponent>(spawnPoint);
EnsureComp<GhostRoleMobSpawnerComponent>(spawnPoint);
ghostRole.RoleName = Loc.GetString(nukeOpsAntag.Name);
}
}
- private void SpawnOperativesForGhostRoles()
+ private void SpawnOperativesForGhostRoles(EntityUid uid, NukeopsRuleComponent? component = null)
{
- if (!SpawnMap())
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (!SpawnMap(uid, component))
{
Logger.InfoS("nukies", "Failed to load map for nukeops");
return;
}
// Basically copied verbatim from traitor code
- var playersPerOperative = _nukeopsRuleConfig.PlayersPerOperative;
- var maxOperatives = _nukeopsRuleConfig.MaxOperatives;
+ var playersPerOperative = component.PlayersPerOperative;
+ var maxOperatives = component.MaxOperatives;
var playerPool = _playerSystem.ServerSessions.ToList();
var numNukies = MathHelper.Clamp(playerPool.Count / playersPerOperative, 1, maxOperatives);
var operatives = new List<IPlayerSession>();
- SpawnOperatives(numNukies, operatives, true);
+ SpawnOperatives(numNukies, operatives, true, component);
}
//For admins forcing someone to nukeOps.
if (!mind.OwnedEntity.HasValue)
return;
- mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(_nukeopsRuleConfig.OperativeRoleProto)));
+ //ok hardcoded value bad but so is everything else here
+ mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>("Nukeops")));
SetOutfitCommand.SetOutfit(mind.OwnedEntity.Value, "SyndicateOperativeGearFull", EntityManager);
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
- if (!RuleAdded || Configuration is not NukeopsRuleConfiguration nukeOpsConfig)
- return;
-
- _nukeopsRuleConfig = nukeOpsConfig;
- var minPlayers = nukeOpsConfig.MinPlayers;
- if (!ev.Forced && ev.Players.Length < minPlayers)
+ var query = EntityQueryEnumerator<NukeopsRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var nukeops, out var gameRule))
{
- _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
- ev.Cancel();
- return;
- }
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- if (ev.Players.Length != 0)
- return;
+ var minPlayers = nukeops.MinPlayers;
+ if (!ev.Forced && ev.Players.Length < minPlayers)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players",
+ ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
+ ev.Cancel();
+ continue;
+ }
+
+ if (ev.Players.Length != 0)
+ continue;
- _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
- ev.Cancel();
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
+ ev.Cancel();
+ }
}
- public override void Started()
+ protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- RuleWinType = WinType.Neutral;
- _winConditions.Clear();
- _nukieOutpost = null;
- _nukiePlanet = null;
-
- _startingGearPrototypes.Clear();
- _operativeNames.Clear();
- _operativeMindPendingData.Clear();
- _operativePlayers.Clear();
-
+ base.Started(uid, component, gameRule, args);
// TODO: Loot table or something
foreach (var proto in new[]
{
- _nukeopsRuleConfig.CommanderStartGearPrototype,
- _nukeopsRuleConfig.MedicStartGearPrototype,
- _nukeopsRuleConfig.OperativeStartGearPrototype
+ component.CommanderStartGearPrototype,
+ component.MedicStartGearPrototype,
+ component.OperativeStartGearPrototype
})
{
- _startingGearPrototypes.Add(proto, _prototypeManager.Index<StartingGearPrototype>(proto));
+ component.StartingGearPrototypes.Add(proto, _prototypeManager.Index<StartingGearPrototype>(proto));
}
- foreach (var proto in new[] { _nukeopsRuleConfig.EliteNames, _nukeopsRuleConfig.NormalNames })
+ foreach (var proto in new[] { component.EliteNames, component.NormalNames })
{
- _operativeNames.Add(proto, new List<string>(_prototypeManager.Index<DatasetPrototype>(proto).Values));
+ component.OperativeNames.Add(proto, new List<string>(_prototypeManager.Index<DatasetPrototype>(proto).Values));
}
// Add pre-existing nuke operatives to the credit list.
- var query = EntityQuery<NukeOperativeComponent, MindComponent>(true);
- foreach (var (_, mindComp) in query)
+ var query = EntityQuery<NukeOperativeComponent, MindComponent, MetaDataComponent>(true);
+ foreach (var (_, mindComp, metaData) in query)
{
if (mindComp.Mind == null || !mindComp.Mind.TryGetSession(out var session))
continue;
- var name = MetaData(mindComp.Owner).EntityName;
- _operativePlayers.Add(name, session);
+ component.OperativePlayers.Add(metaData.EntityName, session);
}
if (GameTicker.RunLevel == GameRunLevel.InRound)
- SpawnOperativesForGhostRoles();
+ SpawnOperativesForGhostRoles(uid, component);
}
- public override void Ended()
- {
- _nukeopsRuleConfig.EndsRound = true;
- _nukeopsRuleConfig.SpawnOutpost = true;
- _nukeopsRuleConfig.CanLoneOpsSpawn = true;
- }
}
using Content.Server.Administration.Commands;
using Content.Server.Cargo.Systems;
using Content.Server.Chat.Managers;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Preferences.Managers;
using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
/// <summary>
/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion.
/// </summary>
-public sealed class PiratesRuleSystem : GameRuleSystem
+public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly NamingSystem _namingSystem = default!;
- [ViewVariables]
- private List<Mind.Mind> _pirates = new();
- [ViewVariables]
- private EntityUid _pirateShip = EntityUid.Invalid;
- [ViewVariables]
- private HashSet<EntityUid> _initialItems = new();
- [ViewVariables]
- private double _initialShipValue;
-
- public override string Prototype => "Pirates";
-
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawningEvent);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextEvent);
+ SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
}
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
{
- if (!RuleAdded)
- return;
-
- if (Deleted(_pirateShip))
- {
- // Major loss, the ship somehow got annihilated.
- ev.AddLine(Loc.GetString("pirates-no-ship"));
- }
- else
+ var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var pirates, out var gameRule))
{
+ if (Deleted(pirates.PirateShip))
+ {
+ // Major loss, the ship somehow got annihilated.
+ ev.AddLine(Loc.GetString("pirates-no-ship"));
+ }
+ else
+ {
- List<(double, EntityUid)> mostValuableThefts = new();
+ List<(double, EntityUid)> mostValuableThefts = new();
- var finalValue = _pricingSystem.AppraiseGrid(_pirateShip, uid =>
- {
- foreach (var mind in _pirates)
+ var comp1 = pirates;
+ var finalValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid =>
{
- if (mind.CurrentEntity == uid)
- return false; // Don't appraise the pirates twice, we count them in separately.
+ foreach (var mind in comp1.Pirates)
+ {
+ if (mind.CurrentEntity == uid)
+ return false; // Don't appraise the pirates twice, we count them in separately.
+ }
+
+ return true;
+ }, (uid, price) =>
+ {
+ if (comp1.InitialItems.Contains(uid))
+ return;
+
+ mostValuableThefts.Add((price, uid));
+ mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
+ if (mostValuableThefts.Count > 5)
+ mostValuableThefts.Pop();
+ });
+
+ foreach (var mind in pirates.Pirates)
+ {
+ if (mind.CurrentEntity is not null)
+ finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value);
}
- return true;
- }, (uid, price) =>
- {
- if (_initialItems.Contains(uid))
- return;
- mostValuableThefts.Add((price, uid));
- mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
- if (mostValuableThefts.Count > 5)
- mostValuableThefts.Pop();
- });
+ var score = finalValue - pirates.InitialShipValue;
- foreach (var mind in _pirates)
- {
- if (mind.CurrentEntity is not null)
- finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value);
- }
+ ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}")));
+ ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}")));
+
+ ev.AddLine("");
+ ev.AddLine(Loc.GetString("pirates-most-valuable"));
- var score = finalValue - _initialShipValue;
+ foreach (var (price, obj) in mostValuableThefts)
+ {
+ ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}")));
+ }
- ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}")));
- ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}")));
+ if (mostValuableThefts.Count == 0)
+ ev.AddLine(Loc.GetString("pirates-stole-nothing"));
+ }
ev.AddLine("");
- ev.AddLine(Loc.GetString("pirates-most-valuable"));
-
- foreach (var (price, obj) in mostValuableThefts)
+ ev.AddLine(Loc.GetString("pirates-list-start"));
+ foreach (var pirate in pirates.Pirates)
{
- ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}")));
+ ev.AddLine($"- {pirate.CharacterName} ({pirate.Session?.Name})");
}
-
- if (mostValuableThefts.Count == 0)
- ev.AddLine(Loc.GetString("pirates-stole-nothing"));
- }
-
- ev.AddLine("");
- ev.AddLine(Loc.GetString("pirates-list-start"));
- foreach (var pirates in _pirates)
- {
- ev.AddLine($"- {pirates.CharacterName} ({pirates.Session?.Name})");
}
}
- public override void Started() { }
-
- public override void Ended() { }
-
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
{
- // Forgive me for copy-pasting nukies.
- if (!RuleAdded)
+ var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var pirates, out var gameRule))
{
- return;
- }
-
- _pirates.Clear();
- _initialItems.Clear();
-
- // Between 1 and <max pirate count>: needs at least n players per op.
- var numOps = Math.Max(1,
- (int)Math.Min(
- Math.Floor((double)ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)), _cfg.GetCVar(CCVars.PiratesMaxOps)));
- var ops = new IPlayerSession[numOps];
- for (var i = 0; i < numOps; i++)
- {
- ops[i] = _random.PickAndTake(ev.PlayerPool);
- }
+ // Forgive me for copy-pasting nukies.
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ return;
+
+ pirates.Pirates.Clear();
+ pirates.InitialItems.Clear();
+
+ // Between 1 and <max pirate count>: needs at least n players per op.
+ var numOps = Math.Max(1,
+ (int) Math.Min(
+ Math.Floor((double) ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)),
+ _cfg.GetCVar(CCVars.PiratesMaxOps)));
+ var ops = new IPlayerSession[numOps];
+ for (var i = 0; i < numOps; i++)
+ {
+ ops[i] = _random.PickAndTake(ev.PlayerPool);
+ }
- var map = "/Maps/Shuttles/pirate.yml";
- var xformQuery = GetEntityQuery<TransformComponent>();
+ var map = "/Maps/Shuttles/pirate.yml";
+ var xformQuery = GetEntityQuery<TransformComponent>();
- var aabbs = _stationSystem.Stations.SelectMany(x =>
- Comp<StationDataComponent>(x).Grids.Select(x => xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB))).ToArray();
+ var aabbs = _stationSystem.Stations.SelectMany(x =>
+ Comp<StationDataComponent>(x).Grids.Select(x =>
+ xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB)))
+ .ToArray();
- var aabb = aabbs[0];
+ var aabb = aabbs[0];
- for (var i = 1; i < aabbs.Length; i++)
- {
- aabb.Union(aabbs[i]);
- }
+ for (var i = 1; i < aabbs.Length; i++)
+ {
+ aabb.Union(aabbs[i]);
+ }
- var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions
- {
- Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
- });
+ var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions
+ {
+ Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
+ });
- if (!gridId.HasValue)
- {
- Logger.ErrorS("pirates", $"Gridid was null when loading \"{map}\", aborting.");
- foreach (var session in ops)
+ if (!gridId.HasValue)
{
- ev.PlayerPool.Add(session);
+ Logger.ErrorS("pirates", $"Gridid was null when loading \"{map}\", aborting.");
+ foreach (var session in ops)
+ {
+ ev.PlayerPool.Add(session);
+ }
+
+ return;
}
- return;
- }
- _pirateShip = gridId.Value;
+ pirates.PirateShip = gridId.Value;
- // TODO: Loot table or something
- var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR
+ // TODO: Loot table or something
+ var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR
- var spawns = new List<EntityCoordinates>();
+ var spawns = new List<EntityCoordinates>();
- // Forgive me for hardcoding prototypes
- foreach (var (_, meta, xform) in EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
- {
- if (meta.EntityPrototype?.ID != "SpawnPointPirates" || xform.ParentUid != _pirateShip) continue;
+ // Forgive me for hardcoding prototypes
+ foreach (var (_, meta, xform) in
+ EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
+ {
+ if (meta.EntityPrototype?.ID != "SpawnPointPirates" || xform.ParentUid != pirates.PirateShip)
+ continue;
- spawns.Add(xform.Coordinates);
- }
+ spawns.Add(xform.Coordinates);
+ }
- if (spawns.Count == 0)
- {
- spawns.Add(Transform(_pirateShip).Coordinates);
- Logger.WarningS("pirates", $"Fell back to default spawn for pirates!");
- }
+ if (spawns.Count == 0)
+ {
+ spawns.Add(Transform(pirates.PirateShip).Coordinates);
+ Logger.WarningS("pirates", $"Fell back to default spawn for pirates!");
+ }
- for (var i = 0; i < ops.Length; i++)
- {
- var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
- var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
+ for (var i = 0; i < ops.Length; i++)
+ {
+ var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
+ var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
- var name = _namingSystem.GetName("Human", gender);
+ var name = _namingSystem.GetName("Human", gender);
- var session = ops[i];
- var newMind = new Mind.Mind(session.UserId)
- {
- CharacterName = name
- };
- newMind.ChangeOwningPlayer(session.UserId);
+ var session = ops[i];
+ var newMind = new Mind.Mind(session.UserId)
+ {
+ CharacterName = name
+ };
+ newMind.ChangeOwningPlayer(session.UserId);
- var mob = Spawn("MobHuman", _random.Pick(spawns));
- MetaData(mob).EntityName = name;
+ var mob = Spawn("MobHuman", _random.Pick(spawns));
+ MetaData(mob).EntityName = name;
- newMind.TransferTo(mob);
- var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
- _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
+ newMind.TransferTo(mob);
+ var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
+ _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
- _pirates.Add(newMind);
+ pirates.Pirates.Add(newMind);
- GameTicker.PlayerJoinGame(session);
- }
+ GameTicker.PlayerJoinGame(session);
+ }
- _initialShipValue = _pricingSystem.AppraiseGrid(_pirateShip, uid =>
- {
- _initialItems.Add(uid);
- return true;
- }); // Include the players in the appraisal.
+ pirates.InitialShipValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid =>
+ {
+ pirates.InitialItems.Add(uid);
+ return true;
+ }); // Include the players in the appraisal.
+ }
}
//Forcing one player to be a pirate.
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
- if (!RuleAdded)
- return;
-
- var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
- if (!ev.Forced && ev.Players.Length < minPlayers)
+ var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var pirates, out var gameRule))
{
- _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
- ev.Cancel();
- return;
- }
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
- if (ev.Players.Length == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
- ev.Cancel();
+ var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
+ if (!ev.Forced && ev.Players.Length < minPlayers)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players",
+ ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
+ ev.Cancel();
+ return;
+ }
+
+ if (ev.Players.Length == 0)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
+ ev.Cancel();
+ }
}
}
}
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Sandbox;
namespace Content.Server.GameTicking.Rules;
-public sealed class SandboxRuleSystem : GameRuleSystem
+public sealed class SandboxRuleSystem : GameRuleSystem<SandboxRuleComponent>
{
[Dependency] private readonly SandboxSystem _sandbox = default!;
- public override string Prototype => "Sandbox";
-
- public override void Started()
+ protected override void Started(EntityUid uid, SandboxRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
+ base.Started(uid, component, gameRule, args);
_sandbox.IsSandboxEnabled = true;
}
- public override void Ended()
+ protected override void Ended(EntityUid uid, SandboxRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
+ base.Ended(uid, component, gameRule, args);
_sandbox.IsSandboxEnabled = false;
}
}
-using System.Linq;
using Content.Server.GameTicking.Presets;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules;
-public sealed class SecretRuleSystem : GameRuleSystem
+public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly GameTicker _ticker = default!;
- public override string Prototype => "Secret";
-
- public override void Started()
+ protected override void Started(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- PickRule();
+ base.Started(uid, component, gameRule, args);
+ PickRule(component);
}
- public override void Ended()
+ protected override void Ended(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
- // Preset should already handle it.
+ base.Ended(uid, component, gameRule, args);
+
+ foreach (var rule in component.AdditionalGameRules)
+ {
+ GameTicker.EndGameRule(rule);
+ }
}
- private void PickRule()
+ private void PickRule(SecretRuleComponent component)
{
// TODO: This doesn't consider what can't start due to minimum player count, but currently there's no way to know anyway.
// as they use cvars.
var preset = _prototypeManager.Index<WeightedRandomPrototype>("Secret").Pick(_random);
Logger.InfoS("gamepreset", $"Selected {preset} for secret.");
- foreach (var rule in _prototypeManager.Index<GamePresetPrototype>(preset).Rules)
+ var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;
+ foreach (var rule in rules)
{
- _ticker.StartGameRule(_prototypeManager.Index<GameRulePrototype>(rule));
+ Logger.Debug($"what the fuck, {rule}");
+ GameTicker.StartGameRule(rule, out var ruleEnt);
+ component.AdditionalGameRules.Add(ruleEnt);
}
}
}
+++ /dev/null
-using System.Linq;
-using System.Threading;
-using Content.Server.Chat.Managers;
-using Content.Server.GameTicking.Rules.Configurations;
-using Content.Server.Players;
-using Content.Server.Roles;
-using Content.Server.Station.Components;
-using Content.Server.Suspicion;
-using Content.Server.Suspicion.Roles;
-using Content.Server.Traitor.Uplink;
-using Content.Shared.CCVar;
-using Content.Shared.Doors.Systems;
-using Content.Shared.EntityList;
-using Content.Shared.GameTicking;
-using Content.Shared.Maps;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Roles;
-using Content.Shared.Suspicion;
-using Robust.Server.GameObjects;
-using Robust.Server.Player;
-using Robust.Shared.Audio;
-using Robust.Shared.Configuration;
-using Robust.Shared.Enums;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using Timer = Robust.Shared.Timing.Timer;
-
-namespace Content.Server.GameTicking.Rules;
-
-/// <summary>
-/// Simple GameRule that will do a TTT-like gamemode with traitors.
-/// </summary>
-public sealed class SuspicionRuleSystem : GameRuleSystem
-{
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
- [Dependency] private readonly SharedDoorSystem _doorSystem = default!;
- [Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
- [Dependency] private readonly UplinkSystem _uplink = default!;
-
- public override string Prototype => "Suspicion";
-
- private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1);
-
- private readonly HashSet<SuspicionRoleComponent> _traitors = new();
-
- public IReadOnlyCollection<SuspicionRoleComponent> Traitors => _traitors;
-
- [DataField("addedSound")] private SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
-
- private CancellationTokenSource _checkTimerCancel = new();
- private TimeSpan? _endTime;
-
- public TimeSpan? EndTime
- {
- get => _endTime;
- set
- {
- _endTime = value;
- SendUpdateToAll();
- }
- }
-
- public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue);
- public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
-
- private const string TraitorID = "SuspicionTraitor";
- private const string InnocentID = "SuspicionInnocent";
- private const string SuspicionLootTable = "SuspicionRule";
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersAssigned);
- SubscribeLocalEvent<RoundStartAttemptEvent>(OnRoundStartAttempt);
- SubscribeLocalEvent<RefreshLateJoinAllowedEvent>(OnLateJoinRefresh);
- SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
-
- SubscribeLocalEvent<SuspicionRoleComponent, PlayerAttachedEvent>(OnPlayerAttached);
- SubscribeLocalEvent<SuspicionRoleComponent, PlayerDetachedEvent>(OnPlayerDetached);
- SubscribeLocalEvent<SuspicionRoleComponent, RoleAddedEvent>(OnRoleAdded);
- SubscribeLocalEvent<SuspicionRoleComponent, RoleRemovedEvent>(OnRoleRemoved);
- }
-
- private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
- {
- if (!RuleAdded)
- return;
-
- var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
-
- if (!ev.Forced && ev.Players.Length < minPlayers)
- {
- _chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {ev.Players.Length} players readied up out of {minPlayers} needed.");
- ev.Cancel();
- return;
- }
-
- if (ev.Players.Length == 0)
- {
- _chatManager.DispatchServerAnnouncement("No players readied up! Can't start Suspicion.");
- ev.Cancel();
- }
- }
-
- private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
- {
- if (!RuleAdded)
- return;
-
- var minTraitors = _cfg.GetCVar(CCVars.SuspicionMinTraitors);
- var playersPerTraitor = _cfg.GetCVar(CCVars.SuspicionPlayersPerTraitor);
- var traitorStartingBalance = _cfg.GetCVar(CCVars.SuspicionStartingBalance);
-
- var list = new List<IPlayerSession>(ev.Players);
- var prefList = new List<IPlayerSession>();
-
- foreach (var player in list)
- {
- if (!ev.Profiles.ContainsKey(player.UserId) || player.AttachedEntity is not {} attached)
- {
- continue;
- }
- prefList.Add(player);
-
- attached.EnsureComponent<SuspicionRoleComponent>();
- }
-
- // Max is players-1 so there's always at least one innocent.
- var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor,
- minTraitors, ev.Players.Length-1);
-
- var traitors = new List<SuspicionTraitorRole>();
-
- for (var i = 0; i < numTraitors; i++)
- {
- IPlayerSession traitor;
- if(prefList.Count == 0)
- {
- if (list.Count == 0)
- {
- Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
- break;
- }
- traitor = _random.PickAndTake(list);
- Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
- }
- else
- {
- traitor = _random.PickAndTake(prefList);
- list.Remove(traitor);
- Logger.InfoS("preset", "Selected a preferred traitor.");
- }
- var mind = traitor.Data.ContentData()?.Mind;
- var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorID);
-
- DebugTools.AssertNotNull(mind?.OwnedEntity);
-
- var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype);
- mind!.AddRole(traitorRole);
- traitors.Add(traitorRole);
-
- // try to place uplink
- _uplink.AddUplink(mind.OwnedEntity!.Value, traitorStartingBalance);
- }
-
- foreach (var player in list)
- {
- var mind = player.Data.ContentData()?.Mind;
- var antagPrototype = _prototypeManager.Index<AntagPrototype>(InnocentID);
-
- DebugTools.AssertNotNull(mind);
-
- mind!.AddRole(new SuspicionInnocentRole(mind, antagPrototype));
- }
-
- foreach (var traitor in traitors)
- {
- traitor.GreetSuspicion(traitors, _chatManager);
- }
- }
-
- public override void Started()
- {
- _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
-
- RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds));
-
- EndTime = _timing.CurTime + RoundMaxTime;
-
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-added-announcement"));
-
- var filter = Filter.Empty()
- .AddWhere(session => ((IPlayerSession) session).ContentData()?.Mind?.HasRole<SuspicionTraitorRole>() ?? false);
-
- SoundSystem.Play(_addedSound.GetSound(), filter, AudioParams.Default);
-
- _doorSystem.AccessType = SharedDoorSystem.AccessTypes.AllowAllNoExternal;
-
- var susLoot = _prototypeManager.Index<EntityLootTablePrototype>(SuspicionLootTable);
-
- foreach (var (_, mapGrid) in EntityManager.EntityQuery<StationMemberComponent, MapGridComponent>(true))
- {
- // I'm so sorry.
- var tiles = mapGrid.GetAllTiles().ToArray();
- Logger.Info($"TILES: {tiles.Length}");
-
- var spawn = susLoot.GetSpawns();
- var count = spawn.Count;
-
- // Try to scale spawned amount by station size...
- if (tiles.Length < 1000)
- {
- count = Math.Min(count, tiles.Length / 10);
-
- // Shuffle so we pick items at random.
- _random.Shuffle(spawn);
- }
-
- for (var i = 0; i < count; i++)
- {
- var item = spawn[i];
-
- // Maximum number of attempts for trying to find a suitable empty tile.
- // We do this because we don't want to hang the server when a devious map has literally no free tiles.
- const int maxTries = 100;
-
- for (var j = 0; j < maxTries; j++)
- {
- var tile = _random.Pick(tiles);
-
- // Let's not spawn things on top of walls.
- if (tile.IsBlockedTurf(false, _lookupSystem) || tile.IsSpace(_tileDefMan))
- continue;
-
- var uid = Spawn(item, tile.GridPosition(_mapManager));
-
- // Keep track of all suspicion-spawned weapons so we can clean them up once the rule ends.
- EnsureComp<SuspicionItemComponent>(uid);
- break;
- }
- }
- }
-
- _checkTimerCancel = new CancellationTokenSource();
- Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
- }
-
- public override void Ended()
- {
- _doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
- EndTime = null;
- _traitors.Clear();
-
- _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
-
- // Clean up all items we spawned before...
- foreach (var item in EntityManager.EntityQuery<SuspicionItemComponent>(true))
- {
- Del(item.Owner);
- }
-
- _checkTimerCancel.Cancel();
- }
-
- private void CheckWinConditions()
- {
- if (!RuleAdded || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
- return;
-
- var traitorsAlive = 0;
- var innocentsAlive = 0;
-
- foreach (var playerSession in _playerManager.ServerSessions)
- {
- if (playerSession.AttachedEntity is not {Valid: true} playerEntity
- || !TryComp(playerEntity, out MobStateComponent? mobState)
- || !HasComp<SuspicionRoleComponent>(playerEntity))
- {
- continue;
- }
-
- if (!_mobStateSystem.IsAlive(playerEntity, mobState))
- {
- continue;
- }
-
- var mind = playerSession.ContentData()?.Mind;
-
- if (mind != null && mind.HasRole<SuspicionTraitorRole>())
- traitorsAlive++;
- else
- innocentsAlive++;
- }
-
- if (innocentsAlive + traitorsAlive == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-stalemate"));
- EndRound(Victory.Stalemate);
- }
-
- else if (traitorsAlive == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-station-win"));
- EndRound(Victory.Innocents);
- }
- else if (innocentsAlive == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-traitor-win"));
- EndRound(Victory.Traitors);
- }
- else if (_timing.CurTime > _endTime)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-traitor-time-has-run-out"));
- EndRound(Victory.Innocents);
- }
- }
-
- private enum Victory
- {
- Stalemate,
- Innocents,
- Traitors
- }
-
- private void EndRound(Victory victory)
- {
- string text;
-
- switch (victory)
- {
- case Victory.Innocents:
- text = Loc.GetString("rule-suspicion-end-round-innocents-victory");
- break;
- case Victory.Traitors:
- text = Loc.GetString("rule-suspicion-end-round-traitors-victory");
- break;
- default:
- text = Loc.GetString("rule-suspicion-end-round-nobody-victory");
- break;
- }
-
- GameTicker.EndRound(text);
-
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", (int) RoundEndDelay.TotalSeconds)));
- _checkTimerCancel.Cancel();
-
- Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
- }
-
- private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
- {
- if (e.NewStatus == SessionStatus.InGame)
- {
- SendUpdateTimerMessage(e.Session);
- }
- }
-
- private void SendUpdateToAll()
- {
- foreach (var player in _playerManager.ServerSessions.Where(p => p.Status == SessionStatus.InGame))
- {
- SendUpdateTimerMessage(player);
- }
- }
-
- private void SendUpdateTimerMessage(IPlayerSession player)
- {
- var msg = new SuspicionMessages.SetSuspicionEndTimerMessage
- {
- EndTime = EndTime
- };
-
- EntityManager.EntityNetManager?.SendSystemNetworkMessage(msg, player.ConnectedClient);
- }
-
- public void AddTraitor(SuspicionRoleComponent role)
- {
- if (!_traitors.Add(role))
- {
- return;
- }
-
- foreach (var traitor in _traitors)
- {
- traitor.AddAlly(role);
- }
-
- role.SetAllies(_traitors);
- }
-
- public void RemoveTraitor(SuspicionRoleComponent role)
- {
- if (!_traitors.Remove(role))
- {
- return;
- }
-
- foreach (var traitor in _traitors)
- {
- traitor.RemoveAlly(role);
- }
-
- role.ClearAllies();
- }
-
- private void Reset(RoundRestartCleanupEvent ev)
- {
- EndTime = null;
- _traitors.Clear();
- }
-
- private void OnPlayerDetached(EntityUid uid, SuspicionRoleComponent component, PlayerDetachedEvent args)
- {
- component.SyncRoles();
- }
-
- private void OnPlayerAttached(EntityUid uid, SuspicionRoleComponent component, PlayerAttachedEvent args)
- {
- component.SyncRoles();
- }
-
- private void OnRoleAdded(EntityUid uid, SuspicionRoleComponent component, RoleAddedEvent args)
- {
- if (args.Role is not SuspicionRole role) return;
- component.Role = role;
- }
-
- private void OnRoleRemoved(EntityUid uid, SuspicionRoleComponent component, RoleRemovedEvent args)
- {
- if (args.Role is not SuspicionRole) return;
- component.Role = null;
- }
-
- private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
- {
- if (!RuleAdded)
- return;
-
- ev.Disallow();
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Chat.Managers;
-using Content.Server.PDA;
-using Content.Server.Players;
-using Content.Server.Spawners.Components;
-using Content.Server.Store.Components;
-using Content.Server.Traitor;
-using Content.Server.Traitor.Uplink;
-using Content.Server.TraitorDeathMatch.Components;
-using Content.Shared.CCVar;
-using Content.Shared.Damage;
-using Content.Shared.Damage.Prototypes;
-using Content.Shared.Hands.Components;
-using Content.Shared.Inventory;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.PDA;
-using Content.Shared.Roles;
-using Robust.Server.GameObjects;
-using Robust.Server.Player;
-using Robust.Shared.Configuration;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Server.GameTicking.Rules;
-
-public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
-{
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
- [Dependency] private readonly IRobustRandom _robustRandom = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly MaxTimeRestartRuleSystem _restarter = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly TransformSystem _transformSystem = default!;
- [Dependency] private readonly UplinkSystem _uplink = default!;
-
- public override string Prototype => "TraitorDeathMatch";
-
- public string PDAPrototypeName => "CaptainPDA";
- public string BeltPrototypeName => "ClothingBeltJanitorFilled";
- public string BackpackPrototypeName => "ClothingBackpackFilled";
-
- private bool _safeToEndRound = false;
-
- private readonly Dictionary<EntityUid, string> _allOriginalNames = new();
-
- private const string TraitorPrototypeID = "Traitor";
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
- SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawned);
- SubscribeLocalEvent<GhostAttemptHandleEvent>(OnGhostAttempt);
- }
-
- private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
- {
- if (!RuleAdded)
- return;
-
- var session = ev.Player;
- var startingBalance = _cfg.GetCVar(CCVars.TraitorDeathMatchStartingBalance);
-
- // Yup, they're a traitor
- var mind = session.Data.ContentData()?.Mind;
- if (mind == null)
- {
- Logger.ErrorS("preset", "Failed getting mind for TDM player.");
- return;
- }
-
- var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorPrototypeID);
- var traitorRole = new TraitorRole(mind, antagPrototype);
- mind.AddRole(traitorRole);
-
- // Delete anything that may contain "dangerous" role-specific items.
- // (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.)
- if (mind.OwnedEntity is {Valid: true} owned)
- {
- var victimSlots = new[] {"id", "belt", "back"};
- foreach (var slot in victimSlots)
- {
- if(_inventory.TryUnequip(owned, slot, out var entityUid, true, true))
- Del(entityUid.Value);
- }
-
- // Replace their items:
-
- var ownedCoords = Transform(owned).Coordinates;
-
- // pda
- var newPDA = Spawn(PDAPrototypeName, ownedCoords);
- _inventory.TryEquip(owned, newPDA, "id", true);
-
- // belt
- var newTmp = Spawn(BeltPrototypeName, ownedCoords);
- _inventory.TryEquip(owned, newTmp, "belt", true);
-
- // backpack
- newTmp = Spawn(BackpackPrototypeName, ownedCoords);
- _inventory.TryEquip(owned, newTmp, "back", true);
-
- if (!_uplink.AddUplink(owned, startingBalance))
- return;
-
- _allOriginalNames[owned] = Name(owned);
-
- // The PDA needs to be marked with the correct owner.
- var pda = Comp<PDAComponent>(newPDA);
- EntityManager.EntitySysManager.GetEntitySystem<PDASystem>().SetOwner(pda, Name(owned));
- EntityManager.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>(newPDA).UserId = mind.UserId;
- }
-
- // Finally, it would be preferable if they spawned as far away from other players as reasonably possible.
- if (mind.OwnedEntity != null && FindAnyIsolatedSpawnLocation(mind, out var bestTarget))
- {
- Transform(mind.OwnedEntity.Value).Coordinates = bestTarget;
- }
- else
- {
- // The station is too drained of air to safely continue.
- if (_safeToEndRound)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-death-match-station-is-too-unsafe-announcement"));
- _restarter.RoundMaxTime = TimeSpan.FromMinutes(1);
- _restarter.RestartTimer();
- _safeToEndRound = false;
- }
- }
- }
-
- private void OnGhostAttempt(GhostAttemptHandleEvent ev)
- {
- if (!RuleAdded || ev.Handled)
- return;
-
- ev.Handled = true;
-
- var mind = ev.Mind;
-
- if (mind.OwnedEntity is {Valid: true} entity && TryComp(entity, out MobStateComponent? mobState))
- {
- if (_mobStateSystem.IsCritical(entity, mobState))
- {
- // TODO BODY SYSTEM KILL
- var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100);
- Get<DamageableSystem>().TryChangeDamage(entity, damage, true);
- }
- else if (!_mobStateSystem.IsDead(entity,mobState))
- {
- if (HasComp<HandsComponent>(entity))
- {
- ev.Result = false;
- return;
- }
- }
- }
- var session = mind.Session;
- if (session == null)
- {
- ev.Result = false;
- return;
- }
-
- GameTicker.Respawn(session);
- ev.Result = true;
- }
-
- private void OnRoundEndText(RoundEndTextAppendEvent ev)
- {
- if (!RuleAdded)
- return;
-
- var lines = new List<string>();
- lines.Add(Loc.GetString("traitor-death-match-end-round-description-first-line"));
-
- foreach (var uplink in EntityManager.EntityQuery<StoreComponent>(true))
- {
- var owner = uplink.AccountOwner;
- if (owner != null && _allOriginalNames.ContainsKey(owner.Value))
- {
- var tcbalance = _uplink.GetTCBalance(uplink);
-
- lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
- ("originalName", _allOriginalNames[owner.Value]),
- ("tcBalance", tcbalance)));
- }
- }
-
- ev.AddLine(string.Join('\n', lines));
- }
-
- public override void Started()
- {
- _restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
- _restarter.RestartTimer();
- _safeToEndRound = true;
- }
-
- public override void Ended()
- {
- }
-
- // It would be nice if this function were moved to some generic helpers class.
- private bool FindAnyIsolatedSpawnLocation(Mind.Mind ignoreMe, out EntityCoordinates bestTarget)
- {
- // Collate people to avoid...
- var existingPlayerPoints = new List<EntityCoordinates>();
- foreach (var player in _playerManager.ServerSessions)
- {
- var avoidMeMind = player.Data.ContentData()?.Mind;
- if ((avoidMeMind == null) || (avoidMeMind == ignoreMe))
- continue;
- var avoidMeEntity = avoidMeMind.OwnedEntity;
- if (avoidMeEntity == null)
- continue;
- if (TryComp(avoidMeEntity.Value, out MobStateComponent? mobState))
- {
- // Does have mob state component; if critical or dead, they don't really matter for spawn checks
- if (_mobStateSystem.IsCritical(avoidMeEntity.Value, mobState) || _mobStateSystem.IsDead(avoidMeEntity.Value, mobState))
- continue;
- }
- else
- {
- // Doesn't have mob state component. Assume something interesting is going on and don't count this as someone to avoid.
- continue;
- }
- existingPlayerPoints.Add(Transform(avoidMeEntity.Value).Coordinates);
- }
-
- // Iterate over each possible spawn point, comparing to the existing player points.
- // On failure, the returned target is the location that we're already at.
- var bestTargetDistanceFromNearest = -1.0f;
- // Need the random shuffle or it stuffs the first person into Atmospherics pretty reliably
- var ents = EntityManager.EntityQuery<SpawnPointComponent>().Select(x => x.Owner).ToList();
- _robustRandom.Shuffle(ents);
- var foundATarget = false;
- bestTarget = EntityCoordinates.Invalid;
-
- foreach (var entity in ents)
- {
- var transform = Transform(entity);
-
- if (transform.GridUid == null || transform.MapUid == null)
- continue;
-
- var position = _transformSystem.GetGridOrMapTilePosition(entity, transform);
-
- if (!_atmosphereSystem.IsTileMixtureProbablySafe(transform.GridUid.Value, transform.MapUid.Value, position))
- continue;
-
- var distanceFromNearest = float.PositiveInfinity;
- foreach (var existing in existingPlayerPoints)
- {
- if (Transform(entity).Coordinates.TryDistance(EntityManager, existing, out var dist))
- distanceFromNearest = Math.Min(distanceFromNearest, dist);
- }
- if (bestTargetDistanceFromNearest < distanceFromNearest)
- {
- bestTarget = Transform(entity).Coordinates;
- bestTargetDistanceFromNearest = distanceFromNearest;
- foundATarget = true;
- }
- }
- return foundATarget;
- }
-
-}
using System.Linq;
using Content.Server.Chat.Managers;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.NPC.Systems;
using Content.Server.Objectives.Interfaces;
using Content.Server.PDA.Ringer;
namespace Content.Server.GameTicking.Rules;
-public sealed class TraitorRuleSystem : GameRuleSystem
+public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IObjectivesManager _objectivesManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly FactionSystem _faction = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
private ISawmill _sawmill = default!;
- public override string Prototype => "Traitor";
-
- private readonly SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
- public List<TraitorRole> Traitors = new();
-
- private const string TraitorPrototypeID = "Traitor";
- private const string TraitorUplinkPresetId = "StorePresetUplink";
-
- public int TotalTraitors => Traitors.Count;
- public string[] Codewords = new string[3];
-
- private int _playersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
- private int _maxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
-
- public enum SelectionState
- {
- WaitingForSpawn = 0,
- ReadyToSelect = 1,
- SelectionMade = 2,
- }
-
- public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
- private TimeSpan _announceAt = TimeSpan.Zero;
- private Dictionary<IPlayerSession, HumanoidCharacterProfile> _startCandidates = new();
+ private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
+ private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
public override void Initialize()
{
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
}
- public override void Update(float frameTime)
+ protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
- base.Update(frameTime);
-
- if (SelectionStatus == SelectionState.ReadyToSelect && _gameTiming.CurTime >= _announceAt)
- DoTraitorStart();
- }
+ base.ActiveTick(uid, component, gameRule, frameTime);
- public override void Started(){}
-
- public override void Ended()
- {
- Traitors.Clear();
- _startCandidates.Clear();
- SelectionStatus = SelectionState.WaitingForSpawn;
+ if (component.SelectionStatus == TraitorRuleComponent.SelectionState.ReadyToSelect && _gameTiming.CurTime > component.AnnounceAt)
+ DoTraitorStart(component);
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
- MakeCodewords();
- if (!RuleAdded)
- return;
-
- var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
- if (!ev.Forced && ev.Players.Length < minPlayers)
+ var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var traitor, out var gameRule))
{
- _chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
- ev.Cancel();
- return;
- }
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- if (ev.Players.Length == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
- ev.Cancel();
+ MakeCodewords(traitor);
+
+ var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
+ if (!ev.Forced && ev.Players.Length < minPlayers)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players",
+ ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
+ ev.Cancel();
+ continue;
+ }
+
+ if (ev.Players.Length == 0)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
+ ev.Cancel();
+ }
}
}
- private void MakeCodewords()
+ private void MakeCodewords(TraitorRuleComponent component)
{
var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values;
var verbs = _prototypeManager.Index<DatasetPrototype>("verbs").Values;
var codewordPool = adjectives.Concat(verbs).ToList();
var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count);
- Codewords = new string[finalCodewordCount];
+ component.Codewords = new string[finalCodewordCount];
for (var i = 0; i < finalCodewordCount; i++)
{
- Codewords[i] = _random.PickAndTake(codewordPool);
+ component.Codewords[i] = _random.PickAndTake(codewordPool);
}
}
- private void DoTraitorStart()
+ private void DoTraitorStart(TraitorRuleComponent component)
{
- if (!_startCandidates.Any())
+ if (!component.StartCandidates.Any())
{
_sawmill.Error("Tried to start Traitor mode without any candidates.");
return;
}
- var numTraitors = MathHelper.Clamp(_startCandidates.Count / _playersPerTraitor, 1, _maxTraitors);
- var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
-
- var traitorPool = FindPotentialTraitors(_startCandidates);
+ var numTraitors = MathHelper.Clamp(component.StartCandidates.Count / PlayersPerTraitor, 1, MaxTraitors);
+ var traitorPool = FindPotentialTraitors(component.StartCandidates, component);
var selectedTraitors = PickTraitors(numTraitors, traitorPool);
foreach (var traitor in selectedTraitors)
+ {
MakeTraitor(traitor);
+ }
- SelectionStatus = SelectionState.SelectionMade;
+ component.SelectionStatus = TraitorRuleComponent.SelectionState.SelectionMade;
}
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
{
- if (!RuleAdded)
- return;
-
- foreach (var player in ev.Players)
+ var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var traitor, out var gameRule))
{
- if (!ev.Profiles.ContainsKey(player.UserId))
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
continue;
+ foreach (var player in ev.Players)
+ {
+ if (!ev.Profiles.ContainsKey(player.UserId))
+ continue;
- _startCandidates[player] = ev.Profiles[player.UserId];
- }
+ traitor.StartCandidates[player] = ev.Profiles[player.UserId];
+ }
- var delay = TimeSpan.FromSeconds(
- _cfg.GetCVar(CCVars.TraitorStartDelay) +
- _random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
+ var delay = TimeSpan.FromSeconds(
+ _cfg.GetCVar(CCVars.TraitorStartDelay) +
+ _random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
- _announceAt = _gameTiming.CurTime + delay;
+ traitor.AnnounceAt = _gameTiming.CurTime + delay;
- SelectionStatus = SelectionState.ReadyToSelect;
+ traitor.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToSelect;
+ }
}
- public List<IPlayerSession> FindPotentialTraitors(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates)
+ public List<IPlayerSession> FindPotentialTraitors(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates, TraitorRuleComponent component)
{
var list = new List<IPlayerSession>();
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
foreach (var player in list)
{
var profile = candidates[player];
- if (profile.AntagPreferences.Contains(TraitorPrototypeID))
+ if (profile.AntagPreferences.Contains(component.TraitorPrototypeId))
{
prefList.Add(player);
}
public bool MakeTraitor(IPlayerSession traitor)
{
+ var traitorRule = EntityQuery<TraitorRuleComponent>().FirstOrDefault();
+ if (traitorRule == null)
+ {
+ //todo fuck me this shit is awful
+ GameTicker.StartGameRule("traitor", out var ruleEntity);
+ traitorRule = EntityManager.GetComponent<TraitorRuleComponent>(ruleEntity);
+ }
+
var mind = traitor.Data.ContentData()?.Mind;
if (mind == null)
{
if (pda == null || !_uplink.AddUplink(mind.OwnedEntity.Value, startingBalance))
return false;
+
// add the ringtone uplink and get its code for greeting
var code = AddComp<RingerUplinkComponent>(pda.Value).Code;
- var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorPrototypeID);
+ var antagPrototype = _prototypeManager.Index<AntagPrototype>(traitorRule.TraitorPrototypeId);
var traitorRole = new TraitorRole(mind, antagPrototype);
mind.AddRole(traitorRole);
- Traitors.Add(traitorRole);
- traitorRole.GreetTraitor(Codewords, code);
+ traitorRule.Traitors.Add(traitorRole);
+ traitorRole.GreetTraitor(traitorRule.Codewords, code);
_faction.RemoveFaction(entity, "NanoTrasen", false);
_faction.AddFaction(entity, "Syndicate");
}
//give traitors their codewords and uplink code to keep in their character info menu
- traitorRole.Mind.Briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", Codewords)))
+ traitorRole.Mind.Briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", traitorRule.Codewords)))
+ "\n" + Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("", code)));
- _audioSystem.PlayGlobal(_addedSound, Filter.Empty().AddPlayer(traitor), false, AudioParams.Default);
+ _audioSystem.PlayGlobal(traitorRule.AddedSound, Filter.Empty().AddPlayer(traitor), false, AudioParams.Default);
return true;
}
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
{
- if (!RuleAdded)
- return;
- if (TotalTraitors >= _maxTraitors)
- return;
- if (!ev.LateJoin)
- return;
- if (!ev.Profile.AntagPreferences.Contains(TraitorPrototypeID))
- return;
+ var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var traitor, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
+ if (traitor.TotalTraitors >= MaxTraitors)
+ continue;
+ if (!ev.LateJoin)
+ continue;
+ if (!ev.Profile.AntagPreferences.Contains(traitor.TraitorPrototypeId))
+ continue;
- if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
- return;
+ if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
+ continue;
- if (!job.CanBeAntag)
- return;
+ if (!job.CanBeAntag)
+ continue;
- // Before the announcement is made, late-joiners are considered the same as players who readied.
- if (SelectionStatus < SelectionState.SelectionMade)
- {
- _startCandidates[ev.Player] = ev.Profile;
- return;
- }
+ // Before the announcement is made, late-joiners are considered the same as players who readied.
+ if (traitor.SelectionStatus < TraitorRuleComponent.SelectionState.SelectionMade)
+ {
+ traitor.StartCandidates[ev.Player] = ev.Profile;
+ continue;
+ }
- // the nth player we adjust our probabilities around
- int target = ((_playersPerTraitor * TotalTraitors) + 1);
+ // the nth player we adjust our probabilities around
+ var target = PlayersPerTraitor * traitor.TotalTraitors + 1;
- float chance = (1f / _playersPerTraitor);
+ var chance = 1f / PlayersPerTraitor;
- // If we have too many traitors, divide by how many players below target for next traitor we are.
- if (ev.JoinOrder < target)
- {
- chance /= (target - ev.JoinOrder);
- } else // Tick up towards 100% chance.
- {
- chance *= ((ev.JoinOrder + 1) - target);
- }
- if (chance > 1)
- chance = 1;
+ // If we have too many traitors, divide by how many players below target for next traitor we are.
+ if (ev.JoinOrder < target)
+ {
+ chance /= (target - ev.JoinOrder);
+ }
+ else // Tick up towards 100% chance.
+ {
+ chance *= ((ev.JoinOrder + 1) - target);
+ }
- // Now that we've calculated our chance, roll and make them a traitor if we roll under.
- // You get one shot.
- if (_random.Prob(chance))
- {
- MakeTraitor(ev.Player);
+ if (chance > 1)
+ chance = 1;
+
+ // Now that we've calculated our chance, roll and make them a traitor if we roll under.
+ // You get one shot.
+ if (_random.Prob(chance))
+ {
+ MakeTraitor(ev.Player);
+ }
}
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
- if (!RuleAdded)
- return;
-
- var result = Loc.GetString("traitor-round-end-result", ("traitorCount", Traitors.Count));
+ var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var traitor, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- result += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", Codewords))) + "\n";
+ var result = Loc.GetString("traitor-round-end-result", ("traitorCount", traitor.Traitors.Count));
- foreach (var traitor in Traitors)
- {
- var name = traitor.Mind.CharacterName;
- traitor.Mind.TryGetSession(out var session);
- var username = session?.Name;
+ result += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", traitor.Codewords))) +
+ "\n";
- var objectives = traitor.Mind.AllObjectives.ToArray();
- if (objectives.Length == 0)
+ foreach (var t in traitor.Traitors)
{
+ var name = t.Mind.CharacterName;
+ t.Mind.TryGetSession(out var session);
+ var username = session?.Name;
+
+ var objectives = t.Mind.AllObjectives.ToArray();
+ if (objectives.Length == 0)
+ {
+ if (username != null)
+ {
+ if (name == null)
+ result += "\n" + Loc.GetString("traitor-user-was-a-traitor", ("user", username));
+ else
+ result += "\n" + Loc.GetString("traitor-user-was-a-traitor-named", ("user", username),
+ ("name", name));
+ }
+ else if (name != null)
+ result += "\n" + Loc.GetString("traitor-was-a-traitor-named", ("name", name));
+
+ continue;
+ }
+
if (username != null)
{
if (name == null)
- result += "\n" + Loc.GetString("traitor-user-was-a-traitor", ("user", username));
+ result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives",
+ ("user", username));
else
- result += "\n" + Loc.GetString("traitor-user-was-a-traitor-named", ("user", username), ("name", name));
+ result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives-named",
+ ("user", username), ("name", name));
}
else if (name != null)
- result += "\n" + Loc.GetString("traitor-was-a-traitor-named", ("name", name));
-
- continue;
- }
-
- if (username != null)
- {
- if (name == null)
- result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives", ("user", username));
- else
- result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives-named", ("user", username), ("name", name));
- }
- else if (name != null)
- result += "\n" + Loc.GetString("traitor-was-a-traitor-with-objectives-named", ("name", name));
-
- foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
- {
- result += "\n" + Loc.GetString($"preset-traitor-objective-issuer-{objectiveGroup.Key}");
+ result += "\n" + Loc.GetString("traitor-was-a-traitor-with-objectives-named", ("name", name));
- foreach (var objective in objectiveGroup)
+ foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
{
- foreach (var condition in objective.Conditions)
+ result += "\n" + Loc.GetString($"preset-traitor-objective-issuer-{objectiveGroup.Key}");
+
+ foreach (var objective in objectiveGroup)
{
- var progress = condition.Progress;
- if (progress > 0.99f)
- {
- result += "\n- " + Loc.GetString(
- "traitor-objective-condition-success",
- ("condition", condition.Title),
- ("markupColor", "green")
- );
- }
- else
+ foreach (var condition in objective.Conditions)
{
- result += "\n- " + Loc.GetString(
- "traitor-objective-condition-fail",
- ("condition", condition.Title),
- ("progress", (int) (progress * 100)),
- ("markupColor", "red")
- );
+ var progress = condition.Progress;
+ if (progress > 0.99f)
+ {
+ result += "\n- " + Loc.GetString(
+ "traitor-objective-condition-success",
+ ("condition", condition.Title),
+ ("markupColor", "green")
+ );
+ }
+ else
+ {
+ result += "\n- " + Loc.GetString(
+ "traitor-objective-condition-fail",
+ ("condition", condition.Title),
+ ("progress", (int) (progress * 100)),
+ ("markupColor", "red")
+ );
+ }
}
}
}
}
+
+ ev.AddLine(result);
}
- ev.AddLine(result);
}
- public IEnumerable<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind)
+ public List<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind)
{
- var traitors = Traitors;
- List<TraitorRole> removeList = new();
+ List<TraitorRole> allTraitors = new();
+ foreach (var traitor in EntityQuery<TraitorRuleComponent>())
+ {
+ foreach (var role in GetOtherTraitorsAliveAndConnected(ourMind, traitor))
+ {
+ if (!allTraitors.Contains(role))
+ allTraitors.Add(role);
+ }
+ }
- return Traitors // don't want
- .Where(t => t.Mind is not null) // no mind
+ return allTraitors;
+ }
+
+ public List<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind, TraitorRuleComponent component)
+ {
+ return component.Traitors // don't want
.Where(t => t.Mind.OwnedEntity is not null) // no entity
.Where(t => t.Mind.Session is not null) // player disconnected
.Where(t => t.Mind != ourMind) // ourselves
.Where(t => _mobStateSystem.IsAlive((EntityUid) t.Mind.OwnedEntity!)) // dead
- .Where(t => t.Mind.CurrentEntity == t.Mind.OwnedEntity); // not in original body
+ .Where(t => t.Mind.CurrentEntity == t.Mind.OwnedEntity).ToList(); // not in original body
}
}
using Content.Server.Chat.Managers;
using Content.Server.Disease;
using Content.Server.Disease.Components;
-using Content.Server.Humanoid;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind.Components;
using Content.Server.Players;
using Content.Server.Popups;
namespace Content.Server.GameTicking.Rules;
-public sealed class ZombieRuleSystem : GameRuleSystem
+public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
- private Dictionary<string, string> _initialInfectedNames = new();
-
- public override string Prototype => "Zombie";
-
- private const string PatientZeroPrototypeID = "InitialInfected";
- private const string InitialZombieVirusPrototype = "PassiveZombieVirus";
- private const string ZombifySelfActionPrototype = "TurnUndead";
-
public override void Initialize()
{
base.Initialize();
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
- if (!RuleAdded)
- return;
-
- //this is just the general condition thing used for determining the win/lose text
- var percent = GetInfectedPercentage(out var livingHumans);
-
- if (percent <= 0)
- ev.AddLine(Loc.GetString("zombie-round-end-amount-none"));
- else if (percent <= 0.25)
- ev.AddLine(Loc.GetString("zombie-round-end-amount-low"));
- else if (percent <= 0.5)
- ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
- else if (percent < 1)
- ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
- else
- ev.AddLine(Loc.GetString("zombie-round-end-amount-all"));
-
- ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", _initialInfectedNames.Count)));
- foreach (var player in _initialInfectedNames)
+ foreach (var zombie in EntityQuery<ZombieRuleComponent>())
{
- ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
- ("name", player.Key),
- ("username", player.Value)));
- }
+ //this is just the general condition thing used for determining the win/lose text
+ var percent = GetInfectedPercentage(out var livingHumans);
+
+ if (percent <= 0)
+ ev.AddLine(Loc.GetString("zombie-round-end-amount-none"));
+ else if (percent <= 0.25)
+ ev.AddLine(Loc.GetString("zombie-round-end-amount-low"));
+ else if (percent <= 0.5)
+ ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
+ else if (percent < 1)
+ ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
+ else
+ ev.AddLine(Loc.GetString("zombie-round-end-amount-all"));
- //Gets a bunch of the living players and displays them if they're under a threshold.
- //InitialInfected is used for the threshold because it scales with the player count well.
- if (livingHumans.Count > 0 && livingHumans.Count <= _initialInfectedNames.Count)
- {
- ev.AddLine("");
- ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count)));
- foreach (var survivor in livingHumans)
+ ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", zombie.InitialInfectedNames.Count)));
+ foreach (var player in zombie.InitialInfectedNames)
+ {
+ ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
+ ("name", player.Key),
+ ("username", player.Value)));
+ }
+
+ //Gets a bunch of the living players and displays them if they're under a threshold.
+ //InitialInfected is used for the threshold because it scales with the player count well.
+ if (livingHumans.Count > 0 && livingHumans.Count <= zombie.InitialInfectedNames.Count)
{
- var meta = MetaData(survivor);
- var username = string.Empty;
- if (TryComp<MindComponent>(survivor, out var mindcomp))
- if (mindcomp.Mind != null && mindcomp.Mind.Session != null)
- username = mindcomp.Mind.Session.Name;
-
- ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
- ("name", meta.EntityName),
- ("username", username)));
+ ev.AddLine("");
+ ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count)));
+ foreach (var survivor in livingHumans)
+ {
+ var meta = MetaData(survivor);
+ var username = string.Empty;
+ if (TryComp<MindComponent>(survivor, out var mindcomp))
+ if (mindcomp.Mind != null && mindcomp.Mind.Session != null)
+ username = mindcomp.Mind.Session.Name;
+
+ ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
+ ("name", meta.EntityName),
+ ("username", username)));
+ }
}
}
}
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
{
- if (!RuleAdded)
- return;
-
- _initialInfectedNames = new();
-
- InfectInitialPlayers();
+ var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var zombies, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
+ InfectInitialPlayers(zombies);
+ }
}
/// <remarks>
/// </remarks>
private void OnMobStateChanged(MobStateChangedEvent ev)
{
- if (!RuleAdded)
- return;
CheckRoundEnd(ev.Target);
}
private void OnEntityZombified(EntityZombifiedEvent ev)
{
- if (!RuleAdded)
- return;
CheckRoundEnd(ev.Target);
}
/// <param name="target">depending on this uid, we should care about the round ending</param>
private void CheckRoundEnd(EntityUid target)
{
- //we only care about players, not monkeys and such.
- if (!HasComp<HumanoidAppearanceComponent>(target))
- return;
+ var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var zombies, out var gameRule))
+ {
+ if (GameTicker.IsGameRuleActive(uid, gameRule))
+ continue;
+
+ //we only care about players, not monkeys and such.
+ if (!HasComp<HumanoidAppearanceComponent>(target))
+ continue;
- var percent = GetInfectedPercentage(out var num);
- if (num.Count == 1) //only one human left. spooky
- _popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], num[0]);
- if (percent >= 1) //oops, all zombies
- _roundEndSystem.EndRound();
+ var percent = GetInfectedPercentage(out var num);
+ if (num.Count == 1) //only one human left. spooky
+ _popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], num[0]);
+ if (percent >= 1) //oops, all zombies
+ _roundEndSystem.EndRound();
+ }
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
- if (!RuleAdded)
- return;
-
- var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
- if (!ev.Forced && ev.Players.Length < minPlayers)
+ var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var zombies, out var gameRule))
{
- _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
- ev.Cancel();
- return;
- }
+ if (!GameTicker.IsGameRuleAdded(uid, gameRule))
+ continue;
- if (ev.Players.Length == 0)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready"));
- ev.Cancel();
+ var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
+ if (!ev.Forced && ev.Players.Length < minPlayers)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
+ ev.Cancel();
+ continue;
+ }
+
+ if (ev.Players.Length == 0)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready"));
+ ev.Cancel();
+ }
}
}
- public override void Started()
+ protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- //this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly
- InfectInitialPlayers();
+ base.Started(uid, component, gameRule, args);
+ InfectInitialPlayers(component);
}
- public override void Ended() { }
-
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
{
_zombify.ZombifyEntity(uid);
- var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
+ var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
_action.RemoveAction(uid, action);
}
/// allowing this gamemode to be started midround. As such, it doesn't need
/// any information besides just running.
/// </remarks>
- private void InfectInitialPlayers()
+ private void InfectInitialPlayers(ZombieRuleComponent component)
{
var allPlayers = _playerManager.ServerSessions.ToList();
var playerList = new List<IPlayerSession>();
playerList.Add(player);
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
- if (pref.AntagPreferences.Contains(PatientZeroPrototypeID))
+ if (pref.AntagPreferences.Contains(component.PatientZeroPrototypeID))
prefList.Add(player);
}
}
DebugTools.AssertNotNull(mind.OwnedEntity);
- mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(PatientZeroPrototypeID)));
+ mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(component.PatientZeroPrototypeID)));
var inCharacterName = string.Empty;
if (mind.OwnedEntity != null)
{
- _diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, InitialZombieVirusPrototype);
+ _diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, component.InitialZombieVirusPrototype);
inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName;
- var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
+ var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
_action.AddAction(mind.OwnedEntity.Value, action, null);
}
//gets the names now in case the players leave.
//this gets unhappy if people with the same name get chose. Probably shouldn't happen.
- _initialInfectedNames.Add(inCharacterName, mind.Session.Name);
+ component.InitialInfectedNames.Add(inCharacterName, mind.Session.Name);
// I went all the way to ChatManager.cs and all i got was this lousy T-shirt
// You got a free T-shirt!?!?
public IObjectiveCondition GetAssigned(Mind.Mind mind)
{
var entityMgr = IoCManager.Resolve<IEntityManager>();
- var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
- if (traitors.Count == 0) return new EscapeShuttleCondition{}; //You were made a traitor by admins, and are the first/only.
+ var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
+ if (traitors.Count == 0)
+ return new EscapeShuttleCondition(); //You were made a traitor by admins, and are the first/only.
return new RandomTraitorAliveCondition { _target = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Mind };
}
public IObjectiveCondition GetAssigned(Mind.Mind mind)
{
+ //todo shit of a fuck
var entityMgr = IoCManager.Resolve<IEntityManager>();
+
var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
List<Traitor.TraitorRole> removeList = new();
{
foreach (var condition in objective.Conditions)
{
- if (condition.GetType() == typeof(RandomTraitorProgressCondition))
+ if (condition is RandomTraitorProgressCondition)
{
removeList.Add(traitor);
}
public bool CanBeAssigned(Mind.Mind mind)
{
- return EntitySystem.Get<TraitorRuleSystem>().TotalTraitors >= _requiredTraitors;
+ return EntitySystem.Get<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).Count >= _requiredTraitors;
}
}
}
-using Content.Server.GameTicking.Rules;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
public class ConditionalSpawnerComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("prototypes", customTypeSerializer:typeof(PrototypeIdListSerializer<EntityPrototype>))]
+ [DataField("prototypes", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> Prototypes { get; set; } = new();
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("gameRules", customTypeSerializer:typeof(PrototypeIdListSerializer<GameRulePrototype>))]
+ [DataField("gameRules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public readonly List<string> GameRules = new();
[ViewVariables(VVAccess.ReadWrite)]
using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Spawners.Components;
using JetBrains.Annotations;
using Robust.Shared.Random;
private void OnCondSpawnMapInit(EntityUid uid, ConditionalSpawnerComponent component, MapInitEvent args)
{
- TrySpawn(component);
+ TrySpawn(uid, component);
}
private void OnRandSpawnMapInit(EntityUid uid, RandomSpawnerComponent component, MapInitEvent args)
{
- Spawn(component);
- EntityManager.QueueDeleteEntity(uid);
+ Spawn(uid, component);
+ QueueDel(uid);
}
- private void OnRuleStarted(GameRuleStartedEvent args)
+ private void OnRuleStarted(ref GameRuleStartedEvent args)
{
- foreach (var spawner in EntityManager.EntityQuery<ConditionalSpawnerComponent>())
+ var query = EntityQueryEnumerator<ConditionalSpawnerComponent>();
+ while (query.MoveNext(out var uid, out var spawner))
{
- RuleStarted(spawner, args);
+ RuleStarted(uid, spawner, args);
}
}
- public void RuleStarted(ConditionalSpawnerComponent component, GameRuleStartedEvent obj)
+ public void RuleStarted(EntityUid uid, ConditionalSpawnerComponent component, GameRuleStartedEvent obj)
{
- if(component.GameRules.Contains(obj.Rule.ID))
- Spawn(component);
+ if (component.GameRules.Contains(obj.RuleId))
+ Spawn(uid, component);
}
- private void TrySpawn(ConditionalSpawnerComponent component)
+ private void TrySpawn(EntityUid uid, ConditionalSpawnerComponent component)
{
if (component.GameRules.Count == 0)
{
- Spawn(component);
+ Spawn(uid, component);
return;
}
foreach (var rule in component.GameRules)
{
- if (!_ticker.IsGameRuleStarted(rule)) continue;
- Spawn(component);
+ if (!_ticker.IsGameRuleActive(rule))
+ continue;
+ Spawn(uid, component);
return;
}
}
- private void Spawn(ConditionalSpawnerComponent component)
+ private void Spawn(EntityUid uid, ConditionalSpawnerComponent component)
{
if (component.Chance != 1.0f && !_robustRandom.Prob(component.Chance))
return;
if (component.Prototypes.Count == 0)
{
- Logger.Warning($"Prototype list in ConditionalSpawnComponent is empty! Entity: {component.Owner}");
+ Logger.Warning($"Prototype list in ConditionalSpawnComponent is empty! Entity: {ToPrettyString(uid)}");
return;
}
- if (!Deleted(component.Owner))
- EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), Transform(component.Owner).Coordinates);
+ if (!Deleted(uid))
+ EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), Transform(uid).Coordinates);
}
- private void Spawn(RandomSpawnerComponent component)
+ private void Spawn(EntityUid uid, RandomSpawnerComponent component)
{
if (component.RarePrototypes.Count > 0 && (component.RareChance == 1.0f || _robustRandom.Prob(component.RareChance)))
{
- EntityManager.SpawnEntity(_robustRandom.Pick(component.RarePrototypes), Transform(component.Owner).Coordinates);
+ EntityManager.SpawnEntity(_robustRandom.Pick(component.RarePrototypes), Transform(uid).Coordinates);
return;
}
if (component.Prototypes.Count == 0)
{
- Logger.Warning($"Prototype list in RandomSpawnerComponent is empty! Entity: {component.Owner}");
+ Logger.Warning($"Prototype list in RandomSpawnerComponent is empty! Entity: {ToPrettyString(uid)}");
return;
}
- if (Deleted(component.Owner)) return;
+ if (Deleted(uid))
+ return;
var offset = component.Offset;
var xOffset = _robustRandom.NextFloat(-offset, offset);
var yOffset = _robustRandom.NextFloat(-offset, offset);
- var coordinates = Transform(component.Owner).Coordinates.Offset(new Vector2(xOffset, yOffset));
+ var coordinates = Transform(uid).Coordinates.Offset(new Vector2(xOffset, yOffset));
EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), coordinates);
}
-using System.Linq;
using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Configurations;
-using Content.Shared.CCVar;
-using Content.Shared.GameTicking;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
using JetBrains.Annotations;
-using Robust.Server.Player;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.StationEvents
/// game presets use.
/// </summary>
[UsedImplicitly]
- public sealed class BasicStationEventSchedulerSystem : GameRuleSystem
+ public sealed class BasicStationEventSchedulerSystem : GameRuleSystem<BasicStationEventSchedulerComponent>
{
- public override string Prototype => "BasicStationEventScheduler";
-
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EventManagerSystem _event = default!;
- 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
- [ViewVariables(VVAccess.ReadWrite)]
- private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
-
- public override void Started() { }
-
- public override void Ended()
+ protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
+ GameRuleEndedEvent args)
{
- _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
+ component.TimeUntilNextEvent = BasicStationEventSchedulerComponent.MinimumTimeUntilFirstEvent;
}
+
public override void Update(float frameTime)
{
base.Update(frameTime);
- if (!RuleStarted || !_event.EventsEnabled)
+ if (!_event.EventsEnabled)
return;
- if (_timeUntilNextEvent > 0)
+ var query = EntityQueryEnumerator<BasicStationEventSchedulerComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var eventScheduler, out var gameRule))
{
- _timeUntilNextEvent -= frameTime;
- return;
- }
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ continue;
- _event.RunRandomEvent();
- ResetTimer();
+ if (eventScheduler.TimeUntilNextEvent > 0)
+ {
+ eventScheduler.TimeUntilNextEvent -= frameTime;
+ return;
+ }
+
+ _event.RunRandomEvent();
+ ResetTimer(eventScheduler);
+ }
}
/// <summary>
/// Reset the event timer once the event is done.
/// </summary>
- private void ResetTimer()
+ private void ResetTimer(BasicStationEventSchedulerComponent component)
{
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
- _timeUntilNextEvent = _random.Next(300, 1500);
+ component.TimeUntilNextEvent = _random.Next(300, 1500);
}
}
}
--- /dev/null
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.StationEvents.Components;
+
+/// <summary>
+/// Used an event that spawns an anomaly somewhere random on the map.
+/// </summary>
+[RegisterComponent, Access(typeof(AnomalySpawnRule))]
+public sealed class AnomalySpawnRuleComponent : Component
+{
+ [DataField("anomalySpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+ public string AnomalySpawnerPrototype = "RandomAnomalySpawner";
+}
--- /dev/null
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(BasicStationEventSchedulerSystem))]
+public sealed class BasicStationEventSchedulerComponent : Component
+{
+ public 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
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float TimeUntilNextEvent = MinimumTimeUntilFirstEvent;
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.StationEvents.Components;
+
+/// <summary>
+/// This is used for an event that spawns an artifact
+/// somewhere random on the station.
+/// </summary>
+[RegisterComponent, Access(typeof(BluespaceArtifactRule))]
+public sealed class BluespaceArtifactRuleComponent : Component
+{
+ [DataField("artifactSpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+ public string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
+
+ [DataField("artifactFlashPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+ public string ArtifactFlashPrototype = "EffectFlashBluespace";
+
+ [DataField("possibleSightings")]
+ public List<string> PossibleSighting = new()
+ {
+ "bluespace-artifact-sighting-1",
+ "bluespace-artifact-sighting-2",
+ "bluespace-artifact-sighting-3",
+ "bluespace-artifact-sighting-4",
+ "bluespace-artifact-sighting-5",
+ "bluespace-artifact-sighting-6",
+ "bluespace-artifact-sighting-7"
+ };
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(BluespaceLockerRule))]
+public sealed class BluespaceLockerRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(BreakerFlipRule))]
+public sealed class BreakerFlipRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(BureaucraticErrorRule))]
+public sealed class BureaucraticErrorRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(DiseaseOutbreakRule))]
+public sealed class DiseaseOutbreakRuleComponent : Component
+{
+ /// <summary>
+ /// Disease prototypes I decided were not too deadly for a random event
+ /// </summary>
+ /// <remarks>
+ /// Fire name
+ /// </remarks>
+ [DataField("notTooSeriousDiseases")]
+ public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
+ {
+ "SpaceCold",
+ "VanAusdallsRobovirus",
+ "VentCough",
+ "AMIV",
+ "SpaceFlu",
+ "BirdFlew",
+ "TongueTwister"
+ };
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(FalseAlarmRule))]
+public sealed class FalseAlarmRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+using Content.Shared.Atmos;
+using Robust.Shared.Map;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(GasLeakRule))]
+public sealed class GasLeakRuleComponent : Component
+{
+ public readonly Gas[] LeakableGases =
+ {
+ Gas.Miasma,
+ Gas.Plasma,
+ Gas.Tritium,
+ Gas.Frezon,
+ };
+
+ /// <summary>
+ /// Running cooldown of how much time until another leak.
+ /// </summary>
+ public float TimeUntilLeak;
+
+ /// <summary>
+ /// How long between more gas being added to the tile.
+ /// </summary>
+ public float LeakCooldown = 1.0f;
+
+ // Event variables
+ public EntityUid TargetStation;
+ public EntityUid TargetGrid;
+ public Vector2i TargetTile;
+ public EntityCoordinates TargetCoords;
+ public bool FoundTile;
+ public Gas LeakGas;
+ public float MolesPerSecond;
+ public readonly int MinimumMolesPerSecond = 20;
+
+ /// <summary>
+ /// Don't want to make it too fast to give people time to flee.
+ /// </summary>
+ public int MaximumMolesPerSecond = 50;
+
+ public int MinimumGas = 250;
+ public int MaximumGas = 1000;
+ public float SparkChance = 0.05f;
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(KudzuGrowthRule))]
+public sealed class KudzuGrowthRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(LoneOpsSpawnRule))]
+public sealed class LoneOpsSpawnRuleComponent : Component
+{
+ [DataField("loneOpsShuttlePath")]
+ public string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
+
+ [DataField("gameRuleProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+ public string GameRuleProto = "Nukeops";
+
+ [DataField("additionalRule")]
+ public EntityUid? AdditionalRule;
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(MeteorSwarmRule))]
+public sealed class MeteorSwarmRuleComponent : Component
+{
+ public float _cooldown;
+
+ /// <summary>
+ /// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
+ /// </summary>
+ public int _waveCounter;
+
+ public int MinimumWaves = 3;
+ public int MaximumWaves = 8;
+
+ public float MinimumCooldown = 10f;
+ public float MaximumCooldown = 60f;
+
+ public int MeteorsPerWave = 5;
+ public float MeteorVelocity = 10f;
+ public float MaxAngularVelocity = 0.25f;
+ public float MinAngularVelocity = -0.25f;
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(MouseMigrationRule))]
+public sealed class MouseMigrationRuleComponent : Component
+{
+ [DataField("spawnedPrototypeChoices")]
+ public List<string> SpawnedPrototypeChoices = new() //we double up for that ez fake probability
+ {
+ "MobMouse",
+ "MobMouse1",
+ "MobMouse2",
+ "MobRatServant"
+ };
+}
--- /dev/null
+using System.Threading;
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(PowerGridCheckRule))]
+public sealed class PowerGridCheckRuleComponent : Component
+{
+ public CancellationTokenSource? AnnounceCancelToken;
+
+ public readonly List<EntityUid> Powered = new();
+ public readonly List<EntityUid> Unpowered = new();
+
+ public float SecondsUntilOff = 30.0f;
+
+ public int NumberPerSecond = 0;
+ public float UpdateRate => 1.0f / NumberPerSecond;
+ public float FrameTimeAccumulator = 0.0f;
+}
--- /dev/null
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(RampingStationEventSchedulerSystem))]
+public sealed class RampingStationEventSchedulerComponent : Component
+{
+ [DataField("endTime"), ViewVariables(VVAccess.ReadWrite)]
+ public float EndTime;
+
+ [DataField("maxChaos"), ViewVariables(VVAccess.ReadWrite)]
+ public float MaxChaos;
+
+ [DataField("startingChaos"), ViewVariables(VVAccess.ReadWrite)]
+ public float StartingChaos;
+
+ [DataField("timeUntilNextEvent"), ViewVariables(VVAccess.ReadWrite)]
+ public float TimeUntilNextEvent;
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(RandomSentienceRule))]
+public sealed class RandomSentienceRuleComponent : Component
+{
+
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(RevenantSpawnRule))]
+public sealed class RevenantSpawnRuleComponent : Component
+{
+ [DataField("revenantPrototype")]
+ public string RevenantPrototype = "MobRevenant";
+}
-namespace Content.Server.StationEvents.Components;
+using Content.Server.StationEvents.Events;
-[RegisterComponent]
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(RandomSentienceRule))]
public sealed class SentienceTargetComponent : Component
{
[DataField("flavorKind", required: true)]
+using Content.Server.StationEvents.Events;
using Content.Shared.Radio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
-namespace Content.Server.GameTicking.Rules.Configurations;
+namespace Content.Server.StationEvents.Components;
/// <summary>
/// Solar Flare event specific configuration
/// </summary>
-public sealed class SolarFlareEventRuleConfiguration : StationEventRuleConfiguration
+[RegisterComponent, Access(typeof(SolarFlareRule))]
+public sealed class SolarFlareRuleComponent : Component
{
- /// <summary>
- /// In seconds, most early moment event can end
- /// </summary>
- [DataField("minEndAfter")]
- public int MinEndAfter;
-
- /// <summary>
- /// In seconds, most late moment event can end
- /// </summary>
- [DataField("maxEndAfter")]
- public int MaxEndAfter;
-
/// <summary>
/// If true, only headsets affected, but e.g. handheld radio will still work
/// </summary>
/// </summary>
[DataField("doorToggleChancePerSecond")]
public float DoorToggleChancePerSecond;
-}
\ No newline at end of file
+}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(SpiderSpawnRule))]
+public sealed class SpiderSpawnRuleComponent : Component
+{
+
+}
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Server.GameTicking.Rules.Configurations;
+namespace Content.Server.StationEvents.Components;
/// <summary>
-/// Defines a configuration for a given station event game rule, since all station events are just
-/// game rules.
+/// Defines basic data for a station event
/// </summary>
-[UsedImplicitly]
-public class StationEventRuleConfiguration : GameRuleConfiguration
+[RegisterComponent]
+public sealed class StationEventComponent : Component
{
- [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 int ReoccurrenceDelay = 30;
/// <summary>
- /// When in the lifetime to start the event.
+ /// How long after being added does the event start
/// </summary>
- [DataField("startAfter")]
- public float StartAfter;
+ [DataField("startDelay")]
+ public TimeSpan StartDelay = TimeSpan.Zero;
/// <summary>
- /// When in the lifetime to end the event..
+ /// How long the event lasts.
/// </summary>
- [DataField("endAfter")]
- public float EndAfter = float.MaxValue;
+ [DataField("duration")]
+ public TimeSpan Duration = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// The max amount of time the event lasts.
+ /// </summary>
+ [DataField("maxDuration")]
+ public TimeSpan? MaxDuration;
/// <summary>
/// How many players need to be present on station for the event to run
/// </summary>
[DataField("maxOccurrences")]
public int? MaxOccurrences;
+
+ /// <summary>
+ /// When the station event starts.
+ /// </summary>
+ [DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan StartTime;
+
+ /// <summary>
+ /// When the station event starts.
+ /// </summary>
+ [DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan EndTime;
}
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(VentClogRule))]
+public sealed class VentClogRuleComponent : Component
+{
+ [DataField("safeishVentChemicals")]
+ public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
+ {
+ "Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
+ };
+
+}
-namespace Content.Server.StationEvents.Components;
+using Content.Server.StationEvents.Events;
-[RegisterComponent]
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(VentClogRule))]
public sealed class VentCritterSpawnLocationComponent : Component
{
--- /dev/null
+using Content.Server.StationEvents.Events;
+
+namespace Content.Server.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(VentCrittersRule))]
+public sealed class VentCrittersRuleComponent : Component
+{
+ [DataField("spawnedPrototypeChoices")]
+ public List<string> SpawnedPrototypeChoices = new()
+ {
+ "MobMouse",
+ "MobMouse1",
+ "MobMouse2"
+ };
+}
using System.Linq;
using Content.Server.GameTicking;
-using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Configurations;
+using Content.Server.StationEvents.Components;
using Content.Shared.CCVar;
-using Content.Shared.GameTicking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
_sawmill = Logger.GetSawmill("events");
_configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true);
+
+ SubscribeLocalEvent<StationEventComponent, EntityUnpausedEvent>(OnUnpaused);
+ }
+
+ private void OnUnpaused(EntityUid uid, StationEventComponent component, ref EntityUnpausedEvent args)
+ {
+ component.StartTime += args.PausedTime;
+ component.EndTime += args.PausedTime;
}
public override void Shutdown()
{
var randomEvent = PickRandomEvent();
- if (randomEvent == null
- || !_prototype.TryIndex<GameRulePrototype>(randomEvent.Id, out var proto))
+ if (randomEvent == null)
{
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
_sawmill.Error(errStr);
return errStr;
}
- GameTicker.AddGameRule(proto);
- var str = Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Id));
+ var ent = GameTicker.AddGameRule(randomEvent);
+ var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent)));
_sawmill.Info(str);
return str;
}
/// <summary>
/// Randomly picks a valid event.
/// </summary>
- public StationEventRuleConfiguration? PickRandomEvent()
+ public string? PickRandomEvent()
{
var availableEvents = AvailableEvents();
_sawmill.Info($"Picking from {availableEvents.Count} total available events");
/// Pick a random event from the available events at this time, also considering their weightings.
/// </summary>
/// <returns></returns>
- private StationEventRuleConfiguration? FindEvent(List<StationEventRuleConfiguration> availableEvents)
+ private string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
{
if (availableEvents.Count == 0)
{
var sumOfWeights = 0;
- foreach (var stationEvent in availableEvents)
+ foreach (var stationEvent in availableEvents.Values)
{
sumOfWeights += (int) stationEvent.Weight;
}
sumOfWeights = _random.Next(sumOfWeights);
- foreach (var stationEvent in availableEvents)
+ foreach (var (proto, stationEvent) in availableEvents)
{
sumOfWeights -= (int) stationEvent.Weight;
if (sumOfWeights <= 0)
{
- return stationEvent;
+ return proto.ID;
}
}
/// </summary>
/// <param name="ignoreEarliestStart"></param>
/// <returns></returns>
- private List<StationEventRuleConfiguration> AvailableEvents(bool ignoreEarliestStart = false)
+ private Dictionary<EntityPrototype, StationEventComponent> 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 currentTime = !ignoreEarliestStart
+ ? GameTicker.RoundDuration()
+ : TimeSpan.Zero;
- var result = new List<StationEventRuleConfiguration>();
+ var result = new Dictionary<EntityPrototype, StationEventComponent>();
- foreach (var stationEvent in AllEvents())
+ foreach (var (proto, stationEvent) in AllEvents())
{
- if (CanRun(stationEvent, playerCount, currentTime))
+ if (CanRun(proto, stationEvent, playerCount, currentTime))
{
- _sawmill.Debug($"Adding event {stationEvent.Id} to possibilities");
- result.Add(stationEvent);
+ _sawmill.Debug($"Adding event {proto.ID} to possibilities");
+ result.Add(proto, stationEvent);
}
}
return result;
}
- private IEnumerable<StationEventRuleConfiguration> AllEvents()
+ public Dictionary<EntityPrototype, StationEventComponent> AllEvents()
+ {
+ var allEvents = new Dictionary<EntityPrototype, StationEventComponent>();
+ foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
+ {
+ if (prototype.Abstract)
+ continue;
+
+ if (!prototype.TryGetComponent<StationEventComponent>(out var stationEvent))
+ continue;
+
+ allEvents.Add(prototype, stationEvent);
+ }
+
+ return allEvents;
+ }
+
+ private int GetOccurrences(EntityPrototype stationEvent)
{
- return _prototype.EnumeratePrototypes<GameRulePrototype>()
- .Where(p => p.Configuration is StationEventRuleConfiguration)
- .Select(p => (StationEventRuleConfiguration) p.Configuration);
+ return GetOccurrences(stationEvent.ID);
}
- private int GetOccurrences(StationEventRuleConfiguration stationEvent)
+ private int GetOccurrences(string stationEvent)
{
- return GameTicker.AllPreviousGameRules.Count(p => p.Item2.ID == stationEvent.Id);
+ return GameTicker.AllPreviousGameRules.Count(p => p.Item2 == stationEvent);
}
- public TimeSpan TimeSinceLastEvent(StationEventRuleConfiguration? stationEvent)
+ public TimeSpan TimeSinceLastEvent(EntityPrototype stationEvent)
{
foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse())
{
- if (rule.Configuration is not StationEventRuleConfiguration)
- continue;
-
- if (stationEvent == null || rule.ID == stationEvent.Id)
+ if (rule == stationEvent.ID)
return time;
}
return TimeSpan.Zero;
}
- private bool CanRun(StationEventRuleConfiguration stationEvent, int playerCount, TimeSpan currentTime)
+ private bool CanRun(EntityPrototype prototype, StationEventComponent stationEvent, int playerCount, TimeSpan currentTime)
{
- if (GameTicker.IsGameRuleStarted(stationEvent.Id))
+ if (GameTicker.IsGameRuleActive(prototype.ID))
return false;
- if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(stationEvent) >= stationEvent.MaxOccurrences.Value)
+ if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(prototype) >= stationEvent.MaxOccurrences.Value)
{
return false;
}
return false;
}
- var lastRun = TimeSinceLastEvent(stationEvent);
+ var lastRun = TimeSinceLastEvent(prototype);
if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes <
stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes)
{
using System.Linq;
using Content.Server.Anomaly;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Station.Components;
+using Content.Server.StationEvents.Components;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
-public sealed class AnomalySpawn : StationEventSystem
+public sealed class AnomalySpawnRule : StationEventSystem<AnomalySpawnRuleComponent>
{
- [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AnomalySystem _anomaly = default!;
- public override string Prototype => "AnomalySpawn";
-
- public readonly string AnomalySpawnerPrototype = "RandomAnomalySpawner";
-
- public override void Added()
+ protected override void Added(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
- base.Added();
+ base.Added(uid, component, gameRule, args);
var str = Loc.GetString("anomaly-spawn-event-announcement",
- ("sighting", Loc.GetString($"anomaly-spawn-sighting-{_random.Next(1, 6)}")));
+ ("sighting", Loc.GetString($"anomaly-spawn-sighting-{RobustRandom.Next(1, 6)}")));
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
}
- public override void Started()
+ protected override void Started(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0)
return; // No stations
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 2));
for (var i = 0; i < amountToSpawn; i++)
{
- _anomaly.SpawnOnRandomGridLocation(grid.Value, AnomalySpawnerPrototype);
+ _anomaly.SpawnOnRandomGridLocation(grid.Value, component.AnomalySpawnerPrototype);
}
}
}
+++ /dev/null
-using Robust.Shared.Random;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class BluespaceArtifact : StationEventSystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
-
- public override string Prototype => "BluespaceArtifact";
-
- public readonly string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
- public readonly string ArtifactFlashPrototype = "EffectFlashBluespace";
-
- public readonly List<string> PossibleSighting = new()
- {
- "bluespace-artifact-sighting-1",
- "bluespace-artifact-sighting-2",
- "bluespace-artifact-sighting-3",
- "bluespace-artifact-sighting-4",
- "bluespace-artifact-sighting-5",
- "bluespace-artifact-sighting-6",
- "bluespace-artifact-sighting-7"
- };
-
- public override void Added()
- {
- base.Added();
-
- var str = Loc.GetString("bluespace-artifact-event-announcement",
- ("sighting", Loc.GetString(_random.Pick(PossibleSighting))));
- ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
- }
-
- public override void Started()
- {
- base.Started();
- var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
- for (var i = 0; i < amountToSpawn; i++)
- {
- if (!TryFindRandomTile(out _, out _, out _, out var coords))
- return;
-
- EntityManager.SpawnEntity(ArtifactSpawnerPrototype, coords);
- EntityManager.SpawnEntity(ArtifactFlashPrototype, coords);
-
- Sawmill.Info($"Spawning random artifact at {coords}");
- }
- }
-}
--- /dev/null
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+using Robust.Shared.Random;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class BluespaceArtifactRule : StationEventSystem<BluespaceArtifactRuleComponent>
+{
+ protected override void Added(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
+ {
+ base.Added(uid, component, gameRule, args);
+
+ var str = Loc.GetString("bluespace-artifact-event-announcement",
+ ("sighting", Loc.GetString(RobustRandom.Pick(component.PossibleSighting))));
+ ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
+ }
+
+ protected override void Started(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
+ for (var i = 0; i < amountToSpawn; i++)
+ {
+ if (!TryFindRandomTile(out _, out _, out _, out var coords))
+ return;
+
+ Spawn(component.ArtifactSpawnerPrototype, coords);
+ Spawn(component.ArtifactFlashPrototype, coords);
+
+ Sawmill.Info($"Spawning random artifact at {coords}");
+ }
+ }
+}
using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Resist;
using Content.Server.Station.Components;
+using Content.Server.StationEvents.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.Coordinates;
-using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
-public sealed class BluespaceLockerLink : StationEventSystem
+public sealed class BluespaceLockerRule : StationEventSystem<BluespaceLockerRuleComponent>
{
- [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly BluespaceLockerSystem _bluespaceLocker = default!;
- public override string Prototype => "BluespaceLockerLink";
-
- public override void Started()
+ protected override void Started(EntityUid uid, BluespaceLockerRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
var targets = EntityQuery<EntityStorageComponent, ResistLockerComponent>().ToList();
- _robustRandom.Shuffle(targets);
+ RobustRandom.Shuffle(targets);
foreach (var target in targets)
{
using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Station.Components;
+using Content.Server.StationEvents.Components;
using JetBrains.Annotations;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
[UsedImplicitly]
-public sealed class BreakerFlip : StationEventSystem
+public sealed class BreakerFlipRule : StationEventSystem<BreakerFlipRuleComponent>
{
[Dependency] private readonly ApcSystem _apcSystem = default!;
- public override string Prototype => "BreakerFlip";
-
- public override void Added()
+ protected override void Added(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
- base.Added();
+ base.Added(uid, component, gameRule, args);
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
ChatSystem.DispatchGlobalAnnouncement(str, playSound: false, colorOverride: Color.Gold);
}
- public override void Started()
+ protected override void Started(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0)
return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
var stationApcs = new List<ApcComponent>();
- foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
+ foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
{
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
{
stationApcs.Add(apc);
}
}
-
+
var toDisable = Math.Min(RobustRandom.Next(3, 7), stationApcs.Count);
if (toDisable == 0)
return;
using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Station.Systems;
+using Content.Server.StationEvents.Components;
using JetBrains.Annotations;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
[UsedImplicitly]
-public sealed class BureaucraticError : StationEventSystem
+public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticErrorRuleComponent>
{
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
- public override string Prototype => "BureaucraticError";
-
- public override void Started()
+ protected override void Started(EntityUid uid, BureaucraticErrorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0)
return; // No stations
using Content.Server.Disease;
using Content.Server.Disease.Components;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
using Content.Shared.Disease;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
/// Infects a couple people
/// with a random disease that isn't super deadly
/// </summary>
-public sealed class DiseaseOutbreak : StationEventSystem
+public sealed class DiseaseOutbreakRule : StationEventSystem<DiseaseOutbreakRuleComponent>
{
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- public override string Prototype => "DiseaseOutbreak";
-
- /// <summary>
- /// Disease prototypes I decided were not too deadly for a random event
- /// </summary>
- public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
- {
- "SpaceCold",
- "VanAusdallsRobovirus",
- "VentCough",
- "AMIV",
- "SpaceFlu",
- "BirdFlew",
- "TongueTwister"
- };
-
/// <summary>
/// Finds 2-5 random, alive entities that can host diseases
/// and gives them a randomly selected disease.
/// They all get the same disease.
/// </summary>
- public override void Started()
+ protected override void Started(EntityUid uid, DiseaseOutbreakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
+
HashSet<EntityUid> stationsToNotify = new();
List<DiseaseCarrierComponent> aliveList = new();
- foreach (var (carrier, mobState) in EntityManager.EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
+ foreach (var (carrier, mobState) in EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
{
if (!_mobStateSystem.IsDead(mobState.Owner, mobState))
aliveList.Add(carrier);
// 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 = RobustRandom.Pick(NotTooSeriousDiseases);
+ var diseaseName = RobustRandom.Pick(component.NotTooSeriousDiseases);
if (!PrototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
return;
+++ /dev/null
-using Content.Server.GameTicking.Rules.Configurations;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Player;
-
-namespace Content.Server.StationEvents.Events
-{
- [UsedImplicitly]
- public sealed class FalseAlarm : StationEventSystem
- {
- public override string Prototype => "FalseAlarm";
-
- public override void Started()
- {
- base.Started();
-
- var ev = GetRandomEventUnweighted(PrototypeManager, RobustRandom);
-
- if (ev.Configuration is not StationEventRuleConfiguration cfg)
- return;
-
- if (cfg.StartAnnouncement != null)
- {
- ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(cfg.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
- }
-
- if (cfg.StartAudio != null)
- {
- SoundSystem.Play(cfg.StartAudio.GetSound(), Filter.Broadcast(), cfg.StartAudio.Params);
- }
- }
- }
-}
--- /dev/null
+using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+using JetBrains.Annotations;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+
+namespace Content.Server.StationEvents.Events;
+
+[UsedImplicitly]
+public sealed class FalseAlarmRule : StationEventSystem<FalseAlarmRuleComponent>
+{
+ [Dependency] private readonly EventManagerSystem _event = default!;
+
+ protected override void Started(EntityUid uid, FalseAlarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ var allEv = _event.AllEvents().Select(p => p.Value).ToList();
+ var picked = RobustRandom.Pick(allEv);
+
+ if (picked.StartAnnouncement != null)
+ {
+ ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(picked.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
+ }
+ Audio.PlayGlobal(picked.StartAudio, Filter.Broadcast(), true);
+ }
+}
+++ /dev/null
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.GameTicking.Rules;
-using Content.Server.GameTicking.Rules.Configurations;
-using Content.Shared.Atmos;
-using Robust.Shared.Audio;
-using Robust.Shared.Map;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-
-namespace Content.Server.StationEvents.Events
-{
- internal sealed class GasLeak : StationEventSystem
- {
- [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
-
- public override string Prototype => "GasLeak";
-
- private static readonly Gas[] LeakableGases =
- {
- Gas.Miasma,
- Gas.Plasma,
- Gas.Tritium,
- Gas.Frezon,
- };
-
- /// <summary>
- /// Running cooldown of how much time until another leak.
- /// </summary>
- private float _timeUntilLeak;
-
- /// <summary>
- /// How long between more gas being added to the tile.
- /// </summary>
- private const float LeakCooldown = 1.0f;
-
-
- // Event variables
-
- private EntityUid _targetStation;
- private EntityUid _targetGrid;
- private Vector2i _targetTile;
- private EntityCoordinates _targetCoords;
- private bool _foundTile;
- private Gas _leakGas;
- private float _molesPerSecond;
- private const int MinimumMolesPerSecond = 20;
- private float _endAfter = float.MaxValue;
-
- /// <summary>
- /// Don't want to make it too fast to give people time to flee.
- /// </summary>
- private const int MaximumMolesPerSecond = 50;
-
- private const int MinimumGas = 250;
- private const int MaximumGas = 1000;
- private const float SparkChance = 0.05f;
-
- public override void Started()
- {
- base.Started();
-
- var mod = MathF.Sqrt(GetSeverityModifier());
-
- // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
- if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
- {
- _foundTile = true;
-
- _leakGas = RobustRandom.Pick(LeakableGases);
- // Was 50-50 on using normal distribution.
- var totalGas = RobustRandom.Next(MinimumGas, MaximumGas) * mod;
- var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
- _molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
- _endAfter = totalGas / _molesPerSecond + startAfter;
- Sawmill.Info($"Leaking {totalGas} of {_leakGas} over {_endAfter - startAfter} seconds at {_targetTile}");
- }
-
- // Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
- // there just to fuck with people even if there is no valid tile is funny.
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (!RuleStarted)
- return;
-
- if (Elapsed > _endAfter)
- {
- ForceEndSelf();
- return;
- }
-
- _timeUntilLeak -= frameTime;
-
- if (_timeUntilLeak > 0f) return;
- _timeUntilLeak += LeakCooldown;
-
- if (!_foundTile ||
- _targetGrid == default ||
- EntityManager.Deleted(_targetGrid) ||
- !_atmosphere.IsSimulatedGrid(_targetGrid))
- {
- ForceEndSelf();
- return;
- }
-
- var environment = _atmosphere.GetTileMixture(_targetGrid, null, _targetTile, true);
-
- environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
- }
-
- public override void Ended()
- {
- base.Ended();
-
- Spark();
-
- _foundTile = false;
- _targetGrid = default;
- _targetTile = default;
- _targetCoords = default;
- _leakGas = Gas.Oxygen;
- _endAfter = float.MaxValue;
- }
-
- private void Spark()
- {
- if (RobustRandom.NextFloat() <= SparkChance)
- {
- if (!_foundTile ||
- _targetGrid == default ||
- (!EntityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
- !_atmosphere.IsSimulatedGrid(_targetGrid))
- {
- return;
- }
-
- // Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
- // it COULD start potentially start a bigger fire.
- _atmosphere.HotspotExpose(_targetGrid, _targetTile, 700f, 50f, null, true);
- SoundSystem.Play("/Audio/Effects/sparks4.ogg", Filter.Pvs(_targetCoords), _targetCoords);
- }
- }
- }
-}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+using Robust.Shared.Audio;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.StationEvents.Events
+{
+ internal sealed class GasLeakRule : StationEventSystem<GasLeakRuleComponent>
+ {
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ protected override void Started(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ if (!TryComp<StationEventComponent>(uid, out var stationEvent))
+ return;
+
+ var mod = MathF.Sqrt(GetSeverityModifier());
+
+ // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
+ if (TryFindRandomTile(out component.TargetTile, out component.TargetStation, out component.TargetGrid, out component.TargetCoords))
+ {
+ component.FoundTile = true;
+
+ component.LeakGas = RobustRandom.Pick(component.LeakableGases);
+ // Was 50-50 on using normal distribution.
+ var totalGas = RobustRandom.Next(component.MinimumGas, component.MaximumGas) * mod;
+ var startAfter = stationEvent.StartDelay;
+ component.MolesPerSecond = RobustRandom.Next(component.MinimumMolesPerSecond, component.MaximumMolesPerSecond);
+
+ stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.TotalSeconds);
+ }
+
+ // Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
+ // there just to fuck with people even if there is no valid tile is funny.
+ }
+
+ protected override void ActiveTick(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, float frameTime)
+ {
+ base.ActiveTick(uid, component, gameRule, frameTime);
+ component.TimeUntilLeak -= frameTime;
+
+ if (component.TimeUntilLeak > 0f)
+ return;
+ component.TimeUntilLeak += component.LeakCooldown;
+
+ if (!component.FoundTile ||
+ component.TargetGrid == default ||
+ Deleted(component.TargetGrid) ||
+ !_atmosphere.IsSimulatedGrid(component.TargetGrid))
+ {
+ ForceEndSelf(uid, gameRule);
+ return;
+ }
+
+ var environment = _atmosphere.GetTileMixture(component.TargetGrid, null, component.TargetTile, true);
+
+ environment?.AdjustMoles(component.LeakGas, component.LeakCooldown * component.MolesPerSecond);
+ }
+
+ protected override void Ended(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
+ {
+ base.Ended(uid, component, gameRule, args);
+ Spark(uid, component);
+ }
+
+ private void Spark(EntityUid uid, GasLeakRuleComponent component)
+ {
+ if (RobustRandom.NextFloat() <= component.SparkChance)
+ {
+ if (!component.FoundTile ||
+ component.TargetGrid == default ||
+ (!Exists(component.TargetGrid) ? EntityLifeStage.Deleted : MetaData(component.TargetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
+ !_atmosphere.IsSimulatedGrid(component.TargetGrid))
+ {
+ return;
+ }
+
+ // Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
+ // it COULD start potentially start a bigger fire.
+ _atmosphere.HotspotExpose(component.TargetGrid, component.TargetTile, 700f, 50f, null, true);
+ Audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/sparks4.ogg"), component.TargetCoords);
+ }
+ }
+ }
+}
+++ /dev/null
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class KudzuGrowth : StationEventSystem
-{
- public override string Prototype => "KudzuGrowth";
-
- private EntityUid _targetGrid;
- private Vector2i _targetTile;
- private EntityCoordinates _targetCoords;
-
- public override void Started()
- {
- base.Started();
-
- // Pick a place to plant the kudzu.
- if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords))
- {
- EntityManager.SpawnEntity("Kudzu", _targetCoords);
- Sawmill.Info($"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
- }
-
- // If the kudzu tile selection fails we just let the announcement happen anyways because it's funny and people
- // will be hunting the non-existent, dangerous plant.
- }
-}
--- /dev/null
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class KudzuGrowthRule : StationEventSystem<KudzuGrowthRuleComponent>
+{
+ protected override void Started(EntityUid uid, KudzuGrowthRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ // Pick a place to plant the kudzu.
+ if (!TryFindRandomTile(out var targetTile, out _, out var targetGrid, out var targetCoords))
+ return;
+ Spawn("Kudzu", targetCoords);
+ Sawmill.Info($"Spawning a Kudzu at {targetTile} on {targetGrid}");
+
+ }
+}
+++ /dev/null
-using Robust.Server.GameObjects;
-using Robust.Server.Maps;
-using Robust.Shared.Map;
-using Content.Server.GameTicking;
-using Robust.Shared.Prototypes;
-using Content.Server.GameTicking.Rules;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class LoneOpsSpawn : StationEventSystem
-{
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly MapLoaderSystem _map = default!;
- [Dependency] private readonly GameTicker _gameTicker = default!;
- [Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
-
- public override string Prototype => "LoneOpsSpawn";
- public const string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
- public const string GameRuleProto = "Nukeops";
-
- public override void Started()
- {
- base.Started();
-
- if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
- return;
-
- var shuttleMap = _mapManager.CreateMap();
- var options = new MapLoadOptions()
- {
- LoadMap = true,
- };
-
- _map.TryLoad(shuttleMap, LoneOpsShuttlePath, out var grids, options);
-
- if (!_prototypeManager.TryIndex<GameRulePrototype>(GameRuleProto, out var ruleProto))
- return;
-
- _nukeopsRuleSystem.LoadLoneOpsConfig();
- _gameTicker.StartGameRule(ruleProto);
- }
-}
-
--- /dev/null
+using Robust.Server.GameObjects;
+using Robust.Server.Maps;
+using Robust.Shared.Map;
+using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleComponent>
+{
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly MapLoaderSystem _map = default!;
+ [Dependency] private readonly GameTicker _gameTicker = default!;
+ [Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
+
+ protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
+ return;
+
+ var shuttleMap = _mapManager.CreateMap();
+ var options = new MapLoadOptions
+ {
+ LoadMap = true,
+ };
+
+ _map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options);
+
+ var nukeopsEntity = _gameTicker.AddGameRule(component.GameRuleProto);
+ component.AdditionalRule = nukeopsEntity;
+ var nukeopsComp = EntityManager.GetComponent<NukeopsRuleComponent>(nukeopsEntity);
+ nukeopsComp.SpawnOutpost = false;
+ nukeopsComp.EndsRound = false;
+ _gameTicker.StartGameRule(nukeopsEntity);
+ }
+
+ protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
+ {
+ base.Ended(uid, component, gameRule, args);
+
+ if (component.AdditionalRule != null)
+ GameTicker.EndGameRule(component.AdditionalRule.Value);
+ }
+}
+
-using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
using Content.Shared.Spawners.Components;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
namespace Content.Server.StationEvents.Events
{
- public sealed class MeteorSwarm : StationEventSystem
+ public sealed class MeteorSwarmRule : StationEventSystem<MeteorSwarmRuleComponent>
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
- public override string Prototype => "MeteorSwarm";
-
- private float _cooldown;
-
- /// <summary>
- /// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
- /// </summary>
- private int _waveCounter;
-
- private const int MinimumWaves = 3;
- private const int MaximumWaves = 8;
-
- private const float MinimumCooldown = 10f;
- private const float MaximumCooldown = 60f;
-
- private const int MeteorsPerWave = 5;
- private const float MeteorVelocity = 10f;
- private const float MaxAngularVelocity = 0.25f;
- private const float MinAngularVelocity = -0.25f;
-
- public override void Started()
+ protected override void Started(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
- var mod = Math.Sqrt(GetSeverityModifier());
- _waveCounter = (int) (RobustRandom.Next(MinimumWaves, MaximumWaves) * mod);
- }
+ base.Started(uid, component, gameRule, args);
- public override void Ended()
- {
- base.Ended();
- _waveCounter = 0;
- _cooldown = 0f;
+ var mod = Math.Sqrt(GetSeverityModifier());
+ component._waveCounter = (int) (RobustRandom.Next(component.MinimumWaves, component.MaximumWaves) * mod);
}
- public override void Update(float frameTime)
+ protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
- base.Update(frameTime);
-
- if (!RuleStarted)
- return;
-
- if (_waveCounter <= 0)
+ base.ActiveTick(uid, component, gameRule, frameTime);
+ if (component._waveCounter <= 0)
{
- ForceEndSelf();
+ ForceEndSelf(uid, gameRule);
return;
}
var mod = GetSeverityModifier();
- _cooldown -= frameTime;
+ component._cooldown -= frameTime;
- if (_cooldown > 0f)
+ if (component._cooldown > 0f)
return;
- _waveCounter--;
+ component._waveCounter--;
- _cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() / mod + MinimumCooldown;
+ component._cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() / mod + component.MinimumCooldown;
Box2? playableArea = null;
var mapId = GameTicker.DefaultMap;
if (playableArea == null)
{
- ForceEndSelf();
+ ForceEndSelf(uid, gameRule);
return;
}
var center = playableArea.Value.Center;
- for (var i = 0; i < MeteorsPerWave; i++)
+ for (var i = 0; i < component.MeteorsPerWave; i++)
{
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
_physics.SetBodyStatus(physics, BodyStatus.InAir);
_physics.SetLinearDamping(physics, 0f);
_physics.SetAngularDamping(physics, 0f);
- _physics.ApplyLinearImpulse(meteor, -offset.Normalized * MeteorVelocity * physics.Mass, body: physics);
+ _physics.ApplyLinearImpulse(meteor, -offset.Normalized * component.MeteorVelocity * physics.Mass, body: physics);
_physics.ApplyAngularImpulse(
meteor,
- physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * RobustRandom.NextFloat() + MinAngularVelocity),
+ physics.Mass * ((component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity),
body: physics);
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
-public sealed class MouseMigration : StationEventSystem
+public sealed class MouseMigrationRule : StationEventSystem<MouseMigrationRuleComponent>
{
- public static List<string> SpawnedPrototypeChoices = new List<string>() //we double up for that ez fake probability
- {"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
-
- public override string Prototype => "MouseMigration";
-
- public override void Started()
+ protected override void Started(EntityUid uid, MouseMigrationRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
+ base.Started(uid, component, gameRule, args);
var modifier = GetSeverityModifier();
// sqrt so we dont get insane values for ramping events
var spawnAmount = (int) (RobustRandom.Next(7, 15) * Math.Sqrt(modifier)); // A small colony of critters.
- for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
+ for (var i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
{
- var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
+ var spawnChoice = RobustRandom.Pick(component.SpawnedPrototypeChoices);
if (RobustRandom.Prob(Math.Min(0.01f * modifier, 1.0f)) || i == 0) //small chance for multiple, but always at least 1
spawnChoice = "SpawnPointGhostRatKing";
+++ /dev/null
-using Content.Server.Power.Components;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Player;
-using Robust.Shared.Utility;
-using System.Threading;
-using Content.Server.Power.EntitySystems;
-using Timer = Robust.Shared.Timing.Timer;
-using System.Linq;
-using Robust.Shared.Random;
-using Content.Server.Station.Components;
-
-namespace Content.Server.StationEvents.Events
-{
- [UsedImplicitly]
- public sealed class PowerGridCheck : StationEventSystem
- {
- [Dependency] private readonly ApcSystem _apcSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
-
- public override string Prototype => "PowerGridCheck";
-
- private CancellationTokenSource? _announceCancelToken;
-
- private readonly List<EntityUid> _powered = new();
- private readonly List<EntityUid> _unpowered = new();
-
- private const float SecondsUntilOff = 30.0f;
-
- private int _numberPerSecond = 0;
- private float UpdateRate => 1.0f / _numberPerSecond;
- private float _frameTimeAccumulator = 0.0f;
- private float _endAfter = 0.0f;
-
- public override void Added()
- {
- base.Added();
- _endAfter = RobustRandom.Next(60, 120);
- }
-
- public override void Started()
- {
- if (StationSystem.Stations.Count == 0)
- return;
- var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
-
- foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
- {
- if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
- _powered.Add(apc.Owner);
- }
-
- RobustRandom.Shuffle(_powered);
-
- _numberPerSecond = Math.Max(1, (int)(_powered.Count / SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
-
- base.Started();
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (!RuleStarted)
- return;
-
- if (Elapsed > _endAfter)
- {
- ForceEndSelf();
- return;
- }
-
- var updates = 0;
- _frameTimeAccumulator += frameTime;
- if (_frameTimeAccumulator > UpdateRate)
- {
- updates = (int) (_frameTimeAccumulator / UpdateRate);
- _frameTimeAccumulator -= UpdateRate * updates;
- }
-
- for (var i = 0; i < updates; i++)
- {
- if (_powered.Count == 0)
- break;
-
- var selected = _powered.Pop();
- if (EntityManager.Deleted(selected)) continue;
- if (EntityManager.TryGetComponent<ApcComponent>(selected, out var apcComponent))
- {
- if (apcComponent.MainBreakerEnabled)
- _apcSystem.ApcToggleBreaker(selected, apcComponent);
- }
- _unpowered.Add(selected);
- }
- }
-
- public override void Ended()
- {
- foreach (var entity in _unpowered)
- {
- if (EntityManager.Deleted(entity)) continue;
-
- if (EntityManager.TryGetComponent(entity, out ApcComponent? apcComponent))
- {
- if(!apcComponent.MainBreakerEnabled)
- _apcSystem.ApcToggleBreaker(entity, apcComponent);
- }
- }
-
- // Can't use the default EndAudio
- _announceCancelToken?.Cancel();
- _announceCancelToken = new CancellationTokenSource();
- Timer.Spawn(3000, () =>
- {
- _audioSystem.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
- }, _announceCancelToken.Token);
- _unpowered.Clear();
-
- base.Ended();
- }
- }
-}
--- /dev/null
+using Content.Server.Power.Components;
+using JetBrains.Annotations;
+using Robust.Shared.Audio;
+using Robust.Shared.Player;
+using Robust.Shared.Utility;
+using System.Threading;
+using Content.Server.Power.EntitySystems;
+using Timer = Robust.Shared.Timing.Timer;
+using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
+using Robust.Shared.Random;
+using Content.Server.Station.Components;
+using Content.Server.StationEvents.Components;
+
+namespace Content.Server.StationEvents.Events
+{
+ [UsedImplicitly]
+ public sealed class PowerGridCheckRule : StationEventSystem<PowerGridCheckRuleComponent>
+ {
+ [Dependency] private readonly ApcSystem _apcSystem = default!;
+
+ protected override void Started(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ if (StationSystem.Stations.Count == 0)
+ return;
+ var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
+
+ foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
+ {
+ if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
+ component.Powered.Add(apc.Owner);
+ }
+
+ RobustRandom.Shuffle(component.Powered);
+
+ component.NumberPerSecond = Math.Max(1, (int)(component.Powered.Count / component.SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
+ }
+
+ protected override void Ended(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
+ {
+ base.Ended(uid, component, gameRule, args);
+
+ foreach (var entity in component.Unpowered)
+ {
+ if (Deleted(entity))
+ continue;
+
+ if (TryComp(entity, out ApcComponent? apcComponent))
+ {
+ if(!apcComponent.MainBreakerEnabled)
+ _apcSystem.ApcToggleBreaker(entity, apcComponent);
+ }
+ }
+
+ // Can't use the default EndAudio
+ component.AnnounceCancelToken?.Cancel();
+ component.AnnounceCancelToken = new CancellationTokenSource();
+ Timer.Spawn(3000, () =>
+ {
+ Audio.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
+ }, component.AnnounceCancelToken.Token);
+ component.Unpowered.Clear();
+ }
+
+ protected override void ActiveTick(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, float frameTime)
+ {
+ base.ActiveTick(uid, component, gameRule, frameTime);
+
+ var updates = 0;
+ component.FrameTimeAccumulator += frameTime;
+ if (component.FrameTimeAccumulator > component.UpdateRate)
+ {
+ updates = (int) (component.FrameTimeAccumulator / component.UpdateRate);
+ component.FrameTimeAccumulator -= component.UpdateRate * updates;
+ }
+
+ for (var i = 0; i < updates; i++)
+ {
+ if (component.Powered.Count == 0)
+ break;
+
+ var selected = component.Powered.Pop();
+ if (Deleted(selected))
+ continue;
+ if (TryComp<ApcComponent>(selected, out var apcComponent))
+ {
+ if (apcComponent.MainBreakerEnabled)
+ _apcSystem.ApcToggleBreaker(selected, apcComponent);
+ }
+ component.Unpowered.Add(selected);
+ }
+ }
+ }
+}
using System.Linq;
using Content.Server.Chat.Systems;
+using Content.Server.GameTicking.Rules.Components;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Station.Systems;
using Content.Server.StationEvents.Components;
namespace Content.Server.StationEvents.Events;
-public sealed class RandomSentience : StationEventSystem
+public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRuleComponent>
{
- public override string Prototype => "RandomSentience";
-
- public override void Started()
+ protected override void Started(EntityUid uid, RandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- base.Started();
HashSet<EntityUid> stationsToNotify = new();
var mod = GetSeverityModifier();
- var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
+ var targetList = EntityQuery<SentienceTargetComponent>().ToList();
RobustRandom.Shuffle(targetList);
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
if (toMakeSentient-- == 0)
break;
- EntityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
- var ghostRole = AddComp<GhostRoleComponent>(target.Owner);
- AddComp<GhostTakeoverAvailableComponent>(target.Owner);
- ghostRole.RoleName = EntityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
+ RemComp<SentienceTargetComponent>(target.Owner);
+ var ghostRole = EnsureComp<GhostRoleComponent>(target.Owner);
+ EnsureComp<GhostTakeoverAvailableComponent>(target.Owner);
+ ghostRole.RoleName = MetaData(target.Owner).EntityName;
ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
groups.Add(Loc.GetString(target.FlavorKind));
}
var kind2 = groupList.Count > 1 ? groupList[1] : "???";
var kind3 = groupList.Count > 2 ? groupList[2] : "???";
- var entSysMgr = IoCManager.Resolve<IEntitySystemManager>();
- var stationSystem = entSysMgr.GetEntitySystem<StationSystem>();
- var chatSystem = entSysMgr.GetEntitySystem<ChatSystem>();
foreach (var target in targetList)
{
- var station = stationSystem.GetOwningStation(target.Owner);
+ var station = StationSystem.GetOwningStation(target.Owner);
if(station == null) continue;
stationsToNotify.Add((EntityUid) station);
}
foreach (var station in stationsToNotify)
{
- chatSystem.DispatchStationAnnouncement(
+ ChatSystem.DispatchStationAnnouncement(
station,
Loc.GetString("station-event-random-sentience-announcement",
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
+++ /dev/null
-namespace Content.Server.StationEvents.Events;
-
-public sealed class RevenantSpawn : StationEventSystem
-{
- public override string Prototype => "RevenantSpawn";
- private static readonly string RevenantPrototype = "MobRevenant";
-
- public override void Started()
- {
- base.Started();
-
- if (TryFindRandomTile(out _, out _, out _, out var coords))
- {
- Sawmill.Info($"Spawning revenant at {coords}");
- EntityManager.SpawnEntity(RevenantPrototype, coords);
- }
- }
-}
--- /dev/null
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class RevenantSpawnRule : StationEventSystem<RevenantSpawnRuleComponent>
+{
+ protected override void Started(EntityUid uid, RevenantSpawnRuleComponent component, GameRuleComponent gameRule,
+ GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ if (TryFindRandomTile(out _, out _, out _, out var coords))
+ {
+ Sawmill.Info($"Spawning revenant at {coords}");
+ Spawn(component.RevenantPrototype, coords);
+ }
+ }
+}
+++ /dev/null
-using Content.Server.GameTicking.Rules.Configurations;
-using Content.Server.Radio.Components;
-using Content.Server.Radio;
-using Robust.Shared.Random;
-using Content.Server.Light.EntitySystems;
-using Content.Server.Light.Components;
-using Content.Shared.Radio.Components;
-using Content.Shared.Doors.Components;
-using Content.Shared.Doors.Systems;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class SolarFlare : StationEventSystem
-{
- [Dependency] private readonly PoweredLightSystem _poweredLight = default!;
- [Dependency] private readonly SharedDoorSystem _door = default!;
-
- public override string Prototype => "SolarFlare";
-
- private SolarFlareEventRuleConfiguration _event = default!;
- private float _effectTimer = 0;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
- }
-
- public override void Added()
- {
- base.Added();
-
- if (Configuration is not SolarFlareEventRuleConfiguration ev)
- return;
-
- _event = ev;
- _event.EndAfter = RobustRandom.Next(ev.MinEndAfter, ev.MaxEndAfter);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (!RuleStarted)
- return;
-
- _effectTimer -= frameTime;
- if (_effectTimer < 0)
- {
- _effectTimer += 1;
- var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
- while (lightQuery.MoveNext(out var uid, out var light))
- {
- if (RobustRandom.Prob(_event.LightBreakChancePerSecond))
- _poweredLight.TryDestroyBulb(uid, light);
- }
- var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
- while (airlockQuery.MoveNext(out var uid, out var airlock, out var door))
- {
- if (airlock.AutoClose && RobustRandom.Prob(_event.DoorToggleChancePerSecond))
- _door.TryToggleDoor(uid, door);
- }
- }
-
- if (Elapsed > _event.EndAfter)
- {
- ForceEndSelf();
- return;
- }
- }
-
- private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
- {
- if (RuleStarted && _event.AffectedChannels.Contains(args.Channel.ID))
- if (!_event.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
- args.Cancelled = true;
- }
-}
--- /dev/null
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Radio;
+using Robust.Shared.Random;
+using Content.Server.Light.EntitySystems;
+using Content.Server.Light.Components;
+using Content.Server.StationEvents.Components;
+using Content.Shared.Radio.Components;
+using Content.Shared.Doors.Components;
+using Content.Shared.Doors.Systems;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class SolarFlareRule : StationEventSystem<SolarFlareRuleComponent>
+{
+ [Dependency] private readonly PoweredLightSystem _poweredLight = default!;
+ [Dependency] private readonly SharedDoorSystem _door = default!;
+
+ private float _effectTimer = 0;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
+ }
+
+ protected override void ActiveTick(EntityUid uid, SolarFlareRuleComponent component, GameRuleComponent gameRule, float frameTime)
+ {
+ base.ActiveTick(uid, component, gameRule, frameTime);
+
+ _effectTimer -= frameTime;
+ if (_effectTimer < 0)
+ {
+ _effectTimer += 1;
+ var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
+ while (lightQuery.MoveNext(out var lightEnt, out var light))
+ {
+ if (RobustRandom.Prob(component.LightBreakChancePerSecond))
+ _poweredLight.TryDestroyBulb(lightEnt, light);
+ }
+ var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
+ while (airlockQuery.MoveNext(out var airlockEnt, out var airlock, out var door))
+ {
+ if (airlock.AutoClose && RobustRandom.Prob(component.DoorToggleChancePerSecond))
+ _door.TryToggleDoor(airlockEnt, door);
+ }
+ }
+ }
+
+ private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
+ {
+ var query = EntityQueryEnumerator<SolarFlareRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var flare, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ continue;
+
+ if (!flare.AffectedChannels.Contains(args.Channel.ID))
+ continue;
+
+ if (!flare.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
+ args.Cancelled = true;
+ }
+ }
+}
+++ /dev/null
-using Content.Server.StationEvents.Components;
-using Content.Shared.Actions;
-using Robust.Shared.Random;
-using System.Linq;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class SpiderSpawn : StationEventSystem
-{
- public override string Prototype => "SpiderSpawn";
-
- public override void Started()
- {
- base.Started();
- var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
- RobustRandom.Shuffle(spawnLocations);
-
- var mod = Math.Sqrt(GetSeverityModifier());
-
- var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
- Sawmill.Info($"Spawning {spawnAmount} of spiders");
- foreach (var location in spawnLocations)
- {
- if (spawnAmount-- == 0)
- break;
-
- var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
-
- EntityManager.SpawnEntity("MobGiantSpiderAngry", coords.Coordinates);
- }
- }
-}
--- /dev/null
+using Content.Server.StationEvents.Components;
+using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class SpiderSpawnRule : StationEventSystem<SpiderSpawnRuleComponent>
+{
+ protected override void Started(EntityUid uid, SpiderSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+ var spawnLocations = EntityQuery<VentCritterSpawnLocationComponent>().ToList();
+ RobustRandom.Shuffle(spawnLocations);
+
+ var mod = Math.Sqrt(GetSeverityModifier());
+
+ var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
+ Sawmill.Info($"Spawning {spawnAmount} of spiders");
+ foreach (var location in spawnLocations)
+ {
+ if (spawnAmount-- == 0)
+ break;
+
+ var xform = Transform(location.Owner);
+ Spawn("MobGiantSpiderAngry", xform.Coordinates);
+ }
+ }
+}
-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.GameTicking.Rules.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
+using Content.Server.StationEvents.Components;
using Content.Shared.Database;
-using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.StationEvents.Events
+namespace Content.Server.StationEvents.Events;
+
+/// <summary>
+/// An abstract entity system inherited by all station events for their behavior.
+/// </summary>
+public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : Component
{
- /// <summary>
- /// An abstract entity system inherited by all station events for their behavior.
- /// </summary>
- public abstract class StationEventSystem : GameRuleSystem
+ [Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] protected readonly IMapManager MapManager = default!;
+ [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+ [Dependency] protected readonly IRobustRandom RobustRandom = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] protected readonly ChatSystem ChatSystem = default!;
+ [Dependency] protected readonly SharedAudioSystem Audio = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] protected readonly StationSystem StationSystem = default!;
+
+ protected ISawmill Sawmill = default!;
+
+ public override void Initialize()
{
- [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] private readonly AtmosphereSystem _atmosphere = 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();
+ base.Initialize();
- Sawmill = Logger.GetSawmill("stationevents");
- }
+ Sawmill = Logger.GetSawmill("stationevents");
+ }
+
+ /// <inheritdoc/>
+ protected override void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args)
+ {
+ base.Added(uid, component, gameRule, args);
- /// <summary>
- /// Called once to setup the event after StartAfter has elapsed, or if an event is forcibly started.
- /// </summary>
- public override void Started()
+ if (!TryComp<StationEventComponent>(uid, out var stationEvent))
+ return;
+
+ AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {ToPrettyString(uid)}");
+
+ if (stationEvent.StartAnnouncement != null)
{
- AdminLogManager.Add(LogType.EventStarted, LogImpact.High, $"Event started: {Configuration.Id}");
+ ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
}
- /// <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}");
+ Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true);
+ stationEvent.StartTime = _timing.CurTime + stationEvent.StartDelay;
+ }
- if (Configuration is not StationEventRuleConfiguration ev)
- return;
+ /// <inheritdoc/>
+ protected override void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
- if (ev.StartAnnouncement != null)
- {
- ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
- }
+ if (!TryComp<StationEventComponent>(uid, out var stationEvent))
+ return;
- if (ev.StartAudio != null)
- {
- SoundSystem.Play(ev.StartAudio.GetSound(), Filter.Broadcast(), ev.StartAudio.Params);
- }
-
- Elapsed = 0;
- }
+ AdminLogManager.Add(LogType.EventStarted, LogImpact.High, $"Event started: {ToPrettyString(uid)}");
+ var duration = stationEvent.MaxDuration == null
+ ? stationEvent.Duration
+ : TimeSpan.FromSeconds(RobustRandom.NextDouble(stationEvent.Duration.TotalSeconds,
+ stationEvent.MaxDuration.Value.TotalSeconds));
+ stationEvent.EndTime = _timing.CurTime + duration;
+ }
- /// <summary>
- /// Called once when the station event ends for any reason.
- /// </summary>
- public override void Ended()
- {
- AdminLogManager.Add(LogType.EventStopped, $"Event ended: {Configuration.Id}");
+ /// <inheritdoc/>
+ protected override void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args)
+ {
+ base.Ended(uid, component, gameRule, args);
- if (Configuration is not StationEventRuleConfiguration ev)
- return;
+ if (!TryComp<StationEventComponent>(uid, out var stationEvent))
+ return;
- if (ev.EndAnnouncement != null)
- {
- ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.EndAnnouncement), playSound: false, colorOverride: Color.Gold);
- }
+ AdminLogManager.Add(LogType.EventStopped, $"Event ended: {ToPrettyString(uid)}");
- if (ev.EndAudio != null)
- {
- SoundSystem.Play(ev.EndAudio.GetSound(), Filter.Broadcast(), ev.EndAudio.Params);
- }
+ if (stationEvent.EndAnnouncement != null)
+ {
+ ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playSound: false, colorOverride: Color.Gold);
}
- /// <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;
+ Audio.PlayGlobal(stationEvent.EndAudio, Filter.Broadcast(), true);
+ }
- Elapsed += frameTime;
+ /// <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>
+ /// <inheritdoc/>
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator<StationEventComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var stationEvent, out var ruleData))
+ {
+ if (!GameTicker.IsGameRuleAdded(uid, ruleData))
+ continue;
- if (!RuleStarted && Elapsed >= data.StartAfter)
+ if (!GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= stationEvent.StartTime)
{
- GameTicker.StartGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
+ GameTicker.StartGameRule(uid, ruleData);
}
-
- if (RuleStarted && Elapsed >= data.EndAfter)
+ else if (GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= stationEvent.EndTime)
{
- GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
+ GameTicker.EndGameRule(uid, ruleData);
}
}
+ }
+
+ #region Helper Functions
+
+ protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
+ {
+ GameTicker.EndGameRule(uid, component);
+ }
- #region Helper Functions
+ protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
+ {
+ tile = default;
- protected void ForceEndSelf()
+ targetCoords = EntityCoordinates.Invalid;
+ if (StationSystem.Stations.Count == 0)
{
- GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
+ targetStation = EntityUid.Invalid;
+ targetGrid = EntityUid.Invalid;
+ return false;
}
-
- protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
+ targetStation = RobustRandom.Pick(StationSystem.Stations);
+ var possibleTargets = Comp<StationDataComponent>(targetStation).Grids;
+ if (possibleTargets.Count == 0)
{
- tile = default;
+ targetGrid = EntityUid.Invalid;
+ return false;
+ }
- 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);
- targetGrid = RobustRandom.Pick(possibleTargets);
+ if (!TryComp<MapGridComponent>(targetGrid, out var gridComp))
+ return false;
- if (!TryComp<MapGridComponent>(targetGrid, out var gridComp))
- return false;
+ var found = false;
+ var (gridPos, _, gridMatrix) = _transform.GetWorldPositionRotationMatrix(targetGrid);
+ var gridBounds = gridMatrix.TransformBox(gridComp.LocalAABB);
- var found = false;
- var (gridPos, _, gridMatrix) = Transform(targetGrid).GetWorldPositionRotationMatrix();
- var gridBounds = gridMatrix.TransformBox(gridComp.LocalAABB);
+ 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);
- for (var i = 0; i < 10; i++)
+ tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
+ if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile,
+ mapGridComp: gridComp)
+ || _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp))
{
- 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 (_atmosphere.IsTileSpace(gridComp.Owner, Transform(targetGrid).MapUid, tile,
- mapGridComp: gridComp)
- || _atmosphere.IsTileAirBlocked(gridComp.Owner, tile, mapGridComp: gridComp))
- {
- continue;
- }
-
- found = true;
- targetCoords = gridComp.GridTileToLocal(tile);
- break;
+ continue;
}
- 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());
- }
-
- public float GetSeverityModifier()
- {
- var ev = new GetSeverityModifierEvent();
- RaiseLocalEvent(ev);
- return ev.Modifier;
+ found = true;
+ targetCoords = gridComp.GridTileToLocal(tile);
+ break;
}
- #endregion
+ return found;
}
+ public float GetSeverityModifier()
+ {
+ var ev = new GetSeverityModifierEvent();
+ RaiseLocalEvent(ev);
+ return ev.Modifier;
+ }
+
+ #endregion
+}
+/// <summary>
+/// Raised broadcast to determine what the severity modifier should be for an event, some positive number that can be multiplied with various things.
+/// Handled by usually other game rules (like the ramping scheduler).
+/// Most events should try and make use of this if possible.
+/// </summary>
+public sealed class GetSeverityModifierEvent : EntityEventArgs
+{
/// <summary>
- /// Raised broadcast to determine what the severity modifier should be for an event, some positive number that can be multiplied with various things.
- /// Handled by usually other game rules (like the ramping scheduler).
- /// Most events should try and make use of this if possible.
+ /// Should be multiplied/added to rather than set, for commutativity.
/// </summary>
- public sealed class GetSeverityModifierEvent : EntityEventArgs
- {
- /// <summary>
- /// Should be multiplied/added to rather than set, for commutativity.
- /// </summary>
- public float Modifier = 1.0f;
- }
+ public float Modifier = 1.0f;
}
using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Fluids.EntitySystems;
-using Robust.Server.GameObjects;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
namespace Content.Server.StationEvents.Events;
[UsedImplicitly]
-public sealed class VentClog : StationEventSystem
+public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
{
- public override string Prototype => "VentClog";
+ [Dependency] private readonly SmokeSystem _smoke = default!;
- public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
+ protected override void Started(EntityUid uid, VentClogRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
- "Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
- };
-
- public override void Started()
- {
- base.Started();
+ base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0)
return;
}
else
{
- solution.AddReagent(RobustRandom.Pick(SafeishVentChemicals), 200);
+ solution.AddReagent(RobustRandom.Pick(component.SafeishVentChemicals), 200);
}
var foamEnt = Spawn("Foam", transform.Coordinates);
var smoke = EnsureComp<SmokeComponent>(foamEnt);
smoke.SpreadAmount = 20;
- EntityManager.System<SmokeSystem>().Start(foamEnt, smoke, solution, 20f);
- EntityManager.System<AudioSystem>().PlayPvs(sound, transform.Coordinates);
+ _smoke.Start(foamEnt, smoke, solution, 20f);
+ Audio.PlayPvs(sound, transform.Coordinates);
}
}
-
}
+++ /dev/null
-using Content.Server.StationEvents.Components;
-using Content.Shared.Actions;
-using Robust.Shared.Random;
-using System.Linq;
-
-namespace Content.Server.StationEvents.Events;
-
-public sealed class VentCritters : StationEventSystem
-{
- public static List<string> SpawnedPrototypeChoices = new List<string>()
- {"MobMouse", "MobMouse1", "MobMouse2"};
-
- public override string Prototype => "VentCritters";
-
- public override void Started()
- {
- base.Started();
- var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
- var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
- RobustRandom.Shuffle(spawnLocations);
-
- var spawnAmount = (int) (RobustRandom.Next(4, 12)); // A small colony of critters.
- Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
- foreach (var location in spawnLocations)
- {
- if (spawnAmount-- == 0)
- break;
-
- var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
-
- EntityManager.SpawnEntity(spawnChoice, coords.Coordinates);
- }
- }
-}
--- /dev/null
+using Content.Server.StationEvents.Components;
+using Robust.Shared.Random;
+using System.Linq;
+using Content.Server.GameTicking.Rules.Components;
+
+namespace Content.Server.StationEvents.Events;
+
+public sealed class VentCrittersRule : StationEventSystem<VentCrittersRuleComponent>
+{
+ protected override void Started(EntityUid uid, VentCrittersRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ var spawnChoice = RobustRandom.Pick(component.SpawnedPrototypeChoices);
+ var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
+ RobustRandom.Shuffle(spawnLocations);
+
+ var spawnAmount = RobustRandom.Next(4, 12); // A small colony of critters.
+ Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
+ foreach (var location in spawnLocations)
+ {
+ if (spawnAmount-- == 0)
+ break;
+
+ var coords = Transform(location.Owner);
+ Spawn(spawnChoice, coords.Coordinates);
+ }
+ }
+}
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.StationEvents.Components;
using Content.Server.StationEvents.Events;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
namespace Content.Server.StationEvents;
-public sealed class RampingStationEventSchedulerSystem : GameRuleSystem
+public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingStationEventSchedulerComponent>
{
- public override string Prototype => "RampingStationEventScheduler";
-
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EventManagerSystem _event = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
- [ViewVariables(VVAccess.ReadWrite)]
- private float _endTime;
- [ViewVariables(VVAccess.ReadWrite)]
- private float _maxChaos;
- [ViewVariables(VVAccess.ReadWrite)]
- private float _startingChaos;
- [ViewVariables(VVAccess.ReadWrite)]
- private float _timeUntilNextEvent;
-
- [ViewVariables]
- public float ChaosModifier
+ public float GetChaosModifier(EntityUid uid, RampingStationEventSchedulerComponent component)
{
- get
- {
- var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
- if (roundTime > _endTime)
- return _maxChaos;
+ var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
+ if (roundTime > component.EndTime)
+ return component.MaxChaos;
- return (_maxChaos / _endTime) * roundTime + _startingChaos;
- }
+ return component.MaxChaos / component.EndTime * roundTime + component.StartingChaos;
}
public override void Initialize()
SubscribeLocalEvent<GetSeverityModifierEvent>(OnGetSeverityModifier);
}
- public override void Started()
+ protected override void Started(EntityUid uid, RampingStationEventSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
+ base.Started(uid, component, gameRule, args);
+
var avgChaos = _cfg.GetCVar(CCVars.EventsRampingAverageChaos);
var avgTime = _cfg.GetCVar(CCVars.EventsRampingAverageEndTime);
// Worlds shittiest probability distribution
// Got a complaint? Send them to
- _maxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4);
+ component.MaxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4);
// This is in minutes, so *60 for seconds (for the chaos calc)
- _endTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f;
- _startingChaos = _maxChaos / 10;
+ component.EndTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f;
+ component.StartingChaos = component.MaxChaos / 10;
- PickNextEventTime();
- }
-
- public override void Ended()
- {
- _endTime = 0f;
- _maxChaos = 0f;
- _startingChaos = 0f;
- _timeUntilNextEvent = 0f;
+ PickNextEventTime(uid, component);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
- if (!RuleStarted || !_event.EventsEnabled)
+ if (!_event.EventsEnabled)
return;
- if (_timeUntilNextEvent > 0f)
+ var query = EntityQueryEnumerator<RampingStationEventSchedulerComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var scheduler, out var gameRule))
{
- _timeUntilNextEvent -= frameTime;
- return;
- }
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
- PickNextEventTime();
- _event.RunRandomEvent();
+ if (scheduler.TimeUntilNextEvent > 0f)
+ {
+ scheduler.TimeUntilNextEvent -= frameTime;
+ return;
+ }
+
+ PickNextEventTime(uid, scheduler);
+ _event.RunRandomEvent();
+ }
}
private void OnGetSeverityModifier(GetSeverityModifierEvent ev)
{
- if (!RuleStarted)
- return;
+ var query = EntityQueryEnumerator<RampingStationEventSchedulerComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var scheduler, out var gameRule))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, gameRule))
+ return;
- ev.Modifier *= ChaosModifier;
- Logger.Info($"Ramping set modifier to {ev.Modifier}");
+ ev.Modifier *= GetChaosModifier(uid, scheduler);
+ Logger.Info($"Ramping set modifier to {ev.Modifier}");
+ }
}
- private void PickNextEventTime()
+ private void PickNextEventTime(EntityUid uid, RampingStationEventSchedulerComponent component)
{
- var mod = ChaosModifier;
+ var mod = GetChaosModifier(uid, component);
// 4-12 minutes baseline. Will get faster over time as the chaos mod increases.
- _timeUntilNextEvent = _random.NextFloat(240f / mod, 720f / mod);
+ component.TimeUntilNextEvent = _random.NextFloat(240f / mod, 720f / mod);
}
}
+++ /dev/null
-using Content.Server.Chat.Managers;
-using Content.Shared.Roles;
-
-namespace Content.Server.Suspicion.Roles
-{
- public sealed class SuspicionInnocentRole : SuspicionRole
- {
- public AntagPrototype Prototype { get; }
-
- public SuspicionInnocentRole(Mind.Mind mind, AntagPrototype antagPrototype) : base(mind)
- {
- Prototype = antagPrototype;
- Name = Loc.GetString(antagPrototype.Name);
- Antagonist = antagPrototype.Antagonist;
- }
-
- public override string Name { get; }
- public string Objective => Loc.GetString(Prototype.Objective);
- public override bool Antagonist { get; }
-
- public override void Greet()
- {
- base.Greet();
-
- var chat = IoCManager.Resolve<IChatManager>();
-
- if (Mind.TryGetSession(out var session))
- {
- chat.DispatchServerMessage(session, $"You're an {Name}!");
- chat.DispatchServerMessage(session, $"Objective: {Objective}");
- }
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-
-namespace Content.Server.Suspicion.Roles
-{
- public abstract class SuspicionRole : Role
- {
- protected SuspicionRole(Mind.Mind mind) : base(mind) { }
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Chat.Managers;
-using Content.Shared.Roles;
-
-namespace Content.Server.Suspicion.Roles
-{
- public sealed class SuspicionTraitorRole : SuspicionRole
- {
- public AntagPrototype Prototype { get; }
-
- public SuspicionTraitorRole(Mind.Mind mind, AntagPrototype antagPrototype) : base(mind)
- {
- Prototype = antagPrototype;
- Name = Loc.GetString(antagPrototype.Name);
- Antagonist = antagPrototype.Antagonist;
- }
-
- public override string Name { get; }
- public string Objective => Loc.GetString(Prototype.Objective);
- public override bool Antagonist { get; }
-
- public void GreetSuspicion(List<SuspicionTraitorRole> traitors, IChatManager chatMgr)
- {
- if (Mind.TryGetSession(out var session))
- {
- chatMgr.DispatchServerMessage(session, Loc.GetString("suspicion-role-greeting", ("roleName", Name)));
- chatMgr.DispatchServerMessage(session, Loc.GetString("suspicion-objective", ("objectiveText", Objective)));
-
- var allPartners = string.Join(", ", traitors.Where(p => p != this).Select(p => p.Mind.CharacterName));
-
- var partnerText = Loc.GetString(
- "suspicion-partners-in-crime",
- ("partnersCount", traitors.Count-1),
- ("partnerNames", allPartners)
- );
-
- chatMgr.DispatchServerMessage(session, partnerText);
- }
- }
- }
-}
+++ /dev/null
-namespace Content.Server.Suspicion;
-
-/// <summary>
-/// Tag component meant for bookkeeping items spawned by the suspicion rule.
-/// </summary>
-[RegisterComponent]
-public sealed class SuspicionItemComponent : Component
-{
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.GameTicking.Rules;
-using Content.Server.Mind.Components;
-using Content.Server.Roles;
-using Content.Server.Suspicion.Roles;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Suspicion;
-
-namespace Content.Server.Suspicion
-{
- [RegisterComponent]
- public sealed class SuspicionRoleComponent : SharedSuspicionRoleComponent
- {
- [Dependency] private readonly IEntityManager _entMan = default!;
- private Role? _role;
- [ViewVariables]
- private readonly HashSet<SuspicionRoleComponent> _allies = new();
-
- [ViewVariables]
- public Role? Role
- {
- get => _role;
- set
- {
- if (_role == value)
- {
- return;
- }
-
- _role = value;
-
- Dirty();
-
- var sus = EntitySystem.Get<SuspicionRuleSystem>();
-
- if (value == null || !value.Antagonist)
- {
- ClearAllies();
- sus.RemoveTraitor(this);
- }
- else if (value.Antagonist)
- {
- SetAllies(sus.Traitors);
- sus.AddTraitor(this);
- }
- }
- }
-
- [ViewVariables] public bool KnowsAllies => IsTraitor();
-
- public bool IsDead()
- {
- return _entMan.TryGetComponent(Owner, out MobStateComponent? state) &&
- _entMan.EntitySysManager.GetEntitySystem<MobStateSystem>().IsDead(Owner, state);
- }
-
- public bool IsInnocent()
- {
- return !IsTraitor();
- }
-
- public bool IsTraitor()
- {
- return Role?.Antagonist ?? false;
- }
-
- public void SyncRoles()
- {
- if (!_entMan.TryGetComponent(Owner, out MindComponent? mind) ||
- !mind.HasMind)
- {
- return;
- }
-
- Role = mind.Mind!.AllRoles.First(role => role is SuspicionRole);
- }
-
- public void AddAlly(SuspicionRoleComponent ally)
- {
- if (ally == this)
- {
- return;
- }
-
- _allies.Add(ally);
- }
-
- public bool RemoveAlly(SuspicionRoleComponent ally)
- {
- if (_allies.Remove(ally))
- {
- Dirty();
-
- return true;
- }
-
- return false;
- }
-
- public void SetAllies(IEnumerable<SuspicionRoleComponent> allies)
- {
- _allies.Clear();
-
- _allies.UnionWith(allies.Where(a => a != this));
-
- Dirty();
- }
-
- public void ClearAllies()
- {
- _allies.Clear();
-
- Dirty();
- }
- public override ComponentState GetComponentState()
- {
- if (Role == null)
- {
- return new SuspicionRoleComponentState(null, null, Array.Empty<(string, EntityUid)>());
- }
-
- var allies = new List<(string name, EntityUid)>();
-
- foreach (var role in _allies)
- {
- if (role.Role?.Mind.CharacterName == null)
- {
- continue;
- }
-
- allies.Add((role.Role!.Mind.CharacterName, role.Owner));
- }
-
- return new SuspicionRoleComponentState(Role?.Name, Role?.Antagonist, allies.ToArray());
- }
- }
-}
+++ /dev/null
-using Content.Shared.Examine;
-
-namespace Content.Server.Suspicion
-{
- public sealed class SuspicionRoleSystem : EntitySystem
- {
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<SuspicionRoleComponent, ExaminedEvent>(OnExamined);
- }
- private void OnExamined(EntityUid uid, SuspicionRoleComponent component, ExaminedEvent args)
- {
- if (!component.IsDead())
- {
- return;
- }
-
- var traitor = component.IsTraitor();
- var color = traitor ? "red" : "green";
- var role = traitor ? "suspicion-role-component-role-traitor" : "suspicion-role-component-role-innocent";
- var article = traitor ? "generic-article-a" : "generic-article-an";
-
- var tooltip = Loc.GetString("suspicion-role-component-on-examine-tooltip",
- ("article", Loc.GetString(article)),
- ("colorName", color),
- ("role",Loc.GetString(role)));
-
- args.PushMarkup(tooltip);
- }
- }
-}
+++ /dev/null
-namespace Content.Server.TraitorDeathMatch.Components
-{
- [RegisterComponent]
- public sealed class TraitorDeathMatchRedemptionComponent : Component
- {
- }
-}
+++ /dev/null
-using Robust.Shared.Network;
-
-namespace Content.Server.TraitorDeathMatch.Components
-{
- [RegisterComponent]
- public sealed class TraitorDeathMatchReliableOwnerTagComponent : Component
- {
- [ViewVariables]
- public NetUserId? UserId { get; set; }
- }
-}
-
+++ /dev/null
-using Content.Server.Mind.Components;
-using Content.Server.TraitorDeathMatch.Components;
-using Content.Server.Store.Components;
-using Content.Server.Store.Systems;
-using Content.Server.Traitor.Uplink;
-using Content.Shared.FixedPoint;
-using Content.Shared.Interaction;
-using Content.Shared.Inventory;
-using Content.Shared.Popups;
-
-namespace Content.Server.TraitorDeathMatch;
-
-public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem
-{
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly UplinkSystem _uplink = default!;
- [Dependency] private readonly StoreSystem _store = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<TraitorDeathMatchRedemptionComponent, InteractUsingEvent>(OnInteractUsing);
- }
-
- private void OnInteractUsing(EntityUid uid, TraitorDeathMatchRedemptionComponent component, InteractUsingEvent args)
- {
- if (!EntityManager.TryGetComponent<MindComponent>(args.User, out var userMindComponent))
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString("traitor-death-match-redemption-component-interact-using-no-mind-message"))), uid, args.User);
- return;
- }
-
- var userMind = userMindComponent.Mind;
- if (userMind == null)
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString("traitor-death-match-redemption-component-interact-using-no-user-mind-message"))), uid, args.User);
- return;
- }
-
- if (!EntityManager.TryGetComponent<StoreComponent>(args.Used, out var victimUplink))
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString("traitor-death-match-redemption-component-interact-using-no-pda-message"))), uid, args.User);
- return;
- }
-
- if (!EntityManager.TryGetComponent<TraitorDeathMatchReliableOwnerTagComponent>(args.Used,
- out var victimPDAuid))
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString("traitor-death-match-redemption-component-interact-using-no-pda-owner-message"))), uid, args.User);
- return;
- }
-
- if (victimPDAuid.UserId == userMind.UserId)
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-pda-different-user-message"))), uid, args.User);
- return;
- }
-
- StoreComponent? userUplink = null;
-
- if (_inventory.TryGetSlotEntity(args.User, "id", out var pdaUid) &&
- EntityManager.TryGetComponent<StoreComponent>(pdaUid, out var userUplinkComponent))
- userUplink = userUplinkComponent;
-
- if (userUplink == null)
- {
- _popup.PopupEntity(Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-main-message",
- ("secondMessage",
- Loc.GetString(
- "traitor-death-match-redemption-component-interact-using-no-pda-in-pocket-message"))), uid, args.User);
- return;
- }
-
-
- // We have finally determined both PDA components. FINALLY.
-
- // 4 is the per-PDA bonus amount
- var transferAmount = _uplink.GetTCBalance(victimUplink) + 4;
- victimUplink.Balance.Clear();
- _store.TryAddCurrency(new Dictionary<string, FixedPoint2>() { {"Telecrystal", FixedPoint2.New(transferAmount)}}, userUplink.Owner, userUplink);
-
- EntityManager.DeleteEntity(victimUplink.Owner);
-
- _popup.PopupEntity(Loc.GetString("traitor-death-match-redemption-component-interact-using-success-message",
- ("tcAmount", transferAmount)), uid, args.User);
-
- args.Handled = true;
- }
-}
+++ /dev/null
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Suspicion
-{
- [NetworkedComponent()]
- public abstract class SharedSuspicionRoleComponent : Component
- {
- }
-
- [Serializable, NetSerializable]
- public sealed class SuspicionRoleComponentState : ComponentState
- {
- public readonly string? Role;
- public readonly bool? Antagonist;
- public readonly (string name, EntityUid)[] Allies;
-
- public SuspicionRoleComponentState(string? role, bool? antagonist, (string name, EntityUid)[] allies)
- {
- Role = role;
- Antagonist = antagonist;
- Allies = allies;
- }
- }
-}
+++ /dev/null
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Suspicion
-{
- public static class SuspicionMessages
- {
- [Serializable, NetSerializable]
- public sealed class SetSuspicionEndTimerMessage : EntityEventArgs
- {
- public TimeSpan? EndTime;
- }
- }
-}
+++ /dev/null
-- type: entity
- name: PDA Redemption Machine Spawner
- id: TraitorDMRedemptionMachineSpawner
- parent: MarkerBase
- components:
- - type: Sprite
- layers:
- - state: blue
- - sprite: Structures/Machines/traitordm.rsi
- state: redemption
- - type: ConditionalSpawner
- prototypes:
- - TraitorDMRedemptionMachine
- chance: 1.0
- gameRules:
- - TraitorDeathMatch
+++ /dev/null
-- type: entity
- id: TraitorDMRedemptionMachine
- parent: BaseMachinePowered
- name: traitor deathmatch pda redemption machine
- description: Put someone else's PDA into this to get telecrystals.
- components:
- - type: Sprite
- layers:
- - sprite: Structures/Machines/traitordm.rsi
- state: redemption
- - sprite: Structures/Machines/traitordm.rsi
- state: redemption-unshaded
- shader: unshaded
- - type: Physics
- bodyType: Static
- - type: Fixtures
- fixtures:
- - shape:
- !type:PhysShapeAabb
- bounds: "-0.25,-0.25,0.25,0.25"
- density: 190
- mask:
- - MachineMask
- layer:
- - MachineLayer
- - type: TraitorDeathMatchRedemption
- placement:
- mode: AlignTileAny
-- type: gameRule
+- type: entity
id: AnomalySpawn
- config:
- !type:StationEventRuleConfiguration
- id: AnomalySpawn
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
startAfter: 30
- endAfter: 35
+ duration: 35
+ - type: AnomalySpawnRule
-- type: gameRule
+- type: entity
id: BluespaceArtifact
- config:
- !type:StationEventRuleConfiguration
- id: BluespaceArtifact
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 5
startAfter: 30
- endAfter: 35
+ duration: 35
+ - type: BluespaceArtifactRule
-- type: gameRule
- id: BluespaceLockerLink
- config:
- !type:StationEventRuleConfiguration
- id: BluespaceLockerLink
+- type: entity
+ id: BluespaceLocker
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 0
reoccurrenceDelay: 5
earliestStart: 1
- endAfter: 1
+ duration: 1
+ - type: BluespaceLockerRule
-- type: gameRule
+- type: entity
id: BreakerFlip
- config:
- !type:StationEventRuleConfiguration
- id: BreakerFlip
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
- endAfter: 1
+ duration: 1
minimumPlayers: 15
+ - type: BreakerFlipRule
-- type: gameRule
+- type: entity
id: BureaucraticError
- config:
- !type:StationEventRuleConfiguration
- id: BureaucraticError
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
startAnnouncement: station-event-bureaucratic-error-announcement
minimumPlayers: 25
weight: 5
- endAfter: 1
+ duration: 1
+ - type: BureaucraticErrorRule
-- type: gameRule
+- type: entity
id: DiseaseOutbreak
- config:
- !type:StationEventRuleConfiguration
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
startAnnouncement: station-event-disease-outbreak-announcement
startAudio:
path: /Audio/Announcements/outbreak7.ogg
params:
volume: -4
- id: DiseaseOutbreak
weight: 5
- endAfter: 1
+ duration: 1
earliestStart: 15
+ - type: DiseaseOutbreakRule
-- type: gameRule
+- type: entity
id: Dragon
- config:
- !type:StationEventRuleConfiguration
- id: Dragon
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
- endAfter: 2
+ duration: 2
earliestStart: 15
minimumPlayers: 15
+ - type: DragonRule
-- type: gameRule
+- type: entity
id: RevenantSpawn
- config:
- !type:StationEventRuleConfiguration
- id: RevenantSpawn
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 5
- endAfter: 1
+ duration: 1
earliestStart: 45
minimumPlayers: 20
+ - type: RevenantSpawnRule
-- type: gameRule
+- type: entity
id: FalseAlarm
- config:
- !type:StationEventRuleConfiguration
- id: FalseAlarm
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 15
- endAfter: 1
+ duration: 1
+ - type: FalseAlarmRule
-- type: gameRule
+- type: entity
id: GasLeak
- config:
- !type:StationEventRuleConfiguration
- id: GasLeak
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
startAnnouncement: station-event-gas-leak-start-announcement
startAudio:
path: /Audio/Announcements/attention.ogg
minimumPlayers: 5
weight: 5
startAfter: 20
+ - type: GasLeakRule
-- type: gameRule
+- type: entity
id: KudzuGrowth
- config:
- !type:StationEventRuleConfiguration
- id: KudzuGrowth
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 15
minimumPlayers: 15
weight: 5
startAfter: 50
- endAfter: 240
+ duration: 240
+ - type: KudzuGrowthRule
-- type: gameRule
+- type: entity
id: MeteorSwarm
- config:
- !type:StationEventRuleConfiguration
- id: MeteorSwarm
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 30
weight: 5
minimumPlayers: 20
params:
volume: -4
startAfter: 30
+ - type: MeteorSwarmRule
-- type: gameRule
+- type: entity
id: MouseMigration
- config:
- !type:StationEventRuleConfiguration
- id: MouseMigration
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 30
minimumPlayers: 35
weight: 5
- endAfter: 50
+ duration: 50
+ - type: MouseMigrationRule
-- type: gameRule
+- type: entity
id: PowerGridCheck
- config:
- !type:StationEventRuleConfiguration
- id: PowerGridCheck
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
startAnnouncement: station-event-power-grid-check-start-announcement
endAnnouncement: station-event-power-grid-check-end-announcement
params:
volume: -4
startAfter: 12
+ duration: 60
+ maxDuration: 120
+ - type: PowerGridCheckRule
-- type: gameRule
+- type: entity
id: RandomSentience
- config:
- !type:StationEventRuleConfiguration
- id: RandomSentience
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
- endAfter: 1
+ duration: 1
startAudio:
path: /Audio/Announcements/attention.ogg
+ - type: RandomSentienceRule
-- type: gameRule
+- type: entity
id: SolarFlare
- config: !type:SolarFlareEventRuleConfiguration
- id: SolarFlare
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
weight: 10
startAnnouncement: station-event-solar-flare-start-announcement
endAnnouncement: station-event-solar-flare-end-announcement
startAudio:
path: /Audio/Announcements/attention.ogg
- minEndAfter: 120
- maxEndAfter: 240
+ duration: 120
+ maxDuration: 240
+ - type: SolarFlareRule
onlyJamHeadsets: true
affectedChannels:
- Common
lightBreakChancePerSecond: 0.0003
doorToggleChancePerSecond: 0.001
-- type: gameRule
+- type: entity
id: VentClog
- config:
- !type:StationEventRuleConfiguration
- id: VentClog
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
startAnnouncement: station-event-vent-clog-start-announcement
startAudio:
path: /Audio/Announcements/attention.ogg
minimumPlayers: 15
weight: 5
startAfter: 50
- endAfter: 60
+ duration: 60
+ - type: VentClogRule
-- type: gameRule
+- type: entity
id: VentCritters
- config:
- !type:StationEventRuleConfiguration
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
id: VentCritters
earliestStart: 15
minimumPlayers: 15
weight: 5
- endAfter: 60
+ duration: 60
+ - type: VentCrittersRule
-- type: gameRule
+- type: entity
id: SpiderSpawn
- config:
- !type:StationEventRuleConfiguration
- id: SpiderSpawn
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 20
minimumPlayers: 15
weight: 5
- endAfter: 60
+ duration: 60
+ - type: SpiderSpawnRule
-- type: gameRule
+- type: entity
id: ZombieOutbreak
- config:
- !type:StationEventRuleConfiguration
- id: Zombie
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 50
weight: 2.5
- endAfter: 1
+ duration: 1
+ - type: ZombieRule
-- type: gameRule
+- type: entity
id: LoneOpsSpawn
- config:
- !type:StationEventRuleConfiguration
- id: LoneOpsSpawn
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: StationEvent
earliestStart: 55
weight: 5
minimumPlayers: 10
reoccurrenceDelay: 25
- endAfter: 1
+ duration: 1
+ - type: LoneOpsSpawnRule
+ - type: NukeopsRule
-- type: gameRule
+- type: entity
+ id: BaseGameRule
+ abstract: true
+ noSpawn: true
+ components:
+ - type: GameRule
+
+- type: entity
id: DeathMatch
- config:
- !type:GenericGameRuleConfiguration
- id: DeathMatch
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: DeathMatchRule
-- type: gameRule
+- type: entity
id: InactivityTimeRestart
- config:
- !type:InactivityGameRuleConfiguration
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: InactivityRule
inactivityMaxTime: 600
roundEndDelay: 10
-- type: gameRule
+- type: entity
id: MaxTimeRestart
- config:
- !type:MaxTimeRestartRuleConfiguration
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: MaxTimeRestartRule
roundMaxTime: 300
roundEndDelay: 10
-- type: gameRule
+- type: entity
id: Nukeops
- config:
- !type:NukeopsRuleConfiguration
- id: Nukeops
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: NukeopsRule
-- type: gameRule
+- type: entity
id: Pirates
- config:
- !type:GenericGameRuleConfiguration
- id: Pirates
-
-- type: gameRule
- id: Suspicion
- config:
- !type:GenericGameRuleConfiguration
- id: Suspicion
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: PiratesRule
-- type: gameRule
+- type: entity
id: Traitor
- config:
- !type:GenericGameRuleConfiguration
- id: Traitor
-
-- type: gameRule
- id: TraitorDeathMatch
- config:
- !type:GenericGameRuleConfiguration
- id: TraitorDeathMatch
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: TraitorRule
-- type: gameRule
+- type: entity
id: Sandbox
- config:
- !type:GenericGameRuleConfiguration
- id: Sandbox
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: SandboxRule
-- type: gameRule
+- type: entity
id: Secret
- config:
- !type:GenericGameRuleConfiguration
- id: Secret
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: SecretRule
-- type: gameRule
+- type: entity
id: Zombie
- config:
- !type:GenericGameRuleConfiguration
- id: Zombie
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: ZombieRule
# event schedulers
-- type: gameRule
+- type: entity
id: BasicStationEventScheduler
- config:
- !type:GenericGameRuleConfiguration
- id: BasicStationEventScheduler
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: BasicStationEventScheduler
-- type: gameRule
+- type: entity
id: RampingStationEventScheduler
- config:
- !type:GenericGameRuleConfiguration
- id: RampingStationEventScheduler
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: RampingStationEventScheduler
+++ /dev/null
-- type: antag
- id: SuspicionInnocent
- name: roles-antag-suspicion-innocent-name
- antagonist: false
- setPreference: false
- objective: roles-antag-suspicion-innocent-objective
+++ /dev/null
-- type: antag
- id: SuspicionTraitor
- name: roles-antag-suspicion-suspect-name
- antagonist: true
- setPreference: true
- objective: roles-antag-suspicion-suspect-objective
- Traitor
- BasicStationEventScheduler
-- type: gamePreset
- id: Suspicion
- alias:
- - suspicion
- - sus
- name: suspicion-title
- description: suspicion-description
- rules:
- - Suspicion
-
- type: gamePreset
id: Deathmatch
alias:
rules:
- DeathMatch
-- type: gamePreset
- id: TraitorDeathMatch
- alias:
- - traitordm
- - traitordeathmatch
- name: traitor-death-match-title
- description: traitor-death-match-description
- rules:
- - TraitorDeathMatch
- - MaxTimeRestart
-
- type: gamePreset
id: Nukeops
alias: