SharedBodySystem.Body.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. // SPDX-FileCopyrightText: 2022 Jezithyr <Jezithyr@gmail.com>
  2. // SPDX-FileCopyrightText: 2022 Paul Ritter <ritter.paul1@googlemail.com>
  3. // SPDX-FileCopyrightText: 2022 keronshb <54602815+keronshb@users.noreply.github.com>
  4. // SPDX-FileCopyrightText: 2022 metalgearsloth <metalgearsloth@gmail.com>
  5. // SPDX-FileCopyrightText: 2023 Doru991 <75124791+Doru991@users.noreply.github.com>
  6. // SPDX-FileCopyrightText: 2023 DrSmugleaf <DrSmugleaf@users.noreply.github.com>
  7. // SPDX-FileCopyrightText: 2023 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
  8. // SPDX-FileCopyrightText: 2023 Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
  9. // SPDX-FileCopyrightText: 2023 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com>
  10. // SPDX-FileCopyrightText: 2023 TemporalOroboros <TemporalOroboros@gmail.com>
  11. // SPDX-FileCopyrightText: 2023 metalgearsloth <comedian_vs_clown@hotmail.com>
  12. // SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me>
  13. // SPDX-FileCopyrightText: 2024 Jezithyr <jezithyr@gmail.com>
  14. // SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
  15. // SPDX-FileCopyrightText: 2024 ShadowCommander <shadowjjt@gmail.com>
  16. // SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
  17. // SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
  18. // SPDX-FileCopyrightText: 2024 username <113782077+whateverusername0@users.noreply.github.com>
  19. // SPDX-FileCopyrightText: 2024 whateverusername0 <whateveremail>
  20. // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
  21. // SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
  22. // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
  23. // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
  24. //
  25. // SPDX-License-Identifier: AGPL-3.0-or-later
  26. using System.Linq;
  27. using System.Numerics;
  28. using Content.Shared.Body.Components;
  29. using Content.Shared.Body.Organ;
  30. using Content.Shared.Body.Part;
  31. using Content.Shared.Body.Prototypes;
  32. using Content.Shared.DragDrop;
  33. using Content.Shared.Gibbing.Components;
  34. using Content.Shared.Gibbing.Events;
  35. using Content.Shared.Gibbing.Systems;
  36. using Content.Shared.Inventory;
  37. using Robust.Shared.Audio;
  38. using Robust.Shared.Audio.Systems;
  39. using Robust.Shared.Containers;
  40. using Robust.Shared.Map;
  41. using Robust.Shared.Utility;
  42. // Shitmed Change
  43. using Content.Shared._Shitmed.Body.Events;
  44. using Content.Shared._Shitmed.Body.Part;
  45. using Content.Shared._Shitmed.Humanoid.Events;
  46. using Content.Shared._Shitmed.Medical.Surgery;
  47. using Content.Shared.Silicons.Borgs.Components;
  48. using Content.Shared.Containers.ItemSlots;
  49. using Content.Shared.Humanoid;
  50. using Content.Shared.Inventory.Events;
  51. using Content.Shared.Pulling.Events;
  52. using Content.Shared.Standing;
  53. using Robust.Shared.Network;
  54. using Robust.Shared.Timing;
  55. namespace Content.Shared.Body.Systems;
  56. public partial class SharedBodySystem
  57. {
  58. /*
  59. * tl;dr of how bobby works
  60. * - BodyComponent uses a BodyPrototype as a template.
  61. * - On MapInit we spawn the root entity in the prototype and spawn all connections outwards from here
  62. * - Each "connection" is a body part (e.g. arm, hand, etc.) and each part can also contain organs.
  63. */
  64. [Dependency] private readonly InventorySystem _inventory = default!;
  65. [Dependency] private readonly GibbingSystem _gibbingSystem = default!;
  66. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  67. [Dependency] private readonly ItemSlotsSystem _slots = default!; // Shitmed Change
  68. [Dependency] private readonly IGameTiming _gameTiming = default!; // Shitmed Change
  69. private const float GibletLaunchImpulse = 8;
  70. private const float GibletLaunchImpulseVariance = 3;
  71. private void InitializeBody()
  72. {
  73. // Body here to handle root body parts.
  74. SubscribeLocalEvent<BodyComponent, EntInsertedIntoContainerMessage>(OnBodyInserted);
  75. SubscribeLocalEvent<BodyComponent, EntRemovedFromContainerMessage>(OnBodyRemoved);
  76. SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
  77. SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
  78. SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
  79. SubscribeLocalEvent<BodyComponent, StandAttemptEvent>(OnStandAttempt); // Shitmed Change
  80. SubscribeLocalEvent<BodyComponent, ProfileLoadFinishedEvent>(OnProfileLoadFinished); // Shitmed change
  81. SubscribeLocalEvent<BodyComponent, IsEquippingAttemptEvent>(OnBeingEquippedAttempt); // Shitmed Change
  82. }
  83. private void OnAttemptStopPulling(Entity<BodyComponent> ent, ref AttemptStopPullingEvent args) // Goobstation
  84. {
  85. if (args.User == null || !Exists(args.User.Value))
  86. return;
  87. if (args.User.Value != ent.Owner)
  88. return;
  89. if (ent.Comp.LegEntities.Count > 0 || ent.Comp.RequiredLegs == 0)
  90. return;
  91. args.Cancelled = true;
  92. }
  93. private void OnBodyInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
  94. {
  95. // Root body part?
  96. var slotId = args.Container.ID;
  97. if (slotId != BodyRootContainerId)
  98. return;
  99. var insertedUid = args.Entity;
  100. if (TryComp(insertedUid, out BodyPartComponent? part))
  101. {
  102. AddPart((ent, ent), (insertedUid, part), slotId);
  103. RecursiveBodyUpdate((insertedUid, part), ent);
  104. }
  105. if (TryComp(insertedUid, out OrganComponent? organ))
  106. {
  107. AddOrgan((insertedUid, organ), ent, ent);
  108. }
  109. }
  110. private void OnBodyRemoved(Entity<BodyComponent> ent, ref EntRemovedFromContainerMessage args)
  111. {
  112. // Root body part?
  113. var slotId = args.Container.ID;
  114. if (slotId != BodyRootContainerId)
  115. return;
  116. var removedUid = args.Entity;
  117. DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent);
  118. DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent);
  119. if (TryComp(removedUid, out BodyPartComponent? part))
  120. {
  121. RemovePart((ent, ent), (removedUid, part), slotId);
  122. RecursiveBodyUpdate((removedUid, part), null);
  123. }
  124. if (TryComp(removedUid, out OrganComponent? organ))
  125. RemoveOrgan((removedUid, organ), ent);
  126. }
  127. private void OnBodyInit(Entity<BodyComponent> ent, ref ComponentInit args)
  128. {
  129. // Setup the initial container.
  130. ent.Comp.RootContainer = Containers.EnsureContainer<ContainerSlot>(ent, BodyRootContainerId);
  131. }
  132. private void OnBodyMapInit(Entity<BodyComponent> ent, ref MapInitEvent args)
  133. {
  134. if (ent.Comp.Prototype is null)
  135. return;
  136. // One-time setup
  137. // Obviously can't run in Init to avoid double-spawns on save / load.
  138. var prototype = Prototypes.Index(ent.Comp.Prototype.Value);
  139. MapInitBody(ent, prototype);
  140. EnsureComp<SurgeryTargetComponent>(ent); // Shitmed change
  141. }
  142. private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
  143. {
  144. var protoRoot = prototype.Slots[prototype.Root];
  145. if (protoRoot.Part is null)
  146. return;
  147. // This should already handle adding the entity to the root.
  148. var rootPartUid = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId);
  149. var rootPart = Comp<BodyPartComponent>(rootPartUid);
  150. rootPart.Body = bodyEntity;
  151. Dirty(rootPartUid, rootPart);
  152. // Setup the rest of the body entities.
  153. SetupOrgans((rootPartUid, rootPart), protoRoot.Organs);
  154. MapInitParts(rootPartUid, rootPart, prototype); // Shitmed Change
  155. }
  156. private void OnBodyCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
  157. {
  158. args.Handled = true;
  159. }
  160. /// <summary>
  161. /// Sets up all of the relevant body parts for a particular body entity and root part.
  162. /// </summary>
  163. private void MapInitParts(EntityUid rootPartId, BodyPartComponent rootPart, BodyPrototype prototype) // Shitmed Change
  164. {
  165. // Start at the root part and traverse the body graph, setting up parts as we go.
  166. // Basic BFS pathfind.
  167. var rootSlot = prototype.Root;
  168. var frontier = new Queue<string>();
  169. frontier.Enqueue(rootSlot);
  170. // Child -> Parent connection.
  171. var cameFrom = new Dictionary<string, string>();
  172. cameFrom[rootSlot] = rootSlot;
  173. // Maps slot to its relevant entity.
  174. var cameFromEntities = new Dictionary<string, EntityUid>();
  175. cameFromEntities[rootSlot] = rootPartId;
  176. while (frontier.TryDequeue(out var currentSlotId))
  177. {
  178. var currentSlot = prototype.Slots[currentSlotId];
  179. foreach (var connection in currentSlot.Connections)
  180. {
  181. // Already been handled
  182. if (!cameFrom.TryAdd(connection, currentSlotId))
  183. continue;
  184. // Setup part
  185. var connectionSlot = prototype.Slots[connection];
  186. var parentEntity = cameFromEntities[currentSlotId];
  187. var parentPartComponent = Comp<BodyPartComponent>(parentEntity);
  188. // Spawn the entity on the target
  189. // then get the body part type, create the slot, and finally
  190. // we can insert it into the container.
  191. var childPart = Spawn(connectionSlot.Part, new EntityCoordinates(parentEntity, Vector2.Zero));
  192. cameFromEntities[connection] = childPart;
  193. var childPartComponent = Comp<BodyPartComponent>(childPart);
  194. TryCreatePartSlot(parentEntity, connection, childPartComponent.PartType, out var partSlot, parentPartComponent);
  195. // Shitmed Change Start
  196. childPartComponent.ParentSlot = partSlot;
  197. Dirty(childPart, childPartComponent);
  198. // Shitmed Change End
  199. var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection));
  200. if (partSlot is null || !Containers.Insert(childPart, cont))
  201. {
  202. Log.Error($"Could not create slot for connection {connection} in body {prototype.ID}");
  203. QueueDel(childPart);
  204. continue;
  205. }
  206. // Add organs
  207. SetupOrgans((childPart, childPartComponent), connectionSlot.Organs);
  208. // Enqueue it so we can also get its neighbors.
  209. frontier.Enqueue(connection);
  210. }
  211. }
  212. }
  213. private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, string> organs)
  214. {
  215. foreach (var (organSlotId, organProto) in organs)
  216. {
  217. TryCreateOrganSlot(ent, organSlotId, out var slot); // Shitmed Change
  218. SpawnInContainerOrDrop(organProto, ent, GetOrganContainerId(organSlotId));
  219. if (slot is null)
  220. {
  221. Log.Error($"Could not create organ for slot {organSlotId} in {ToPrettyString(ent)}");
  222. }
  223. }
  224. }
  225. /// <summary>
  226. /// Gets all body containers on this entity including the root one.
  227. /// </summary>
  228. public IEnumerable<BaseContainer> GetBodyContainers(
  229. EntityUid id,
  230. BodyComponent? body = null,
  231. BodyPartComponent? rootPart = null)
  232. {
  233. if (!Resolve(id, ref body, logMissing: false)
  234. || body.RootContainer.ContainedEntity is null
  235. || !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
  236. {
  237. yield break;
  238. }
  239. yield return body.RootContainer;
  240. foreach (var childContainer in GetPartContainers(body.RootContainer.ContainedEntity.Value, rootPart))
  241. {
  242. yield return childContainer;
  243. }
  244. }
  245. /// <summary>
  246. /// Gets all child body parts of this entity, including the root entity.
  247. /// </summary>
  248. public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(
  249. EntityUid? id,
  250. BodyComponent? body = null,
  251. BodyPartComponent? rootPart = null)
  252. {
  253. if (id is null
  254. || !Resolve(id.Value, ref body, logMissing: false)
  255. || body.RootContainer.ContainedEntity is null
  256. || body is null // Shitmed Change
  257. || body.RootContainer == default // Shitmed Change
  258. || !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
  259. {
  260. yield break;
  261. }
  262. foreach (var child in GetBodyPartChildren(body.RootContainer.ContainedEntity.Value, rootPart))
  263. {
  264. yield return child;
  265. }
  266. }
  267. public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(
  268. EntityUid? bodyId,
  269. BodyComponent? body = null)
  270. {
  271. if (bodyId is null || !Resolve(bodyId.Value, ref body, logMissing: false))
  272. yield break;
  273. foreach (var part in GetBodyChildren(bodyId, body))
  274. {
  275. foreach (var organ in GetPartOrgans(part.Id, part.Component))
  276. {
  277. yield return organ;
  278. }
  279. }
  280. }
  281. /// <summary>
  282. /// Returns all body part slots for this entity.
  283. /// </summary>
  284. /// <param name="bodyId"></param>
  285. /// <param name="body"></param>
  286. /// <returns></returns>
  287. public IEnumerable<BodyPartSlot> GetBodyAllSlots(
  288. EntityUid bodyId,
  289. BodyComponent? body = null)
  290. {
  291. if (!Resolve(bodyId, ref body, logMissing: false)
  292. || body.RootContainer.ContainedEntity is null)
  293. {
  294. yield break;
  295. }
  296. foreach (var slot in GetAllBodyPartSlots(body.RootContainer.ContainedEntity.Value))
  297. {
  298. yield return slot;
  299. }
  300. }
  301. public virtual HashSet<EntityUid> GibBody(
  302. EntityUid bodyId,
  303. bool gibOrgans = false,
  304. BodyComponent? body = null,
  305. bool launchGibs = true,
  306. Vector2? splatDirection = null,
  307. float splatModifier = 1,
  308. Angle splatCone = default,
  309. SoundSpecifier? gibSoundOverride = null,
  310. // Shitmed Change
  311. GibType gib = GibType.Gib,
  312. GibContentsOption contents = GibContentsOption.Drop)
  313. {
  314. var gibs = new HashSet<EntityUid>();
  315. if (!Resolve(bodyId, ref body, logMissing: false))
  316. return gibs;
  317. var root = GetRootPartOrNull(bodyId, body);
  318. if (root != null && TryComp(root.Value.Entity, out GibbableComponent? gibbable))
  319. {
  320. gibSoundOverride ??= gibbable.GibSound;
  321. }
  322. var parts = GetBodyChildren(bodyId, body).ToArray();
  323. gibs.EnsureCapacity(parts.Length);
  324. foreach (var part in parts)
  325. {
  326. _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, gib, contents, ref gibs, // Shitmed Change
  327. playAudio: false, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier,
  328. launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
  329. if (!gibOrgans)
  330. continue;
  331. foreach (var organ in GetPartOrgans(part.Id, part.Component))
  332. {
  333. _gibbingSystem.TryGibEntityWithRef(bodyId, organ.Id, GibType.Drop, GibContentsOption.Skip,
  334. ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier,
  335. launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
  336. }
  337. }
  338. var bodyTransform = Transform(bodyId);
  339. if (TryComp<InventoryComponent>(bodyId, out var inventory))
  340. {
  341. foreach (var item in _inventory.GetHandOrInventoryEntities(bodyId))
  342. {
  343. SharedTransform.DropNextTo(item, (bodyId, bodyTransform));
  344. gibs.Add(item);
  345. }
  346. }
  347. _audioSystem.PlayPredicted(gibSoundOverride, bodyTransform.Coordinates, null);
  348. return gibs;
  349. }
  350. // Shitmed Change Start
  351. public virtual HashSet<EntityUid> GibPart(
  352. EntityUid partId,
  353. BodyPartComponent? part = null,
  354. bool launchGibs = true,
  355. Vector2? splatDirection = null,
  356. float splatModifier = 1,
  357. Angle splatCone = default,
  358. SoundSpecifier? gibSoundOverride = null)
  359. {
  360. var gibs = new HashSet<EntityUid>();
  361. if (!Resolve(partId, ref part, logMissing: false))
  362. return gibs;
  363. if (part.Body is { } bodyEnt)
  364. {
  365. if (IsPartRoot(bodyEnt, partId, part: part) || !part.CanSever)
  366. return gibs;
  367. DropSlotContents((partId, part));
  368. RemovePartChildren((partId, part), bodyEnt);
  369. foreach (var organ in GetPartOrgans(partId, part))
  370. {
  371. _gibbingSystem.TryGibEntityWithRef(bodyEnt, organ.Id, GibType.Drop, GibContentsOption.Skip,
  372. ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier,
  373. launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
  374. }
  375. var ev = new BodyPartDroppedEvent((partId, part));
  376. RaiseLocalEvent(bodyEnt, ref ev);
  377. }
  378. _gibbingSystem.TryGibEntityWithRef(partId, partId, GibType.Gib, GibContentsOption.Drop, ref gibs,
  379. playAudio: true, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier,
  380. launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone);
  381. if (HasComp<InventoryComponent>(partId))
  382. {
  383. foreach (var item in _inventory.GetHandOrInventoryEntities(partId))
  384. {
  385. SharedTransform.AttachToGridOrMap(item);
  386. gibs.Add(item);
  387. }
  388. }
  389. _audioSystem.PlayPredicted(gibSoundOverride, Transform(partId).Coordinates, null);
  390. return gibs;
  391. }
  392. public virtual bool BurnPart(EntityUid partId,
  393. BodyPartComponent? part = null)
  394. {
  395. if (!Resolve(partId, ref part, logMissing: false))
  396. return false;
  397. if (part.Body is { } bodyEnt)
  398. {
  399. if (IsPartRoot(bodyEnt, partId, part: part))
  400. return false;
  401. var gibs = new HashSet<EntityUid>();
  402. // Todo: Kill this in favor of husking.
  403. DropSlotContents((partId, part));
  404. RemovePartChildren((partId, part), bodyEnt);
  405. foreach (var organ in GetPartOrgans(partId, part))
  406. _gibbingSystem.TryGibEntityWithRef(bodyEnt, organ.Id, GibType.Drop, GibContentsOption.Skip,
  407. ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse, launchImpulseVariance: GibletLaunchImpulseVariance);
  408. _gibbingSystem.TryGibEntityWithRef(partId, partId, GibType.Gib, GibContentsOption.Gib, ref gibs,
  409. playAudio: false, launchGibs: true, launchImpulse: GibletLaunchImpulse, launchImpulseVariance: GibletLaunchImpulseVariance);
  410. if (HasComp<InventoryComponent>(partId))
  411. foreach (var item in _inventory.GetHandOrInventoryEntities(partId))
  412. SharedTransform.AttachToGridOrMap(item);
  413. if (_net.IsServer) // Goob edit
  414. QueueDel(partId);
  415. return true;
  416. }
  417. return false;
  418. }
  419. private void OnProfileLoadFinished(EntityUid uid, BodyComponent component, ProfileLoadFinishedEvent args)
  420. {
  421. if (!HasComp<HumanoidAppearanceComponent>(uid)
  422. || TerminatingOrDeleted(uid)
  423. || !Initialized(uid)) // We do this last one for urists on test envs.
  424. return;
  425. foreach (var part in GetBodyChildren(uid, component))
  426. EnsureComp<BodyPartAppearanceComponent>(part.Id);
  427. }
  428. private void OnStandAttempt(Entity<BodyComponent> ent, ref StandAttemptEvent args)
  429. {
  430. if (ent.Comp.LegEntities.Count < ent.Comp.RequiredLegs)
  431. args.Cancel();
  432. }
  433. private void OnBeingEquippedAttempt(Entity<BodyComponent> ent, ref IsEquippingAttemptEvent args)
  434. {
  435. if (!TryComp(args.EquipTarget, out BodyComponent? targetBody)
  436. || targetBody.Prototype == null
  437. || HasComp<BorgChassisComponent>(args.EquipTarget))
  438. return;
  439. if (TryGetPartFromSlotContainer(args.Slot, out var bodyPart)
  440. && bodyPart is not null)
  441. {
  442. var bodyPartString = bodyPart.Value.ToString().ToLower();
  443. var prototype = Prototypes.Index(targetBody.Prototype.Value);
  444. var hasPartConnection = prototype.Slots.Values.Any(slot =>
  445. slot.Connections.Contains(bodyPartString));
  446. if (hasPartConnection
  447. && !GetBodyChildrenOfType(args.EquipTarget, bodyPart.Value).Any())
  448. {
  449. _popup.PopupClient(Loc.GetString("equip-part-missing-error",
  450. ("target", args.EquipTarget), ("part", bodyPartString)), args.Equipee, args.Equipee);
  451. args.Cancel();
  452. }
  453. }
  454. }
  455. // Shitmed Change End
  456. }