SharedSurgerySystem.Steps.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. // SPDX-FileCopyrightText: 2024 Skubman <ba.fallaria@gmail.com>
  2. // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
  3. // SPDX-FileCopyrightText: 2025 Janet Blackquill <uhhadd@gmail.com>
  4. // SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
  5. // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
  6. // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
  7. // SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
  8. // SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
  9. //
  10. // SPDX-License-Identifier: AGPL-3.0-or-later
  11. using Content.Shared.Humanoid;
  12. using Content.Shared.Humanoid.Markings;
  13. using Content.Shared.Body.Part;
  14. using Content.Shared.Body.Organ;
  15. using Content.Shared._Shitmed.BodyEffects;
  16. using Content.Shared._Shitmed.Body.Events;
  17. using Content.Shared.Buckle.Components;
  18. using Content.Shared.Containers.ItemSlots;
  19. using Content.Shared.Damage;
  20. using Content.Shared.Damage.Prototypes;
  21. using Content.Shared.DoAfter;
  22. using Content.Shared.IdentityManagement;
  23. using Content.Shared._Shitmed.Medical.Surgery.Conditions;
  24. using Content.Shared._Shitmed.Medical.Surgery.Effects.Step;
  25. using Content.Shared._Shitmed.Medical.Surgery.Steps;
  26. using Content.Shared._Shitmed.Medical.Surgery.Steps.Parts;
  27. using Content.Shared._Shitmed.Medical.Surgery.Tools;
  28. //using Content.Shared.Mood;
  29. using Content.Shared.Inventory;
  30. using Content.Shared.Item;
  31. using Content.Shared._Shitmed.Body.Organ;
  32. using Content.Shared._Shitmed.Body.Part;
  33. using Content.Shared.Popups;
  34. using Robust.Shared.Prototypes;
  35. using System.Linq;
  36. namespace Content.Shared._Shitmed.Medical.Surgery;
  37. public abstract partial class SharedSurgerySystem
  38. {
  39. private static readonly string[] BruteDamageTypes = { "Slash", "Blunt", "Piercing" };
  40. private static readonly string[] BurnDamageTypes = { "Heat", "Shock", "Cold", "Caustic" };
  41. private void InitializeSteps()
  42. {
  43. SubscribeLocalEvent<SurgeryStepComponent, SurgeryStepEvent>(OnToolStep);
  44. SubscribeLocalEvent<SurgeryStepComponent, SurgeryStepCompleteCheckEvent>(OnToolCheck);
  45. SubscribeLocalEvent<SurgeryStepComponent, SurgeryCanPerformStepEvent>(OnToolCanPerform);
  46. //SubSurgery<SurgeryCutLarvaRootsStepComponent>(OnCutLarvaRootsStep, OnCutLarvaRootsCheck);
  47. /* Abandon all hope ye who enter here. Now I am become shitcoder, the bloater of files.
  48. On a serious note, I really hate how much bloat this pattern of subscribing to a StepEvent and a CheckEvent
  49. creates in terms of readability. And while Check DOES only run on the server side, it's still annoying to parse through.*/
  50. SubSurgery<SurgeryTendWoundsEffectComponent>(OnTendWoundsStep, OnTendWoundsCheck);
  51. SubSurgery<SurgeryStepCavityEffectComponent>(OnCavityStep, OnCavityCheck);
  52. SubSurgery<SurgeryAddPartStepComponent>(OnAddPartStep, OnAddPartCheck);
  53. SubSurgery<SurgeryAffixPartStepComponent>(OnAffixPartStep, OnAffixPartCheck);
  54. SubSurgery<SurgeryRemovePartStepComponent>(OnRemovePartStep, OnRemovePartCheck);
  55. SubSurgery<SurgeryAddOrganStepComponent>(OnAddOrganStep, OnAddOrganCheck);
  56. SubSurgery<SurgeryRemoveOrganStepComponent>(OnRemoveOrganStep, OnRemoveOrganCheck);
  57. SubSurgery<SurgeryAffixOrganStepComponent>(OnAffixOrganStep, OnAffixOrganCheck);
  58. SubSurgery<SurgeryAddMarkingStepComponent>(OnAddMarkingStep, OnAddMarkingCheck);
  59. SubSurgery<SurgeryRemoveMarkingStepComponent>(OnRemoveMarkingStep, OnRemoveMarkingCheck);
  60. SubSurgery<SurgeryAddOrganSlotStepComponent>(OnAddOrganSlotStep, OnAddOrganSlotCheck);
  61. Subs.BuiEvents<SurgeryTargetComponent>(SurgeryUIKey.Key, subs =>
  62. {
  63. subs.Event<SurgeryStepChosenBuiMsg>(OnSurgeryTargetStepChosen);
  64. });
  65. }
  66. private void SubSurgery<TComp>(EntityEventRefHandler<TComp, SurgeryStepEvent> onStep,
  67. EntityEventRefHandler<TComp, SurgeryStepCompleteCheckEvent> onComplete) where TComp : IComponent
  68. {
  69. SubscribeLocalEvent(onStep);
  70. SubscribeLocalEvent(onComplete);
  71. }
  72. #region Event Methods
  73. private void OnToolStep(Entity<SurgeryStepComponent> ent, ref SurgeryStepEvent args)
  74. {
  75. if(!TryToolAudio(ent, args))
  76. return;
  77. AddOrRemoveComponentsToEntity(args.Part, ent.Comp.Add);
  78. AddOrRemoveComponentsToEntity(args.Part, ent.Comp.Remove, true);
  79. AddOrRemoveComponentsToEntity(args.Body, ent.Comp.BodyAdd);
  80. AddOrRemoveComponentsToEntity(args.Body, ent.Comp.BodyRemove,true);
  81. // Dude this fucking function is so bloated now what the fuck.
  82. HandleOrganModification(args.Part, args.Body, ent.Comp.AddOrganOnAdd);
  83. HandleOrganModification(args.Part, args.Body, ent.Comp.RemoveOrganOnAdd, false);
  84. HandleSanitization(args);
  85. }
  86. private void OnToolCheck(Entity<SurgeryStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  87. {
  88. if (TryToolCheck(ent.Comp.Add, args.Part) ||
  89. TryToolCheck(ent.Comp.Remove, args.Part, checkMissing: false) ||
  90. TryToolCheck(ent.Comp.BodyAdd, args.Body) ||
  91. TryToolCheck(ent.Comp.BodyRemove, args.Body, checkMissing: false))
  92. {
  93. args.Cancelled = true;
  94. return;
  95. }
  96. if (TryToolOrganCheck(ent.Comp.AddOrganOnAdd, args.Part)
  97. || TryToolOrganCheck(ent.Comp.RemoveOrganOnAdd, args.Part, checkMissing: false))
  98. args.Cancelled = true;
  99. }
  100. private void OnToolCanPerform(Entity<SurgeryStepComponent> ent, ref SurgeryCanPerformStepEvent args)
  101. {
  102. if (HasComp<SurgeryOperatingTableConditionComponent>(ent))
  103. {
  104. if (!TryComp(args.Body, out BuckleComponent? buckle) ||
  105. !HasComp<OperatingTableComponent>(buckle.BuckledTo))
  106. {
  107. args.Invalid = StepInvalidReason.NeedsOperatingTable;
  108. return;
  109. }
  110. }
  111. if (_inventory.TryGetContainerSlotEnumerator(args.Body, out var containerSlotEnumerator, args.TargetSlots))
  112. {
  113. if (HasComp<SurgeryIgnoreClothingComponent>(args.User))
  114. return;
  115. while (containerSlotEnumerator.MoveNext(out var containerSlot))
  116. {
  117. if (!containerSlot.ContainedEntity.HasValue)
  118. continue;
  119. args.Invalid = StepInvalidReason.Armor;
  120. args.Popup = Loc.GetString("surgery-ui-window-steps-error-armor");
  121. return;
  122. }
  123. }
  124. RaiseLocalEvent(args.Body, ref args);
  125. if (args.Invalid != StepInvalidReason.None)
  126. return;
  127. if (ent.Comp.Tool != null)
  128. {
  129. args.ValidTools ??= new Dictionary<EntityUid, float>();
  130. foreach (var reg in ent.Comp.Tool.Values)
  131. {
  132. if (!AnyHaveComp(args.Tools, reg.Component, out var tool, out var speed))
  133. {
  134. args.Invalid = StepInvalidReason.MissingTool;
  135. if (reg.Component is ISurgeryToolComponent required)
  136. args.Popup = $"You need {required.ToolName} to perform this step!";
  137. return;
  138. }
  139. args.ValidTools[tool] = speed;
  140. }
  141. }
  142. }
  143. private void OnTendWoundsStep(Entity<SurgeryTendWoundsEffectComponent> ent, ref SurgeryStepEvent args)
  144. {
  145. var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes;
  146. if (!HasDamageGroup(args.Body, group, out var damageable)
  147. && !HasDamageGroup(args.Part, group, out var _)
  148. || damageable == null) // This shouldnt be possible but the compiler doesn't shut up.
  149. return;
  150. // Right now the bonus is based off the body's total damage, maybe we could make it based off each part in the future.
  151. var bonus = ent.Comp.HealMultiplier * damageable.DamagePerGroup[ent.Comp.MainGroup];
  152. if (_mobState.IsDead(args.Body))
  153. bonus *= 0.2;
  154. var adjustedDamage = new DamageSpecifier(ent.Comp.Damage);
  155. foreach (var type in group)
  156. adjustedDamage.DamageDict[type] -= bonus;
  157. var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, adjustedDamage, 0.5f);
  158. RaiseLocalEvent(args.Body, ref ev);
  159. }
  160. private void OnTendWoundsCheck(Entity<SurgeryTendWoundsEffectComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  161. {
  162. var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes;
  163. if (HasDamageGroup(args.Body, group, out var _)
  164. || HasDamageGroup(args.Part, group, out var _))
  165. args.Cancelled = true;
  166. }
  167. /*private void OnCutLarvaRootsStep(Entity<SurgeryCutLarvaRootsStepComponent> ent, ref SurgeryStepEvent args)
  168. {
  169. if (TryComp(args.Body, out VictimInfectedComponent? infected) &&
  170. infected.BurstAt > _timing.CurTime &&
  171. infected.SpawnedLarva == null)
  172. {
  173. infected.RootsCut = true;
  174. }
  175. }
  176. private void OnCutLarvaRootsCheck(Entity<SurgeryCutLarvaRootsStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  177. {
  178. if (!TryComp(args.Body, out VictimInfectedComponent? infected) || !infected.RootsCut)
  179. args.Cancelled = true;
  180. // The larva has fully developed and surgery is now impossible
  181. // TODO: Surgery should still be possible, but the fully developed larva should escape while also saving the hosts life
  182. if (infected != null && infected.SpawnedLarva != null)
  183. args.Cancelled = true;
  184. }*/
  185. private void OnCavityStep(Entity<SurgeryStepCavityEffectComponent> ent, ref SurgeryStepEvent args)
  186. {
  187. if (!TryComp(args.Part, out BodyPartComponent? partComp) || partComp.PartType != BodyPartType.Torso)
  188. return;
  189. var activeHandEntity = _hands.EnumerateHeld(args.User).FirstOrDefault();
  190. if (activeHandEntity != default
  191. && ent.Comp.Action == "Insert"
  192. && TryComp(activeHandEntity, out ItemComponent? itemComp)
  193. && (itemComp.Size.Id == "Tiny"
  194. || itemComp.Size.Id == "Small"))
  195. _itemSlotsSystem.TryInsert(ent, partComp.ItemInsertionSlot, activeHandEntity, args.User);
  196. else if (ent.Comp.Action == "Remove")
  197. _itemSlotsSystem.TryEjectToHands(ent, partComp.ItemInsertionSlot, args.User);
  198. }
  199. private void OnCavityCheck(Entity<SurgeryStepCavityEffectComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  200. {
  201. // Normally this check would simply be partComp.ItemInsertionSlot.HasItem, but as mentioned before,
  202. // For whatever reason it's not instantiating the field on the clientside after the wizmerge.
  203. if (!TryComp(args.Part, out BodyPartComponent? partComp)
  204. || !TryComp(args.Part, out ItemSlotsComponent? itemComp)
  205. || ent.Comp.Action == "Insert"
  206. && !itemComp.Slots[partComp.ContainerName].HasItem
  207. || ent.Comp.Action == "Remove"
  208. && itemComp.Slots[partComp.ContainerName].HasItem)
  209. args.Cancelled = true;
  210. }
  211. private void OnAddPartStep(Entity<SurgeryAddPartStepComponent> ent, ref SurgeryStepEvent args)
  212. {
  213. if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
  214. return;
  215. foreach (var tool in args.Tools)
  216. {
  217. if (TryComp(tool, out BodyPartComponent? partComp)
  218. && partComp.PartType == removedComp.Part
  219. && (removedComp.Symmetry == null || partComp.Symmetry == removedComp.Symmetry))
  220. {
  221. var slotName = removedComp.Symmetry != null
  222. ? $"{removedComp.Symmetry?.ToString().ToLower()} {removedComp.Part.ToString().ToLower()}"
  223. : removedComp.Part.ToString().ToLower();
  224. _body.TryCreatePartSlot(args.Part, slotName, partComp.PartType, out var _);
  225. _body.AttachPart(args.Part, slotName, tool);
  226. EnsureComp<BodyPartReattachedComponent>(tool);
  227. var ev = new BodyPartAttachedEvent((tool, partComp));
  228. RaiseLocalEvent(args.Body, ref ev);
  229. }
  230. }
  231. }
  232. private void OnAddOrganSlotStep(Entity<SurgeryAddOrganSlotStepComponent> ent, ref SurgeryStepEvent args)
  233. {
  234. if (!TryComp(args.Surgery, out SurgeryOrganSlotConditionComponent? condition))
  235. return;
  236. _body.TryCreateOrganSlot(args.Part, condition.OrganSlot, out _);
  237. }
  238. private void OnAddOrganSlotCheck(Entity<SurgeryAddOrganSlotStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  239. {
  240. if (!TryComp(args.Surgery, out SurgeryOrganSlotConditionComponent? condition))
  241. return;
  242. args.Cancelled = !_body.CanInsertOrgan(args.Part, condition.OrganSlot);
  243. }
  244. private void OnAffixPartStep(Entity<SurgeryAffixPartStepComponent> ent, ref SurgeryStepEvent args)
  245. {
  246. if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
  247. return;
  248. var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault();
  249. if (targetPart != default)
  250. {
  251. // We reward players for properly affixing the parts by healing a little bit of damage, and enabling the part temporarily.
  252. var ev = new BodyPartEnableChangedEvent(true);
  253. RaiseLocalEvent(targetPart.Id, ref ev);
  254. _damageable.TryChangeDamage(args.Body,
  255. _body.GetHealingSpecifier(targetPart.Component) * 2,
  256. canSever: false, // Just in case we heal a brute damage specifier and the logic gets fucky lol
  257. targetPart: _body.GetTargetBodyPart(targetPart.Component.PartType, targetPart.Component.Symmetry));
  258. RemComp<BodyPartReattachedComponent>(targetPart.Id);
  259. }
  260. }
  261. private void OnAffixPartCheck(Entity<SurgeryAffixPartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  262. {
  263. if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
  264. return;
  265. var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault();
  266. if (targetPart != default
  267. && HasComp<BodyPartReattachedComponent>(targetPart.Id))
  268. args.Cancelled = true;
  269. }
  270. private void OnAddPartCheck(Entity<SurgeryAddPartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  271. {
  272. if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp)
  273. || !_body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).Any())
  274. args.Cancelled = true;
  275. }
  276. private void OnRemovePartStep(Entity<SurgeryRemovePartStepComponent> ent, ref SurgeryStepEvent args)
  277. {
  278. if (!TryComp(args.Part, out BodyPartComponent? partComp)
  279. || partComp.Body != args.Body)
  280. return;
  281. var ev = new AmputateAttemptEvent(args.Part);
  282. RaiseLocalEvent(args.Part, ref ev);
  283. _hands.TryPickupAnyHand(args.User, args.Part);
  284. }
  285. private void OnRemovePartCheck(Entity<SurgeryRemovePartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  286. {
  287. if (!TryComp(args.Part, out BodyPartComponent? partComp)
  288. || partComp.Body == args.Body)
  289. args.Cancelled = true;
  290. }
  291. private void OnAddOrganStep(Entity<SurgeryAddOrganStepComponent> ent, ref SurgeryStepEvent args)
  292. {
  293. if (!TryComp(args.Part, out BodyPartComponent? partComp)
  294. || partComp.Body != args.Body
  295. || !TryComp(args.Surgery, out SurgeryOrganConditionComponent? organComp)
  296. || organComp.Organ == null)
  297. return;
  298. // Adding organs is generally done for a single one at a time, so we only need to check for the first.
  299. var firstOrgan = organComp.Organ.Values.FirstOrDefault();
  300. if (firstOrgan == default)
  301. return;
  302. foreach (var tool in args.Tools)
  303. {
  304. if (HasComp(tool, firstOrgan.Component.GetType())
  305. && TryComp<OrganComponent>(tool, out var insertedOrgan)
  306. && _body.InsertOrgan(args.Part, tool, insertedOrgan.SlotId, partComp, insertedOrgan))
  307. {
  308. EnsureComp<OrganReattachedComponent>(tool);
  309. if (_body.TrySetOrganUsed(tool, true, insertedOrgan)
  310. && insertedOrgan.OriginalBody != args.Body)
  311. {
  312. var ev = new SurgeryStepDamageChangeEvent(args.User, args.Body, args.Part, ent);
  313. RaiseLocalEvent(ent, ref ev);
  314. args.Complete = true;
  315. }
  316. break;
  317. }
  318. }
  319. }
  320. private void OnAddOrganCheck(Entity<SurgeryAddOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  321. {
  322. if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
  323. || organComp.Organ is null
  324. || !TryComp(args.Part, out BodyPartComponent? partComp)
  325. || partComp.Body != args.Body)
  326. return;
  327. // For now we naively assume that every entity will only have one of each organ type.
  328. // that we do surgery on, but in the future we'll need to reference their prototype somehow
  329. // to know if they need 2 hearts, 2 lungs, etc.
  330. foreach (var reg in organComp.Organ.Values)
  331. {
  332. if (!_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var _))
  333. {
  334. args.Cancelled = true;
  335. }
  336. }
  337. }
  338. private void OnAffixOrganStep(Entity<SurgeryAffixOrganStepComponent> ent, ref SurgeryStepEvent args)
  339. {
  340. if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp)
  341. || removedOrganComp.Organ == null
  342. || !removedOrganComp.Reattaching)
  343. return;
  344. foreach (var reg in removedOrganComp.Organ.Values)
  345. {
  346. _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
  347. if (organs != null && organs.Count > 0)
  348. RemComp<OrganReattachedComponent>(organs[0].Id);
  349. }
  350. }
  351. private void OnAffixOrganCheck(Entity<SurgeryAffixOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  352. {
  353. if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp)
  354. || removedOrganComp.Organ == null
  355. || !removedOrganComp.Reattaching)
  356. return;
  357. foreach (var reg in removedOrganComp.Organ.Values)
  358. {
  359. _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
  360. if (organs != null
  361. && organs.Count > 0
  362. && organs.Any(organ => HasComp<OrganReattachedComponent>(organ.Id)))
  363. args.Cancelled = true;
  364. }
  365. }
  366. private void OnRemoveOrganStep(Entity<SurgeryRemoveOrganStepComponent> ent, ref SurgeryStepEvent args)
  367. {
  368. if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
  369. || organComp.Organ == null)
  370. return;
  371. foreach (var reg in organComp.Organ.Values)
  372. {
  373. _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
  374. if (organs != null && organs.Count > 0)
  375. {
  376. _body.RemoveOrgan(organs[0].Id, organs[0].Organ);
  377. _hands.TryPickupAnyHand(args.User, organs[0].Id);
  378. }
  379. }
  380. }
  381. private void OnRemoveOrganCheck(Entity<SurgeryRemoveOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  382. {
  383. if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
  384. || organComp.Organ == null
  385. || !TryComp(args.Part, out BodyPartComponent? partComp)
  386. || partComp.Body != args.Body)
  387. return;
  388. foreach (var reg in organComp.Organ.Values)
  389. {
  390. if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs)
  391. && organs != null
  392. && organs.Count > 0)
  393. {
  394. args.Cancelled = true;
  395. return;
  396. }
  397. }
  398. }
  399. // TODO: Refactor bodies to include ears as a prototype instead of doing whatever the hell this is.
  400. private void OnAddMarkingStep(Entity<SurgeryAddMarkingStepComponent> ent, ref SurgeryStepEvent args)
  401. {
  402. if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
  403. || ent.Comp.Organ == null)
  404. return;
  405. var organType = ent.Comp.Organ.Values.FirstOrDefault();
  406. if (organType == default)
  407. return;
  408. var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
  409. foreach (var tool in args.Tools)
  410. {
  411. if (TryComp(tool, out MarkingContainerComponent? markingComp)
  412. && HasComp(tool, organType.Component.GetType()))
  413. {
  414. if (!bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
  415. || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString)))
  416. {
  417. EnsureComp<BodyPartAppearanceComponent>(args.Part);
  418. _body.ModifyMarkings(args.Body, args.Part, bodyAppearance, ent.Comp.MarkingCategory, markingComp.Marking);
  419. if (ent.Comp.Accent != null
  420. && ent.Comp.Accent.Values.FirstOrDefault() is { } accent)
  421. {
  422. var compType = accent.Component.GetType();
  423. if (!HasComp(args.Body, compType))
  424. AddComp(args.Body, _compFactory.GetComponent(compType));
  425. }
  426. QueueDel(tool); // Again since this isnt actually being inserted we just delete it lol.
  427. }
  428. }
  429. }
  430. }
  431. private void OnAddMarkingCheck(Entity<SurgeryAddMarkingStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  432. {
  433. var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
  434. if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
  435. || !bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
  436. || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString)))
  437. args.Cancelled = true;
  438. }
  439. private void OnRemoveMarkingStep(Entity<SurgeryRemoveMarkingStepComponent> ent, ref SurgeryStepEvent args)
  440. {
  441. }
  442. private void OnRemoveMarkingCheck(Entity<SurgeryRemoveMarkingStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
  443. {
  444. }
  445. private void OnSurgeryTargetStepChosen(Entity<SurgeryTargetComponent> ent, ref SurgeryStepChosenBuiMsg args)
  446. {
  447. var user = args.Actor;
  448. if (GetEntity(args.Entity) is {} body &&
  449. GetEntity(args.Part) is {} targetPart)
  450. {
  451. TryDoSurgeryStep(body, targetPart, user, args.Surgery, args.Step);
  452. }
  453. }
  454. #endregion
  455. #region Helper Methods
  456. // I wonder if theres not a function that can do this already.
  457. private bool HasDamageGroup(EntityUid entity, string[] group, out DamageableComponent? damageable)
  458. {
  459. if (!TryComp<DamageableComponent>(entity, out var damageableComp))
  460. {
  461. damageable = null;
  462. return false;
  463. }
  464. damageable = damageableComp;
  465. return group.Any(damageType => damageableComp.Damage.DamageDict.TryGetValue(damageType, out var value) && value > 0);
  466. }
  467. private void HandleSanitization(SurgeryStepEvent args)
  468. {
  469. if (_inventory.TryGetSlotEntity(args.User, "gloves", out var _)
  470. && _inventory.TryGetSlotEntity(args.User, "mask", out var _))
  471. return;
  472. if (HasComp<SanitizedComponent>(args.User))
  473. return;
  474. var sepsis = new DamageSpecifier(_prototypes.Index<DamageTypePrototype>("Poison"), 5);
  475. var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, sepsis, 0.5f);
  476. RaiseLocalEvent(args.Body, ref ev);
  477. }
  478. private bool TryToolAudio(Entity<SurgeryStepComponent> ent, SurgeryStepEvent args)
  479. {
  480. if (ent.Comp.Tool == null)
  481. return true;
  482. foreach (var reg in ent.Comp.Tool.Values)
  483. {
  484. if (!AnyHaveComp(args.Tools, reg.Component, out var tool, out _))
  485. return false;
  486. if (_net.IsServer &&
  487. TryComp(tool, out SurgeryToolComponent? toolComp) &&
  488. toolComp.EndSound != null)
  489. {
  490. _audio.PlayPvs(toolComp.EndSound, tool);
  491. }
  492. }
  493. return true;
  494. }
  495. private void HandleOrganModification(EntityUid organTarget,
  496. EntityUid bodyTarget,
  497. Dictionary<string, ComponentRegistry>? modifications,
  498. bool remove = false)
  499. {
  500. if (modifications == null)
  501. return;
  502. var organSlotIdToOrgan = _body.GetPartOrgans(organTarget).ToDictionary(o => o.Item2.SlotId, o => o);
  503. foreach (var (slotId, components) in modifications)
  504. {
  505. if (!organSlotIdToOrgan.TryGetValue(slotId, out var organValue))
  506. continue;
  507. var (organId, organ) = organValue;
  508. if (remove)
  509. {
  510. if (organValue.Item2.OnAdd == null || organ.OnAdd == null)
  511. continue;
  512. RaiseLocalEvent(organId, new OrganComponentsModifyEvent(bodyTarget, false));
  513. foreach (var key in components.Keys)
  514. organ.OnAdd.Remove(key);
  515. }
  516. else
  517. {
  518. organ.OnAdd ??= new ComponentRegistry();
  519. foreach (var (key, compToAdd) in components)
  520. organ.OnAdd[key] = compToAdd;
  521. EnsureComp<OrganEffectComponent>(organId);
  522. RaiseLocalEvent(organId, new OrganComponentsModifyEvent(bodyTarget, true));
  523. }
  524. }
  525. }
  526. private void AddOrRemoveComponentsToEntity(EntityUid ent, ComponentRegistry? componentRegistry, bool remove = false)
  527. {
  528. if(componentRegistry == null)
  529. return;
  530. foreach (var reg in componentRegistry.Values)
  531. {
  532. var compType = reg.Component.GetType();
  533. if (remove)
  534. RemComp(ent, compType);
  535. else
  536. {
  537. if (HasComp(ent, compType))
  538. continue;
  539. AddComp(ent, _compFactory.GetComponent(compType));
  540. }
  541. }
  542. }
  543. private bool TryToolCheck(ComponentRegistry? components, EntityUid target, bool checkMissing = true)
  544. {
  545. if (components == null)
  546. return false;
  547. foreach (var (key,entry) in components)
  548. {
  549. var hasComponent = HasComp(target, entry.Component.GetType());
  550. if (checkMissing != hasComponent)
  551. return true; // Early exit if condition fails
  552. }
  553. return false;
  554. }
  555. private bool TryToolOrganCheck(IReadOnlyDictionary<string, ComponentRegistry>? organChanges, EntityUid part, bool checkMissing = true)
  556. {
  557. if (organChanges == null)
  558. return false;
  559. var organSlotIdToOrgan = _body.GetPartOrgans(part).ToDictionary(o => o.Item2.SlotId, o => o.Item2);
  560. foreach (var (organSlotId, compsToAdd) in organChanges)
  561. {
  562. if (!organSlotIdToOrgan.TryGetValue(organSlotId, out var organ))
  563. continue;
  564. if (checkMissing)
  565. {
  566. if (organ.OnAdd == null || compsToAdd.Keys.Any(key => !organ.OnAdd.ContainsKey(key)))
  567. {
  568. return true;
  569. }
  570. }
  571. else
  572. {
  573. if (organ.OnAdd == null)
  574. continue;
  575. if (compsToAdd.Keys.Any(key => organ.OnAdd != null && organ.OnAdd.ContainsKey(key)))
  576. {
  577. return true;
  578. }
  579. }
  580. }
  581. return false;
  582. }
  583. /// <summary>
  584. /// Do a surgery step on a part, if it can be done.
  585. /// Returns true if it succeeded.
  586. /// </summary>
  587. public bool TryDoSurgeryStep(EntityUid body, EntityUid targetPart, EntityUid user, EntProtoId surgeryId, EntProtoId stepId)
  588. {
  589. if (!IsSurgeryValid(body, targetPart, surgeryId, stepId, user, out var surgery, out var part, out var step))
  590. return false;
  591. if (!PreviousStepsComplete(body, part, surgery, stepId) ||
  592. IsStepComplete(body, part, stepId, surgery))
  593. return false;
  594. if (!CanPerformStep(user, body, part, step, true, out _, out _, out var validTools))
  595. return false;
  596. var speed = 1f;
  597. var usedEv = new SurgeryToolUsedEvent(user, body);
  598. // We need to check for nullability because of surgeries that dont require a tool, like Cavity Implants
  599. if (validTools?.Count > 0)
  600. {
  601. foreach (var (tool, toolSpeed) in validTools)
  602. {
  603. RaiseLocalEvent(tool, ref usedEv);
  604. if (usedEv.Cancelled)
  605. return false;
  606. speed *= toolSpeed;
  607. }
  608. if (_net.IsServer)
  609. {
  610. foreach (var tool in validTools.Keys)
  611. {
  612. if (TryComp(tool, out SurgeryToolComponent? toolComp) &&
  613. toolComp.StartSound != null)
  614. {
  615. _audio.PlayPvs(toolComp.StartSound, tool);
  616. }
  617. }
  618. }
  619. }
  620. if (TryComp(body, out TransformComponent? xform))
  621. _rotateToFace.TryFaceCoordinates(user, _transform.GetMapCoordinates(body, xform).Position);
  622. var ev = new SurgeryDoAfterEvent(surgeryId, stepId);
  623. // TODO: Move 2 seconds to a field of SurgeryStepComponent
  624. var duration = GetSurgeryDuration(step, user, body, speed);
  625. if (TryComp(user, out SurgerySpeedModifierComponent? surgerySpeedMod)
  626. && surgerySpeedMod is not null)
  627. duration = duration / surgerySpeedMod.SpeedModifier;
  628. var doAfter = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(duration), ev, body, part)
  629. {
  630. BreakOnMove = true,
  631. //BreakOnTargetMove = true, I fucking hate wizden dude.
  632. CancelDuplicate = true,
  633. DuplicateCondition = DuplicateConditions.SameEvent,
  634. NeedHand = true,
  635. BreakOnHandChange = true,
  636. };
  637. if (!_doAfter.TryStartDoAfter(doAfter))
  638. return false;
  639. var userName = Identity.Entity(user, EntityManager);
  640. var targetName = Identity.Entity(body, EntityManager);
  641. var locName = $"surgery-popup-procedure-{surgeryId}-step-{stepId}";
  642. var locResult = Loc.GetString(locName,
  643. ("user", userName), ("target", targetName), ("part", part));
  644. if (locResult == locName)
  645. locResult = Loc.GetString($"surgery-popup-step-{stepId}",
  646. ("user", userName), ("target", targetName), ("part", part));
  647. _popup.PopupEntity(locResult, user);
  648. return true;
  649. }
  650. private float GetSurgeryDuration(EntityUid surgeryStep, EntityUid user, EntityUid target, float toolSpeed)
  651. {
  652. if (!TryComp(surgeryStep, out SurgeryStepComponent? stepComp))
  653. return 2f; // Shouldnt really happen but just a failsafe.
  654. var speed = toolSpeed;
  655. if (TryComp(user, out SurgerySpeedModifierComponent? surgerySpeedMod))
  656. speed *= surgerySpeedMod.SpeedModifier;
  657. return stepComp.Duration / speed;
  658. }
  659. private (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, Entity<SurgeryComponent?> surgery, List<EntityUid> requirements)
  660. {
  661. if (!Resolve(surgery, ref surgery.Comp))
  662. return null;
  663. if (requirements.Contains(surgery))
  664. throw new ArgumentException($"Surgery {surgery} has a requirement loop: {string.Join(", ", requirements)}");
  665. requirements.Add(surgery);
  666. if (surgery.Comp.Requirement is { } requirementId &&
  667. GetSingleton(requirementId) is { } requirement &&
  668. GetNextStep(body, part, requirement, requirements) is { } requiredNext)
  669. {
  670. return requiredNext;
  671. }
  672. for (var i = 0; i < surgery.Comp.Steps.Count; i++)
  673. {
  674. var surgeryStep = surgery.Comp.Steps[i];
  675. if (!IsStepComplete(body, part, surgeryStep, surgery))
  676. return ((surgery, surgery.Comp), i);
  677. }
  678. return null;
  679. }
  680. public (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, EntityUid surgery)
  681. {
  682. return GetNextStep(body, part, surgery, new List<EntityUid>());
  683. }
  684. public bool PreviousStepsComplete(EntityUid body, EntityUid part, Entity<SurgeryComponent> surgery, EntProtoId step)
  685. {
  686. // TODO RMC14 use index instead of the prototype id
  687. if (surgery.Comp.Requirement is { } requirement)
  688. {
  689. if (GetSingleton(requirement) is not { } requiredEnt ||
  690. !TryComp(requiredEnt, out SurgeryComponent? requiredComp) ||
  691. !PreviousStepsComplete(body, part, (requiredEnt, requiredComp), step))
  692. {
  693. return false;
  694. }
  695. }
  696. foreach (var surgeryStep in surgery.Comp.Steps)
  697. {
  698. if (surgeryStep == step)
  699. break;
  700. if (!IsStepComplete(body, part, surgeryStep, surgery))
  701. return false;
  702. }
  703. return true;
  704. }
  705. public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part,
  706. EntityUid step, bool doPopup, out string? popup, out StepInvalidReason reason,
  707. out Dictionary<EntityUid, float>? validTools)
  708. {
  709. var type = BodyPartType.Other;
  710. if (TryComp(part, out BodyPartComponent? partComp))
  711. {
  712. type = partComp.PartType;
  713. }
  714. var slot = type switch
  715. {
  716. BodyPartType.Head => SlotFlags.HEAD,
  717. BodyPartType.Torso => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
  718. BodyPartType.Arm => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
  719. BodyPartType.Hand => SlotFlags.GLOVES,
  720. BodyPartType.Leg => SlotFlags.OUTERCLOTHING | SlotFlags.LEGS,
  721. BodyPartType.Foot => SlotFlags.FEET,
  722. BodyPartType.Tail => SlotFlags.NONE,
  723. BodyPartType.Other => SlotFlags.NONE,
  724. _ => SlotFlags.NONE
  725. };
  726. var check = new SurgeryCanPerformStepEvent(user, body, GetTools(user), slot);
  727. RaiseLocalEvent(step, ref check);
  728. popup = check.Popup;
  729. validTools = check.ValidTools;
  730. if (check.Invalid != StepInvalidReason.None)
  731. {
  732. if (doPopup && check.Popup != null)
  733. _popup.PopupEntity(check.Popup, user, user, PopupType.SmallCaution);
  734. reason = check.Invalid;
  735. return false;
  736. }
  737. reason = default;
  738. return true;
  739. }
  740. public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part, EntityUid step, bool doPopup)
  741. {
  742. return CanPerformStep(user, body, part, step, doPopup, out _, out _, out _);
  743. }
  744. public bool IsStepComplete(EntityUid body, EntityUid part, EntProtoId step, EntityUid surgery)
  745. {
  746. if (GetSingleton(step) is not { } stepEnt)
  747. return false;
  748. var ev = new SurgeryStepCompleteCheckEvent(body, part, surgery);
  749. RaiseLocalEvent(stepEnt, ref ev);
  750. return !ev.Cancelled;
  751. }
  752. private bool AnyHaveComp(List<EntityUid> tools, IComponent component, out EntityUid withComp, out float speed)
  753. {
  754. foreach (var tool in tools)
  755. {
  756. if (EntityManager.TryGetComponent(tool, component.GetType(), out var found) && found is ISurgeryToolComponent toolComp)
  757. {
  758. withComp = tool;
  759. speed = toolComp.Speed;
  760. return true;
  761. }
  762. }
  763. withComp = EntityUid.Invalid;
  764. speed = 1f;
  765. return false;
  766. }
  767. #endregion
  768. }