mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
parent
edfe6ed580
commit
63c90e4d4e
19 changed files with 1004 additions and 22 deletions
111
Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs
Normal file
111
Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using System.Linq;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DetachableOrganSystem))]
|
||||
public sealed class DetachableOrganSystemTest : GameTest
|
||||
{
|
||||
private const string DetachTestOldBody = "DetachTestOldBody";
|
||||
private const string DetachTestNewBody = "DetachTestNewBody";
|
||||
private const string DetachTestGrandParentOrgan = "DetachTestGrandParentOrgan";
|
||||
private const string DetachTestRootOrgan = "DetachTestRootOrgan";
|
||||
private const string DetachTestChildOrgan = "DetachTestChildOrgan";
|
||||
private const string DetachTestSiblingOrgan = "DetachTestSiblingOrgan";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {DetachTestOldBody}
|
||||
components:
|
||||
- type: Body
|
||||
|
||||
- type: entity
|
||||
id: {DetachTestNewBody}
|
||||
components:
|
||||
- type: Body
|
||||
|
||||
- type: entity
|
||||
id: {DetachTestGrandParentOrgan}
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ParentOrgan
|
||||
- type: ChildOrgan
|
||||
|
||||
- type: entity
|
||||
id: {DetachTestRootOrgan}
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ParentOrgan
|
||||
- type: ChildOrgan
|
||||
- type: DetachableOrgan
|
||||
detachedBody: DetachTestNewBody
|
||||
|
||||
- type: entity
|
||||
id: {DetachTestChildOrgan}
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ChildOrgan
|
||||
|
||||
- type: entity
|
||||
id: {DetachTestSiblingOrgan}
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ChildOrgan
|
||||
";
|
||||
|
||||
[SidedDependency(Side.Server)] private SharedContainerSystem _container = default!;
|
||||
[SidedDependency(Side.Server)] private OrganRelationSystem _organRelation = default!;
|
||||
[SidedDependency(Side.Server)] private DetachableOrganSystem _detachableOrgan = default!;
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void Detach()
|
||||
{
|
||||
var oldBody = SSpawn(DetachTestOldBody);
|
||||
var oldBodyContainer = _container.GetContainer(oldBody, BodyComponent.ContainerID);
|
||||
|
||||
var grandParent = SSpawn(DetachTestGrandParentOrgan);
|
||||
var root = SSpawn(DetachTestRootOrgan);
|
||||
var child = SSpawn(DetachTestChildOrgan);
|
||||
var sibling = SSpawn(DetachTestSiblingOrgan);
|
||||
|
||||
_container.Insert(grandParent, oldBodyContainer, force: true);
|
||||
_container.Insert(root, oldBodyContainer, force: true);
|
||||
_container.Insert(child, oldBodyContainer, force: true);
|
||||
_container.Insert(sibling, oldBodyContainer, force: true);
|
||||
|
||||
_organRelation.Relate(grandParent, root);
|
||||
_organRelation.Relate(grandParent, sibling);
|
||||
_organRelation.Relate(root, child);
|
||||
|
||||
var newBody = _detachableOrgan.Detach(root);
|
||||
|
||||
Assert.That(newBody, Is.Not.Null);
|
||||
|
||||
var newBodyContained = _container.GetContainer(newBody!.Value, BodyComponent.ContainerID).ContainedEntities.ToList();
|
||||
Assert.That(newBodyContained, Is.EquivalentTo(new[] { root, child }));
|
||||
|
||||
var oldBodyContained = oldBodyContainer.ContainedEntities.ToList();
|
||||
Assert.That(oldBodyContained, Is.EquivalentTo(new[] { grandParent, sibling }));
|
||||
|
||||
var grandParentComp = SComp<ParentOrganComponent>(grandParent);
|
||||
Assert.That(grandParentComp.Children, Does.Not.Contain(root));
|
||||
Assert.That(grandParentComp.Children, Does.Contain(sibling));
|
||||
|
||||
Assert.That(SComp<ChildOrganComponent>(root).Parent, Is.Null);
|
||||
Assert.That(SComp<ParentOrganComponent>(root).Children, Does.Contain(child));
|
||||
Assert.That(SComp<ChildOrganComponent>(child).Parent, Is.EqualTo(root));
|
||||
|
||||
Assert.That(SComp<OrganComponent>(root).Body, Is.EqualTo(newBody));
|
||||
Assert.That(SComp<OrganComponent>(child).Body, Is.EqualTo(newBody));
|
||||
Assert.That(SComp<OrganComponent>(grandParent).Body, Is.EqualTo(oldBody));
|
||||
Assert.That(SComp<OrganComponent>(sibling).Body, Is.EqualTo(oldBody));
|
||||
}
|
||||
}
|
||||
67
Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs
Normal file
67
Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System.Linq;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(InitialBodySystem))]
|
||||
public sealed class InitialBodySpawnTest : GameTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: organCategory
|
||||
id: InitialBodySpawnTestParent
|
||||
|
||||
- type: organCategory
|
||||
id: InitialBodySpawnTestChild
|
||||
|
||||
- type: entity
|
||||
id: InitialBodySpawnTestBody
|
||||
components:
|
||||
- type: Body
|
||||
- type: InitialBody
|
||||
organs:
|
||||
InitialBodySpawnTestParent: InitialBodySpawnTestParentOrgan
|
||||
InitialBodySpawnTestChild: InitialBodySpawnTestChildOrgan
|
||||
relationships:
|
||||
InitialBodySpawnTestParent: [InitialBodySpawnTestChild]
|
||||
|
||||
- type: entity
|
||||
id: InitialBodySpawnTestParentOrgan
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ParentOrgan
|
||||
|
||||
- type: entity
|
||||
id: InitialBodySpawnTestChildOrgan
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ChildOrgan
|
||||
";
|
||||
|
||||
[SidedDependency(Side.Server)] private SharedContainerSystem _container = default!;
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void SpawningWiresUpOrganRelations()
|
||||
{
|
||||
var body = SSpawn("InitialBodySpawnTestBody");
|
||||
|
||||
var bodyContainer = _container.GetContainer(body, BodyComponent.ContainerID);
|
||||
var contained = bodyContainer.ContainedEntities.ToList();
|
||||
Assert.That(contained, Has.Count.EqualTo(2));
|
||||
|
||||
var parent = contained.Single(SEntMan.HasComponent<ParentOrganComponent>);
|
||||
var child = contained.Single(SEntMan.HasComponent<ChildOrganComponent>);
|
||||
|
||||
var parentComp = SComp<ParentOrganComponent>(parent);
|
||||
var childComp = SComp<ChildOrganComponent>(child);
|
||||
|
||||
Assert.That(parentComp.Children, Does.Contain(child));
|
||||
Assert.That(childComp.Parent, Is.EqualTo(parent));
|
||||
}
|
||||
}
|
||||
179
Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs
Normal file
179
Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(InitialBodySystem))]
|
||||
public sealed class InitialBodyValidationTest : GameTest
|
||||
{
|
||||
[SidedDependency(Side.Server)] private IComponentFactory _componentFactory = default!;
|
||||
[SidedDependency(Side.Server)] private IPrototypeManager _prototype = default!;
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void RelatedOrgansCanBeRelated()
|
||||
{
|
||||
using var scope = Assert.EnterMultipleScope();
|
||||
|
||||
foreach (var proto in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
|
||||
continue;
|
||||
|
||||
if (!proto.TryComp<InitialBodyComponent>(out var initial, _componentFactory))
|
||||
continue;
|
||||
|
||||
if (initial.Relationships is not { } relationships)
|
||||
continue;
|
||||
|
||||
var organs = initial.Organs;
|
||||
foreach (var (parent, children) in relationships)
|
||||
{
|
||||
if (!organs.TryGetValue(parent, out var parentProtoId))
|
||||
continue;
|
||||
|
||||
var parentProto = _prototype.Index(parentProtoId);
|
||||
if (!parentProto.HasComp<ParentOrganComponent>(_componentFactory))
|
||||
{
|
||||
Assert.Fail($"{proto.ID}'s parent organ {parent} {parentProtoId} is missing {nameof(ParentOrganComponent)}");
|
||||
}
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (!organs.TryGetValue(child, out var childProtoId))
|
||||
continue;
|
||||
|
||||
var childProto = _prototype.Index(childProtoId);
|
||||
if (!childProto.HasComp<ChildOrganComponent>(_componentFactory))
|
||||
{
|
||||
Assert.Fail($"{proto.ID}'s child organ {child} {childProtoId} (child of {parent} {parentProtoId}) is missing {nameof(ChildOrganComponent)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void NoMultipleParents()
|
||||
{
|
||||
using var scope = Assert.EnterMultipleScope();
|
||||
|
||||
foreach (var proto in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
|
||||
continue;
|
||||
|
||||
if (!proto.TryComp<InitialBodyComponent>(out var initial, _componentFactory))
|
||||
continue;
|
||||
|
||||
if (initial.Relationships is not { } relationships)
|
||||
continue;
|
||||
|
||||
var parentOf = new Dictionary<ProtoId<OrganCategoryPrototype>, ProtoId<OrganCategoryPrototype>>();
|
||||
foreach (var (parent, children) in relationships)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (parentOf.TryGetValue(child, out var existingParent))
|
||||
{
|
||||
Assert.Fail($"{proto.ID}'s organ category {child} is claimed as a child by both {existingParent} and {parent}");
|
||||
}
|
||||
|
||||
parentOf[child] = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void InternalChildOrgansAreNotDetachable()
|
||||
{
|
||||
using var scope = Assert.EnterMultipleScope();
|
||||
|
||||
foreach (var proto in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
|
||||
continue;
|
||||
|
||||
if (!proto.HasComp<InternalChildOrganComponent>(_componentFactory))
|
||||
continue;
|
||||
|
||||
if (proto.HasComp<DetachableOrganComponent>(_componentFactory))
|
||||
{
|
||||
Assert.Fail($"{proto.ID} has both {nameof(InternalChildOrganComponent)} and {nameof(DetachableOrganComponent)}. Pick a lane, make your organs internal or detachable, but not both.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void NoCyclicalRelationships()
|
||||
{
|
||||
using var scope = Assert.EnterMultipleScope();
|
||||
|
||||
foreach (var proto in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
|
||||
continue;
|
||||
|
||||
if (!proto.TryComp<InitialBodyComponent>(out var initial, _componentFactory))
|
||||
continue;
|
||||
|
||||
if (initial.Relationships is not { } relationships)
|
||||
continue;
|
||||
|
||||
var visited = new HashSet<ProtoId<OrganCategoryPrototype>>();
|
||||
var stack = new List<ProtoId<OrganCategoryPrototype>>();
|
||||
|
||||
foreach (var parent in relationships.Keys)
|
||||
{
|
||||
if (TryFindCycle(parent, relationships, visited, stack, out var cycle))
|
||||
{
|
||||
Assert.Fail($"{proto.ID} has a cycle in its InitialBodyComponent relationships: {string.Join(" -> ", cycle)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryFindCycle(
|
||||
ProtoId<OrganCategoryPrototype> node,
|
||||
Dictionary<ProtoId<OrganCategoryPrototype>, HashSet<ProtoId<OrganCategoryPrototype>>> relationships,
|
||||
HashSet<ProtoId<OrganCategoryPrototype>> visited,
|
||||
List<ProtoId<OrganCategoryPrototype>> stack,
|
||||
out List<ProtoId<OrganCategoryPrototype>> cycle)
|
||||
{
|
||||
if (stack.Contains(node))
|
||||
{
|
||||
var start = stack.IndexOf(node);
|
||||
cycle = stack.Skip(start).Append(node).ToList();
|
||||
return true;
|
||||
}
|
||||
|
||||
cycle = new List<ProtoId<OrganCategoryPrototype>>();
|
||||
|
||||
if (!visited.Add(node))
|
||||
return false;
|
||||
|
||||
if (!relationships.TryGetValue(node, out var children))
|
||||
return false;
|
||||
|
||||
stack.Add(node);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (TryFindCycle(child, relationships, visited, stack, out cycle))
|
||||
return true;
|
||||
}
|
||||
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
using System.Linq;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(OrganRelationSystem))]
|
||||
public sealed class OrganRelationSystemTest : GameTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: OrganRelationTestOrgan
|
||||
components:
|
||||
- type: Organ
|
||||
- type: ParentOrgan
|
||||
- type: ChildOrgan
|
||||
";
|
||||
|
||||
[SidedDependency(Side.Server)] private OrganRelationSystem _organRelation = default!;
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void RelateAndOrphan()
|
||||
{
|
||||
var parent = SSpawn("OrganRelationTestOrgan");
|
||||
var child = SSpawn("OrganRelationTestOrgan");
|
||||
|
||||
_organRelation.Relate(parent, child);
|
||||
|
||||
var parentComp = SComp<ParentOrganComponent>(parent);
|
||||
var childComp = SComp<ChildOrganComponent>(child);
|
||||
|
||||
Assert.That(parentComp.Children, Does.Contain(child));
|
||||
Assert.That(childComp.Parent, Is.EqualTo(parent));
|
||||
|
||||
_organRelation.Orphan(child);
|
||||
|
||||
Assert.That(parentComp.Children, Does.Not.Contain(child));
|
||||
Assert.That(childComp.Parent, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void Traversal()
|
||||
{
|
||||
var grandParent = SSpawn("OrganRelationTestOrgan");
|
||||
var parent = SSpawn("OrganRelationTestOrgan");
|
||||
var child = SSpawn("OrganRelationTestOrgan");
|
||||
|
||||
_organRelation.Relate(grandParent, parent);
|
||||
_organRelation.Relate(parent, child);
|
||||
|
||||
var allChildren = _organRelation.AllChildren(grandParent).Select(e => e.Owner).ToList();
|
||||
Assert.That(allChildren, Is.EquivalentTo(new[] { parent, child }));
|
||||
|
||||
var allParents = _organRelation.AllParents(child).Select(e => e.Owner).ToList();
|
||||
Assert.That(allParents, Is.EquivalentTo(new[] { parent, grandParent }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void ParentDeletion()
|
||||
{
|
||||
var parent = SSpawn("OrganRelationTestOrgan");
|
||||
var child = SSpawn("OrganRelationTestOrgan");
|
||||
|
||||
_organRelation.Relate(parent, child);
|
||||
|
||||
SDeleteNow(parent);
|
||||
|
||||
Assert.That(SEntMan.Deleted(child), Is.False);
|
||||
Assert.That(SComp<ChildOrganComponent>(child).Parent, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[RunOnSide(Side.Server)]
|
||||
public void ChildDeletion()
|
||||
{
|
||||
var parent = SSpawn("OrganRelationTestOrgan");
|
||||
var child = SSpawn("OrganRelationTestOrgan");
|
||||
|
||||
_organRelation.Relate(parent, child);
|
||||
|
||||
SDeleteNow(child);
|
||||
|
||||
Assert.That(SComp<ParentOrganComponent>(parent).Children, Does.Not.Contain(child));
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,8 @@ public sealed class PrototypeSaveTest : GameTest
|
|||
{
|
||||
// The only prototypes that should get ignored are those that REQUIRE setup to get a sprite. At that point it is
|
||||
// the responsibility of the spawner to ensure that a valid sprite is set.
|
||||
"VirtualItem"
|
||||
"VirtualItem",
|
||||
"DetachedBody"
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
|
|
|||
42
Content.Server/Body/BodyCommand.cs
Normal file
42
Content.Server/Body/BodyCommand.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Content.Server.Body;
|
||||
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class BodyCommand : ToolshedCommand
|
||||
{
|
||||
private SharedContainerSystem? _container;
|
||||
|
||||
[CommandImplementation("insert")]
|
||||
public void Insert([PipedArgument] EntityUid body, EntityUid organ)
|
||||
{
|
||||
_container ??= GetSys<SharedContainerSystem>();
|
||||
|
||||
if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
|
||||
return;
|
||||
|
||||
_container.Insert(organ, container, force: true);
|
||||
}
|
||||
|
||||
[CommandImplementation("organs")]
|
||||
public IEnumerable<EntityUid> Organs([PipedArgument] EntityUid body)
|
||||
{
|
||||
_container ??= GetSys<SharedContainerSystem>();
|
||||
|
||||
if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
|
||||
return Enumerable.Empty<EntityUid>();
|
||||
|
||||
return container.ContainedEntities.ToList();
|
||||
}
|
||||
|
||||
[CommandImplementation("organs")]
|
||||
public IEnumerable<EntityUid> Organs([PipedArgument] IEnumerable<EntityUid> body)
|
||||
{
|
||||
return body.SelectMany(Organs);
|
||||
}
|
||||
}
|
||||
112
Content.Server/Body/OrganCommand.cs
Normal file
112
Content.Server/Body/OrganCommand.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Content.Server.Body;
|
||||
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class OrganCommand : ToolshedCommand
|
||||
{
|
||||
private DetachableOrganSystem? _detachableOrgan;
|
||||
private SharedContainerSystem? _container;
|
||||
private OrganRelationSystem? _organRelation;
|
||||
|
||||
[CommandImplementation("parent")]
|
||||
public EntityUid? Parent([PipedArgument] EntityUid child)
|
||||
{
|
||||
if (!TryComp<ChildOrganComponent>(child, out var childOrgan))
|
||||
return null;
|
||||
|
||||
return childOrgan.Parent;
|
||||
}
|
||||
|
||||
[CommandImplementation("parent")]
|
||||
public IEnumerable<EntityUid?> Parent([PipedArgument] IEnumerable<EntityUid> children)
|
||||
{
|
||||
return children.Select(Parent);
|
||||
}
|
||||
|
||||
[CommandImplementation("children")]
|
||||
public IEnumerable<EntityUid> Children([PipedArgument] EntityUid parent)
|
||||
{
|
||||
if (!TryComp<ParentOrganComponent>(parent, out var parentOrgan))
|
||||
yield break;
|
||||
|
||||
foreach (var child in parentOrgan.Children)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("children")]
|
||||
public IEnumerable<EntityUid> Children([PipedArgument] IEnumerable<EntityUid> parents)
|
||||
{
|
||||
return parents.SelectMany(Children);
|
||||
}
|
||||
|
||||
[CommandImplementation("detach")]
|
||||
public EntityUid? Detach([PipedArgument] EntityUid organ)
|
||||
{
|
||||
_detachableOrgan ??= GetSys<DetachableOrganSystem>();
|
||||
return _detachableOrgan.Detach(organ);
|
||||
}
|
||||
|
||||
[CommandImplementation("detach")]
|
||||
public IEnumerable<EntityUid?> Detach([PipedArgument] IEnumerable<EntityUid> organs)
|
||||
{
|
||||
return organs.Select(Detach);
|
||||
}
|
||||
|
||||
[CommandImplementation("attach")]
|
||||
public void Attach([PipedArgument] EntityUid parent, EntityUid child)
|
||||
{
|
||||
_container ??= GetSys<SharedContainerSystem>();
|
||||
_organRelation ??= GetSys<OrganRelationSystem>();
|
||||
|
||||
if (!TryComp<ParentOrganComponent>(parent, out var parentOrgan) ||
|
||||
!TryComp<ChildOrganComponent>(child, out var childOrgan) ||
|
||||
!TryComp<OrganComponent>(parent, out var parentOrganComponent) ||
|
||||
parentOrganComponent.Body is not { } body)
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
|
||||
return;
|
||||
|
||||
_container.Insert(child, container, force: true);
|
||||
_organRelation.Relate((parent, parentOrgan), (child, childOrgan));
|
||||
}
|
||||
|
||||
[CommandImplementation("attach")]
|
||||
public void Attach([PipedArgument] IEnumerable<EntityUid> parents, EntityUid child)
|
||||
{
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
Attach(parent, child);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("is")]
|
||||
public bool Is([PipedArgument] EntityUid organ, ProtoId<OrganCategoryPrototype> category)
|
||||
{
|
||||
if (!TryComp<OrganComponent>(organ, out var organComp))
|
||||
return false;
|
||||
|
||||
return organComp.Category == category;
|
||||
}
|
||||
|
||||
[CommandImplementation("is")]
|
||||
public IEnumerable<bool> Is([PipedArgument] IEnumerable<EntityUid> organs, ProtoId<OrganCategoryPrototype> category)
|
||||
{
|
||||
return organs.Select(it => Is(it, category));
|
||||
}
|
||||
|
||||
[CommandImplementation("of_type")]
|
||||
public IEnumerable<EntityUid> Typed([PipedArgument] IEnumerable<EntityUid> organs, ProtoId<OrganCategoryPrototype> category)
|
||||
{
|
||||
return organs.Where(it => Is(it, category));
|
||||
}
|
||||
}
|
||||
17
Content.Shared/Body/ChildOrganComponent.cs
Normal file
17
Content.Shared/Body/ChildOrganComponent.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
/// <summary>
|
||||
/// An organ that can be a child to a parent organ within a body.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(OrganRelationSystem))]
|
||||
public sealed partial class ChildOrganComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The current organ that's a parent to this one, if any
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Parent;
|
||||
}
|
||||
18
Content.Shared/Body/DetachableOrganComponent.cs
Normal file
18
Content.Shared/Body/DetachableOrganComponent.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
/// <summary>
|
||||
/// An organ that can be detached with all of its children into a new body
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(DetachableOrganSystem))]
|
||||
public sealed partial class DetachableOrganComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The body to spawn upon detaching
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId DetachedBody;
|
||||
}
|
||||
52
Content.Shared/Body/DetachableOrganSystem.cs
Normal file
52
Content.Shared/Body/DetachableOrganSystem.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Shared.Body;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class DetachableOrganSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private EntityQuery<DetachableOrganComponent> _detachableOrgan;
|
||||
[Dependency] private EntityQuery<OrganComponent> _organ;
|
||||
[Dependency] private OrganRelationSystem _organRelation = default!;
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Detaches an organ from its containing body.
|
||||
/// </summary>
|
||||
/// <param name="organ">The organ to detach</param>
|
||||
/// <returns>The body that spawned when this organ was detached</returns>
|
||||
[PublicAPI]
|
||||
public EntityUid? Detach(Entity<DetachableOrganComponent?> organ)
|
||||
{
|
||||
if (!_detachableOrgan.Resolve(organ, ref organ.Comp) || !_organ.TryComp(organ, out var organComp) || organComp.Body is not { } oldBody)
|
||||
return null;
|
||||
|
||||
_organRelation.Orphan(organ.Owner);
|
||||
var body = PredictedSpawnNextToOrDrop(organ.Comp.DetachedBody, oldBody);
|
||||
|
||||
if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
|
||||
{
|
||||
Log.Error($"Entity {ToPrettyString(body)} relied on by {nameof(DetachableOrganComponent)} on {ToPrettyString(organ)} is missing a container ({BodyComponent.ContainerID}).");
|
||||
Del(body);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_container.Insert(organ.Owner, container, force: true))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(organ)} could not be transferred to new body {ToPrettyString(body)}.");
|
||||
}
|
||||
|
||||
foreach (var child in _organRelation.AllChildren(organ.Owner))
|
||||
{
|
||||
if (!_container.Insert(child.Owner, container, force: true))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(child)} could not be transferred to new body {ToPrettyString(body)}.");
|
||||
_organRelation.Orphan(child.AsNullable());
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,10 @@ public sealed partial class InitialBodyComponent : Component
|
|||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public Dictionary<ProtoId<OrganCategoryPrototype>, EntProtoId<OrganComponent>> Organs;
|
||||
|
||||
/// <summary>
|
||||
/// Organ relationships, based on their categories. Keys are parents to their values.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<ProtoId<OrganCategoryPrototype>, HashSet<ProtoId<OrganCategoryPrototype>>>? Relationships;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
using System.Numerics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class InitialBodySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private OrganRelationSystem _organRelation = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -31,8 +33,9 @@ public sealed partial class InitialBodySystem : EntitySystem
|
|||
|
||||
var xform = Transform(ent);
|
||||
var coords = new EntityCoordinates(ent, Vector2.Zero);
|
||||
var spawned = new Dictionary<ProtoId<OrganCategoryPrototype>, EntityUid>();
|
||||
|
||||
foreach (var proto in ent.Comp.Organs.Values)
|
||||
foreach (var (part, proto) in ent.Comp.Organs)
|
||||
{
|
||||
// TODO: When e#6192 is merged replace this all with TrySpawnInContainer...
|
||||
var spawn = Spawn(proto, coords);
|
||||
|
|
@ -41,6 +44,26 @@ public sealed partial class InitialBodySystem : EntitySystem
|
|||
{
|
||||
Log.Error($"Entity {ToPrettyString(ent)} with a {nameof(InitialBodyComponent)} failed to insert an entity: {ToPrettyString(spawn)}.\n");
|
||||
Del(spawn);
|
||||
continue;
|
||||
}
|
||||
|
||||
spawned[part] = spawn;
|
||||
}
|
||||
|
||||
if (ent.Comp.Relationships is null)
|
||||
return;
|
||||
|
||||
foreach (var (partId, parentUid) in spawned)
|
||||
{
|
||||
if (!ent.Comp.Relationships.TryGetValue(partId, out var children))
|
||||
continue;
|
||||
|
||||
foreach (var childId in children)
|
||||
{
|
||||
if (!spawned.TryGetValue(childId, out var childUid))
|
||||
continue;
|
||||
|
||||
_organRelation.Relate(parentUid, childUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
Content.Shared/Body/InternalChildOrganComponent.cs
Normal file
9
Content.Shared/Body/InternalChildOrganComponent.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Marker components for child organs that are considered "internal" to their parent. e.g. kidneys are internal to a torso, but an arm isn't.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class InternalChildOrganComponent : Component;
|
||||
120
Content.Shared/Body/OrganRelationSystem.cs
Normal file
120
Content.Shared/Body/OrganRelationSystem.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class OrganRelationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private EntityQuery<ChildOrganComponent> _child;
|
||||
[Dependency] private EntityQuery<ParentOrganComponent> _parent;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ParentOrganComponent, ComponentShutdown>(OnParentShutdown);
|
||||
SubscribeLocalEvent<ChildOrganComponent, ComponentShutdown>(OnChildShutdown);
|
||||
}
|
||||
|
||||
private void OnChildShutdown(Entity<ChildOrganComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (ent.Comp.Parent is not { } parentUid)
|
||||
return;
|
||||
|
||||
var parentComp = _parent.Comp(parentUid);
|
||||
parentComp.Children.Remove(ent);
|
||||
Dirty(parentUid, parentComp);
|
||||
}
|
||||
|
||||
private void OnParentShutdown(Entity<ParentOrganComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (ent.Comp.Children.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var childUid in ent.Comp.Children)
|
||||
{
|
||||
var childComp = _child.Comp(childUid);
|
||||
childComp.Parent = null;
|
||||
|
||||
Dirty(childUid, childComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates a parent organ and a child organ.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void Relate(Entity<ParentOrganComponent?> parent, Entity<ChildOrganComponent?> child)
|
||||
{
|
||||
if (!_parent.Resolve(parent, ref parent.Comp) || !_child.Resolve(child, ref child.Comp))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(child.Comp.Parent == null);
|
||||
|
||||
parent.Comp.Children.Add(child);
|
||||
Dirty(parent, parent.Comp);
|
||||
|
||||
child.Comp.Parent = parent;
|
||||
Dirty(child, child.Comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Breaks the relationship between a child orphan and its parent.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void Orphan(Entity<ChildOrganComponent?> child)
|
||||
{
|
||||
if (!_child.Resolve(child, ref child.Comp))
|
||||
return;
|
||||
|
||||
if (child.Comp.Parent is not { } parentUid)
|
||||
return;
|
||||
|
||||
child.Comp.Parent = null;
|
||||
Dirty(child, child.Comp);
|
||||
|
||||
var parentComp = _parent.Comp(parentUid);
|
||||
parentComp.Children.Remove(child);
|
||||
Dirty(parentUid, parentComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all parents of a child organ recursively.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public IEnumerable<Entity<ParentOrganComponent>> AllParents(Entity<ChildOrganComponent?> child)
|
||||
{
|
||||
if (!_child.Resolve(child, ref child.Comp))
|
||||
yield break;
|
||||
|
||||
while (child.Comp?.Parent is { } parent)
|
||||
{
|
||||
yield return (parent, _parent.Comp(parent));
|
||||
|
||||
if (!_child.TryGetComponent(parent, out var parentChild))
|
||||
yield break;
|
||||
|
||||
child = (parent, parentChild);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all children of a parent organ recursively.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public IEnumerable<Entity<ChildOrganComponent>> AllChildren(Entity<ParentOrganComponent?> parent)
|
||||
{
|
||||
if (!_parent.Resolve(parent, ref parent.Comp, false))
|
||||
yield break;
|
||||
|
||||
foreach (var child in parent.Comp.Children)
|
||||
{
|
||||
yield return (child, _child.Comp(child));
|
||||
|
||||
foreach (var childChild in AllChildren(child))
|
||||
{
|
||||
yield return childChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Content.Shared/Body/ParentOrganComponent.cs
Normal file
17
Content.Shared/Body/ParentOrganComponent.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
/// <summary>
|
||||
/// An organ that can be a parent to other organs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(OrganRelationSystem))]
|
||||
public sealed partial class ParentOrganComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The current set of child organs parented to this one.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntityUid> Children = new();
|
||||
}
|
||||
23
Resources/Locale/en-US/body/commands.ftl
Normal file
23
Resources/Locale/en-US/body/commands.ftl
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
command-description-body-insert =
|
||||
Inserts the given organ into the body.
|
||||
|
||||
command-description-body-organs =
|
||||
Returns all organs contained within the body.
|
||||
|
||||
command-description-organ-parent =
|
||||
Returns the parent of the organ.
|
||||
|
||||
command-description-organ-children =
|
||||
Returns the children of the organ.
|
||||
|
||||
command-description-organ-detach =
|
||||
(DEBUG ONLY) Detaches an organ from its current body into a detached body.
|
||||
|
||||
command-description-organ-attach =
|
||||
Attaches an organ to another organ.
|
||||
|
||||
command-description-organ-is =
|
||||
Returns if an organ is the given type.
|
||||
|
||||
command-description-organ-of_type =
|
||||
Filters to organs of the given type.
|
||||
|
|
@ -43,7 +43,10 @@
|
|||
- Meat
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseTorso
|
||||
name: torso
|
||||
abstract: true
|
||||
|
|
@ -68,7 +71,10 @@
|
|||
- Special
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseHead
|
||||
name: head
|
||||
abstract: true
|
||||
|
|
@ -95,7 +101,10 @@
|
|||
- HeadTop
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseArmLeft
|
||||
name: left arm
|
||||
abstract: true
|
||||
|
|
@ -114,7 +123,10 @@
|
|||
- LArm
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseArmRight
|
||||
name: right arm
|
||||
abstract: true
|
||||
|
|
@ -133,7 +145,9 @@
|
|||
- RArm
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseHandLeft
|
||||
name: left hand
|
||||
abstract: true
|
||||
|
|
@ -156,7 +170,9 @@
|
|||
- LHand
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseHandRight
|
||||
name: right hand
|
||||
abstract: true
|
||||
|
|
@ -179,7 +195,10 @@
|
|||
- RHand
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseLegLeft
|
||||
name: left leg
|
||||
abstract: true
|
||||
|
|
@ -198,7 +217,10 @@
|
|||
- LLeg
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseParent
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseLegRight
|
||||
name: right leg
|
||||
abstract: true
|
||||
|
|
@ -217,7 +239,9 @@
|
|||
- RLeg
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseFootLeft
|
||||
name: left foot
|
||||
abstract: true
|
||||
|
|
@ -236,7 +260,9 @@
|
|||
- LFoot
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildExternal
|
||||
id: OrganBaseFootRight
|
||||
name: right foot
|
||||
abstract: true
|
||||
|
|
@ -255,7 +281,9 @@
|
|||
- RFoot
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseBrain
|
||||
name: brain
|
||||
abstract: true
|
||||
|
|
@ -288,7 +316,9 @@
|
|||
Taco: Brain
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseEyes
|
||||
name: eyes
|
||||
abstract: true
|
||||
|
|
@ -311,7 +341,9 @@
|
|||
- Eyes
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseTongue
|
||||
name: tongue
|
||||
abstract: true
|
||||
|
|
@ -324,7 +356,9 @@
|
|||
- state: tongue
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseAppendix
|
||||
name: appendix
|
||||
abstract: true
|
||||
|
|
@ -337,7 +371,9 @@
|
|||
- state: appendix
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseEars
|
||||
name: ears
|
||||
abstract: true
|
||||
|
|
@ -350,7 +386,9 @@
|
|||
- state: ears
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseLungs
|
||||
name: lungs
|
||||
abstract: true
|
||||
|
|
@ -383,7 +421,9 @@
|
|||
canReact: false
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseHeart
|
||||
name: heart
|
||||
abstract: true
|
||||
|
|
@ -399,7 +439,9 @@
|
|||
stages: [ Bloodstream ]
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseStomach
|
||||
name: stomach
|
||||
abstract: true
|
||||
|
|
@ -431,7 +473,9 @@
|
|||
maxVol: 150 # 1.25 liters which is average for a human
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseLiver
|
||||
name: liver
|
||||
abstract: true
|
||||
|
|
@ -449,7 +493,9 @@
|
|||
stages: [ Metabolites ]
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
parent:
|
||||
- OrganBase
|
||||
- OrganBaseChildInternal
|
||||
id: OrganBaseKidneys
|
||||
name: kidneys
|
||||
abstract: true
|
||||
|
|
@ -498,3 +544,26 @@
|
|||
sexStateOverrides:
|
||||
Male: torso_m
|
||||
Female: torso_f
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: OrganBaseParent
|
||||
components:
|
||||
- type: ParentOrgan
|
||||
- type: DetachableOrgan
|
||||
detachedBody: DetachedBody
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: OrganBaseChildExternal
|
||||
components:
|
||||
- type: ChildOrgan
|
||||
- type: DetachableOrgan
|
||||
detachedBody: DetachedBody
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: OrganBaseChildInternal
|
||||
components:
|
||||
- type: ChildOrgan
|
||||
- type: InternalChildOrgan
|
||||
|
|
|
|||
15
Resources/Prototypes/Body/detached.yml
Normal file
15
Resources/Prototypes/Body/detached.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
- type: entity
|
||||
parent:
|
||||
- BaseSpeciesLayers
|
||||
id: DetachedBody
|
||||
save: false
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: Body
|
||||
- type: VisualBody
|
||||
- type: ContainerContainer
|
||||
- type: Appearance
|
||||
- type: HideableHumanoidLayers
|
||||
- type: Item
|
||||
- type: InteractionOutline
|
||||
- type: InputMover
|
||||
|
|
@ -87,3 +87,11 @@
|
|||
type: HumanoidMarkingModifierBoundUserInterface
|
||||
enum.StrippingUiKey.Key:
|
||||
type: StrippableBoundUserInterface
|
||||
- type: InitialBody
|
||||
relationships:
|
||||
Torso: [Head, ArmLeft, ArmRight, LegLeft, LegRight, Appendix, Lungs, Heart, Stomach, Liver, Kidneys]
|
||||
Head: [Brain, Eyes, Tongue, Ears]
|
||||
ArmLeft: [HandLeft]
|
||||
ArmRight: [HandRight]
|
||||
LegLeft: [FootLeft]
|
||||
LegRight: [FootRight]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue