mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
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:
co-authored by
deltanedas
Null
parent
6a1749ad01
commit
94d3fdafa2
@@ -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;
|
||||
|
||||
+3
-7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user