BodySystem stuff 2: overused boogaloo (#1174)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -14,15 +14,14 @@ using Content.Server.GameObjects.EntitySystems;
|
||||
namespace Content.Server.BodySystem {
|
||||
|
||||
/// <summary>
|
||||
/// Component representing the many BodyParts attached to each other.
|
||||
/// Component representing a collection of <see cref="BodyPart">BodyParts</see> attached to each other.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BodyManagerComponent : Component, IInteractHand {
|
||||
public class BodyManagerComponent : Component, IBodyPartContainer {
|
||||
|
||||
public sealed override string Name => "BodyManager";
|
||||
#pragma warning disable CS0649
|
||||
[Dependency]
|
||||
private IPrototypeManager _prototypeManager;
|
||||
[Dependency] private IPrototypeManager _prototypeManager;
|
||||
#pragma warning restore
|
||||
|
||||
[ViewVariables]
|
||||
@@ -32,45 +31,63 @@ namespace Content.Server.BodySystem {
|
||||
private Dictionary<string, BodyPart> _partDictionary = new Dictionary<string, BodyPart>();
|
||||
|
||||
/// <summary>
|
||||
/// The BodyTemplate that this BodyManagerComponent is adhering to.
|
||||
/// The <see cref="BodyTemplate"/> that this BodyManagerComponent is adhering to.
|
||||
/// </summary>
|
||||
public BodyTemplate Template => _template;
|
||||
|
||||
/// <summary>
|
||||
/// Maps BodyTemplate slot name to the BodyPart object filling it (if there is one).
|
||||
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="BodyPart"/> object filling it (if there is one).
|
||||
/// </summary>
|
||||
public Dictionary<string, BodyPart> PartDictionary => _partDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// List of all BodyParts in this body, taken from the keys of _parts.
|
||||
/// List of all occupied slots in this body, taken from the values of _parts.
|
||||
/// </summary>
|
||||
public IEnumerable<BodyPart> Parts
|
||||
public IEnumerable<string> AllSlots
|
||||
{
|
||||
get
|
||||
{
|
||||
return _template.Slots.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all occupied slots in this body, taken from the values of _parts.
|
||||
/// </summary>
|
||||
public IEnumerable<string> OccupiedSlots
|
||||
{
|
||||
get
|
||||
{
|
||||
return _partDictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all <see cref="BodyPart">BodyParts</see> in this body, taken from the keys of _parts.
|
||||
/// </summary>
|
||||
public IEnumerable<BodyPart> Parts {
|
||||
get {
|
||||
return _partDictionary.Values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursive search that returns whether a given BodyPart is connected to the center BodyPart. Not efficient (O(n^2)), but most bodies don't have a ton of BodyParts.
|
||||
/// Recursive search that returns whether a given <see cref="BodyPart"/> is connected to the center <see cref="BodyPart"/>.
|
||||
/// Not efficient (O(n^2)), but most bodies don't have a ton of <see cref="BodyPart">BodyParts</see>.
|
||||
/// </summary>
|
||||
protected bool ConnectedToCenterPart(BodyPart target)
|
||||
{
|
||||
public bool ConnectedToCenterPart(BodyPart target) {
|
||||
List<string> searchedSlots = new List<string> { };
|
||||
if (TryGetSlotName(target, out string result))
|
||||
return false;
|
||||
return ConnectedToCenterPartRecursion(searchedSlots, result);
|
||||
}
|
||||
|
||||
protected bool ConnectedToCenterPartRecursion(List<string> searchedSlots, string slotName)
|
||||
{
|
||||
private bool ConnectedToCenterPartRecursion(List<string> searchedSlots, string slotName) {
|
||||
TryGetBodyPart(slotName, out BodyPart part);
|
||||
if (part == GetCenterBodyPart())
|
||||
if (part != null && part == GetCenterBodyPart())
|
||||
return true;
|
||||
searchedSlots.Add(slotName);
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections))
|
||||
{
|
||||
foreach (string connection in connections)
|
||||
{
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections)) {
|
||||
foreach (string connection in connections) {
|
||||
if (!searchedSlots.Contains(connection) && ConnectedToCenterPartRecursion(searchedSlots, connection))
|
||||
return true;
|
||||
}
|
||||
@@ -80,56 +97,92 @@ namespace Content.Server.BodySystem {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the central BodyPart of this body based on the BodyTemplate. For humans, this is the torso. Returns null if not found.
|
||||
/// Returns the central <see cref="BodyPart"/> of this body based on the <see cref="BodyTemplate"/>. For humans, this is the torso. Returns null if not found.
|
||||
/// </summary>
|
||||
protected BodyPart GetCenterBodyPart()
|
||||
{
|
||||
public BodyPart GetCenterBodyPart() {
|
||||
_partDictionary.TryGetValue(_template.CenterSlot, out BodyPart center);
|
||||
return center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the BodyPart in the given slotName if there is one. Returns true if a BodyPart is found, false otherwise. If false, result will be null.
|
||||
/// Returns whether the given slot name exists within the current <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
protected bool TryGetBodyPart(string slotName, out BodyPart result)
|
||||
public bool SlotExists(string slotName)
|
||||
{
|
||||
return _template.SlotExists(slotName);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the <see cref="BodyPart"/> in the given slotName if there is one. Returns true if a <see cref="BodyPart"/> is found,
|
||||
/// false otherwise. If false, result will be null.
|
||||
/// </summary>
|
||||
public bool TryGetBodyPart(string slotName, out BodyPart result) {
|
||||
return _partDictionary.TryGetValue(slotName, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the slotName that the given BodyPart resides in. Returns true if the BodyPart is part of this body, false otherwise. If false, result will be null.
|
||||
/// Grabs the slotName that the given <see cref="BodyPart"/> resides in. Returns true if the <see cref="BodyPart"/> is
|
||||
/// part of this body and a slot is found, false otherwise. If false, result will be null.
|
||||
/// </summary>
|
||||
protected bool TryGetSlotName(BodyPart part, out string result)
|
||||
{
|
||||
public bool TryGetSlotName(BodyPart part, out string result) {
|
||||
result = _partDictionary.FirstOrDefault(x => x.Value == part).Key; //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.
|
||||
return result == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the <see cref="BodyPartType"/> of the given slotName if there is one. Returns true if the slot is found, false otherwise. If false, result will be null.
|
||||
/// </summary>
|
||||
public bool TryGetSlotType(string slotName, out BodyPartType result)
|
||||
{
|
||||
return _template.Slots.TryGetValue(slotName, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the names of all connected slots to the given slotName from the template. Returns true if connections are found to the slotName, false otherwise. If false, connections will be null.
|
||||
/// </summary>
|
||||
protected bool TryGetBodyPartConnections(string slotName, out List<string> connections)
|
||||
{
|
||||
public bool TryGetBodyPartConnections(string slotName, out List<string> connections) {
|
||||
return _template.Connections.TryGetValue(slotName, out connections);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all occupied slots connected to the given slot, regardless of whether the target slot is occupied. Returns true if successful, false if there was an error or no connected BodyParts were found.
|
||||
/// </summary>
|
||||
public bool TryGetBodyPartConnections(string slotName, out List<BodyPart> result)
|
||||
{
|
||||
result = null;
|
||||
if (!_template.Connections.TryGetValue(slotName, out List<string> connections))
|
||||
return false;
|
||||
List<BodyPart> toReturn = new List<BodyPart>();
|
||||
foreach (string connection in connections)
|
||||
{
|
||||
if (TryGetBodyPart(connection, out BodyPart bodyPartResult))
|
||||
{
|
||||
toReturn.Add(bodyPartResult);
|
||||
}
|
||||
}
|
||||
if (toReturn.Count <= 0)
|
||||
return false;
|
||||
result = toReturn;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/////////
|
||||
///////// Server-specific stuff
|
||||
/////////
|
||||
|
||||
public bool InteractHand(InteractHandEventArgs eventArgs)
|
||||
{
|
||||
//TODO: remove organs?
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer) {
|
||||
base.ExposeData(serializer);
|
||||
|
||||
string templateName = null;
|
||||
serializer.DataField(ref templateName, "BaseTemplate", "bodyTemplate.Humanoid");
|
||||
if (serializer.Reading)
|
||||
{
|
||||
if (serializer.Reading) {
|
||||
if (!_prototypeManager.TryIndex(templateName, out BodyTemplatePrototype templateData))
|
||||
throw new InvalidOperationException("No BodyTemplatePrototype was found with the name " + templateName + " while loading a BodyTemplate!"); //Should never happen unless you fuck up the prototype.
|
||||
|
||||
@@ -144,18 +197,14 @@ namespace Content.Server.BodySystem {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given preset - forcefully changes all limbs found in both the preset and this template!
|
||||
/// Loads the given <see cref="BodyPreset"/> - forcefully changes all limbs found in both the preset and this template!
|
||||
/// </summary>
|
||||
public void LoadBodyPreset(BodyPreset preset)
|
||||
{
|
||||
foreach (var (slotName, type) in _template.Slots)
|
||||
{
|
||||
if (!preset.PartIDs.TryGetValue(slotName, out string partID))
|
||||
{ //For each slot in our BodyManagerComponent's template, try and grab what the ID of what the preset says should be inside it.
|
||||
public void LoadBodyPreset(BodyPreset preset) {
|
||||
foreach (var (slotName, type) in _template.Slots) {
|
||||
if (!preset.PartIDs.TryGetValue(slotName, out string partID)) { //For each slot in our BodyManagerComponent's template, try and grab what the ID of what the preset says should be inside it.
|
||||
continue; //If the preset doesn't define anything for it, continue.
|
||||
}
|
||||
if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData))
|
||||
{ //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed.
|
||||
if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData)) { //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed.
|
||||
throw new InvalidOperationException("BodyPart prototype with ID " + partID + " could not be found!");
|
||||
}
|
||||
_partDictionary.Remove(slotName); //Try and remove an existing limb if that exists.
|
||||
@@ -164,88 +213,129 @@ namespace Content.Server.BodySystem {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current BodyTemplate to the new BodyTemplate. Attempts to keep previous BodyParts if there is a slot for them in both BodyTemplates.
|
||||
/// Changes the current <see cref="BodyTemplate"/> to the given <see cref="BodyTemplate"/>. Attempts to keep previous <see cref="BodyPart">BodyParts</see>
|
||||
/// if there is a slot for them in both <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
|
||||
{
|
||||
foreach (KeyValuePair<string, BodyPart> part in _partDictionary)
|
||||
{
|
||||
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate) {
|
||||
foreach (KeyValuePair<string, BodyPart> part in _partDictionary) {
|
||||
//TODO: Make this work.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs all limbs of the given type in this body.
|
||||
/// Grabs all <see cref="BodyPart">BodyParts</see> of the given type in this body.
|
||||
/// </summary>
|
||||
public List<BodyPart> GetBodyPartsOfType(BodyPartType type)
|
||||
{
|
||||
public List<BodyPart> GetBodyPartsOfType(BodyPartType type) {
|
||||
List<BodyPart> toReturn = new List<BodyPart>();
|
||||
foreach (var (slotName, bodyPart) in _partDictionary)
|
||||
{
|
||||
foreach (var (slotName, bodyPart) in _partDictionary) {
|
||||
if (bodyPart.PartType == type)
|
||||
toReturn.Add(bodyPart);
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given BodyPart reference, potentially dropping other BodyParts if they were hanging off it.
|
||||
/// Installs the given <see cref="BodyPart"/> into the given slot. Returns true if successful, false otherwise.
|
||||
/// </summary>
|
||||
/// <returns>Returns the dropped entity, or null if no part is dropped</returns>
|
||||
public IEntity DisconnectBodyPart(BodyPart part, bool dropEntity)
|
||||
public bool InstallBodyPart(BodyPart part, string slotName)
|
||||
{
|
||||
if (!SlotExists(slotName)) //Make sure the given slot exists
|
||||
return false;
|
||||
if (TryGetBodyPart(slotName, out BodyPart result)) //And that nothing is in it
|
||||
return false;
|
||||
_partDictionary.Add(slotName, part);
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the given slot, deleting the <see cref="IEntity"/> afterwards. Returns true if successful, false otherwise.
|
||||
/// </summary>
|
||||
public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName)
|
||||
{
|
||||
if (!InstallBodyPart(part.ContainedBodyPart, slotName))
|
||||
return false;
|
||||
part.Owner.Delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="BodyPart"/> reference, potentially dropping other <see cref="BodyPart">BodyParts</see>
|
||||
/// if they were hanging off it. Returns the IEntity representing the dropped BodyPart.
|
||||
/// </summary>
|
||||
public IEntity DropBodyPart(BodyPart part)
|
||||
{
|
||||
if (!_partDictionary.ContainsValue(part))
|
||||
return null;
|
||||
if (part != null)
|
||||
{
|
||||
string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key;
|
||||
_partDictionary.Remove(slotName);
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections)) //Call disconnect on all limbs that were hanging off this limb.
|
||||
{
|
||||
foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit
|
||||
{
|
||||
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result))
|
||||
{
|
||||
DisconnectBodyPartByName(connectionName, dropEntity);
|
||||
DisconnectBodyPartByName(connectionName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_partDictionary.Remove(slotName);
|
||||
if (dropEntity)
|
||||
{
|
||||
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
|
||||
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
|
||||
return partEntity;
|
||||
}
|
||||
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
|
||||
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
|
||||
return partEntity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal string version of DisconnectBodyPart for performance purposes.
|
||||
/// Disconnects the given <see cref="BodyPart"/> reference, potentially dropping other <see cref="BodyPart">BodyParts</see> if they were hanging off it.
|
||||
/// </summary>
|
||||
private void DisconnectBodyPartByName(string name, bool dropEntity)
|
||||
{
|
||||
if (!TryGetBodyPart(name, out BodyPart part))
|
||||
public void DisconnectBodyPart(BodyPart part, bool dropEntity) {
|
||||
if (!_partDictionary.ContainsValue(part))
|
||||
return;
|
||||
if (part != null)
|
||||
{
|
||||
if (TryGetBodyPartConnections(name, out List<string> connections))
|
||||
if (part != null) {
|
||||
string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key;
|
||||
_partDictionary.Remove(slotName);
|
||||
if (TryGetBodyPartConnections(slotName, out List<string> connections)) //Call disconnect on all limbs that were hanging off this limb.
|
||||
{
|
||||
foreach (string connectionName in connections)
|
||||
foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit
|
||||
{
|
||||
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result))
|
||||
{
|
||||
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result)) {
|
||||
DisconnectBodyPartByName(connectionName, dropEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
_partDictionary.Remove(name);
|
||||
if (dropEntity)
|
||||
{
|
||||
if (dropEntity) {
|
||||
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
|
||||
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal string version of DisconnectBodyPart for performance purposes. Yes, it is actually more performant.
|
||||
/// </summary>
|
||||
private void DisconnectBodyPartByName(string name, bool dropEntity) {
|
||||
if (!TryGetBodyPart(name, out BodyPart part))
|
||||
return;
|
||||
if (part != null) {
|
||||
_partDictionary.Remove(name);
|
||||
if (TryGetBodyPartConnections(name, out List<string> connections)) {
|
||||
foreach (string connectionName in connections) {
|
||||
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result)) {
|
||||
DisconnectBodyPartByName(connectionName, dropEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dropEntity) {
|
||||
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
|
||||
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user