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:
DrSmugleaf
2022-10-23 00:46:28 +02:00
committed by GitHub
parent 9a38736c3c
commit f323fb7644
140 changed files with 2478 additions and 2571 deletions

View File

@@ -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);
}
}

View 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;
}
}

View 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);
}
}

View File

@@ -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;
}
}
}