using Content.Server.Clothing.Systems;
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.Shared.Abilities.Psionics;
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 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 LoadoutSystem _loadout = 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.EquipIdCard(spawned,
profile.Name,
job,
station);
_loadout.ApplyCharacterLoadout(spawned, job, profile, [], false); // TODO: find a way to get playtimes and whitelisted
}
foreach (var special in job.Special)
{
special.AfterEquip(spawned);
}
// TODO: In a future PR, make it so that the Paradox Anomaly spawns with a completely 1:1 clone of the victim's entire PsionicComponent.
if (HasComp(uid))
EnsureComp(spawned);
return spawned;
}
}