From 94d3fdafa2344729c9efead9942fc50b4f49d18b Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Sat, 18 May 2024 19:44:48 +0000
Subject: [PATCH] paradox anomaly antag rework (#1206)
* move paradox code around and update it
* make MidRoundAntagRule just give spawn locations
* update the yaml
* give anom and listening post raffles
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: Null <56081759+NullWanderer@users.noreply.github.com>
---
.../Components/ParadoxAnomalySpawner.cs | 17 --
.../Systems/ParadoxAnomalySystem.cs | 164 ------------------
.../Components/ParadoxClonerRuleComponent.cs | 7 +
.../StationEvents/Events/ParadoxClonerRule.cs | 104 +++++++++++
.../Components/MidRoundAntagRuleComponent.cs | 19 +-
.../MidRoundAntagSpawnLocationComponent.cs | 10 +-
.../StationEvents/Events/MidRoundAntagRule.cs | 44 ++---
.../Entities/Markers/Spawners/ghost_roles.yml | 6 +-
.../Prototypes/DeltaV/GameRules/events.yml | 47 +++++
.../Nyanotrasen/GameRules/events.yml | 29 ----
10 files changed, 196 insertions(+), 251 deletions(-)
delete mode 100644 Content.Server/DeltaV/ParadoxAnomaly/Components/ParadoxAnomalySpawner.cs
delete mode 100644 Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs
create mode 100644 Content.Server/DeltaV/StationEvents/Components/ParadoxClonerRuleComponent.cs
create mode 100644 Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs
diff --git a/Content.Server/DeltaV/ParadoxAnomaly/Components/ParadoxAnomalySpawner.cs b/Content.Server/DeltaV/ParadoxAnomaly/Components/ParadoxAnomalySpawner.cs
deleted file mode 100644
index c3264584a1c..00000000000
--- a/Content.Server/DeltaV/ParadoxAnomaly/Components/ParadoxAnomalySpawner.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Server.DeltaV.ParadoxAnomaly.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.DeltaV.ParadoxAnomaly.Components;
-
-///
-/// Creates a random paradox anomaly and tranfers mind to it when taken by a player.
-///
-[RegisterComponent, Access(typeof(ParadoxAnomalySystem))]
-public sealed partial class ParadoxAnomalySpawnerComponent : Component
-{
- ///
- /// Antag game rule to start for the paradox anomaly.
- ///
- [DataField]
- public EntProtoId Rule = "ParadoxAnomaly";
-}
diff --git a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs
deleted file mode 100644
index 891a0464dc2..00000000000
--- a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using Content.Server.DeltaV.ParadoxAnomaly.Components;
-using Content.Server.DetailExaminable;
-using Content.Server.GenericAntag;
-using Content.Server.Ghost.Roles;
-using Content.Server.Ghost.Roles.Components;
-using Content.Server.Psionics;
-using Content.Server.Spawners.Components;
-using Content.Server.Station.Systems;
-using Content.Server.Terminator.Systems;
-using Content.Shared.Humanoid;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Mind;
-using Content.Shared.Mind.Components;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Content.Shared.Roles.Jobs;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Content.Server.DeltaV.ParadoxAnomaly.Systems;
-
-///
-/// 90% of the work is done by exterminator since its a reskin.
-/// All the logic here is spawning since thats tricky.
-///
-public sealed class ParadoxAnomalySystem : EntitySystem
-{
- [Dependency] private readonly GenericAntagSystem _genericAntag = default!;
- [Dependency] private readonly GhostRoleSystem _ghostRole = default!;
- [Dependency] private readonly IPrototypeManager _proto = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly MetaDataSystem _metaData = default!;
- [Dependency] private readonly PsionicsSystem _psionics = default!;
- [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!;
- [Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly SharedRoleSystem _role = default!;
- [Dependency] private readonly StationSystem _station = default!;
- [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
- [Dependency] private readonly TerminatorSystem _terminator = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnTakeGhostRole);
- }
-
- private void OnTakeGhostRole(Entity ent, ref TakeGhostRoleEvent args)
- {
- Log.Info($"Using paradox anomaly spawner {ent}");
- if (!TrySpawnParadoxAnomaly(ent.Comp.Rule, out var twin))
- return;
-
- Log.Info($"Created paradox anomaly {ToPrettyString(twin):twin}");
- var role = Comp(ent);
- _ghostRole.GhostRoleInternalCreateMindAndTransfer(args.Player, ent, twin.Value, role);
- _ghostRole.UnregisterGhostRole((ent.Owner, role));
-
- args.TookRole = true;
- QueueDel(ent);
- }
-
- private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityUid? twin)
- {
- twin = null;
-
- // Get a list of potential candidates
- var candidates = new List<(EntityUid, EntityUid, SpeciesPrototype, HumanoidCharacterProfile)>();
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var mindContainer, out var humanoid))
- {
- if (humanoid.LastProfileLoaded is not {} profile)
- continue;
-
- if (!_proto.TryIndex(humanoid.Species, out var species))
- continue;
-
- if (_mind.GetMind(uid, mindContainer) is not {} mindId || !HasComp(mindId))
- continue;
-
- if (_role.MindIsAntagonist(mindId))
- continue;
-
- // TODO: when metempsychosis real skip whoever has Karma
-
- candidates.Add((uid, mindId, species, profile));
- }
-
- twin = SpawnParadoxAnomaly(candidates, rule);
- return twin != null;
- }
-
- private EntityUid? SpawnParadoxAnomaly(List<(EntityUid, EntityUid, SpeciesPrototype, HumanoidCharacterProfile)> candidates, string rule)
- {
- // Select a candidate.
- if (candidates.Count == 0)
- return null;
-
- var (uid, mindId, species, profile) = _random.Pick(candidates);
- var jobId = Comp(mindId).Prototype;
- var job = _proto.Index(jobId!);
-
- // Find a suitable spawn point.
- var station = _station.GetOwningStation(uid);
- var latejoins = new List();
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var spawnUid, out var spawnPoint))
- {
- if (spawnPoint.SpawnType != SpawnPointType.LateJoin)
- continue;
-
- if (_station.GetOwningStation(spawnUid) == station)
- latejoins.Add(spawnUid);
- }
-
- if (latejoins.Count == 0)
- return null;
-
- // Spawn the twin.
- var destination = Transform(_random.Pick(latejoins)).Coordinates;
- var spawned = Spawn(species.Prototype, destination);
-
- // Set the kill target to the chosen player
- _terminator.SetTarget(spawned, mindId);
- _genericAntag.MakeAntag(spawned, rule);
-
- //////////////////////////
- // /!\ WARNING /!\ //
- // MAJOR SHITCODE BELOW //
- // /!\ WARNING /!\ //
- //////////////////////////
-
- // Copy the details.
- _humanoid.LoadProfile(spawned, profile);
- _metaData.SetEntityName(spawned, Name(uid));
-
- if (TryComp(uid, out var detail))
- {
- var detailCopy = EnsureComp(spawned);
- detailCopy.Content = detail.Content;
- }
-
- if (job.StartingGear != null && _proto.TryIndex(job.StartingGear, out var gear))
- {
- _stationSpawning.EquipStartingGear(spawned, gear);
- _stationSpawning.SetPdaAndIdCardData(spawned,
- profile.Name,
- job,
- station);
- }
-
- foreach (var special in job.Special)
- {
- special.AfterEquip(spawned);
- }
-
- var psi = EnsureComp(spawned);
- _psionics.RollPsionics(spawned, psi, false, 100);
-
- return spawned;
- }
-}
diff --git a/Content.Server/DeltaV/StationEvents/Components/ParadoxClonerRuleComponent.cs b/Content.Server/DeltaV/StationEvents/Components/ParadoxClonerRuleComponent.cs
new file mode 100644
index 00000000000..ccb4d937389
--- /dev/null
+++ b/Content.Server/DeltaV/StationEvents/Components/ParadoxClonerRuleComponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.StationEvents.Components;
+
+///
+/// Creates a paradox anomaly of a random person when taken by a player.
+///
+[RegisterComponent]
+public sealed partial class ParadoxClonerRuleComponent : Component;
diff --git a/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs b/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs
new file mode 100644
index 00000000000..102563ddcb2
--- /dev/null
+++ b/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs
@@ -0,0 +1,104 @@
+using Content.Server.Antag;
+using Content.Server.GameTicking.Rules;
+using Content.Server.Psionics;
+using Content.Server.Station.Systems;
+using Content.Server.StationEvents.Components;
+using Content.Server.StationEvents.Events;
+using Content.Server.Terminator.Systems;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Mind;
+using Content.Shared.Mind.Components;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Content.Server.StationEvents.Events;
+
+///
+/// Creates clones of random players to make into selected antags.
+/// 90% of the actual antag's work is done by exterminator (rip) since its a reskin.
+///
+public sealed class ParadoxClonerRule : StationEventSystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PsionicsSystem _psionics = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly SharedRoleSystem _role = default!;
+ [Dependency] private readonly StationSystem _station = default!;
+ [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
+ [Dependency] private readonly TerminatorSystem _terminator = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSelectEntity);
+ }
+
+ private void OnSelectEntity(Entity ent, ref AntagSelectEntityEvent args)
+ {
+ if (args.Session?.AttachedEntity is not {} spawner)
+ return;
+
+ Log.Debug($"Rule {ToPrettyString(ent)} creating a paradox anomaly using spawner {spawner}");
+ if (!TrySpawnParadoxAnomaly(spawner, out var clone))
+ return;
+
+ Log.Info($"Created paradox anomaly {ToPrettyString(clone):clone}");
+ args.Entity = clone;
+ }
+
+ private bool TrySpawnParadoxAnomaly(EntityUid spawner, [NotNullWhen(true)] out EntityUid? clone)
+ {
+ clone = null;
+
+ // Get a list of potential candidates
+ var candidates = new List<(EntityUid, Entity, HumanoidCharacterProfile)>();
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var mindContainer, out var humanoid))
+ {
+ if (humanoid.LastProfileLoaded is {} profile &&
+ _mind.GetMind(uid, mindContainer) is {} mindId &&
+ TryComp(mindId, out var job) &&
+ !_role.MindIsAntagonist(mindId))
+ {
+ candidates.Add((uid, (mindId, job), profile));
+ }
+ }
+
+ if (candidates.Count == 0)
+ {
+ Log.Warning("Found no eligible players to paradox clone!");
+ return false;
+ }
+
+ clone = SpawnParadoxAnomaly(spawner, candidates);
+ return true;
+ }
+
+ private EntityUid SpawnParadoxAnomaly(EntityUid spawner, List<(EntityUid, Entity, HumanoidCharacterProfile)> candidates)
+ {
+ // Select a candidate.
+ var (uid, (mindId, job), profile) = _random.Pick(candidates);
+
+ // Spawn the clone.
+ var coords = Transform(spawner).Coordinates;
+ var station = _station.GetOwningStation(uid);
+ var spawned = _stationSpawning.SpawnPlayerMob(coords, job, profile, station);
+
+ // Set the kill target to the chosen player
+ _terminator.SetTarget(spawned, mindId);
+
+ // guaranteed psionic power
+ var psi = EnsureComp(spawned);
+ _psionics.RollPsionics(spawned, psi, false, 100);
+
+ return spawned;
+ }
+}
diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagRuleComponent.cs
index 21050a50ca8..f09731b18f9 100644
--- a/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagRuleComponent.cs
+++ b/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagRuleComponent.cs
@@ -1,15 +1,8 @@
-using Content.Server.Nyanotrasen.StationEvents.Events;
-using Content.Server.StationEvents.Events;
-using Robust.Shared.Prototypes;
-
namespace Content.Server.StationEvents.Components;
-[RegisterComponent, Access(typeof(MidRoundAntagRule))]
-public sealed partial class MidRoundAntagRuleComponent : Component
-{
- ///
- /// Spawner to create at a random mid round antag marker.
- ///
- [DataField(required: true)]
- public EntProtoId Spawner = string.Empty;
-}
+///
+/// Spawns any antags at random midround antag spawnpoints, falls back to vent critter spawners.
+/// Requires AntagSelection.
+///
+[RegisterComponent]
+public sealed partial class MidRoundAntagRuleComponent : Component;
diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagSpawnLocationComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagSpawnLocationComponent.cs
index 655e0a50710..de4bf4f75e9 100644
--- a/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagSpawnLocationComponent.cs
+++ b/Content.Server/Nyanotrasen/StationEvents/Components/MidRoundAntagSpawnLocationComponent.cs
@@ -1,8 +1,4 @@
-namespace Content.Server.StationEvents
-{
- [RegisterComponent]
- public sealed partial class MidRoundAntagSpawnLocationComponent : Component
- {
+namespace Content.Server.StationEvents;
- }
-}
+[RegisterComponent]
+public sealed partial class MidRoundAntagSpawnLocationComponent : Component;
diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs
index 9cdcdee844a..3e09009841e 100644
--- a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs
+++ b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs
@@ -1,42 +1,46 @@
-using Content.Server.GameTicking.Components;
-using Content.Server.StationEvents;
+using Content.Server.Antag;
using Content.Server.StationEvents.Components;
-using Content.Server.StationEvents.Events;
-using Robust.Shared.Random;
+using Robust.Shared.Map;
-namespace Content.Server.Nyanotrasen.StationEvents.Events;
+namespace Content.Server.StationEvents.Events;
+///
+/// Makes antags spawn at a random midround antag or vent critter spawner.
+///
public sealed class MidRoundAntagRule : StationEventSystem
{
- protected override void Started(EntityUid uid, MidRoundAntagRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- base.Started(uid, component, gameRule, args);
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSelectLocation);
+ }
+
+ private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args)
+ {
if (!TryGetRandomStation(out var station))
return;
- var spawnLocations = FindSpawns(station.Value);
- if (spawnLocations.Count == 0)
+ var spawns = FindSpawns(station.Value);
+ if (spawns.Count == 0)
{
- Log.Warning("Couldn't find any midround antag spawners or vent critter spawners, not spawning an antag.");
+ Log.Warning($"Couldn't find any suitable midround antag spawners for {ToPrettyString(ent):rule}");
return;
}
- var spawn = RobustRandom.Pick(spawnLocations);
-
- var proto = component.Spawner;
- Log.Info($"Spawning midround antag {proto} at {spawn.Coordinates}");
- Spawn(proto, spawn.Coordinates);
+ args.Coordinates.AddRange(spawns);
}
- private List FindSpawns(EntityUid station)
+ private List FindSpawns(EntityUid station)
{
- var spawns = new List();
+ var spawns = new List();
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out _, out var xform))
{
if (StationSystem.GetOwningStation(uid, xform) == station && xform.GridUid != null)
- spawns.Add(xform);
+ spawns.Add(_xform.GetMapCoordinates(xform));
}
// if there are any midround antag spawns mapped, use them
@@ -49,7 +53,7 @@ public sealed class MidRoundAntagRule : StationEventSystem