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; } }