SharedBodySystem.Parts.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  1. // SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
  2. // SPDX-FileCopyrightText: 2023 Jezithyr <jezithyr@gmail.com>
  3. // SPDX-FileCopyrightText: 2023 TemporalOroboros <TemporalOroboros@gmail.com>
  4. // SPDX-FileCopyrightText: 2023 Visne <39844191+Visne@users.noreply.github.com>
  5. // SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
  6. // SPDX-FileCopyrightText: 2024 Alzore <140123969+Blackern5000@users.noreply.github.com>
  7. // SPDX-FileCopyrightText: 2024 Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com>
  8. // SPDX-FileCopyrightText: 2024 CaasGit <87243814+CaasGit@users.noreply.github.com>
  9. // SPDX-FileCopyrightText: 2024 Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
  10. // SPDX-FileCopyrightText: 2024 Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
  11. // SPDX-FileCopyrightText: 2024 DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
  12. // SPDX-FileCopyrightText: 2024 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
  13. // SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com>
  14. // SPDX-FileCopyrightText: 2024 Emisse <99158783+Emisse@users.noreply.github.com>
  15. // SPDX-FileCopyrightText: 2024 EmoGarbage404 <retron404@gmail.com>
  16. // SPDX-FileCopyrightText: 2024 Eoin Mcloughlin <helloworld@eoinrul.es>
  17. // SPDX-FileCopyrightText: 2024 Errant <35878406+Errant-4@users.noreply.github.com>
  18. // SPDX-FileCopyrightText: 2024 Flareguy <78941145+Flareguy@users.noreply.github.com>
  19. // SPDX-FileCopyrightText: 2024 Hrosts <35345601+Hrosts@users.noreply.github.com>
  20. // SPDX-FileCopyrightText: 2024 IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
  21. // SPDX-FileCopyrightText: 2024 Ian <ignaz.k@live.de>
  22. // SPDX-FileCopyrightText: 2024 Ilya246 <57039557+Ilya246@users.noreply.github.com>
  23. // SPDX-FileCopyrightText: 2024 Joel Zimmerman <JoelZimmerman@users.noreply.github.com>
  24. // SPDX-FileCopyrightText: 2024 JustCone <141039037+JustCone14@users.noreply.github.com>
  25. // SPDX-FileCopyrightText: 2024 Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
  26. // SPDX-FileCopyrightText: 2024 Ko4ergaPunk <62609550+Ko4ergaPunk@users.noreply.github.com>
  27. // SPDX-FileCopyrightText: 2024 Kukutis96513 <146854220+Kukutis96513@users.noreply.github.com>
  28. // SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
  29. // SPDX-FileCopyrightText: 2024 Lye <128915833+Lyroth001@users.noreply.github.com>
  30. // SPDX-FileCopyrightText: 2024 MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com>
  31. // SPDX-FileCopyrightText: 2024 Mervill <mervills.email@gmail.com>
  32. // SPDX-FileCopyrightText: 2024 Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
  33. // SPDX-FileCopyrightText: 2024 MureixloI <132683811+MureixloI@users.noreply.github.com>
  34. // SPDX-FileCopyrightText: 2024 NakataRin <45946146+NakataRin@users.noreply.github.com>
  35. // SPDX-FileCopyrightText: 2024 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
  36. // SPDX-FileCopyrightText: 2024 OrangeMoronage9622 <whyteterry0092@gmail.com>
  37. // SPDX-FileCopyrightText: 2024 PJBot <pieterjan.briers+bot@gmail.com>
  38. // SPDX-FileCopyrightText: 2024 Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
  39. // SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
  40. // SPDX-FileCopyrightText: 2024 Plykiya <58439124+Plykiya@users.noreply.github.com>
  41. // SPDX-FileCopyrightText: 2024 Preston Smith <92108534+thetolbean@users.noreply.github.com>
  42. // SPDX-FileCopyrightText: 2024 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
  43. // SPDX-FileCopyrightText: 2024 Repo <47093363+Titian3@users.noreply.github.com>
  44. // SPDX-FileCopyrightText: 2024 RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com>
  45. // SPDX-FileCopyrightText: 2024 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
  46. // SPDX-FileCopyrightText: 2024 Simon <63975668+Simyon264@users.noreply.github.com>
  47. // SPDX-FileCopyrightText: 2024 SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
  48. // SPDX-FileCopyrightText: 2024 Stalen <33173619+stalengd@users.noreply.github.com>
  49. // SPDX-FileCopyrightText: 2024 TakoDragon <69509841+BackeTako@users.noreply.github.com>
  50. // SPDX-FileCopyrightText: 2024 Thomas <87614336+Aeshus@users.noreply.github.com>
  51. // SPDX-FileCopyrightText: 2024 TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com>
  52. // SPDX-FileCopyrightText: 2024 Ubaser <134914314+UbaserB@users.noreply.github.com>
  53. // SPDX-FileCopyrightText: 2024 Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
  54. // SPDX-FileCopyrightText: 2024 Vasilis <vasilis@pikachu.systems>
  55. // SPDX-FileCopyrightText: 2024 Vigers Ray <60344369+VigersRay@users.noreply.github.com>
  56. // SPDX-FileCopyrightText: 2024 beck-thompson <107373427+beck-thompson@users.noreply.github.com>
  57. // SPDX-FileCopyrightText: 2024 deathride58 <deathride58@users.noreply.github.com>
  58. // SPDX-FileCopyrightText: 2024 dffdff2423 <dffdff2423@gmail.com>
  59. // SPDX-FileCopyrightText: 2024 eoineoineoin <github@eoinrul.es>
  60. // SPDX-FileCopyrightText: 2024 foboscheshir <156405958+foboscheshir@users.noreply.github.com>
  61. // SPDX-FileCopyrightText: 2024 github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  62. // SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
  63. // SPDX-FileCopyrightText: 2024 lzk <124214523+lzk228@users.noreply.github.com>
  64. // SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
  65. // SPDX-FileCopyrightText: 2024 metalgearsloth <comedian_vs_clown@hotmail.com>
  66. // SPDX-FileCopyrightText: 2024 nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
  67. // SPDX-FileCopyrightText: 2024 plykiya <plykiya@protonmail.com>
  68. // SPDX-FileCopyrightText: 2024 saintmuntzer <47153094+saintmuntzer@users.noreply.github.com>
  69. // SPDX-FileCopyrightText: 2024 shamp <140359015+shampunj@users.noreply.github.com>
  70. // SPDX-FileCopyrightText: 2024 slarticodefast <161409025+slarticodefast@users.noreply.github.com>
  71. // SPDX-FileCopyrightText: 2024 strO0pwafel <153459934+strO0pwafel@users.noreply.github.com>
  72. // SPDX-FileCopyrightText: 2024 stroopwafel <j.o.luijkx@student.tudelft.nl>
  73. // SPDX-FileCopyrightText: 2024 themias <89101928+themias@users.noreply.github.com>
  74. // SPDX-FileCopyrightText: 2024 to4no_fix <156101927+chavonadelal@users.noreply.github.com>
  75. // SPDX-FileCopyrightText: 2024 voidnull000 <18663194+voidnull000@users.noreply.github.com>
  76. // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
  77. // SPDX-FileCopyrightText: 2025 Aiden <aiden@djkraz.com>
  78. // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
  79. // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
  80. //
  81. // SPDX-License-Identifier: AGPL-3.0-or-later
  82. using System.Diagnostics.CodeAnalysis;
  83. using System.Linq;
  84. using Content.Shared.Body.Components;
  85. using Content.Shared.Body.Events;
  86. using Content.Shared.Body.Organ;
  87. using Content.Shared.Body.Part;
  88. using Content.Shared.Damage;
  89. using Content.Shared.Damage.Prototypes;
  90. using Content.Shared.Movement.Components;
  91. using Robust.Shared.Containers;
  92. using Robust.Shared.Utility;
  93. // Shitmed Change Start
  94. using Content.Shared._Shitmed.Body.Events;
  95. using Content.Shared._Shitmed.Body.Part;
  96. using Content.Shared._Shitmed.BodyEffects;
  97. using Content.Shared.Humanoid;
  98. using Content.Shared.Inventory;
  99. using Content.Shared.Random;
  100. namespace Content.Shared.Body.Systems;
  101. public partial class SharedBodySystem
  102. {
  103. [Dependency] private readonly RandomHelperSystem _randomHelper = default!; // Shitmed Change
  104. [Dependency] private readonly InventorySystem _inventorySystem = default!; // Shitmed Change
  105. private void InitializeParts()
  106. {
  107. // TODO: This doesn't handle comp removal on child ents.
  108. // If you modify this also see the Body partial for root parts.
  109. SubscribeLocalEvent<BodyPartComponent, EntInsertedIntoContainerMessage>(OnBodyPartInserted);
  110. SubscribeLocalEvent<BodyPartComponent, EntRemovedFromContainerMessage>(OnBodyPartRemoved);
  111. // Shitmed Change Start
  112. SubscribeLocalEvent<BodyPartComponent, MapInitEvent>(OnMapInit);
  113. SubscribeLocalEvent<BodyPartComponent, ComponentRemove>(OnBodyPartRemove);
  114. SubscribeLocalEvent<BodyPartComponent, AmputateAttemptEvent>(OnAmputateAttempt);
  115. SubscribeLocalEvent<BodyPartComponent, BodyPartEnableChangedEvent>(OnPartEnableChanged);
  116. }
  117. private void OnMapInit(Entity<BodyPartComponent> ent, ref MapInitEvent args)
  118. {
  119. if (ent.Comp.PartType == BodyPartType.Torso)
  120. {
  121. // For whatever reason this slot is initialized properly on the server, but not on the client.
  122. // This seems to be an issue due to wiz-merge, on my old branch it was properly instantiating
  123. // ItemInsertionSlot's container on both ends. It does show up properly on ItemSlotsComponent though.
  124. _slots.AddItemSlot(ent, ent.Comp.ContainerName, ent.Comp.ItemInsertionSlot);
  125. Dirty(ent, ent.Comp);
  126. }
  127. if (ent.Comp.OnAdd is not null || ent.Comp.OnRemove is not null)
  128. EnsureComp<BodyPartEffectComponent>(ent);
  129. foreach (var connection in ent.Comp.Children.Keys)
  130. {
  131. Containers.EnsureContainer<ContainerSlot>(ent, GetPartSlotContainerId(connection));
  132. }
  133. foreach (var organ in ent.Comp.Organs.Keys)
  134. {
  135. Containers.EnsureContainer<ContainerSlot>(ent, GetOrganContainerId(organ));
  136. }
  137. }
  138. private void OnBodyPartRemove(Entity<BodyPartComponent> ent, ref ComponentRemove args)
  139. {
  140. if (ent.Comp.PartType == BodyPartType.Torso)
  141. _slots.RemoveItemSlot(ent, ent.Comp.ItemInsertionSlot);
  142. }
  143. private void OnPartEnableChanged(Entity<BodyPartComponent> partEnt, ref BodyPartEnableChangedEvent args)
  144. {
  145. if (!partEnt.Comp.CanEnable && args.Enabled)
  146. return;
  147. partEnt.Comp.Enabled = args.Enabled;
  148. if (args.Enabled)
  149. {
  150. EnablePart(partEnt);
  151. if (partEnt.Comp.Body is { Valid: true } bodyEnt)
  152. RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, true));
  153. }
  154. else
  155. {
  156. DisablePart(partEnt);
  157. if (partEnt.Comp.Body is { Valid: true } bodyEnt)
  158. RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, false));
  159. }
  160. Dirty(partEnt, partEnt.Comp);
  161. }
  162. private void EnablePart(Entity<BodyPartComponent> partEnt)
  163. {
  164. if (!TryComp(partEnt.Comp.Body, out BodyComponent? body))
  165. return;
  166. // I hate having to hardcode these checks so much.
  167. if (partEnt.Comp.PartType == BodyPartType.Leg)
  168. AddLeg(partEnt, (partEnt.Comp.Body.Value, body));
  169. if (partEnt.Comp.PartType == BodyPartType.Arm)
  170. {
  171. var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault();
  172. if (hand != default)
  173. {
  174. var ev = new BodyPartEnabledEvent(hand);
  175. RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
  176. }
  177. }
  178. if (partEnt.Comp.PartType == BodyPartType.Hand)
  179. {
  180. var ev = new BodyPartEnabledEvent(partEnt);
  181. RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
  182. }
  183. }
  184. /// <summary>
  185. /// Shitmed Change: This function handles dropping the items in an entity's slots if they lose all of a given part.
  186. /// Such as their hands, feet, head, etc.
  187. /// </summary>
  188. public void DropSlotContents(Entity<BodyPartComponent> partEnt)
  189. {
  190. if (partEnt.Comp.Body is not null
  191. && TryComp<InventoryComponent>(partEnt.Comp.Body, out var inventory) // Prevent error for non-humanoids
  192. && GetBodyPartCount(partEnt.Comp.Body.Value, partEnt.Comp.PartType) == 1
  193. && TryGetPartSlotContainerName(partEnt.Comp.PartType, out var containerNames))
  194. {
  195. foreach (var containerName in containerNames)
  196. _inventorySystem.DropSlotContents(partEnt.Comp.Body.Value, containerName, inventory);
  197. }
  198. }
  199. private void DisablePart(Entity<BodyPartComponent> partEnt)
  200. {
  201. if (!TryComp(partEnt.Comp.Body, out BodyComponent? body))
  202. return;
  203. if (partEnt.Comp.PartType == BodyPartType.Leg)
  204. RemoveLeg(partEnt, (partEnt.Comp.Body.Value, body));
  205. if (partEnt.Comp.PartType == BodyPartType.Arm)
  206. {
  207. var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault();
  208. if (hand != default)
  209. {
  210. var ev = new BodyPartDisabledEvent(hand);
  211. RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
  212. }
  213. }
  214. if (partEnt.Comp.PartType == BodyPartType.Hand)
  215. {
  216. var ev = new BodyPartDisabledEvent(partEnt);
  217. RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev);
  218. }
  219. }
  220. // TODO: Refactor this crap. I hate it so much.
  221. private void RemovePartEffect(Entity<BodyPartComponent> partEnt, Entity<BodyComponent?> bodyEnt)
  222. {
  223. if (TerminatingOrDeleted(bodyEnt)
  224. || !Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
  225. return;
  226. RemovePartChildren(partEnt, bodyEnt, bodyEnt.Comp);
  227. }
  228. protected void RemovePartChildren(Entity<BodyPartComponent> partEnt, EntityUid bodyEnt, BodyComponent? body = null)
  229. {
  230. if (!Resolve(bodyEnt, ref body, logMissing: false))
  231. return;
  232. if (partEnt.Comp.Children.Any())
  233. {
  234. foreach (var slotId in partEnt.Comp.Children.Keys)
  235. {
  236. if (Containers.TryGetContainer(partEnt, GetPartSlotContainerId(slotId), out var container) &&
  237. container is ContainerSlot slot &&
  238. slot.ContainedEntity is { } childEntity &&
  239. TryComp(childEntity, out BodyPartComponent? childPart))
  240. {
  241. var ev = new BodyPartEnableChangedEvent(false);
  242. RaiseLocalEvent(childEntity, ref ev);
  243. DropPart((childEntity, childPart));
  244. }
  245. }
  246. Dirty(bodyEnt, body);
  247. }
  248. }
  249. protected virtual void DropPart(Entity<BodyPartComponent> partEnt)
  250. {
  251. DropSlotContents(partEnt);
  252. // I don't know if this can cause issues, since any part that's being detached HAS to have a Body.
  253. // though I really just want the compiler to shut the fuck up.
  254. var body = partEnt.Comp.Body.GetValueOrDefault();
  255. if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted)
  256. {
  257. var enableEvent = new BodyPartEnableChangedEvent(false);
  258. RaiseLocalEvent(partEnt, ref enableEvent);
  259. var droppedEvent = new BodyPartDroppedEvent(partEnt);
  260. RaiseLocalEvent(body, ref droppedEvent);
  261. SharedTransform.AttachToGridOrMap(partEnt, transform);
  262. _randomHelper.RandomOffset(partEnt, 0.5f);
  263. }
  264. }
  265. private void OnAmputateAttempt(Entity<BodyPartComponent> partEnt, ref AmputateAttemptEvent args) =>
  266. DropPart(partEnt);
  267. // Shitmed Change End
  268. private void OnBodyPartInserted(Entity<BodyPartComponent> ent, ref EntInsertedIntoContainerMessage args)
  269. {
  270. // Body part inserted into another body part.
  271. var insertedUid = args.Entity;
  272. var slotId = args.Container.ID;
  273. if (ent.Comp.Body is null)
  274. return;
  275. if (TryComp(insertedUid, out BodyPartComponent? part) && slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part))) // Shitmed Change
  276. {
  277. AddPart(ent.Comp.Body.Value, (insertedUid, part), slotId);
  278. RecursiveBodyUpdate((insertedUid, part), ent.Comp.Body.Value);
  279. CheckBodyPart((insertedUid, part), GetTargetBodyPart(part), false); // Shitmed Change
  280. }
  281. if (TryComp(insertedUid, out OrganComponent? organ) && slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId)) // Shitmed Change
  282. {
  283. AddOrgan((insertedUid, organ), ent.Comp.Body.Value, ent);
  284. }
  285. }
  286. private void OnBodyPartRemoved(Entity<BodyPartComponent> ent, ref EntRemovedFromContainerMessage args)
  287. {
  288. // Body part removed from another body part.
  289. var removedUid = args.Entity;
  290. var slotId = args.Container.ID;
  291. // Shitmed Change Start
  292. if (TryComp(removedUid, out BodyPartComponent? part))
  293. {
  294. if (!slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part)))
  295. return;
  296. DebugTools.Assert(part.Body == ent.Comp.Body);
  297. if (part.Body is not null)
  298. {
  299. CheckBodyPart((removedUid, part), GetTargetBodyPart(part), true);
  300. RemovePart(part.Body.Value, (removedUid, part), slotId);
  301. RecursiveBodyUpdate((removedUid, part), null);
  302. }
  303. }
  304. if (TryComp(removedUid, out OrganComponent? organ))
  305. {
  306. if (!slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId))
  307. return;
  308. DebugTools.Assert(organ.Body == ent.Comp.Body);
  309. RemoveOrgan((removedUid, organ), ent);
  310. }
  311. // Shitmed Change End
  312. }
  313. private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyUid)
  314. {
  315. ent.Comp.Body = bodyUid;
  316. Dirty(ent, ent.Comp);
  317. foreach (var slotId in ent.Comp.Organs.Keys)
  318. {
  319. if (!Containers.TryGetContainer(ent, GetOrganContainerId(slotId), out var container))
  320. continue;
  321. foreach (var organ in container.ContainedEntities)
  322. {
  323. if (!TryComp(organ, out OrganComponent? organComp))
  324. continue;
  325. Dirty(organ, organComp);
  326. if (organComp.Body is { Valid: true } oldBodyUid)
  327. {
  328. var removedEv = new OrganRemovedFromBodyEvent(oldBodyUid, ent);
  329. RaiseLocalEvent(organ, ref removedEv);
  330. }
  331. organComp.Body = bodyUid;
  332. if (bodyUid is not null)
  333. {
  334. var addedEv = new OrganAddedToBodyEvent(bodyUid.Value, ent);
  335. RaiseLocalEvent(organ, ref addedEv);
  336. }
  337. }
  338. }
  339. // The code for RemovePartEffect() should live here, because it literally is the point of this recursive function.
  340. // But the debug asserts at the top plus existing tests need refactoring for this. So we'll be lazy.
  341. foreach (var slotId in ent.Comp.Children.Keys)
  342. {
  343. if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container))
  344. continue;
  345. foreach (var containedUid in container.ContainedEntities)
  346. {
  347. if (TryComp(containedUid, out BodyPartComponent? childPart))
  348. RecursiveBodyUpdate((containedUid, childPart), bodyUid);
  349. }
  350. }
  351. }
  352. protected virtual void AddPart(
  353. Entity<BodyComponent?> bodyEnt,
  354. Entity<BodyPartComponent> partEnt,
  355. string slotId)
  356. {
  357. Dirty(partEnt, partEnt.Comp);
  358. partEnt.Comp.Body = bodyEnt;
  359. if (partEnt.Comp.Enabled && partEnt.Comp.Body is { Valid: true } body) // Shitmed Change
  360. RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, true));
  361. var ev = new BodyPartAddedEvent(slotId, partEnt);
  362. RaiseLocalEvent(bodyEnt, ref ev);
  363. AddLeg(partEnt, bodyEnt);
  364. }
  365. protected virtual void RemovePart(
  366. Entity<BodyComponent?> bodyEnt,
  367. Entity<BodyPartComponent> partEnt,
  368. string slotId)
  369. {
  370. Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false);
  371. Dirty(partEnt, partEnt.Comp);
  372. // Shitmed Change Start
  373. if (partEnt.Comp.Body is { Valid: true } body)
  374. RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, false));
  375. partEnt.Comp.ParentSlot = null;
  376. // Shitmed Change End
  377. var ev = new BodyPartRemovedEvent(slotId, partEnt);
  378. RaiseLocalEvent(bodyEnt, ref ev);
  379. RemoveLeg(partEnt, bodyEnt);
  380. RemovePartEffect(partEnt, bodyEnt); // Shitmed Change
  381. PartRemoveDamage(bodyEnt, partEnt);
  382. }
  383. private void AddLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
  384. {
  385. if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
  386. return;
  387. if (legEnt.Comp.PartType == BodyPartType.Leg)
  388. {
  389. bodyEnt.Comp.LegEntities.Add(legEnt);
  390. UpdateMovementSpeed(bodyEnt);
  391. Dirty(bodyEnt, bodyEnt.Comp);
  392. }
  393. }
  394. private void RemoveLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
  395. {
  396. if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
  397. return;
  398. if (legEnt.Comp.PartType == BodyPartType.Leg)
  399. {
  400. bodyEnt.Comp.LegEntities.Remove(legEnt);
  401. UpdateMovementSpeed(bodyEnt);
  402. Dirty(bodyEnt, bodyEnt.Comp);
  403. Standing.Down(bodyEnt); // Shitmed Change
  404. }
  405. }
  406. // Shitmed Change: made virtual, bleeding damage is done on server
  407. protected virtual void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
  408. {
  409. }
  410. /// <summary>
  411. /// Tries to get the parent body part to this if applicable.
  412. /// Doesn't validate if it's a part of body system.
  413. /// </summary>
  414. public EntityUid? GetParentPartOrNull(EntityUid uid)
  415. {
  416. if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
  417. return null;
  418. var parent = container.Owner;
  419. if (!HasComp<BodyPartComponent>(parent))
  420. return null;
  421. return parent;
  422. }
  423. /// <summary>
  424. /// Tries to get the parent body part and slot to this if applicable.
  425. /// </summary>
  426. public (EntityUid Parent, string Slot)? GetParentPartAndSlotOrNull(EntityUid uid)
  427. {
  428. if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
  429. return null;
  430. var slotId = GetPartSlotContainerIdFromContainer(container.ID);
  431. if (string.IsNullOrEmpty(slotId))
  432. return null;
  433. var parent = container.Owner;
  434. if (!TryComp<BodyPartComponent>(parent, out var parentBody)
  435. || !parentBody.Children.ContainsKey(slotId))
  436. return null;
  437. return (parent, slotId);
  438. }
  439. /// <summary>
  440. /// Tries to get the relevant parent body part to this if it exists.
  441. /// It won't exist if this is the root body part or if it's not in a body.
  442. /// </summary>
  443. public bool TryGetParentBodyPart(
  444. EntityUid partUid,
  445. [NotNullWhen(true)] out EntityUid? parentUid,
  446. [NotNullWhen(true)] out BodyPartComponent? parentComponent)
  447. {
  448. DebugTools.Assert(HasComp<BodyPartComponent>(partUid));
  449. parentUid = null;
  450. parentComponent = null;
  451. if (Containers.TryGetContainingContainer((partUid, null, null), out var container) &&
  452. TryComp(container.Owner, out parentComponent))
  453. {
  454. parentUid = container.Owner;
  455. return true;
  456. }
  457. return false;
  458. }
  459. #region Slots
  460. /// <summary>
  461. /// Creates a BodyPartSlot on the specified partUid.
  462. /// </summary>
  463. private BodyPartSlot? CreatePartSlot(
  464. EntityUid partUid,
  465. string slotId,
  466. BodyPartType partType,
  467. BodyPartComponent? part = null)
  468. {
  469. if (!Resolve(partUid, ref part, logMissing: false))
  470. return null;
  471. Containers.EnsureContainer<ContainerSlot>(partUid, GetPartSlotContainerId(slotId));
  472. // Shitmed Change: Don't throw if the slot already exists
  473. if (part.Children.TryGetValue(slotId, out var existing))
  474. return existing;
  475. var partSlot = new BodyPartSlot(slotId, partType);
  476. part.Children.Add(slotId, partSlot);
  477. Dirty(partUid, part);
  478. return partSlot;
  479. }
  480. /// <summary>
  481. /// Tries to create a BodyPartSlot on the specified partUid.
  482. /// </summary>
  483. /// <returns>false if not relevant or can't add it.</returns>
  484. public bool TryCreatePartSlot(
  485. EntityUid? partId,
  486. string slotId,
  487. BodyPartType partType,
  488. [NotNullWhen(true)] out BodyPartSlot? slot,
  489. BodyPartComponent? part = null)
  490. {
  491. slot = null;
  492. if (partId is null
  493. || !Resolve(partId.Value, ref part, logMissing: false))
  494. {
  495. return false;
  496. }
  497. Containers.EnsureContainer<ContainerSlot>(partId.Value, GetPartSlotContainerId(slotId));
  498. slot = new BodyPartSlot(slotId, partType);
  499. if (!part.Children.ContainsKey(slotId) // Shitmed Change
  500. && !part.Children.TryAdd(slotId, slot.Value))
  501. return false;
  502. Dirty(partId.Value, part);
  503. return true;
  504. }
  505. public bool TryCreatePartSlotAndAttach(
  506. EntityUid parentId,
  507. string slotId,
  508. EntityUid childId,
  509. BodyPartType partType,
  510. BodyPartComponent? parent = null,
  511. BodyPartComponent? child = null)
  512. {
  513. return TryCreatePartSlot(parentId, slotId, partType, out _, parent)
  514. && AttachPart(parentId, slotId, childId, parent, child);
  515. }
  516. #endregion
  517. #region RootPartManagement
  518. /// <summary>
  519. /// Returns true if the partId is the root body container for the specified bodyId.
  520. /// </summary>
  521. public bool IsPartRoot(
  522. EntityUid bodyId,
  523. EntityUid partId,
  524. BodyComponent? body = null,
  525. BodyPartComponent? part = null)
  526. {
  527. return Resolve(partId, ref part)
  528. && Resolve(bodyId, ref body)
  529. && Containers.TryGetContainingContainer(bodyId, partId, out var container)
  530. && container.ID == BodyRootContainerId;
  531. }
  532. /// <summary>
  533. /// Returns true if we can attach the partId to the bodyId as the root entity.
  534. /// </summary>
  535. public bool CanAttachToRoot(
  536. EntityUid bodyId,
  537. EntityUid partId,
  538. BodyComponent? body = null,
  539. BodyPartComponent? part = null)
  540. {
  541. return Resolve(bodyId, ref body)
  542. && Resolve(partId, ref part)
  543. && body.RootContainer.ContainedEntity is null
  544. && bodyId != part.Body;
  545. }
  546. /// <summary>
  547. /// Returns the root part of this body if it exists.
  548. /// </summary>
  549. public (EntityUid Entity, BodyPartComponent BodyPart)? GetRootPartOrNull(EntityUid bodyId, BodyComponent? body = null)
  550. {
  551. if (!Resolve(bodyId, ref body)
  552. || body.RootContainer.ContainedEntity is null)
  553. {
  554. return null;
  555. }
  556. return (body.RootContainer.ContainedEntity.Value,
  557. Comp<BodyPartComponent>(body.RootContainer.ContainedEntity.Value));
  558. }
  559. /// <summary>
  560. /// Returns true if the partId can be attached to the parentId in the specified slot.
  561. /// </summary>
  562. public bool CanAttachPart(
  563. EntityUid parentId,
  564. BodyPartSlot slot,
  565. EntityUid partId,
  566. BodyPartComponent? parentPart = null,
  567. BodyPartComponent? part = null)
  568. {
  569. return Resolve(partId, ref part, logMissing: false)
  570. && Resolve(parentId, ref parentPart, logMissing: false)
  571. && CanAttachPart(parentId, slot.Id, partId, parentPart, part);
  572. }
  573. /// <summary>
  574. /// Returns true if we can attach the specified partId to the parentId in the specified slot.
  575. /// </summary>
  576. public bool CanAttachPart(
  577. EntityUid parentId,
  578. string slotId,
  579. EntityUid partId,
  580. BodyPartComponent? parentPart = null,
  581. BodyPartComponent? part = null)
  582. {
  583. return Resolve(partId, ref part, logMissing: false)
  584. && Resolve(parentId, ref parentPart, logMissing: false)
  585. && parentPart.Children.TryGetValue(slotId, out var parentSlotData)
  586. && part.PartType == parentSlotData.Type
  587. && Containers.TryGetContainer(parentId, GetPartSlotContainerId(slotId), out var container)
  588. && Containers.CanInsert(partId, container);
  589. }
  590. /// <summary>
  591. /// Shitmed Change: Returns true if this parentId supports attaching a new part to the specified slot.
  592. /// </summary>
  593. public bool CanAttachToSlot(
  594. EntityUid parentId,
  595. string slotId,
  596. BodyPartComponent? parentPart = null)
  597. {
  598. return Resolve(parentId, ref parentPart, logMissing: false)
  599. && parentPart.Children.ContainsKey(slotId);
  600. }
  601. public bool AttachPartToRoot(
  602. EntityUid bodyId,
  603. EntityUid partId,
  604. BodyComponent? body = null,
  605. BodyPartComponent? part = null)
  606. {
  607. return Resolve(bodyId, ref body)
  608. && Resolve(partId, ref part)
  609. && CanAttachToRoot(bodyId, partId, body, part)
  610. && Containers.Insert(partId, body.RootContainer);
  611. }
  612. #endregion
  613. #region Attach/Detach
  614. /// <summary>
  615. /// Attaches a body part to the specified body part parent.
  616. /// </summary>
  617. public bool AttachPart(
  618. EntityUid parentPartId,
  619. string slotId,
  620. EntityUid partId,
  621. BodyPartComponent? parentPart = null,
  622. BodyPartComponent? part = null)
  623. {
  624. return Resolve(parentPartId, ref parentPart, logMissing: false)
  625. && parentPart.Children.TryGetValue(slotId, out var slot)
  626. && AttachPart(parentPartId, slot, partId, parentPart, part);
  627. }
  628. /// <summary>
  629. /// Attaches a body part to the specified body part parent.
  630. /// </summary>
  631. public bool AttachPart(
  632. EntityUid parentPartId,
  633. BodyPartSlot slot,
  634. EntityUid partId,
  635. BodyPartComponent? parentPart = null,
  636. BodyPartComponent? part = null)
  637. {
  638. if (!Resolve(parentPartId, ref parentPart, logMissing: false)
  639. || !Resolve(partId, ref part, logMissing: false)
  640. || !CanAttachPart(parentPartId, slot.Id, partId, parentPart, part)
  641. || !parentPart.Children.ContainsKey(slot.Id))
  642. {
  643. return false;
  644. }
  645. if (!Containers.TryGetContainer(parentPartId, GetPartSlotContainerId(slot.Id), out var container))
  646. {
  647. DebugTools.Assert($"Unable to find body slot {slot.Id} for {ToPrettyString(parentPartId)}");
  648. return false;
  649. }
  650. part.ParentSlot = slot;
  651. if (TryComp(parentPart.Body, out HumanoidAppearanceComponent? bodyAppearance)
  652. && !HasComp<BodyPartAppearanceComponent>(partId)
  653. && !TerminatingOrDeleted(parentPartId)
  654. && !TerminatingOrDeleted(partId)) // Saw some exceptions involving these due to the spawn menu.
  655. EnsureComp<BodyPartAppearanceComponent>(partId);
  656. return Containers.Insert(partId, container);
  657. }
  658. #endregion
  659. #region Misc
  660. public void UpdateMovementSpeed(
  661. EntityUid bodyId,
  662. BodyComponent? body = null,
  663. MovementSpeedModifierComponent? movement = null)
  664. {
  665. if (!Resolve(bodyId, ref body, ref movement, logMissing: false)
  666. || body.RequiredLegs <= 0)
  667. {
  668. return;
  669. }
  670. var walkSpeed = 0f;
  671. var sprintSpeed = 0f;
  672. var acceleration = 0f;
  673. foreach (var legEntity in body.LegEntities)
  674. {
  675. if (!TryComp<MovementBodyPartComponent>(legEntity, out var legModifier))
  676. continue;
  677. walkSpeed += legModifier.WalkSpeed;
  678. sprintSpeed += legModifier.SprintSpeed;
  679. acceleration += legModifier.Acceleration;
  680. }
  681. walkSpeed /= body.RequiredLegs;
  682. sprintSpeed /= body.RequiredLegs;
  683. acceleration /= body.RequiredLegs;
  684. Movement.ChangeBaseSpeed(bodyId, walkSpeed, sprintSpeed, acceleration, movement);
  685. }
  686. #endregion
  687. #region Queries
  688. /// <summary>
  689. /// Get all organs for the specified body part.
  690. /// </summary>
  691. public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid partId, BodyPartComponent? part = null)
  692. {
  693. if (!Resolve(partId, ref part, logMissing: false))
  694. yield break;
  695. foreach (var slotId in part.Organs.Keys)
  696. {
  697. var containerSlotId = GetOrganContainerId(slotId);
  698. if (!Containers.TryGetContainer(partId, containerSlotId, out var container))
  699. continue;
  700. foreach (var containedEnt in container.ContainedEntities)
  701. {
  702. if (!TryComp(containedEnt, out OrganComponent? organ))
  703. continue;
  704. yield return (containedEnt, organ);
  705. }
  706. }
  707. }
  708. /// <summary>
  709. /// Gets all BaseContainers for body parts on this entity and its child entities.
  710. /// </summary>
  711. public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartComponent? part = null)
  712. {
  713. if (!Resolve(id, ref part, logMissing: false) ||
  714. part.Children.Count == 0)
  715. {
  716. yield break;
  717. }
  718. foreach (var slotId in part.Children.Keys)
  719. {
  720. var containerSlotId = GetPartSlotContainerId(slotId);
  721. if (!Containers.TryGetContainer(id, containerSlotId, out var container))
  722. continue;
  723. yield return container;
  724. foreach (var ent in container.ContainedEntities)
  725. {
  726. foreach (var childContainer in GetPartContainers(ent))
  727. {
  728. yield return childContainer;
  729. }
  730. }
  731. }
  732. }
  733. /// <summary>
  734. /// Returns all body part components for this entity including itself.
  735. /// </summary>
  736. public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyPartChildren(
  737. EntityUid partId,
  738. BodyPartComponent? part = null)
  739. {
  740. if (!Resolve(partId, ref part, logMissing: false))
  741. yield break;
  742. yield return (partId, part);
  743. foreach (var slotId in part.Children.Keys)
  744. {
  745. var containerSlotId = GetPartSlotContainerId(slotId);
  746. if (Containers.TryGetContainer(partId, containerSlotId, out var container))
  747. {
  748. foreach (var containedEnt in container.ContainedEntities)
  749. {
  750. if (!TryComp(containedEnt, out BodyPartComponent? childPart))
  751. continue;
  752. foreach (var value in GetBodyPartChildren(containedEnt, childPart))
  753. {
  754. yield return value;
  755. }
  756. }
  757. }
  758. }
  759. }
  760. /// <summary>
  761. /// Returns all body part slots for this entity.
  762. /// </summary>
  763. public IEnumerable<BodyPartSlot> GetAllBodyPartSlots(
  764. EntityUid partId,
  765. BodyPartComponent? part = null)
  766. {
  767. if (!Resolve(partId, ref part, logMissing: false))
  768. yield break;
  769. foreach (var (slotId, slot) in part.Children)
  770. {
  771. yield return slot;
  772. var containerSlotId = GetOrganContainerId(slotId);
  773. if (Containers.TryGetContainer(partId, containerSlotId, out var container))
  774. {
  775. foreach (var containedEnt in container.ContainedEntities)
  776. {
  777. if (!TryComp(containedEnt, out BodyPartComponent? childPart))
  778. continue;
  779. foreach (var subSlot in GetAllBodyPartSlots(containedEnt, childPart))
  780. {
  781. yield return subSlot;
  782. }
  783. }
  784. }
  785. }
  786. }
  787. /// <summary>
  788. /// Returns true if the bodyId has any parts of this type.
  789. /// </summary>
  790. public bool BodyHasPartType(
  791. EntityUid bodyId,
  792. BodyPartType type,
  793. BodyComponent? body = null)
  794. {
  795. return GetBodyChildrenOfType(bodyId, type, body).Any();
  796. }
  797. /// <summary>
  798. /// Returns true if the parentId has the specified childId.
  799. /// </summary>
  800. public bool PartHasChild(
  801. EntityUid parentId,
  802. EntityUid childId,
  803. BodyPartComponent? parent,
  804. BodyPartComponent? child)
  805. {
  806. if (!Resolve(parentId, ref parent, logMissing: false)
  807. || !Resolve(childId, ref child, logMissing: false))
  808. {
  809. return false;
  810. }
  811. foreach (var (foundId, _) in GetBodyPartChildren(parentId, parent))
  812. {
  813. if (foundId == childId)
  814. return true;
  815. }
  816. return false;
  817. }
  818. /// <summary>
  819. /// Returns true if the bodyId has the specified partId.
  820. /// </summary>
  821. public bool BodyHasChild(
  822. EntityUid bodyId,
  823. EntityUid partId,
  824. BodyComponent? body = null,
  825. BodyPartComponent? part = null)
  826. {
  827. return Resolve(bodyId, ref body, logMissing: false)
  828. && body.RootContainer.ContainedEntity is not null
  829. && Resolve(partId, ref part, logMissing: false)
  830. && TryComp(body.RootContainer.ContainedEntity, out BodyPartComponent? rootPart)
  831. && PartHasChild(body.RootContainer.ContainedEntity.Value, partId, rootPart, part);
  832. }
  833. public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(
  834. EntityUid bodyId,
  835. BodyPartType type,
  836. BodyComponent? body = null,
  837. // Shitmed Change
  838. BodyPartSymmetry? symmetry = null)
  839. {
  840. foreach (var part in GetBodyChildren(bodyId, body))
  841. {
  842. if (part.Component.PartType == type && (symmetry == null || part.Component.Symmetry == symmetry)) // Shitmed Change
  843. yield return part;
  844. }
  845. }
  846. /// <summary>
  847. /// Returns a list of ValueTuples of <see cref="T"/> and OrganComponent on each organ
  848. /// in the given part.
  849. /// </summary>
  850. /// <param name="uid">The part entity id to check on.</param>
  851. /// <param name="part">The part to check for organs on.</param>
  852. /// <typeparam name="T">The component to check for.</typeparam>
  853. public List<(T Comp, OrganComponent Organ)> GetBodyPartOrganComponents<T>(
  854. EntityUid uid,
  855. BodyPartComponent? part = null)
  856. where T : IComponent
  857. {
  858. if (!Resolve(uid, ref part))
  859. return new List<(T Comp, OrganComponent Organ)>();
  860. var query = GetEntityQuery<T>();
  861. var list = new List<(T Comp, OrganComponent Organ)>();
  862. foreach (var organ in GetPartOrgans(uid, part))
  863. {
  864. if (query.TryGetComponent(organ.Id, out var comp))
  865. list.Add((comp, organ.Component));
  866. }
  867. return list;
  868. }
  869. /// <summary>
  870. /// Tries to get a list of ValueTuples of <see cref="T"/> and OrganComponent on each organs
  871. /// in the given part.
  872. /// </summary>
  873. /// <param name="uid">The part entity id to check on.</param>
  874. /// <param name="comps">The list of components.</param>
  875. /// <param name="part">The part to check for organs on.</param>
  876. /// <typeparam name="T">The component to check for.</typeparam>
  877. /// <returns>Whether any were found.</returns>
  878. public bool TryGetBodyPartOrganComponents<T>(
  879. EntityUid uid,
  880. [NotNullWhen(true)] out List<(T Comp, OrganComponent Organ)>? comps,
  881. BodyPartComponent? part = null)
  882. where T : IComponent
  883. {
  884. if (!Resolve(uid, ref part))
  885. {
  886. comps = null;
  887. return false;
  888. }
  889. comps = GetBodyPartOrganComponents<T>(uid, part);
  890. if (comps.Count != 0)
  891. return true;
  892. comps = null;
  893. return false;
  894. }
  895. // Shitmed Change Start
  896. /// <summary>
  897. /// Tries to get a list of ValueTuples of EntityUid and OrganComponent on each organ
  898. /// in the given part.
  899. /// </summary>
  900. /// <param name="uid">The part entity id to check on.</param>
  901. /// <param name="type">The type of component to check for.</param>
  902. /// <param name="part">The part to check for organs on.</param>
  903. /// <param name="organs">The organs found on the body part.</param>
  904. /// <returns>Whether any were found.</returns>
  905. /// <remarks>
  906. /// This method is somewhat of a copout to the fact that we can't use reflection to generically
  907. /// get the type of component on runtime due to sandboxing. So we simply do a HasComp check for each organ.
  908. /// </remarks>
  909. public bool TryGetBodyPartOrgans(
  910. EntityUid uid,
  911. Type type,
  912. [NotNullWhen(true)] out List<(EntityUid Id, OrganComponent Organ)>? organs,
  913. BodyPartComponent? part = null)
  914. {
  915. if (!Resolve(uid, ref part))
  916. {
  917. organs = null;
  918. return false;
  919. }
  920. var list = new List<(EntityUid Id, OrganComponent Organ)>();
  921. foreach (var organ in GetPartOrgans(uid, part))
  922. {
  923. if (HasComp(organ.Id, type))
  924. list.Add((organ.Id, organ.Component));
  925. }
  926. if (list.Count != 0)
  927. {
  928. organs = list;
  929. return true;
  930. }
  931. organs = null;
  932. return false;
  933. }
  934. private bool TryGetPartSlotContainerName(BodyPartType partType, out HashSet<string> containerNames)
  935. {
  936. containerNames = partType switch
  937. {
  938. BodyPartType.Hand => new() { "gloves" },
  939. BodyPartType.Foot => new() { "shoes" },
  940. BodyPartType.Head => new() { "eyes", "ears", "head", "mask" },
  941. _ => new()
  942. };
  943. return containerNames.Count > 0;
  944. }
  945. private bool TryGetPartFromSlotContainer(string slot, out BodyPartType? partType)
  946. {
  947. partType = slot switch
  948. {
  949. "gloves" => BodyPartType.Hand,
  950. "shoes" => BodyPartType.Foot,
  951. "eyes" or "ears" or "head" or "mask" => BodyPartType.Head,
  952. _ => null
  953. };
  954. return partType is not null;
  955. }
  956. public int GetBodyPartCount(EntityUid bodyId, BodyPartType partType, BodyComponent? body = null)
  957. {
  958. if (!Resolve(bodyId, ref body, logMissing: false))
  959. return 0;
  960. int count = 0;
  961. foreach (var part in GetBodyChildren(bodyId, body))
  962. {
  963. if (part.Component.PartType == partType)
  964. count++;
  965. }
  966. return count;
  967. }
  968. public string GetSlotFromBodyPart(BodyPartComponent? part)
  969. {
  970. var slotName = "";
  971. if (part is null)
  972. return slotName;
  973. if (part.SlotId != "")
  974. slotName = part.SlotId;
  975. else
  976. slotName = part.PartType.ToString().ToLower();
  977. if (part.Symmetry != BodyPartSymmetry.None)
  978. return $"{part.Symmetry.ToString().ToLower()} {slotName}";
  979. else
  980. return slotName;
  981. }
  982. // Shitmed Change End
  983. /// <summary>
  984. /// Gets the parent body part and all immediate child body parts for the partId.
  985. /// </summary>
  986. public IEnumerable<EntityUid> GetBodyPartAdjacentParts(
  987. EntityUid partId,
  988. BodyPartComponent? part = null)
  989. {
  990. if (!Resolve(partId, ref part, logMissing: false))
  991. yield break;
  992. if (TryGetParentBodyPart(partId, out var parentUid, out _))
  993. yield return parentUid.Value;
  994. foreach (var slotId in part.Children.Keys)
  995. {
  996. var container = Containers.GetContainer(partId, GetPartSlotContainerId(slotId));
  997. foreach (var containedEnt in container.ContainedEntities)
  998. {
  999. yield return containedEnt;
  1000. }
  1001. }
  1002. }
  1003. public IEnumerable<(EntityUid AdjacentId, T Component)> GetBodyPartAdjacentPartsComponents<T>(
  1004. EntityUid partId,
  1005. BodyPartComponent? part = null)
  1006. where T : IComponent
  1007. {
  1008. if (!Resolve(partId, ref part, logMissing: false))
  1009. yield break;
  1010. var query = GetEntityQuery<T>();
  1011. foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part))
  1012. {
  1013. if (query.TryGetComponent(adjacentId, out var component))
  1014. yield return (adjacentId, component);
  1015. }
  1016. }
  1017. public bool TryGetBodyPartAdjacentPartsComponents<T>(
  1018. EntityUid partId,
  1019. [NotNullWhen(true)] out List<(EntityUid AdjacentId, T Component)>? comps,
  1020. BodyPartComponent? part = null)
  1021. where T : IComponent
  1022. {
  1023. if (!Resolve(partId, ref part, logMissing: false))
  1024. {
  1025. comps = null;
  1026. return false;
  1027. }
  1028. var query = GetEntityQuery<T>();
  1029. comps = new List<(EntityUid AdjacentId, T Component)>();
  1030. foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part))
  1031. {
  1032. if (query.TryGetComponent(adjacentId, out var component))
  1033. comps.Add((adjacentId, component));
  1034. }
  1035. if (comps.Count != 0)
  1036. return true;
  1037. comps = null;
  1038. return false;
  1039. }
  1040. #endregion
  1041. }