using System; using System.Collections.Generic; 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 { /// /// 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. /// Capable of loading data from a . /// public class BodyTemplate { [ViewVariables] public bool Initialized { 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; } = ""; /// /// Maps all parts on this template to its BodyPartType. /// For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid /// template. /// [ViewVariables] public Dictionary Slots { get; private set; } = new Dictionary(); /// /// Maps limb name to the list of their connections to other limbs. /// For instance, on the humanoid template "torso" is mapped to a list /// containing "right arm", "left arm", "left leg", and "right leg". /// This is mapped both ways during runtime, but in the prototype only one /// way has to be defined, i.e., "torso" to "left arm" will automatically /// map "left arm" to "torso". /// [ViewVariables] public Dictionary> Connections { get; private set; } = new Dictionary>(); [ViewVariables] public Dictionary Layers { get; private set; } = new Dictionary(); [ViewVariables] public Dictionary MechanismLayers { get; private set; } = new Dictionary(); public bool Equals(BodyTemplate other) { return GetHashCode() == other.GetHashCode(); } /// /// Checks if the given slot exists in this . /// /// True if it does, false otherwise. public bool HasSlot(string slotName) { return Slots.Keys.Any(slot => slot == slotName); } /// /// Calculates the hash code for this instance of . /// It does not matter in which order the Connections or Slots are defined. /// /// /// An integer unique to this 's layout. /// public override int GetHashCode() { var slotsHash = 0; var connectionsHash = 0; foreach (var (key, value) in Slots) { var slot = key.GetHashCode(); slot = HashCode.Combine(slot, value.GetHashCode()); slotsHash ^= slot; } var connections = new List(); foreach (var (key, value) in Connections) { foreach (var targetBodyPart in value) { var connection = key.GetHashCode() ^ targetBodyPart.GetHashCode(); if (!connections.Contains(connection)) { connections.Add(connection); } } } foreach (var connection in connections) { connectionsHash ^= connection; } // One of the unit tests considers 0 to be an error, but it will be 0 if // the BodyTemplate is empty, so let's shift that up to 1. var hash = HashCode.Combine( CenterSlot.GetHashCode(), slotsHash, connectionsHash); if (hash == 0) { hash++; } return hash; } public virtual void Initialize(BodyTemplatePrototype prototype) { 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; } } }