diff --git a/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs b/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs new file mode 100644 index 00000000000..cd6a4b4c2b9 --- /dev/null +++ b/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs @@ -0,0 +1,53 @@ +using Content.Server.Nyanotrasen.Cloning; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.DeltaV; + +[TestFixture] +[TestOf(typeof(MetempsychoticMachineSystem))] +public sealed class MetempsychosisTest +{ + [Test] + public async Task AllHumanoidPoolSpeciesExist() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + // Per RobustIntegrationTest.cs, wait until state is settled to access it. + await server.WaitIdleAsync(); + + var prototypeManager = server.ResolveDependency(); + + var metemComponent = new MetempsychoticMachineComponent(); + + await server.WaitAssertion(() => + { + prototypeManager.TryIndex(metemComponent.MetempsychoticHumanoidPool, + out var humanoidPool); + prototypeManager.TryIndex(metemComponent.MetempsychoticNonHumanoidPool, + out var nonHumanoidPool); + + Assert.That(humanoidPool, Is.Not.Null, "MetempsychoticHumanoidPool is null!"); + Assert.That(nonHumanoidPool, Is.Not.Null, "MetempsychoticNonHumanoidPool is null!"); + + Assert.That(humanoidPool.Weights, Is.Not.Empty, + "MetempsychoticHumanoidPool has no valid prototypes!"); + Assert.That(nonHumanoidPool.Weights, Is.Not.Empty, + "MetempsychoticNonHumanoidPool has no valid prototypes!"); + + foreach (var key in humanoidPool.Weights.Keys) + { + Assert.That(prototypeManager.TryIndex(key, out _), + $"MetempsychoticHumanoidPool has invalid prototype {key}!"); + } + + foreach (var key in nonHumanoidPool.Weights.Keys) + { + Assert.That(prototypeManager.TryIndex(key, out _), + $"MetempsychoticNonHumanoidPool has invalid prototype {key}!"); + } + }); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index 41768066392..c95c37312e5 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -32,7 +32,7 @@ public sealed class CloningConsoleSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; - + public override void Initialize() { base.Initialize(); @@ -169,8 +169,8 @@ public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUi if (mind.UserId.HasValue == false || mind.Session == null) return; - - if (_cloningSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier)) + // Nyano: Adds scannerComp.MetemKarmaBonus + if (_cloningSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier, scannerComp.MetemKarmaBonus)) _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} successfully cloned {ToPrettyString(body.Value)}."); } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index eabf10013d0..00612833676 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -33,8 +33,22 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Server.Psionics; using Content.Server.Traits.Assorted; //Nyano - Summary: allows the potential psionic ability to be written to the character. +using Content.Server.Psionics; //DeltaV needed for Psionic Systems +using Content.Shared.Speech; //DeltaV Start Metem Usings +using Content.Shared.Tag; +using Content.Shared.Preferences; +using Content.Shared.Emoting; +using Content.Server.Speech.Components; +using Content.Server.StationEvents.Components; +using Content.Server.Ghost.Roles.Components; +using Content.Server.Nyanotrasen.Cloning; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.GameObjects.Components.Localization; //DeltaV End Metem Usings +using Content.Server.EntityList; +using Content.Shared.SSDIndicator; +using Content.Shared.Damage.ForceSay; +using Content.Server.Polymorph.Components; namespace Content.Server.Cloning { @@ -62,6 +76,8 @@ public sealed class CloningSystem : EntitySystem [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly MetempsychoticMachineSystem _metem = default!; //DeltaV + [Dependency] private readonly TagSystem _tag = default!; //DeltaV public readonly Dictionary ClonesWaitingForMind = new(); public const float EasyModeCloningCost = 0.7f; @@ -136,8 +152,8 @@ private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEv args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); } - - public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent? clonePod, float failChanceModifier = 1) + // Nyano: Adds float karmaBonus + public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent? clonePod, float failChanceModifier = 1, float karmaBonus = 0.25f) { if (!Resolve(uid, ref clonePod)) return false; @@ -167,6 +183,12 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(bodyToClone, out var humanoid)) return false; // whatever body was to be cloned, was not a humanoid + // Begin Nyano-code: allow paradox anomalies to be cloned. + var pref = humanoid.LastProfileLoaded; + + if (pref == null) + return false; + // End Nyano-code if (!_prototype.TryIndex(humanoid.Species, out var speciesPrototype)) return false; @@ -222,11 +244,11 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(uid); return true; } + // End Nyano-code. } // end of genetic damage checks - var mob = Spawn(speciesPrototype.Prototype, Transform(uid).MapPosition); - _humanoidSystem.CloneAppearance(bodyToClone, mob); + var mob = FetchAndSpawnMob(clonePod, pref, speciesPrototype, humanoid, bodyToClone, karmaBonus); //DeltaV Replaces CloneAppearance with Metem/Clone via FetchAndSpawnMob ///Nyano - Summary: adds the potential psionic trait to the reanimated mob. EnsureComp(mob); @@ -326,7 +348,6 @@ private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) var transform = Transform(uid); var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform)); var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true); - if (HasComp(uid)) { _audio.PlayPvs(clonePod.ScreamSound, uid); @@ -354,6 +375,84 @@ private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) RemCompDeferred(uid); } + /// + /// Start Nyano Code: Handles fetching the mob and any appearance stuff... + /// + private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharacterProfile pref, SpeciesPrototype speciesPrototype, HumanoidAppearanceComponent humanoid, EntityUid bodyToClone, float karmaBonus) + { + List sexes = new(); + bool switchingSpecies = false; + bool applyKarma = false; + var toSpawn = speciesPrototype.Prototype; + TryComp(bodyToClone, out var oldKarma); + + if (TryComp(clonePod.Owner, out var metem)) + { + toSpawn = _metem.GetSpawnEntity(clonePod.Owner, karmaBonus, metem, speciesPrototype, out var newSpecies, oldKarma?.Score); + applyKarma = true; + + if (newSpecies != null) + { + sexes = newSpecies.Sexes; + + if (speciesPrototype.ID != newSpecies.ID) + switchingSpecies = true; + + speciesPrototype = newSpecies; + } + } + + var mob = Spawn(toSpawn, Transform(clonePod.Owner).MapPosition); + if (TryComp(mob, out var newHumanoid)) + { + if (switchingSpecies || HasComp(bodyToClone)) + { + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); + if (sexes.Contains(humanoid.Sex)) + pref = pref.WithSex(humanoid.Sex); + + pref = pref.WithGender(humanoid.Gender); + pref = pref.WithAge(humanoid.Age); + + } + _humanoidSystem.LoadProfile(mob, pref); + } + + if (applyKarma) + { + var karma = EnsureComp(mob); + karma.Score++; + if (oldKarma != null) + karma.Score += oldKarma.Score; + } + + var ev = new CloningEvent(bodyToClone, mob); + RaiseLocalEvent(bodyToClone, ref ev); + + if (!ev.NameHandled) + _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); + + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = humanoid.Gender; + Dirty(grammar); + + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + + _tag.AddTag(mob, "DoorBumpOpener"); + + return mob; + } + //End Nyano Code public void Reset(RoundRestartCleanupEvent ev) { ClonesWaitingForMind.Clear(); diff --git a/Content.Server/Medical/Components/MedicalScannerComponent.cs b/Content.Server/Medical/Components/MedicalScannerComponent.cs index aef56d6c0b7..96de6499875 100644 --- a/Content.Server/Medical/Components/MedicalScannerComponent.cs +++ b/Content.Server/Medical/Components/MedicalScannerComponent.cs @@ -15,5 +15,8 @@ public sealed partial class MedicalScannerComponent : SharedMedicalScannerCompon [DataField, ViewVariables(VVAccess.ReadWrite)] public float CloningFailChanceMultiplier = 1f; + + // Nyano, needed for Metem Machine. + public float MetemKarmaBonus = 0.25f; } } diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs new file mode 100644 index 00000000000..246495cee00 --- /dev/null +++ b/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Server.Nyanotrasen.Cloning +{ + /// + /// This tracks how many times you have already been cloned and lowers your chance of getting a humanoid each time. + /// + [RegisterComponent] + public sealed partial class MetempsychosisKarmaComponent : Component + { + [DataField("score")] + public int Score = 0; + } +} diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs new file mode 100644 index 00000000000..0adcc9b5b25 --- /dev/null +++ b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Random; + +namespace Content.Server.Nyanotrasen.Cloning +{ + [RegisterComponent] + public sealed partial class MetempsychoticMachineComponent : Component + { + /// + /// Chance you will spawn as a humanoid instead of a non humanoid. + /// + [DataField("humanoidBaseChance")] + public float HumanoidBaseChance = 0.75f; + + [ValidatePrototypeId] + [DataField("metempsychoticHumanoidPool")] + public string MetempsychoticHumanoidPool = "MetempsychoticHumanoidPool"; + + [ValidatePrototypeId] + [DataField("metempsychoticNonHumanoidPool")] + public string MetempsychoticNonHumanoidPool = "MetempsychoticNonhumanoidPool"; + } +} diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs new file mode 100644 index 00000000000..62dc1b078e0 --- /dev/null +++ b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs @@ -0,0 +1,47 @@ +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Random; +using Content.Shared.Random.Helpers; +using Robust.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Server.Nyanotrasen.Cloning +{ + public sealed class MetempsychoticMachineSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private ISawmill _sawmill = default!; + + public string GetSpawnEntity(EntityUid uid, float karmaBonus, MetempsychoticMachineComponent component, SpeciesPrototype oldSpecies, out SpeciesPrototype? species, int? karma = null) + { + var chance = component.HumanoidBaseChance + karmaBonus; + + if (karma != null) + chance -= ((1 - component.HumanoidBaseChance) * (float) karma); + + if (chance > 1 && _random.Prob(chance - 1)) + { + species = oldSpecies; + return oldSpecies.Prototype; + } + else + chance = 1; + + chance = Math.Clamp(chance, 0, 1); + if (_random.Prob(chance) && + _prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) && + _prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype)) + { + species = speciesPrototype; + return speciesPrototype.Prototype; + } + else + { + species = null; + _sawmill.Error("Could not index species for metempsychotic machine..."); + return "MobHuman"; + } + } + } +} diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs index b0bc0eb9a0c..4785482ada4 100644 --- a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs +++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs @@ -6,6 +6,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using Content.Shared.Preferences; //DeltaV, used for Metempsychosis, Fugitive, and Paradox Anomaly namespace Content.Shared.Humanoid; diff --git a/Resources/Locale/en-US/nyanotrasen/research/technologies.ftl b/Resources/Locale/en-US/nyanotrasen/research/technologies.ftl index 6b0debd6fd8..2aa6625c2c4 100644 --- a/Resources/Locale/en-US/nyanotrasen/research/technologies.ftl +++ b/Resources/Locale/en-US/nyanotrasen/research/technologies.ftl @@ -1,2 +1,3 @@ research-technology-psionic-countermeasures = Psionic Countermeasures research-technology-teleportation = Teleportation +research-technology-metempsychosis = Metempsychosis diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 30cad0ea0a9..f251f158b80 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -466,7 +466,7 @@ - MailingUnitElectronics - SalvageMagnetMachineCircuitboard - StationMapElectronics -# - MetempsychoticMachineCircuitboard + - MetempsychoticMachineCircuitboard - DeepFryerMachineCircuitboard # End Nyano additions - SalvageExpeditionsComputerCircuitboard # DeltaV diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml index a1a7a859e08..0e4c262b543 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml @@ -1,3 +1,24 @@ +- type: entity + id: MetempsychoticMachineCircuitboard + parent: BaseMachineCircuitboard + name: metempsychotic machine machine board + description: A machine printed circuit board for a cloning pod + components: + - type: Sprite + state: medical + - type: MachineBoard + prototype: MetempsychoticMachine + requirements: + Capacitor: 2 + Manipulator: 2 + materialRequirements: + Glass: 1 + Cable: 1 + - type: ReverseEngineering + difficulty: 3 + recipes: + - MetempsychoticMachineCircuitboard + - type: entity id: ReverseEngineeringMachineCircuitboard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml new file mode 100644 index 00000000000..d8e791af1ed --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml @@ -0,0 +1,25 @@ +- type: entity + parent: CloningPod + id: MetempsychoticMachine + name: metempsychotic machine + description: Speeds along the transmigration of a soul to its next vessel. + components: + - type: MetempsychoticMachine + - type: CloningPod + - type: Machine + board: MetempsychoticMachineCircuitboard + - type: Sprite + sprite: Nyanotrasen/Structures/Machines/metempsychotic.rsi + snapCardinals: true + layers: + - state: pod_0 + - type: Appearance + - type: GenericVisualizer + visuals: + enum.CloningPodVisuals.Status: + base: + Cloning: { state: pod_1 } + NoMind: { state: pod_1 } + Gore: { state: pod_1 } + Idle: { state: pod_0 } + - type: Psionic diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml index 591b8aec081..418864cd408 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml @@ -1,11 +1,11 @@ -#- type: latheRecipe -# id: MetempsychoticMachineCircuitboard -# result: MetempsychoticMachineCircuitboard -# completetime: 4 -# materials: -# Steel: 100 -# Glass: 900 -# Gold: 100 +- type: latheRecipe + id: MetempsychoticMachineCircuitboard + result: MetempsychoticMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 - type: latheRecipe id: ReverseEngineeringMachineCircuitboard @@ -50,4 +50,4 @@ completetime: 4 materials: Steel: 100 - Glass: 900 \ No newline at end of file + Glass: 900 diff --git a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml index 78599728c05..7c89c0f7d04 100644 --- a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml +++ b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml @@ -14,6 +14,22 @@ - ClothingHeadCage # - ShellSoulbreaker # DeltaV - Placing it under Exotic Ammunition because that's what it is. +- type: technology + id: Metempsychosis + name: research-technology-metempsychosis + icon: + sprite: Nyanotrasen/Structures/Machines/metempsychotic.rsi + state: pod_0 + discipline: Experimental + tier: 2 + cost: 15000 + recipeUnlocks: + - BiomassReclaimerMachineCircuitboard + - CloningConsoleComputerCircuitboard + - MedicalScannerMachineCircuitboard + - MetempsychoticMachineCircuitboard + + # Tier 3 # - type: technology diff --git a/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml b/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml new file mode 100644 index 00000000000..891067b1c1f --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml @@ -0,0 +1,13 @@ +- type: weightedRandom + id: MetempsychoticHumanoidPool # Species prototypes + weights: + Human: 1 + Felinid: 1 + Oni: 1 + Arachnid: 1 + Harpy: 1 + Moth: 1 + Diona: 0.5 + Reptilian: 0.5 + SlimePerson: 0.5 + Vulpkanin: 0.5 diff --git a/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml new file mode 100644 index 00000000000..dcbe23f6082 --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml @@ -0,0 +1,9 @@ +- type: weightedRandom + id: MetempsychoticNonhumanoidPool # Entity prototypes + weights: + MobMonkey: 1 + MobGorilla: 1 + # MobKangaroo: 0.5 # Mobs here need to be either VERY funny or up to standard. + MobXenoQueen: 0.01 + MobCrab: 0.01 + MobPenguin: 1 #ODJ's orders diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json new file mode 100644 index 00000000000..7276fde67e5 --- /dev/null +++ b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-4.0", + "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "pod_0" + }, + { + "name": "pod_1", + "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] + } + ] +} diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png new file mode 100644 index 00000000000..b7a870e9d2a Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png differ diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png new file mode 100644 index 00000000000..750e0c7feda Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png differ