CloningPodSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. using Content.Server.Atmos.EntitySystems;
  2. using Content.Server.Chat.Systems;
  3. using Content.Server.Cloning.Components;
  4. using Content.Server.DeviceLinking.Systems;
  5. using Content.Server.EUI;
  6. using Content.Server.Fluids.EntitySystems;
  7. using Content.Server.Materials;
  8. using Content.Server.Popups;
  9. using Content.Server.Power.EntitySystems;
  10. using Content.Shared.Atmos;
  11. using Content.Shared.CCVar;
  12. using Content.Shared.Chemistry.Components;
  13. using Content.Shared.Cloning;
  14. using Content.Shared.Damage;
  15. using Content.Shared.DeviceLinking.Events;
  16. using Content.Shared.Emag.Components;
  17. using Content.Shared.Emag.Systems;
  18. using Content.Shared.Examine;
  19. using Content.Shared.GameTicking;
  20. using Content.Shared.Mind;
  21. using Content.Shared.Mind.Components;
  22. using Content.Shared.Mobs.Systems;
  23. using Robust.Server.Containers;
  24. using Robust.Server.Player;
  25. using Robust.Shared.Audio.Systems;
  26. using Robust.Shared.Configuration;
  27. using Robust.Shared.Containers;
  28. using Robust.Shared.Physics.Components;
  29. using Robust.Shared.Prototypes;
  30. using Robust.Shared.Random;
  31. namespace Content.Server.Cloning;
  32. public sealed class CloningPodSystem : EntitySystem
  33. {
  34. [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
  35. [Dependency] private readonly IPlayerManager _playerManager = null!;
  36. [Dependency] private readonly EuiManager _euiManager = null!;
  37. [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!;
  38. [Dependency] private readonly ContainerSystem _containerSystem = default!;
  39. [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
  40. [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
  41. [Dependency] private readonly IRobustRandom _robustRandom = default!;
  42. [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
  43. [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
  44. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  45. [Dependency] private readonly PuddleSystem _puddleSystem = default!;
  46. [Dependency] private readonly ChatSystem _chatSystem = default!;
  47. [Dependency] private readonly SharedAudioSystem _audio = default!;
  48. [Dependency] private readonly IConfigurationManager _configManager = default!;
  49. [Dependency] private readonly MaterialStorageSystem _material = default!;
  50. [Dependency] private readonly PopupSystem _popupSystem = default!;
  51. [Dependency] private readonly SharedMindSystem _mindSystem = default!;
  52. [Dependency] private readonly CloningSystem _cloning = default!;
  53. [Dependency] private readonly EmagSystem _emag = default!;
  54. public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new();
  55. public readonly ProtoId<CloningSettingsPrototype> SettingsId = "CloningPod";
  56. public const float EasyModeCloningCost = 0.7f;
  57. public override void Initialize()
  58. {
  59. base.Initialize();
  60. SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
  61. SubscribeLocalEvent<BeingClonedComponent, MindAddedMessage>(HandleMindAdded);
  62. SubscribeLocalEvent<CloningPodComponent, ComponentInit>(OnComponentInit);
  63. SubscribeLocalEvent<CloningPodComponent, PortDisconnectedEvent>(OnPortDisconnected);
  64. SubscribeLocalEvent<CloningPodComponent, AnchorStateChangedEvent>(OnAnchor);
  65. SubscribeLocalEvent<CloningPodComponent, ExaminedEvent>(OnExamined);
  66. SubscribeLocalEvent<CloningPodComponent, GotEmaggedEvent>(OnEmagged);
  67. }
  68. private void OnComponentInit(Entity<CloningPodComponent> ent, ref ComponentInit args)
  69. {
  70. ent.Comp.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(ent.Owner, "clonepod-bodyContainer");
  71. _signalSystem.EnsureSinkPorts(ent.Owner, ent.Comp.PodPort);
  72. }
  73. internal void TransferMindToClone(EntityUid mindId, MindComponent mind)
  74. {
  75. if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) ||
  76. !EntityManager.EntityExists(entity) ||
  77. !TryComp<MindContainerComponent>(entity, out var mindComp) ||
  78. mindComp.Mind != null)
  79. return;
  80. _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind);
  81. _mindSystem.UnVisit(mindId, mind);
  82. ClonesWaitingForMind.Remove(mind);
  83. }
  84. private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message)
  85. {
  86. if (clonedComponent.Parent == EntityUid.Invalid ||
  87. !EntityManager.EntityExists(clonedComponent.Parent) ||
  88. !TryComp<CloningPodComponent>(clonedComponent.Parent, out var cloningPodComponent) ||
  89. uid != cloningPodComponent.BodyContainer.ContainedEntity)
  90. {
  91. EntityManager.RemoveComponent<BeingClonedComponent>(uid);
  92. return;
  93. }
  94. UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent);
  95. }
  96. private void OnPortDisconnected(Entity<CloningPodComponent> ent, ref PortDisconnectedEvent args)
  97. {
  98. ent.Comp.ConnectedConsole = null;
  99. }
  100. private void OnAnchor(Entity<CloningPodComponent> ent, ref AnchorStateChangedEvent args)
  101. {
  102. if (ent.Comp.ConnectedConsole == null || !TryComp<CloningConsoleComponent>(ent.Comp.ConnectedConsole, out var console))
  103. return;
  104. if (args.Anchored)
  105. {
  106. _cloningConsoleSystem.RecheckConnections(ent.Comp.ConnectedConsole.Value, ent.Owner, console.GeneticScanner, console);
  107. return;
  108. }
  109. _cloningConsoleSystem.UpdateUserInterface(ent.Comp.ConnectedConsole.Value, console);
  110. }
  111. private void OnExamined(Entity<CloningPodComponent> ent, ref ExaminedEvent args)
  112. {
  113. if (!args.IsInDetailsRange || !_powerReceiverSystem.IsPowered(ent.Owner))
  114. return;
  115. args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(ent.Owner, ent.Comp.RequiredMaterial))));
  116. }
  117. public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity<MindComponent> mindEnt, CloningPodComponent? clonePod, float failChanceModifier = 1)
  118. {
  119. if (!Resolve(uid, ref clonePod))
  120. return false;
  121. if (HasComp<ActiveCloningPodComponent>(uid))
  122. return false;
  123. var mind = mindEnt.Comp;
  124. if (ClonesWaitingForMind.TryGetValue(mind, out var clone))
  125. {
  126. if (EntityManager.EntityExists(clone) &&
  127. !_mobStateSystem.IsDead(clone) &&
  128. TryComp<MindContainerComponent>(clone, out var cloneMindComp) &&
  129. (cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt))
  130. return false; // Mind already has clone
  131. ClonesWaitingForMind.Remove(mind);
  132. }
  133. if (mind.OwnedEntity != null && !_mobStateSystem.IsDead(mind.OwnedEntity.Value))
  134. return false; // Body controlled by mind is not dead
  135. // Yes, we still need to track down the client because we need to open the Eui
  136. if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client))
  137. return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad.
  138. if (!TryComp<PhysicsComponent>(bodyToClone, out var physics))
  139. return false;
  140. var cloningCost = (int)Math.Round(physics.FixturesMass);
  141. if (_configManager.GetCVar(CCVars.BiomassEasyMode))
  142. cloningCost = (int)Math.Round(cloningCost * EasyModeCloningCost);
  143. // biomass checks
  144. var biomassAmount = _material.GetMaterialAmount(uid, clonePod.RequiredMaterial);
  145. if (biomassAmount < cloningCost)
  146. {
  147. if (clonePod.ConnectedConsole != null)
  148. _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false);
  149. return false;
  150. }
  151. // end of biomass checks
  152. // genetic damage checks
  153. if (TryComp<DamageableComponent>(bodyToClone, out var damageable) &&
  154. damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg))
  155. {
  156. var chance = Math.Clamp((float)(cellularDmg / 100), 0, 1);
  157. chance *= failChanceModifier;
  158. if (cellularDmg > 0 && clonePod.ConnectedConsole != null)
  159. _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false);
  160. if (_robustRandom.Prob(chance))
  161. {
  162. clonePod.FailedClone = true;
  163. UpdateStatus(uid, CloningPodStatus.Gore, clonePod);
  164. AddComp<ActiveCloningPodComponent>(uid);
  165. _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost);
  166. clonePod.UsedBiomass = cloningCost;
  167. return true;
  168. }
  169. }
  170. // end of genetic damage checks
  171. if (!_cloning.TryCloning(bodyToClone, _transformSystem.GetMapCoordinates(bodyToClone), SettingsId, out var mob)) // spawn a new body
  172. {
  173. if (clonePod.ConnectedConsole != null)
  174. _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-uncloneable-trait-error"), InGameICChatType.Speak, false);
  175. return false;
  176. }
  177. var cloneMindReturn = EntityManager.AddComponent<BeingClonedComponent>(mob.Value);
  178. cloneMindReturn.Mind = mind;
  179. cloneMindReturn.Parent = uid;
  180. _containerSystem.Insert(mob.Value, clonePod.BodyContainer);
  181. ClonesWaitingForMind.Add(mind, mob.Value);
  182. _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mind, this), client);
  183. UpdateStatus(uid, CloningPodStatus.NoMind, clonePod);
  184. AddComp<ActiveCloningPodComponent>(uid);
  185. _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost);
  186. clonePod.UsedBiomass = cloningCost;
  187. return true;
  188. }
  189. public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod)
  190. {
  191. cloningPod.Status = status;
  192. _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status);
  193. }
  194. public override void Update(float frameTime)
  195. {
  196. var query = EntityQueryEnumerator<ActiveCloningPodComponent, CloningPodComponent>();
  197. while (query.MoveNext(out var uid, out var _, out var cloning))
  198. {
  199. if (!_powerReceiverSystem.IsPowered(uid))
  200. continue;
  201. if (cloning.BodyContainer.ContainedEntity == null && !cloning.FailedClone)
  202. continue;
  203. cloning.CloningProgress += frameTime;
  204. if (cloning.CloningProgress < cloning.CloningTime)
  205. continue;
  206. if (cloning.FailedClone)
  207. EndFailedCloning(uid, cloning);
  208. else
  209. Eject(uid, cloning);
  210. }
  211. }
  212. /// <summary>
  213. /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew.
  214. /// </summary>
  215. private void OnEmagged(Entity<CloningPodComponent> ent, ref GotEmaggedEvent args)
  216. {
  217. if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
  218. return;
  219. if (_emag.CheckFlag(ent.Owner, EmagType.Interaction))
  220. return;
  221. if (!this.IsPowered(ent.Owner, EntityManager))
  222. return;
  223. _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), ent.Owner);
  224. args.Handled = true;
  225. }
  226. public void Eject(EntityUid uid, CloningPodComponent? clonePod)
  227. {
  228. if (!Resolve(uid, ref clonePod))
  229. return;
  230. if (clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity || clonePod.CloningProgress < clonePod.CloningTime)
  231. return;
  232. EntityManager.RemoveComponent<BeingClonedComponent>(entity);
  233. _containerSystem.Remove(entity, clonePod.BodyContainer);
  234. clonePod.CloningProgress = 0f;
  235. clonePod.UsedBiomass = 0;
  236. UpdateStatus(uid, CloningPodStatus.Idle, clonePod);
  237. RemCompDeferred<ActiveCloningPodComponent>(uid);
  238. }
  239. private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod)
  240. {
  241. clonePod.FailedClone = false;
  242. clonePod.CloningProgress = 0f;
  243. UpdateStatus(uid, CloningPodStatus.Idle, clonePod);
  244. var transform = Transform(uid);
  245. var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform));
  246. var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
  247. if (HasComp<EmaggedComponent>(uid))
  248. {
  249. _audio.PlayPvs(clonePod.ScreamSound, uid);
  250. Spawn(clonePod.MobSpawnId, transform.Coordinates);
  251. }
  252. Solution bloodSolution = new();
  253. var i = 0;
  254. while (i < 1)
  255. {
  256. tileMix?.AdjustMoles(Gas.Ammonia, 6f);
  257. bloodSolution.AddReagent("Blood", 50);
  258. if (_robustRandom.Prob(0.2f))
  259. i++;
  260. }
  261. _puddleSystem.TrySpillAt(uid, bloodSolution, out _);
  262. if (!HasComp<EmaggedComponent>(uid))
  263. {
  264. _material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int)(clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
  265. }
  266. clonePod.UsedBiomass = 0;
  267. RemCompDeferred<ActiveCloningPodComponent>(uid);
  268. }
  269. public void Reset(RoundRestartCleanupEvent ev)
  270. {
  271. ClonesWaitingForMind.Clear();
  272. }
  273. }