// SPDX-FileCopyrightText: 2022 Jezithyr // SPDX-FileCopyrightText: 2023 Jezithyr // SPDX-FileCopyrightText: 2023 TemporalOroboros // SPDX-FileCopyrightText: 2023 Visne <39844191+Visne@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 0x6273 <0x40@keemail.me> // SPDX-FileCopyrightText: 2024 Alzore <140123969+Blackern5000@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 CaasGit <87243814+CaasGit@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Cojoke <83733158+Cojoke-dot@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 DrSmugleaf // SPDX-FileCopyrightText: 2024 Ed <96445749+TheShuEd@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Emisse <99158783+Emisse@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 EmoGarbage404 // SPDX-FileCopyrightText: 2024 Eoin Mcloughlin // SPDX-FileCopyrightText: 2024 Errant <35878406+Errant-4@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Flareguy <78941145+Flareguy@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Hrosts <35345601+Hrosts@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Ian // SPDX-FileCopyrightText: 2024 Ilya246 <57039557+Ilya246@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Joel Zimmerman // SPDX-FileCopyrightText: 2024 JustCone <141039037+JustCone14@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Killerqu00 <47712032+Killerqu00@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Ko4ergaPunk <62609550+Ko4ergaPunk@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Kukutis96513 <146854220+Kukutis96513@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Lye <128915833+Lyroth001@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Mervill // SPDX-FileCopyrightText: 2024 Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 MureixloI <132683811+MureixloI@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 NakataRin <45946146+NakataRin@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Nemanja <98561806+EmoGarbage404@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 OrangeMoronage9622 // SPDX-FileCopyrightText: 2024 PJBot // SPDX-FileCopyrightText: 2024 Pieter-Jan Briers // SPDX-FileCopyrightText: 2024 Piras314 // SPDX-FileCopyrightText: 2024 Plykiya <58439124+Plykiya@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Preston Smith <92108534+thetolbean@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Repo <47093363+Titian3@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Simon <63975668+Simyon264@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Stalen <33173619+stalengd@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 TakoDragon <69509841+BackeTako@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Thomas <87614336+Aeshus@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Ubaser <134914314+UbaserB@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 Unkn0wn_Gh0st // SPDX-FileCopyrightText: 2024 Vasilis // SPDX-FileCopyrightText: 2024 Vigers Ray <60344369+VigersRay@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 beck-thompson <107373427+beck-thompson@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 deathride58 // SPDX-FileCopyrightText: 2024 dffdff2423 // SPDX-FileCopyrightText: 2024 eoineoineoin // SPDX-FileCopyrightText: 2024 foboscheshir <156405958+foboscheshir@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 gluesniffler <159397573+gluesniffler@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 lzk <124214523+lzk228@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 metalgearsloth // SPDX-FileCopyrightText: 2024 nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 plykiya // SPDX-FileCopyrightText: 2024 saintmuntzer <47153094+saintmuntzer@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 shamp <140359015+shampunj@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 slarticodefast <161409025+slarticodefast@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 strO0pwafel <153459934+strO0pwafel@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 stroopwafel // SPDX-FileCopyrightText: 2024 themias <89101928+themias@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 to4no_fix <156101927+chavonadelal@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 voidnull000 <18663194+voidnull000@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 Aiden // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org> // // SPDX-License-Identifier: AGPL-3.0-or-later using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Events; using Content.Shared.Body.Organ; using Content.Shared.Body.Part; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Movement.Components; using Robust.Shared.Containers; using Robust.Shared.Utility; // Shitmed Change Start using Content.Shared._Shitmed.Body.Events; using Content.Shared._Shitmed.Body.Part; using Content.Shared._Shitmed.BodyEffects; using Content.Shared.Humanoid; using Content.Shared.Inventory; using Content.Shared.Random; namespace Content.Shared.Body.Systems; public partial class SharedBodySystem { [Dependency] private readonly RandomHelperSystem _randomHelper = default!; // Shitmed Change [Dependency] private readonly InventorySystem _inventorySystem = default!; // Shitmed Change private void InitializeParts() { // TODO: This doesn't handle comp removal on child ents. // If you modify this also see the Body partial for root parts. SubscribeLocalEvent(OnBodyPartInserted); SubscribeLocalEvent(OnBodyPartRemoved); // Shitmed Change Start SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnBodyPartRemove); SubscribeLocalEvent(OnAmputateAttempt); SubscribeLocalEvent(OnPartEnableChanged); } private void OnMapInit(Entity ent, ref MapInitEvent args) { if (ent.Comp.PartType == BodyPartType.Torso) { // For whatever reason this slot is initialized properly on the server, but not on the client. // This seems to be an issue due to wiz-merge, on my old branch it was properly instantiating // ItemInsertionSlot's container on both ends. It does show up properly on ItemSlotsComponent though. _slots.AddItemSlot(ent, ent.Comp.ContainerName, ent.Comp.ItemInsertionSlot); Dirty(ent, ent.Comp); } if (ent.Comp.OnAdd is not null || ent.Comp.OnRemove is not null) EnsureComp(ent); foreach (var connection in ent.Comp.Children.Keys) { Containers.EnsureContainer(ent, GetPartSlotContainerId(connection)); } foreach (var organ in ent.Comp.Organs.Keys) { Containers.EnsureContainer(ent, GetOrganContainerId(organ)); } } private void OnBodyPartRemove(Entity ent, ref ComponentRemove args) { if (ent.Comp.PartType == BodyPartType.Torso) _slots.RemoveItemSlot(ent, ent.Comp.ItemInsertionSlot); } private void OnPartEnableChanged(Entity partEnt, ref BodyPartEnableChangedEvent args) { if (!partEnt.Comp.CanEnable && args.Enabled) return; partEnt.Comp.Enabled = args.Enabled; if (args.Enabled) { EnablePart(partEnt); if (partEnt.Comp.Body is { Valid: true } bodyEnt) RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, true)); } else { DisablePart(partEnt); if (partEnt.Comp.Body is { Valid: true } bodyEnt) RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(bodyEnt, false)); } Dirty(partEnt, partEnt.Comp); } private void EnablePart(Entity partEnt) { if (!TryComp(partEnt.Comp.Body, out BodyComponent? body)) return; // I hate having to hardcode these checks so much. if (partEnt.Comp.PartType == BodyPartType.Leg) AddLeg(partEnt, (partEnt.Comp.Body.Value, body)); if (partEnt.Comp.PartType == BodyPartType.Arm) { var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault(); if (hand != default) { var ev = new BodyPartEnabledEvent(hand); RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); } } if (partEnt.Comp.PartType == BodyPartType.Hand) { var ev = new BodyPartEnabledEvent(partEnt); RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); } } /// /// Shitmed Change: This function handles dropping the items in an entity's slots if they lose all of a given part. /// Such as their hands, feet, head, etc. /// public void DropSlotContents(Entity partEnt) { if (partEnt.Comp.Body is not null && TryComp(partEnt.Comp.Body, out var inventory) // Prevent error for non-humanoids && GetBodyPartCount(partEnt.Comp.Body.Value, partEnt.Comp.PartType) == 1 && TryGetPartSlotContainerName(partEnt.Comp.PartType, out var containerNames)) { foreach (var containerName in containerNames) _inventorySystem.DropSlotContents(partEnt.Comp.Body.Value, containerName, inventory); } } private void DisablePart(Entity partEnt) { if (!TryComp(partEnt.Comp.Body, out BodyComponent? body)) return; if (partEnt.Comp.PartType == BodyPartType.Leg) RemoveLeg(partEnt, (partEnt.Comp.Body.Value, body)); if (partEnt.Comp.PartType == BodyPartType.Arm) { var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault(); if (hand != default) { var ev = new BodyPartDisabledEvent(hand); RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); } } if (partEnt.Comp.PartType == BodyPartType.Hand) { var ev = new BodyPartDisabledEvent(partEnt); RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); } } // TODO: Refactor this crap. I hate it so much. private void RemovePartEffect(Entity partEnt, Entity bodyEnt) { if (TerminatingOrDeleted(bodyEnt) || !Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false)) return; RemovePartChildren(partEnt, bodyEnt, bodyEnt.Comp); } protected void RemovePartChildren(Entity partEnt, EntityUid bodyEnt, BodyComponent? body = null) { if (!Resolve(bodyEnt, ref body, logMissing: false)) return; if (partEnt.Comp.Children.Any()) { foreach (var slotId in partEnt.Comp.Children.Keys) { if (Containers.TryGetContainer(partEnt, GetPartSlotContainerId(slotId), out var container) && container is ContainerSlot slot && slot.ContainedEntity is { } childEntity && TryComp(childEntity, out BodyPartComponent? childPart)) { var ev = new BodyPartEnableChangedEvent(false); RaiseLocalEvent(childEntity, ref ev); DropPart((childEntity, childPart)); } } Dirty(bodyEnt, body); } } protected virtual void DropPart(Entity partEnt) { DropSlotContents(partEnt); // I don't know if this can cause issues, since any part that's being detached HAS to have a Body. // though I really just want the compiler to shut the fuck up. var body = partEnt.Comp.Body.GetValueOrDefault(); if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted) { var enableEvent = new BodyPartEnableChangedEvent(false); RaiseLocalEvent(partEnt, ref enableEvent); var droppedEvent = new BodyPartDroppedEvent(partEnt); RaiseLocalEvent(body, ref droppedEvent); SharedTransform.AttachToGridOrMap(partEnt, transform); _randomHelper.RandomOffset(partEnt, 0.5f); } } private void OnAmputateAttempt(Entity partEnt, ref AmputateAttemptEvent args) => DropPart(partEnt); // Shitmed Change End private void OnBodyPartInserted(Entity ent, ref EntInsertedIntoContainerMessage args) { // Body part inserted into another body part. var insertedUid = args.Entity; var slotId = args.Container.ID; if (ent.Comp.Body is null) return; if (TryComp(insertedUid, out BodyPartComponent? part) && slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part))) // Shitmed Change { AddPart(ent.Comp.Body.Value, (insertedUid, part), slotId); RecursiveBodyUpdate((insertedUid, part), ent.Comp.Body.Value); CheckBodyPart((insertedUid, part), GetTargetBodyPart(part), false); // Shitmed Change } if (TryComp(insertedUid, out OrganComponent? organ) && slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId)) // Shitmed Change { AddOrgan((insertedUid, organ), ent.Comp.Body.Value, ent); } } private void OnBodyPartRemoved(Entity ent, ref EntRemovedFromContainerMessage args) { // Body part removed from another body part. var removedUid = args.Entity; var slotId = args.Container.ID; // Shitmed Change Start if (TryComp(removedUid, out BodyPartComponent? part)) { if (!slotId.Contains(PartSlotContainerIdPrefix + GetSlotFromBodyPart(part))) return; DebugTools.Assert(part.Body == ent.Comp.Body); if (part.Body is not null) { CheckBodyPart((removedUid, part), GetTargetBodyPart(part), true); RemovePart(part.Body.Value, (removedUid, part), slotId); RecursiveBodyUpdate((removedUid, part), null); } } if (TryComp(removedUid, out OrganComponent? organ)) { if (!slotId.Contains(OrganSlotContainerIdPrefix + organ.SlotId)) return; DebugTools.Assert(organ.Body == ent.Comp.Body); RemoveOrgan((removedUid, organ), ent); } // Shitmed Change End } private void RecursiveBodyUpdate(Entity ent, EntityUid? bodyUid) { ent.Comp.Body = bodyUid; Dirty(ent, ent.Comp); foreach (var slotId in ent.Comp.Organs.Keys) { if (!Containers.TryGetContainer(ent, GetOrganContainerId(slotId), out var container)) continue; foreach (var organ in container.ContainedEntities) { if (!TryComp(organ, out OrganComponent? organComp)) continue; Dirty(organ, organComp); if (organComp.Body is { Valid: true } oldBodyUid) { var removedEv = new OrganRemovedFromBodyEvent(oldBodyUid, ent); RaiseLocalEvent(organ, ref removedEv); } organComp.Body = bodyUid; if (bodyUid is not null) { var addedEv = new OrganAddedToBodyEvent(bodyUid.Value, ent); RaiseLocalEvent(organ, ref addedEv); } } } // The code for RemovePartEffect() should live here, because it literally is the point of this recursive function. // But the debug asserts at the top plus existing tests need refactoring for this. So we'll be lazy. foreach (var slotId in ent.Comp.Children.Keys) { if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container)) continue; foreach (var containedUid in container.ContainedEntities) { if (TryComp(containedUid, out BodyPartComponent? childPart)) RecursiveBodyUpdate((containedUid, childPart), bodyUid); } } } protected virtual void AddPart( Entity bodyEnt, Entity partEnt, string slotId) { Dirty(partEnt, partEnt.Comp); partEnt.Comp.Body = bodyEnt; if (partEnt.Comp.Enabled && partEnt.Comp.Body is { Valid: true } body) // Shitmed Change RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, true)); var ev = new BodyPartAddedEvent(slotId, partEnt); RaiseLocalEvent(bodyEnt, ref ev); AddLeg(partEnt, bodyEnt); } protected virtual void RemovePart( Entity bodyEnt, Entity partEnt, string slotId) { Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false); Dirty(partEnt, partEnt.Comp); // Shitmed Change Start if (partEnt.Comp.Body is { Valid: true } body) RaiseLocalEvent(partEnt, new BodyPartComponentsModifyEvent(body, false)); partEnt.Comp.ParentSlot = null; // Shitmed Change End var ev = new BodyPartRemovedEvent(slotId, partEnt); RaiseLocalEvent(bodyEnt, ref ev); RemoveLeg(partEnt, bodyEnt); RemovePartEffect(partEnt, bodyEnt); // Shitmed Change PartRemoveDamage(bodyEnt, partEnt); } private void AddLeg(Entity legEnt, Entity bodyEnt) { if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false)) return; if (legEnt.Comp.PartType == BodyPartType.Leg) { bodyEnt.Comp.LegEntities.Add(legEnt); UpdateMovementSpeed(bodyEnt); Dirty(bodyEnt, bodyEnt.Comp); } } private void RemoveLeg(Entity legEnt, Entity bodyEnt) { if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false)) return; if (legEnt.Comp.PartType == BodyPartType.Leg) { bodyEnt.Comp.LegEntities.Remove(legEnt); UpdateMovementSpeed(bodyEnt); Dirty(bodyEnt, bodyEnt.Comp); Standing.Down(bodyEnt); // Shitmed Change } } // Shitmed Change: made virtual, bleeding damage is done on server protected virtual void PartRemoveDamage(Entity bodyEnt, Entity partEnt) { } /// /// Tries to get the parent body part to this if applicable. /// Doesn't validate if it's a part of body system. /// public EntityUid? GetParentPartOrNull(EntityUid uid) { if (!Containers.TryGetContainingContainer((uid, null, null), out var container)) return null; var parent = container.Owner; if (!HasComp(parent)) return null; return parent; } /// /// Tries to get the parent body part and slot to this if applicable. /// public (EntityUid Parent, string Slot)? GetParentPartAndSlotOrNull(EntityUid uid) { if (!Containers.TryGetContainingContainer((uid, null, null), out var container)) return null; var slotId = GetPartSlotContainerIdFromContainer(container.ID); if (string.IsNullOrEmpty(slotId)) return null; var parent = container.Owner; if (!TryComp(parent, out var parentBody) || !parentBody.Children.ContainsKey(slotId)) return null; return (parent, slotId); } /// /// Tries to get the relevant parent body part to this if it exists. /// It won't exist if this is the root body part or if it's not in a body. /// public bool TryGetParentBodyPart( EntityUid partUid, [NotNullWhen(true)] out EntityUid? parentUid, [NotNullWhen(true)] out BodyPartComponent? parentComponent) { DebugTools.Assert(HasComp(partUid)); parentUid = null; parentComponent = null; if (Containers.TryGetContainingContainer((partUid, null, null), out var container) && TryComp(container.Owner, out parentComponent)) { parentUid = container.Owner; return true; } return false; } #region Slots /// /// Creates a BodyPartSlot on the specified partUid. /// private BodyPartSlot? CreatePartSlot( EntityUid partUid, string slotId, BodyPartType partType, BodyPartComponent? part = null) { if (!Resolve(partUid, ref part, logMissing: false)) return null; Containers.EnsureContainer(partUid, GetPartSlotContainerId(slotId)); // Shitmed Change: Don't throw if the slot already exists if (part.Children.TryGetValue(slotId, out var existing)) return existing; var partSlot = new BodyPartSlot(slotId, partType); part.Children.Add(slotId, partSlot); Dirty(partUid, part); return partSlot; } /// /// Tries to create a BodyPartSlot on the specified partUid. /// /// false if not relevant or can't add it. public bool TryCreatePartSlot( EntityUid? partId, string slotId, BodyPartType partType, [NotNullWhen(true)] out BodyPartSlot? slot, BodyPartComponent? part = null) { slot = null; if (partId is null || !Resolve(partId.Value, ref part, logMissing: false)) { return false; } Containers.EnsureContainer(partId.Value, GetPartSlotContainerId(slotId)); slot = new BodyPartSlot(slotId, partType); if (!part.Children.ContainsKey(slotId) // Shitmed Change && !part.Children.TryAdd(slotId, slot.Value)) return false; Dirty(partId.Value, part); return true; } public bool TryCreatePartSlotAndAttach( EntityUid parentId, string slotId, EntityUid childId, BodyPartType partType, BodyPartComponent? parent = null, BodyPartComponent? child = null) { return TryCreatePartSlot(parentId, slotId, partType, out _, parent) && AttachPart(parentId, slotId, childId, parent, child); } #endregion #region RootPartManagement /// /// Returns true if the partId is the root body container for the specified bodyId. /// public bool IsPartRoot( EntityUid bodyId, EntityUid partId, BodyComponent? body = null, BodyPartComponent? part = null) { return Resolve(partId, ref part) && Resolve(bodyId, ref body) && Containers.TryGetContainingContainer(bodyId, partId, out var container) && container.ID == BodyRootContainerId; } /// /// Returns true if we can attach the partId to the bodyId as the root entity. /// public bool CanAttachToRoot( EntityUid bodyId, EntityUid partId, BodyComponent? body = null, BodyPartComponent? part = null) { return Resolve(bodyId, ref body) && Resolve(partId, ref part) && body.RootContainer.ContainedEntity is null && bodyId != part.Body; } /// /// Returns the root part of this body if it exists. /// public (EntityUid Entity, BodyPartComponent BodyPart)? GetRootPartOrNull(EntityUid bodyId, BodyComponent? body = null) { if (!Resolve(bodyId, ref body) || body.RootContainer.ContainedEntity is null) { return null; } return (body.RootContainer.ContainedEntity.Value, Comp(body.RootContainer.ContainedEntity.Value)); } /// /// Returns true if the partId can be attached to the parentId in the specified slot. /// public bool CanAttachPart( EntityUid parentId, BodyPartSlot slot, EntityUid partId, BodyPartComponent? parentPart = null, BodyPartComponent? part = null) { return Resolve(partId, ref part, logMissing: false) && Resolve(parentId, ref parentPart, logMissing: false) && CanAttachPart(parentId, slot.Id, partId, parentPart, part); } /// /// Returns true if we can attach the specified partId to the parentId in the specified slot. /// public bool CanAttachPart( EntityUid parentId, string slotId, EntityUid partId, BodyPartComponent? parentPart = null, BodyPartComponent? part = null) { return Resolve(partId, ref part, logMissing: false) && Resolve(parentId, ref parentPart, logMissing: false) && parentPart.Children.TryGetValue(slotId, out var parentSlotData) && part.PartType == parentSlotData.Type && Containers.TryGetContainer(parentId, GetPartSlotContainerId(slotId), out var container) && Containers.CanInsert(partId, container); } /// /// Shitmed Change: Returns true if this parentId supports attaching a new part to the specified slot. /// public bool CanAttachToSlot( EntityUid parentId, string slotId, BodyPartComponent? parentPart = null) { return Resolve(parentId, ref parentPart, logMissing: false) && parentPart.Children.ContainsKey(slotId); } public bool AttachPartToRoot( EntityUid bodyId, EntityUid partId, BodyComponent? body = null, BodyPartComponent? part = null) { return Resolve(bodyId, ref body) && Resolve(partId, ref part) && CanAttachToRoot(bodyId, partId, body, part) && Containers.Insert(partId, body.RootContainer); } #endregion #region Attach/Detach /// /// Attaches a body part to the specified body part parent. /// public bool AttachPart( EntityUid parentPartId, string slotId, EntityUid partId, BodyPartComponent? parentPart = null, BodyPartComponent? part = null) { return Resolve(parentPartId, ref parentPart, logMissing: false) && parentPart.Children.TryGetValue(slotId, out var slot) && AttachPart(parentPartId, slot, partId, parentPart, part); } /// /// Attaches a body part to the specified body part parent. /// public bool AttachPart( EntityUid parentPartId, BodyPartSlot slot, EntityUid partId, BodyPartComponent? parentPart = null, BodyPartComponent? part = null) { if (!Resolve(parentPartId, ref parentPart, logMissing: false) || !Resolve(partId, ref part, logMissing: false) || !CanAttachPart(parentPartId, slot.Id, partId, parentPart, part) || !parentPart.Children.ContainsKey(slot.Id)) { return false; } if (!Containers.TryGetContainer(parentPartId, GetPartSlotContainerId(slot.Id), out var container)) { DebugTools.Assert($"Unable to find body slot {slot.Id} for {ToPrettyString(parentPartId)}"); return false; } part.ParentSlot = slot; if (TryComp(parentPart.Body, out HumanoidAppearanceComponent? bodyAppearance) && !HasComp(partId) && !TerminatingOrDeleted(parentPartId) && !TerminatingOrDeleted(partId)) // Saw some exceptions involving these due to the spawn menu. EnsureComp(partId); return Containers.Insert(partId, container); } #endregion #region Misc public void UpdateMovementSpeed( EntityUid bodyId, BodyComponent? body = null, MovementSpeedModifierComponent? movement = null) { if (!Resolve(bodyId, ref body, ref movement, logMissing: false) || body.RequiredLegs <= 0) { return; } var walkSpeed = 0f; var sprintSpeed = 0f; var acceleration = 0f; foreach (var legEntity in body.LegEntities) { if (!TryComp(legEntity, out var legModifier)) continue; walkSpeed += legModifier.WalkSpeed; sprintSpeed += legModifier.SprintSpeed; acceleration += legModifier.Acceleration; } walkSpeed /= body.RequiredLegs; sprintSpeed /= body.RequiredLegs; acceleration /= body.RequiredLegs; Movement.ChangeBaseSpeed(bodyId, walkSpeed, sprintSpeed, acceleration, movement); } #endregion #region Queries /// /// Get all organs for the specified body part. /// public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid partId, BodyPartComponent? part = null) { if (!Resolve(partId, ref part, logMissing: false)) yield break; foreach (var slotId in part.Organs.Keys) { var containerSlotId = GetOrganContainerId(slotId); if (!Containers.TryGetContainer(partId, containerSlotId, out var container)) continue; foreach (var containedEnt in container.ContainedEntities) { if (!TryComp(containedEnt, out OrganComponent? organ)) continue; yield return (containedEnt, organ); } } } /// /// Gets all BaseContainers for body parts on this entity and its child entities. /// public IEnumerable GetPartContainers(EntityUid id, BodyPartComponent? part = null) { if (!Resolve(id, ref part, logMissing: false) || part.Children.Count == 0) { yield break; } foreach (var slotId in part.Children.Keys) { var containerSlotId = GetPartSlotContainerId(slotId); if (!Containers.TryGetContainer(id, containerSlotId, out var container)) continue; yield return container; foreach (var ent in container.ContainedEntities) { foreach (var childContainer in GetPartContainers(ent)) { yield return childContainer; } } } } /// /// Returns all body part components for this entity including itself. /// public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyPartChildren( EntityUid partId, BodyPartComponent? part = null) { if (!Resolve(partId, ref part, logMissing: false)) yield break; yield return (partId, part); foreach (var slotId in part.Children.Keys) { var containerSlotId = GetPartSlotContainerId(slotId); if (Containers.TryGetContainer(partId, containerSlotId, out var container)) { foreach (var containedEnt in container.ContainedEntities) { if (!TryComp(containedEnt, out BodyPartComponent? childPart)) continue; foreach (var value in GetBodyPartChildren(containedEnt, childPart)) { yield return value; } } } } } /// /// Returns all body part slots for this entity. /// public IEnumerable GetAllBodyPartSlots( EntityUid partId, BodyPartComponent? part = null) { if (!Resolve(partId, ref part, logMissing: false)) yield break; foreach (var (slotId, slot) in part.Children) { yield return slot; var containerSlotId = GetOrganContainerId(slotId); if (Containers.TryGetContainer(partId, containerSlotId, out var container)) { foreach (var containedEnt in container.ContainedEntities) { if (!TryComp(containedEnt, out BodyPartComponent? childPart)) continue; foreach (var subSlot in GetAllBodyPartSlots(containedEnt, childPart)) { yield return subSlot; } } } } } /// /// Returns true if the bodyId has any parts of this type. /// public bool BodyHasPartType( EntityUid bodyId, BodyPartType type, BodyComponent? body = null) { return GetBodyChildrenOfType(bodyId, type, body).Any(); } /// /// Returns true if the parentId has the specified childId. /// public bool PartHasChild( EntityUid parentId, EntityUid childId, BodyPartComponent? parent, BodyPartComponent? child) { if (!Resolve(parentId, ref parent, logMissing: false) || !Resolve(childId, ref child, logMissing: false)) { return false; } foreach (var (foundId, _) in GetBodyPartChildren(parentId, parent)) { if (foundId == childId) return true; } return false; } /// /// Returns true if the bodyId has the specified partId. /// public bool BodyHasChild( EntityUid bodyId, EntityUid partId, BodyComponent? body = null, BodyPartComponent? part = null) { return Resolve(bodyId, ref body, logMissing: false) && body.RootContainer.ContainedEntity is not null && Resolve(partId, ref part, logMissing: false) && TryComp(body.RootContainer.ContainedEntity, out BodyPartComponent? rootPart) && PartHasChild(body.RootContainer.ContainedEntity.Value, partId, rootPart, part); } public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType( EntityUid bodyId, BodyPartType type, BodyComponent? body = null, // Shitmed Change BodyPartSymmetry? symmetry = null) { foreach (var part in GetBodyChildren(bodyId, body)) { if (part.Component.PartType == type && (symmetry == null || part.Component.Symmetry == symmetry)) // Shitmed Change yield return part; } } /// /// Returns a list of ValueTuples of and OrganComponent on each organ /// in the given part. /// /// The part entity id to check on. /// The part to check for organs on. /// The component to check for. public List<(T Comp, OrganComponent Organ)> GetBodyPartOrganComponents( EntityUid uid, BodyPartComponent? part = null) where T : IComponent { if (!Resolve(uid, ref part)) return new List<(T Comp, OrganComponent Organ)>(); var query = GetEntityQuery(); var list = new List<(T Comp, OrganComponent Organ)>(); foreach (var organ in GetPartOrgans(uid, part)) { if (query.TryGetComponent(organ.Id, out var comp)) list.Add((comp, organ.Component)); } return list; } /// /// Tries to get a list of ValueTuples of and OrganComponent on each organs /// in the given part. /// /// The part entity id to check on. /// The list of components. /// The part to check for organs on. /// The component to check for. /// Whether any were found. public bool TryGetBodyPartOrganComponents( EntityUid uid, [NotNullWhen(true)] out List<(T Comp, OrganComponent Organ)>? comps, BodyPartComponent? part = null) where T : IComponent { if (!Resolve(uid, ref part)) { comps = null; return false; } comps = GetBodyPartOrganComponents(uid, part); if (comps.Count != 0) return true; comps = null; return false; } // Shitmed Change Start /// /// Tries to get a list of ValueTuples of EntityUid and OrganComponent on each organ /// in the given part. /// /// The part entity id to check on. /// The type of component to check for. /// The part to check for organs on. /// The organs found on the body part. /// Whether any were found. /// /// This method is somewhat of a copout to the fact that we can't use reflection to generically /// get the type of component on runtime due to sandboxing. So we simply do a HasComp check for each organ. /// public bool TryGetBodyPartOrgans( EntityUid uid, Type type, [NotNullWhen(true)] out List<(EntityUid Id, OrganComponent Organ)>? organs, BodyPartComponent? part = null) { if (!Resolve(uid, ref part)) { organs = null; return false; } var list = new List<(EntityUid Id, OrganComponent Organ)>(); foreach (var organ in GetPartOrgans(uid, part)) { if (HasComp(organ.Id, type)) list.Add((organ.Id, organ.Component)); } if (list.Count != 0) { organs = list; return true; } organs = null; return false; } private bool TryGetPartSlotContainerName(BodyPartType partType, out HashSet containerNames) { containerNames = partType switch { BodyPartType.Hand => new() { "gloves" }, BodyPartType.Foot => new() { "shoes" }, BodyPartType.Head => new() { "eyes", "ears", "head", "mask" }, _ => new() }; return containerNames.Count > 0; } private bool TryGetPartFromSlotContainer(string slot, out BodyPartType? partType) { partType = slot switch { "gloves" => BodyPartType.Hand, "shoes" => BodyPartType.Foot, "eyes" or "ears" or "head" or "mask" => BodyPartType.Head, _ => null }; return partType is not null; } public int GetBodyPartCount(EntityUid bodyId, BodyPartType partType, BodyComponent? body = null) { if (!Resolve(bodyId, ref body, logMissing: false)) return 0; int count = 0; foreach (var part in GetBodyChildren(bodyId, body)) { if (part.Component.PartType == partType) count++; } return count; } public string GetSlotFromBodyPart(BodyPartComponent? part) { var slotName = ""; if (part is null) return slotName; if (part.SlotId != "") slotName = part.SlotId; else slotName = part.PartType.ToString().ToLower(); if (part.Symmetry != BodyPartSymmetry.None) return $"{part.Symmetry.ToString().ToLower()} {slotName}"; else return slotName; } // Shitmed Change End /// /// Gets the parent body part and all immediate child body parts for the partId. /// public IEnumerable GetBodyPartAdjacentParts( EntityUid partId, BodyPartComponent? part = null) { if (!Resolve(partId, ref part, logMissing: false)) yield break; if (TryGetParentBodyPart(partId, out var parentUid, out _)) yield return parentUid.Value; foreach (var slotId in part.Children.Keys) { var container = Containers.GetContainer(partId, GetPartSlotContainerId(slotId)); foreach (var containedEnt in container.ContainedEntities) { yield return containedEnt; } } } public IEnumerable<(EntityUid AdjacentId, T Component)> GetBodyPartAdjacentPartsComponents( EntityUid partId, BodyPartComponent? part = null) where T : IComponent { if (!Resolve(partId, ref part, logMissing: false)) yield break; var query = GetEntityQuery(); foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part)) { if (query.TryGetComponent(adjacentId, out var component)) yield return (adjacentId, component); } } public bool TryGetBodyPartAdjacentPartsComponents( EntityUid partId, [NotNullWhen(true)] out List<(EntityUid AdjacentId, T Component)>? comps, BodyPartComponent? part = null) where T : IComponent { if (!Resolve(partId, ref part, logMissing: false)) { comps = null; return false; } var query = GetEntityQuery(); comps = new List<(EntityUid AdjacentId, T Component)>(); foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part)) { if (query.TryGetComponent(adjacentId, out var component)) comps.Add((adjacentId, component)); } if (comps.Count != 0) return true; comps = null; return false; } #endregion }