ECS and cleanup body system, merge body templates and presets into body prototypes (#11991)
Co-authored-by: Jezithyr <Jezithyr@gmail.com>
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the parts used in a body.
|
||||
/// </summary>
|
||||
[Prototype("bodyPreset")]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyPresetPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("partIDs")]
|
||||
private Dictionary<string, string> _partIDs = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("name")]
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> PartIDs => new(_partIDs);
|
||||
}
|
||||
}
|
||||
54
Content.Shared/Body/Prototypes/BodyPrototype.cs
Normal file
54
Content.Shared/Body/Prototypes/BodyPrototype.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes;
|
||||
|
||||
[Prototype("body")]
|
||||
public sealed class BodyPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
private string _name = string.Empty;
|
||||
|
||||
[DataField("name")]
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
private set => _name = Loc.GetString(value);
|
||||
}
|
||||
|
||||
[DataField("root")] public string Root { get; } = string.Empty;
|
||||
|
||||
[DataField("slots")] public Dictionary<string, BodyPrototypeSlot> Slots { get; } = new();
|
||||
|
||||
private BodyPrototype() { }
|
||||
|
||||
public BodyPrototype(string id, string name, string root, Dictionary<string, BodyPrototypeSlot> slots)
|
||||
{
|
||||
ID = id;
|
||||
Name = name;
|
||||
Root = root;
|
||||
Slots = slots;
|
||||
}
|
||||
}
|
||||
|
||||
[DataRecord]
|
||||
public sealed record BodyPrototypeSlot
|
||||
{
|
||||
[DataField("part", required: true)] public readonly string Part = default!;
|
||||
public readonly HashSet<string> Connections = new();
|
||||
public readonly Dictionary<string, string> Organs = new();
|
||||
|
||||
public BodyPrototypeSlot(string part, HashSet<string>? connections, Dictionary<string, string>? organs)
|
||||
{
|
||||
Part = part;
|
||||
Connections = connections ?? new HashSet<string>();
|
||||
Organs = organs ?? new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public void Deconstruct(out string part, out HashSet<string> connections, out Dictionary<string, string> organs)
|
||||
{
|
||||
part = Part;
|
||||
connections = Connections;
|
||||
organs = Organs;
|
||||
}
|
||||
}
|
||||
191
Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs
Normal file
191
Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes;
|
||||
|
||||
[TypeSerializer]
|
||||
public sealed class BodyPrototypeSerializer : ITypeReader<BodyPrototype, MappingDataNode>
|
||||
{
|
||||
private (ValidationNode Node, List<string> Connections) ValidateSlot(ISerializationManager serializationManager, MappingDataNode slot, string slotId, IDependencyCollection dependencies)
|
||||
{
|
||||
var nodes = new List<ValidationNode>();
|
||||
var prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||
if (!slot.TryGet("part", out ValueDataNode? part))
|
||||
{
|
||||
nodes.Add(new ErrorNode(slot, $"No part value data node found in root slot {slotId}"));
|
||||
}
|
||||
else if (!prototypes.HasIndex<EntityPrototype>(part.Value))
|
||||
{
|
||||
nodes.Add(new ErrorNode(slot, $"No entity prototype found with id {part.Value} for root slot {slotId}"));
|
||||
}
|
||||
|
||||
var connections = new List<string>();
|
||||
if (slot.TryGet("connections", out SequenceDataNode? connectionsNode))
|
||||
{
|
||||
foreach (var node in connectionsNode)
|
||||
{
|
||||
if (node is not ValueDataNode connection)
|
||||
{
|
||||
nodes.Add(new ErrorNode(node, $"Connection is not a value data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
connections.Add(connection.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (slot.TryGet("organs", out MappingDataNode? organsNode))
|
||||
{
|
||||
foreach (var (key, value) in organsNode)
|
||||
{
|
||||
if (key is not ValueDataNode)
|
||||
{
|
||||
nodes.Add(new ErrorNode(key, $"Key is not a value data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value is not ValueDataNode organ)
|
||||
{
|
||||
nodes.Add(new ErrorNode(value, $"Value is not a value data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!prototypes.TryIndex(organ.Value, out EntityPrototype? organPrototype))
|
||||
{
|
||||
nodes.Add(new ErrorNode(value, $"No organ entity prototype found with id {organ.Value}"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!organPrototype.HasComponent<OrganComponent>())
|
||||
{
|
||||
nodes.Add(new ErrorNode(value, $"Organ {organ.Value} does not have a body component"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var validation = new ValidatedSequenceNode(nodes);
|
||||
return (validation, connections);
|
||||
}
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
var nodes = new List<ValidationNode>();
|
||||
if (!node.TryGet("id", out ValueDataNode? id))
|
||||
nodes.Add(new ErrorNode(node, "No id value data node found"));
|
||||
|
||||
if (!node.TryGet("root", out ValueDataNode? root))
|
||||
nodes.Add(new ErrorNode(node, $"No root value data node found"));
|
||||
|
||||
if (!node.TryGet("slots", out MappingDataNode? slots))
|
||||
{
|
||||
nodes.Add(new ErrorNode(node, $"No slots mapping data node found"));
|
||||
}
|
||||
else if (root != null)
|
||||
{
|
||||
if (!slots.TryGet(root.Value, out MappingDataNode? _))
|
||||
{
|
||||
nodes.Add(new ErrorNode(slots, $"No slot found with id {root.Value}"));
|
||||
return new ValidatedSequenceNode(nodes);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in slots)
|
||||
{
|
||||
if (key is not ValueDataNode)
|
||||
{
|
||||
nodes.Add(new ErrorNode(key, $"Key is not a value data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value is not MappingDataNode slot)
|
||||
{
|
||||
nodes.Add(new ErrorNode(value, $"Slot is not a mapping data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = ValidateSlot(serializationManager, slot, root.Value, dependencies);
|
||||
nodes.Add(result.Node);
|
||||
|
||||
foreach (var connection in result.Connections)
|
||||
{
|
||||
if (!slots.TryGet(connection, out MappingDataNode? _))
|
||||
nodes.Add(new ErrorNode(slots, $"No slot found with id {connection}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ValidatedSequenceNode(nodes);
|
||||
}
|
||||
|
||||
public BodyPrototype Read(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies,
|
||||
bool skipHook, ISerializationContext? context = null, BodyPrototype? value = default)
|
||||
{
|
||||
var id = node.Get<ValueDataNode>("id").Value;
|
||||
var name = node.Get<ValueDataNode>("name").Value;
|
||||
var root = node.Get<ValueDataNode>("root").Value;
|
||||
var slotNodes = node.Get<MappingDataNode>("slots");
|
||||
var allConnections = new Dictionary<string, (string Part, HashSet<string>? Connections, Dictionary<string, string>? Organs)>();
|
||||
|
||||
foreach (var (keyNode, valueNode) in slotNodes)
|
||||
{
|
||||
var slotId = ((ValueDataNode) keyNode).Value;
|
||||
var slot = ((MappingDataNode) valueNode);
|
||||
var part = slot.Get<ValueDataNode>("part").Value;
|
||||
|
||||
HashSet<string>? connections = null;
|
||||
if (slot.TryGet("connections", out SequenceDataNode? slotConnectionsNode))
|
||||
{
|
||||
connections = new HashSet<string>();
|
||||
|
||||
foreach (var connection in slotConnectionsNode.Cast<ValueDataNode>())
|
||||
{
|
||||
connections.Add(connection.Value);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, string>? organs = null;
|
||||
if (slot.TryGet("organs", out MappingDataNode? slotOrgansNode))
|
||||
{
|
||||
organs = new Dictionary<string, string>();
|
||||
|
||||
foreach (var (organKeyNode, organValueNode) in slotOrgansNode)
|
||||
{
|
||||
organs.Add(((ValueDataNode) organKeyNode).Value, ((ValueDataNode) organValueNode).Value);
|
||||
}
|
||||
}
|
||||
|
||||
allConnections.Add(slotId, (part, connections, organs));
|
||||
}
|
||||
|
||||
foreach (var (slotId, (_, connections, _)) in allConnections)
|
||||
{
|
||||
if (connections == null)
|
||||
continue;
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
var other = allConnections[connection];
|
||||
other.Connections ??= new HashSet<string>();
|
||||
other.Connections.Add(slotId);
|
||||
allConnections[connection] = other;
|
||||
}
|
||||
}
|
||||
|
||||
var slots = new Dictionary<string, BodyPrototypeSlot>();
|
||||
foreach (var (slotId, (part, connections, organs)) in allConnections)
|
||||
{
|
||||
var slot = new BodyPrototypeSlot(part, connections, organs);
|
||||
slots.Add(slotId, slot);
|
||||
}
|
||||
|
||||
return new BodyPrototype(id, name, root, slots);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the layout of a body.
|
||||
/// </summary>
|
||||
[Prototype("bodyTemplate")]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyTemplatePrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[DataField("slots")]
|
||||
private Dictionary<string, BodyPartType> _slots = new();
|
||||
|
||||
[DataField("connections")]
|
||||
private Dictionary<string, List<string>> _rawConnections = new();
|
||||
|
||||
[DataField("layers")]
|
||||
private Dictionary<string, string> _layers = new();
|
||||
|
||||
[DataField("mechanismLayers")]
|
||||
private Dictionary<string, string> _mechanismLayers = new();
|
||||
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("name")]
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("centerSlot")]
|
||||
public string CenterSlot { get; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, BodyPartType> Slots => new(_slots);
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, HashSet<string>> Connections { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> Layers => new(_layers);
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> MechanismLayers => new(_mechanismLayers);
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
//Our prototypes don't force the user to define a BodyPart connection twice. E.g. Head: Torso v.s. Torso: Head.
|
||||
//The user only has to do one. We want it to be that way in the code, though, so this cleans that up.
|
||||
var cleanedConnections = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
foreach (var targetSlotName in _slots.Keys)
|
||||
{
|
||||
var tempConnections = new HashSet<string>();
|
||||
foreach (var (slotName, slotConnections) in _rawConnections)
|
||||
{
|
||||
if (slotName == targetSlotName)
|
||||
{
|
||||
foreach (var connection in slotConnections)
|
||||
{
|
||||
if (!tempConnections.Contains(connection))
|
||||
{
|
||||
tempConnections.Add(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (slotConnections.Contains(targetSlotName))
|
||||
{
|
||||
tempConnections.Add(slotName);
|
||||
}
|
||||
}
|
||||
|
||||
if (tempConnections.Count > 0)
|
||||
{
|
||||
cleanedConnections.Add(targetSlotName, tempConnections);
|
||||
}
|
||||
}
|
||||
|
||||
Connections = cleanedConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user