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:
36
Content.Shared/Body/Components/BodyComponent.cs
Normal file
36
Content.Shared/Body/Components/BodyComponent.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.DragDrop;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Body.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed class BodyComponent : Component, IDraggable
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer<BodyPrototype>))]
|
||||
public readonly string? Prototype;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("root")]
|
||||
public BodyPartSlot Root = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("gibSound")]
|
||||
public SoundSpecifier GibSound = new SoundCollectionSpecifier("gib");
|
||||
|
||||
bool IDraggable.CanStartDrag(StartDragDropEvent args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDraggable.CanDrop(CanDropEvent args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
18
Content.Shared/Body/Components/BodyComponentState.cs
Normal file
18
Content.Shared/Body/Components/BodyComponentState.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Components;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyComponentState : ComponentState
|
||||
{
|
||||
public readonly BodyPartSlot Root;
|
||||
public readonly SoundSpecifier GibSound;
|
||||
|
||||
public BodyComponentState(BodyPartSlot root, SoundSpecifier gibSound)
|
||||
{
|
||||
Root = root;
|
||||
GibSound = gibSound;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class MechanismComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private SharedBodyPartComponent? _part;
|
||||
|
||||
public SharedBodyComponent? Body => Part?.Body;
|
||||
|
||||
public SharedBodyPartComponent? Part
|
||||
{
|
||||
get => _part;
|
||||
set
|
||||
{
|
||||
if (_part == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var old = _part;
|
||||
_part = value;
|
||||
|
||||
if (old != null)
|
||||
{
|
||||
if (old.Body == null)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, new RemovedFromPartEvent(old), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, new RemovedFromPartInBodyEvent(old.Body, old), true);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
if (value.Body == null)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, new AddedToPartEvent(value), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, new AddedToPartInBodyEvent(value.Body, value), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("maxDurability")] public int MaxDurability { get; set; } = 10;
|
||||
|
||||
[DataField("currentDurability")] public int CurrentDurability { get; set; } = 10;
|
||||
|
||||
[DataField("destroyThreshold")] public int DestroyThreshold { get; set; } = -10;
|
||||
|
||||
// TODO BODY: Surgery description and adding a message to the examine tooltip of the entity that owns this mechanism
|
||||
// TODO BODY
|
||||
[DataField("resistance")] public int Resistance { get; set; } = 0;
|
||||
|
||||
// TODO BODY OnSizeChanged
|
||||
/// <summary>
|
||||
/// Determines whether this
|
||||
/// <see cref="MechanismComponent"/> can fit into a <see cref="SharedBodyPartComponent"/>.
|
||||
/// </summary>
|
||||
[DataField("size")] public int Size { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// What kind of <see cref="SharedBodyPartComponent"/> this
|
||||
/// <see cref="MechanismComponent"/> can be easily installed into.
|
||||
/// </summary>
|
||||
[DataField("compatibility")]
|
||||
public BodyPartCompatibility Compatibility { get; set; } = BodyPartCompatibility.Universal;
|
||||
}
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Body.Components
|
||||
{
|
||||
// TODO BODY Damage methods for collections of IDamageableComponents
|
||||
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedBodyComponent : Component, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("template", required: true)]
|
||||
private string? TemplateId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("preset", required: true)]
|
||||
private string? PresetId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public BodyTemplatePrototype? Template => TemplateId == null
|
||||
? null
|
||||
: _prototypeManager.Index<BodyTemplatePrototype>(TemplateId);
|
||||
|
||||
[ViewVariables]
|
||||
public BodyPresetPrototype? Preset => PresetId == null
|
||||
? null
|
||||
: _prototypeManager.Index<BodyPresetPrototype>(PresetId);
|
||||
|
||||
[ViewVariables]
|
||||
private Dictionary<string, BodyPartSlot> SlotIds { get; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Dictionary<SharedBodyPartComponent, BodyPartSlot> SlotParts { get; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IEnumerable<BodyPartSlot> Slots => SlotIds.Values;
|
||||
|
||||
[ViewVariables]
|
||||
public IEnumerable<KeyValuePair<SharedBodyPartComponent, BodyPartSlot>> Parts => SlotParts;
|
||||
|
||||
public BodyPartSlot? CenterSlot =>
|
||||
Template?.CenterSlot is { } centerSlot
|
||||
? SlotIds.GetValueOrDefault(centerSlot)
|
||||
: null;
|
||||
|
||||
public SharedBodyPartComponent? CenterPart => CenterSlot?.Part;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// TODO BODY BeforeDeserialization
|
||||
// TODO BODY Move to template or somewhere else
|
||||
if (TemplateId != null)
|
||||
{
|
||||
var template = _prototypeManager.Index<BodyTemplatePrototype>(TemplateId);
|
||||
|
||||
foreach (var (id, partType) in template.Slots)
|
||||
{
|
||||
SetSlot(id, partType);
|
||||
}
|
||||
|
||||
foreach (var (slotId, connectionIds) in template.Connections)
|
||||
{
|
||||
var connections = connectionIds.Select(id => SlotIds[id]);
|
||||
SlotIds[slotId].SetConnectionsInternal(connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
foreach (var slot in SlotIds.Values)
|
||||
{
|
||||
slot.Shutdown();
|
||||
}
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
private BodyPartSlot SetSlot(string id, BodyPartType type)
|
||||
{
|
||||
var slot = new BodyPartSlot(id, type);
|
||||
|
||||
SlotIds[id] = slot;
|
||||
slot.PartAdded += part => OnAddPart(slot, part);
|
||||
slot.PartRemoved += part => OnRemovePart(slot, part);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
private Dictionary<BodyPartSlot, SharedBodyPartComponent> GetHangingParts(BodyPartSlot from)
|
||||
{
|
||||
var hanging = new Dictionary<BodyPartSlot, SharedBodyPartComponent>();
|
||||
|
||||
foreach (var connection in from.Connections)
|
||||
{
|
||||
if (connection.Part != null &&
|
||||
!ConnectedToCenter(connection.Part))
|
||||
{
|
||||
hanging.Add(connection, connection.Part);
|
||||
}
|
||||
}
|
||||
|
||||
return hanging;
|
||||
}
|
||||
|
||||
protected virtual bool CanAddPart(string slotId, SharedBodyPartComponent part)
|
||||
{
|
||||
if (!SlotIds.TryGetValue(slotId, out var slot) ||
|
||||
slot.CanAddPart(part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void OnAddPart(BodyPartSlot slot, SharedBodyPartComponent part)
|
||||
{
|
||||
SlotParts[part] = slot;
|
||||
part.Body = this;
|
||||
|
||||
var argsAdded = new BodyPartAddedEventArgs(slot.Id, part);
|
||||
|
||||
// TODO: Body refactor. Somebody is doing it
|
||||
// EntitySystem.Get<SharedHumanoidAppearanceSystem>().BodyPartAdded(Owner, argsAdded);
|
||||
foreach (var component in IoCManager.Resolve<IEntityManager>().GetComponents<IBodyPartAdded>(Owner).ToArray())
|
||||
{
|
||||
component.BodyPartAdded(argsAdded);
|
||||
}
|
||||
|
||||
// TODO BODY Sort this duplicate out
|
||||
OnBodyChanged();
|
||||
}
|
||||
|
||||
protected virtual void OnRemovePart(BodyPartSlot slot, SharedBodyPartComponent part)
|
||||
{
|
||||
SlotParts.Remove(part);
|
||||
|
||||
foreach (var connectedSlot in slot.Connections)
|
||||
{
|
||||
if (connectedSlot.Part != null &&
|
||||
!ConnectedToCenter(connectedSlot.Part))
|
||||
{
|
||||
RemovePart(connectedSlot.Part);
|
||||
}
|
||||
}
|
||||
|
||||
part.Body = null;
|
||||
|
||||
var args = new BodyPartRemovedEventArgs(slot.Id, part);
|
||||
|
||||
|
||||
// TODO: Body refactor. Somebody is doing it
|
||||
// EntitySystem.Get<SharedHumanoidAppearanceSystem>().BodyPartRemoved(Owner, args);
|
||||
foreach (var component in IoCManager.Resolve<IEntityManager>().GetComponents<IBodyPartRemoved>(Owner))
|
||||
{
|
||||
component.BodyPartRemoved(args);
|
||||
}
|
||||
|
||||
// creadth: fall down if no legs
|
||||
if (part.PartType == BodyPartType.Leg &&
|
||||
GetPartsOfType(BodyPartType.Leg).ToArray().Length == 0)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
||||
}
|
||||
|
||||
if (part.IsVital && SlotParts.Count(x => x.Value.PartType == part.PartType) == 0)
|
||||
{
|
||||
// TODO BODY SYSTEM KILL : Find a more elegant way of killing em than just dumping bloodloss damage.
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Bloodloss"), 300);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(part.Owner, damage);
|
||||
}
|
||||
|
||||
OnBodyChanged();
|
||||
}
|
||||
|
||||
// TODO BODY Sensible templates
|
||||
public bool TryAddPart(string slotId, SharedBodyPartComponent part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
DebugTools.AssertNotNull(slotId);
|
||||
|
||||
if (!CanAddPart(slotId, part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return SlotIds.TryGetValue(slotId, out var slot) &&
|
||||
slot.TryAddPart(part);
|
||||
}
|
||||
|
||||
public void SetPart(string slotId, SharedBodyPartComponent part)
|
||||
{
|
||||
if (!SlotIds.TryGetValue(slotId, out var slot))
|
||||
{
|
||||
slot = SetSlot(slotId, part.PartType);
|
||||
SlotIds[slotId] = slot;
|
||||
}
|
||||
|
||||
slot.SetPart(part);
|
||||
}
|
||||
|
||||
public bool HasPart(SharedBodyPartComponent part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
return SlotParts.ContainsKey(part);
|
||||
}
|
||||
|
||||
public bool RemovePart(SharedBodyPartComponent part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
return SlotParts.TryGetValue(part, out var slot) &&
|
||||
slot.RemovePart();
|
||||
}
|
||||
|
||||
public bool TryDropPart(BodyPartSlot slot, [NotNullWhen(true)] out Dictionary<BodyPartSlot, SharedBodyPartComponent>? dropped)
|
||||
{
|
||||
DebugTools.AssertNotNull(slot);
|
||||
|
||||
if (!SlotIds.TryGetValue(slot.Id, out var ownedSlot) ||
|
||||
ownedSlot != slot ||
|
||||
slot.Part == null)
|
||||
{
|
||||
dropped = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldPart = slot.Part;
|
||||
dropped = GetHangingParts(slot);
|
||||
|
||||
if (!slot.RemovePart())
|
||||
{
|
||||
dropped = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
dropped[slot] = oldPart;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ConnectedToCenter(SharedBodyPartComponent part)
|
||||
{
|
||||
return TryGetSlot(part, out var result) &&
|
||||
ConnectedToCenterPartRecursion(result);
|
||||
}
|
||||
|
||||
private bool ConnectedToCenterPartRecursion(BodyPartSlot slot, HashSet<BodyPartSlot>? searched = null)
|
||||
{
|
||||
searched ??= new HashSet<BodyPartSlot>();
|
||||
|
||||
if (Template?.CenterSlot == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slot.Part == CenterPart)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
searched.Add(slot);
|
||||
|
||||
foreach (var connection in slot.Connections)
|
||||
{
|
||||
if (!searched.Contains(connection) &&
|
||||
ConnectedToCenterPartRecursion(connection, searched))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public BodyPartSlot? GetSlot(SharedBodyPartComponent part)
|
||||
{
|
||||
return SlotParts.GetValueOrDefault(part);
|
||||
}
|
||||
|
||||
public bool TryGetSlot(SharedBodyPartComponent part, [NotNullWhen(true)] out BodyPartSlot? slot)
|
||||
{
|
||||
return (slot = GetSlot(part)) != null;
|
||||
}
|
||||
|
||||
public IEnumerable<BodyPartSlot> GetSlotsOfType(BodyPartType type)
|
||||
{
|
||||
foreach (var slot in SlotIds.Values)
|
||||
{
|
||||
if (slot.PartType == type)
|
||||
{
|
||||
yield return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPartOfType(BodyPartType type)
|
||||
{
|
||||
foreach (var _ in GetPartsOfType(type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<SharedBodyPartComponent> GetPartsOfType(BodyPartType type)
|
||||
{
|
||||
foreach (var slot in GetSlotsOfType(type))
|
||||
{
|
||||
if (slot.Part != null)
|
||||
{
|
||||
yield return slot.Part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyChanged()
|
||||
{
|
||||
Dirty();
|
||||
}
|
||||
|
||||
// TODO BODY optimize this
|
||||
public BodyPartSlot SlotAt(int index)
|
||||
{
|
||||
return SlotIds.Values.ElementAt(index);
|
||||
}
|
||||
|
||||
public KeyValuePair<SharedBodyPartComponent, BodyPartSlot> PartAt(int index)
|
||||
{
|
||||
return SlotParts.ElementAt(index);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
var parts = new (string slot, EntityUid partId)[SlotParts.Count];
|
||||
|
||||
var i = 0;
|
||||
foreach (var (part, slot) in SlotParts)
|
||||
{
|
||||
parts[i] = (slot.Id, part.Owner);
|
||||
i++;
|
||||
}
|
||||
|
||||
return new BodyComponentState(parts);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not BodyComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newParts = state.Parts();
|
||||
|
||||
foreach (var (oldPart, slot) in SlotParts)
|
||||
{
|
||||
if (!newParts.TryGetValue(slot.Id, out var newPart) ||
|
||||
newPart != oldPart)
|
||||
{
|
||||
RemovePart(oldPart);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (slotId, newPart) in newParts)
|
||||
{
|
||||
if (!SlotIds.TryGetValue(slotId, out var slot) ||
|
||||
slot.Part != newPart)
|
||||
{
|
||||
SetPart(slotId, newPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual HashSet<EntityUid> Gib(bool gibParts = false)
|
||||
{
|
||||
var entMgr = IoCManager.Resolve<IEntityManager>();
|
||||
var metaQuery = entMgr.GetEntityQuery<MetaDataComponent>();
|
||||
var gibs = new HashSet<EntityUid>();
|
||||
foreach (var part in SlotParts.Keys)
|
||||
{
|
||||
if (!metaQuery.TryGetComponent(part.Owner, out var meta) ||
|
||||
meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
SlotParts.Remove(part);
|
||||
continue;
|
||||
}
|
||||
gibs.Add(part.Owner);
|
||||
RemovePart(part);
|
||||
|
||||
if (gibParts)
|
||||
gibs.UnionWith(part.Gib());
|
||||
}
|
||||
|
||||
return gibs;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyComponentState : ComponentState
|
||||
{
|
||||
private Dictionary<string, SharedBodyPartComponent>? _parts;
|
||||
|
||||
public readonly (string slot, EntityUid partId)[] PartIds;
|
||||
|
||||
public BodyComponentState((string slot, EntityUid partId)[] partIds)
|
||||
{
|
||||
PartIds = partIds;
|
||||
}
|
||||
|
||||
public Dictionary<string, SharedBodyPartComponent> Parts(IEntityManager? entityManager = null)
|
||||
{
|
||||
if (_parts != null)
|
||||
{
|
||||
return _parts;
|
||||
}
|
||||
|
||||
entityManager ??= IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var parts = new Dictionary<string, SharedBodyPartComponent>(PartIds.Length);
|
||||
|
||||
foreach (var (slot, partId) in PartIds)
|
||||
{
|
||||
if (!entityManager.EntityExists(partId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entityManager.TryGetComponent(partId, out SharedBodyPartComponent? part))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
parts[slot] = part;
|
||||
}
|
||||
|
||||
return _parts = parts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Body.Components
|
||||
{
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedBodyPartComponent : Component
|
||||
{
|
||||
public const string ContainerId = "bodypart";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private SharedBodyComponent? _body;
|
||||
|
||||
// TODO BODY Remove
|
||||
[DataField("mechanisms")]
|
||||
private readonly List<string> _mechanismIds = new();
|
||||
public IReadOnlyList<string> MechanismIds => _mechanismIds;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<MechanismComponent> _mechanisms = new();
|
||||
|
||||
[ViewVariables]
|
||||
public SharedBodyComponent? Body
|
||||
{
|
||||
get => _body;
|
||||
set
|
||||
{
|
||||
if (_body == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var old = _body;
|
||||
_body = value;
|
||||
|
||||
if (old != null)
|
||||
{
|
||||
RemovedFromBody(old);
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
AddedToBody(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="BodyPartType"/> that this <see cref="IBodyPart"/> is considered
|
||||
/// to be.
|
||||
/// For example, <see cref="BodyPartType.Arm"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("partType")]
|
||||
public BodyPartType PartType { get; private set; } = BodyPartType.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how many mechanisms can be fit inside this
|
||||
/// <see cref="SharedBodyPartComponent"/>.
|
||||
/// </summary>
|
||||
[ViewVariables] [DataField("size")] public int Size { get; private set; } = 1;
|
||||
|
||||
[ViewVariables] public int SizeUsed { get; private set; }
|
||||
|
||||
// TODO BODY size used
|
||||
// TODO BODY surgerydata
|
||||
|
||||
/// <summary>
|
||||
/// What types of BodyParts this <see cref="SharedBodyPartComponent"/> can easily attach to.
|
||||
/// For the most part, most limbs aren't universal and require extra work to
|
||||
/// attach between types.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("compatibility")]
|
||||
public BodyPartCompatibility Compatibility = BodyPartCompatibility.Universal;
|
||||
|
||||
// TODO BODY Mechanisms occupying different parts at the body level
|
||||
[ViewVariables]
|
||||
public IReadOnlyCollection<MechanismComponent> Mechanisms => _mechanisms;
|
||||
|
||||
// TODO BODY Replace with a simulation of organs
|
||||
/// <summary>
|
||||
/// Whether or not the owning <see cref="Body"/> will die if all
|
||||
/// <see cref="SharedBodyPartComponent"/>s of this type are removed from it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("vital")]
|
||||
public bool IsVital = false;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("symmetry")]
|
||||
public BodyPartSymmetry Symmetry = BodyPartSymmetry.None;
|
||||
|
||||
protected virtual void OnAddMechanism(MechanismComponent mechanism)
|
||||
{
|
||||
var prototypeId = _entMan.GetComponent<MetaDataComponent>(mechanism.Owner).EntityPrototype!.ID;
|
||||
|
||||
if (!_mechanismIds.Contains(prototypeId))
|
||||
{
|
||||
_mechanismIds.Add(prototypeId);
|
||||
}
|
||||
|
||||
mechanism.Part = this;
|
||||
SizeUsed += mechanism.Size;
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
protected virtual void OnRemoveMechanism(MechanismComponent mechanism)
|
||||
{
|
||||
_mechanismIds.Remove(_entMan.GetComponent<MetaDataComponent>(mechanism.Owner).EntityPrototype!.ID);
|
||||
mechanism.Part = null;
|
||||
SizeUsed -= mechanism.Size;
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
var mechanismIds = new EntityUid[_mechanisms.Count];
|
||||
|
||||
var i = 0;
|
||||
foreach (var mechanism in _mechanisms)
|
||||
{
|
||||
mechanismIds[i] = mechanism.Owner;
|
||||
i++;
|
||||
}
|
||||
|
||||
return new BodyPartComponentState(mechanismIds);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not BodyPartComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newMechanisms = state.Mechanisms();
|
||||
|
||||
foreach (var mechanism in _mechanisms.ToArray())
|
||||
{
|
||||
if (!newMechanisms.Contains(mechanism))
|
||||
{
|
||||
RemoveMechanism(mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mechanism in newMechanisms)
|
||||
{
|
||||
if (!_mechanisms.Contains(mechanism))
|
||||
{
|
||||
TryAddMechanism(mechanism, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CanAddMechanism(MechanismComponent mechanism)
|
||||
{
|
||||
return SizeUsed + mechanism.Size <= Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a <see cref="MechanismComponent"/> to this part.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to add.</param>
|
||||
/// <param name="force">
|
||||
/// Whether or not to check if the mechanism is compatible.
|
||||
/// Passing true does not guarantee it to be added, for example if
|
||||
/// it was already added before.
|
||||
/// </param>
|
||||
/// <returns>true if added, false otherwise even if it was already added.</returns>
|
||||
public bool TryAddMechanism(MechanismComponent mechanism, bool force = false)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
if (!force && !CanAddMechanism(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_mechanisms.Add(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OnAddMechanism(mechanism);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="mechanism"/> from this part.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to remove.</param>
|
||||
/// <returns>True if it was removed, false otherwise.</returns>
|
||||
public bool RemoveMechanism(MechanismComponent mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
if (!_mechanisms.Remove(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OnRemoveMechanism(mechanism);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="mechanism"/> from this
|
||||
/// part and drops it at the specified coordinates.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to remove.</param>
|
||||
/// <param name="coordinates">The coordinates to drop it at.</param>
|
||||
/// <returns>True if it was removed, false otherwise.</returns>
|
||||
public bool RemoveMechanism(MechanismComponent mechanism, EntityCoordinates coordinates)
|
||||
{
|
||||
if (RemoveMechanism(mechanism))
|
||||
{
|
||||
_entMan.GetComponent<TransformComponent>(mechanism.Owner).Coordinates = coordinates;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to destroy the given <see cref="MechanismComponent"/> from
|
||||
/// this part.
|
||||
/// The mechanism won't be deleted if it is not in this body part.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the mechanism was in this body part and destroyed,
|
||||
/// false otherwise.
|
||||
/// </returns>
|
||||
public bool DeleteMechanism(MechanismComponent mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
if (!RemoveMechanism(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_entMan.DeleteEntity(mechanism.Owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddedToBody(SharedBodyComponent body)
|
||||
{
|
||||
OnAddedToBody(body);
|
||||
|
||||
foreach (var mechanism in _mechanisms)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(mechanism.Owner, new AddedToBodyEvent(body), true);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovedFromBody(SharedBodyComponent old)
|
||||
{
|
||||
if (_entMan.TryGetComponent<TransformComponent>(Owner, out var transformComponent))
|
||||
{
|
||||
transformComponent.AttachToGridOrMap();
|
||||
}
|
||||
|
||||
OnRemovedFromBody(old);
|
||||
|
||||
foreach (var mechanism in _mechanisms)
|
||||
{
|
||||
_entMan.EventBus.RaiseLocalEvent(mechanism.Owner, new RemovedFromBodyEvent(old), true);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnAddedToBody(SharedBodyComponent body) { }
|
||||
|
||||
protected virtual void OnRemovedFromBody(SharedBodyComponent old) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gibs the body part.
|
||||
/// </summary>
|
||||
public virtual HashSet<EntityUid> Gib()
|
||||
{
|
||||
var gibs = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var mechanism in _mechanisms.ToArray())
|
||||
{
|
||||
gibs.Add(mechanism.Owner);
|
||||
RemoveMechanism(mechanism);
|
||||
}
|
||||
|
||||
return gibs;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyPartComponentState : ComponentState
|
||||
{
|
||||
[NonSerialized] private List<MechanismComponent>? _mechanisms;
|
||||
|
||||
public readonly EntityUid[] MechanismIds;
|
||||
|
||||
public BodyPartComponentState(EntityUid[] mechanismIds)
|
||||
{
|
||||
MechanismIds = mechanismIds;
|
||||
}
|
||||
|
||||
public List<MechanismComponent> Mechanisms(IEntityManager? entityManager = null)
|
||||
{
|
||||
if (_mechanisms != null)
|
||||
{
|
||||
return _mechanisms;
|
||||
}
|
||||
|
||||
IoCManager.Resolve(ref entityManager);
|
||||
|
||||
var mechanisms = new List<MechanismComponent>(MechanismIds.Length);
|
||||
|
||||
foreach (var id in MechanismIds)
|
||||
{
|
||||
if (!entityManager.EntityExists(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entityManager.TryGetComponent(id, out MechanismComponent? mechanism))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mechanisms.Add(mechanism);
|
||||
}
|
||||
|
||||
return _mechanisms = mechanisms;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using Content.Shared.Body.Components;
|
||||
|
||||
namespace Content.Shared.Body.Events
|
||||
namespace Content.Shared.Body.Events
|
||||
{
|
||||
// All of these events are raised on a mechanism entity when added/removed to a body in different
|
||||
// ways.
|
||||
@@ -10,9 +8,9 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class AddedToBodyEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyComponent Body;
|
||||
public EntityUid Body;
|
||||
|
||||
public AddedToBodyEvent(SharedBodyComponent body)
|
||||
public AddedToBodyEvent(EntityUid body)
|
||||
{
|
||||
Body = body;
|
||||
}
|
||||
@@ -23,9 +21,9 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class AddedToPartEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyPartComponent Part;
|
||||
public EntityUid Part;
|
||||
|
||||
public AddedToPartEvent(SharedBodyPartComponent part)
|
||||
public AddedToPartEvent(EntityUid part)
|
||||
{
|
||||
Part = part;
|
||||
}
|
||||
@@ -36,10 +34,10 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class AddedToPartInBodyEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyComponent Body;
|
||||
public SharedBodyPartComponent Part;
|
||||
public EntityUid Body;
|
||||
public EntityUid Part;
|
||||
|
||||
public AddedToPartInBodyEvent(SharedBodyComponent body, SharedBodyPartComponent part)
|
||||
public AddedToPartInBodyEvent(EntityUid body, EntityUid part)
|
||||
{
|
||||
Body = body;
|
||||
Part = part;
|
||||
@@ -51,9 +49,9 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class RemovedFromBodyEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyComponent Old;
|
||||
public EntityUid Old;
|
||||
|
||||
public RemovedFromBodyEvent(SharedBodyComponent old)
|
||||
public RemovedFromBodyEvent(EntityUid old)
|
||||
{
|
||||
Old = old;
|
||||
}
|
||||
@@ -64,9 +62,9 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class RemovedFromPartEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyPartComponent Old;
|
||||
public EntityUid Old;
|
||||
|
||||
public RemovedFromPartEvent(SharedBodyPartComponent old)
|
||||
public RemovedFromPartEvent(EntityUid old)
|
||||
{
|
||||
Old = old;
|
||||
}
|
||||
@@ -77,10 +75,10 @@ namespace Content.Shared.Body.Events
|
||||
/// </summary>
|
||||
public sealed class RemovedFromPartInBodyEvent : EntityEventArgs
|
||||
{
|
||||
public SharedBodyComponent OldBody;
|
||||
public SharedBodyPartComponent OldPart;
|
||||
public EntityUid OldBody;
|
||||
public EntityUid OldPart;
|
||||
|
||||
public RemovedFromPartInBodyEvent(SharedBodyComponent oldBody, SharedBodyPartComponent oldPart)
|
||||
public RemovedFromPartInBodyEvent(EntityUid oldBody, EntityUid oldPart)
|
||||
{
|
||||
OldBody = oldBody;
|
||||
OldPart = oldPart;
|
||||
|
||||
17
Content.Shared/Body/Organ/OrganComponent.cs
Normal file
17
Content.Shared/Body/Organ/OrganComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body.Organ;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed class OrganComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("body")]
|
||||
public EntityUid? Body;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("parent")]
|
||||
public OrganSlot? ParentSlot;
|
||||
}
|
||||
16
Content.Shared/Body/Organ/OrganComponentState.cs
Normal file
16
Content.Shared/Body/Organ/OrganComponentState.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Organ;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class OrganComponentState : ComponentState
|
||||
{
|
||||
public readonly EntityUid? Body;
|
||||
public readonly OrganSlot? Parent;
|
||||
|
||||
public OrganComponentState(EntityUid? body, OrganSlot? parent)
|
||||
{
|
||||
Body = body;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
20
Content.Shared/Body/Organ/OrganSlot.cs
Normal file
20
Content.Shared/Body/Organ/OrganSlot.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Organ;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
[DataRecord]
|
||||
public sealed record OrganSlot(string Id, EntityUid Parent)
|
||||
{
|
||||
public EntityUid? Child { get; set; }
|
||||
|
||||
// Rider doesn't suggest explicit properties during deconstruction without this
|
||||
public void Deconstruct(out EntityUid? child, out string id, out EntityUid parent)
|
||||
{
|
||||
child = Child;
|
||||
id = Id;
|
||||
parent = Parent;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
//TODO: This should be a prototype. --DrSmugleaf
|
||||
/// <summary>
|
||||
/// Determines whether two <see cref="SharedBodyPartComponent"/>s can connect.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyPartCompatibility
|
||||
{
|
||||
Universal = 0,
|
||||
Biological,
|
||||
Mechanical,
|
||||
Slime,
|
||||
}
|
||||
}
|
||||
44
Content.Shared/Body/Part/BodyPartComponent.cs
Normal file
44
Content.Shared/Body/Part/BodyPartComponent.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body.Part;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed class BodyPartComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("body")]
|
||||
public EntityUid? Body;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("parent")]
|
||||
public BodyPartSlot? ParentSlot;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("children")]
|
||||
public Dictionary<string, BodyPartSlot> Children = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("organs")]
|
||||
public Dictionary<string, OrganSlot> Organs = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("partType")]
|
||||
public BodyPartType PartType = BodyPartType.Other;
|
||||
|
||||
// TODO BODY Replace with a simulation of organs
|
||||
/// <summary>
|
||||
/// Whether or not the owning <see cref="Body"/> will die if all
|
||||
/// <see cref="BodyComponent"/>s of this type are removed from it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("vital")]
|
||||
public bool IsVital;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("symmetry")]
|
||||
public BodyPartSymmetry Symmetry = BodyPartSymmetry.None;
|
||||
}
|
||||
34
Content.Shared/Body/Part/BodyPartComponentState.cs
Normal file
34
Content.Shared/Body/Part/BodyPartComponentState.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Body.Organ;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyPartComponentState : ComponentState
|
||||
{
|
||||
public readonly EntityUid? Body;
|
||||
public readonly BodyPartSlot? ParentSlot;
|
||||
public readonly Dictionary<string, BodyPartSlot> Children;
|
||||
public readonly Dictionary<string, OrganSlot> Organs;
|
||||
public readonly BodyPartType PartType;
|
||||
public readonly bool IsVital;
|
||||
public readonly BodyPartSymmetry Symmetry;
|
||||
|
||||
public BodyPartComponentState(
|
||||
EntityUid? body,
|
||||
BodyPartSlot? parentSlot,
|
||||
Dictionary<string, BodyPartSlot> children,
|
||||
Dictionary<string, OrganSlot> organs,
|
||||
BodyPartType partType,
|
||||
bool isVital,
|
||||
BodyPartSymmetry symmetry)
|
||||
{
|
||||
ParentSlot = parentSlot;
|
||||
Children = children;
|
||||
Organs = organs;
|
||||
PartType = partType;
|
||||
IsVital = isVital;
|
||||
Symmetry = symmetry;
|
||||
Body = body;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +1,21 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
namespace Content.Shared.Body.Part;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
[DataRecord]
|
||||
public sealed record BodyPartSlot(string Id, EntityUid Parent, BodyPartType? Type)
|
||||
{
|
||||
public sealed class BodyPartSlot
|
||||
public EntityUid? Child { get; set; }
|
||||
|
||||
// Rider doesn't suggest explicit properties during deconstruction without this
|
||||
public void Deconstruct(out EntityUid? child, out string id, out EntityUid parent, out BodyPartType? type)
|
||||
{
|
||||
public BodyPartSlot(string id, BodyPartType partType, IEnumerable<BodyPartSlot> connections)
|
||||
{
|
||||
Id = id;
|
||||
PartType = partType;
|
||||
Connections = new HashSet<BodyPartSlot>(connections);
|
||||
}
|
||||
|
||||
public BodyPartSlot(string id, BodyPartType partType)
|
||||
{
|
||||
Id = id;
|
||||
PartType = partType;
|
||||
Connections = new HashSet<BodyPartSlot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID of this slot.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The part type that this slot accepts.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public BodyPartType PartType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The part currently in this slot, if any.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public SharedBodyPartComponent? Part { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of slots that this slot connects to.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<BodyPartSlot> Connections { get; private set; }
|
||||
|
||||
public event Action<SharedBodyPartComponent>? PartAdded;
|
||||
|
||||
public event Action<SharedBodyPartComponent>? PartRemoved;
|
||||
|
||||
internal void SetConnectionsInternal(IEnumerable<BodyPartSlot> connections)
|
||||
{
|
||||
Connections = new HashSet<BodyPartSlot>(connections);
|
||||
}
|
||||
|
||||
public bool CanAddPart(SharedBodyPartComponent part)
|
||||
{
|
||||
return Part == null && part.PartType == PartType;
|
||||
}
|
||||
|
||||
public bool TryAddPart(SharedBodyPartComponent part)
|
||||
{
|
||||
if (!CanAddPart(part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetPart(part);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPart(SharedBodyPartComponent part)
|
||||
{
|
||||
if (Part != null)
|
||||
{
|
||||
RemovePart();
|
||||
}
|
||||
|
||||
Part = part;
|
||||
PartAdded?.Invoke(part);
|
||||
}
|
||||
|
||||
public bool RemovePart()
|
||||
{
|
||||
if (Part == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var old = Part;
|
||||
Part = null;
|
||||
|
||||
PartRemoved?.Invoke(old);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Part = null;
|
||||
Connections.Clear();
|
||||
PartAdded = null;
|
||||
PartRemoved = null;
|
||||
}
|
||||
child = Child;
|
||||
id = Id;
|
||||
parent = Parent;
|
||||
type = Type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the symmetry of a <see cref="SharedBodyPartComponent"/>.
|
||||
/// Defines the symmetry of a <see cref="BodyComponent"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyPartSymmetry
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the type of a <see cref="SharedBodyPartComponent"/>.
|
||||
/// Defines the type of a <see cref="BodyComponent"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyPartType
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
@@ -10,18 +9,16 @@ namespace Content.Shared.Body.Part
|
||||
public interface IBodyPartAdded : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a <see cref="SharedBodyPartComponent"/> is added to the
|
||||
/// Called when a <see cref="BodyComponent"/> is added to the
|
||||
/// entity owning this component.
|
||||
/// </summary>
|
||||
/// <param name="args">Information about the part that was added.</param>
|
||||
void BodyPartAdded(BodyPartAddedEventArgs args);
|
||||
}
|
||||
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyPartAddedEventArgs : EventArgs
|
||||
{
|
||||
public BodyPartAddedEventArgs(string slot, SharedBodyPartComponent part)
|
||||
public BodyPartAddedEventArgs(string slot, BodyPartComponent part)
|
||||
{
|
||||
Slot = slot;
|
||||
Part = part;
|
||||
@@ -35,6 +32,6 @@ namespace Content.Shared.Body.Part
|
||||
/// <summary>
|
||||
/// The part that was added.
|
||||
/// </summary>
|
||||
public SharedBodyPartComponent Part { get; }
|
||||
public BodyPartComponent Part { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
@@ -10,17 +9,16 @@ namespace Content.Shared.Body.Part
|
||||
public interface IBodyPartRemoved
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a <see cref="SharedBodyPartComponent"/> is removed from the
|
||||
/// Called when a <see cref="BodyComponent"/> is removed from the
|
||||
/// entity owning this component.
|
||||
/// </summary>
|
||||
/// <param name="args">Information about the part that was removed.</param>
|
||||
void BodyPartRemoved(BodyPartRemovedEventArgs args);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BodyPartRemovedEventArgs : EventArgs
|
||||
{
|
||||
public BodyPartRemovedEventArgs(string slot, SharedBodyPartComponent part)
|
||||
public BodyPartRemovedEventArgs(string slot, BodyPartComponent part)
|
||||
{
|
||||
Slot = slot;
|
||||
Part = part;
|
||||
@@ -34,6 +32,6 @@ namespace Content.Shared.Body.Part
|
||||
/// <summary>
|
||||
/// The part that was removed.
|
||||
/// </summary>
|
||||
public SharedBodyPartComponent Part { get; }
|
||||
public BodyPartComponent Part { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Content.Shared/Body/Systems/SharedBodySystem.Body.cs
Normal file
213
Content.Shared/Body/Systems/SharedBodySystem.Body.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Coordinates;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
public void InitializeBody()
|
||||
{
|
||||
SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
|
||||
SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, ComponentGetState>(OnBodyGetState);
|
||||
SubscribeLocalEvent<BodyComponent, ComponentHandleState>(OnBodyHandleState);
|
||||
}
|
||||
|
||||
private void OnBodyMapInit(EntityUid bodyId, BodyComponent body, MapInitEvent args)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (body.Prototype == null || body.Root != null)
|
||||
return;
|
||||
|
||||
var prototype = Prototypes.Index<BodyPrototype>(body.Prototype);
|
||||
InitBody(body, prototype);
|
||||
}
|
||||
|
||||
private void OnBodyInit(EntityUid bodyId, BodyComponent body, ComponentInit args)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (body.Prototype == null || body.Root != null)
|
||||
return;
|
||||
|
||||
var prototype = Prototypes.Index<BodyPrototype>(body.Prototype);
|
||||
InitBody(body, prototype);
|
||||
}
|
||||
|
||||
private void OnBodyGetState(EntityUid uid, BodyComponent body, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new BodyComponentState(body.Root, body.GibSound);
|
||||
}
|
||||
|
||||
private void OnBodyHandleState(EntityUid uid, BodyComponent body, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not BodyComponentState state)
|
||||
return;
|
||||
|
||||
body.Root = state.Root;
|
||||
body.GibSound = state.GibSound;
|
||||
}
|
||||
|
||||
public bool TryCreateBodyRootSlot(
|
||||
EntityUid? bodyId,
|
||||
string slotId,
|
||||
[NotNullWhen(true)] out BodyPartSlot? slot,
|
||||
BodyComponent? body = null)
|
||||
{
|
||||
slot = null;
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (bodyId == null ||
|
||||
!Resolve(bodyId.Value, ref body, false) ||
|
||||
body.Root != null)
|
||||
return false;
|
||||
|
||||
slot = new BodyPartSlot(slotId, bodyId.Value, null);
|
||||
body.Root = slot;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitBody(BodyComponent body, BodyPrototype prototype)
|
||||
{
|
||||
var root = prototype.Slots[prototype.Root];
|
||||
var bodyId = Spawn(root.Part, body.Owner.ToCoordinates());
|
||||
var partComponent = Comp<BodyPartComponent>(bodyId);
|
||||
var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType);
|
||||
body.Root = slot;
|
||||
partComponent.Body = bodyId;
|
||||
|
||||
Containers.EnsureContainer<Container>(body.Owner, BodyContainerId);
|
||||
|
||||
AttachPart(bodyId, slot, partComponent);
|
||||
InitPart(partComponent, prototype, prototype.Root);
|
||||
}
|
||||
|
||||
private void InitPart(BodyPartComponent parent, BodyPrototype prototype, string slotId, HashSet<string>? initialized = null)
|
||||
{
|
||||
initialized ??= new HashSet<string>();
|
||||
|
||||
if (initialized.Contains(slotId))
|
||||
return;
|
||||
|
||||
initialized.Add(slotId);
|
||||
|
||||
var (_, connections, organs) = prototype.Slots[slotId];
|
||||
connections = new HashSet<string>(connections);
|
||||
connections.ExceptWith(initialized);
|
||||
|
||||
var coordinates = parent.Owner.ToCoordinates();
|
||||
var subConnections = new List<(BodyPartComponent child, string slotId)>();
|
||||
|
||||
Containers.EnsureContainer<Container>(parent.Owner, BodyContainerId);
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
var childSlot = prototype.Slots[connection];
|
||||
var childPart = Spawn(childSlot.Part, coordinates);
|
||||
var childPartComponent = Comp<BodyPartComponent>(childPart);
|
||||
var slot = CreatePartSlot(connection, parent.Owner, childPartComponent.PartType, parent);
|
||||
if (slot == null)
|
||||
{
|
||||
Logger.Error($"Could not create slot for connection {connection} in body {prototype.ID}");
|
||||
continue;
|
||||
}
|
||||
|
||||
AttachPart(childPart, slot, childPartComponent);
|
||||
subConnections.Add((childPartComponent, connection));
|
||||
}
|
||||
|
||||
foreach (var (organSlotId, organId) in organs)
|
||||
{
|
||||
var organ = Spawn(organId, coordinates);
|
||||
var organComponent = Comp<OrganComponent>(organ);
|
||||
|
||||
var slot = CreateOrganSlot(organSlotId, parent.Owner, parent);
|
||||
if (slot == null)
|
||||
{
|
||||
Logger.Error($"Could not create slot for connection {organSlotId} in body {prototype.ID}");
|
||||
continue;
|
||||
}
|
||||
|
||||
InsertOrgan(organ, slot, organComponent);
|
||||
}
|
||||
|
||||
foreach (var connection in subConnections)
|
||||
{
|
||||
InitPart(connection.child, prototype, connection.slotId, initialized);
|
||||
}
|
||||
}
|
||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(EntityUid? id, BodyComponent? body = null)
|
||||
{
|
||||
if (id == null ||
|
||||
!Resolve(id.Value, ref body, false) ||
|
||||
!TryComp(body.Root.Child, out BodyPartComponent? part))
|
||||
yield break;
|
||||
|
||||
yield return (body.Root.Child.Value, part);
|
||||
|
||||
foreach (var child in GetPartChildren(body.Root.Child))
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(EntityUid? bodyId, BodyComponent? body = null)
|
||||
{
|
||||
if (bodyId == null || !Resolve(bodyId.Value, ref body, false))
|
||||
yield break;
|
||||
|
||||
foreach (var part in GetBodyChildren(bodyId, body))
|
||||
{
|
||||
foreach (var organ in GetPartOrgans(part.Id, part.Component))
|
||||
{
|
||||
yield return organ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<BodyPartSlot> GetBodyAllSlots(EntityUid? bodyId, BodyComponent? body = null)
|
||||
{
|
||||
if (bodyId == null || !Resolve(bodyId.Value, ref body, false))
|
||||
yield break;
|
||||
|
||||
foreach (var slot in GetPartAllSlots(body.Root.Child))
|
||||
{
|
||||
yield return slot;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual HashSet<EntityUid> GibBody(EntityUid? partId, bool gibOrgans = false,
|
||||
BodyComponent? body = null)
|
||||
{
|
||||
if (partId == null || !Resolve(partId.Value, ref body, false))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var parts = GetBodyChildren(partId, body).ToArray();
|
||||
var gibs = new HashSet<EntityUid>(parts.Length);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
DropPart(part.Id, part.Component);
|
||||
gibs.Add(part.Id);
|
||||
|
||||
if (!gibOrgans)
|
||||
continue;
|
||||
|
||||
foreach (var organ in GetPartOrgans(part.Id, part.Component))
|
||||
{
|
||||
DropOrgan(organ.Id, organ.Component);
|
||||
gibs.Add(organ.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return gibs;
|
||||
}
|
||||
}
|
||||
230
Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
Normal file
230
Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
private void InitializeOrgans()
|
||||
{
|
||||
SubscribeLocalEvent<OrganComponent, ComponentGetState>(OnOrganGetState);
|
||||
SubscribeLocalEvent<OrganComponent, ComponentHandleState>(OnOrganHandleState);
|
||||
}
|
||||
|
||||
private OrganSlot? CreateOrganSlot(string slotId, EntityUid parent, BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(parent, ref part, false))
|
||||
return null;
|
||||
|
||||
var slot = new OrganSlot(slotId, parent);
|
||||
part.Organs.Add(slotId, slot);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
private bool CanInsertOrgan(EntityUid? organId, OrganSlot slot, OrganComponent? organ = null)
|
||||
{
|
||||
return organId != null &&
|
||||
slot.Child == null &&
|
||||
Resolve(organId.Value, ref organ, false) &&
|
||||
Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container) &&
|
||||
container.CanInsert(organId.Value);
|
||||
}
|
||||
|
||||
private void OnOrganGetState(EntityUid uid, OrganComponent organ, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new OrganComponentState(organ.Body, organ.ParentSlot);
|
||||
}
|
||||
|
||||
private void OnOrganHandleState(EntityUid uid, OrganComponent organ, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not OrganComponentState state)
|
||||
return;
|
||||
|
||||
organ.Body = state.Body;
|
||||
organ.ParentSlot = state.Parent;
|
||||
}
|
||||
|
||||
public bool InsertOrgan(EntityUid? organId, OrganSlot slot, OrganComponent? organ = null)
|
||||
{
|
||||
if (organId == null ||
|
||||
!Resolve(organId.Value, ref organ, false) ||
|
||||
!CanInsertOrgan(organId, slot, organ))
|
||||
return false;
|
||||
|
||||
DropOrgan(slot.Child);
|
||||
DropOrgan(organId, organ);
|
||||
|
||||
var container = Containers.EnsureContainer<Container>(slot.Parent, BodyContainerId);
|
||||
if (!container.Insert(organId.Value))
|
||||
return false;
|
||||
|
||||
slot.Child = organId;
|
||||
organ.ParentSlot = slot;
|
||||
organ.Body = CompOrNull<BodyPartComponent>(slot.Parent)?.Body;
|
||||
|
||||
Dirty(slot.Parent);
|
||||
Dirty(organId.Value);
|
||||
|
||||
if (organ.Body == null)
|
||||
{
|
||||
RaiseLocalEvent(organId.Value, new AddedToPartEvent(slot.Parent));
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseLocalEvent(organId.Value, new AddedToPartInBodyEvent(organ.Body.Value, slot.Parent));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AddOrganToFirstValidSlot(
|
||||
EntityUid? childId,
|
||||
EntityUid? parentId,
|
||||
OrganComponent? child = null,
|
||||
BodyPartComponent? parent = null)
|
||||
{
|
||||
if (childId == null ||
|
||||
!Resolve(childId.Value, ref child, false) ||
|
||||
parentId == null ||
|
||||
!Resolve(parentId.Value, ref parent, false))
|
||||
return false;
|
||||
|
||||
foreach (var slot in parent.Organs.Values)
|
||||
{
|
||||
if (slot.Child == null)
|
||||
continue;
|
||||
|
||||
InsertOrgan(childId, slot, child);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool DropOrgan(EntityUid? organId, OrganComponent? organ = null)
|
||||
{
|
||||
if (organId == null ||
|
||||
!Resolve(organId.Value, ref organ, false) ||
|
||||
organ.ParentSlot is not { } slot)
|
||||
return false;
|
||||
|
||||
var oldParent = CompOrNull<BodyPartComponent>(organ.ParentSlot.Parent);
|
||||
|
||||
slot.Child = null;
|
||||
organ.ParentSlot = null;
|
||||
organ.Body = null;
|
||||
|
||||
if (Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container))
|
||||
container.Remove(organId.Value);
|
||||
|
||||
if (TryComp(organId, out TransformComponent? transform))
|
||||
transform.AttachToGridOrMap();
|
||||
|
||||
organ.Owner.RandomOffset(0.25f);
|
||||
|
||||
if (oldParent == null)
|
||||
return true;
|
||||
|
||||
if (oldParent.Body != null)
|
||||
{
|
||||
RaiseLocalEvent(organId.Value, new RemovedFromPartInBodyEvent(oldParent.Body.Value, oldParent.Owner));
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseLocalEvent(organId.Value, new RemovedFromPartEvent(oldParent.Owner));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DropOrganAt(EntityUid? organId, EntityCoordinates dropAt, OrganComponent? organ = null)
|
||||
{
|
||||
if (organId == null || !DropOrgan(organId, organ))
|
||||
return false;
|
||||
|
||||
if (TryComp(organId.Value, out TransformComponent? transform))
|
||||
transform.Coordinates = dropAt;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DeleteOrgan(EntityUid? id, OrganComponent? part = null)
|
||||
{
|
||||
if (id == null || !Resolve(id.Value, ref part, false))
|
||||
return false;
|
||||
|
||||
DropOrgan(id, part);
|
||||
|
||||
if (Deleted(id.Value))
|
||||
return false;
|
||||
|
||||
Del(id.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of ValueTuples of <see cref="T"/> and OrganComponent on each organ
|
||||
/// in the given body.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to check for the component on.</param>
|
||||
/// <param name="body">The body to check for organs on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
public List<(T Comp, OrganComponent Organ)> GetBodyOrganComponents<T>(
|
||||
EntityUid uid,
|
||||
BodyComponent? body = null)
|
||||
where T : Component
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
return new List<(T Comp, OrganComponent Organ)>();
|
||||
|
||||
var query = EntityManager.GetEntityQuery<T>();
|
||||
var list = new List<(T Comp, OrganComponent Organ)>(3);
|
||||
foreach (var organ in GetBodyOrgans(uid, body))
|
||||
{
|
||||
if (query.TryGetComponent(organ.Id, out var comp))
|
||||
list.Add((comp, organ.Component));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a list of ValueTuples of <see cref="T"/> and OrganComponent on each organs
|
||||
/// in the given body.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to check for the component on.</param>
|
||||
/// <param name="comps">The list of components.</param>
|
||||
/// <param name="body">The body to check for organs on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
/// <returns>Whether any were found.</returns>
|
||||
public bool TryGetBodyOrganComponents<T>(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] out List<(T Comp, OrganComponent Organ)>? comps,
|
||||
BodyComponent? body = null)
|
||||
where T : Component
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
{
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
comps = GetBodyOrganComponents<T>(uid, body);
|
||||
|
||||
if (comps.Count == 0)
|
||||
{
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
359
Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
Normal file
359
Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
Normal file
@@ -0,0 +1,359 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
private void InitializeParts()
|
||||
{
|
||||
SubscribeLocalEvent<BodyPartComponent, ComponentRemove>(OnPartRemoved);
|
||||
SubscribeLocalEvent<BodyPartComponent, ComponentGetState>(OnPartGetState);
|
||||
SubscribeLocalEvent<BodyPartComponent, ComponentHandleState>(OnPartHandleState);
|
||||
}
|
||||
|
||||
private void OnPartGetState(EntityUid uid, BodyPartComponent part, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new BodyPartComponentState(
|
||||
part.Body,
|
||||
part.ParentSlot,
|
||||
part.Children,
|
||||
part.Organs,
|
||||
part.PartType,
|
||||
part.IsVital,
|
||||
part.Symmetry
|
||||
);
|
||||
}
|
||||
|
||||
private void OnPartHandleState(EntityUid uid, BodyPartComponent part, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not BodyPartComponentState state)
|
||||
return;
|
||||
|
||||
part.Body = state.Body;
|
||||
part.ParentSlot = state.ParentSlot;
|
||||
part.Children = state.Children;
|
||||
part.Organs = state.Organs;
|
||||
part.PartType = state.PartType;
|
||||
part.IsVital = state.IsVital;
|
||||
part.Symmetry = state.Symmetry;
|
||||
}
|
||||
|
||||
private void OnPartRemoved(EntityUid uid, BodyPartComponent part, ComponentRemove args)
|
||||
{
|
||||
if (part.ParentSlot is { } slot)
|
||||
{
|
||||
slot.Child = null;
|
||||
Dirty(slot.Parent);
|
||||
}
|
||||
|
||||
foreach (var childSlot in part.Children.Values.ToArray())
|
||||
{
|
||||
DropPart(childSlot.Child);
|
||||
}
|
||||
}
|
||||
|
||||
private BodyPartSlot? CreatePartSlot(
|
||||
string slotId,
|
||||
EntityUid parent,
|
||||
BodyPartType partType,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(parent, ref part, false))
|
||||
return null;
|
||||
|
||||
var slot = new BodyPartSlot(slotId, parent, partType);
|
||||
part.Children.Add(slotId, slot);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
public bool TryCreatePartSlot(
|
||||
EntityUid? parentId,
|
||||
string id,
|
||||
[NotNullWhen(true)] out BodyPartSlot? slot,
|
||||
BodyPartComponent? parent = null)
|
||||
{
|
||||
slot = null;
|
||||
|
||||
if (parentId == null ||
|
||||
!Resolve(parentId.Value, ref parent, false))
|
||||
return false;
|
||||
|
||||
slot = new BodyPartSlot(id, parentId.Value, null);
|
||||
if (!parent.Children.TryAdd(id, slot))
|
||||
{
|
||||
slot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCreatePartSlotAndAttach(
|
||||
EntityUid? parentId,
|
||||
string id,
|
||||
EntityUid? childId,
|
||||
BodyPartComponent? parent = null,
|
||||
BodyPartComponent? child = null)
|
||||
{
|
||||
return TryCreatePartSlot(parentId, id, out var slot, parent) && AttachPart(childId, slot, child);
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetPartChildren(EntityUid? id, BodyPartComponent? part = null)
|
||||
{
|
||||
if (id == null || !Resolve(id.Value, ref part, false))
|
||||
yield break;
|
||||
|
||||
foreach (var slot in part.Children.Values)
|
||||
{
|
||||
if (!TryComp(slot.Child, out BodyPartComponent? childPart))
|
||||
continue;
|
||||
|
||||
yield return (slot.Child.Value, childPart);
|
||||
|
||||
foreach (var subChild in GetPartChildren(slot.Child, childPart))
|
||||
{
|
||||
yield return subChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid? partId, BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null || !Resolve(partId.Value, ref part, false))
|
||||
yield break;
|
||||
|
||||
foreach (var slot in part.Organs.Values)
|
||||
{
|
||||
if (!TryComp(slot.Child, out OrganComponent? organ))
|
||||
continue;
|
||||
|
||||
yield return (slot.Child.Value, organ);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<BodyPartSlot> GetPartAllSlots(EntityUid? partId, BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null ||
|
||||
!Resolve(partId.Value, ref part, false))
|
||||
yield break;
|
||||
|
||||
foreach (var slot in part.Children.Values)
|
||||
{
|
||||
yield return slot;
|
||||
|
||||
if (!TryComp(slot.Child, out BodyComponent? childPart))
|
||||
continue;
|
||||
|
||||
foreach (var subChild in GetBodyAllSlots(slot.Child, childPart))
|
||||
{
|
||||
yield return subChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanAttachPart([NotNullWhen(true)] EntityUid? partId, BodyPartSlot slot, BodyPartComponent? part = null)
|
||||
{
|
||||
return partId != null &&
|
||||
slot.Child == null &&
|
||||
Resolve(partId.Value, ref part, false) &&
|
||||
(slot.Type == null || slot.Type == part.PartType) &&
|
||||
Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container) &&
|
||||
container.CanInsert(partId.Value);
|
||||
}
|
||||
|
||||
public virtual bool AttachPart(
|
||||
EntityUid? partId,
|
||||
BodyPartSlot slot,
|
||||
[NotNullWhen(true)] BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null ||
|
||||
!Resolve(partId.Value, ref part, false) ||
|
||||
!CanAttachPart(partId, slot, part))
|
||||
return false;
|
||||
|
||||
DropPart(slot.Child);
|
||||
DropPart(partId, part);
|
||||
|
||||
var container = Containers.EnsureContainer<Container>(slot.Parent, BodyContainerId);
|
||||
if (!container.Insert(partId.Value))
|
||||
return false;
|
||||
|
||||
slot.Child = partId;
|
||||
part.ParentSlot = slot;
|
||||
|
||||
if (TryComp(slot.Parent, out BodyPartComponent? parentPart))
|
||||
{
|
||||
part.Body = parentPart.Body;
|
||||
}
|
||||
else if (TryComp(slot.Parent, out BodyComponent? parentBody))
|
||||
{
|
||||
part.Body = parentBody.Owner;
|
||||
}
|
||||
else
|
||||
{
|
||||
part.Body = null;
|
||||
}
|
||||
|
||||
Dirty(slot.Parent);
|
||||
Dirty(partId.Value);
|
||||
|
||||
if (part.Body is { } newBody)
|
||||
{
|
||||
var argsAdded = new BodyPartAddedEventArgs(slot.Id, part);
|
||||
|
||||
// TODO: Body refactor. Somebody is doing it
|
||||
// EntitySystem.Get<SharedHumanoidAppearanceSystem>().BodyPartAdded(Owner, argsAdded);
|
||||
foreach (var component in AllComps<IBodyPartAdded>(newBody).ToArray())
|
||||
{
|
||||
component.BodyPartAdded(argsAdded);
|
||||
}
|
||||
|
||||
foreach (var organ in GetPartOrgans(partId, part))
|
||||
{
|
||||
RaiseLocalEvent(organ.Id, new AddedToBodyEvent(newBody), true);
|
||||
}
|
||||
|
||||
Dirty(newBody);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool DropPart(EntityUid? partId, [NotNullWhen(true)] BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null ||
|
||||
!Resolve(partId.Value, ref part, false) ||
|
||||
part.ParentSlot is not { } slot)
|
||||
return false;
|
||||
|
||||
var oldBody = part.Body;
|
||||
|
||||
slot.Child = null;
|
||||
part.ParentSlot = null;
|
||||
part.Body = null;
|
||||
|
||||
if (Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container))
|
||||
container.Remove(partId.Value);
|
||||
|
||||
if (TryComp(partId, out TransformComponent? transform))
|
||||
transform.AttachToGridOrMap();
|
||||
|
||||
part.Owner.RandomOffset(0.25f);
|
||||
|
||||
if (oldBody != null)
|
||||
{
|
||||
var args = new BodyPartRemovedEventArgs(slot.Id, part);
|
||||
foreach (var component in AllComps<IBodyPartRemoved>(oldBody.Value))
|
||||
{
|
||||
component.BodyPartRemoved(args);
|
||||
}
|
||||
|
||||
if (part.PartType == BodyPartType.Leg &&
|
||||
!GetBodyChildrenOfType(oldBody, BodyPartType.Leg).Any())
|
||||
{
|
||||
Standing.Down(oldBody.Value);
|
||||
}
|
||||
|
||||
if (part.IsVital && !GetBodyChildrenOfType(oldBody, part.PartType).Any())
|
||||
{
|
||||
// TODO BODY SYSTEM KILL : Find a more elegant way of killing em than just dumping bloodloss damage.
|
||||
var damage = new DamageSpecifier(Prototypes.Index<DamageTypePrototype>("Bloodloss"), 300);
|
||||
Damageable.TryChangeDamage(part.Owner, damage);
|
||||
}
|
||||
|
||||
foreach (var organSlot in part.Organs.Values)
|
||||
{
|
||||
if (organSlot.Child is not { } child)
|
||||
continue;
|
||||
|
||||
RaiseLocalEvent(child, new RemovedFromBodyEvent(oldBody.Value), true);
|
||||
}
|
||||
}
|
||||
|
||||
Dirty(slot.Parent);
|
||||
Dirty(partId.Value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DropPartAt(EntityUid? partId, EntityCoordinates dropAt, BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null || !DropPart(partId, part))
|
||||
return false;
|
||||
|
||||
if (TryComp(partId.Value, out TransformComponent? transform))
|
||||
transform.Coordinates = dropAt;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OrphanPart(EntityUid? partId, BodyPartComponent? part = null)
|
||||
{
|
||||
if (partId == null || !Resolve(partId.Value, ref part, false))
|
||||
return false;
|
||||
|
||||
DropPart(partId, part);
|
||||
|
||||
foreach (var slot in part.Children.Values)
|
||||
{
|
||||
DropPart(slot.Child);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool DeletePart(EntityUid? id, BodyPartComponent? part = null)
|
||||
{
|
||||
if (id == null || !Resolve(id.Value, ref part, false))
|
||||
return false;
|
||||
|
||||
DropPart(id, part);
|
||||
|
||||
if (Deleted(id.Value))
|
||||
return false;
|
||||
|
||||
Del(id.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(EntityUid? bodyId, BodyPartType type, BodyComponent? body = null)
|
||||
{
|
||||
foreach (var part in GetBodyChildren(bodyId, body))
|
||||
{
|
||||
if (part.Component.PartType == type)
|
||||
yield return part;
|
||||
}
|
||||
}
|
||||
|
||||
public bool BodyHasChildOfType(EntityUid? bodyId, BodyPartType type, BodyComponent? body = null)
|
||||
{
|
||||
return GetBodyChildrenOfType(bodyId, type, body).Any();
|
||||
}
|
||||
|
||||
public bool BodyHasChild(
|
||||
EntityUid? parentId,
|
||||
EntityUid? childId,
|
||||
BodyComponent? parent = null,
|
||||
BodyPartComponent? child = null)
|
||||
{
|
||||
if (parentId == null ||
|
||||
!Resolve(parentId.Value, ref parent, false) ||
|
||||
childId == null ||
|
||||
!Resolve(childId.Value, ref child, false))
|
||||
return false;
|
||||
|
||||
return child.ParentSlot?.Child == parentId;
|
||||
}
|
||||
}
|
||||
26
Content.Shared/Body/Systems/SharedBodySystem.cs
Normal file
26
Content.Shared/Body/Systems/SharedBodySystem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public abstract partial class SharedBodySystem : EntitySystem
|
||||
{
|
||||
private const string BodyContainerId = "BodyContainer";
|
||||
|
||||
[Dependency] protected readonly IPrototypeManager Prototypes = default!;
|
||||
|
||||
[Dependency] protected readonly SharedContainerSystem Containers = default!;
|
||||
[Dependency] protected readonly DamageableSystem Damageable = default!;
|
||||
[Dependency] protected readonly StandingStateSystem Standing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeBody();
|
||||
InitializeParts();
|
||||
InitializeOrgans();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user