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