diff --git a/Content.Server/Body/BodyCommands.cs b/Content.Server/Body/BodyCommands.cs
index 47da73fb64..3769d78dfe 100644
--- a/Content.Server/Body/BodyCommands.cs
+++ b/Content.Server/Body/BodyCommands.cs
@@ -93,7 +93,7 @@ namespace Content.Server.Body
}
else
{
- body.DisconnectBodyPart(hand.Value, true);
+ body.RemovePart(hand.Value, true);
}
}
}
diff --git a/Content.Server/Body/BodyPreset.cs b/Content.Server/Body/BodyPreset.cs
index 2afc863beb..03572fcbb8 100644
--- a/Content.Server/Body/BodyPreset.cs
+++ b/Content.Server/Body/BodyPreset.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Content.Shared.Body.Part;
using Content.Shared.Body.Preset;
+using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Body
@@ -13,24 +14,25 @@ namespace Content.Server.Body
///
public class BodyPreset
{
- public BodyPreset(BodyPresetPrototype data)
- {
- LoadFromPrototype(data);
- }
+ [ViewVariables] public bool Initialized { get; private set; }
- [ViewVariables] public string Name { get; private set; }
+ [ViewVariables] public string Name { get; protected set; }
///
- /// Maps a template slot to the ID of the that should
- /// fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
+ /// Maps a template slot to the ID of the
+ /// that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
///
[ViewVariables]
- public Dictionary PartIDs { get; private set; }
+ public Dictionary PartIDs { get; protected set; }
- protected virtual void LoadFromPrototype(BodyPresetPrototype data)
+ public virtual void Initialize(BodyPresetPrototype prototype)
{
- Name = data.Name;
- PartIDs = data.PartIDs;
+ DebugTools.Assert(!Initialized, $"{nameof(BodyPreset)} {Name} has already been initialized!");
+
+ Name = prototype.Name;
+ PartIDs = prototype.PartIDs;
+
+ Initialized = true;
}
}
}
diff --git a/Content.Server/Body/BodyTemplate.cs b/Content.Server/Body/BodyTemplate.cs
index ae32c02688..88af6ee085 100644
--- a/Content.Server/Body/BodyTemplate.cs
+++ b/Content.Server/Body/BodyTemplate.cs
@@ -4,6 +4,7 @@ using System.Linq;
using Content.Server.GameObjects.Components.Body;
using Content.Shared.Body.Template;
using Content.Shared.GameObjects.Components.Body;
+using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Body
@@ -11,35 +12,22 @@ namespace Content.Server.Body
///
/// This class is a data capsule representing the standard format of a
/// .
- /// For instance, the "humanoid" BodyTemplate defines two arms, each connected to
- /// a torso and so on.
+ /// For instance, the "humanoid" BodyTemplate defines two arms, each
+ /// connected to a torso and so on.
/// Capable of loading data from a .
///
public class BodyTemplate
{
- public BodyTemplate()
- {
- Name = "empty";
- CenterSlot = "";
- Slots = new Dictionary();
- Connections = new Dictionary>();
- Layers = new Dictionary();
- MechanismLayers = new Dictionary();
- }
+ [ViewVariables] public bool Initialized { get; private set; }
- public BodyTemplate(BodyTemplatePrototype data)
- {
- LoadFromPrototype(data);
- }
-
- [ViewVariables] public string Name { get; private set; }
+ [ViewVariables] public string Name { get; private set; } = "";
///
/// The name of the center BodyPart. For humans, this is set to "torso".
/// Used in many calculations.
///
[ViewVariables]
- public string CenterSlot { get; set; }
+ public string CenterSlot { get; set; } = "";
///
/// Maps all parts on this template to its BodyPartType.
@@ -47,7 +35,7 @@ namespace Content.Server.Body
/// template.
///
[ViewVariables]
- public Dictionary Slots { get; private set; }
+ public Dictionary Slots { get; private set; } = new Dictionary();
///
/// Maps limb name to the list of their connections to other limbs.
@@ -58,13 +46,13 @@ namespace Content.Server.Body
/// map "left arm" to "torso".
///
[ViewVariables]
- public Dictionary> Connections { get; private set; }
+ public Dictionary> Connections { get; private set; } = new Dictionary>();
[ViewVariables]
- public Dictionary Layers { get; private set; }
+ public Dictionary Layers { get; private set; } = new Dictionary();
[ViewVariables]
- public Dictionary MechanismLayers { get; private set; }
+ public Dictionary MechanismLayers { get; private set; } = new Dictionary();
public bool Equals(BodyTemplate other)
{
@@ -75,7 +63,7 @@ namespace Content.Server.Body
/// Checks if the given slot exists in this .
///
/// True if it does, false otherwise.
- public bool SlotExists(string slotName)
+ public bool HasSlot(string slotName)
{
return Slots.Keys.Any(slot => slot == slotName);
}
@@ -132,14 +120,18 @@ namespace Content.Server.Body
return hash;
}
- protected virtual void LoadFromPrototype(BodyTemplatePrototype data)
+ public virtual void Initialize(BodyTemplatePrototype prototype)
{
- Name = data.Name;
- CenterSlot = data.CenterSlot;
- Slots = data.Slots;
- Connections = data.Connections;
- Layers = data.Layers;
- MechanismLayers = data.MechanismLayers;
+ DebugTools.Assert(!Initialized, $"{nameof(BodyTemplate)} {Name} has already been initialized!");
+
+ Name = prototype.Name;
+ CenterSlot = prototype.CenterSlot;
+ Slots = new Dictionary(prototype.Slots);
+ Connections = new Dictionary>(prototype.Connections);
+ Layers = new Dictionary(prototype.Layers);
+ MechanismLayers = new Dictionary(prototype.MechanismLayers);
+
+ Initialized = true;
}
}
}
diff --git a/Content.Server/Body/Network/BodyNetwork.cs b/Content.Server/Body/Network/BodyNetwork.cs
index 911bfbde75..b9ba20c272 100644
--- a/Content.Server/Body/Network/BodyNetwork.cs
+++ b/Content.Server/Body/Network/BodyNetwork.cs
@@ -31,9 +31,16 @@ namespace Content.Server.Body.Network
public virtual void OnRemove() { }
///
- /// Called every update by .
+ /// Called every update by
+ /// .
///
- public virtual void Update(float frameTime) { }
+ public virtual void PreMetabolism(float frameTime) { }
+
+ ///
+ /// Called every update by
+ /// .
+ ///
+ public virtual void PostMetabolism(float frameTime) { }
}
public static class BodyNetworkExtensions
diff --git a/Content.Server/Body/Surgery/BiologicalSurgeryData.cs b/Content.Server/Body/Surgery/BiologicalSurgeryData.cs
index 30cbd2a56b..49fad56ec4 100644
--- a/Content.Server/Body/Surgery/BiologicalSurgeryData.cs
+++ b/Content.Server/Body/Surgery/BiologicalSurgeryData.cs
@@ -243,7 +243,7 @@ namespace Content.Server.Body.Surgery
performer.PopupMessage(Loc.GetString("Saw off the limb!"));
// TODO do_after: Delay
- bmTarget.DisconnectBodyPart(Parent, true);
+ bmTarget.RemovePart(Parent, true);
}
}
}
diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.Parts.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.Parts.cs
new file mode 100644
index 0000000000..614cb5eb0e
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.Parts.cs
@@ -0,0 +1,543 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Body;
+using Content.Server.GameObjects.EntitySystems;
+using Content.Server.Interfaces.GameObjects.Components.Interaction;
+using Content.Shared.Body.Part.Properties.Movement;
+using Content.Shared.Body.Part.Properties.Other;
+using Content.Shared.GameObjects.Components.Body;
+using Content.Shared.GameObjects.Components.Damage;
+using Content.Shared.GameObjects.Components.Movement;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Log;
+using Robust.Shared.Utility;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.GameObjects.Components.Body
+{
+ public partial class BodyManagerComponent
+ {
+ private readonly Dictionary _parts = new Dictionary();
+
+ [ViewVariables] public BodyPreset Preset { get; private set; } = default!;
+
+ ///
+ /// All with
+ /// that are currently affecting move speed, mapped to how big that leg
+ /// they're on is.
+ ///
+ [ViewVariables]
+ private readonly Dictionary _activeLegs = new Dictionary();
+
+ ///
+ /// Maps slot name to the
+ /// object filling it (if there is one).
+ ///
+ [ViewVariables]
+ public IReadOnlyDictionary Parts => _parts;
+
+ ///
+ /// List of all occupied slots in this body, taken from the values of
+ /// .
+ ///
+ public IEnumerable OccupiedSlots => Parts.Keys;
+
+ ///
+ /// List of all slots in this body, taken from the keys of
+ /// slots.
+ ///
+ public IEnumerable AllSlots => Template.Slots.Keys;
+
+ public bool TryAddPart(string slot, DroppedBodyPartComponent part, bool force = false)
+ {
+ DebugTools.AssertNotNull(part);
+
+ if (!TryAddPart(slot, part.ContainedBodyPart, force))
+ {
+ return false;
+ }
+
+ part.Owner.Delete();
+ return true;
+ }
+
+ public bool TryAddPart(string slot, IBodyPart part, bool force = false)
+ {
+ DebugTools.AssertNotNull(part);
+ DebugTools.AssertNotNull(slot);
+
+ // Make sure the given slot exists
+ if (!force)
+ {
+ if (!HasSlot(slot))
+ {
+ return false;
+ }
+
+ // And that nothing is in it
+ if (!_parts.TryAdd(slot, part))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ _parts[slot] = part;
+ }
+
+ part.Body = this;
+
+ var argsAdded = new BodyPartAddedEventArgs(part, slot);
+
+ foreach (var component in Owner.GetAllComponents().ToArray())
+ {
+ component.BodyPartAdded(argsAdded);
+ }
+
+ // TODO: Sort this duplicate out
+ OnBodyChanged();
+
+ if (!Template.Layers.TryGetValue(slot, out var partMap) ||
+ !_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
+ {
+ Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
+ return false;
+ }
+
+ part.RSIMap = partEnum;
+
+ var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum);
+
+ SendNetworkMessage(partMessage);
+
+ foreach (var mechanism in part.Mechanisms)
+ {
+ if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
+ {
+ continue;
+ }
+
+ if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
+ {
+ Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
+ continue;
+ }
+
+ var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum);
+
+ SendNetworkMessage(mechanismMessage);
+ }
+
+ return true;
+ }
+
+ public bool HasPart(string slot)
+ {
+ return _parts.ContainsKey(slot);
+ }
+
+ public void RemovePart(IBodyPart part, bool drop)
+ {
+ DebugTools.AssertNotNull(part);
+
+ var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
+
+ if (string.IsNullOrEmpty(slotName)) return;
+
+ RemovePart(slotName, drop);
+ }
+
+ public bool RemovePart(string slot, bool drop)
+ {
+ DebugTools.AssertNotNull(slot);
+
+ if (!_parts.Remove(slot, out var part))
+ {
+ return false;
+ }
+
+ IEntity? dropped = null;
+ if (drop)
+ {
+ part.SpawnDropped(out dropped);
+ }
+
+ part.Body = null;
+
+ var args = new BodyPartRemovedEventArgs(part, slot);
+
+ foreach (var component in Owner.GetAllComponents())
+ {
+ component.BodyPartRemoved(args);
+ }
+
+ if (part.RSIMap != null)
+ {
+ var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid);
+ SendNetworkMessage(message);
+ }
+
+ foreach (var mechanism in part.Mechanisms)
+ {
+ if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
+ {
+ continue;
+ }
+
+ if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
+ {
+ Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
+ continue;
+ }
+
+ var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum);
+
+ SendNetworkMessage(mechanismMessage);
+ }
+
+ if (CurrentDamageState == DamageState.Dead) return true;
+
+ // creadth: fall down if no legs
+ if (part.PartType == BodyPartType.Leg && Parts.Count(x => x.Value.PartType == BodyPartType.Leg) == 0)
+ {
+ EntitySystem.Get().Down(Owner);
+ }
+
+ // creadth: immediately kill entity if last vital part removed
+ if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
+ {
+ CurrentDamageState = DamageState.Dead;
+ ForceHealthChangedEvent();
+ }
+
+ if (TryGetSlotConnections(slot, out var connections))
+ {
+ foreach (var connectionName in connections)
+ {
+ if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
+ {
+ RemovePart(connectionName, drop);
+ }
+ }
+ }
+
+ OnBodyChanged();
+ return true;
+ }
+
+ public bool RemovePart(IBodyPart part, [NotNullWhen(true)] out string? slot)
+ {
+ DebugTools.AssertNotNull(part);
+
+ var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
+
+ if (pair.Equals(default))
+ {
+ slot = null;
+ return false;
+ }
+
+ slot = pair.Key;
+
+ return RemovePart(slot, false);
+ }
+
+ public IEntity? DropPart(IBodyPart part)
+ {
+ DebugTools.AssertNotNull(part);
+
+ if (!_parts.ContainsValue(part))
+ {
+ return null;
+ }
+
+ if (!RemovePart(part, out var slotName))
+ {
+ return null;
+ }
+
+ // Call disconnect on all limbs that were hanging off this limb.
+ if (TryGetSlotConnections(slotName, out var connections))
+ {
+ // This loop is an unoptimized travesty. TODO: optimize to be less shit
+ foreach (var connectionName in connections)
+ {
+ if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
+ {
+ RemovePart(connectionName, true);
+ }
+ }
+ }
+
+ part.SpawnDropped(out var dropped);
+
+ OnBodyChanged();
+ return dropped;
+ }
+
+ public bool ConnectedToCenter(IBodyPart part)
+ {
+ var searchedSlots = new List();
+
+ return TryGetSlot(part, out var result) &&
+ ConnectedToCenterPartRecursion(searchedSlots, result);
+ }
+
+ private bool ConnectedToCenterPartRecursion(ICollection searchedSlots, string slotName)
+ {
+ if (!TryGetPart(slotName, out var part))
+ {
+ return false;
+ }
+
+ if (part == CenterPart())
+ {
+ return true;
+ }
+
+ searchedSlots.Add(slotName);
+
+ if (!TryGetSlotConnections(slotName, out var connections))
+ {
+ return false;
+ }
+
+ foreach (var connection in connections)
+ {
+ if (!searchedSlots.Contains(connection) &&
+ ConnectedToCenterPartRecursion(searchedSlots, connection))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public IBodyPart? CenterPart()
+ {
+ Parts.TryGetValue(Template.CenterSlot, out var center);
+ return center;
+ }
+
+ public bool HasSlot(string slot)
+ {
+ return Template.HasSlot(slot);
+ }
+
+ public bool TryGetPart(string slot, [NotNullWhen(true)] out IBodyPart? result)
+ {
+ return Parts.TryGetValue(slot, out result);
+ }
+
+ public bool TryGetSlot(IBodyPart part, [NotNullWhen(true)] out string? slot)
+ {
+ // We enforce that there is only one of each value in the dictionary,
+ // so we can iterate through the dictionary values to get the key from there.
+ var pair = Parts.FirstOrDefault(x => x.Value == part);
+ slot = pair.Key;
+
+ return !pair.Equals(default);
+ }
+
+ public bool TryGetSlotType(string slot, out BodyPartType result)
+ {
+ return Template.Slots.TryGetValue(slot, out result);
+ }
+
+ public bool TryGetSlotConnections(string slot, [NotNullWhen(true)] out List? connections)
+ {
+ return Template.Connections.TryGetValue(slot, out connections);
+ }
+
+ public bool TryGetPartConnections(string slot, [NotNullWhen(true)] out List? result)
+ {
+ result = null;
+
+ if (!Template.Connections.TryGetValue(slot, out var connections))
+ {
+ return false;
+ }
+
+ var toReturn = new List();
+ foreach (var connection in connections)
+ {
+ if (TryGetPart(connection, out var partResult))
+ {
+ toReturn.Add(partResult);
+ }
+ }
+
+ if (toReturn.Count <= 0)
+ {
+ return false;
+ }
+
+ result = toReturn;
+ return true;
+ }
+
+ public bool TryGetPartConnections(IBodyPart part, [NotNullWhen(true)] out List? connections)
+ {
+ connections = null;
+
+ return TryGetSlot(part, out var slotName) &&
+ TryGetPartConnections(slotName, out connections);
+ }
+
+ public List GetPartsOfType(BodyPartType type)
+ {
+ var toReturn = new List();
+
+ foreach (var part in Parts.Values)
+ {
+ if (part.PartType == type)
+ {
+ toReturn.Add(part);
+ }
+ }
+
+ return toReturn;
+ }
+
+ private void CalculateSpeed()
+ {
+ if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
+ {
+ return;
+ }
+
+ float speedSum = 0;
+ foreach (var part in _activeLegs.Keys)
+ {
+ if (!part.HasProperty())
+ {
+ _activeLegs.Remove(part);
+ }
+ }
+
+ foreach (var (key, value) in _activeLegs)
+ {
+ if (key.TryGetProperty(out LegProperty? leg))
+ {
+ // Speed of a leg = base speed * (1+log1024(leg length))
+ speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
+ }
+ }
+
+ if (speedSum <= 0.001f || _activeLegs.Count <= 0)
+ {
+ playerMover.BaseWalkSpeed = 0.8f;
+ playerMover.BaseSprintSpeed = 2.0f;
+ }
+ else
+ {
+ // Extra legs stack diminishingly.
+ // Final speed = speed sum/(leg count-log4(leg count))
+ playerMover.BaseWalkSpeed =
+ speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0));
+
+ playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f;
+ }
+ }
+
+ ///
+ /// Called when the layout of this body changes.
+ ///
+ private void OnBodyChanged()
+ {
+ // Calculate move speed based on this body.
+ if (Owner.HasComponent())
+ {
+ _activeLegs.Clear();
+ var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty)));
+
+ foreach (var part in legParts)
+ {
+ var footDistance = DistanceToNearestFoot(part);
+
+ if (Math.Abs(footDistance - float.MinValue) > 0.001f)
+ {
+ _activeLegs.Add(part, footDistance);
+ }
+ }
+
+ CalculateSpeed();
+ }
+ }
+
+ ///
+ /// Returns the combined length of the distance to the nearest with a
+ /// . Returns
+ /// if there is no foot found. If you consider a a node map, then it will look for
+ /// a foot node from the given node. It can
+ /// only search through BodyParts with .
+ ///
+ public float DistanceToNearestFoot(IBodyPart source)
+ {
+ if (source.HasProperty() && source.TryGetProperty(out var property))
+ {
+ return property.ReachDistance;
+ }
+
+ return LookForFootRecursion(source, new List());
+ }
+
+ private float LookForFootRecursion(IBodyPart current,
+ ICollection searchedParts)
+ {
+ if (!current.TryGetProperty(out var extProperty))
+ {
+ return float.MinValue;
+ }
+
+ // Get all connected parts if the current part has an extension property
+ if (!TryGetPartConnections(current, out var connections))
+ {
+ return float.MinValue;
+ }
+
+ // If a connected BodyPart is a foot, return this BodyPart's length.
+ foreach (var connection in connections)
+ {
+ if (!searchedParts.Contains(connection) && connection.HasProperty())
+ {
+ return extProperty.ReachDistance;
+ }
+ }
+
+ // Otherwise, get the recursion values of all connected BodyParts and
+ // store them in a list.
+ var distances = new List();
+ foreach (var connection in connections)
+ {
+ if (!searchedParts.Contains(connection))
+ {
+ continue;
+ }
+
+ var result = LookForFootRecursion(connection, searchedParts);
+
+ if (Math.Abs(result - float.MinValue) > 0.001f)
+ {
+ distances.Add(result);
+ }
+ }
+
+ // If one or more of the searches found a foot, return the smallest one
+ // and add this ones length.
+ if (distances.Count > 0)
+ {
+ return distances.Min() + extProperty.ReachDistance;
+ }
+
+ return float.MinValue;
+
+ // No extension property, no go.
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
index 29329a75dd..13add1a697 100644
--- a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
+++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
@@ -2,16 +2,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Content.Server.Body;
using Content.Server.Body.Network;
using Content.Server.GameObjects.Components.Metabolism;
using Content.Server.GameObjects.EntitySystems;
-using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Observer;
using Content.Shared.Body.Part;
-using Content.Shared.Body.Part.Properties.Movement;
-using Content.Shared.Body.Part.Properties.Other;
using Content.Shared.Body.Preset;
using Content.Shared.Body.Template;
using Content.Shared.GameObjects.Components.Body;
@@ -19,11 +15,8 @@ using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
-using Robust.Shared.GameObjects.Systems;
-using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
-using Robust.Shared.Log;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -39,8 +32,9 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
[ComponentReference(typeof(IDamageableComponent))]
[ComponentReference(typeof(ISharedBodyManagerComponent))]
+ [ComponentReference(typeof(IBodyPartManager))]
[ComponentReference(typeof(IBodyManagerComponent))]
- public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
+ public partial class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
@@ -48,41 +42,10 @@ namespace Content.Server.GameObjects.Components.Body
[ViewVariables] private string _presetName = default!;
- private readonly Dictionary _parts = new Dictionary();
-
[ViewVariables] private readonly Dictionary _networks = new Dictionary();
- ///
- /// All with
- /// that are currently affecting move speed, mapped to how big that leg
- /// they're on is.
- ///
- [ViewVariables]
- private readonly Dictionary _activeLegs = new Dictionary();
-
[ViewVariables] public BodyTemplate Template { get; private set; } = default!;
- [ViewVariables] public BodyPreset Preset { get; private set; } = default!;
-
- ///
- /// Maps slot name to the
- /// object filling it (if there is one).
- ///
- [ViewVariables]
- public IReadOnlyDictionary Parts => _parts;
-
- ///
- /// List of all slots in this body, taken from the keys of
- /// slots.
- ///
- public IEnumerable AllSlots => Template.Slots.Keys;
-
- ///
- /// List of all occupied slots in this body, taken from the values of
- /// .
- ///
- public IEnumerable OccupiedSlots => Parts.Keys;
-
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
@@ -92,14 +55,15 @@ namespace Content.Server.GameObjects.Components.Body
"bodyTemplate.Humanoid",
template =>
{
- if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype templateData))
+ if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype prototype))
{
// Invalid prototype
throw new InvalidOperationException(
$"No {nameof(BodyTemplatePrototype)} found with name {template}");
}
- Template = new BodyTemplate(templateData);
+ Template = new BodyTemplate();
+ Template.Initialize(prototype);
},
() => Template.Name);
@@ -108,14 +72,15 @@ namespace Content.Server.GameObjects.Components.Body
"bodyPreset.BasicHuman",
preset =>
{
- if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype presetData))
+ if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype prototype))
{
// Invalid prototype
throw new InvalidOperationException(
$"No {nameof(BodyPresetPrototype)} found with name {preset}");
}
- Preset = new BodyPreset(presetData);
+ Preset = new BodyPreset();
+ Preset.Initialize(prototype);
},
() => _presetName);
}
@@ -156,7 +121,7 @@ namespace Content.Server.GameObjects.Components.Body
}
// Try and remove an existing limb if that exists.
- RemoveBodyPart(slotName, false);
+ RemovePart(slotName, false);
// Add a new BodyPart with the BodyPartPrototype as a baseline to our
// BodyComponent.
@@ -167,21 +132,21 @@ namespace Content.Server.GameObjects.Components.Body
OnBodyChanged(); // TODO: Duplicate code
}
- ///
- /// Changes the current to the given
- /// .
- /// Attempts to keep previous if there is a
- /// slot for them in both .
- ///
- public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
- {
- foreach (var part in Parts)
- {
- // TODO: Make this work.
- }
-
- OnBodyChanged();
- }
+ // ///
+ // /// Changes the current to the given
+ // /// .
+ // /// Attempts to keep previous if there is a
+ // /// slot for them in both .
+ // ///
+ // public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
+ // {
+ // foreach (var part in Parts)
+ // {
+ // // TODO: Make this work.
+ // }
+ //
+ // OnBodyChanged();
+ // }
///
/// This method is called by before
@@ -201,7 +166,7 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var network in _networks.Values)
{
- network.Update(frameTime);
+ network.PreMetabolism(frameTime);
}
}
@@ -223,73 +188,7 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var network in _networks.Values)
{
- network.Update(frameTime);
- }
- }
-
- ///
- /// Called when the layout of this body changes.
- ///
- private void OnBodyChanged()
- {
- // Calculate move speed based on this body.
- if (Owner.HasComponent())
- {
- _activeLegs.Clear();
- var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty)));
-
- foreach (var part in legParts)
- {
- var footDistance = DistanceToNearestFoot(this, part);
-
- if (Math.Abs(footDistance - float.MinValue) > 0.001f)
- {
- _activeLegs.Add(part, footDistance);
- }
- }
-
- CalculateSpeed();
- }
- }
-
- private void CalculateSpeed()
- {
- if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
- {
- return;
- }
-
- float speedSum = 0;
- foreach (var part in _activeLegs.Keys)
- {
- if (!part.HasProperty())
- {
- _activeLegs.Remove(part);
- }
- }
-
- foreach (var (key, value) in _activeLegs)
- {
- if (key.TryGetProperty(out LegProperty? leg))
- {
- // Speed of a leg = base speed * (1+log1024(leg length))
- speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
- }
- }
-
- if (speedSum <= 0.001f || _activeLegs.Count <= 0)
- {
- playerMover.BaseWalkSpeed = 0.8f;
- playerMover.BaseSprintSpeed = 2.0f;
- }
- else
- {
- // Extra legs stack diminishingly.
- // Final speed = speed sum/(leg count-log4(leg count))
- playerMover.BaseWalkSpeed =
- speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0));
-
- playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f;
+ network.PostMetabolism(frameTime);
}
}
@@ -301,482 +200,6 @@ namespace Content.Server.GameObjects.Components.Body
}
}
- #region BodyPart Functions
-
- ///
- /// Recursively searches for if is connected to
- /// the center. Not efficient (O(n^2)), but most bodies don't have a ton
- /// of s.
- ///
- /// The body part to find the center for.
- /// True if it is connected to the center, false otherwise.
- private bool ConnectedToCenterPart(IBodyPart target)
- {
- var searchedSlots = new List();
-
- return TryGetSlotName(target, out var result) &&
- ConnectedToCenterPartRecursion(searchedSlots, result);
- }
-
- private bool ConnectedToCenterPartRecursion(ICollection searchedSlots, string slotName)
- {
- if (!TryGetBodyPart(slotName, out var part))
- {
- return false;
- }
-
- if (part == GetCenterBodyPart())
- {
- return true;
- }
-
- searchedSlots.Add(slotName);
-
- if (!TryGetBodyPartConnections(slotName, out List connections))
- {
- return false;
- }
-
- foreach (var connection in connections)
- {
- if (!searchedSlots.Contains(connection) &&
- ConnectedToCenterPartRecursion(searchedSlots, connection))
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// Finds the central , if any, of this body based on
- /// the . For humans, this is the torso.
- ///
- /// The if one exists, null otherwise.
- private IBodyPart? GetCenterBodyPart()
- {
- Parts.TryGetValue(Template.CenterSlot, out var center);
- return center;
- }
-
- ///
- /// Returns whether the given slot name exists within the current
- /// .
- ///
- private bool SlotExists(string slotName)
- {
- return Template.SlotExists(slotName);
- }
-
- ///
- /// Finds the in the given if
- /// one exists.
- ///
- /// The slot to search in.
- /// The body part in that slot, if any.
- /// True if found, false otherwise.
- private bool TryGetBodyPart(string slotName, [NotNullWhen(true)] out IBodyPart? result)
- {
- return Parts.TryGetValue(slotName, out result!);
- }
-
- ///
- /// Finds the slotName that the given resides in.
- ///
- /// The to find the slot for.
- /// The slot found, if any.
- /// True if a slot was found, false otherwise
- private bool TryGetSlotName(IBodyPart part, [NotNullWhen(true)] out string result)
- {
- // We enforce that there is only one of each value in the dictionary,
- // so we can iterate through the dictionary values to get the key from there.
- var pair = Parts.FirstOrDefault(x => x.Value == part);
- result = pair.Key;
-
- return !pair.Equals(default);
- }
-
- ///
- /// Finds the in the given
- /// if one exists.
- ///
- /// The slot to search in.
- ///
- /// The of that slot, if any.
- ///
- /// True if found, false otherwise.
- public bool TryGetSlotType(string slotName, out BodyPartType result)
- {
- return Template.Slots.TryGetValue(slotName, out result);
- }
-
- ///
- /// Finds the names of all slots connected to the given
- /// for the template.
- ///
- /// The slot to search in.
- /// The connections found, if any.
- /// True if the connections are found, false otherwise.
- private bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List connections)
- {
- return Template.Connections.TryGetValue(slotName, out connections!);
- }
-
- ///
- /// Grabs all occupied slots connected to the given slot,
- /// regardless of whether the given is occupied.
- ///
- /// The slot name to find connections from.
- /// The connected body parts, if any.
- ///
- /// True if successful, false if there was an error or no connected
- /// s were found.
- ///
- public bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List result)
- {
- result = null!;
-
- if (!Template.Connections.TryGetValue(slotName, out var connections))
- {
- return false;
- }
-
- var toReturn = new List();
- foreach (var connection in connections)
- {
- if (TryGetBodyPart(connection, out var bodyPartResult))
- {
- toReturn.Add(bodyPartResult);
- }
- }
-
- if (toReturn.Count <= 0)
- {
- return false;
- }
-
- result = toReturn;
- return true;
- }
-
- ///
- /// Grabs all parts connected to the given , regardless
- /// of whether the given is occupied.
- ///
- ///
- /// True if successful, false if there was an error or no connected
- /// s were found.
- ///
- private bool TryGetBodyPartConnections(IBodyPart part, [NotNullWhen(true)] out List result)
- {
- result = null!;
-
- return TryGetSlotName(part, out var slotName) &&
- TryGetBodyPartConnections(slotName, out result);
- }
-
- ///
- /// Grabs all of the given type in this body.
- ///
- public List GetBodyPartsOfType(BodyPartType type)
- {
- var toReturn = new List();
-
- foreach (var part in Parts.Values)
- {
- if (part.PartType == type)
- {
- toReturn.Add(part);
- }
- }
-
- return toReturn;
- }
-
- ///
- /// Installs the given into the
- /// given slot, deleting the afterwards.
- ///
- /// True if successful, false otherwise.
- public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName)
- {
- DebugTools.AssertNotNull(part);
-
- if (!TryAddPart(slotName, part.ContainedBodyPart))
- {
- return false;
- }
-
- part.Owner.Delete();
- return true;
- }
-
- ///
- /// Disconnects the given reference, potentially
- /// dropping other BodyParts if they were hanging
- /// off of it.
- ///
- ///
- /// The representing the dropped
- /// , or null if none was dropped.
- ///
- public IEntity? DropPart(IBodyPart part)
- {
- DebugTools.AssertNotNull(part);
-
- if (!_parts.ContainsValue(part))
- {
- return null;
- }
-
- if (!RemoveBodyPart(part, out var slotName))
- {
- return null;
- }
-
- // Call disconnect on all limbs that were hanging off this limb.
- if (TryGetBodyPartConnections(slotName, out List connections))
- {
- // This loop is an unoptimized travesty. TODO: optimize to be less shit
- foreach (var connectionName in connections)
- {
- if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result))
- {
- DisconnectBodyPart(connectionName, true);
- }
- }
- }
-
- part.SpawnDropped(out var dropped);
-
- OnBodyChanged();
- return dropped;
- }
-
- ///
- /// Disconnects the given reference, potentially
- /// dropping other BodyParts if they were hanging
- /// off of it.
- ///
- public void DisconnectBodyPart(IBodyPart part, bool dropEntity)
- {
- DebugTools.AssertNotNull(part);
-
- var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
- if (string.IsNullOrEmpty(slotName)) return;
- DisconnectBodyPart(slotName, dropEntity);
-
- }
-
- ///
- /// Disconnects a body part in the given slot if one exists,
- /// optionally dropping it.
- ///
- /// The slot to remove the body part from
- ///
- /// Whether or not to drop the body part as an entity if it exists.
- ///
- private void DisconnectBodyPart(string slotName, bool dropEntity)
- {
- DebugTools.AssertNotNull(slotName);
-
- if (!HasPart(slotName))
- {
- return;
- }
-
- RemoveBodyPart(slotName, dropEntity);
-
- if (TryGetBodyPartConnections(slotName, out List connections))
- {
- foreach (var connectionName in connections)
- {
- if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result))
- {
- DisconnectBodyPart(connectionName, dropEntity);
- }
- }
- }
-
- OnBodyChanged();
- }
-
- public bool TryAddPart(string slot, IBodyPart part, bool force = false)
- {
- DebugTools.AssertNotNull(part);
- DebugTools.AssertNotNull(slot);
-
- // Make sure the given slot exists
- if (!force)
- {
- if (!SlotExists(slot))
- {
- return false;
- }
-
- // And that nothing is in it
- if (!_parts.TryAdd(slot, part))
- {
- return false;
- }
- }
- else
- {
- _parts[slot] = part;
- }
-
- part.Body = this;
-
- var argsAdded = new BodyPartAddedEventArgs(part, slot);
-
- foreach (var component in Owner.GetAllComponents().ToArray())
- {
- component.BodyPartAdded(argsAdded);
- }
-
- // TODO: Sort this duplicate out
- OnBodyChanged();
-
- if (!Template.Layers.TryGetValue(slot, out var partMap) ||
- !_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
- {
- Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
- return false;
- }
-
- part.RSIMap = partEnum;
-
- var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum);
-
- SendNetworkMessage(partMessage);
-
- foreach (var mechanism in part.Mechanisms)
- {
- if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
- {
- continue;
- }
-
- if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
- {
- Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
- continue;
- }
-
- var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum);
-
- SendNetworkMessage(mechanismMessage);
- }
-
- return true;
- }
-
- public bool HasPart(string slot)
- {
- return _parts.ContainsKey(slot);
- }
-
- ///
- /// Removes the body part in slot from this body,
- /// if one exists.
- ///
- /// The slot to remove it from.
- ///
- /// Whether or not to drop the removed .
- ///
- ///
- private bool RemoveBodyPart(string slotName, bool drop)
- {
- DebugTools.AssertNotNull(slotName);
-
- if (!_parts.Remove(slotName, out var part))
- {
- return false;
- }
-
- IEntity? dropped = null;
- if (drop)
- {
- part.SpawnDropped(out dropped);
- }
-
- part.Body = null;
-
- var args = new BodyPartRemovedEventArgs(part, slotName);
-
- foreach (var component in Owner.GetAllComponents())
- {
- component.BodyPartRemoved(args);
- }
-
- if (part.RSIMap != null)
- {
- var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid);
- SendNetworkMessage(message);
- }
-
- foreach (var mechanism in part.Mechanisms)
- {
- if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
- {
- continue;
- }
-
- if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
- {
- Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
- continue;
- }
-
- var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum);
-
- SendNetworkMessage(mechanismMessage);
- }
-
- if (CurrentDamageState == DamageState.Dead) return true;
-
- // creadth: fall down if no legs
- if (part.PartType == BodyPartType.Leg && Parts.Count(x => x.Value.PartType == BodyPartType.Leg) == 0)
- {
- EntitySystem.Get().Down(Owner);
- }
-
- // creadth: immediately kill entity if last vital part removed
- if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
- {
- CurrentDamageState = DamageState.Dead;
- ForceHealthChangedEvent();
- }
-
- return true;
- }
-
- ///
- /// Removes the body part from this body, if one exists.
- ///
- /// The part to remove from this body.
- /// The slot that the part was in, if any.
- /// True if was removed, false otherwise.
- private bool RemoveBodyPart(IBodyPart part, [NotNullWhen(true)] out string? slotName)
- {
- DebugTools.AssertNotNull(part);
-
- var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
-
- if (pair.Equals(default))
- {
- slotName = null;
- return false;
- }
-
- slotName = pair.Key;
-
- return RemoveBodyPart(slotName, false);
- }
-
- #endregion
-
#region BodyNetwork Functions
private bool EnsureNetwork(BodyNetwork network)
@@ -854,81 +277,6 @@ namespace Content.Server.GameObjects.Components.Body
}
#endregion
-
- #region Recursion Functions
-
- ///
- /// Returns the combined length of the distance to the nearest with a
- /// . Returns
- /// if there is no foot found. If you consider a a node map, then it will look for
- /// a foot node from the given node. It can
- /// only search through BodyParts with .
- ///
- private static float DistanceToNearestFoot(BodyManagerComponent body, IBodyPart source)
- {
- if (source.HasProperty() && source.TryGetProperty(out var property))
- {
- return property.ReachDistance;
- }
-
- return LookForFootRecursion(body, source, new List());
- }
-
- // TODO: Make this not static and not keep me up at night
- private static float LookForFootRecursion(BodyManagerComponent body, IBodyPart current,
- ICollection searchedParts)
- {
- if (!current.TryGetProperty(out var extProperty))
- {
- return float.MinValue;
- }
-
- // Get all connected parts if the current part has an extension property
- if (!body.TryGetBodyPartConnections(current, out var connections))
- {
- return float.MinValue;
- }
-
- // If a connected BodyPart is a foot, return this BodyPart's length.
- foreach (var connection in connections)
- {
- if (!searchedParts.Contains(connection) && connection.HasProperty())
- {
- return extProperty.ReachDistance;
- }
- }
-
- // Otherwise, get the recursion values of all connected BodyParts and
- // store them in a list.
- var distances = new List();
- foreach (var connection in connections)
- {
- if (!searchedParts.Contains(connection))
- {
- continue;
- }
-
- var result = LookForFootRecursion(body, connection, searchedParts);
-
- if (Math.Abs(result - float.MinValue) > 0.001f)
- {
- distances.Add(result);
- }
- }
-
- // If one or more of the searches found a foot, return the smallest one
- // and add this ones length.
- if (distances.Count > 0)
- {
- return distances.Min() + extProperty.ReachDistance;
- }
-
- return float.MinValue;
-
- // No extension property, no go.
- }
-
- #endregion
}
public interface IBodyManagerHealthChangeParams
diff --git a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs
index 8c16794a4f..71dae107ba 100644
--- a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs
+++ b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs
@@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Body
{
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
typeResult != ContainedBodyPart?.PartType ||
- !bodyManager.TryGetBodyPartConnections(slot, out var parts))
+ !bodyManager.TryGetPartConnections(slot, out var parts))
{
continue;
}
@@ -151,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Body
var target = (string) targetObject!;
string message;
- if (_bodyManagerComponentCache.InstallDroppedBodyPart(this, target))
+ if (_bodyManagerComponentCache.TryAddPart(target, this))
{
message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart);
}
diff --git a/Content.Server/GameObjects/Components/Body/IBodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/IBodyManagerComponent.cs
index 26414eefa4..da0c41ab36 100644
--- a/Content.Server/GameObjects/Components/Body/IBodyManagerComponent.cs
+++ b/Content.Server/GameObjects/Components/Body/IBodyManagerComponent.cs
@@ -6,20 +6,14 @@ using Content.Shared.GameObjects.Components.Body;
namespace Content.Server.GameObjects.Components.Body
{
// TODO: Merge with ISharedBodyManagerComponent
- public interface IBodyManagerComponent : ISharedBodyManagerComponent
+ public interface IBodyManagerComponent : ISharedBodyManagerComponent, IBodyPartManager
{
///
- /// The that this
- /// is adhering to.
+ /// The that this
+ /// is adhering to.
///
public BodyTemplate Template { get; }
- ///
- /// The that this
- /// is adhering to.
- ///
- public BodyPreset Preset { get; }
-
///
/// Installs the given into the given slot.
///
diff --git a/Content.Server/GameObjects/Components/Body/IBodyPartManager.cs b/Content.Server/GameObjects/Components/Body/IBodyPartManager.cs
new file mode 100644
index 0000000000..86c015e74f
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Body/IBodyPartManager.cs
@@ -0,0 +1,154 @@
+#nullable enable
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Content.Server.Body;
+using Content.Shared.GameObjects.Components.Body;
+using Robust.Shared.Interfaces.GameObjects;
+
+namespace Content.Server.GameObjects.Components.Body
+{
+ public interface IBodyPartManager : IComponent
+ {
+ ///
+ /// The that this
+ ///
+ /// is adhering to.
+ ///
+ public BodyPreset Preset { get; }
+
+ ///
+ /// Installs the given into the
+ /// given slot, deleting the afterwards.
+ ///
+ /// True if successful, false otherwise.
+ bool TryAddPart(string slot, DroppedBodyPartComponent part, bool force = false);
+
+ bool TryAddPart(string slot, IBodyPart part, bool force = false);
+
+ bool HasPart(string slot);
+
+ ///
+ /// Removes the given reference, potentially
+ /// dropping other BodyParts if they
+ /// were hanging off of it.
+ ///
+ void RemovePart(IBodyPart part, bool drop);
+
+ ///
+ /// Removes the body part in slot from this body,
+ /// if one exists.
+ ///
+ /// The slot to remove it from.
+ ///
+ /// Whether or not to drop the removed .
+ ///
+ /// True if the part was removed, false otherwise.
+ bool RemovePart(string slot, bool drop);
+
+ ///
+ /// Removes the body part from this body, if one exists.
+ ///
+ /// The part to remove from this body.
+ /// The slot that the part was in, if any.
+ /// True if was removed, false otherwise.
+ bool RemovePart(IBodyPart part, [NotNullWhen(true)] out string? slotName);
+
+ ///
+ /// Disconnects the given reference, potentially
+ /// dropping other BodyParts if they were hanging
+ /// off of it.
+ ///
+ ///
+ /// The representing the dropped
+ /// , or null if none was dropped.
+ ///
+ IEntity? DropPart(IBodyPart part);
+
+ ///
+ /// Recursively searches for if is connected to
+ /// the center.
+ ///
+ /// The body part to find the center for.
+ /// True if it is connected to the center, false otherwise.
+ bool ConnectedToCenter(IBodyPart part);
+
+ ///
+ /// Finds the central , if any, of this body based on
+ /// the . For humans, this is the torso.
+ ///
+ /// The if one exists, null otherwise.
+ IBodyPart? CenterPart();
+
+ ///
+ /// Returns whether the given part slot name exists within the current
+ /// .
+ ///
+ /// The slot to check for.
+ /// True if the slot exists in this body, false otherwise.
+ bool HasSlot(string slot);
+
+ ///
+ /// Finds the in the given if
+ /// one exists.
+ ///
+ /// The part slot to search in.
+ /// The body part in that slot, if any.
+ /// True if found, false otherwise.
+ bool TryGetPart(string slot, [NotNullWhen(true)] out IBodyPart? result);
+
+ ///
+ /// Finds the slotName that the given resides in.
+ ///
+ /// The to find the slot for.
+ /// The slot found, if any.
+ /// True if a slot was found, false otherwise
+ bool TryGetSlot(IBodyPart part, [NotNullWhen(true)] out string? slot);
+
+ ///
+ /// Finds the in the given
+ /// if one exists.
+ ///
+ /// The slot to search in.
+ ///
+ /// The of that slot, if any.
+ ///
+ /// True if found, false otherwise.
+ bool TryGetSlotType(string slot, out BodyPartType result);
+
+ ///
+ /// Finds the names of all slots connected to the given
+ /// for the template.
+ ///
+ /// The slot to search in.
+ /// The connections found, if any.
+ /// True if the connections are found, false otherwise.
+ bool TryGetSlotConnections(string slot, [NotNullWhen(true)] out List? connections);
+
+ ///
+ /// Grabs all occupied slots connected to the given slot,
+ /// regardless of whether the given is occupied.
+ ///
+ /// The slot name to find connections from.
+ /// The connected body parts, if any.
+ ///
+ /// True if successful, false if the slot couldn't be found on this body.
+ ///
+ bool TryGetPartConnections(string slot, [NotNullWhen(true)] out List? connections);
+
+ ///
+ /// Grabs all parts connected to the given , regardless
+ /// of whether the given is occupied.
+ ///
+ /// The part to find connections from.
+ /// The connected body parts, if any.
+ ///
+ /// True if successful, false if the part couldn't be found on this body.
+ ///
+ bool TryGetPartConnections(IBodyPart part, [NotNullWhen(true)] out List? connections);
+
+ ///
+ /// Grabs all of the given type in this body.
+ ///
+ List GetPartsOfType(BodyPartType type);
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs
index 88873c2c2a..17aa781a2f 100644
--- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs
+++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs
@@ -479,7 +479,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
var headCount = 0;
if (victim.TryGetComponent(out var bodyManagerComponent))
{
- var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
+ var heads = bodyManagerComponent.GetPartsOfType(BodyPartType.Head);
foreach (var head in heads)
{
var droppedHead = bodyManagerComponent.DropPart(head);
diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs
index aafe33baf4..641632a2f8 100644
--- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs
+++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs
@@ -101,8 +101,8 @@ namespace Content.Server.GameObjects.Components.Movement
var bodyManager = user.GetComponent();
- if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
- bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
+ if (bodyManager.GetPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
+ bodyManager.GetPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
{
reason = Loc.GetString("You are unable to climb!");
return false;