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>
This commit is contained in:
deltanedas
2024-05-18 19:44:48 +00:00
committed by GitHub
co-authored by deltanedas Null
parent 6a1749ad01
commit 94d3fdafa2
10 changed files with 196 additions and 251 deletions
@@ -1,17 +0,0 @@
using Content.Server.DeltaV.ParadoxAnomaly.Systems;
using Robust.Shared.Prototypes;
namespace Content.Server.DeltaV.ParadoxAnomaly.Components;
/// <summary>
/// Creates a random paradox anomaly and tranfers mind to it when taken by a player.
/// </summary>
[RegisterComponent, Access(typeof(ParadoxAnomalySystem))]
public sealed partial class ParadoxAnomalySpawnerComponent : Component
{
/// <summary>
/// Antag game rule to start for the paradox anomaly.
/// </summary>
[DataField]
public EntProtoId Rule = "ParadoxAnomaly";
}
@@ -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;
/// <summary>
/// 90% of the work is done by exterminator since its a reskin.
/// All the logic here is spawning since thats tricky.
/// </summary>
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<ParadoxAnomalySpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
}
private void OnTakeGhostRole(Entity<ParadoxAnomalySpawnerComponent> 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<GhostRoleComponent>(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<MindContainerComponent, HumanoidAppearanceComponent>();
while (query.MoveNext(out var uid, out var mindContainer, out var humanoid))
{
if (humanoid.LastProfileLoaded is not {} profile)
continue;
if (!_proto.TryIndex<SpeciesPrototype>(humanoid.Species, out var species))
continue;
if (_mind.GetMind(uid, mindContainer) is not {} mindId || !HasComp<JobComponent>(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<JobComponent>(mindId).Prototype;
var job = _proto.Index<JobPrototype>(jobId!);
// Find a suitable spawn point.
var station = _station.GetOwningStation(uid);
var latejoins = new List<EntityUid>();
var query = EntityQueryEnumerator<SpawnPointComponent>();
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<DetailExaminableComponent>(uid, out var detail))
{
var detailCopy = EnsureComp<DetailExaminableComponent>(spawned);
detailCopy.Content = detail.Content;
}
if (job.StartingGear != null && _proto.TryIndex<StartingGearPrototype>(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<PotentialPsionicComponent>(spawned);
_psionics.RollPsionics(spawned, psi, false, 100);
return spawned;
}
}
@@ -0,0 +1,7 @@
namespace Content.Server.StationEvents.Components;
/// <summary>
/// Creates a paradox anomaly of a random person when taken by a player.
/// </summary>
[RegisterComponent]
public sealed partial class ParadoxClonerRuleComponent : Component;
@@ -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;
/// <summary>
/// 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.
/// </summary>
public sealed class ParadoxClonerRule : StationEventSystem<ParadoxClonerRuleComponent>
{
[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<ParadoxClonerRuleComponent, AntagSelectEntityEvent>(OnSelectEntity);
}
private void OnSelectEntity(Entity<ParadoxClonerRuleComponent> 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<JobComponent>, HumanoidCharacterProfile)>();
var query = EntityQueryEnumerator<MindContainerComponent, HumanoidAppearanceComponent>();
while (query.MoveNext(out var uid, out var mindContainer, out var humanoid))
{
if (humanoid.LastProfileLoaded is {} profile &&
_mind.GetMind(uid, mindContainer) is {} mindId &&
TryComp<JobComponent>(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<JobComponent>, 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<PotentialPsionicComponent>(spawned);
_psionics.RollPsionics(spawned, psi, false, 100);
return spawned;
}
}
@@ -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
{
/// <summary>
/// Spawner to create at a random mid round antag marker.
/// </summary>
[DataField(required: true)]
public EntProtoId Spawner = string.Empty;
}
/// <summary>
/// Spawns any antags at random midround antag spawnpoints, falls back to vent critter spawners.
/// Requires <c>AntagSelection</c>.
/// </summary>
[RegisterComponent]
public sealed partial class MidRoundAntagRuleComponent : Component;
@@ -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;
@@ -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;
/// <summary>
/// Makes antags spawn at a random midround antag or vent critter spawner.
/// </summary>
public sealed class MidRoundAntagRule : StationEventSystem<MidRoundAntagRuleComponent>
{
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<MidRoundAntagRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
}
private void OnSelectLocation(Entity<MidRoundAntagRuleComponent> 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<TransformComponent> FindSpawns(EntityUid station)
private List<MapCoordinates> FindSpawns(EntityUid station)
{
var spawns = new List<TransformComponent>();
var spawns = new List<MapCoordinates>();
var query = EntityQueryEnumerator<MidRoundAntagSpawnLocationComponent, TransformComponent>();
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<MidRoundAntagRuleComp
while (fallbackQuery.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));
}
return spawns;
@@ -31,6 +31,8 @@
name: ghost-role-information-listeningop-name
description: ghost-role-information-listeningop-description
rules: ghost-role-information-listeningop-rules
raffle:
settings: default
requirements: # Worth considering these numbers for the goal of making sure someone willing to MRP takes this.
- !type:OverallPlaytimeRequirement
time: 259200 # 72 hours
@@ -61,7 +63,9 @@
name: ghost-role-information-paradox-anomaly-name
description: ghost-role-information-paradox-anomaly-description
rules: ghost-role-information-paradox-anomaly-rules
- type: ParadoxAnomalySpawner
raffle:
settings: default
- type: GhostRoleAntagSpawner
- type: Sprite
sprite: Markers/jobs.rsi
layers:
@@ -56,7 +56,54 @@
minimumPlayers: 25
maxOccurrences: 1
duration: 1
# TODO:
# 1. use the load grid rule to load the post itself
# 2. rename below to DebrisSpawnRule or something and have it just be debris
# 3. use AntagSelection to spawn the listeners
# the map will need spawnpoints like SpawnPointNukies added
- type: PirateRadioSpawnRule
debrisCount: 6
distanceModifier: 13
debrisDistanceModifier: 3
# Mid round antag spawns
- type: entity
abstract: true
parent: BaseGameRule
id: BaseMidRoundAntag
components:
- type: StationEvent
weight: 7
reoccurrenceDelay: 5
minimumPlayers: 15
earliestStart: 25
- type: MidRoundAntagRule
#- type: entity
# noSpawn: true
# parent: BaseMidRoundAntag
# id: RatKingSpawn
# components:
# if you uncomment this update this for antag refactor, below wont work
# - type: MidRoundAntagRule
# spawner: SpawnPointGhostRatKing
- type: entity
noSpawn: true
parent: BaseMidRoundAntag
id: ParadoxAnomalySpawn
components:
- type: ParadoxClonerRule
- type: AntagSelection
definitions:
- spawnerPrototype: SpawnPointGhostParadoxAnomaly
min: 1
max: 1
pickPlayer: false
components:
# TerminatorComponent is added by paradox cloner
- type: GenericAntag
rule: ParadoxAnomaly
mindComponents:
- type: TerminatorRole
prototype: Terminator
@@ -12,35 +12,6 @@
earliestStart: 15
- type: NoosphericStormRule
# Mid round antag spawns
- type: entity
abstract: true
parent: BaseGameRule
id: BaseMidRoundAntag
components:
- type: StationEvent
weight: 7
reoccurrenceDelay: 5
minimumPlayers: 15
earliestStart: 25
- type: MidRoundAntagRule
#- type: entity
# noSpawn: true
# parent: BaseMidRoundAntag
# id: RatKingSpawn
# components:
# - type: MidRoundAntagRule
# spawner: SpawnPointGhostRatKing
- type: entity
noSpawn: true
parent: BaseMidRoundAntag
id: ParadoxAnomalySpawn
components:
- type: MidRoundAntagRule
spawner: SpawnPointGhostParadoxAnomaly
# Base glimmer event
- type: entity
id: BaseGlimmerEvent