From b8952ecb830680a34071b7aabab42da06c486519 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 20:42:07 -0400 Subject: [PATCH 01/33] Cloning Refactor --- .../Cloning/CloningConsoleSystem.cs | 88 ++-- Content.Server/Cloning/CloningSystem.cs | 447 +++++++++++------- .../Components/ActiveCloningPodComponent.cs | 9 - .../BiomassReclaimerSystem.cs | 85 ++-- .../Medical/MedicalScannerSystem.cs | 91 ++-- .../Cloning/MetempsychosisKarmaComponent.cs | 2 +- .../Cloning/MetempsychoticMachineComponent.cs | 22 - .../Cloning/MetempsychoticMachineSystem.cs | 47 -- Content.Shared/CCVar/CCVars.cs | 16 +- Content.Shared/Cloning/CloningPodComponent.cs | 95 +++- .../Machines/metempsychoticMachine.yml | 2 +- .../metempsychoticNonHumanoids.yml | 4 +- 12 files changed, 473 insertions(+), 435 deletions(-) delete mode 100644 Content.Server/Cloning/Components/ActiveCloningPodComponent.cs delete mode 100644 Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs delete mode 100644 Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index c95c37312e5..32d7d18c953 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(); @@ -58,8 +58,10 @@ private void OnButtonPressed(EntityUid uid, CloningConsoleComponent consoleCompo switch (args.Button) { case UiButton.Clone: - if (consoleComponent.GeneticScanner != null && consoleComponent.CloningPod != null) - TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, consoleComponent: consoleComponent); + if (consoleComponent.GeneticScanner != null + && consoleComponent.CloningPod != null + && TryComp(consoleComponent.CloningPod.Value, out var cloningPod)) + TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, cloningPod, consoleComponent: consoleComponent); break; } UpdateUserInterface(uid, consoleComponent); @@ -93,13 +95,15 @@ private void OnMapInit(EntityUid uid, CloningConsoleComponent component, MapInit private void OnNewLink(EntityUid uid, CloningConsoleComponent component, NewLinkEvent args) { - if (TryComp(args.Sink, out var scanner) && args.SourcePort == CloningConsoleComponent.ScannerPort) + if (TryComp(args.Sink, out var scanner) + && args.SourcePort == CloningConsoleComponent.ScannerPort) { component.GeneticScanner = args.Sink; scanner.ConnectedConsole = uid; } - if (TryComp(args.Sink, out var pod) && args.SourcePort == CloningConsoleComponent.PodPort) + if (TryComp(args.Sink, out var pod) + && args.SourcePort == CloningConsoleComponent.PodPort) { component.CloningPod = args.Sink; pod.ConnectedConsole = uid; @@ -125,11 +129,10 @@ private void OnUIOpen(EntityUid uid, CloningConsoleComponent component, AfterAct private void OnAnchorChanged(EntityUid uid, CloningConsoleComponent component, ref AnchorStateChangedEvent args) { - if (args.Anchored) - { - RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component); + if (!args.Anchored + || !RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component)) return; - } + UpdateUserInterface(uid, component); } @@ -148,49 +151,52 @@ public void UpdateUserInterface(EntityUid consoleUid, CloningConsoleComponent co _uiSystem.SetUiState(ui, newState); } - public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent? cloningPod = null, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) + public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent cloningPod, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) { - if (!Resolve(uid, ref consoleComponent) || !Resolve(cloningPodUid, ref cloningPod) || !Resolve(scannerUid, ref scannerComp)) - return; - - if (!Transform(cloningPodUid).Anchored || !Transform(scannerUid).Anchored) - return; - - if (!consoleComponent.CloningPodInRange || !consoleComponent.GeneticScannerInRange) + if (!Resolve(uid, ref consoleComponent) + || !Resolve(scannerUid, ref scannerComp) + || !Transform(cloningPodUid).Anchored + || !Transform(scannerUid).Anchored + || !consoleComponent.CloningPodInRange + || !consoleComponent.GeneticScannerInRange) return; var body = scannerComp.BodyContainer.ContainedEntity; - if (body is null) + if (body is null + || !_mindSystem.TryGetMind(body.Value, out var mindId, out var mind) + || mind.UserId.HasValue == false + || mind.Session == null) return; - if (!_mindSystem.TryGetMind(body.Value, out var mindId, out var mind)) - return; - - if (mind.UserId.HasValue == false || mind.Session == null) - return; - // 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)}."); + if (_cloningSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier)) + { + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} started cloning {ToPrettyString(body.Value)}."); + _cloningSystem.AttemptCloning(cloningPodUid, cloningPod); + } } - public void RecheckConnections(EntityUid console, EntityUid? cloningPod, EntityUid? scanner, CloningConsoleComponent? consoleComp = null) + public bool RecheckConnections(EntityUid console, EntityUid? cloningPod, EntityUid? scanner, CloningConsoleComponent? consoleComp = null) { if (!Resolve(console, ref consoleComp)) - return; + return false; + var connected = true; if (scanner != null) { - Transform(scanner.Value).Coordinates.TryDistance(EntityManager, Transform((console)).Coordinates, out float scannerDistance); + Transform(scanner.Value).Coordinates.TryDistance(EntityManager, Transform(console).Coordinates, out float scannerDistance); consoleComp.GeneticScannerInRange = scannerDistance <= consoleComp.MaxDistance; + connected = false; } if (cloningPod != null) { - Transform(cloningPod.Value).Coordinates.TryDistance(EntityManager, Transform((console)).Coordinates, out float podDistance); + Transform(cloningPod.Value).Coordinates.TryDistance(EntityManager, Transform(console).Coordinates, out float podDistance); consoleComp.CloningPodInRange = podDistance <= consoleComp.MaxDistance; + connected = false; } UpdateUserInterface(console, consoleComp); + return connected; } private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConsoleComponent consoleComponent) { @@ -206,25 +212,19 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso EntityUid? scanBody = scanner.BodyContainer.ContainedEntity; // GET STATE - if (scanBody == null || !HasComp(scanBody)) + if (scanBody == null + || !HasComp(scanBody)) clonerStatus = ClonerStatus.ScannerEmpty; else { scanBodyInfo = MetaData(scanBody.Value).EntityName; if (!_mobStateSystem.IsDead(scanBody.Value)) - { clonerStatus = ClonerStatus.ScannerOccupantAlive; - } - else - { - if (!_mindSystem.TryGetMind(scanBody.Value, out _, out var mind) || - mind.UserId == null || - !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) - { - clonerStatus = ClonerStatus.NoMindDetected; - } - } + else if (!_mindSystem.TryGetMind(scanBody.Value, out _, out var mind) + || mind.UserId == null + || !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) + clonerStatus = ClonerStatus.NoMindDetected; } } @@ -240,7 +240,7 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso EntityUid? cloneBody = clonePod.BodyContainer.ContainedEntity; clonerMindPresent = clonePod.Status == CloningPodStatus.Cloning; - if (HasComp(consoleComponent.CloningPod)) + if (clonePod.ActivelyCloning) { if (cloneBody != null) cloneBodyInfo = Identity.Name(cloneBody.Value, EntityManager); @@ -248,9 +248,7 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso } } else - { clonerStatus = ClonerStatus.NoClonerDetected; - } return new CloningConsoleBoundUserInterfaceState( scanBodyInfo, diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 92e658591a0..8bfe4c1b69a 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -23,6 +23,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Random; using Content.Shared.Roles.Jobs; using Robust.Server.Containers; using Robust.Server.GameObjects; @@ -33,9 +34,8 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -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.Server.Traits.Assorted; +using Content.Shared.Speech; using Content.Shared.Tag; using Content.Shared.Preferences; using Content.Shared.Emoting; @@ -44,12 +44,21 @@ 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 Robust.Shared.GameObjects.Components.Localization; using Content.Shared.SSDIndicator; using Content.Shared.Damage.ForceSay; -using Content.Server.Polymorph.Components; using Content.Shared.Chat; +using Content.Server.Body.Components; +using Content.Shared.Random.Helpers; +using Content.Shared.Contests; +using Content.Shared.Abilities.Psionics; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Language.Components; +using Content.Shared.Language; +using Robust.Shared.Utility; +using Timer = Robust.Shared.Timing.Timer; +using Content.Server.Power.Components; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Content.Server.Cloning { @@ -57,31 +66,31 @@ public sealed class CloningSystem : EntitySystem { [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; [Dependency] private readonly IPlayerManager _playerManager = null!; - [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly EuiManager _euiManager = null!; [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly ContainerSystem _containerSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly PuddleSystem _puddleSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly MaterialStorageSystem _material = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [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 + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; public readonly Dictionary ClonesWaitingForMind = new(); - public const float EasyModeCloningCost = 0.7f; public override void Initialize() { @@ -94,6 +103,7 @@ public override void Initialize() SubscribeLocalEvent(OnAnchor); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnPowerChanged); } private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) @@ -102,12 +112,19 @@ private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, Compon _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); } + private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) + { + if (!args.Powered + && component.ActivelyCloning) + CauseCloningFail(uid, component); + } + internal void TransferMindToClone(EntityUid mindId, MindComponent mind) { - if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) || - !EntityManager.EntityExists(entity) || - !TryComp(entity, out var mindComp) || - mindComp.Mind != null) + if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) + || !EntityManager.EntityExists(entity) + || !TryComp(entity, out var mindComp) + || mindComp.Mind != null) return; _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); @@ -117,10 +134,10 @@ internal void TransferMindToClone(EntityUid mindId, MindComponent mind) private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) { - if (clonedComponent.Parent == EntityUid.Invalid || - !EntityManager.EntityExists(clonedComponent.Parent) || - !TryComp(clonedComponent.Parent, out var cloningPodComponent) || - uid != cloningPodComponent.BodyContainer.ContainedEntity) + if (clonedComponent.Parent == EntityUid.Invalid + || !EntityManager.EntityExists(clonedComponent.Parent) + || !TryComp(clonedComponent.Parent, out var cloningPodComponent) + || uid != cloningPodComponent.BodyContainer.ContainedEntity) { EntityManager.RemoveComponent(uid); return; @@ -135,124 +152,112 @@ private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisc private void OnAnchor(EntityUid uid, CloningPodComponent component, ref AnchorStateChangedEvent args) { - if (component.ConnectedConsole == null || !TryComp(component.ConnectedConsole, out var console)) - return; + if (component.ActivelyCloning) + CauseCloningFail(uid, component); - if (args.Anchored) - { - _cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console); + if (component.ConnectedConsole == null + || !TryComp(component.ConnectedConsole, out var console) + || !args.Anchored + || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console)) return; - } + _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); } private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEvent args) { - if (!args.IsInDetailsRange || !_powerReceiverSystem.IsPowered(uid)) + if (!args.IsInDetailsRange + || !_powerReceiverSystem.IsPowered(uid)) return; args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); } - // 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; - - if (HasComp(uid)) - return false; - - var mind = mindEnt.Comp; - if (ClonesWaitingForMind.TryGetValue(mind, out var clone)) - { - if (EntityManager.EntityExists(clone) && - !_mobStateSystem.IsDead(clone) && - TryComp(clone, out var cloneMindComp) && - (cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt)) - return false; // Mind already has clone - - ClonesWaitingForMind.Remove(mind); - } - if (mind.OwnedEntity != null && !_mobStateSystem.IsDead(mind.OwnedEntity.Value)) - return false; // Body controlled by mind is not dead - - // Yes, we still need to track down the client because we need to open the Eui - if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) - return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. - - if (!TryComp(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; - - if (!TryComp(bodyToClone, out var physics)) - return false; - - var cloningCost = (int) Math.Round(physics.FixturesMass); + private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone) + { + if (!TryComp(bodyToClone, out _)) + return true; - if (_configManager.GetCVar(CCVars.BiomassEasyMode)) - cloningCost = (int) Math.Round(cloningCost * EasyModeCloningCost); + _chatSystem.TrySendInGameICMessage(uid, + Loc.GetString("cloning-console-uncloneable-trait-error"), + InGameICChatType.Speak, false); - // Check if they have the uncloneable trait - if (TryComp(bodyToClone, out _)) - { - if (clonePod.ConnectedConsole != null) - { - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, - Loc.GetString("cloning-console-uncloneable-trait-error"), - InGameICChatType.Speak, false); - } - - return false; - } + return false; + } - // biomass checks - var biomassAmount = _material.GetMaterialAmount(uid, clonePod.RequiredMaterial); + private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod) + { + var cloningCost = (int) Math.Round(physics.FixturesMass * _config.GetCVar(CCVars.CloningBiomassCostMultiplier)); - if (biomassAmount < cloningCost) + if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) { - if (clonePod.ConnectedConsole != null) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); + _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); return false; } _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); clonePod.UsedBiomass = cloningCost; - // end of biomass checks - // genetic damage checks - if (TryComp(bodyToClone, out var damageable) && - damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg)) + return true; + } + + private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, float failChanceModifier = 1) + { + if (TryComp(bodyToClone, out var damageable) + && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) + && clonePod.ConnectedConsole is not null) { var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); chance *= failChanceModifier; - if (cellularDmg > 0 && clonePod.ConnectedConsole != null) + if (cellularDmg > 0) _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); - if (_robustRandom.Prob(chance)) + if (_random.Prob(chance)) { - UpdateStatus(uid, CloningPodStatus.Gore, clonePod); - clonePod.FailedClone = true; - AddComp(uid); + CauseCloningFail(uid, clonePod); return true; } - // End Nyano-code. } - // end of genetic damage checks + return false; + } + + private void CauseCloningFail(EntityUid uid, CloningPodComponent component) + { + UpdateStatus(uid, CloningPodStatus.Gore, component); + component.FailedClone = true; + component.ActivelyCloning = true; + } + + public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent clonePod, float failChanceModifier = 1) + { + if (!_mobStateSystem.IsDead(bodyToClone) + || clonePod.ActivelyCloning + || clonePod.ConnectedConsole == null + || CheckUncloneable(uid, bodyToClone) + || !TryComp(bodyToClone, out var humanoid) + || !TryComp(bodyToClone, out var physics) + || CheckBiomassCost(uid, physics, clonePod) + || !ClonesWaitingForMind.TryGetValue(mindEnt.Comp, out var clone) + || !TryComp(clone, out var cloneMindComp) + || cloneMindComp.Mind == null + || cloneMindComp.Mind == mindEnt + || mindEnt.Comp.UserId == null + || !_playerManager.TryGetSessionById(mindEnt.Comp.UserId.Value, out var client)) + return false; + + ClonesWaitingForMind.Remove(mindEnt.Comp); + + var pref = humanoid.LastProfileLoaded; + + if (pref == null + || !_prototypeManager.TryIndex(humanoid.Species, out var speciesPrototype)) + return false; - var mob = FetchAndSpawnMob(clonePod, pref, speciesPrototype, humanoid, bodyToClone, karmaBonus); //DeltaV Replaces CloneAppearance with Metem/Clone via FetchAndSpawnMob + if (CheckGeneticDamage(uid, bodyToClone, clonePod, failChanceModifier)) + return true; - ///Nyano - Summary: adds the potential psionic trait to the reanimated mob. - EnsureComp(mob); + var mob = FetchAndSpawnMob(uid, clonePod, pref, speciesPrototype, humanoid, bodyToClone); var ev = new CloningEvent(bodyToClone, mob); RaiseLocalEvent(bodyToClone, ref ev); @@ -261,65 +266,56 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(mob); - cloneMindReturn.Mind = mind; + cloneMindReturn.Mind = mindEnt.Comp; cloneMindReturn.Parent = uid; _containerSystem.Insert(mob, clonePod.BodyContainer); - ClonesWaitingForMind.Add(mind, mob); + ClonesWaitingForMind.Add(mindEnt.Comp, mob); UpdateStatus(uid, CloningPodStatus.NoMind, clonePod); - _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mind, this), client); + _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mindEnt.Comp, this), client); - AddComp(uid); + clonePod.ActivelyCloning = true; - // TODO: Ideally, components like this should be components on the mind entity so this isn't necessary. - // Add on special job components to the mob. if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) - { foreach (var special in prototype.Special) - { if (special is AddComponentSpecial) special.AfterEquip(mob); - } - } return true; } - public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) + public void AttemptCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) { - cloningPod.Status = status; - _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => UpdateCloning(cloningPod, cloningPodComponent)); } - public override void Update(float frameTime) + public void UpdateCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var _, out var cloning)) - { - if (!_powerReceiverSystem.IsPowered(uid)) - continue; - - if (cloning.BodyContainer.ContainedEntity == null && !cloning.FailedClone) - continue; + if (!cloningPodComponent.ActivelyCloning + || !_powerReceiverSystem.IsPowered(cloningPod) + || cloningPodComponent.BodyContainer.ContainedEntity == null + || cloningPodComponent.FailedClone) + EndFailedCloning(cloningPod, cloningPodComponent); - cloning.CloningProgress += frameTime; - if (cloning.CloningProgress < cloning.CloningTime) - continue; + Eject(cloningPod, cloningPodComponent); + } - if (cloning.FailedClone) - EndFailedCloning(uid, cloning); - else - Eject(uid, cloning); - } + public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) + { + cloningPod.Status = status; + _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); } /// - /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. + /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. /// private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) { if (!this.IsPowered(uid, EntityManager)) return; + if (clonePod.ActivelyCloning) + CauseCloningFail(uid, clonePod); + _audio.PlayPvs(clonePod.SparkSound, uid); _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); args.Handled = true; @@ -327,10 +323,9 @@ private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmagg public void Eject(EntityUid uid, CloningPodComponent? clonePod) { - if (!Resolve(uid, ref clonePod)) - return; - - if (clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity || clonePod.CloningProgress < clonePod.CloningTime) + if (!Resolve(uid, ref clonePod) + || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity + || clonePod.CloningProgress < clonePod.CloningTime) return; EntityManager.RemoveComponent(entity); @@ -338,59 +333,81 @@ public void Eject(EntityUid uid, CloningPodComponent? clonePod) clonePod.CloningProgress = 0f; clonePod.UsedBiomass = 0; UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - RemCompDeferred(uid); + clonePod.ActivelyCloning = false; } private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) { + if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) + { + if (TryComp(entity, out var physics) + && TryComp(entity, out var bloodstream)) + MakeAHugeMess(uid, physics, bloodstream); + else MakeAHugeMess(uid); + + QueueDel(entity); + } + clonePod.FailedClone = false; clonePod.CloningProgress = 0f; UpdateStatus(uid, CloningPodStatus.Idle, 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); - Spawn(clonePod.MobSpawnId, transform.Coordinates); + Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); } + if (!HasComp(uid)) + _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); + + clonePod.UsedBiomass = 0; + clonePod.ActivelyCloning = false; + } + + /// + /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. + /// + /// + /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA + /// + /// + /// + /// + private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) + { + var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); Solution bloodSolution = new(); - var i = 0; - while (i < 1) - { - tileMix?.AdjustMoles(Gas.Ammonia, 6f); - bloodSolution.AddReagent("Blood", 50); - if (_robustRandom.Prob(0.2f)) - i++; - } - _puddleSystem.TrySpillAt(uid, bloodSolution, out _); + tileMix?.AdjustMoles(Gas.Ammonia, 0.5f + * ((physics is not null) + ? physics.Mass + : 71)); - if (!HasComp(uid)) - { - _material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); - } + bloodSolution.AddReagent("blood", 0.8f + * ((blood is not null) + ? blood.BloodMaxVolume + : 300)); - clonePod.UsedBiomass = 0; - RemCompDeferred(uid); + _puddleSystem.TrySpillAt(uid, bloodSolution, out _); } /// - /// Start Nyano Code: Handles fetching the mob and any appearance stuff... + /// 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) + private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clonePodComp, HumanoidCharacterProfile pref, SpeciesPrototype speciesPrototype, HumanoidAppearanceComponent humanoid, EntityUid bodyToClone) { List sexes = new(); bool switchingSpecies = false; - bool applyKarma = false; var toSpawn = speciesPrototype.Prototype; - TryComp(bodyToClone, out var oldKarma); + var forceOldProfile = true; + var oldKarma = 0; + if (TryComp(bodyToClone, out var oldKarmaComp)) + oldKarma += oldKarmaComp.Score; - if (TryComp(clonePod.Owner, out var metem)) + if (clonePodComp.DoMetempsychosis) { - toSpawn = _metem.GetSpawnEntity(clonePod.Owner, karmaBonus, metem, speciesPrototype, out var newSpecies, oldKarma?.Score); - applyKarma = true; + toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); + forceOldProfile = !changeProfile; if (newSpecies != null) { @@ -398,15 +415,15 @@ private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharact if (speciesPrototype.ID != newSpecies.ID) switchingSpecies = true; - - speciesPrototype = newSpecies; } } + EntityUid mob = Spawn(toSpawn, _transformSystem.GetMapCoordinates(clonePod)); + EnsureComp(mob, out var newKarma); + newKarma.Score += oldKarma; - var mob = Spawn(toSpawn, Transform(clonePod.Owner).MapPosition); if (TryComp(mob, out var newHumanoid)) { - if (switchingSpecies || HasComp(bodyToClone)) + if (switchingSpecies && !forceOldProfile) { pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); if (sexes.Contains(humanoid.Sex)) @@ -419,14 +436,6 @@ private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharact _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); @@ -436,9 +445,29 @@ private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharact var grammar = EnsureComp(mob); grammar.ProperNoun = true; grammar.Gender = humanoid.Gender; - Dirty(grammar); + Dirty(mob, grammar); + + if (forceOldProfile + && TryComp(bodyToClone, out var psionic)) + { + var newPsionic = _serialization.CreateCopy(psionic, null, false, true); + AddComp(mob, newPsionic, true); + } + + if (TryComp(bodyToClone, out var oldKnowLangs)) + { + var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); + AddComp(mob, newKnowLangs, true); + } + + + if (TryComp(bodyToClone, out var oldSpeakLangs)) + { + var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); + AddComp(mob, newSpeakLangs, true); + } + - EnsureComp(mob); EnsureComp(mob); EnsureComp(mob); EnsureComp(mob); @@ -453,10 +482,68 @@ private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharact return mob; } - //End Nyano Code public void Reset(RoundRestartCleanupEvent ev) { ClonesWaitingForMind.Clear(); } + + public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, SpeciesPrototype oldSpecies, int karma, out SpeciesPrototype? species, out bool changeProfile) + { + changeProfile = true; + species = oldSpecies; + if (!_prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) + || !_prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype) + || !_prototypeManager.TryIndex(component.MetempsychoticNonHumanoidPool, out var nonHumanoidPool) + || !_prototypeManager.TryIndex(nonHumanoidPool.Pick(), out var entityPrototype)) + { + DebugTools.Assert("Could not index species for metempsychotic machine."); + changeProfile = false; + return oldSpecies.Prototype; + } + var chance = (component.HumanoidBaseChance - karma * component.KarmaOffset) * _contests.MindContest(oldBody, true); + + + var ev = new ReincarnatingEvent(oldBody, chance); + RaiseLocalEvent(oldBody, ref ev); + + chance = ev.OverrideChance + ? ev.ReincarnationChances + : chance * ev.ReincarnationChanceModifier; + + switch (ev.ForcedType) + { + case ForcedMetempsychosisType.None: + if (!ev.NeverTrulyClone + && chance > 1 + && _random.Prob(chance - 1)) + { + changeProfile = false; + return oldSpecies.Prototype; + } + + chance = Math.Clamp(chance, 0, 1); + if (_random.Prob(chance)) + { + species = speciesPrototype; + return speciesPrototype.Prototype; + } + species = null; + return entityPrototype.ID; + + case ForcedMetempsychosisType.Clone: + changeProfile = false; + return oldSpecies.Prototype; + + case ForcedMetempsychosisType.RandomHumanoid: + species = speciesPrototype; + return speciesPrototype.Prototype; + + case ForcedMetempsychosisType.RandomNonHumanoid: + species = null; + return entityPrototype.ID; + } + changeProfile = false; + return oldSpecies.Prototype; + } } } diff --git a/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs b/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs deleted file mode 100644 index 11e0e36166a..00000000000 --- a/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Cloning.Components; - -/// -/// Shrimply a tracking component for pods that are cloning. -/// -[RegisterComponent] -public sealed partial class ActiveCloningPodComponent : Component -{ -} diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index d07858aec5c..e9c71aaff59 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -81,11 +81,9 @@ public override void Update(float frameTime) } if (reclaimer.ProcessingTimer > 0) - { continue; - } - var actualYield = (int) (reclaimer.CurrentExpectedYield); // can only have integer biomass + var actualYield = (int) reclaimer.CurrentExpectedYield; // can only have integer biomass reclaimer.CurrentExpectedYield = reclaimer.CurrentExpectedYield - actualYield; // store non-integer leftovers _material.SpawnMultipleFromMaterial(actualYield, BiomassPrototype, Transform(uid).Coordinates); @@ -109,13 +107,10 @@ public override void Initialize() private void OnSuicide(Entity ent, ref SuicideEvent args) { - if (args.Handled) - return; - - if (HasComp(ent)) - return; - - if (TryComp(ent, out var power) && !power.Powered) + if (args.Handled + || HasComp(ent) + || TryComp(ent, out var power) + && !power.Powered) return; _popup.PopupEntity(Loc.GetString("biomass-reclaimer-suicide-others", ("victim", args.Victim)), ent, PopupType.LargeCaution); @@ -138,11 +133,9 @@ private void OnShutdown(EntityUid uid, ActiveBiomassReclaimerComponent component private void OnPowerChanged(EntityUid uid, BiomassReclaimerComponent component, ref PowerChangedEvent args) { - if (args.Powered) - { - if (component.ProcessingTimer > 0) - EnsureComp(uid); - } + if (args.Powered + && component.ProcessingTimer > 0) + EnsureComp(uid); else RemComp(uid); } @@ -153,13 +146,10 @@ private void OnUnanchorAttempt(EntityUid uid, ActiveBiomassReclaimerComponent co } private void OnAfterInteractUsing(Entity reclaimer, ref AfterInteractUsingEvent args) { - if (!args.CanReach || args.Target == null) - return; - - if (!CanGib(reclaimer, args.Used)) - return; - - if (!TryComp(args.Used, out var physics)) + if (!args.CanReach + || args.Target == null + || !CanGib(reclaimer, args.Used) + || !TryComp(args.Used, out var physics)) return; var delay = reclaimer.Comp.BaseInsertionDelay * physics.FixturesMass; @@ -186,10 +176,11 @@ private void OnClimbedOn(Entity reclaimer, ref Climbe private void OnDoAfter(Entity reclaimer, ref ReclaimerDoAfterEvent args) { - if (args.Handled || args.Cancelled) - return; - - if (args.Args.Used == null || args.Args.Target == null || !HasComp(args.Args.Target.Value)) + if (args.Handled + || args.Cancelled + || args.Args.Used == null + || args.Args.Target == null + || !HasComp(args.Args.Target.Value)) return; _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Args.Target.Value):target} in {ToPrettyString(reclaimer):reclaimer}"); @@ -207,18 +198,13 @@ private void StartProcessing(EntityUid toProcess, Entity(ent); if (TryComp(toProcess, out var stream)) - { component.BloodReagent = stream.BloodReagent; - } if (TryComp(toProcess, out var butcherableComponent)) - { component.SpawnedEntities = butcherableComponent.SpawnedEntities; - } - var expectedYield = physics.FixturesMass * component.YieldPerUnitMass; - if (HasComp(toProcess)) - expectedYield *= component.ProduceYieldMultiplier; - component.CurrentExpectedYield += expectedYield; + component.CurrentExpectedYield += HasComp(toProcess) + ? physics.FixturesMass * component.YieldPerUnitMass * component.ProduceYieldMultiplier + : physics.FixturesMass * component.YieldPerUnitMass; component.ProcessingTimer = physics.FixturesMass * component.ProcessingTimePerUnitMass; @@ -227,31 +213,26 @@ private void StartProcessing(EntityUid toProcess, Entity reclaimer, EntityUid dragged) { - if (HasComp(reclaimer)) + if (HasComp(reclaimer) + || !Transform(reclaimer).Anchored + || TryComp(reclaimer, out var power) && !power.Powered) return false; bool isPlant = HasComp(dragged); - if (!isPlant && !HasComp(dragged)) - return false; - - if (!Transform(reclaimer).Anchored) - return false; - - if (TryComp(reclaimer, out var power) && !power.Powered) + if (!isPlant + && !HasComp(dragged) + || !isPlant + && reclaimer.Comp.SafetyEnabled + && !_mobState.IsDead(dragged)) return false; - if (!isPlant && reclaimer.Comp.SafetyEnabled && !_mobState.IsDead(dragged)) + if (_configManager.GetCVar(CCVars.CloningReclaimSouledBodies) + && HasComp(dragged) + && _minds.TryGetMind(dragged, out _, out var mind) + && mind.UserId != null + && _playerManager.TryGetSessionById(mind.UserId.Value, out _)) return false; - // Reject souled bodies in easy mode. - if (_configManager.GetCVar(CCVars.BiomassEasyMode) && - HasComp(dragged) && - _minds.TryGetMind(dragged, out _, out var mind)) - { - if (mind.UserId != null && _playerManager.TryGetSessionById(mind.UserId.Value, out _)) - return false; - } - return true; } } diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 91184ddc162..2548258102b 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -15,7 +15,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Robust.Server.Containers; -using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; // Hmm... +using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; namespace Content.Server.Medical { @@ -78,29 +78,27 @@ private void OnRelayMovement(EntityUid uid, MedicalScannerComponent scannerCompo private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component, GetVerbsEvent args) { - if (args.Using == null || - !args.CanAccess || - !args.CanInteract || - IsOccupied(component) || - !CanScannerInsert(uid, args.Using.Value, component)) + if (args.Using == null + || !args.CanAccess + || !args.CanInteract + || IsOccupied(component) + || !CanScannerInsert(uid, args.Using.Value, component) + || !TryComp(args.Using.Value, out var metadata)) return; - var name = "Unknown"; - if (TryComp(args.Using.Value, out var metadata)) - name = metadata.EntityName; - InteractionVerb verb = new() { Act = () => InsertBody(uid, args.Target, component), Category = VerbCategory.Insert, - Text = name + Text = metadata.EntityName }; args.Verbs.Add(verb); } private void AddAlternativeVerbs(EntityUid uid, MedicalScannerComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract) + if (!args.CanAccess + || !args.CanInteract) return; // Eject verb @@ -115,11 +113,8 @@ private void AddAlternativeVerbs(EntityUid uid, MedicalScannerComponent componen }; args.Verbs.Add(verb); } - - // Self-insert verb - if (!IsOccupied(component) && - CanScannerInsert(uid, args.User, component) && - _blocker.CanMove(args.User)) + else if (CanScannerInsert(uid, args.User, component) + && _blocker.CanMove(args.User)) { AlternativeVerb verb = new() { @@ -147,41 +142,26 @@ private void OnPortDisconnected(EntityUid uid, MedicalScannerComponent component private void OnAnchorChanged(EntityUid uid, MedicalScannerComponent component, ref AnchorStateChangedEvent args) { - if (component.ConnectedConsole == null || !TryComp(component.ConnectedConsole, out var console)) + if (component.ConnectedConsole == null + || !TryComp(component.ConnectedConsole, out var console) + || !args.Anchored + || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, console.CloningPod, uid, console)) return; - if (args.Anchored) - { - _cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, console.CloningPod, uid, console); - return; - } _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); } private MedicalScannerStatus GetStatus(EntityUid uid, MedicalScannerComponent scannerComponent) { - if (this.IsPowered(uid, EntityManager)) - { - var body = scannerComponent.BodyContainer.ContainedEntity; - if (body == null) - return MedicalScannerStatus.Open; + if (!this.IsPowered(uid, EntityManager)) + return MedicalScannerStatus.Off; - if (!TryComp(body.Value, out var state)) - { // Is not alive or dead or critical - return MedicalScannerStatus.Yellow; - } + var body = scannerComponent.BodyContainer.ContainedEntity; + if (body == null) + return MedicalScannerStatus.Open; - return GetStatusFromDamageState(body.Value, state); - } - return MedicalScannerStatus.Off; - } + if (!TryComp(body.Value, out var state)) + return MedicalScannerStatus.Yellow; - public static bool IsOccupied(MedicalScannerComponent scannerComponent) - { - return scannerComponent.BodyContainer.ContainedEntity != null; - } - - private MedicalScannerStatus GetStatusFromDamageState(EntityUid uid, MobStateComponent state) - { if (_mobStateSystem.IsAlive(uid, state)) return MedicalScannerStatus.Green; @@ -194,12 +174,15 @@ private MedicalScannerStatus GetStatusFromDamageState(EntityUid uid, MobStateCom return MedicalScannerStatus.Yellow; } + public static bool IsOccupied(MedicalScannerComponent scannerComponent) + { + return scannerComponent.BodyContainer.ContainedEntity != null; + } + private void UpdateAppearance(EntityUid uid, MedicalScannerComponent scannerComponent) { if (TryComp(uid, out var appearance)) - { _appearance.SetData(uid, MedicalScannerVisuals.Status, GetStatus(uid, scannerComponent), appearance); - } } public override void Update(float frameTime) @@ -214,20 +197,14 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var scanner)) - { UpdateAppearance(uid, scanner); - } } public void InsertBody(EntityUid uid, EntityUid to_insert, MedicalScannerComponent? scannerComponent) { - if (!Resolve(uid, ref scannerComponent)) - return; - - if (scannerComponent.BodyContainer.ContainedEntity != null) - return; - - if (!HasComp(to_insert)) + if (!Resolve(uid, ref scannerComponent) + || scannerComponent.BodyContainer.ContainedEntity != null + || !HasComp(to_insert)) return; _containerSystem.Insert(to_insert, scannerComponent.BodyContainer); @@ -236,10 +213,8 @@ public void InsertBody(EntityUid uid, EntityUid to_insert, MedicalScannerCompone public void EjectBody(EntityUid uid, MedicalScannerComponent? scannerComponent) { - if (!Resolve(uid, ref scannerComponent)) - return; - - if (scannerComponent.BodyContainer.ContainedEntity is not { Valid: true } contained) + if (!Resolve(uid, ref scannerComponent) + || scannerComponent.BodyContainer.ContainedEntity is not { Valid: true } contained) return; _containerSystem.Remove(contained, scannerComponent.BodyContainer); diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs index 246495cee00..3d2bde7b147 100644 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs +++ b/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs @@ -6,7 +6,7 @@ namespace Content.Server.Nyanotrasen.Cloning [RegisterComponent] public sealed partial class MetempsychosisKarmaComponent : Component { - [DataField("score")] + [DataField] public int Score = 0; } } diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs deleted file mode 100644 index 0adcc9b5b25..00000000000 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 62dc1b078e0..00000000000 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs +++ /dev/null @@ -1,47 +0,0 @@ -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/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index ff2a915b1ba..0c680448a02 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1678,17 +1678,19 @@ public static readonly CVarDef public static readonly CVarDef CrewManifestUnsecure = CVarDef.Create("crewmanifest.unsecure", true, CVar.REPLICATED); - /* - * Biomass - */ + #region Cloning /// /// Enabled: Cloning has 70% cost and reclaimer will refuse to reclaim corpses with souls. (For LRP). /// Disabled: Cloning has full biomass cost and reclaimer can reclaim corpses with souls. (Playtested and balanced for MRP+). /// - public static readonly CVarDef BiomassEasyMode = - CVarDef.Create("biomass.easy_mode", false, CVar.SERVERONLY); + public static readonly CVarDef CloningBiomassCostMultiplier = + CVarDef.Create("cloning.biomass_cost_multiplier", 1f, CVar.SERVERONLY); + + public static readonly CVarDef CloningReclaimSouledBodies = + CVarDef.Create("cloning.reclaim_souled_bodies", false, CVar.SERVERONLY); + #endregion /* * Anomaly */ @@ -2291,7 +2293,7 @@ public static readonly CVarDef /// public static readonly CVarDef StationGoalsChance = CVarDef.Create("game.station_goals_chance", 0.1f, CVar.SERVERONLY); - + #region CPR System /// @@ -2338,7 +2340,7 @@ public static readonly CVarDef /// public static readonly CVarDef CPRAirlossReductionMultiplier = CVarDef.Create("cpr.airloss_reduction_multiplier", 1f, CVar.REPLICATED | CVar.SERVER); - + #endregion #region Contests System diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 88d587c1457..92b4dff9cd8 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.DeviceLinking; using Content.Shared.Materials; +using Content.Shared.Random; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; @@ -17,7 +18,7 @@ public sealed partial class CloningPodComponent : Component public ContainerSlot BodyContainer = default!; /// - /// How long the cloning has been going on for. + /// How long the cloning has been going on for. /// [ViewVariables] public float CloningProgress = 0; @@ -29,34 +30,34 @@ public sealed partial class CloningPodComponent : Component public bool FailedClone = false; /// - /// The material that is used to clone entities. + /// The material that is used to clone entities. /// - [DataField("requiredMaterial"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId RequiredMaterial = "Biomass"; /// - /// The current amount of time it takes to clone a body + /// The current amount of time it takes to clone a body /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float CloningTime = 30f; /// - /// The mob to spawn on emag + /// The mob to spawn on emag /// - [DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public EntProtoId MobSpawnId = "MobAbomination"; /// - /// Emag sound effects. + /// Emag sound effects. /// - [DataField("sparkSound")] + [DataField] public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks") { Params = AudioParams.Default.WithVolume(8), }; // TODO: Remove this from here when cloning and/or zombies are refactored - [DataField("screamSound")] + [DataField] public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams") { Params = AudioParams.Default.WithVolume(4), @@ -67,6 +68,50 @@ public sealed partial class CloningPodComponent : Component [ViewVariables] public EntityUid? ConnectedConsole; + + /// + /// Tracks whether a Cloner is actively cloning someone. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool ActivelyCloning; + + #region Metempsychosis + /// + /// Controls whether a cloning machine performs the Metempsychosis functions, EG: Is this a Cloner or a Metem Machine? + /// Metempsychosis refers to the metaphysical process of Reincarnation + /// + [DataField] + public bool DoMetempsychosis; + + /// + /// How much should each point of Karma decrease the odds of reincarnating as a humanoid. + /// + [DataField] + public float KarmaOffset = 0.5f; + + /// + /// The base chances for a Metem Machine to produce a Humanoid. + /// > 1 has a chance of acting like a true Cloner + /// On a successful roll, produces a random Humanoid. + /// A failed roll poduces a random NonHumanoid + /// + [DataField] + public float HumanoidBaseChance = 1; + + /// + /// The proto that the Metem Machine picks a random Humanoid from. + /// + [ValidatePrototypeId] + [DataField] + public string MetempsychoticHumanoidPool = "MetempsychoticHumanoidPool"; + + /// + /// The proto that the Metem Machine picks a random Non-Humanoid from + /// + [ValidatePrototypeId] + [DataField] + public string MetempsychoticNonHumanoidPool = "MetempsychoticNonhumanoidPool"; + #endregion } [Serializable, NetSerializable] @@ -84,8 +129,17 @@ public enum CloningPodStatus : byte NoMind } +[Serializable, NetSerializable] +public enum ForcedMetempsychosisType : byte +{ + None, + Clone, + RandomHumanoid, + RandomNonHumanoid +} + /// -/// Raised after a new mob got spawned when cloning a humanoid +/// Raised after a new mob got spawned when cloning a humanoid /// [ByRefEvent] public struct CloningEvent @@ -101,3 +155,22 @@ public CloningEvent(EntityUid source, EntityUid target) Target = target; } } + +/// +/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). Allowing for innate effects from the mob to influence the reincarnation. +/// +[ByRefEvent] +public struct ReincarnatingEvent +{ + public bool OverrideChance; + public bool NeverTrulyClone; + public ForcedMetempsychosisType ForcedType = ForcedMetempsychosisType.None; + public readonly EntityUid OldBody; + public float ReincarnationChanceModifier = 1; + public float ReincarnationChances; + public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) + { + OldBody = oldBody; + ReincarnationChances = reincarnationChances; + } +} diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml index d8e791af1ed..68deab02f26 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml @@ -4,8 +4,8 @@ name: metempsychotic machine description: Speeds along the transmigration of a soul to its next vessel. components: - - type: MetempsychoticMachine - type: CloningPod + doMetempsychosis: true - type: Machine board: MetempsychoticMachineCircuitboard - type: Sprite diff --git a/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml index dcbe23f6082..feabd9977bc 100644 --- a/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml +++ b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml @@ -3,7 +3,7 @@ weights: MobMonkey: 1 MobGorilla: 1 - # MobKangaroo: 0.5 # Mobs here need to be either VERY funny or up to standard. + MobKangaroo: 0.5 MobXenoQueen: 0.01 MobCrab: 0.01 - MobPenguin: 1 #ODJ's orders + MobPenguin: 1 From 206d51aec2533205aa92248f62c4296d67a0b510 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 20:45:02 -0400 Subject: [PATCH 02/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 8bfe4c1b69a..127a97a9139 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -347,6 +347,7 @@ private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) QueueDel(entity); } + else MakeAHugeMess(uid); clonePod.FailedClone = false; clonePod.CloningProgress = 0f; @@ -379,14 +380,14 @@ private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, Bloo Solution bloodSolution = new(); tileMix?.AdjustMoles(Gas.Ammonia, 0.5f - * ((physics is not null) - ? physics.Mass - : 71)); + * ((physics is not null) + ? physics.Mass + : 71)); bloodSolution.AddReagent("blood", 0.8f - * ((blood is not null) - ? blood.BloodMaxVolume - : 300)); + * ((blood is not null) + ? blood.BloodMaxVolume + : 300)); _puddleSystem.TrySpillAt(uid, bloodSolution, out _); } From 3057750e053a2a27a046d33e7dee5545f25d3eb6 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:00:26 -0400 Subject: [PATCH 03/33] Migrate Metem out of Nyano --- .../Circuitboards/Machine/production.yml | 21 ++++++++++++++++++ .../Machines/metempsychoticMachine.yml | 4 ++-- .../Devices/CircuitBoards/production.yml | 21 ------------------ .../Recipes/Lathes/electronics.yml | 9 -------- .../Nyanotrasen/Research/experimental.yml | 15 ------------- .../Prototypes/Recipes/Lathes/electronics.yml | 17 ++++++++++---- .../Prototypes/Research/experimental.yml | 15 +++++++++++++ .../Machines/metempsychotic.rsi/meta.json | 0 .../Machines/metempsychotic.rsi/pod_0.png | Bin .../Machines/metempsychotic.rsi/pod_1.png | Bin .../Machines/metempsychotic.rsi/pod_g.png | Bin 0 -> 1469 bytes 11 files changed, 51 insertions(+), 51 deletions(-) rename Resources/Prototypes/{Nyanotrasen => }/Entities/Structures/Machines/metempsychoticMachine.yml (86%) rename Resources/Textures/{Nyanotrasen => }/Structures/Machines/metempsychotic.rsi/meta.json (100%) rename Resources/Textures/{Nyanotrasen => }/Structures/Machines/metempsychotic.rsi/pod_0.png (100%) rename Resources/Textures/{Nyanotrasen => }/Structures/Machines/metempsychotic.rsi/pod_1.png (100%) create mode 100644 Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_g.png diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index f60ffad8478..b7a7e6477fc 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -544,6 +544,27 @@ recipes: - CloningPodMachineCircuitboard +- 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: MedicalScannerMachineCircuitboard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml similarity index 86% rename from Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml rename to Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml index 68deab02f26..95da480939e 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml @@ -9,7 +9,7 @@ - type: Machine board: MetempsychoticMachineCircuitboard - type: Sprite - sprite: Nyanotrasen/Structures/Machines/metempsychotic.rsi + sprite: Structures/Machines/metempsychotic.rsi snapCardinals: true layers: - state: pod_0 @@ -20,6 +20,6 @@ base: Cloning: { state: pod_1 } NoMind: { state: pod_1 } - Gore: { state: pod_1 } + Gore: { state: pod_g } Idle: { state: pod_0 } - type: Psionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml index fc40ea16397..6e80ec7c4e9 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml @@ -1,24 +1,3 @@ -- 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/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml index 418864cd408..1e53c715af5 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml @@ -1,12 +1,3 @@ -- type: latheRecipe - id: MetempsychoticMachineCircuitboard - result: MetempsychoticMachineCircuitboard - completetime: 4 - materials: - Steel: 100 - Glass: 900 - Gold: 100 - - type: latheRecipe id: ReverseEngineeringMachineCircuitboard result: ReverseEngineeringMachineCircuitboard diff --git a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml index 7c89c0f7d04..289efd317c5 100644 --- a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml +++ b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml @@ -14,21 +14,6 @@ - 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 diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 050dfa05cf8..0c0226e0b9d 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -117,6 +117,15 @@ Glass: 900 Gold: 100 +- type: latheRecipe + id: MetempsychoticMachineCircuitboard + result: MetempsychoticMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 + - type: latheRecipe id: ThermomachineFreezerMachineCircuitBoard result: ThermomachineFreezerMachineCircuitBoard @@ -921,7 +930,7 @@ materials: Steel: 100 Glass: 900 - + - type: latheRecipe id: ShuttleGunPerforatorCircuitboard result: ShuttleGunPerforatorCircuitboard @@ -930,7 +939,7 @@ Steel: 100 Glass: 900 Gold: 100 - + - type: latheRecipe id: ShuttleGunKineticCircuitboard result: ShuttleGunKineticCircuitboard @@ -938,7 +947,7 @@ materials: Steel: 100 Glass: 900 - + - type: latheRecipe id: ShuttleGunFriendshipCircuitboard result: ShuttleGunFriendshipCircuitboard @@ -947,7 +956,7 @@ Steel: 100 Glass: 900 Gold: 50 - + - type: latheRecipe id: ShuttleGunDusterCircuitboard result: ShuttleGunDusterCircuitboard diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 65186340d7f..08c1fc05496 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -137,6 +137,21 @@ - WeaponParticleDecelerator - HoloprojectorField +- type: technology + id: Metempsychosis + name: research-technology-metempsychosis + icon: + sprite: Structures/Machines/metempsychotic.rsi + state: pod_0 + discipline: Experimental + tier: 2 + cost: 15000 + recipeUnlocks: + - BiomassReclaimerMachineCircuitboard + - CloningConsoleComputerCircuitboard + - MedicalScannerMachineCircuitboard + - MetempsychoticMachineCircuitboard + # Tier 3 #- type: technology # DeltaV - LRP diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json similarity index 100% rename from Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_0.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_0.png diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_1.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_1.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_g.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_g.png new file mode 100644 index 0000000000000000000000000000000000000000..e85f4b2cba5881f708f0e0824e88feb528139d99 GIT binary patch literal 1469 zcmchW`7_%I7{0D=AHM4cjldWay@Y_a!@rW0044su1?-! zI{Yw1N}SbQ*`LLbNb+`Z1eg;VAH)ObGTH+T0BuFE?-#-19m*5y>l_#z6PA>cm|H}l z{~k+9Uju*&0GI%P*8tEoINk>UCCtYOxrHPESfAz1mV|d<&3e48o6$xcXOYz=stqXp zr+K6WX3bJ})np8=>^OpEAU|H1JzaZewtsvJKWDmlt*ZU z1NJw0mgI&@#k;JP2A`|KBXYcG*Xb-N4F?~E0g#7l zC`ZQCBStM8qE=V(s^BocvZ;DgBqD3U!zN?Wc-r6N|d(O4}l{1c^2l zi`CnUrA4qd^>u1t5M9b$9JQ}lH`^X!GV)R7B+0$Cxml3R+Ar4EQVPZWy|W)_qvQ`r zmUBVneVC=ZnbuRPaP6(B;<0eaEx%N0w?(KYAY^Chix^QkXDhJMh@14t(+7%0F#nnM zIDCkLld^KNyLy4nIBW1I#4?GP4BN!ih4%C#EbgEo$_UH~Eo5rB&THxj`hdGt_#BGi zH8wIBf#*+7pZ2iA4m}6Rubb{&wx*b3`8C4sEXEs5dNPGz{$k%yN9RrFo7Dhk`O{$8 zv9H;zAwAxF+^pt%X==}>bn~#DJvn3a+K?t>5Z7Z5>E#VrubfN;i zq(H-Sh1ceJbGR982t%;+$C}X1eB8jtUGWx~NGQ)|Q^BXSl;~3jmCAGaRzCw{e{}iZ=g)oaK{;fWxoC0iIhSc z&vkyNscyug+Re2kd>_fOHtKQw6~pQVtuo+O}L7(bBHq;6SFm(T}l`-XSNh OE#T&ib7DG%lK%s28`sbP literal 0 HcmV?d00001 From f01d9501c0f9afb56bf8c0a6520b7eb306385e47 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:17:53 -0400 Subject: [PATCH 04/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 127a97a9139..b5e8a26e901 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -58,7 +58,7 @@ using Robust.Shared.Utility; using Timer = Robust.Shared.Timing.Timer; using Content.Server.Power.Components; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Content.Shared.Damage.Prototypes; namespace Content.Server.Cloning { @@ -89,6 +89,7 @@ public sealed class CloningSystem : EntitySystem [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly ContestsSystem _contests = default!; [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; public readonly Dictionary ClonesWaitingForMind = new(); @@ -422,6 +423,13 @@ private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clone EnsureComp(mob, out var newKarma); newKarma.Score += oldKarma; + // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Clonexadone is perfect for this. + if (HasComp(mob)) + { + DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), _random.NextFloat(105, 120)); + _damageable.TryChangeDamage(mob, damage, true); + } + if (TryComp(mob, out var newHumanoid)) { if (switchingSpecies && !forceOldProfile) From b22b1b8dcf385b04102939f3939cc12868662429 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:28:39 -0400 Subject: [PATCH 05/33] GO --- Content.Server/Cloning/CloningSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index b5e8a26e901..b42a35e0eae 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -423,7 +423,7 @@ private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clone EnsureComp(mob, out var newKarma); newKarma.Score += oldKarma; - // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Clonexadone is perfect for this. + // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. if (HasComp(mob)) { DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), _random.NextFloat(105, 120)); From ece2b0a47f2b1493019a78d732e719a23c9f1209 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:29:17 -0400 Subject: [PATCH 06/33] Update meta.json --- .../Structures/Machines/metempsychotic.rsi/meta.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json index 7276fde67e5..9b2d3017272 100644 --- a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-4.0", - "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512)", + "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512), gore state edited by VMSolidus", "size": { "x": 32, "y": 32 @@ -13,6 +13,10 @@ { "name": "pod_1", "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] + }, + { + "name": "pod_g", + "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] } ] } From 2e80c823c1196b746ad477826f119a79da424118 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:30:20 -0400 Subject: [PATCH 07/33] Update meta.json --- .../Textures/Structures/Machines/metempsychotic.rsi/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json index 9b2d3017272..4f4260813d1 100644 --- a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json @@ -16,7 +16,7 @@ }, { "name": "pod_g", - "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] + "delays": [ [ 0.1, 0.1, 0.1, 0.1 ] ] } ] } From 728107dd67af3c1fd03c632c87f28632e94b6655 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:38:09 -0400 Subject: [PATCH 08/33] Last update I swear --- .../Metempsychosis/MetempsychosisTest.cs | 53 ------------------- Content.Server/Cloning/CloningSystem.cs | 5 ++ 2 files changed, 5 insertions(+), 53 deletions(-) delete mode 100644 Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs diff --git a/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs b/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs deleted file mode 100644 index cd6a4b4c2b9..00000000000 --- a/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -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/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index b42a35e0eae..31e592f2607 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -286,6 +286,11 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(entity, out var physics) + && physics.Mass > 71) + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => UpdateCloning(cloningPod, cloningPodComponent)); + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => UpdateCloning(cloningPod, cloningPodComponent)); } From 218c59319172d54d957871f40b37e2f7aeaa9b6a Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:41:05 -0400 Subject: [PATCH 09/33] Update CCVars.cs --- Content.Shared/CCVar/CCVars.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 0c680448a02..7cdc0333e48 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1681,12 +1681,14 @@ public static readonly CVarDef #region Cloning /// - /// Enabled: Cloning has 70% cost and reclaimer will refuse to reclaim corpses with souls. (For LRP). - /// Disabled: Cloning has full biomass cost and reclaimer can reclaim corpses with souls. (Playtested and balanced for MRP+). + /// How much should the cost to clone an entity be multiplied by. /// public static readonly CVarDef CloningBiomassCostMultiplier = CVarDef.Create("cloning.biomass_cost_multiplier", 1f, CVar.SERVERONLY); + /// + /// Whether or not the Biomass Reclaimer is allowed to roundremove bodies with a soul. + /// public static readonly CVarDef CloningReclaimSouledBodies = CVarDef.Create("cloning.reclaim_souled_bodies", false, CVar.SERVERONLY); From fbc8c539e9a14bfd914fd55b5345ee3f04a3e22f Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 21:50:24 -0400 Subject: [PATCH 10/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 31e592f2607..c69dc9735c3 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -204,6 +204,9 @@ private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPo private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, float failChanceModifier = 1) { + if (clonePod.DoMetempsychosis) + return false; + if (TryComp(bodyToClone, out var damageable) && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) && clonePod.ConnectedConsole is not null) From 89080c172bc04021f091d2b072570364aafa95ca Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 22:00:36 -0400 Subject: [PATCH 11/33] Metem Machine Biomass Discount --- Content.Server/Cloning/CloningSystem.cs | 4 +++- Content.Shared/Cloning/CloningPodComponent.cs | 3 +++ .../Entities/Structures/Machines/metempsychoticMachine.yml | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index c69dc9735c3..ba66a0098c1 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -188,7 +188,9 @@ private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone) private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod) { - var cloningCost = (int) Math.Round(physics.FixturesMass * _config.GetCVar(CCVars.CloningBiomassCostMultiplier)); + var cloningCost = (int) Math.Round(physics.FixturesMass + * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) + * clonePod.BiomassCostMultiplier); if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) { diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 92b4dff9cd8..1b5218d5003 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -23,6 +23,9 @@ public sealed partial class CloningPodComponent : Component [ViewVariables] public float CloningProgress = 0; + [DataField] + public float BiomassCostMultiplier = 1; + [ViewVariables] public int UsedBiomass = 70; diff --git a/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml index 95da480939e..6bfbbbc1fcf 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml @@ -6,6 +6,7 @@ components: - type: CloningPod doMetempsychosis: true + biomassCostMultiplier: 0.5 - type: Machine board: MetempsychoticMachineCircuitboard - type: Sprite From 37243b42cdd62cab189a01bb1d52773b69edf1c3 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 22:10:40 -0400 Subject: [PATCH 12/33] Genetic damage to clone scales with genetic damage to original. Metem machines ignore the bonus genetic damage. --- Content.Server/Cloning/CloningSystem.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index ba66a0098c1..0c8dd8c236a 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -204,8 +204,9 @@ private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPo return true; } - private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, float failChanceModifier = 1) + private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) { + geneticDamage = 0; if (clonePod.DoMetempsychosis) return false; @@ -213,6 +214,7 @@ private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPod && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) && clonePod.ConnectedConsole is not null) { + geneticDamage += (float) cellularDmg; var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); chance *= failChanceModifier; @@ -260,10 +262,10 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity /// Start Nyano Code: Handles fetching the mob and any appearance stuff... /// - private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clonePodComp, HumanoidCharacterProfile pref, SpeciesPrototype speciesPrototype, HumanoidAppearanceComponent humanoid, EntityUid bodyToClone) + private EntityUid FetchAndSpawnMob + (EntityUid clonePod, + CloningPodComponent clonePodComp, + HumanoidCharacterProfile pref, + SpeciesPrototype speciesPrototype, + HumanoidAppearanceComponent humanoid, + EntityUid bodyToClone, + float geneticDamage) { List sexes = new(); bool switchingSpecies = false; @@ -421,6 +430,9 @@ private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clone toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); forceOldProfile = !changeProfile; + if (!changeProfile) + geneticDamage = 0; + if (newSpecies != null) { sexes = newSpecies.Sexes; @@ -436,7 +448,7 @@ private EntityUid FetchAndSpawnMob(EntityUid clonePod, CloningPodComponent clone // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. if (HasComp(mob)) { - DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), _random.NextFloat(105, 120)); + DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), 101f + geneticDamage); _damageable.TryChangeDamage(mob, damage, true); } From 463edb76d1265a70c2fda2e1802bb9f6c1396ffe Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 22:11:05 -0400 Subject: [PATCH 13/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 0c8dd8c236a..ff2a422f791 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -430,7 +430,7 @@ private EntityUid FetchAndSpawnMob toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); forceOldProfile = !changeProfile; - if (!changeProfile) + if (changeProfile) geneticDamage = 0; if (newSpecies != null) From e669b0f679ed82113b98f0bbe247d02150300f85 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 22:30:56 -0400 Subject: [PATCH 14/33] Wall of CCVars. --- Content.Server/Cloning/CloningSystem.cs | 28 ++++++++++++++--- Content.Shared/CCVar/CCVars.cs | 42 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index ff2a422f791..6d80b217fcb 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -59,6 +59,7 @@ using Timer = Robust.Shared.Timing.Timer; using Content.Server.Power.Components; using Content.Shared.Damage.Prototypes; +using Content.Server.Database.Migrations.Postgres; namespace Content.Server.Cloning { @@ -406,7 +407,7 @@ private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, Bloo } /// - /// Start Nyano Code: Handles fetching the mob and any appearance stuff... + /// This function handles the Clone vs. Metem logic, as well as creation of the new body. /// private EntityUid FetchAndSpawnMob (EntityUid clonePod, @@ -456,13 +457,32 @@ private EntityUid FetchAndSpawnMob { if (switchingSpecies && !forceOldProfile) { + var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); + var oldName = _serialization.CreateCopy(pref.Name, null, false, true); + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); - if (sexes.Contains(humanoid.Sex)) + + if (sexes.Contains(humanoid.Sex) + && _config.GetCVar(CCVars.CloningPreserveSex)) pref = pref.WithSex(humanoid.Sex); - pref = pref.WithGender(humanoid.Gender); - pref = pref.WithAge(humanoid.Age); + if (_config.GetCVar(CCVars.CloningPreserveGender)) + pref = pref.WithGender(humanoid.Gender); + + if (_config.GetCVar(CCVars.CloningPreserveAge)) + pref = pref.WithAge(humanoid.Age); + + if (_config.GetCVar(CCVars.CloningPreserveHeight)) + pref = pref.WithHeight(humanoid.Height); + + if (_config.GetCVar(CCVars.CloningPreserveWidth)) + pref = pref.WithWidth(humanoid.Width); + + if (_config.GetCVar(CCVars.CloningPreserveName)) + pref = pref.WithName(oldName); + if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) + pref = pref.WithFlavorText(flavorText); } _humanoidSystem.LoadProfile(mob, pref); } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 7cdc0333e48..fe634cdbe84 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1692,6 +1692,48 @@ public static readonly CVarDef public static readonly CVarDef CloningReclaimSouledBodies = CVarDef.Create("cloning.reclaim_souled_bodies", false, CVar.SERVERONLY); + /// + /// Controls whether or not Metempsychosis will potentially give people a sex change. + /// + public static readonly CVarDef CloningPreserveSex = + CVarDef.Create("cloning.preserve_sex", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Pronouns when reincarnating people. + /// + public static readonly CVarDef CloningPreserveGender = + CVarDef.Create("cloning.preserve_gender", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Age. + /// + public static readonly CVarDef CloningPreserveAge = + CVarDef.Create("cloning.preserve_age", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves height. + /// + public static readonly CVarDef CloningPreserveHeight = + CVarDef.Create("cloning.preserve_height", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves width. + /// + public static readonly CVarDef CloningPreserveWidth = + CVarDef.Create("cloning.preserve_width", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Names. EG: Are you actually a new person? + /// + public static readonly CVarDef CloningPreserveName = + CVarDef.Create("cloning.preserve_name", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Flavor Text. + /// + public static readonly CVarDef CloningPreserveFlavorText = + CVarDef.Create("cloning.preserve_flavor_text", true, CVar.SERVERONLY); + #endregion /* * Anomaly From ab6b49ceab90b5338bae1df508bd5d11e9c1a72d Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 14 Aug 2024 22:38:24 -0400 Subject: [PATCH 15/33] now correctly modifies gender if configured to do so. --- Content.Server/Cloning/CloningSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 6d80b217fcb..22c0706fac3 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -423,6 +423,7 @@ private EntityUid FetchAndSpawnMob var toSpawn = speciesPrototype.Prototype; var forceOldProfile = true; var oldKarma = 0; + var oldGender = humanoid.Gender; if (TryComp(bodyToClone, out var oldKarmaComp)) oldKarma += oldKarmaComp.Score; @@ -468,6 +469,7 @@ private EntityUid FetchAndSpawnMob if (_config.GetCVar(CCVars.CloningPreserveGender)) pref = pref.WithGender(humanoid.Gender); + else oldGender = humanoid.Gender; if (_config.GetCVar(CCVars.CloningPreserveAge)) pref = pref.WithAge(humanoid.Age); @@ -495,7 +497,7 @@ private EntityUid FetchAndSpawnMob var grammar = EnsureComp(mob); grammar.ProperNoun = true; - grammar.Gender = humanoid.Gender; + grammar.Gender = oldGender; Dirty(mob, grammar); if (forceOldProfile From 0e76d9aff74d6f4fc4932a7f7a7331f74634a904 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 13:42:44 -0400 Subject: [PATCH 16/33] Update Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com> Signed-off-by: VMSolidus --- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index e9c71aaff59..3a15c47ac8a 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -219,11 +219,7 @@ private bool CanGib(Entity reclaimer, EntityUid dragg return false; bool isPlant = HasComp(dragged); - if (!isPlant - && !HasComp(dragged) - || !isPlant - && reclaimer.Comp.SafetyEnabled - && !_mobState.IsDead(dragged)) + if (!HasComp(dragged) && (!HasComp(dragged) || reclaimerComp.SafetyEnabled && !_mobState.IsDead(dragged))) return false; if (_configManager.GetCVar(CCVars.CloningReclaimSouledBodies) From 5f40c861d6f9cf08089a0eefac99af1619a79aaa Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 14:04:39 -0400 Subject: [PATCH 17/33] Update BiomassReclaimerSystem.cs --- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index 3a15c47ac8a..815c7d2eda4 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -219,7 +219,7 @@ private bool CanGib(Entity reclaimer, EntityUid dragg return false; bool isPlant = HasComp(dragged); - if (!HasComp(dragged) && (!HasComp(dragged) || reclaimerComp.SafetyEnabled && !_mobState.IsDead(dragged))) + if (!HasComp(dragged) && (!HasComp(dragged) || reclaimer.Comp.SafetyEnabled && !_mobState.IsDead(dragged))) return false; if (_configManager.GetCVar(CCVars.CloningReclaimSouledBodies) From 9ed6f79f7cacfc53db37172073d31e83c3260e5d Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 14:18:11 -0400 Subject: [PATCH 18/33] Apply suggestions from code review Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com> Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Signed-off-by: VMSolidus --- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 3 +-- Content.Server/Medical/MedicalScannerSystem.cs | 6 +++--- Content.Shared/CCVar/CCVars.cs | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index 815c7d2eda4..adec43e76e2 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -109,8 +109,7 @@ private void OnSuicide(Entity ent, ref SuicideEvent a { if (args.Handled || HasComp(ent) - || TryComp(ent, out var power) - && !power.Powered) + || TryComp(ent, out var power) && !power.Powered) return; _popup.PopupEntity(Loc.GetString("biomass-reclaimer-suicide-others", ("victim", args.Victim)), ent, PopupType.LargeCaution); diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 2548258102b..9b3a9ee1885 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -97,8 +97,7 @@ private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component private void AddAlternativeVerbs(EntityUid uid, MedicalScannerComponent component, GetVerbsEvent args) { - if (!args.CanAccess - || !args.CanInteract) + if (!args.CanAccess || !args.CanInteract) return; // Eject verb @@ -143,13 +142,14 @@ private void OnPortDisconnected(EntityUid uid, MedicalScannerComponent component private void OnAnchorChanged(EntityUid uid, MedicalScannerComponent component, ref AnchorStateChangedEvent args) { if (component.ConnectedConsole == null - || !TryComp(component.ConnectedConsole, out var console) || !args.Anchored + || !TryComp(component.ConnectedConsole, out var console) || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, console.CloningPod, uid, console)) return; _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); } + private MedicalScannerStatus GetStatus(EntityUid uid, MedicalScannerComponent scannerComponent) { if (!this.IsPowered(uid, EntityManager)) diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index fe634cdbe84..84b3b371823 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1690,7 +1690,7 @@ public static readonly CVarDef /// Whether or not the Biomass Reclaimer is allowed to roundremove bodies with a soul. /// public static readonly CVarDef CloningReclaimSouledBodies = - CVarDef.Create("cloning.reclaim_souled_bodies", false, CVar.SERVERONLY); + CVarDef.Create("cloning.reclaim_souled_bodies", true, CVar.SERVERONLY); /// /// Controls whether or not Metempsychosis will potentially give people a sex change. @@ -1708,7 +1708,7 @@ public static readonly CVarDef /// Controls whether or not Metempsychosis preserves Age. /// public static readonly CVarDef CloningPreserveAge = - CVarDef.Create("cloning.preserve_age", true, CVar.SERVERONLY); + CVarDef.Create("cloning.preserve_age", false, CVar.SERVERONLY); /// /// Controls whether or not Metempsychosis preserves height. From 0a688d80621a96af94a813a3be40cce524cecc58 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 01:53:59 -0400 Subject: [PATCH 19/33] Apply suggestions from code review Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com> Signed-off-by: VMSolidus --- Content.Server/Cloning/CloningConsoleSystem.cs | 4 ++-- Content.Server/Cloning/CloningSystem.cs | 8 ++++---- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 6 +++--- Content.Server/Medical/MedicalScannerSystem.cs | 2 +- Content.Shared/CCVar/CCVars.cs | 1 + Content.Shared/Cloning/CloningPodComponent.cs | 3 +++ 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index 32d7d18c953..4475678d15c 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -222,8 +222,8 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso if (!_mobStateSystem.IsDead(scanBody.Value)) clonerStatus = ClonerStatus.ScannerOccupantAlive; else if (!_mindSystem.TryGetMind(scanBody.Value, out _, out var mind) - || mind.UserId == null - || !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) + || mind.UserId == null + || !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) clonerStatus = ClonerStatus.NoMindDetected; } } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 22c0706fac3..519184e848b 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -395,13 +395,13 @@ private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, Bloo tileMix?.AdjustMoles(Gas.Ammonia, 0.5f * ((physics is not null) - ? physics.Mass - : 71)); + ? physics.Mass + : 71)); bloodSolution.AddReagent("blood", 0.8f * ((blood is not null) - ? blood.BloodMaxVolume - : 300)); + ? blood.BloodMaxVolume + : 300)); _puddleSystem.TrySpillAt(uid, bloodSolution, out _); } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index adec43e76e2..2c9ae363873 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -83,7 +83,7 @@ public override void Update(float frameTime) if (reclaimer.ProcessingTimer > 0) continue; - var actualYield = (int) reclaimer.CurrentExpectedYield; // can only have integer biomass + var actualYield = (int) reclaimer.CurrentExpectedYield; // Can only have integer biomass physically reclaimer.CurrentExpectedYield = reclaimer.CurrentExpectedYield - actualYield; // store non-integer leftovers _material.SpawnMultipleFromMaterial(actualYield, BiomassPrototype, Transform(uid).Coordinates); @@ -202,8 +202,8 @@ private void StartProcessing(EntityUid toProcess, Entity(toProcess) - ? physics.FixturesMass * component.YieldPerUnitMass * component.ProduceYieldMultiplier - : physics.FixturesMass * component.YieldPerUnitMass; + ? physics.FixturesMass * component.YieldPerUnitMass * component.ProduceYieldMultiplier + : physics.FixturesMass * component.YieldPerUnitMass; component.ProcessingTimer = physics.FixturesMass * component.ProcessingTimePerUnitMass; diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 9b3a9ee1885..d3c32bba500 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -90,7 +90,7 @@ private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component { Act = () => InsertBody(uid, args.Target, component), Category = VerbCategory.Insert, - Text = metadata.EntityName + Text = MetaData(args.Using.Value).EntityName }; args.Verbs.Add(verb); } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 84b3b371823..147f8c38eb5 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1735,6 +1735,7 @@ public static readonly CVarDef CVarDef.Create("cloning.preserve_flavor_text", true, CVar.SERVERONLY); #endregion + /* * Anomaly */ diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 1b5218d5003..003a7a5696a 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -79,6 +79,7 @@ public sealed partial class CloningPodComponent : Component public bool ActivelyCloning; #region Metempsychosis + /// /// Controls whether a cloning machine performs the Metempsychosis functions, EG: Is this a Cloner or a Metem Machine? /// Metempsychosis refers to the metaphysical process of Reincarnation @@ -114,6 +115,7 @@ public sealed partial class CloningPodComponent : Component [ValidatePrototypeId] [DataField] public string MetempsychoticNonHumanoidPool = "MetempsychoticNonhumanoidPool"; + #endregion } @@ -171,6 +173,7 @@ public struct ReincarnatingEvent public readonly EntityUid OldBody; public float ReincarnationChanceModifier = 1; public float ReincarnationChances; + public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) { OldBody = oldBody; From 8127adfa98833141223bb600b676bbd049566f7d Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 14:41:22 -0400 Subject: [PATCH 20/33] Last updates per Feedback --- .../Cloning/CloningConsoleSystem.cs | 13 +++--- .../BiomassReclaimerSystem.cs | 8 ++-- .../Medical/MedicalScannerSystem.cs | 3 +- Content.Shared/Cloning/CloningPodComponent.cs | 44 +++---------------- .../Cloning/CloningSystem.Events.cs | 37 ++++++++++++++++ 5 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 Content.Shared/Cloning/CloningSystem.Events.cs diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index 4475678d15c..d7893004793 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -52,16 +52,16 @@ private void OnInit(EntityUid uid, CloningConsoleComponent component, ComponentI } private void OnButtonPressed(EntityUid uid, CloningConsoleComponent consoleComponent, UiButtonPressedMessage args) { - if (!_powerReceiverSystem.IsPowered(uid)) + if (!_powerReceiverSystem.IsPowered(uid) + || consoleComponent.GeneticScanner is null + || consoleComponent.CloningPod is null + || !TryComp(consoleComponent.CloningPod.Value, out var cloningPod)) return; switch (args.Button) { case UiButton.Clone: - if (consoleComponent.GeneticScanner != null - && consoleComponent.CloningPod != null - && TryComp(consoleComponent.CloningPod.Value, out var cloningPod)) - TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, cloningPod, consoleComponent: consoleComponent); + TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, cloningPod, consoleComponent: consoleComponent); break; } UpdateUserInterface(uid, consoleComponent); @@ -153,8 +153,7 @@ public void UpdateUserInterface(EntityUid consoleUid, CloningConsoleComponent co public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent cloningPod, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) { - if (!Resolve(uid, ref consoleComponent) - || !Resolve(scannerUid, ref scannerComp) + if (!Resolve(uid, ref consoleComponent, ref scannerComp) || !Transform(cloningPodUid).Anchored || !Transform(scannerUid).Anchored || !consoleComponent.CloningPodInRange diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index 2c9ae363873..eaf04d64b2b 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -28,7 +28,6 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; -using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Medical.BiomassReclaimer @@ -147,11 +146,12 @@ private void OnAfterInteractUsing(Entity reclaimer, r { if (!args.CanReach || args.Target == null - || !CanGib(reclaimer, args.Used) - || !TryComp(args.Used, out var physics)) + || !CanGib(reclaimer, args.Used)) return; - var delay = reclaimer.Comp.BaseInsertionDelay * physics.FixturesMass; + var delay = reclaimer.Comp.BaseInsertionDelay * (TryComp(args.Used, out var physics) + ? physics.FixturesMass + : 1); _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, delay, new ReclaimerDoAfterEvent(), reclaimer, target: args.Target, used: args.Used) { BreakOnTargetMove = true, diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index d3c32bba500..5a7afa770f5 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -82,8 +82,7 @@ private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component || !args.CanAccess || !args.CanInteract || IsOccupied(component) - || !CanScannerInsert(uid, args.Using.Value, component) - || !TryComp(args.Using.Value, out var metadata)) + || !CanScannerInsert(uid, args.Using.Value, component)) return; InteractionVerb verb = new() diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 003a7a5696a..7b988f789c3 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -82,8 +82,12 @@ public sealed partial class CloningPodComponent : Component /// /// Controls whether a cloning machine performs the Metempsychosis functions, EG: Is this a Cloner or a Metem Machine? - /// Metempsychosis refers to the metaphysical process of Reincarnation + /// Metempsychosis refers to the metaphysical process of Reincarnation. /// + /// + /// A Machine with this enabled will essentially create a random new character instead of creating a living version of the old character. + /// Although, the specifics of how much of the new body is a "new character" is highly adjustable in server configuration. + /// [DataField] public bool DoMetempsychosis; @@ -142,41 +146,3 @@ public enum ForcedMetempsychosisType : byte RandomHumanoid, RandomNonHumanoid } - -/// -/// Raised after a new mob got spawned when cloning a humanoid -/// -[ByRefEvent] -public struct CloningEvent -{ - public bool NameHandled = false; - - public readonly EntityUid Source; - public readonly EntityUid Target; - - public CloningEvent(EntityUid source, EntityUid target) - { - Source = source; - Target = target; - } -} - -/// -/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). Allowing for innate effects from the mob to influence the reincarnation. -/// -[ByRefEvent] -public struct ReincarnatingEvent -{ - public bool OverrideChance; - public bool NeverTrulyClone; - public ForcedMetempsychosisType ForcedType = ForcedMetempsychosisType.None; - public readonly EntityUid OldBody; - public float ReincarnationChanceModifier = 1; - public float ReincarnationChances; - - public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) - { - OldBody = oldBody; - ReincarnationChances = reincarnationChances; - } -} diff --git a/Content.Shared/Cloning/CloningSystem.Events.cs b/Content.Shared/Cloning/CloningSystem.Events.cs new file mode 100644 index 00000000000..60fea1ab111 --- /dev/null +++ b/Content.Shared/Cloning/CloningSystem.Events.cs @@ -0,0 +1,37 @@ +namespace Content.Shared.Cloning; +/// +/// Raised after a new mob got spawned when cloning a humanoid +/// +[ByRefEvent] +public struct CloningEvent +{ + public bool NameHandled = false; + + public readonly EntityUid Source; + public readonly EntityUid Target; + + public CloningEvent(EntityUid source, EntityUid target) + { + Source = source; + Target = target; + } +} + +/// +/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). Allowing for innate effects from the mob to influence the reincarnation. +/// +[ByRefEvent] +public struct ReincarnatingEvent +{ + public bool OverrideChance; + public bool NeverTrulyClone; + public ForcedMetempsychosisType ForcedType = ForcedMetempsychosisType.None; + public readonly EntityUid OldBody; + public float ReincarnationChanceModifier = 1; + public float ReincarnationChances; + public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) + { + OldBody = oldBody; + ReincarnationChances = reincarnationChances; + } +} From 675da4cbb206de876b6ab7f4391af6965df61783 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 15:07:35 -0400 Subject: [PATCH 21/33] Refactor Uncloneable --- Content.Server/Cloning/CloningSystem.cs | 31 ++++++++++--------- .../MetempsychosisKarmaComponent.cs | 2 +- .../Traits/Assorted/UncloneableSystem.cs | 24 ++++++++++++++ .../Cloning/CloningSystem.Events.cs | 19 ++++++++++++ 4 files changed, 61 insertions(+), 15 deletions(-) rename Content.Server/{Nyanotrasen/Cloning => Cloning/Components}/MetempsychosisKarmaComponent.cs (87%) create mode 100644 Content.Server/Traits/Assorted/UncloneableSystem.cs diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 519184e848b..4ef2cb11c7a 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -34,7 +34,6 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Server.Traits.Assorted; using Content.Shared.Speech; using Content.Shared.Tag; using Content.Shared.Preferences; @@ -42,7 +41,6 @@ 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; using Content.Shared.SSDIndicator; @@ -59,7 +57,6 @@ using Timer = Robust.Shared.Timing.Timer; using Content.Server.Power.Components; using Content.Shared.Damage.Prototypes; -using Content.Server.Database.Migrations.Postgres; namespace Content.Server.Cloning { @@ -175,23 +172,29 @@ private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEv args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); } - private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone) + private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) { - if (!TryComp(bodyToClone, out _)) - return true; + var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); + RaiseLocalEvent(bodyToClone, ref ev); + cloningCostMultiplier = ev.CloningCostMultiplier; - _chatSystem.TrySendInGameICMessage(uid, - Loc.GetString("cloning-console-uncloneable-trait-error"), - InGameICChatType.Speak, false); + if (ev.Cancelled && ev.CloningFailMessage is not null) + { + _chatSystem.TrySendInGameICMessage(uid, + Loc.GetString(ev.CloningFailMessage), + InGameICChatType.Speak, false); + return false; + } - return false; + return true; } - private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod) + private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) { var cloningCost = (int) Math.Round(physics.FixturesMass * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) - * clonePod.BiomassCostMultiplier); + * clonePod.BiomassCostMultiplier + * cloningCostMultiplier); if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) { @@ -243,10 +246,10 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(bodyToClone, out var humanoid) || !TryComp(bodyToClone, out var physics) - || CheckBiomassCost(uid, physics, clonePod) + || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier) || !ClonesWaitingForMind.TryGetValue(mindEnt.Comp, out var clone) || !TryComp(clone, out var cloneMindComp) || cloneMindComp.Mind == null diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs b/Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs similarity index 87% rename from Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs rename to Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs index 3d2bde7b147..5f7b7af1cda 100644 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs +++ b/Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Nyanotrasen.Cloning +namespace Content.Server.Cloning.Components { /// /// This tracks how many times you have already been cloned and lowers your chance of getting a humanoid each time. diff --git a/Content.Server/Traits/Assorted/UncloneableSystem.cs b/Content.Server/Traits/Assorted/UncloneableSystem.cs new file mode 100644 index 00000000000..f0edeaa14f4 --- /dev/null +++ b/Content.Server/Traits/Assorted/UncloneableSystem.cs @@ -0,0 +1,24 @@ +using Content.Shared.Cloning; + +namespace Content.Server.Traits.Assorted +{ + public sealed class UncloneableSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAttemptCloning); + } + + private void OnAttemptCloning(EntityUid uid, UncloneableComponent component, ref AttemptCloningEvent args) + { + if (args.DoMetempsychosis + || args.CloningFailMessage is not null + || args.Cancelled) + return; + + args.CloningFailMessage = "cloning-console-uncloneable-trait-error"; + args.Cancelled = true; + } + } +} \ No newline at end of file diff --git a/Content.Shared/Cloning/CloningSystem.Events.cs b/Content.Shared/Cloning/CloningSystem.Events.cs index 60fea1ab111..1784fb884d2 100644 --- a/Content.Shared/Cloning/CloningSystem.Events.cs +++ b/Content.Shared/Cloning/CloningSystem.Events.cs @@ -35,3 +35,22 @@ public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) ReincarnationChances = reincarnationChances; } } + +/// +/// Raised on a corpse that someone is attempting to clone, but before the process actually begins. +/// ALlows for Entities to influence whether the cloning can begin in the first place, either by canceling it, or modifying the cost. +/// +[ByRefEvent] +public struct AttemptCloningEvent +{ + public bool Cancelled; + public bool DoMetempsychosis; + public EntityUid CloningPod; + public string? CloningFailMessage; + public float CloningCostMultiplier = 1; + public AttemptCloningEvent(EntityUid cloningPod, bool doMetempsychosis) + { + DoMetempsychosis = doMetempsychosis; + CloningPod = cloningPod; + } +} From fdbcf14d6ec3611fecefe41c95a25683d7a43d5e Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 15:12:01 -0400 Subject: [PATCH 22/33] Rename the dumb states to things more sensible --- .../Structures/Machines/metempsychoticMachine.yml | 10 +++++----- .../{pod_1.png => cloning_active.png} | Bin .../{pod_g.png => cloning_failed.png} | Bin .../{pod_0.png => cloning_idle.png} | Bin .../Machines/metempsychotic.rsi/meta.json | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) rename Resources/Textures/Structures/Machines/metempsychotic.rsi/{pod_1.png => cloning_active.png} (100%) rename Resources/Textures/Structures/Machines/metempsychotic.rsi/{pod_g.png => cloning_failed.png} (100%) rename Resources/Textures/Structures/Machines/metempsychotic.rsi/{pod_0.png => cloning_idle.png} (100%) diff --git a/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml index 6bfbbbc1fcf..9667ed12d46 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml @@ -13,14 +13,14 @@ sprite: Structures/Machines/metempsychotic.rsi snapCardinals: true layers: - - state: pod_0 + - state: cloning_idle - type: Appearance - type: GenericVisualizer visuals: enum.CloningPodVisuals.Status: base: - Cloning: { state: pod_1 } - NoMind: { state: pod_1 } - Gore: { state: pod_g } - Idle: { state: pod_0 } + Cloning: { state: cloning_active } + NoMind: { state: cloning_active } + Gore: { state: cloning_failed } + Idle: { state: cloning_idle } - type: Psionic diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_1.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_active.png similarity index 100% rename from Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_1.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_active.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_g.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_failed.png similarity index 100% rename from Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_g.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_failed.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_0.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_idle.png similarity index 100% rename from Resources/Textures/Structures/Machines/metempsychotic.rsi/pod_0.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_idle.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json index 4f4260813d1..da5034f7139 100644 --- a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json @@ -1,21 +1,21 @@ { "version": 1, "license": "CC-BY-4.0", - "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512), gore state edited by VMSolidus", + "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512), failed state edited by VMSolidus", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "pod_0" + "name": "cloning_idle" }, { - "name": "pod_1", + "name": "cloning_active", "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] }, { - "name": "pod_g", + "name": "cloning_failed", "delays": [ [ 0.1, 0.1, 0.1, 0.1 ] ] } ] From 05b18c61e005996e3b66f1e2707cbbb0672ce63c Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 15:16:39 -0400 Subject: [PATCH 23/33] Update experimental.yml --- Resources/Prototypes/Research/experimental.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 08c1fc05496..0dbcded5460 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -142,7 +142,7 @@ name: research-technology-metempsychosis icon: sprite: Structures/Machines/metempsychotic.rsi - state: pod_0 + state: cloning_idle discipline: Experimental tier: 2 cost: 15000 From b6c06ef2a5d0bc4558005840bee63f63994ba465 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 18 Aug 2024 15:32:34 -0400 Subject: [PATCH 24/33] Update UncloneableSystem.cs --- Content.Server/Traits/Assorted/UncloneableSystem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Content.Server/Traits/Assorted/UncloneableSystem.cs b/Content.Server/Traits/Assorted/UncloneableSystem.cs index f0edeaa14f4..3c75d640c47 100644 --- a/Content.Server/Traits/Assorted/UncloneableSystem.cs +++ b/Content.Server/Traits/Assorted/UncloneableSystem.cs @@ -12,8 +12,7 @@ public override void Initialize() private void OnAttemptCloning(EntityUid uid, UncloneableComponent component, ref AttemptCloningEvent args) { - if (args.DoMetempsychosis - || args.CloningFailMessage is not null + if (args.CloningFailMessage is not null || args.Cancelled) return; From 9c60e0151351469938ca14b8399c460aa37440b4 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 29 Aug 2024 21:08:20 -0400 Subject: [PATCH 25/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 889 ++++++++++++------------ 1 file changed, 444 insertions(+), 445 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index f09e5f24790..7c59cb1576f 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -58,550 +58,549 @@ using Content.Server.Power.Components; using Content.Shared.Damage.Prototypes; -namespace Content.Server.Cloning +namespace Content.Server.Cloning; + +public sealed class CloningSystem : EntitySystem { - public sealed class CloningSystem : EntitySystem + [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EuiManager _euiManager = null!; + [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly ContainerSystem _containerSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly PuddleSystem _puddleSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly MaterialStorageSystem _material = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly MetaDataSystem _metaSystem = default!; + [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + + public readonly Dictionary ClonesWaitingForMind = new(); + + public override void Initialize() { - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - [Dependency] private readonly IPlayerManager _playerManager = null!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly EuiManager _euiManager = null!; - [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; - [Dependency] private readonly ContainerSystem _containerSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly PuddleSystem _puddleSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly IConfigurationManager _config = default!; - [Dependency] private readonly MaterialStorageSystem _material = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly MetaDataSystem _metaSystem = default!; - [Dependency] private readonly SharedJobSystem _jobs = default!; - [Dependency] private readonly TagSystem _tag = default!; - [Dependency] private readonly ContestsSystem _contests = default!; - [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly DamageableSystem _damageable = default!; - - public readonly Dictionary ClonesWaitingForMind = new(); - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(Reset); - SubscribeLocalEvent(HandleMindAdded); - SubscribeLocalEvent(OnPortDisconnected); - SubscribeLocalEvent(OnAnchor); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(OnPowerChanged); - } + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(Reset); + SubscribeLocalEvent(HandleMindAdded); + SubscribeLocalEvent(OnPortDisconnected); + SubscribeLocalEvent(OnAnchor); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnPowerChanged); + } - private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) - { - clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); - _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); - } + private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) + { + clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); + _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); + } - private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) - { - if (!args.Powered - && component.ActivelyCloning) - CauseCloningFail(uid, component); - } + private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) + { + if (!args.Powered + && component.ActivelyCloning) + CauseCloningFail(uid, component); + } - internal void TransferMindToClone(EntityUid mindId, MindComponent mind) - { - if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) - || !EntityManager.EntityExists(entity) - || !TryComp(entity, out var mindComp) - || mindComp.Mind != null) - return; - - _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); - _mindSystem.UnVisit(mindId, mind); - ClonesWaitingForMind.Remove(mind); - } + internal void TransferMindToClone(EntityUid mindId, MindComponent mind) + { + if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) + || !EntityManager.EntityExists(entity) + || !TryComp(entity, out var mindComp) + || mindComp.Mind != null) + return; + + _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); + _mindSystem.UnVisit(mindId, mind); + ClonesWaitingForMind.Remove(mind); + } - private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) + private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) + { + if (clonedComponent.Parent == EntityUid.Invalid + || !EntityManager.EntityExists(clonedComponent.Parent) + || !TryComp(clonedComponent.Parent, out var cloningPodComponent) + || uid != cloningPodComponent.BodyContainer.ContainedEntity) { - if (clonedComponent.Parent == EntityUid.Invalid - || !EntityManager.EntityExists(clonedComponent.Parent) - || !TryComp(clonedComponent.Parent, out var cloningPodComponent) - || uid != cloningPodComponent.BodyContainer.ContainedEntity) - { - EntityManager.RemoveComponent(uid); - return; - } - UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); + EntityManager.RemoveComponent(uid); + return; } + UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); + } - private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) - { - pod.ConnectedConsole = null; - } + private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) + { + pod.ConnectedConsole = null; + } - private void OnAnchor(EntityUid uid, CloningPodComponent component, ref AnchorStateChangedEvent args) - { - if (component.ActivelyCloning) - CauseCloningFail(uid, component); + private void OnAnchor(EntityUid uid, CloningPodComponent component, ref AnchorStateChangedEvent args) + { + if (component.ActivelyCloning) + CauseCloningFail(uid, component); - if (component.ConnectedConsole == null - || !TryComp(component.ConnectedConsole, out var console) - || !args.Anchored - || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console)) - return; + if (component.ConnectedConsole == null + || !TryComp(component.ConnectedConsole, out var console) + || !args.Anchored + || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console)) + return; - _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); - } + _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); + } - private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange - || !_powerReceiverSystem.IsPowered(uid)) - return; + private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange + || !_powerReceiverSystem.IsPowered(uid)) + return; - args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); - } + args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); + } - private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) + private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) + { + var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); + RaiseLocalEvent(bodyToClone, ref ev); + cloningCostMultiplier = ev.CloningCostMultiplier; + + if (ev.Cancelled && ev.CloningFailMessage is not null) { - var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); - RaiseLocalEvent(bodyToClone, ref ev); - cloningCostMultiplier = ev.CloningCostMultiplier; + _chatSystem.TrySendInGameICMessage(uid, + Loc.GetString(ev.CloningFailMessage), + InGameICChatType.Speak, false); + return false; + } - if (ev.Cancelled && ev.CloningFailMessage is not null) - { - _chatSystem.TrySendInGameICMessage(uid, - Loc.GetString(ev.CloningFailMessage), - InGameICChatType.Speak, false); - return false; - } + return true; + } - return true; - } + private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) + { + var cloningCost = (int) Math.Round(physics.FixturesMass + * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) + * clonePod.BiomassCostMultiplier + * cloningCostMultiplier); - private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) + if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) { - var cloningCost = (int) Math.Round(physics.FixturesMass - * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) - * clonePod.BiomassCostMultiplier - * cloningCostMultiplier); + _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); + return false; + } - if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) - { - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); - return false; - } + _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); + clonePod.UsedBiomass = cloningCost; - _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); - clonePod.UsedBiomass = cloningCost; + return true; + } - return true; - } + private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) + { + geneticDamage = 0; + if (clonePod.DoMetempsychosis) + return false; - private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) + if (TryComp(bodyToClone, out var damageable) + && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) + && clonePod.ConnectedConsole is not null) { - geneticDamage = 0; - if (clonePod.DoMetempsychosis) - return false; - - if (TryComp(bodyToClone, out var damageable) - && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) - && clonePod.ConnectedConsole is not null) - { - geneticDamage += (float) cellularDmg; - var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); - chance *= failChanceModifier; + geneticDamage += (float) cellularDmg; + var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); + chance *= failChanceModifier; - if (cellularDmg > 0) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); + if (cellularDmg > 0) + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); - if (_random.Prob(chance)) - { - CauseCloningFail(uid, clonePod); - return true; - } + if (_random.Prob(chance)) + { + CauseCloningFail(uid, clonePod); + return true; } - return false; } + return false; + } - private void CauseCloningFail(EntityUid uid, CloningPodComponent component) - { - UpdateStatus(uid, CloningPodStatus.Gore, component); - component.FailedClone = true; - component.ActivelyCloning = true; - } + private void CauseCloningFail(EntityUid uid, CloningPodComponent component) + { + UpdateStatus(uid, CloningPodStatus.Gore, component); + component.FailedClone = true; + component.ActivelyCloning = true; + } - public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent clonePod, float failChanceModifier = 1) - { - if (!_mobStateSystem.IsDead(bodyToClone) - || clonePod.ActivelyCloning - || clonePod.ConnectedConsole == null - || !CheckUncloneable(uid, bodyToClone, clonePod, out var cloningCostMultiplier) - || !TryComp(bodyToClone, out var humanoid) - || !TryComp(bodyToClone, out var physics) - || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier) - || !ClonesWaitingForMind.TryGetValue(mindEnt.Comp, out var clone) - || !TryComp(clone, out var cloneMindComp) - || cloneMindComp.Mind == null - || cloneMindComp.Mind == mindEnt - || mindEnt.Comp.UserId == null - || !_playerManager.TryGetSessionById(mindEnt.Comp.UserId.Value, out var client)) - return false; - - ClonesWaitingForMind.Remove(mindEnt.Comp); - - var pref = humanoid.LastProfileLoaded; - - if (pref == null - || !_prototypeManager.TryIndex(humanoid.Species, out var speciesPrototype)) - return false; - - if (CheckGeneticDamage(uid, bodyToClone, clonePod, out var geneticDamage, failChanceModifier)) - return true; + public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent clonePod, float failChanceModifier = 1) + { + if (!_mobStateSystem.IsDead(bodyToClone) + || clonePod.ActivelyCloning + || clonePod.ConnectedConsole == null + || !CheckUncloneable(uid, bodyToClone, clonePod, out var cloningCostMultiplier) + || !TryComp(bodyToClone, out var humanoid) + || !TryComp(bodyToClone, out var physics) + || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier) + || !ClonesWaitingForMind.TryGetValue(mindEnt.Comp, out var clone) + || !TryComp(clone, out var cloneMindComp) + || cloneMindComp.Mind == null + || cloneMindComp.Mind == mindEnt + || mindEnt.Comp.UserId == null + || !_playerManager.TryGetSessionById(mindEnt.Comp.UserId.Value, out var client)) + return false; - var mob = FetchAndSpawnMob(uid, clonePod, pref, speciesPrototype, humanoid, bodyToClone, geneticDamage); + ClonesWaitingForMind.Remove(mindEnt.Comp); - var ev = new CloningEvent(bodyToClone, mob); - RaiseLocalEvent(bodyToClone, ref ev); + var pref = humanoid.LastProfileLoaded; - if (!ev.NameHandled) - _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); + if (pref == null + || !_prototypeManager.TryIndex(humanoid.Species, out var speciesPrototype)) + return false; - var cloneMindReturn = EntityManager.AddComponent(mob); - cloneMindReturn.Mind = mindEnt.Comp; - cloneMindReturn.Parent = uid; - _containerSystem.Insert(mob, clonePod.BodyContainer); - ClonesWaitingForMind.Add(mindEnt.Comp, mob); - UpdateStatus(uid, CloningPodStatus.NoMind, clonePod); - _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mindEnt.Comp, this), client); + if (CheckGeneticDamage(uid, bodyToClone, clonePod, out var geneticDamage, failChanceModifier)) + return true; - clonePod.ActivelyCloning = true; + var mob = FetchAndSpawnMob(uid, clonePod, pref, speciesPrototype, humanoid, bodyToClone, geneticDamage); - if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) - foreach (var special in prototype.Special) - if (special is AddComponentSpecial) - special.AfterEquip(mob); + var ev = new CloningEvent(bodyToClone, mob); + RaiseLocalEvent(bodyToClone, ref ev); - return true; - } + if (!ev.NameHandled) + _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); - public void AttemptCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) - { - if (cloningPodComponent.BodyContainer.ContainedEntity is { Valid: true } entity - && TryComp(entity, out var physics) - && physics.Mass > 71) - Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => UpdateCloning(cloningPod, cloningPodComponent)); + var cloneMindReturn = EntityManager.AddComponent(mob); + cloneMindReturn.Mind = mindEnt.Comp; + cloneMindReturn.Parent = uid; + _containerSystem.Insert(mob, clonePod.BodyContainer); + ClonesWaitingForMind.Add(mindEnt.Comp, mob); + UpdateStatus(uid, CloningPodStatus.NoMind, clonePod); + _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mindEnt.Comp, this), client); - Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => UpdateCloning(cloningPod, cloningPodComponent)); - } + clonePod.ActivelyCloning = true; - public void UpdateCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) - { - if (!cloningPodComponent.ActivelyCloning - || !_powerReceiverSystem.IsPowered(cloningPod) - || cloningPodComponent.BodyContainer.ContainedEntity == null - || cloningPodComponent.FailedClone) - EndFailedCloning(cloningPod, cloningPodComponent); + if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) + foreach (var special in prototype.Special) + if (special is AddComponentSpecial) + special.AfterEquip(mob); - Eject(cloningPod, cloningPodComponent); - } + return true; + } - public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) - { - cloningPod.Status = status; - _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); - } + public void AttemptCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) + { + if (cloningPodComponent.BodyContainer.ContainedEntity is { Valid: true } entity + && TryComp(entity, out var physics) + && physics.Mass > 71) + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => UpdateCloning(cloningPod, cloningPodComponent)); - /// - /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. - /// - private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) - { - if (!this.IsPowered(uid, EntityManager)) - return; + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => UpdateCloning(cloningPod, cloningPodComponent)); + } - if (clonePod.ActivelyCloning) - CauseCloningFail(uid, clonePod); + public void UpdateCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) + { + if (!cloningPodComponent.ActivelyCloning + || !_powerReceiverSystem.IsPowered(cloningPod) + || cloningPodComponent.BodyContainer.ContainedEntity == null + || cloningPodComponent.FailedClone) + EndFailedCloning(cloningPod, cloningPodComponent); - _audio.PlayPvs(clonePod.SparkSound, uid); - _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); - args.Handled = true; - } + Eject(cloningPod, cloningPodComponent); + } - public void Eject(EntityUid uid, CloningPodComponent? clonePod) - { - if (!Resolve(uid, ref clonePod) - || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity - || clonePod.CloningProgress < clonePod.CloningTime) - return; - - EntityManager.RemoveComponent(entity); - _containerSystem.Remove(entity, clonePod.BodyContainer); - clonePod.CloningProgress = 0f; - clonePod.UsedBiomass = 0; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - clonePod.ActivelyCloning = false; - } + public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) + { + cloningPod.Status = status; + _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); + } - private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) - { - if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) - { - if (TryComp(entity, out var physics) - && TryComp(entity, out var bloodstream)) - MakeAHugeMess(uid, physics, bloodstream); - else MakeAHugeMess(uid); + /// + /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. + /// + private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) + { + if (!this.IsPowered(uid, EntityManager)) + return; - QueueDel(entity); - } - else MakeAHugeMess(uid); + if (clonePod.ActivelyCloning) + CauseCloningFail(uid, clonePod); - clonePod.FailedClone = false; - clonePod.CloningProgress = 0f; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - if (HasComp(uid)) - { - _audio.PlayPvs(clonePod.ScreamSound, uid); - Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); - } + _audio.PlayPvs(clonePod.SparkSound, uid); + _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); + args.Handled = true; + } + + public void Eject(EntityUid uid, CloningPodComponent? clonePod) + { + if (!Resolve(uid, ref clonePod) + || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity + || clonePod.CloningProgress < clonePod.CloningTime) + return; + + EntityManager.RemoveComponent(entity); + _containerSystem.Remove(entity, clonePod.BodyContainer); + clonePod.CloningProgress = 0f; + clonePod.UsedBiomass = 0; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + clonePod.ActivelyCloning = false; + } - if (!HasComp(uid)) - _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); + private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) + { + if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) + { + if (TryComp(entity, out var physics) + && TryComp(entity, out var bloodstream)) + MakeAHugeMess(uid, physics, bloodstream); + else MakeAHugeMess(uid); - clonePod.UsedBiomass = 0; - clonePod.ActivelyCloning = false; + QueueDel(entity); } + else MakeAHugeMess(uid); - /// - /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. - /// - /// - /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA - /// - /// - /// - /// - private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) + clonePod.FailedClone = false; + clonePod.CloningProgress = 0f; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + if (HasComp(uid)) { - var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); - Solution bloodSolution = new(); + _audio.PlayPvs(clonePod.ScreamSound, uid); + Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); + } - tileMix?.AdjustMoles(Gas.Ammonia, 0.5f - * ((physics is not null) - ? physics.Mass - : 71)); + if (!HasComp(uid)) + _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); - bloodSolution.AddReagent("blood", 0.8f - * ((blood is not null) - ? blood.BloodMaxVolume - : 300)); + clonePod.UsedBiomass = 0; + clonePod.ActivelyCloning = false; + } - _puddleSystem.TrySpillAt(uid, bloodSolution, out _); - } + /// + /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. + /// + /// + /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA + /// + /// + /// + /// + private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) + { + var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); + Solution bloodSolution = new(); - /// - /// This function handles the Clone vs. Metem logic, as well as creation of the new body. - /// - private EntityUid FetchAndSpawnMob - (EntityUid clonePod, - CloningPodComponent clonePodComp, - HumanoidCharacterProfile pref, - SpeciesPrototype speciesPrototype, - HumanoidAppearanceComponent humanoid, - EntityUid bodyToClone, - float geneticDamage) - { - List sexes = new(); - bool switchingSpecies = false; - var toSpawn = speciesPrototype.Prototype; - var forceOldProfile = true; - var oldKarma = 0; - var oldGender = humanoid.Gender; - if (TryComp(bodyToClone, out var oldKarmaComp)) - oldKarma += oldKarmaComp.Score; - - if (clonePodComp.DoMetempsychosis) - { - toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); - forceOldProfile = !changeProfile; + tileMix?.AdjustMoles(Gas.Ammonia, 0.5f + * ((physics is not null) + ? physics.Mass + : 71)); - if (changeProfile) - geneticDamage = 0; + bloodSolution.AddReagent("blood", 0.8f + * ((blood is not null) + ? blood.BloodMaxVolume + : 300)); - if (newSpecies != null) - { - sexes = newSpecies.Sexes; + _puddleSystem.TrySpillAt(uid, bloodSolution, out _); + } - if (speciesPrototype.ID != newSpecies.ID) - switchingSpecies = true; - } - } - EntityUid mob = Spawn(toSpawn, _transformSystem.GetMapCoordinates(clonePod)); - EnsureComp(mob, out var newKarma); - newKarma.Score += oldKarma; + /// + /// This function handles the Clone vs. Metem logic, as well as creation of the new body. + /// + private EntityUid FetchAndSpawnMob + (EntityUid clonePod, + CloningPodComponent clonePodComp, + HumanoidCharacterProfile pref, + SpeciesPrototype speciesPrototype, + HumanoidAppearanceComponent humanoid, + EntityUid bodyToClone, + float geneticDamage) + { + List sexes = new(); + bool switchingSpecies = false; + var toSpawn = speciesPrototype.Prototype; + var forceOldProfile = true; + var oldKarma = 0; + var oldGender = humanoid.Gender; + if (TryComp(bodyToClone, out var oldKarmaComp)) + oldKarma += oldKarmaComp.Score; + + if (clonePodComp.DoMetempsychosis) + { + toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); + forceOldProfile = !changeProfile; + + if (changeProfile) + geneticDamage = 0; - // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. - if (HasComp(mob)) + if (newSpecies != null) { - DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), 101f + geneticDamage); - _damageable.TryChangeDamage(mob, damage, true); + sexes = newSpecies.Sexes; + + if (speciesPrototype.ID != newSpecies.ID) + switchingSpecies = true; } + } + EntityUid mob = Spawn(toSpawn, _transformSystem.GetMapCoordinates(clonePod)); + EnsureComp(mob, out var newKarma); + newKarma.Score += oldKarma; + + // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. + if (HasComp(mob)) + { + DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), 101f + geneticDamage); + _damageable.TryChangeDamage(mob, damage, true); + } - if (TryComp(mob, out var newHumanoid)) + if (TryComp(mob, out var newHumanoid)) + { + if (switchingSpecies && !forceOldProfile) { - if (switchingSpecies && !forceOldProfile) - { - var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); - var oldName = _serialization.CreateCopy(pref.Name, null, false, true); + var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); + var oldName = _serialization.CreateCopy(pref.Name, null, false, true); - pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); - if (sexes.Contains(humanoid.Sex) - && _config.GetCVar(CCVars.CloningPreserveSex)) - pref = pref.WithSex(humanoid.Sex); + if (sexes.Contains(humanoid.Sex) + && _config.GetCVar(CCVars.CloningPreserveSex)) + pref = pref.WithSex(humanoid.Sex); - if (_config.GetCVar(CCVars.CloningPreserveGender)) - pref = pref.WithGender(humanoid.Gender); - else oldGender = humanoid.Gender; + if (_config.GetCVar(CCVars.CloningPreserveGender)) + pref = pref.WithGender(humanoid.Gender); + else oldGender = humanoid.Gender; - if (_config.GetCVar(CCVars.CloningPreserveAge)) - pref = pref.WithAge(humanoid.Age); + if (_config.GetCVar(CCVars.CloningPreserveAge)) + pref = pref.WithAge(humanoid.Age); - if (_config.GetCVar(CCVars.CloningPreserveHeight)) - pref = pref.WithHeight(humanoid.Height); + if (_config.GetCVar(CCVars.CloningPreserveHeight)) + pref = pref.WithHeight(humanoid.Height); - if (_config.GetCVar(CCVars.CloningPreserveWidth)) - pref = pref.WithWidth(humanoid.Width); + if (_config.GetCVar(CCVars.CloningPreserveWidth)) + pref = pref.WithWidth(humanoid.Width); - if (_config.GetCVar(CCVars.CloningPreserveName)) - pref = pref.WithName(oldName); + if (_config.GetCVar(CCVars.CloningPreserveName)) + pref = pref.WithName(oldName); - if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) - pref = pref.WithFlavorText(flavorText); - } - _humanoidSystem.LoadProfile(mob, pref); + if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) + pref = pref.WithFlavorText(flavorText); } + _humanoidSystem.LoadProfile(mob, pref); + } - var ev = new CloningEvent(bodyToClone, mob); - RaiseLocalEvent(bodyToClone, ref ev); + var ev = new CloningEvent(bodyToClone, mob); + RaiseLocalEvent(bodyToClone, ref ev); - if (!ev.NameHandled) - _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); + if (!ev.NameHandled) + _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); - var grammar = EnsureComp(mob); - grammar.ProperNoun = true; - grammar.Gender = oldGender; - Dirty(mob, grammar); + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = oldGender; + Dirty(mob, grammar); - if (forceOldProfile - && TryComp(bodyToClone, out var psionic)) - { - var newPsionic = _serialization.CreateCopy(psionic, null, false, true); - AddComp(mob, newPsionic, true); - } + if (forceOldProfile + && TryComp(bodyToClone, out var psionic)) + { + var newPsionic = _serialization.CreateCopy(psionic, null, false, true); + AddComp(mob, newPsionic, true); + } - if (TryComp(bodyToClone, out var oldKnowLangs)) - { - var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); - AddComp(mob, newKnowLangs, true); - } + if (TryComp(bodyToClone, out var oldKnowLangs)) + { + var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); + AddComp(mob, newKnowLangs, true); + } - if (TryComp(bodyToClone, out var oldSpeakLangs)) - { - var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); - AddComp(mob, newSpeakLangs, true); - } + if (TryComp(bodyToClone, out var oldSpeakLangs)) + { + var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); + AddComp(mob, newSpeakLangs, true); + } - if (clonePodComp.DoMetempsychosis) - EnsureComp(mob); + if (clonePodComp.DoMetempsychosis) + EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + EnsureComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); + RemComp(mob); - _tag.AddTag(mob, "DoorBumpOpener"); + _tag.AddTag(mob, "DoorBumpOpener"); - return mob; - } - public void Reset(RoundRestartCleanupEvent ev) - { - ClonesWaitingForMind.Clear(); - } + return mob; + } + public void Reset(RoundRestartCleanupEvent ev) + { + ClonesWaitingForMind.Clear(); + } - public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, SpeciesPrototype oldSpecies, int karma, out SpeciesPrototype? species, out bool changeProfile) + public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, SpeciesPrototype oldSpecies, int karma, out SpeciesPrototype? species, out bool changeProfile) + { + changeProfile = true; + species = oldSpecies; + if (!_prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) + || !_prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype) + || !_prototypeManager.TryIndex(component.MetempsychoticNonHumanoidPool, out var nonHumanoidPool) + || !_prototypeManager.TryIndex(nonHumanoidPool.Pick(), out var entityPrototype)) { - changeProfile = true; - species = oldSpecies; - if (!_prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) - || !_prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype) - || !_prototypeManager.TryIndex(component.MetempsychoticNonHumanoidPool, out var nonHumanoidPool) - || !_prototypeManager.TryIndex(nonHumanoidPool.Pick(), out var entityPrototype)) - { - DebugTools.Assert("Could not index species for metempsychotic machine."); - changeProfile = false; - return oldSpecies.Prototype; - } - var chance = (component.HumanoidBaseChance - karma * component.KarmaOffset) * _contests.MindContest(oldBody, true); + DebugTools.Assert("Could not index species for metempsychotic machine."); + changeProfile = false; + return oldSpecies.Prototype; + } + var chance = (component.HumanoidBaseChance - karma * component.KarmaOffset) * _contests.MindContest(oldBody, true); - var ev = new ReincarnatingEvent(oldBody, chance); - RaiseLocalEvent(oldBody, ref ev); + var ev = new ReincarnatingEvent(oldBody, chance); + RaiseLocalEvent(oldBody, ref ev); - chance = ev.OverrideChance - ? ev.ReincarnationChances - : chance * ev.ReincarnationChanceModifier; + chance = ev.OverrideChance + ? ev.ReincarnationChances + : chance * ev.ReincarnationChanceModifier; - switch (ev.ForcedType) - { - case ForcedMetempsychosisType.None: - if (!ev.NeverTrulyClone - && chance > 1 - && _random.Prob(chance - 1)) - { - changeProfile = false; - return oldSpecies.Prototype; - } - - chance = Math.Clamp(chance, 0, 1); - if (_random.Prob(chance)) - { - species = speciesPrototype; - return speciesPrototype.Prototype; - } - species = null; - return entityPrototype.ID; - - case ForcedMetempsychosisType.Clone: + switch (ev.ForcedType) + { + case ForcedMetempsychosisType.None: + if (!ev.NeverTrulyClone + && chance > 1 + && _random.Prob(chance - 1)) + { changeProfile = false; return oldSpecies.Prototype; + } - case ForcedMetempsychosisType.RandomHumanoid: + chance = Math.Clamp(chance, 0, 1); + if (_random.Prob(chance)) + { species = speciesPrototype; return speciesPrototype.Prototype; + } + species = null; + return entityPrototype.ID; - case ForcedMetempsychosisType.RandomNonHumanoid: - species = null; - return entityPrototype.ID; - } - changeProfile = false; - return oldSpecies.Prototype; + case ForcedMetempsychosisType.Clone: + changeProfile = false; + return oldSpecies.Prototype; + + case ForcedMetempsychosisType.RandomHumanoid: + species = speciesPrototype; + return speciesPrototype.Prototype; + + case ForcedMetempsychosisType.RandomNonHumanoid: + species = null; + return entityPrototype.ID; } + changeProfile = false; + return oldSpecies.Prototype; } } From e962955f6aa494228157e02f240becb50237254b Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 29 Aug 2024 21:40:29 -0400 Subject: [PATCH 26/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 125 +++++++++++++++--------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 7c59cb1576f..01464723e0b 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -172,6 +172,10 @@ private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEv args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); } + /// + /// Test if the body to be cloned has any conditions that would prevent cloning from taking place. + /// Or, if the body has a particular reason to make cloning more difficult. + /// private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) { var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); @@ -189,6 +193,10 @@ private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodCo return true; } + /// + /// Checks the body's physics component and any previously obtained modifiers to determine biomass cost. + /// If there is insufficient biomass, the cloning cannot start. + /// private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) { var cloningCost = (int) Math.Round(physics.FixturesMass @@ -208,6 +216,10 @@ private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPo return true; } + /// + /// Tests the original body for genetic damage, while returning the cloning damage for later damage. + /// The body's cellular damage is also used as a potential failure state, giving a chance for the cloning to fail immediately. + /// private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) { geneticDamage = 0; @@ -234,6 +246,10 @@ private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPod return false; } + /// + /// When this condition is called, it sets the cloning pod to its fail condition. + /// Such that when the cloning timer ends, the body that would be created, is turned into clone soup. + /// private void CauseCloningFail(EntityUid uid, CloningPodComponent component) { UpdateStatus(uid, CloningPodStatus.Gore, component); @@ -241,6 +257,9 @@ private void CauseCloningFail(EntityUid uid, CloningPodComponent component) component.ActivelyCloning = true; } + /// + /// The master function behind Cloning, called by the cloning console via button press to start the cloning process. + /// public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent clonePod, float failChanceModifier = 1) { if (!_mobStateSystem.IsDead(bodyToClone) @@ -260,12 +279,13 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity + /// Begins the cloning timer, which at the end can either produce clone soup, or a functional body, depending on if anything interrupts the procedure. + /// public void AttemptCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) { if (cloningPodComponent.BodyContainer.ContainedEntity is { Valid: true } entity && TryComp(entity, out var physics) && physics.Mass > 71) - Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => UpdateCloning(cloningPod, cloningPodComponent)); + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => EndCloning(cloningPod, cloningPodComponent)); - Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => UpdateCloning(cloningPod, cloningPodComponent)); + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => EndCloning(cloningPod, cloningPodComponent)); } - public void UpdateCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) + /// + /// Ding, your body is ready. Time to find out if it's soup or solid. + /// + public void EndCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) { if (!cloningPodComponent.ActivelyCloning || !_powerReceiverSystem.IsPowered(cloningPod) || cloningPodComponent.BodyContainer.ContainedEntity == null || cloningPodComponent.FailedClone) - EndFailedCloning(cloningPod, cloningPodComponent); + EndFailedCloning(cloningPod, cloningPodComponent); //Surprise, it's soup! - Eject(cloningPod, cloningPodComponent); + Eject(cloningPod, cloningPodComponent); //Hey look, a body! } public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) @@ -338,7 +364,10 @@ private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmagg args.Handled = true; } - public void Eject(EntityUid uid, CloningPodComponent? clonePod) + /// + /// This is the success condition for cloning. At the end of the timer, if nothing interrupted it, this function is called to finish the cloning by dispensing the body. + /// + private void Eject(EntityUid uid, CloningPodComponent? clonePod) { if (!Resolve(uid, ref clonePod) || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity @@ -353,6 +382,9 @@ public void Eject(EntityUid uid, CloningPodComponent? clonePod) clonePod.ActivelyCloning = false; } + /// + /// And now we turn it over to Chef Pod to make soup! + /// private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) { if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) @@ -382,15 +414,53 @@ private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) clonePod.ActivelyCloning = false; } + /// + /// The body coming out of the machine isn't guaranteed to even be a Humanoid. + /// This function makes sure the body is "Human Playable", with no funny business. + /// + private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile = false, bool DoMetempsychosis) + { + if (forceOldProfile + && TryComp(bodyToClone, out var psionic)) + { + var newPsionic = _serialization.CreateCopy(psionic, null, false, true); + AddComp(uid, newPsionic, true); + } + + if (TryComp(bodyToClone, out var oldKnowLangs)) + { + var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); + AddComp(uid, newKnowLangs, true); + } + + + if (TryComp(bodyToClone, out var oldSpeakLangs)) + { + var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); + AddComp(uid, newSpeakLangs, true); + } + + if (DoMetempsychosis) + EnsureComp(uid); + + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + _tag.AddTag(uid, "DoorBumpOpener"); + } + /// /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. /// /// /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA /// - /// - /// - /// private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) { var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); @@ -503,40 +573,7 @@ private EntityUid FetchAndSpawnMob grammar.Gender = oldGender; Dirty(mob, grammar); - if (forceOldProfile - && TryComp(bodyToClone, out var psionic)) - { - var newPsionic = _serialization.CreateCopy(psionic, null, false, true); - AddComp(mob, newPsionic, true); - } - - if (TryComp(bodyToClone, out var oldKnowLangs)) - { - var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); - AddComp(mob, newKnowLangs, true); - } - - - if (TryComp(bodyToClone, out var oldSpeakLangs)) - { - var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); - AddComp(mob, newSpeakLangs, true); - } - - if (clonePodComp.DoMetempsychosis) - EnsureComp(mob); - - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - - _tag.AddTag(mob, "DoorBumpOpener"); + CleanupCloneComponents(mob, bodyToClone, forceOldProfile, clonePodComp.DoMetempsychosis); return mob; } From 7d17cb5780948e1ed8cb1b40cd2efb438d35cda2 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 29 Aug 2024 22:27:35 -0400 Subject: [PATCH 27/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 01464723e0b..d796f50683f 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -418,7 +418,7 @@ private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) /// The body coming out of the machine isn't guaranteed to even be a Humanoid. /// This function makes sure the body is "Human Playable", with no funny business. /// - private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile = false, bool DoMetempsychosis) + private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile, bool doMetempsychosis) { if (forceOldProfile && TryComp(bodyToClone, out var psionic)) @@ -440,7 +440,7 @@ private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool f AddComp(uid, newSpeakLangs, true); } - if (DoMetempsychosis) + if (doMetempsychosis) EnsureComp(uid); EnsureComp(uid); From 1c78ad56e61729e348d0704db5c5d3fecf001775 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 17:02:03 -0400 Subject: [PATCH 28/33] Update cloning system further --- Content.Server/Cloning/CloningSystem.cs | 115 ++++++++++++------ Content.Shared/Cloning/CloningPodComponent.cs | 24 ++++ 2 files changed, 101 insertions(+), 38 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index d796f50683f..e851284a207 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -57,6 +57,10 @@ using Timer = Robust.Shared.Timing.Timer; using Content.Server.Power.Components; using Content.Shared.Damage.Prototypes; +using Content.Shared.Drunk; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.Enums; namespace Content.Server.Cloning; @@ -88,7 +92,9 @@ public sealed class CloningSystem : EntitySystem [Dependency] private readonly ContestsSystem _contests = default!; [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly DamageableSystem _damageable = default!; - + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly ThirstSystem _thirst = default!; + [Dependency] private readonly SharedDrunkSystem _drunk = default!; public readonly Dictionary ClonesWaitingForMind = new(); public override void Initialize() @@ -479,6 +485,71 @@ private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, Bloo _puddleSystem.TrySpillAt(uid, bloodSolution, out _); } + /// + /// Modify the clone's hunger and thirst values by an amount set in the cloningPod. + /// + private void UpdateHungerAndThirst(EntityUid uid, CloningPodComponent cloningPod) + { + if (cloningPod.HungerAdjustment != 0 + && TryComp(uid, out var hungerComponent)) + _hunger.SetHunger(uid, cloningPod.HungerAdjustment, hungerComponent); + + if (cloningPod.ThirstAdjustment != 0 + && TryComp(uid, out var thirstComponent)) + _thirst.SetThirst(uid, thirstComponent, cloningPod.ThirstAdjustment); + + if (cloningPod.DrunkTimer != 0) + _drunk.TryApplyDrunkenness(uid, cloningPod.DrunkTimer); + } + + private void UpdateCloneAppearance( + EntityUid mob, + HumanoidCharacterProfile pref, + HumanoidAppearanceComponent humanoid, + List sexes, + Gender oldGender, + bool switchingSpecies, + bool forceOldProfile, + out Gender gender) + { + gender = oldGender; + if (!TryComp(mob, out var newHumanoid)) + return; + + if (switchingSpecies && !forceOldProfile) + { + var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); + var oldName = _serialization.CreateCopy(pref.Name, null, false, true); + + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); + + if (sexes.Contains(humanoid.Sex) + && _config.GetCVar(CCVars.CloningPreserveSex)) + pref = pref.WithSex(humanoid.Sex); + + if (_config.GetCVar(CCVars.CloningPreserveGender)) + pref = pref.WithGender(humanoid.Gender); + else gender = humanoid.Gender; + + if (_config.GetCVar(CCVars.CloningPreserveAge)) + pref = pref.WithAge(humanoid.Age); + + if (_config.GetCVar(CCVars.CloningPreserveHeight)) + pref = pref.WithHeight(humanoid.Height); + + if (_config.GetCVar(CCVars.CloningPreserveWidth)) + pref = pref.WithWidth(humanoid.Width); + + if (_config.GetCVar(CCVars.CloningPreserveName)) + pref = pref.WithName(oldName); + + if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) + pref = pref.WithFlavorText(flavorText); + + _humanoidSystem.LoadProfile(mob, pref); + } + } + /// /// This function handles the Clone vs. Metem logic, as well as creation of the new body. /// @@ -521,47 +592,14 @@ private EntityUid FetchAndSpawnMob newKarma.Score += oldKarma; // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. - if (HasComp(mob)) + if (clonePodComp.DoGeneticDamage + && HasComp(mob)) { DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), 101f + geneticDamage); _damageable.TryChangeDamage(mob, damage, true); } - if (TryComp(mob, out var newHumanoid)) - { - if (switchingSpecies && !forceOldProfile) - { - var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); - var oldName = _serialization.CreateCopy(pref.Name, null, false, true); - - pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); - - if (sexes.Contains(humanoid.Sex) - && _config.GetCVar(CCVars.CloningPreserveSex)) - pref = pref.WithSex(humanoid.Sex); - - if (_config.GetCVar(CCVars.CloningPreserveGender)) - pref = pref.WithGender(humanoid.Gender); - else oldGender = humanoid.Gender; - - if (_config.GetCVar(CCVars.CloningPreserveAge)) - pref = pref.WithAge(humanoid.Age); - - if (_config.GetCVar(CCVars.CloningPreserveHeight)) - pref = pref.WithHeight(humanoid.Height); - - if (_config.GetCVar(CCVars.CloningPreserveWidth)) - pref = pref.WithWidth(humanoid.Width); - - if (_config.GetCVar(CCVars.CloningPreserveName)) - pref = pref.WithName(oldName); - - if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) - pref = pref.WithFlavorText(flavorText); - } - _humanoidSystem.LoadProfile(mob, pref); - } - + UpdateCloneAppearance(mob, pref, humanoid, sexes, oldGender, switchingSpecies, forceOldProfile, out var gender); var ev = new CloningEvent(bodyToClone, mob); RaiseLocalEvent(bodyToClone, ref ev); @@ -570,10 +608,11 @@ private EntityUid FetchAndSpawnMob var grammar = EnsureComp(mob); grammar.ProperNoun = true; - grammar.Gender = oldGender; + grammar.Gender = gender; Dirty(mob, grammar); CleanupCloneComponents(mob, bodyToClone, forceOldProfile, clonePodComp.DoMetempsychosis); + UpdateHungerAndThirst(mob, clonePodComp); return mob; } diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 7b988f789c3..c8cb463ae22 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -78,6 +78,30 @@ public sealed partial class CloningPodComponent : Component [ViewVariables(VVAccess.ReadWrite)] public bool ActivelyCloning; + /// + /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as 101 + the genetic damage of the body to be cloned. + /// + [DataField] + public bool DoGeneticDamage = true; + + /// + /// How much should the cloning pod adjust the hunger of an entity by. + /// + [DataField] + public float HungerAdjustment = 50; + + /// + /// How much should the cloning pod adjust the thirst of an entity by. + /// + [DataField] + public float ThirstAdjustment = 50; + + /// + /// How much time should the cloning pod give an entity the durnk condition, in seconds. + /// + [DataField] + public float DrunkTimer = 300; + #region Metempsychosis /// From c9b4924cc354a4fe9ad61dd8a3a4af53ce2ad260 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 17:19:52 -0400 Subject: [PATCH 29/33] Unhardcode crit threshold --- Content.Server/Cloning/CloningSystem.cs | 49 ++++++++++++++----- Content.Shared/Cloning/CloningPodComponent.cs | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index e851284a207..dfe18429c30 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -95,6 +95,7 @@ public sealed class CloningSystem : EntitySystem [Dependency] private readonly HungerSystem _hunger = default!; [Dependency] private readonly ThirstSystem _thirst = default!; [Dependency] private readonly SharedDrunkSystem _drunk = default!; + [Dependency] private readonly MobThresholdSystem _thresholds = default!; public readonly Dictionary ClonesWaitingForMind = new(); public override void Initialize() @@ -502,6 +503,10 @@ private void UpdateHungerAndThirst(EntityUid uid, CloningPodComponent cloningPod _drunk.TryApplyDrunkenness(uid, cloningPod.DrunkTimer); } + /// + /// Updates the HumanoidAppearanceComponent of the clone. + /// If a species swap is occuring, this updates all relevant information as per server config. + /// private void UpdateCloneAppearance( EntityUid mob, HumanoidCharacterProfile pref, @@ -550,6 +555,35 @@ private void UpdateCloneAppearance( } } + /// + /// Optionally makes sure that pronoun preferences are preserved by the clone. + /// Although handled here, the swap(if it occurs) happens during UpdateCloneAppearance. + /// + /// + /// + private void UpdateGrammar(EntityUid mob, Gender gender) + { + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = gender; + Dirty(mob, grammar); + } + + /// + /// Optionally puts the clone in crit with high Cellular damage. + /// Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. + /// + private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, float geneticDamage) + { + if (!clonePodComp.DoGeneticDamage + || !HasComp(mob) + || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) + return; + + DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), (int) threshold + 1 + geneticDamage); + _damageable.TryChangeDamage(mob, damage, true); + } + /// /// This function handles the Clone vs. Metem logic, as well as creation of the new body. /// @@ -591,14 +625,7 @@ private EntityUid FetchAndSpawnMob EnsureComp(mob, out var newKarma); newKarma.Score += oldKarma; - // Put the clone in crit with high Cellular damage. Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. - if (clonePodComp.DoGeneticDamage - && HasComp(mob)) - { - DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), 101f + geneticDamage); - _damageable.TryChangeDamage(mob, damage, true); - } - + UpdateCloneDamage(mob, clonePodComp, geneticDamage); UpdateCloneAppearance(mob, pref, humanoid, sexes, oldGender, switchingSpecies, forceOldProfile, out var gender); var ev = new CloningEvent(bodyToClone, mob); RaiseLocalEvent(bodyToClone, ref ev); @@ -606,11 +633,7 @@ private EntityUid FetchAndSpawnMob if (!ev.NameHandled) _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); - var grammar = EnsureComp(mob); - grammar.ProperNoun = true; - grammar.Gender = gender; - Dirty(mob, grammar); - + UpdateGrammar(mob, gender); CleanupCloneComponents(mob, bodyToClone, forceOldProfile, clonePodComp.DoMetempsychosis); UpdateHungerAndThirst(mob, clonePodComp); diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index c8cb463ae22..249ff55a96a 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -79,7 +79,7 @@ public sealed partial class CloningPodComponent : Component public bool ActivelyCloning; /// - /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as 101 + the genetic damage of the body to be cloned. + /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as the body's crit threshold + 1 + the genetic damage of the body to be cloned. /// [DataField] public bool DoGeneticDamage = true; From a6687d6be32c0f0300ab06a3ce613a88c878ad4d Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 17:45:31 -0400 Subject: [PATCH 30/33] Update CloningSystem.cs --- Content.Server/Cloning/CloningSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index dfe18429c30..50e16d76598 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -278,7 +278,6 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(clone, out var cloneMindComp) - || cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt || mindEnt.Comp.UserId == null || !_playerManager.TryGetSessionById(mindEnt.Comp.UserId.Value, out var client)) From 0cad6d21b0a723be712b7c2eed22e75f327b24e8 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 1 Sep 2024 12:30:41 -0400 Subject: [PATCH 31/33] Split Cloning System into Utility --- .../Cloning/CloningSystem.Utility.cs | 357 ++++++++++++++++ Content.Server/Cloning/CloningSystem.cs | 382 +----------------- 2 files changed, 376 insertions(+), 363 deletions(-) create mode 100644 Content.Server/Cloning/CloningSystem.Utility.cs diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs new file mode 100644 index 00000000000..b80a2eec4f0 --- /dev/null +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -0,0 +1,357 @@ +using Content.Server.Cloning.Components; +using Content.Shared.Atmos; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Components; +using Content.Shared.Cloning; +using Content.Shared.Damage; +using Content.Shared.Emag.Components; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; +using Content.Shared.Speech; +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 Robust.Shared.GameObjects.Components.Localization; +using Content.Shared.SSDIndicator; +using Content.Shared.Damage.ForceSay; +using Content.Shared.Chat; +using Content.Server.Body.Components; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Language.Components; +using Content.Shared.Language; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Enums; + +namespace Content.Server.Cloning; + +public sealed partial class CloningSystem +{ + internal void TransferMindToClone(EntityUid mindId, MindComponent mind) + { + if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) + || !EntityManager.EntityExists(entity) + || !TryComp(entity, out var mindComp) + || mindComp.Mind != null) + return; + + _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); + _mindSystem.UnVisit(mindId, mind); + ClonesWaitingForMind.Remove(mind); + } + private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) + { + if (clonedComponent.Parent == EntityUid.Invalid + || !EntityManager.EntityExists(clonedComponent.Parent) + || !TryComp(clonedComponent.Parent, out var cloningPodComponent) + || uid != cloningPodComponent.BodyContainer.ContainedEntity) + { + EntityManager.RemoveComponent(uid); + return; + } + UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); + } + + /// + /// Test if the body to be cloned has any conditions that would prevent cloning from taking place. + /// Or, if the body has a particular reason to make cloning more difficult. + /// + private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) + { + var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); + RaiseLocalEvent(bodyToClone, ref ev); + cloningCostMultiplier = ev.CloningCostMultiplier; + + if (ev.Cancelled && ev.CloningFailMessage is not null) + { + _chatSystem.TrySendInGameICMessage(uid, + Loc.GetString(ev.CloningFailMessage), + InGameICChatType.Speak, false); + return false; + } + + return true; + } + + /// + /// Checks the body's physics component and any previously obtained modifiers to determine biomass cost. + /// If there is insufficient biomass, the cloning cannot start. + /// + private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) + { + var cloningCost = (int) Math.Round(physics.FixturesMass + * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) + * clonePod.BiomassCostMultiplier + * cloningCostMultiplier); + + if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) + { + _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); + return false; + } + + _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); + clonePod.UsedBiomass = cloningCost; + + return true; + } + + /// + /// Tests the original body for genetic damage, while returning the cloning damage for later damage. + /// The body's cellular damage is also used as a potential failure state, giving a chance for the cloning to fail immediately. + /// + private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) + { + geneticDamage = 0; + if (clonePod.DoMetempsychosis) + return false; + + if (TryComp(bodyToClone, out var damageable) + && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) + && clonePod.ConnectedConsole is not null) + { + geneticDamage += (float) cellularDmg; + var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); + chance *= failChanceModifier; + + if (cellularDmg > 0) + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); + + if (_random.Prob(chance)) + { + CauseCloningFail(uid, clonePod); + return true; + } + } + return false; + } + + /// + /// When this condition is called, it sets the cloning pod to its fail condition. + /// Such that when the cloning timer ends, the body that would be created, is turned into clone soup. + /// + private void CauseCloningFail(EntityUid uid, CloningPodComponent component) + { + UpdateStatus(uid, CloningPodStatus.Gore, component); + component.FailedClone = true; + component.ActivelyCloning = true; + } + + /// + /// This is the success condition for cloning. At the end of the timer, if nothing interrupted it, this function is called to finish the cloning by dispensing the body. + /// + private void Eject(EntityUid uid, CloningPodComponent? clonePod) + { + if (!Resolve(uid, ref clonePod) + || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity + || clonePod.CloningProgress < clonePod.CloningTime) + return; + + EntityManager.RemoveComponent(entity); + _containerSystem.Remove(entity, clonePod.BodyContainer); + clonePod.CloningProgress = 0f; + clonePod.UsedBiomass = 0; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + clonePod.ActivelyCloning = false; + } + + /// + /// And now we turn it over to Chef Pod to make soup! + /// + private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) + { + if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) + { + if (TryComp(entity, out var physics) + && TryComp(entity, out var bloodstream)) + MakeAHugeMess(uid, physics, bloodstream); + else MakeAHugeMess(uid); + + QueueDel(entity); + } + else MakeAHugeMess(uid); + + clonePod.FailedClone = false; + clonePod.CloningProgress = 0f; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + if (HasComp(uid)) + { + _audio.PlayPvs(clonePod.ScreamSound, uid); + Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); + } + + if (!HasComp(uid)) + _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); + + clonePod.UsedBiomass = 0; + clonePod.ActivelyCloning = false; + } + + /// + /// The body coming out of the machine isn't guaranteed to even be a Humanoid. + /// This function makes sure the body is "Human Playable", with no funny business. + /// + private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile, bool doMetempsychosis) + { + if (forceOldProfile + && TryComp(bodyToClone, out var psionic)) + { + var newPsionic = _serialization.CreateCopy(psionic, null, false, true); + AddComp(uid, newPsionic, true); + } + + if (TryComp(bodyToClone, out var oldKnowLangs)) + { + var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); + AddComp(uid, newKnowLangs, true); + } + + + if (TryComp(bodyToClone, out var oldSpeakLangs)) + { + var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); + AddComp(uid, newSpeakLangs, true); + } + + if (doMetempsychosis) + EnsureComp(uid); + + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + _tag.AddTag(uid, "DoorBumpOpener"); + } + + /// + /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. + /// + /// + /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA + /// + private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) + { + var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); + Solution bloodSolution = new(); + + tileMix?.AdjustMoles(Gas.Ammonia, 0.5f + * ((physics is not null) + ? physics.Mass + : 71)); + + bloodSolution.AddReagent("blood", 0.8f + * ((blood is not null) + ? blood.BloodMaxVolume + : 300)); + + _puddleSystem.TrySpillAt(uid, bloodSolution, out _); + } + + /// + /// Modify the clone's hunger and thirst values by an amount set in the cloningPod. + /// + private void UpdateHungerAndThirst(EntityUid uid, CloningPodComponent cloningPod) + { + if (cloningPod.HungerAdjustment != 0 + && TryComp(uid, out var hungerComponent)) + _hunger.SetHunger(uid, cloningPod.HungerAdjustment, hungerComponent); + + if (cloningPod.ThirstAdjustment != 0 + && TryComp(uid, out var thirstComponent)) + _thirst.SetThirst(uid, thirstComponent, cloningPod.ThirstAdjustment); + + if (cloningPod.DrunkTimer != 0) + _drunk.TryApplyDrunkenness(uid, cloningPod.DrunkTimer); + } + + /// + /// Updates the HumanoidAppearanceComponent of the clone. + /// If a species swap is occuring, this updates all relevant information as per server config. + /// + private void UpdateCloneAppearance( + EntityUid mob, + HumanoidCharacterProfile pref, + HumanoidAppearanceComponent humanoid, + List sexes, + Gender oldGender, + bool switchingSpecies, + bool forceOldProfile, + out Gender gender) + { + gender = oldGender; + if (!TryComp(mob, out var newHumanoid)) + return; + + if (switchingSpecies && !forceOldProfile) + { + var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); + var oldName = _serialization.CreateCopy(pref.Name, null, false, true); + + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); + + if (sexes.Contains(humanoid.Sex) + && _config.GetCVar(CCVars.CloningPreserveSex)) + pref = pref.WithSex(humanoid.Sex); + + if (_config.GetCVar(CCVars.CloningPreserveGender)) + pref = pref.WithGender(humanoid.Gender); + else gender = humanoid.Gender; + + if (_config.GetCVar(CCVars.CloningPreserveAge)) + pref = pref.WithAge(humanoid.Age); + + if (_config.GetCVar(CCVars.CloningPreserveHeight)) + pref = pref.WithHeight(humanoid.Height); + + if (_config.GetCVar(CCVars.CloningPreserveWidth)) + pref = pref.WithWidth(humanoid.Width); + + if (_config.GetCVar(CCVars.CloningPreserveName)) + pref = pref.WithName(oldName); + + if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) + pref = pref.WithFlavorText(flavorText); + + _humanoidSystem.LoadProfile(mob, pref); + } + } + + /// + /// Optionally makes sure that pronoun preferences are preserved by the clone. + /// Although handled here, the swap(if it occurs) happens during UpdateCloneAppearance. + /// + /// + /// + private void UpdateGrammar(EntityUid mob, Gender gender) + { + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = gender; + Dirty(mob, grammar); + } + + /// + /// Optionally puts the clone in crit with high Cellular damage. + /// Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. + /// + private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, float geneticDamage) + { + if (!clonePodComp.DoGeneticDamage + || !HasComp(mob) + || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) + return; + + DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), (int) threshold + 1 + geneticDamage); + _damageable.TryChangeDamage(mob, damage, true); + } +} \ No newline at end of file diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 50e16d76598..4a1424ae00f 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -9,13 +9,9 @@ using Content.Server.Materials; using Content.Server.Popups; using Content.Server.Power.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.CCVar; -using Content.Shared.Chemistry.Components; using Content.Shared.Cloning; using Content.Shared.Damage; using Content.Shared.DeviceLinking.Events; -using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.GameTicking; @@ -34,37 +30,21 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Shared.Speech; 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.Shared.Humanoid.Prototypes; -using Robust.Shared.GameObjects.Components.Localization; -using Content.Shared.SSDIndicator; -using Content.Shared.Damage.ForceSay; -using Content.Shared.Chat; -using Content.Server.Body.Components; using Content.Shared.Random.Helpers; using Content.Shared.Contests; -using Content.Shared.Abilities.Psionics; using Robust.Shared.Serialization.Manager; -using Content.Shared.Language.Components; -using Content.Shared.Language; using Robust.Shared.Utility; using Timer = Robust.Shared.Timing.Timer; using Content.Server.Power.Components; -using Content.Shared.Damage.Prototypes; using Content.Shared.Drunk; -using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; -using Robust.Shared.Enums; namespace Content.Server.Cloning; -public sealed class CloningSystem : EntitySystem +public sealed partial class CloningSystem : EntitySystem { [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; [Dependency] private readonly IPlayerManager _playerManager = null!; @@ -112,45 +92,6 @@ public override void Initialize() SubscribeLocalEvent(OnPowerChanged); } - private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) - { - clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); - _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); - } - - private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) - { - if (!args.Powered - && component.ActivelyCloning) - CauseCloningFail(uid, component); - } - - internal void TransferMindToClone(EntityUid mindId, MindComponent mind) - { - if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) - || !EntityManager.EntityExists(entity) - || !TryComp(entity, out var mindComp) - || mindComp.Mind != null) - return; - - _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); - _mindSystem.UnVisit(mindId, mind); - ClonesWaitingForMind.Remove(mind); - } - - private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) - { - if (clonedComponent.Parent == EntityUid.Invalid - || !EntityManager.EntityExists(clonedComponent.Parent) - || !TryComp(clonedComponent.Parent, out var cloningPodComponent) - || uid != cloningPodComponent.BodyContainer.ContainedEntity) - { - EntityManager.RemoveComponent(uid); - return; - } - UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); - } - private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) { pod.ConnectedConsole = null; @@ -178,90 +119,38 @@ private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEv args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); } - - /// - /// Test if the body to be cloned has any conditions that would prevent cloning from taking place. - /// Or, if the body has a particular reason to make cloning more difficult. - /// - private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) + private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) { - var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); - RaiseLocalEvent(bodyToClone, ref ev); - cloningCostMultiplier = ev.CloningCostMultiplier; - - if (ev.Cancelled && ev.CloningFailMessage is not null) - { - _chatSystem.TrySendInGameICMessage(uid, - Loc.GetString(ev.CloningFailMessage), - InGameICChatType.Speak, false); - return false; - } - - return true; + clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); + _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); } - /// - /// Checks the body's physics component and any previously obtained modifiers to determine biomass cost. - /// If there is insufficient biomass, the cloning cannot start. - /// - private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) + private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) { - var cloningCost = (int) Math.Round(physics.FixturesMass - * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) - * clonePod.BiomassCostMultiplier - * cloningCostMultiplier); - - if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) - { - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); - return false; - } - - _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); - clonePod.UsedBiomass = cloningCost; - - return true; + if (!args.Powered + && component.ActivelyCloning) + CauseCloningFail(uid, component); } /// - /// Tests the original body for genetic damage, while returning the cloning damage for later damage. - /// The body's cellular damage is also used as a potential failure state, giving a chance for the cloning to fail immediately. + /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. /// - private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) + private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) { - geneticDamage = 0; - if (clonePod.DoMetempsychosis) - return false; - - if (TryComp(bodyToClone, out var damageable) - && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) - && clonePod.ConnectedConsole is not null) - { - geneticDamage += (float) cellularDmg; - var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); - chance *= failChanceModifier; + if (!this.IsPowered(uid, EntityManager)) + return; - if (cellularDmg > 0) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); + if (clonePod.ActivelyCloning) + CauseCloningFail(uid, clonePod); - if (_random.Prob(chance)) - { - CauseCloningFail(uid, clonePod); - return true; - } - } - return false; + _audio.PlayPvs(clonePod.SparkSound, uid); + _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); + args.Handled = true; } - /// - /// When this condition is called, it sets the cloning pod to its fail condition. - /// Such that when the cloning timer ends, the body that would be created, is turned into clone soup. - /// - private void CauseCloningFail(EntityUid uid, CloningPodComponent component) + private void Reset(RoundRestartCleanupEvent ev) { - UpdateStatus(uid, CloningPodStatus.Gore, component); - component.FailedClone = true; - component.ActivelyCloning = true; + ClonesWaitingForMind.Clear(); } /// @@ -354,235 +243,6 @@ public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodCo _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); } - /// - /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. - /// - private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) - { - if (!this.IsPowered(uid, EntityManager)) - return; - - if (clonePod.ActivelyCloning) - CauseCloningFail(uid, clonePod); - - _audio.PlayPvs(clonePod.SparkSound, uid); - _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); - args.Handled = true; - } - - /// - /// This is the success condition for cloning. At the end of the timer, if nothing interrupted it, this function is called to finish the cloning by dispensing the body. - /// - private void Eject(EntityUid uid, CloningPodComponent? clonePod) - { - if (!Resolve(uid, ref clonePod) - || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity - || clonePod.CloningProgress < clonePod.CloningTime) - return; - - EntityManager.RemoveComponent(entity); - _containerSystem.Remove(entity, clonePod.BodyContainer); - clonePod.CloningProgress = 0f; - clonePod.UsedBiomass = 0; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - clonePod.ActivelyCloning = false; - } - - /// - /// And now we turn it over to Chef Pod to make soup! - /// - private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) - { - if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) - { - if (TryComp(entity, out var physics) - && TryComp(entity, out var bloodstream)) - MakeAHugeMess(uid, physics, bloodstream); - else MakeAHugeMess(uid); - - QueueDel(entity); - } - else MakeAHugeMess(uid); - - clonePod.FailedClone = false; - clonePod.CloningProgress = 0f; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - if (HasComp(uid)) - { - _audio.PlayPvs(clonePod.ScreamSound, uid); - Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); - } - - if (!HasComp(uid)) - _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); - - clonePod.UsedBiomass = 0; - clonePod.ActivelyCloning = false; - } - - /// - /// The body coming out of the machine isn't guaranteed to even be a Humanoid. - /// This function makes sure the body is "Human Playable", with no funny business. - /// - private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile, bool doMetempsychosis) - { - if (forceOldProfile - && TryComp(bodyToClone, out var psionic)) - { - var newPsionic = _serialization.CreateCopy(psionic, null, false, true); - AddComp(uid, newPsionic, true); - } - - if (TryComp(bodyToClone, out var oldKnowLangs)) - { - var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); - AddComp(uid, newKnowLangs, true); - } - - - if (TryComp(bodyToClone, out var oldSpeakLangs)) - { - var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); - AddComp(uid, newSpeakLangs, true); - } - - if (doMetempsychosis) - EnsureComp(uid); - - EnsureComp(uid); - EnsureComp(uid); - EnsureComp(uid); - EnsureComp(uid); - EnsureComp(uid); - RemComp(uid); - RemComp(uid); - RemComp(uid); - RemComp(uid); - _tag.AddTag(uid, "DoorBumpOpener"); - } - - /// - /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. - /// - /// - /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA - /// - private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) - { - var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); - Solution bloodSolution = new(); - - tileMix?.AdjustMoles(Gas.Ammonia, 0.5f - * ((physics is not null) - ? physics.Mass - : 71)); - - bloodSolution.AddReagent("blood", 0.8f - * ((blood is not null) - ? blood.BloodMaxVolume - : 300)); - - _puddleSystem.TrySpillAt(uid, bloodSolution, out _); - } - - /// - /// Modify the clone's hunger and thirst values by an amount set in the cloningPod. - /// - private void UpdateHungerAndThirst(EntityUid uid, CloningPodComponent cloningPod) - { - if (cloningPod.HungerAdjustment != 0 - && TryComp(uid, out var hungerComponent)) - _hunger.SetHunger(uid, cloningPod.HungerAdjustment, hungerComponent); - - if (cloningPod.ThirstAdjustment != 0 - && TryComp(uid, out var thirstComponent)) - _thirst.SetThirst(uid, thirstComponent, cloningPod.ThirstAdjustment); - - if (cloningPod.DrunkTimer != 0) - _drunk.TryApplyDrunkenness(uid, cloningPod.DrunkTimer); - } - - /// - /// Updates the HumanoidAppearanceComponent of the clone. - /// If a species swap is occuring, this updates all relevant information as per server config. - /// - private void UpdateCloneAppearance( - EntityUid mob, - HumanoidCharacterProfile pref, - HumanoidAppearanceComponent humanoid, - List sexes, - Gender oldGender, - bool switchingSpecies, - bool forceOldProfile, - out Gender gender) - { - gender = oldGender; - if (!TryComp(mob, out var newHumanoid)) - return; - - if (switchingSpecies && !forceOldProfile) - { - var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); - var oldName = _serialization.CreateCopy(pref.Name, null, false, true); - - pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); - - if (sexes.Contains(humanoid.Sex) - && _config.GetCVar(CCVars.CloningPreserveSex)) - pref = pref.WithSex(humanoid.Sex); - - if (_config.GetCVar(CCVars.CloningPreserveGender)) - pref = pref.WithGender(humanoid.Gender); - else gender = humanoid.Gender; - - if (_config.GetCVar(CCVars.CloningPreserveAge)) - pref = pref.WithAge(humanoid.Age); - - if (_config.GetCVar(CCVars.CloningPreserveHeight)) - pref = pref.WithHeight(humanoid.Height); - - if (_config.GetCVar(CCVars.CloningPreserveWidth)) - pref = pref.WithWidth(humanoid.Width); - - if (_config.GetCVar(CCVars.CloningPreserveName)) - pref = pref.WithName(oldName); - - if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) - pref = pref.WithFlavorText(flavorText); - - _humanoidSystem.LoadProfile(mob, pref); - } - } - - /// - /// Optionally makes sure that pronoun preferences are preserved by the clone. - /// Although handled here, the swap(if it occurs) happens during UpdateCloneAppearance. - /// - /// - /// - private void UpdateGrammar(EntityUid mob, Gender gender) - { - var grammar = EnsureComp(mob); - grammar.ProperNoun = true; - grammar.Gender = gender; - Dirty(mob, grammar); - } - - /// - /// Optionally puts the clone in crit with high Cellular damage. - /// Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. - /// - private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, float geneticDamage) - { - if (!clonePodComp.DoGeneticDamage - || !HasComp(mob) - || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) - return; - - DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), (int) threshold + 1 + geneticDamage); - _damageable.TryChangeDamage(mob, damage, true); - } - /// /// This function handles the Clone vs. Metem logic, as well as creation of the new body. /// @@ -638,10 +298,6 @@ private EntityUid FetchAndSpawnMob return mob; } - public void Reset(RoundRestartCleanupEvent ev) - { - ClonesWaitingForMind.Clear(); - } public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, SpeciesPrototype oldSpecies, int karma, out SpeciesPrototype? species, out bool changeProfile) { From a5aabbb2c64560533fdc687ae2c9599dcb73b8fc Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 1 Sep 2024 19:46:27 -0400 Subject: [PATCH 32/33] Apply suggestions from code review Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> Signed-off-by: VMSolidus --- .../Cloning/CloningSystem.Utility.cs | 4 ++-- Content.Server/Cloning/CloningSystem.cs | 24 +++++++++---------- .../Traits/Assorted/UncloneableSystem.cs | 2 +- Content.Shared/Cloning/CloningPodComponent.cs | 24 +++++++++---------- .../Cloning/CloningSystem.Events.cs | 6 +++-- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs index b80a2eec4f0..47371b87090 100644 --- a/Content.Server/Cloning/CloningSystem.Utility.cs +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -328,7 +328,7 @@ private void UpdateCloneAppearance( /// /// Optionally makes sure that pronoun preferences are preserved by the clone. - /// Although handled here, the swap(if it occurs) happens during UpdateCloneAppearance. + /// Although handled here, the swap (if it occurs) happens during UpdateCloneAppearance. /// /// /// @@ -354,4 +354,4 @@ private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), (int) threshold + 1 + geneticDamage); _damageable.TryChangeDamage(mob, damage, true); } -} \ No newline at end of file +} diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 4a1424ae00f..b3112ed466a 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -127,8 +127,7 @@ private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, Compon private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) { - if (!args.Powered - && component.ActivelyCloning) + if (!args.Powered && component.ActivelyCloning) CauseCloningFail(uid, component); } @@ -246,14 +245,15 @@ public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodCo /// /// This function handles the Clone vs. Metem logic, as well as creation of the new body. /// - private EntityUid FetchAndSpawnMob - (EntityUid clonePod, - CloningPodComponent clonePodComp, - HumanoidCharacterProfile pref, - SpeciesPrototype speciesPrototype, - HumanoidAppearanceComponent humanoid, - EntityUid bodyToClone, - float geneticDamage) + private EntityUid FetchAndSpawnMob( + EntityUid clonePod, + CloningPodComponent clonePodComp, + HumanoidCharacterProfile pref, + SpeciesPrototype speciesPrototype, + HumanoidAppearanceComponent humanoid, + EntityUid bodyToClone, + float geneticDamage + ) { List sexes = new(); bool switchingSpecies = false; @@ -319,8 +319,8 @@ public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, S RaiseLocalEvent(oldBody, ref ev); chance = ev.OverrideChance - ? ev.ReincarnationChances - : chance * ev.ReincarnationChanceModifier; + ? ev.ReincarnationChances + : chance * ev.ReincarnationChanceModifier; switch (ev.ForcedType) { diff --git a/Content.Server/Traits/Assorted/UncloneableSystem.cs b/Content.Server/Traits/Assorted/UncloneableSystem.cs index 3c75d640c47..6f2af106472 100644 --- a/Content.Server/Traits/Assorted/UncloneableSystem.cs +++ b/Content.Server/Traits/Assorted/UncloneableSystem.cs @@ -20,4 +20,4 @@ private void OnAttemptCloning(EntityUid uid, UncloneableComponent component, ref args.Cancelled = true; } } -} \ No newline at end of file +} diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 249ff55a96a..082b92e8b14 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -18,7 +18,7 @@ public sealed partial class CloningPodComponent : Component public ContainerSlot BodyContainer = default!; /// - /// How long the cloning has been going on for. + /// How long the cloning has been going on for /// [ViewVariables] public float CloningProgress = 0; @@ -33,7 +33,7 @@ public sealed partial class CloningPodComponent : Component public bool FailedClone = false; /// - /// The material that is used to clone entities. + /// The material that is used to clone entities /// [DataField] public ProtoId RequiredMaterial = "Biomass"; @@ -51,7 +51,7 @@ public sealed partial class CloningPodComponent : Component public EntProtoId MobSpawnId = "MobAbomination"; /// - /// Emag sound effects. + /// Emag sound effects /// [DataField] public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks") @@ -73,31 +73,31 @@ public sealed partial class CloningPodComponent : Component public EntityUid? ConnectedConsole; /// - /// Tracks whether a Cloner is actively cloning someone. + /// Tracks whether a Cloner is actively cloning someone /// [ViewVariables(VVAccess.ReadWrite)] public bool ActivelyCloning; /// - /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as the body's crit threshold + 1 + the genetic damage of the body to be cloned. + /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as the body's crit threshold + 1 + the genetic damage of the body to be cloned /// [DataField] public bool DoGeneticDamage = true; /// - /// How much should the cloning pod adjust the hunger of an entity by. + /// How much should the cloning pod adjust the hunger of an entity by /// [DataField] public float HungerAdjustment = 50; /// - /// How much should the cloning pod adjust the thirst of an entity by. + /// How much should the cloning pod adjust the thirst of an entity by /// [DataField] public float ThirstAdjustment = 50; /// - /// How much time should the cloning pod give an entity the durnk condition, in seconds. + /// How much time should the cloning pod give an entity the durnk condition, in seconds /// [DataField] public float DrunkTimer = 300; @@ -116,22 +116,22 @@ public sealed partial class CloningPodComponent : Component public bool DoMetempsychosis; /// - /// How much should each point of Karma decrease the odds of reincarnating as a humanoid. + /// How much should each point of Karma decrease the odds of reincarnating as a humanoid /// [DataField] public float KarmaOffset = 0.5f; /// /// The base chances for a Metem Machine to produce a Humanoid. - /// > 1 has a chance of acting like a true Cloner + /// > 1 has a chance of acting like a true Cloner. /// On a successful roll, produces a random Humanoid. - /// A failed roll poduces a random NonHumanoid + /// A failed roll poduces a random NonHumanoid. /// [DataField] public float HumanoidBaseChance = 1; /// - /// The proto that the Metem Machine picks a random Humanoid from. + /// The proto that the Metem Machine picks a random Humanoid from /// [ValidatePrototypeId] [DataField] diff --git a/Content.Shared/Cloning/CloningSystem.Events.cs b/Content.Shared/Cloning/CloningSystem.Events.cs index 1784fb884d2..a29310d45b9 100644 --- a/Content.Shared/Cloning/CloningSystem.Events.cs +++ b/Content.Shared/Cloning/CloningSystem.Events.cs @@ -1,4 +1,5 @@ namespace Content.Shared.Cloning; + /// /// Raised after a new mob got spawned when cloning a humanoid /// @@ -18,7 +19,8 @@ public CloningEvent(EntityUid source, EntityUid target) } /// -/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). Allowing for innate effects from the mob to influence the reincarnation. +/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). +/// Allowing for innate effects from the mob to influence the reincarnation. /// [ByRefEvent] public struct ReincarnatingEvent @@ -38,7 +40,7 @@ public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) /// /// Raised on a corpse that someone is attempting to clone, but before the process actually begins. -/// ALlows for Entities to influence whether the cloning can begin in the first place, either by canceling it, or modifying the cost. +/// Allows for Entities to influence whether the cloning can begin in the first place, either by canceling it, or modifying the cost. /// [ByRefEvent] public struct AttemptCloningEvent From de49b2bb63d62c4c262a02aa0bb43b9571cb3562 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 1 Sep 2024 22:04:19 -0400 Subject: [PATCH 33/33] Bunch of fixes --- .../Cloning/CloningConsoleSystem.cs | 3 ++- .../Cloning/CloningSystem.Utility.cs | 17 +++++++----- Content.Server/Cloning/CloningSystem.cs | 27 +++++++++++++------ .../Medical/MedicalScannerSystem.cs | 6 ++--- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index d7893004793..524cbe80e48 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -153,7 +153,8 @@ public void UpdateUserInterface(EntityUid consoleUid, CloningConsoleComponent co public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent cloningPod, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) { - if (!Resolve(uid, ref consoleComponent, ref scannerComp) + if (!Resolve(uid, ref consoleComponent) + || !Resolve(scannerUid, ref scannerComp) || !Transform(cloningPodUid).Anchored || !Transform(scannerUid).Anchored || !consoleComponent.CloningPodInRange diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs index 47371b87090..408e1cf24a3 100644 --- a/Content.Server/Cloning/CloningSystem.Utility.cs +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -24,7 +24,6 @@ using Content.Shared.Abilities.Psionics; using Content.Shared.Language.Components; using Content.Shared.Language; -using Content.Shared.Damage.Prototypes; using Content.Shared.Nutrition.Components; using Robust.Shared.Enums; @@ -84,6 +83,9 @@ private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodCo /// private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) { + if (clonePod.ConnectedConsole is null) + return false; + var cloningCost = (int) Math.Round(physics.FixturesMass * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) * clonePod.BiomassCostMultiplier @@ -91,7 +93,7 @@ private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPo if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) { - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); return false; } @@ -148,10 +150,10 @@ private void CauseCloningFail(EntityUid uid, CloningPodComponent component) private void Eject(EntityUid uid, CloningPodComponent? clonePod) { if (!Resolve(uid, ref clonePod) - || clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity - || clonePod.CloningProgress < clonePod.CloningTime) + || clonePod.BodyContainer.ContainedEntity is null) return; + var entity = clonePod.BodyContainer.ContainedEntity.Value; EntityManager.RemoveComponent(entity); _containerSystem.Remove(entity, clonePod.BodyContainer); clonePod.CloningProgress = 0f; @@ -165,8 +167,9 @@ private void Eject(EntityUid uid, CloningPodComponent? clonePod) /// private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) { - if (clonePod.BodyContainer.ContainedEntity is { Valid: true } entity) + if (clonePod.BodyContainer.ContainedEntity is not null) { + var entity = clonePod.BodyContainer.ContainedEntity.Value; if (TryComp(entity, out var physics) && TryComp(entity, out var bloodstream)) MakeAHugeMess(uid, physics, bloodstream); @@ -350,8 +353,8 @@ private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, || !HasComp(mob) || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) return; - - DamageSpecifier damage = new(_prototypeManager.Index("Cellular"), (int) threshold + 1 + geneticDamage); + DamageSpecifier damage = new(); + damage.DamageDict.Add("Cellular", (int) threshold + 1 + geneticDamage); _damageable.TryChangeDamage(mob, damage, true); } } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index b3112ed466a..7931fae4778 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -162,16 +162,26 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(bodyToClone, out var humanoid) - || !TryComp(bodyToClone, out var physics) - || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier) - || !ClonesWaitingForMind.TryGetValue(mindEnt.Comp, out var clone) - || !TryComp(clone, out var cloneMindComp) - || cloneMindComp.Mind == mindEnt - || mindEnt.Comp.UserId == null - || !_playerManager.TryGetSessionById(mindEnt.Comp.UserId.Value, out var client)) + || !TryComp(bodyToClone, out var physics)) return false; - ClonesWaitingForMind.Remove(mindEnt.Comp); + var mind = mindEnt.Comp; + if (ClonesWaitingForMind.TryGetValue(mind, out var clone)) + { + if (EntityManager.EntityExists(clone) && + !_mobStateSystem.IsDead(clone) && + TryComp(clone, out var cloneMindComp) && + (cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt)) + return false; // Mind already has clone + + ClonesWaitingForMind.Remove(mind); + } + + if (mind.OwnedEntity != null && !_mobStateSystem.IsDead(mind.OwnedEntity.Value) + || mind.UserId == null + || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client) + || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier)) + return false; // Special handling for humanoid data related to metempsychosis. This function is needed for Paradox Anomaly code to play nice with reincarnated people var pref = humanoid.LastProfileLoaded; @@ -268,6 +278,7 @@ float geneticDamage { toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); forceOldProfile = !changeProfile; + oldKarma++; if (changeProfile) geneticDamage = 0; diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 5a7afa770f5..a6ce43c4081 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -161,13 +161,13 @@ private MedicalScannerStatus GetStatus(EntityUid uid, MedicalScannerComponent sc if (!TryComp(body.Value, out var state)) return MedicalScannerStatus.Yellow; - if (_mobStateSystem.IsAlive(uid, state)) + if (_mobStateSystem.IsAlive(body.Value, state)) return MedicalScannerStatus.Green; - if (_mobStateSystem.IsCritical(uid, state)) + if (_mobStateSystem.IsCritical(body.Value, state)) return MedicalScannerStatus.Red; - if (_mobStateSystem.IsDead(uid, state)) + if (_mobStateSystem.IsDead(body.Value, state)) return MedicalScannerStatus.Death; return MedicalScannerStatus.Yellow;