mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Nubody (#42419)
* Nubody * fix test fails * gibbing * lung test returns * doc comment * hand organ test * giblet test * yaml formatting * returning * relocate * trimming * re-smite * oops thusd tweak * arachnids have slower metabolism i guess * never mind the old behaviour is bad actually * rider whyyy * style changes and allat * fix collision --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3cd407e7a6
commit
d0c2734dad
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
public sealed class BodySystem : SharedBodySystem
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
@@ -94,7 +94,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
continue;
|
||||
|
||||
//these bloat the hell out of blood/fat
|
||||
if (entProto.HasComponent<BodyPartComponent>())
|
||||
if (entProto.HasComponent<OrganComponent>())
|
||||
continue;
|
||||
|
||||
//these feel obvious...
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using Content.Shared.Body.Organ;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
public sealed class HideMechanismsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
|
||||
|
||||
public override string Command => "hidemechanisms";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _, out var sprite))
|
||||
{
|
||||
_spriteSystem.SetContainerOccluded((uid, sprite), false);
|
||||
|
||||
var tempParent = uid;
|
||||
while (_containerSystem.TryGetContainingContainer((tempParent, null, null), out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
_spriteSystem.SetContainerOccluded((uid, sprite), true);
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Shared.Body.Organ;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
public sealed class ShowMechanismsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
|
||||
|
||||
public override string Command => "showmechanisms";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _, out var sprite))
|
||||
{
|
||||
_spriteSystem.SetContainerOccluded((uid, sprite), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Content.IntegrationTests/Tests/Body/GibbingTest.cs
Normal file
60
Content.IntegrationTests/Tests/Body/GibbingTest.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Gibbing;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(GibbableOrganSystem))]
|
||||
public sealed class GibletTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: GibbingBody
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: Giblet
|
||||
- id: Giblet
|
||||
- id: Giblet
|
||||
|
||||
- type: entity
|
||||
id: Giblet
|
||||
components:
|
||||
- type: Organ
|
||||
- type: GibbableOrgan
|
||||
- type: Physics
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task GibletCountTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapData = await pair.CreateTestMap();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var body = entityManager.SpawnEntity("GibbingBody", mapData.GridCoords);
|
||||
var gibbing = entityManager.System<GibbingSystem>();
|
||||
var giblets = gibbing.Gib(body);
|
||||
|
||||
Assert.That(giblets.Count, Is.EqualTo(3));
|
||||
|
||||
foreach (var giblet in giblets)
|
||||
{
|
||||
Assert.That(entityManager.HasComponent<GibbableOrganComponent>(giblet), Is.True);
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
87
Content.IntegrationTests/Tests/Body/HandOrganTest.cs
Normal file
87
Content.IntegrationTests/Tests/Body/HandOrganTest.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(HandOrganSystem))]
|
||||
public sealed class HandOrganTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: TheBody
|
||||
components:
|
||||
- type: Body
|
||||
- type: Hands
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: LeftHand
|
||||
- id: RightHand
|
||||
|
||||
- type: entity
|
||||
id: LeftHand
|
||||
components:
|
||||
- type: Organ
|
||||
- type: HandOrgan
|
||||
handID: left
|
||||
data:
|
||||
location: Left
|
||||
|
||||
- type: entity
|
||||
id: RightHand
|
||||
components:
|
||||
- type: Organ
|
||||
- type: HandOrgan
|
||||
handID: right
|
||||
data:
|
||||
location: Right
|
||||
";
|
||||
[Test]
|
||||
public async Task HandInsertionAndRemovalTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapData = await pair.CreateTestMap();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var container = entityManager.System<SharedContainerSystem>();
|
||||
var body = entityManager.SpawnEntity("TheBody", mapData.GridCoords);
|
||||
var hands = entityManager.GetComponent<HandsComponent>(body);
|
||||
|
||||
Assert.That(hands.Count, Is.EqualTo(2));
|
||||
|
||||
var handsContainer = container.GetContainer(body, BodyComponent.ContainerID);
|
||||
|
||||
var expectedCount = 2;
|
||||
var contained = handsContainer.ContainedEntities.ToList();
|
||||
foreach (var hand in contained)
|
||||
{
|
||||
expectedCount--;
|
||||
container.Remove(hand, handsContainer);
|
||||
Assert.That(hands.Count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
|
||||
var protos = new List<string>() { "LeftHand", "RightHand" };
|
||||
foreach (var proto in protos)
|
||||
{
|
||||
expectedCount++;
|
||||
entityManager.SpawnInContainerOrDrop(proto, body, BodyComponent.ContainerID);
|
||||
Assert.That(hands.Count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Rotation;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(BodyPartComponent))]
|
||||
[TestOf(typeof(BodyComponent))]
|
||||
public sealed class LegTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: HumanBodyAndAppearanceDummy
|
||||
id: HumanBodyAndAppearanceDummy
|
||||
components:
|
||||
- type: Appearance
|
||||
- type: Body
|
||||
prototype: Human
|
||||
- type: StandingState
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task RemoveLegsFallTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
EntityUid human = default!;
|
||||
AppearanceComponent appearance = null;
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var appearanceSystem = entityManager.System<SharedAppearanceSystem>();
|
||||
var xformSystem = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var map = await pair.CreateTestMap();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
BodyComponent body = null;
|
||||
|
||||
human = entityManager.SpawnEntity("HumanBodyAndAppearanceDummy",
|
||||
new MapCoordinates(Vector2.Zero, map.MapId));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(entityManager.TryGetComponent(human, out body));
|
||||
Assert.That(entityManager.TryGetComponent(human, out appearance));
|
||||
});
|
||||
|
||||
Assert.That(!appearanceSystem.TryGetData(human, RotationVisuals.RotationState, out RotationState _, appearance));
|
||||
|
||||
var bodySystem = entityManager.System<BodySystem>();
|
||||
var legs = bodySystem.GetBodyChildrenOfType(human, BodyPartType.Leg, body);
|
||||
|
||||
foreach (var leg in legs)
|
||||
{
|
||||
xformSystem.DetachEntity(leg.Id, entityManager.GetComponent<TransformComponent>(leg.Id));
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
// Interdependent assertions.
|
||||
Assert.That(appearanceSystem.TryGetData(human, RotationVisuals.RotationState, out RotationState state, appearance));
|
||||
Assert.That(state, Is.EqualTo(RotationState.Horizontal));
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(LungSystem))]
|
||||
public sealed class LungTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: HumanLungDummy
|
||||
id: HumanLungDummy
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
- type: Body
|
||||
prototype: Human
|
||||
- type: MobState
|
||||
allowedStates:
|
||||
- Alive
|
||||
- type: Damageable
|
||||
- type: ThermalRegulator
|
||||
metabolismHeat: 5000
|
||||
radiatedHeat: 400
|
||||
implicitHeatRegulation: 5000
|
||||
sweatHeatRegulation: 5000
|
||||
shiveringHeatRegulation: 5000
|
||||
normalBodyTemperature: 310.15
|
||||
thermalRegulationTemperatureThreshold: 25
|
||||
- type: Respirator
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: 1.5
|
||||
damageRecovery:
|
||||
types:
|
||||
Asphyxiation: -1.5
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task AirConsistencyTest()
|
||||
{
|
||||
// --- Setup
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||
var mapSys = entityManager.System<SharedMapSystem>();
|
||||
|
||||
EntityUid? grid = null;
|
||||
BodyComponent body = default;
|
||||
RespiratorComponent resp = default;
|
||||
EntityUid human = default;
|
||||
GridAtmosphereComponent relevantAtmos = default;
|
||||
var startingMoles = 0.0f;
|
||||
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
|
||||
float GetMapMoles()
|
||||
{
|
||||
var totalMapMoles = 0.0f;
|
||||
foreach (var tile in relevantAtmos.Tiles.Values)
|
||||
{
|
||||
totalMapMoles += tile.Air?.TotalMoles ?? 0.0f;
|
||||
}
|
||||
|
||||
return totalMapMoles;
|
||||
}
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var center = new Vector2(0.5f, 0.5f);
|
||||
var coordinates = new EntityCoordinates(grid.Value, center);
|
||||
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
||||
relevantAtmos = entityManager.GetComponent<GridAtmosphereComponent>(grid.Value);
|
||||
startingMoles = 100f; // Hardcoded because GetMapMoles returns 900 here for some reason.
|
||||
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(entityManager.TryGetComponent(human, out body), Is.True);
|
||||
Assert.That(entityManager.TryGetComponent(human, out resp), Is.True);
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
|
||||
// --- End setup
|
||||
|
||||
var inhaleCycles = 100;
|
||||
for (var i = 0; i < inhaleCycles; i++)
|
||||
{
|
||||
// Breathe in
|
||||
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Exhaling);
|
||||
Assert.That(
|
||||
GetMapMoles(), Is.LessThan(startingMoles),
|
||||
"Did not inhale in any gas"
|
||||
);
|
||||
|
||||
// Breathe out
|
||||
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Inhaling);
|
||||
Assert.That(
|
||||
GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002),
|
||||
"Did not exhale as much gas as was inhaled"
|
||||
);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NoSuffocationTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||
var mapSys = entityManager.System<SharedMapSystem>();
|
||||
|
||||
EntityUid? grid = null;
|
||||
RespiratorComponent respirator = null;
|
||||
EntityUid human = default;
|
||||
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var center = new Vector2(0.5f, 0.5f);
|
||||
|
||||
var coordinates = new EntityCoordinates(grid.Value, center);
|
||||
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
||||
|
||||
var mixture = entityManager.System<AtmosphereSystem>().GetContainingMixture(human);
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mixture.TotalMoles, Is.GreaterThan(0));
|
||||
Assert.That(entityManager.HasComponent<BodyComponent>(human), Is.True);
|
||||
Assert.That(entityManager.TryGetComponent(human, out respirator), Is.True);
|
||||
Assert.That(respirator.SuffocationCycles, Is.LessThanOrEqualTo(respirator.SuffocationCycleThreshold));
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
|
||||
var increment = 10;
|
||||
|
||||
// 20 seconds
|
||||
var total = 20 * cfg.GetCVar(CVars.NetTickrate);
|
||||
|
||||
for (var tick = 0; tick < total; tick += increment)
|
||||
{
|
||||
await server.WaitRunTicks(increment);
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(respirator.SuffocationCycles, Is.LessThanOrEqualTo(respirator.SuffocationCycleThreshold),
|
||||
$"Entity {entityManager.GetComponent<MetaDataComponent>(human).EntityName} is suffocating on tick {tick}");
|
||||
});
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class SaveLoadReparentTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: HumanBodyDummy
|
||||
id: HumanBodyDummy
|
||||
components:
|
||||
- type: Body
|
||||
prototype: Human
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entities = server.ResolveDependency<IEntityManager>();
|
||||
var maps = server.ResolveDependency<IMapManager>();
|
||||
var mapLoader = entities.System<MapLoaderSystem>();
|
||||
var bodySystem = entities.System<SharedBodySystem>();
|
||||
var containerSystem = entities.System<SharedContainerSystem>();
|
||||
var mapSys = entities.System<SharedMapSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
maps.CreateGrid(mapId);
|
||||
var human = entities.SpawnEntity("HumanBodyDummy", new MapCoordinates(0, 0, mapId));
|
||||
|
||||
Assert.That(entities.HasComponent<BodyComponent>(human), Is.True);
|
||||
|
||||
var parts = bodySystem.GetBodyChildren(human).Skip(1).ToArray();
|
||||
var organs = bodySystem.GetBodyOrgans(human).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parts, Is.Not.Empty);
|
||||
Assert.That(organs, Is.Not.Empty);
|
||||
});
|
||||
|
||||
foreach (var (id, component) in parts)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Body, Is.EqualTo(human));
|
||||
Assert.That(component.Body, Is.Not.Null);
|
||||
var parent = bodySystem.GetParentPartOrNull(id);
|
||||
Assert.That(parent, Is.Not.EqualTo(default(EntityUid)));
|
||||
if (!bodySystem.IsPartRoot(component.Body.Value, id, null, component))
|
||||
{
|
||||
Assert.That(parent, Is.Not.Null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(parent, Is.Null);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var (slotId, slot) in component.Children)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(slot.Id, Is.EqualTo(slotId));
|
||||
var container =
|
||||
containerSystem.GetContainer(id, SharedBodySystem.GetPartSlotContainerId(slotId));
|
||||
Assert.That(container.ContainedEntities, Is.Not.Empty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (id, component) in organs)
|
||||
{
|
||||
var parent = bodySystem.GetParentPartOrNull(id);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Body, Is.EqualTo(human));
|
||||
Assert.That(parent, Is.Not.Null);
|
||||
Assert.That(parent.Value, Is.Not.EqualTo(default(EntityUid)));
|
||||
});
|
||||
}
|
||||
|
||||
// Converts an entity query enumerator to an enumerable.
|
||||
static IEnumerable<(EntityUid Uid, TComp Comp)> EnumerateQueryEnumerator<TComp>(EntityQueryEnumerator<TComp> query)
|
||||
where TComp : Component
|
||||
{
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
yield return (uid, comp);
|
||||
}
|
||||
|
||||
Assert.That(
|
||||
EnumerateQueryEnumerator(
|
||||
entities.EntityQueryEnumerator<BodyComponent>()
|
||||
).Where((e) =>
|
||||
entities.GetComponent<MetaDataComponent>(e.Uid).EntityPrototype!.Name == "HumanBodyDummy"
|
||||
),
|
||||
Is.Not.Empty
|
||||
);
|
||||
|
||||
var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml");
|
||||
|
||||
Assert.That(mapLoader.TrySaveMap(mapId, mapPath));
|
||||
mapSys.DeleteMap(mapId);
|
||||
|
||||
Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True);
|
||||
|
||||
var query = EnumerateQueryEnumerator(
|
||||
entities.EntityQueryEnumerator<BodyComponent>()
|
||||
).Where((e) =>
|
||||
entities.GetComponent<MetaDataComponent>(e.Uid).EntityPrototype!.Name == "HumanBodyDummy"
|
||||
).ToArray();
|
||||
|
||||
Assert.That(query, Is.Not.Empty);
|
||||
foreach (var (uid, body) in query)
|
||||
{
|
||||
human = uid;
|
||||
parts = bodySystem.GetBodyChildren(human).Skip(1).ToArray();
|
||||
organs = bodySystem.GetBodyOrgans(human).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parts, Is.Not.Empty);
|
||||
Assert.That(organs, Is.Not.Empty);
|
||||
});
|
||||
|
||||
foreach (var (id, component) in parts)
|
||||
{
|
||||
var parent = bodySystem.GetParentPartOrNull(id);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Body, Is.EqualTo(human));
|
||||
Assert.That(parent, Is.Not.Null);
|
||||
Assert.That(parent.Value, Is.Not.EqualTo(default(EntityUid)));
|
||||
});
|
||||
|
||||
foreach (var (slotId, slot) in component.Children)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(slot.Id, Is.EqualTo(slotId));
|
||||
var container =
|
||||
containerSystem.GetContainer(id, SharedBodySystem.GetPartSlotContainerId(slotId));
|
||||
Assert.That(container.ContainedEntities, Is.Not.Empty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (id, component) in organs)
|
||||
{
|
||||
var parent = bodySystem.GetParentPartOrNull(id);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(component.Body, Is.EqualTo(human));
|
||||
Assert.That(parent, Is.Not.Null);
|
||||
Assert.That(parent.Value, Is.Not.EqualTo(default(EntityUid)));
|
||||
});
|
||||
}
|
||||
|
||||
entities.DeleteEntity(map);
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
@@ -247,7 +244,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
EntityUid human = default;
|
||||
BuckleComponent buckle = null;
|
||||
HandsComponent hands = null;
|
||||
BodyComponent body = null;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
@@ -267,7 +263,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
Assert.That(entityManager.TryGetComponent(human, out buckle));
|
||||
Assert.That(entityManager.HasComponent<StrapComponent>(chair));
|
||||
Assert.That(entityManager.TryGetComponent(human, out hands));
|
||||
Assert.That(entityManager.TryGetComponent(human, out body));
|
||||
});
|
||||
|
||||
// Buckle
|
||||
@@ -289,29 +284,6 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
|
||||
await server.WaitRunTicks(10);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.That(buckle.Buckled);
|
||||
|
||||
// With items in all hands
|
||||
foreach (var hand in hands.Hands.Keys)
|
||||
{
|
||||
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null);
|
||||
}
|
||||
|
||||
var bodySystem = entityManager.System<BodySystem>();
|
||||
var legs = bodySystem.GetBodyChildrenOfType(human, BodyPartType.Leg, body);
|
||||
|
||||
// Break our guy's kneecaps
|
||||
foreach (var leg in legs)
|
||||
{
|
||||
entityManager.DeleteEntity(leg.Id);
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(10);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
|
||||
189
Content.IntegrationTests/Tests/Respirator/LungTest.cs
Normal file
189
Content.IntegrationTests/Tests/Respirator/LungTest.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Respirator;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(LungSystem))]
|
||||
public sealed class LungTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: HumanLungDummy
|
||||
id: HumanLungDummy
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganHumanLungs
|
||||
- type: MobState
|
||||
allowedStates:
|
||||
- Alive
|
||||
- type: Damageable
|
||||
- type: ThermalRegulator
|
||||
metabolismHeat: 5000
|
||||
radiatedHeat: 400
|
||||
implicitHeatRegulation: 5000
|
||||
sweatHeatRegulation: 5000
|
||||
shiveringHeatRegulation: 5000
|
||||
normalBodyTemperature: 310.15
|
||||
thermalRegulationTemperatureThreshold: 25
|
||||
- type: Respirator
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: 1.5
|
||||
damageRecovery:
|
||||
types:
|
||||
Asphyxiation: -1.5
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task AirConsistencyTest()
|
||||
{
|
||||
// --- Setup
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||
var mapSys = entityManager.System<SharedMapSystem>();
|
||||
|
||||
EntityUid? grid = null;
|
||||
RespiratorComponent resp = default;
|
||||
EntityUid human = default;
|
||||
GridAtmosphereComponent relevantAtmos = default;
|
||||
var startingMoles = 0.0f;
|
||||
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
|
||||
float GetMapMoles()
|
||||
{
|
||||
var totalMapMoles = 0.0f;
|
||||
foreach (var tile in relevantAtmos.Tiles.Values)
|
||||
{
|
||||
totalMapMoles += tile.Air?.TotalMoles ?? 0.0f;
|
||||
}
|
||||
|
||||
return totalMapMoles;
|
||||
}
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var center = new Vector2(0.5f, 0.5f);
|
||||
var coordinates = new EntityCoordinates(grid.Value, center);
|
||||
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
||||
relevantAtmos = entityManager.GetComponent<GridAtmosphereComponent>(grid.Value);
|
||||
startingMoles = 100f; // Hardcoded because GetMapMoles returns 900 here for some reason.
|
||||
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(entityManager.TryGetComponent(human, out resp), Is.True);
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
|
||||
// --- End setup
|
||||
|
||||
var inhaleCycles = 100;
|
||||
for (var i = 0; i < inhaleCycles; i++)
|
||||
{
|
||||
// Breathe in
|
||||
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Exhaling);
|
||||
Assert.That(
|
||||
GetMapMoles(), Is.LessThan(startingMoles),
|
||||
"Did not inhale in any gas"
|
||||
);
|
||||
|
||||
// Breathe out
|
||||
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Inhaling);
|
||||
Assert.That(
|
||||
GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002),
|
||||
"Did not exhale as much gas as was inhaled"
|
||||
);
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NoSuffocationTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||
var mapSys = entityManager.System<SharedMapSystem>();
|
||||
|
||||
EntityUid? grid = null;
|
||||
RespiratorComponent respirator = null;
|
||||
EntityUid human = default;
|
||||
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var center = new Vector2(0.5f, 0.5f);
|
||||
|
||||
var coordinates = new EntityCoordinates(grid.Value, center);
|
||||
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
|
||||
|
||||
var mixture = entityManager.System<AtmosphereSystem>().GetContainingMixture(human);
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mixture.TotalMoles, Is.GreaterThan(0));
|
||||
Assert.That(entityManager.TryGetComponent(human, out respirator), Is.True);
|
||||
Assert.That(respirator.SuffocationCycles, Is.LessThanOrEqualTo(respirator.SuffocationCycleThreshold));
|
||||
#pragma warning restore NUnit2045
|
||||
});
|
||||
|
||||
var increment = 10;
|
||||
|
||||
// 20 seconds
|
||||
var total = 20 * cfg.GetCVar(CVars.NetTickrate);
|
||||
|
||||
for (var tick = 0; tick < total; tick += increment)
|
||||
{
|
||||
await server.WaitRunTicks(increment);
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(respirator.SuffocationCycles, Is.LessThanOrEqualTo(respirator.SuffocationCycleThreshold),
|
||||
$"Entity {entityManager.GetComponent<MetaDataComponent>(human).EntityName} is suffocating on tick {tick}");
|
||||
});
|
||||
}
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class AddBodyPartCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
|
||||
public override string Command => "addbodypart";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 4)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var childNetId) || !EntityManager.TryGetEntity(childNetId, out var childId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-invalid-entity-uid", ("uid", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[1], out var parentNetId) || !EntityManager.TryGetEntity(parentNetId, out var parentId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-invalid-entity-uid", ("uid", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<BodyPartType>(args[3], out var partType) &&
|
||||
_bodySystem.TryCreatePartSlotAndAttach(parentId.Value, args[2], childId.Value, partType))
|
||||
{
|
||||
shell.WriteLine($@"Added {childId} to {parentId}.");
|
||||
}
|
||||
else
|
||||
shell.WriteError($@"Could not add {childId} to {parentId}.");
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class AddMechanismCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "addmechanism";
|
||||
public string Description => "Adds a given entity to a containing body.";
|
||||
public string Help => "Usage: addmechanism <entity uid> <bodypart uid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var organIdNet) || !_entManager.TryGetEntity(organIdNet, out var organId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[1], out var partIdNet) || !_entManager.TryGetEntity(partIdNet, out var partId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
var bodySystem = _entManager.System<BodySystem>();
|
||||
|
||||
if (bodySystem.AddOrganToFirstValidSlot(partId.Value, organId.Value))
|
||||
{
|
||||
shell.WriteLine($@"Added {organId} to {partId}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError($@"Could not add {organId} to {partId}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
@@ -20,8 +21,8 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Administration.Systems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clumsy;
|
||||
using Content.Shared.Cluwne;
|
||||
@@ -307,7 +308,6 @@ public sealed partial class AdminVerbSystem
|
||||
args.Verbs.Add(bloodRemoval);
|
||||
}
|
||||
|
||||
// bobby...
|
||||
if (TryComp<BodyComponent>(args.Target, out var body))
|
||||
{
|
||||
var vomitOrgansName = Loc.GetString("admin-smite-vomit-organs-name").ToLowerInvariant();
|
||||
@@ -319,14 +319,14 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
_vomitSystem.Vomit(args.Target, -1000, -1000); // You feel hollow!
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<TransformComponent>((args.Target, body));
|
||||
_bodySystem.TryGetOrgansWithComponent<TransformComponent>((args.Target, body), out var organs);
|
||||
var baseXform = Transform(args.Target);
|
||||
foreach (var organ in organs)
|
||||
{
|
||||
if (HasComp<BrainComponent>(organ.Owner) || HasComp<EyeComponent>(organ.Owner))
|
||||
continue;
|
||||
|
||||
_transformSystem.PlaceNextTo((organ.Owner, organ.Comp1), (args.Target, baseXform));
|
||||
_transformSystem.PlaceNextTo((organ.Owner, organ.Comp), (args.Target, baseXform));
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-vomit-organs-self"), args.Target,
|
||||
@@ -348,9 +348,11 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
var baseXform = Transform(args.Target);
|
||||
foreach (var part in _bodySystem.GetBodyChildrenOfType(args.Target, BodyPartType.Hand))
|
||||
var parts = new HashSet<ProtoId<OrganCategoryPrototype>>() { "HandRight", "HandLeft" };
|
||||
_bodySystem.TryGetOrgansWithComponent<OrganComponent>((args.Target, body), out var organs);
|
||||
foreach (var organ in organs.Where(it => it.Comp.Category is { } category && parts.Contains(category)))
|
||||
{
|
||||
_transformSystem.AttachToGridOrMap(part.Id);
|
||||
_transformSystem.AttachToGridOrMap(organ);
|
||||
}
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target,
|
||||
args.Target, PopupType.LargeCaution);
|
||||
@@ -371,9 +373,11 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
var baseXform = Transform(args.Target);
|
||||
foreach (var part in _bodySystem.GetBodyChildrenOfType(args.Target, BodyPartType.Hand, body))
|
||||
var parts = new HashSet<ProtoId<OrganCategoryPrototype>>() { "HandRight", "HandLeft" };
|
||||
_bodySystem.TryGetOrgansWithComponent<OrganComponent>((args.Target, body), out var organs);
|
||||
foreach (var organ in organs.Where(it => it.Comp.Category is { } category && parts.Contains(category)))
|
||||
{
|
||||
_transformSystem.AttachToGridOrMap(part.Id);
|
||||
_transformSystem.AttachToGridOrMap(organ);
|
||||
break;
|
||||
}
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target,
|
||||
@@ -394,7 +398,8 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"),
|
||||
Act = () =>
|
||||
{
|
||||
foreach (var entity in _bodySystem.GetBodyOrganEntityComps<StomachComponent>((args.Target, body)))
|
||||
_bodySystem.TryGetOrgansWithComponent<StomachComponent>((args.Target, body), out var organs);
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
QueueDel(entity.Owner);
|
||||
}
|
||||
@@ -415,7 +420,8 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"),
|
||||
Act = () =>
|
||||
{
|
||||
foreach (var entity in _bodySystem.GetBodyOrganEntityComps<LungComponent>((args.Target, body)))
|
||||
_bodySystem.TryGetOrgansWithComponent<LungComponent>((args.Target, body), out var organs);
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
QueueDel(entity.Owner);
|
||||
}
|
||||
|
||||
@@ -132,9 +132,6 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
private void OnAnomalySupercritical(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(ent, out var body))
|
||||
return;
|
||||
|
||||
_gibbing.Gib(ent.Owner);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -17,19 +13,17 @@ namespace Content.Server.Body.Commands
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private static readonly EntProtoId DefaultHandPrototype = "LeftHandHuman";
|
||||
private static int _handIdAccumulator;
|
||||
|
||||
public string Command => "addhand";
|
||||
public string Description => "Adds a hand to your entity.";
|
||||
public string Help => $"Usage: {Command} <entityUid> <handPrototypeId> / {Command} <entityUid> / {Command} <handPrototypeId> / {Command}";
|
||||
public string Help => $"Usage: {Command} <entityUid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
|
||||
EntityUid entity;
|
||||
EntityUid hand;
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
@@ -47,7 +41,6 @@ namespace Content.Server.Body.Commands
|
||||
}
|
||||
|
||||
entity = player.AttachedEntity.Value;
|
||||
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
@@ -60,7 +53,6 @@ namespace Content.Server.Body.Commands
|
||||
}
|
||||
|
||||
entity = uid.Value;
|
||||
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -77,35 +69,8 @@ namespace Content.Server.Body.Commands
|
||||
}
|
||||
|
||||
entity = player.AttachedEntity.Value;
|
||||
hand = _entManager.SpawnEntity(args[0], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (!NetEntity.TryParse(args[0], out var netEnt) || !_entManager.TryGetEntity(netEnt, out var uid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity uid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.EntityExists(uid))
|
||||
{
|
||||
shell.WriteLine($"No entity exists with uid {uid}.");
|
||||
return;
|
||||
}
|
||||
|
||||
entity = uid.Value;
|
||||
|
||||
if (!_protoManager.HasIndex<EntityPrototype>(args[1]))
|
||||
{
|
||||
shell.WriteLine($"No hand entity exists with id {args[1]}.");
|
||||
return;
|
||||
}
|
||||
|
||||
hand = _entManager.SpawnEntity(args[1], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -113,41 +78,7 @@ namespace Content.Server.Body.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(entity, out BodyComponent? body) || body.RootContainer.ContainedEntity == null)
|
||||
{
|
||||
var location = _entManager.GetComponentOrNull<BodyPartComponent>(hand)?.Symmetry switch
|
||||
{
|
||||
BodyPartSymmetry.None => HandLocation.Middle,
|
||||
BodyPartSymmetry.Left => HandLocation.Left,
|
||||
BodyPartSymmetry.Right => HandLocation.Right,
|
||||
_ => HandLocation.Right
|
||||
};
|
||||
_entManager.DeleteEntity(hand);
|
||||
|
||||
// You have no body and you must scream.
|
||||
_entManager.System<HandsSystem>().AddHand(entity, $"{hand}-cmd-{_handIdAccumulator++}", location);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(hand, out BodyPartComponent? part))
|
||||
{
|
||||
shell.WriteLine($"Hand entity {hand} does not have a {nameof(BodyPartComponent)} component.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bodySystem = _entManager.System<BodySystem>();
|
||||
|
||||
var attachAt = bodySystem.GetBodyChildrenOfType(entity, BodyPartType.Arm, body).FirstOrDefault();
|
||||
if (attachAt == default)
|
||||
attachAt = bodySystem.GetBodyChildren(entity, body).First();
|
||||
|
||||
var slotId = part.GetHashCode().ToString();
|
||||
|
||||
if (!bodySystem.TryCreatePartSlotAndAttach(attachAt.Id, slotId, hand, BodyPartType.Hand, attachAt.Component, part))
|
||||
{
|
||||
shell.WriteError($"Couldn't create a slot with id {slotId} on entity {_entManager.ToPrettyString(entity)}");
|
||||
return;
|
||||
}
|
||||
_entManager.System<HandsSystem>().AddHand(entity, $"cmd-{_handIdAccumulator++}", HandLocation.Middle);
|
||||
|
||||
shell.WriteLine($"Added hand to entity {_entManager.GetComponent<MetaDataComponent>(entity).EntityName}");
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Body.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class AttachBodyPartCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "attachbodypart";
|
||||
public string Description => "Attaches a body part to you or someone else.";
|
||||
public string Help => $"{Command} <partEntityUid> / {Command} <entityUid> <partEntityUid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
|
||||
EntityUid bodyId;
|
||||
EntityUid? partUid;
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine($"You need to specify an entity to attach the part to if you aren't a player.\n{Help}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.WriteLine($"You need to specify an entity to attach the part to if you aren't attached to an entity.\n{Help}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var partNet) || !_entManager.TryGetEntity(partNet, out partUid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity uid.");
|
||||
return;
|
||||
}
|
||||
|
||||
bodyId = player.AttachedEntity.Value;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
if (!NetEntity.TryParse(args[0], out var entityNet) || !_entManager.TryGetEntity(entityNet, out var entityUid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity uid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[1], out partNet) || !_entManager.TryGetEntity(partNet, out partUid))
|
||||
{
|
||||
shell.WriteLine($"{args[1]} is not a valid entity uid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.EntityExists(entityUid))
|
||||
{
|
||||
shell.WriteLine($"{entityUid} is not a valid entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
bodyId = entityUid.Value;
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(bodyId, out BodyComponent? body))
|
||||
{
|
||||
shell.WriteLine($"Entity {_entManager.GetComponent<MetaDataComponent>(bodyId).EntityName} with uid {bodyId} does not have a {nameof(BodyComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.EntityExists(partUid))
|
||||
{
|
||||
shell.WriteLine($"{partUid} is not a valid entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(partUid, out BodyPartComponent? part))
|
||||
{
|
||||
shell.WriteLine($"Entity {_entManager.GetComponent<MetaDataComponent>(partUid.Value).EntityName} with uid {args[0]} does not have a {nameof(BodyPartComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bodySystem = _entManager.System<BodySystem>();
|
||||
if (bodySystem.BodyHasChild(bodyId, partUid.Value, body, part))
|
||||
{
|
||||
shell.WriteLine($"Body part {_entManager.GetComponent<MetaDataComponent>(partUid.Value).EntityName} with uid {partUid} is already attached to entity {_entManager.GetComponent<MetaDataComponent>(bodyId).EntityName} with uid {bodyId}");
|
||||
return;
|
||||
}
|
||||
|
||||
var slotId = $"AttachBodyPartVerb-{partUid}";
|
||||
|
||||
if (body.RootContainer.ContainedEntity is null && !bodySystem.AttachPartToRoot(bodyId, partUid.Value, body, part))
|
||||
{
|
||||
shell.WriteError("Body container does not have a root entity to attach to the body part!");
|
||||
return;
|
||||
}
|
||||
|
||||
var (rootPartId, rootPart) = bodySystem.GetRootPartOrNull(bodyId, body)!.Value;
|
||||
if (!bodySystem.TryCreatePartSlotAndAttach(rootPartId,
|
||||
slotId,
|
||||
partUid.Value,
|
||||
part.PartType,
|
||||
rootPart,
|
||||
part))
|
||||
{
|
||||
shell.WriteError($"Could not create slot {slotId} on entity {_entManager.ToPrettyString(bodyId)}");
|
||||
return;
|
||||
}
|
||||
shell.WriteLine($"Attached part {_entManager.ToPrettyString(partUid.Value)} to {_entManager.ToPrettyString(bodyId)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Body.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
internal sealed class DestroyMechanismCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
|
||||
public override string Command => "destroymechanism";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"shell-only-players-can-run-this-command"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not {} attached)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(attached, out BodyComponent? body))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"shell-must-have-body"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mechanismName = string.Join(" ", args).ToLowerInvariant();
|
||||
|
||||
foreach (var organ in _bodySystem.GetBodyOrgans(attached, body))
|
||||
{
|
||||
if (_compFactory.GetComponentName(organ.Component.GetType()).ToLowerInvariant() == mechanismName)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(organ.Id);
|
||||
shell.WriteLine(Loc.GetString($"cmd-destroymechanism-success", ("name", mechanismName)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString($"cmd-destroymechanism-no-mechanism-found", ("name", mechanismName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Body.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class RemoveHandCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public string Command => "removehand";
|
||||
public string Description => "Removes a hand from your entity.";
|
||||
public string Help => $"Usage: {Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("Only a player can run this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.WriteLine("You have no entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(player.AttachedEntity, out BodyComponent? body))
|
||||
{
|
||||
var text = $"You have no body{(_random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||
|
||||
shell.WriteLine(text);
|
||||
return;
|
||||
}
|
||||
|
||||
var bodySystem = _entManager.System<BodySystem>();
|
||||
var hand = bodySystem.GetBodyChildrenOfType(player.AttachedEntity.Value, BodyPartType.Hand, body).FirstOrDefault();
|
||||
|
||||
if (hand == default)
|
||||
{
|
||||
shell.WriteLine("You have no hands.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_entManager.System<SharedTransformSystem>().AttachToGridOrMap(hand.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
public sealed class BodySystem : SharedBodySystem
|
||||
{
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, MoveInputEvent>(OnRelayMoveInput);
|
||||
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
}
|
||||
|
||||
private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args)
|
||||
{
|
||||
// If they haven't actually moved then ignore it.
|
||||
if ((args.Entity.Comp.HeldMoveButtons &
|
||||
(MoveButtons.Down | MoveButtons.Left | MoveButtons.Up | MoveButtons.Right)) == 0x0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mobState.IsDead(ent) && _mindSystem.TryGetMind(ent, out var mindId, out var mind))
|
||||
{
|
||||
mind.TimeOfDeath ??= _gameTiming.RealTime;
|
||||
_ghostSystem.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplyMetabolicMultiplier(
|
||||
Entity<BodyComponent> ent,
|
||||
ref ApplyMetabolicMultiplierEvent args)
|
||||
{
|
||||
foreach (var organ in GetBodyOrgans(ent, ent))
|
||||
{
|
||||
RaiseLocalEvent(organ.Id, ref args);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AddPart(
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
{
|
||||
// TODO: Predict this probably.
|
||||
base.AddPart(bodyEnt, partEnt, slotId);
|
||||
|
||||
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||
if (layer != null)
|
||||
{
|
||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||
_humanoidSystem.SetLayersVisibility(bodyEnt.Owner, layers, visible: true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemovePart(
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
{
|
||||
base.RemovePart(bodyEnt, partEnt, slotId);
|
||||
|
||||
if (!TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
|
||||
return;
|
||||
|
||||
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||
|
||||
if (layer is null)
|
||||
return;
|
||||
|
||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||
_humanoidSystem.SetLayersVisibility((bodyEnt, humanoid), layers, visible: false);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -47,7 +47,7 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
|
||||
|
||||
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
|
||||
SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
SubscribeLocalEvent<MetabolizerComponent, BodyRelayedEvent<ApplyMetabolicMultiplierEvent>>(OnApplyMetabolicMultiplier);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
|
||||
@@ -67,9 +67,9 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
|
||||
private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref BodyRelayedEvent<ApplyMetabolicMultiplierEvent> args)
|
||||
{
|
||||
ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
|
||||
ent.Comp.UpdateIntervalMultiplier = args.Args.Multiplier;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
@@ -34,7 +35,7 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosSys = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
||||
[Dependency] private readonly LungSystem _lungSystem = default!;
|
||||
@@ -54,11 +55,17 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
|
||||
// BodyComp stuff
|
||||
SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
|
||||
SubscribeLocalEvent<BodyComponent, ExhaledGasEvent>(OnGasExhaled);
|
||||
SubscribeLocalEvent<BodyComponent, CanMetabolizeGasEvent>(CanBodyMetabolizeGas);
|
||||
SubscribeLocalEvent<BodyComponent, SuffocationEvent>(OnSuffocation);
|
||||
SubscribeLocalEvent<BodyComponent, StopSuffocatingEvent>(OnStopSuffocating);
|
||||
SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(_body.RelayEvent);
|
||||
SubscribeLocalEvent<BodyComponent, ExhaledGasEvent>(_body.RelayEvent);
|
||||
SubscribeLocalEvent<BodyComponent, CanMetabolizeGasEvent>(_body.RelayEvent);
|
||||
SubscribeLocalEvent<BodyComponent, SuffocationEvent>(_body.RelayEvent);
|
||||
SubscribeLocalEvent<BodyComponent, StopSuffocatingEvent>(_body.RelayEvent);
|
||||
|
||||
SubscribeLocalEvent<LungComponent, BodyRelayedEvent<InhaledGasEvent>>(OnGasInhaled);
|
||||
SubscribeLocalEvent<LungComponent, BodyRelayedEvent<ExhaledGasEvent>>(OnGasExhaled);
|
||||
SubscribeLocalEvent<LungComponent, BodyRelayedEvent<CanMetabolizeGasEvent>>(CanBodyMetabolizeGas);
|
||||
SubscribeLocalEvent<LungComponent, BodyRelayedEvent<SuffocationEvent>>(OnSuffocation);
|
||||
SubscribeLocalEvent<LungComponent, BodyRelayedEvent<StopSuffocatingEvent>>(OnStopSuffocating);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
|
||||
@@ -236,72 +243,22 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Tries to safely metabolize the current solutions in a body's lungs.
|
||||
/// </summary>
|
||||
private void CanBodyMetabolizeGas(Entity<BodyComponent> ent, ref CanMetabolizeGasEvent args)
|
||||
private void CanBodyMetabolizeGas(Entity<LungComponent> ent, ref BodyRelayedEvent<CanMetabolizeGasEvent> args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
if (organs.Count == 0)
|
||||
return;
|
||||
|
||||
var solution = _lungSystem.GasToReagent(args.Gas);
|
||||
var solution = _lungSystem.GasToReagent(args.Args.Gas);
|
||||
|
||||
var saturation = 0f;
|
||||
foreach (var organ in organs)
|
||||
saturation += GetSaturation(solution, ent.Owner, out var toxic);
|
||||
|
||||
args.Args = args.Args with
|
||||
{
|
||||
saturation += GetSaturation(solution, organ.Owner, out var toxic);
|
||||
if (!toxic)
|
||||
continue;
|
||||
|
||||
args.Handled = true;
|
||||
args.Toxic = true;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
args.Saturation = saturation;
|
||||
}
|
||||
|
||||
public bool TryInhaleGasToBody(Entity<BodyComponent?> entity, GasMixture gas)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return false;
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((entity, entity.Comp));
|
||||
if (organs.Count == 0)
|
||||
return false;
|
||||
|
||||
var lungRatio = 1.0f / organs.Count;
|
||||
var splitGas = organs.Count == 1 ? gas : gas.RemoveRatio(lungRatio);
|
||||
foreach (var (organUid, lung, _) in organs)
|
||||
{
|
||||
// Merge doesn't remove gas from the giver.
|
||||
_atmosSys.Merge(lung.Air, splitGas);
|
||||
_lungSystem.GasToReagent(organUid, lung);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveGasFromBody(Entity<BodyComponent> ent, GasMixture gas)
|
||||
{
|
||||
var outGas = new GasMixture(gas.Volume);
|
||||
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, ent.Comp));
|
||||
if (organs.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var (organUid, lung, _) in organs)
|
||||
{
|
||||
_atmosSys.Merge(outGas, lung.Air);
|
||||
lung.Air.Clear();
|
||||
|
||||
if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution))
|
||||
_solutionContainerSystem.RemoveAllSolution(lung.Solution.Value);
|
||||
}
|
||||
|
||||
_atmosSys.Merge(gas, outGas);
|
||||
Saturation = saturation,
|
||||
Toxic = toxic,
|
||||
Handled = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -387,24 +344,14 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
private void OnSuffocation(Entity<BodyComponent> ent, ref SuffocationEvent args)
|
||||
private void OnSuffocation(Entity<LungComponent> ent, ref BodyRelayedEvent<SuffocationEvent> args)
|
||||
{
|
||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
_alertsSystem.ShowAlert(ent.Owner, entity.Comp1.Alert);
|
||||
}
|
||||
_alertsSystem.ShowAlert(args.Body.Owner, ent.Comp.Alert);
|
||||
}
|
||||
|
||||
private void OnStopSuffocating(Entity<BodyComponent> ent, ref StopSuffocatingEvent args)
|
||||
private void OnStopSuffocating(Entity<LungComponent> ent, ref BodyRelayedEvent<StopSuffocatingEvent> args)
|
||||
{
|
||||
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
|
||||
foreach (var entity in organs)
|
||||
{
|
||||
_alertsSystem.ClearAlert(ent.Owner, entity.Comp1.Alert);
|
||||
}
|
||||
_alertsSystem.ClearAlert(args.Body.Owner, ent.Comp.Alert);
|
||||
}
|
||||
|
||||
public void UpdateSaturation(EntityUid uid, float amount, RespiratorComponent? respirator = null)
|
||||
@@ -422,24 +369,33 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
|
||||
}
|
||||
|
||||
private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
|
||||
private void OnGasInhaled(Entity<LungComponent> ent, ref BodyRelayedEvent<InhaledGasEvent> args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
_atmosSys.Merge(ent.Comp.Air, args.Args.Gas);
|
||||
_lungSystem.GasToReagent(ent, ent);
|
||||
|
||||
args.Succeeded = TryInhaleGasToBody((entity, entity.Comp), args.Gas);
|
||||
args.Args = args.Args with
|
||||
{
|
||||
Handled = true,
|
||||
Succeeded = true
|
||||
};
|
||||
}
|
||||
|
||||
private void OnGasExhaled(Entity<BodyComponent> entity, ref ExhaledGasEvent args)
|
||||
private void OnGasExhaled(Entity<LungComponent> ent, ref BodyRelayedEvent<ExhaledGasEvent> args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
_atmosSys.Merge(args.Args.Gas, ent.Comp.Air);
|
||||
ent.Comp.Air.Clear();
|
||||
|
||||
args.Handled = true;
|
||||
if (_solutionContainerSystem.ResolveSolution(ent.Owner, ent.Comp.SolutionName, ref ent.Comp.Solution))
|
||||
_solutionContainerSystem.RemoveAllSolution(ent.Comp.Solution.Value);
|
||||
|
||||
RemoveGasFromBody(entity, args.Gas);
|
||||
args.Args = args.Args with
|
||||
{
|
||||
Handled = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
[RegisterComponent]
|
||||
public sealed partial class MobPriceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much of a penalty per part there should be. This is a multiplier for a multiplier, the penalty for each body part is calculated from the total number of slots, and then multiplied by this.
|
||||
/// </summary>
|
||||
[DataField("missingBodyPartPenalty")]
|
||||
public double MissingBodyPartPenalty = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The base price this mob should fetch.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
@@ -16,7 +14,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
@@ -28,7 +25,6 @@ public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
@@ -96,18 +92,7 @@ public sealed class PricingSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var partPenalty = 0.0;
|
||||
if (TryComp<BodyComponent>(uid, out var body))
|
||||
{
|
||||
var partList = _bodySystem.GetBodyChildren(uid, body).ToList();
|
||||
var totalPartsPresent = partList.Sum(_ => 1);
|
||||
var totalParts = partList.Count;
|
||||
|
||||
var partRatio = totalPartsPresent / (double) totalParts;
|
||||
partPenalty = component.Price * (1 - partRatio) * component.MissingBodyPartPenalty;
|
||||
}
|
||||
|
||||
args.Price += (component.Price - partPenalty) * (_mobStateSystem.IsAlive(uid, state) ? 1.0 : component.DeathPenalty);
|
||||
args.Price += component.Price * (_mobStateSystem.IsAlive(uid, state) ? 1.0 : component.DeathPenalty);
|
||||
}
|
||||
|
||||
private double GetSolutionPrice(Entity<SolutionContainerManagerComponent> entity)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Disposal.Tube;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Item;
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Numerics;
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Explosion;
|
||||
@@ -49,9 +48,6 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem), typeof(SharedStaminaSystem)});
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, BodyPartAddedEvent>(HandleBodyPartAdded);
|
||||
SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved);
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, ComponentGetState>(GetComponentState);
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, BeforeExplodeEvent>(OnExploded);
|
||||
@@ -107,32 +103,6 @@ namespace Content.Server.Hands.Systems
|
||||
args.Handled = true; // no shove/stun.
|
||||
}
|
||||
|
||||
private void HandleBodyPartAdded(Entity<HandsComponent> ent, ref BodyPartAddedEvent args)
|
||||
{
|
||||
if (args.Part.Comp.PartType != BodyPartType.Hand)
|
||||
return;
|
||||
|
||||
// If this annoys you, which it should.
|
||||
// Ping Smugleaf.
|
||||
var location = args.Part.Comp.Symmetry switch
|
||||
{
|
||||
BodyPartSymmetry.None => HandLocation.Middle,
|
||||
BodyPartSymmetry.Left => HandLocation.Left,
|
||||
BodyPartSymmetry.Right => HandLocation.Right,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
|
||||
};
|
||||
|
||||
AddHand(ent.AsNullable(), args.Slot, location);
|
||||
}
|
||||
|
||||
private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
|
||||
{
|
||||
if (args.Part.Comp.PartType != BodyPartType.Hand)
|
||||
return;
|
||||
|
||||
RemoveHand(uid, args.Slot);
|
||||
}
|
||||
|
||||
#region interactions
|
||||
|
||||
private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Gibbing;
|
||||
@@ -112,7 +111,7 @@ public sealed class ImmovableRodSystem : EntitySystem
|
||||
}
|
||||
|
||||
// gib or damage em
|
||||
if (TryComp<BodyComponent>(ent, out var body))
|
||||
if (HasComp<BodyComponent>(ent))
|
||||
{
|
||||
component.MobCount++;
|
||||
_popup.PopupEntity(Loc.GetString("immovable-rod-penetrated-mob", ("rod", uid), ("mob", ent)), uid, PopupType.LargeCaution);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
@@ -8,8 +7,6 @@ using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
@@ -46,7 +43,6 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
public sealed class MicrowaveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
@@ -305,26 +301,9 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
_suicide.ApplyLethalDamage((args.Victim, damageableComponent), "Heat");
|
||||
|
||||
var victim = args.Victim;
|
||||
var headCount = 0;
|
||||
|
||||
if (TryComp<BodyComponent>(victim, out var body))
|
||||
{
|
||||
var headSlots = _bodySystem.GetBodyChildrenOfType(victim, BodyPartType.Head, body);
|
||||
|
||||
foreach (var part in headSlots)
|
||||
{
|
||||
_container.Insert(part.Id, ent.Comp.Storage);
|
||||
headCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var othersMessage = headCount > 1
|
||||
? Loc.GetString("microwave-component-suicide-multi-head-others-message", ("victim", victim))
|
||||
: Loc.GetString("microwave-component-suicide-others-message", ("victim", victim));
|
||||
|
||||
var selfMessage = headCount > 1
|
||||
? Loc.GetString("microwave-component-suicide-multi-head-message")
|
||||
: Loc.GetString("microwave-component-suicide-message");
|
||||
var othersMessage = Loc.GetString("microwave-component-suicide-others-message", ("victim", victim));
|
||||
var selfMessage = Loc.GetString("microwave-component-suicide-message");
|
||||
|
||||
_popupSystem.PopupEntity(othersMessage, victim, Filter.PvsExcept(victim), true);
|
||||
_popupSystem.PopupEntity(selfMessage, victim, victim);
|
||||
|
||||
@@ -10,7 +10,7 @@ using Content.Server.Cloning.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
|
||||
@@ -4,11 +4,9 @@ using System.Numerics;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Station.Events;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Species.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Zombies;
|
||||
using Content.Server.Zombies;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Species.Components;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Species.Systems;
|
||||
@@ -17,12 +17,12 @@ public sealed partial class NymphSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NymphComponent, OrganRemovedFromBodyEvent>(OnRemovedFromPart);
|
||||
SubscribeLocalEvent<NymphComponent, OrganGotRemovedEvent>(OnRemovedFromPart);
|
||||
}
|
||||
|
||||
private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, ref OrganRemovedFromBodyEvent args)
|
||||
private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, ref OrganGotRemovedEvent args)
|
||||
{
|
||||
if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.OldBody))
|
||||
if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.Target))
|
||||
return;
|
||||
|
||||
if (!_protoManager.TryIndex<EntityPrototype>(comp.EntityPrototype, out var entityProto))
|
||||
@@ -32,11 +32,11 @@ public sealed partial class NymphSystem : EntitySystem
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var nymph = SpawnAtPosition(entityProto.ID, coords);
|
||||
|
||||
if (HasComp<ZombieComponent>(args.OldBody)) // Zombify the new nymph if old one is a zombie
|
||||
if (HasComp<ZombieComponent>(args.Target)) // Zombify the new nymph if old one is a zombie
|
||||
_zombie.ZombifyEntity(nymph);
|
||||
|
||||
// Move the mind if there is one and it's supposed to be transferred
|
||||
if (comp.TransferMind == true && _mindSystem.TryGetMind(args.OldBody, out var mindId, out var mind))
|
||||
if (comp.TransferMind == true && _mindSystem.TryGetMind(args.Target, out var mindId, out var mind))
|
||||
_mindSystem.TransferTo(mindId, nymph, mind: mind);
|
||||
|
||||
// Delete the old organ
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
@@ -42,9 +40,6 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryComp<BodyComponent>(contained, out var body))
|
||||
Del(contained);
|
||||
|
||||
var gibs = _gibbing.Gib(contained);
|
||||
foreach (var gib in gibs)
|
||||
{
|
||||
|
||||
41
Content.Shared/Body/BodyComponent.cs
Normal file
41
Content.Shared/Body/BodyComponent.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(BodySystem))]
|
||||
public sealed partial class BodyComponent : Component
|
||||
{
|
||||
public const string ContainerID = "body_organs";
|
||||
|
||||
/// <summary>
|
||||
/// The actual container with entities with <see cref="OrganComponent" /> in it
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container? Organs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on organ entity, when it is inserted into a body
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganGotInsertedEvent(EntityUid Target);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on organ entity, when it is removed from a body
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganGotRemovedEvent(EntityUid Target);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on body entity, when an organ is inserted into it
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganInsertedIntoEvent(EntityUid Organ);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on body entity, when an organ is removed from it
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganRemovedFromEvent(EntityUid Organ);
|
||||
20
Content.Shared/Body/BodySystem.Compatibility.cs
Normal file
20
Content.Shared/Body/BodySystem.Compatibility.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class BodySystem
|
||||
{
|
||||
[Obsolete("Use an event-relay based approach instead")]
|
||||
public bool TryGetOrgansWithComponent<TComp>(Entity<BodyComponent?> ent, out List<Entity<TComp>> organs) where TComp : Component
|
||||
{
|
||||
organs = new();
|
||||
if (!_bodyQuery.Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
foreach (var organ in ent.Comp.Organs?.ContainedEntities ?? [])
|
||||
{
|
||||
if (TryComp<TComp>(organ, out var comp))
|
||||
organs.Add((organ, comp));
|
||||
}
|
||||
|
||||
return organs.Count != 0;
|
||||
}
|
||||
}
|
||||
50
Content.Shared/Body/BodySystem.Relay.cs
Normal file
50
Content.Shared/Body/BodySystem.Relay.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.Medical;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class BodySystem
|
||||
{
|
||||
private void InitializeRelay()
|
||||
{
|
||||
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(RefRelayBodyEvent);
|
||||
SubscribeLocalEvent<BodyComponent, TryVomitEvent>(RefRelayBodyEvent);
|
||||
SubscribeLocalEvent<BodyComponent, BeingGibbedEvent>(RefRelayBodyEvent);
|
||||
}
|
||||
|
||||
private void RefRelayBodyEvent<T>(EntityUid uid, BodyComponent component, ref T args) where T : struct
|
||||
{
|
||||
RelayEvent((uid, component), ref args);
|
||||
}
|
||||
|
||||
private void RelayBodyEvent<T>(EntityUid uid, BodyComponent component, T args) where T : class
|
||||
{
|
||||
RelayEvent((uid, component), args);
|
||||
}
|
||||
|
||||
public void RelayEvent<T>(Entity<BodyComponent> ent, ref T args) where T : struct
|
||||
{
|
||||
var ev = new BodyRelayedEvent<T>(ent, args);
|
||||
foreach (var organ in ent.Comp.Organs?.ContainedEntities ?? [])
|
||||
{
|
||||
RaiseLocalEvent(organ, ref ev);
|
||||
}
|
||||
args = ev.Args;
|
||||
}
|
||||
|
||||
public void RelayEvent<T>(Entity<BodyComponent> ent, T args) where T : class
|
||||
{
|
||||
var ev = new BodyRelayedEvent<T>(ent, args);
|
||||
foreach (var organ in ent.Comp.Organs?.ContainedEntities ?? [])
|
||||
{
|
||||
RaiseLocalEvent(organ, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event wrapper for relayed events.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BodyRelayedEvent<TEvent>(Entity<BodyComponent> Body, TEvent Args);
|
||||
89
Content.Shared/Body/BodySystem.cs
Normal file
89
Content.Shared/Body/BodySystem.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Content.Shared.DragDrop;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed partial class BodySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
private EntityQuery<BodyComponent> _bodyQuery;
|
||||
private EntityQuery<OrganComponent> _organQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
|
||||
SubscribeLocalEvent<BodyComponent, ComponentShutdown>(OnBodyShutdown);
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnCanDrag);
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, EntInsertedIntoContainerMessage>(OnBodyEntInserted);
|
||||
SubscribeLocalEvent<BodyComponent, EntRemovedFromContainerMessage>(OnBodyEntRemoved);
|
||||
|
||||
_bodyQuery = GetEntityQuery<BodyComponent>();
|
||||
_organQuery = GetEntityQuery<OrganComponent>();
|
||||
|
||||
InitializeRelay();
|
||||
}
|
||||
|
||||
private void OnBodyInit(Entity<BodyComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
ent.Comp.Organs =
|
||||
_container.EnsureContainer<Container>(ent, BodyComponent.ContainerID);
|
||||
}
|
||||
|
||||
private void OnBodyShutdown(Entity<BodyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (ent.Comp.Organs is { } organs)
|
||||
_container.ShutdownContainer(organs);
|
||||
}
|
||||
|
||||
private void OnBodyEntInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != BodyComponent.ContainerID)
|
||||
return;
|
||||
|
||||
if (!_organQuery.TryComp(args.Entity, out var organ))
|
||||
return;
|
||||
|
||||
var body = new OrganInsertedIntoEvent(args.Entity);
|
||||
RaiseLocalEvent(ent, ref body);
|
||||
|
||||
var ev = new OrganGotInsertedEvent(ent);
|
||||
RaiseLocalEvent(args.Entity, ref ev);
|
||||
|
||||
if (organ.Body != ent)
|
||||
{
|
||||
organ.Body = ent;
|
||||
Dirty(args.Entity, organ);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyEntRemoved(Entity<BodyComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != BodyComponent.ContainerID)
|
||||
return;
|
||||
|
||||
if (!_organQuery.TryComp(args.Entity, out var organ))
|
||||
return;
|
||||
|
||||
var body = new OrganRemovedFromEvent(args.Entity);
|
||||
RaiseLocalEvent(ent, ref body);
|
||||
|
||||
var ev = new OrganGotRemovedEvent(ent);
|
||||
RaiseLocalEvent(args.Entity, ref ev);
|
||||
|
||||
if (organ.Body == null)
|
||||
return;
|
||||
|
||||
organ.Body = null;
|
||||
Dirty(args.Entity, organ);
|
||||
}
|
||||
|
||||
private void OnCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed partial class BodyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Relevant template to spawn for this body.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<BodyPrototype>? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// Container that holds the root body part.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Typically is the torso.
|
||||
/// </remarks>
|
||||
[ViewVariables] public ContainerSlot RootContainer = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public string RootPartSlot => RootContainer.ID;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundSpecifier GibSound = new SoundCollectionSpecifier("gib");
|
||||
|
||||
/// <summary>
|
||||
/// The amount of legs required to move at full speed.
|
||||
/// If 0, then legs do not impact speed.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int RequiredLegs;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntityUid> LegEntities = new();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Content.Shared.Body.Events;
|
||||
|
||||
// All of these events are raised on a mechanism entity when added/removed to a body in different
|
||||
// ways.
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a mechanism when it is added to a body part.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganAddedEvent(EntityUid Part);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a mechanism when it is added to a body part within a body.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganAddedToBodyEvent(EntityUid Body, EntityUid Part);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a mechanism when it is removed from a body part.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganRemovedEvent(EntityUid OldPart);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a mechanism when it is removed from a body part within a body.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OrganRemovedFromBodyEvent(EntityUid OldBody, EntityUid OldPart);
|
||||
7
Content.Shared/Body/GibbableOrganComponent.cs
Normal file
7
Content.Shared/Body/GibbableOrganComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(GibbableOrganSystem))]
|
||||
public sealed partial class GibbableOrganComponent : Component;
|
||||
18
Content.Shared/Body/GibbableOrganSystem.cs
Normal file
18
Content.Shared/Body/GibbableOrganSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Shared.Gibbing;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed class GibbableOrganSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GibbableOrganComponent, BodyRelayedEvent<BeingGibbedEvent>>(OnBeingGibbed);
|
||||
}
|
||||
|
||||
private void OnBeingGibbed(Entity<GibbableOrganComponent> ent, ref BodyRelayedEvent<BeingGibbedEvent> args)
|
||||
{
|
||||
args.Args.Giblets.Add(ent);
|
||||
}
|
||||
}
|
||||
15
Content.Shared/Body/HandOrganComponent.cs
Normal file
15
Content.Shared/Body/HandOrganComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(HandOrganSystem))]
|
||||
public sealed partial class HandOrganComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public string HandID;
|
||||
|
||||
[DataField(required: true)]
|
||||
public Hand Data;
|
||||
}
|
||||
30
Content.Shared/Body/HandOrganSystem.cs
Normal file
30
Content.Shared/Body/HandOrganSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
public sealed class HandOrganSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HandOrganComponent, OrganGotInsertedEvent>(OnGotInserted);
|
||||
SubscribeLocalEvent<HandOrganComponent, OrganGotRemovedEvent>(OnGotRemoved);
|
||||
}
|
||||
|
||||
private void OnGotInserted(Entity<HandOrganComponent> ent, ref OrganGotInsertedEvent args)
|
||||
{
|
||||
_hands.AddHand(args.Target, ent.Comp.HandID, ent.Comp.Data);
|
||||
}
|
||||
|
||||
private void OnGotRemoved(Entity<HandOrganComponent> ent, ref OrganGotRemovedEvent args)
|
||||
{
|
||||
// prevent a recursive double-delete bug
|
||||
if (LifeStage(args.Target) >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
_hands.RemoveHand(args.Target, ent.Comp.HandID);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Body.Organ;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed partial class OrganComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Relevant body this organ is attached to.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Body;
|
||||
}
|
||||
13
Content.Shared/Body/OrganCategoryPrototype.cs
Normal file
13
Content.Shared/Body/OrganCategoryPrototype.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Marker prototype that defines well-known types of organs, e.g. "kidneys" or "left arm".
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class OrganCategoryPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
21
Content.Shared/Body/OrganComponent.cs
Normal file
21
Content.Shared/Body/OrganComponent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(BodySystem))]
|
||||
public sealed partial class OrganComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The body entity containing this organ, if any
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Body;
|
||||
|
||||
/// <summary>
|
||||
/// What kind of organ is this, if any
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<OrganCategoryPrototype>? Category;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBodySystem))]
|
||||
public sealed partial class BodyPartComponent : Component
|
||||
{
|
||||
// Need to set this on container changes as it may be several transform parents up the hierarchy.
|
||||
/// <summary>
|
||||
/// Parent body for this part.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Body;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
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>
|
||||
[DataField("vital"), AutoNetworkedField]
|
||||
public bool IsVital;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public BodyPartSymmetry Symmetry = BodyPartSymmetry.None;
|
||||
|
||||
/// <summary>
|
||||
/// Child body parts attached to this body part.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<string, BodyPartSlot> Children = new();
|
||||
|
||||
/// <summary>
|
||||
/// Organs attached to this body part.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<string, OrganSlot> Organs = new();
|
||||
|
||||
/// <summary>
|
||||
/// These are only for VV/Debug do not use these for gameplay/systems
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private List<ContainerSlot> BodyPartSlotsVV
|
||||
{
|
||||
get
|
||||
{
|
||||
List<ContainerSlot> temp = new();
|
||||
var containerSystem = IoCManager.Resolve<IEntityManager>().System<SharedContainerSystem>();
|
||||
|
||||
foreach (var slotId in Children.Keys)
|
||||
{
|
||||
temp.Add((ContainerSlot) containerSystem.GetContainer(Owner, SharedBodySystem.PartSlotContainerIdPrefix+slotId));
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
private List<ContainerSlot> OrganSlotsVV
|
||||
{
|
||||
get
|
||||
{
|
||||
List<ContainerSlot> temp = new();
|
||||
var containerSystem = IoCManager.Resolve<IEntityManager>().System<SharedContainerSystem>();
|
||||
|
||||
foreach (var slotId in Organs.Keys)
|
||||
{
|
||||
temp.Add((ContainerSlot) containerSystem.GetContainer(Owner, SharedBodySystem.OrganSlotContainerIdPrefix+slotId));
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains metadata about a body part in relation to its slot.
|
||||
/// </summary>
|
||||
[NetSerializable, Serializable]
|
||||
[DataRecord]
|
||||
public partial struct BodyPartSlot
|
||||
{
|
||||
public string Id;
|
||||
public BodyPartType Type;
|
||||
|
||||
public BodyPartSlot(string id, BodyPartType type)
|
||||
{
|
||||
Id = id;
|
||||
Type = type;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contains metadata about an organ part in relation to its slot.
|
||||
/// </summary>
|
||||
[NetSerializable, Serializable]
|
||||
[DataRecord]
|
||||
public partial struct OrganSlot
|
||||
{
|
||||
public string Id;
|
||||
|
||||
public OrganSlot(string id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Shared.Body.Part;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct BodyPartAddedEvent(string Slot, Entity<BodyPartComponent> Part);
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct BodyPartRemovedEvent(string Slot, Entity<BodyPartComponent> Part);
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the symmetry of a <see cref="BodyComponent"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyPartSymmetry
|
||||
{
|
||||
None = 0,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Body.Part
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the type of a <see cref="BodyComponent"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyPartType
|
||||
{
|
||||
Other = 0,
|
||||
Torso,
|
||||
Head,
|
||||
Arm,
|
||||
Hand,
|
||||
Leg,
|
||||
Foot,
|
||||
Tail
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Body.Prototypes;
|
||||
|
||||
[Prototype]
|
||||
public sealed partial class BodyPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("name")]
|
||||
public string Name { get; private set; } = "";
|
||||
|
||||
[DataField("root")] public string Root { get; private set; } = string.Empty;
|
||||
|
||||
[DataField("slots")] public Dictionary<string, BodyPrototypeSlot> Slots { get; private set; } = 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 partial record BodyPrototypeSlot(EntProtoId? Part, HashSet<string> Connections, Dictionary<string, string> Organs);
|
||||
@@ -1,178 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
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(MappingDataNode slot, IDependencyCollection dependencies)
|
||||
{
|
||||
var nodes = new List<ValidationNode>();
|
||||
var prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||
var factory = dependencies.Resolve<IComponentFactory>();
|
||||
|
||||
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 (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>(factory))
|
||||
{
|
||||
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("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 (value is not MappingDataNode slot)
|
||||
{
|
||||
nodes.Add(new ErrorNode(value, $"Slot is not a mapping data node"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = ValidateSlot(slot, 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,
|
||||
SerializationHookContext hookCtx, ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<BodyPrototype>? instanceProvider = null)
|
||||
{
|
||||
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 (slotId, valueNode) in slotNodes)
|
||||
{
|
||||
var slot = (MappingDataNode) valueNode;
|
||||
|
||||
string? part = null;
|
||||
if (slot.TryGet<ValueDataNode>("part", out var value))
|
||||
{
|
||||
part = value.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 (organKey, organValueNode) in slotOrgansNode)
|
||||
{
|
||||
organs.Add(organKey, ((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 ?? new HashSet<string>(), organs ?? new Dictionary<string, string>());
|
||||
slots.Add(slotId, slot);
|
||||
}
|
||||
|
||||
return new BodyPrototype(id, name, root, slots);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
@@ -16,8 +15,8 @@ public sealed class BrainSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BrainComponent, OrganAddedToBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
|
||||
SubscribeLocalEvent<BrainComponent, OrganRemovedFromBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
|
||||
SubscribeLocalEvent<BrainComponent, OrganGotInsertedEvent>((uid, _, args) => HandleMind(args.Target, uid));
|
||||
SubscribeLocalEvent<BrainComponent, OrganGotRemovedEvent>((uid, _, args) => HandleMind(uid, args.Target));
|
||||
SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Gibbing;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
/*
|
||||
* tl;dr of how bobby works
|
||||
* - BodyComponent uses a BodyPrototype as a template.
|
||||
* - On MapInit we spawn the root entity in the prototype and spawn all connections outwards from here
|
||||
* - Each "connection" is a body part (e.g. arm, hand, etc.) and each part can also contain organs.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
private const float GibletLaunchImpulse = 8;
|
||||
private const float GibletLaunchImpulseVariance = 3;
|
||||
|
||||
private void InitializeBody()
|
||||
{
|
||||
// Body here to handle root body parts.
|
||||
SubscribeLocalEvent<BodyComponent, EntInsertedIntoContainerMessage>(OnBodyInserted);
|
||||
SubscribeLocalEvent<BodyComponent, EntRemovedFromContainerMessage>(OnBodyRemoved);
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
|
||||
SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
|
||||
SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
|
||||
SubscribeLocalEvent<BodyComponent, BeingGibbedEvent>(OnBeingGibbed);
|
||||
}
|
||||
|
||||
private void OnBodyInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
// Root body part?
|
||||
var slotId = args.Container.ID;
|
||||
|
||||
if (slotId != BodyRootContainerId)
|
||||
return;
|
||||
|
||||
var insertedUid = args.Entity;
|
||||
|
||||
if (TryComp(insertedUid, out BodyPartComponent? part))
|
||||
{
|
||||
AddPart((ent, ent), (insertedUid, part), slotId);
|
||||
RecursiveBodyUpdate((insertedUid, part), ent);
|
||||
}
|
||||
|
||||
if (TryComp(insertedUid, out OrganComponent? organ))
|
||||
{
|
||||
AddOrgan((insertedUid, organ), ent, ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyRemoved(Entity<BodyComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
// Root body part?
|
||||
var slotId = args.Container.ID;
|
||||
|
||||
if (slotId != BodyRootContainerId)
|
||||
return;
|
||||
|
||||
var removedUid = args.Entity;
|
||||
DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent);
|
||||
DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent);
|
||||
|
||||
if (TryComp(removedUid, out BodyPartComponent? part))
|
||||
{
|
||||
RemovePart((ent, ent), (removedUid, part), slotId);
|
||||
RecursiveBodyUpdate((removedUid, part), null);
|
||||
}
|
||||
|
||||
if (TryComp(removedUid, out OrganComponent? organ))
|
||||
RemoveOrgan((removedUid, organ), ent);
|
||||
}
|
||||
|
||||
private void OnBodyInit(Entity<BodyComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
// Setup the initial container.
|
||||
ent.Comp.RootContainer = Containers.EnsureContainer<ContainerSlot>(ent, BodyRootContainerId);
|
||||
}
|
||||
|
||||
private void OnBodyMapInit(Entity<BodyComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (ent.Comp.Prototype is null)
|
||||
return;
|
||||
|
||||
// One-time setup
|
||||
// Obviously can't run in Init to avoid double-spawns on save / load.
|
||||
var prototype = Prototypes.Index(ent.Comp.Prototype.Value);
|
||||
MapInitBody(ent, prototype);
|
||||
}
|
||||
|
||||
private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype)
|
||||
{
|
||||
var protoRoot = prototype.Slots[prototype.Root];
|
||||
if (protoRoot.Part is null)
|
||||
return;
|
||||
|
||||
// This should already handle adding the entity to the root.
|
||||
var rootPartUid = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId);
|
||||
var rootPart = Comp<BodyPartComponent>(rootPartUid);
|
||||
rootPart.Body = bodyEntity;
|
||||
Dirty(rootPartUid, rootPart);
|
||||
|
||||
// Setup the rest of the body entities.
|
||||
SetupOrgans((rootPartUid, rootPart), protoRoot.Organs);
|
||||
MapInitParts(rootPartUid, prototype);
|
||||
}
|
||||
|
||||
private void OnBodyCanDrag(Entity<BodyComponent> ent, ref CanDragEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up all of the relevant body parts for a particular body entity and root part.
|
||||
/// </summary>
|
||||
private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype)
|
||||
{
|
||||
// Start at the root part and traverse the body graph, setting up parts as we go.
|
||||
// Basic BFS pathfind.
|
||||
var rootSlot = prototype.Root;
|
||||
var frontier = new Queue<string>();
|
||||
frontier.Enqueue(rootSlot);
|
||||
|
||||
// Child -> Parent connection.
|
||||
var cameFrom = new Dictionary<string, string>();
|
||||
cameFrom[rootSlot] = rootSlot;
|
||||
// Maps slot to its relevant entity.
|
||||
var cameFromEntities = new Dictionary<string, EntityUid>();
|
||||
cameFromEntities[rootSlot] = rootPartId;
|
||||
|
||||
while (frontier.TryDequeue(out var currentSlotId))
|
||||
{
|
||||
var currentSlot = prototype.Slots[currentSlotId];
|
||||
|
||||
foreach (var connection in currentSlot.Connections)
|
||||
{
|
||||
// Already been handled
|
||||
if (!cameFrom.TryAdd(connection, currentSlotId))
|
||||
continue;
|
||||
|
||||
// Setup part
|
||||
var connectionSlot = prototype.Slots[connection];
|
||||
var parentEntity = cameFromEntities[currentSlotId];
|
||||
var parentPartComponent = Comp<BodyPartComponent>(parentEntity);
|
||||
|
||||
// Spawn the entity on the target
|
||||
// then get the body part type, create the slot, and finally
|
||||
// we can insert it into the container.
|
||||
var childPart = Spawn(connectionSlot.Part, new EntityCoordinates(parentEntity, Vector2.Zero));
|
||||
cameFromEntities[connection] = childPart;
|
||||
|
||||
var childPartComponent = Comp<BodyPartComponent>(childPart);
|
||||
var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent);
|
||||
var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection));
|
||||
|
||||
if (partSlot is null || !Containers.Insert(childPart, cont))
|
||||
{
|
||||
Log.Error($"Could not create slot for connection {connection} in body {prototype.ID}");
|
||||
QueueDel(childPart);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add organs
|
||||
SetupOrgans((childPart, childPartComponent), connectionSlot.Organs);
|
||||
|
||||
// Enqueue it so we can also get its neighbors.
|
||||
frontier.Enqueue(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupOrgans(Entity<BodyPartComponent> ent, Dictionary<string, string> organs)
|
||||
{
|
||||
foreach (var (organSlotId, organProto) in organs)
|
||||
{
|
||||
var slot = CreateOrganSlot((ent, ent), organSlotId);
|
||||
SpawnInContainerOrDrop(organProto, ent, GetOrganContainerId(organSlotId));
|
||||
|
||||
if (slot is null)
|
||||
{
|
||||
Log.Error($"Could not create organ for slot {organSlotId} in {ToPrettyString(ent)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all body containers on this entity including the root one.
|
||||
/// </summary>
|
||||
public IEnumerable<BaseContainer> GetBodyContainers(
|
||||
EntityUid id,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? rootPart = null)
|
||||
{
|
||||
if (!Resolve(id, ref body, logMissing: false)
|
||||
|| body.RootContainer.ContainedEntity is null
|
||||
|| !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return body.RootContainer;
|
||||
|
||||
foreach (var childContainer in GetPartContainers(body.RootContainer.ContainedEntity.Value, rootPart))
|
||||
{
|
||||
yield return childContainer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all child body parts of this entity, including the root entity.
|
||||
/// </summary>
|
||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(
|
||||
EntityUid? id,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? rootPart = null)
|
||||
{
|
||||
if (id is null
|
||||
|| !Resolve(id.Value, ref body, logMissing: false)
|
||||
|| body.RootContainer.ContainedEntity is null
|
||||
|| !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var child in GetBodyPartChildren(body.RootContainer.ContainedEntity.Value, rootPart))
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(
|
||||
EntityUid? bodyId,
|
||||
BodyComponent? body = null)
|
||||
{
|
||||
if (bodyId is null || !Resolve(bodyId.Value, ref body, logMissing: false))
|
||||
yield break;
|
||||
|
||||
foreach (var part in GetBodyChildren(bodyId, body))
|
||||
{
|
||||
foreach (var organ in GetPartOrgans(part.Id, part.Component))
|
||||
{
|
||||
yield return organ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all body part slots for this entity.
|
||||
/// </summary>
|
||||
/// <param name="bodyId"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<BodyPartSlot> GetBodyAllSlots(
|
||||
EntityUid bodyId,
|
||||
BodyComponent? body = null)
|
||||
{
|
||||
if (!Resolve(bodyId, ref body, logMissing: false)
|
||||
|| body.RootContainer.ContainedEntity is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var slot in GetAllBodyPartSlots(body.RootContainer.ContainedEntity.Value))
|
||||
{
|
||||
yield return slot;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeingGibbed(Entity<BodyComponent> ent, ref BeingGibbedEvent args)
|
||||
{
|
||||
var parts = GetBodyChildren(ent, ent).ToArray();
|
||||
args.Giblets.EnsureCapacity(args.Giblets.Capacity + parts.Length);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
foreach (var organ in GetPartOrgans(part.Id, part.Component))
|
||||
{
|
||||
args.Giblets.Add(organ.Id);
|
||||
}
|
||||
PredictedQueueDel(part.Id);
|
||||
}
|
||||
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities(ent.Owner))
|
||||
{
|
||||
args.Giblets.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
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 Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
private void AddOrgan(
|
||||
Entity<OrganComponent> organEnt,
|
||||
EntityUid bodyUid,
|
||||
EntityUid parentPartUid)
|
||||
{
|
||||
organEnt.Comp.Body = bodyUid;
|
||||
var addedEv = new OrganAddedEvent(parentPartUid);
|
||||
RaiseLocalEvent(organEnt, ref addedEv);
|
||||
|
||||
if (organEnt.Comp.Body is not null)
|
||||
{
|
||||
var addedInBodyEv = new OrganAddedToBodyEvent(bodyUid, parentPartUid);
|
||||
RaiseLocalEvent(organEnt, ref addedInBodyEv);
|
||||
}
|
||||
|
||||
Dirty(organEnt, organEnt.Comp);
|
||||
}
|
||||
|
||||
private void RemoveOrgan(Entity<OrganComponent> organEnt, EntityUid parentPartUid)
|
||||
{
|
||||
var removedEv = new OrganRemovedEvent(parentPartUid);
|
||||
RaiseLocalEvent(organEnt, ref removedEv);
|
||||
|
||||
if (organEnt.Comp.Body is { Valid: true } bodyUid)
|
||||
{
|
||||
var removedInBodyEv = new OrganRemovedFromBodyEvent(bodyUid, parentPartUid);
|
||||
RaiseLocalEvent(organEnt, ref removedInBodyEv);
|
||||
}
|
||||
|
||||
organEnt.Comp.Body = null;
|
||||
Dirty(organEnt, organEnt.Comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the specified organ slot on the parent entity.
|
||||
/// </summary>
|
||||
private OrganSlot? CreateOrganSlot(Entity<BodyPartComponent?> parentEnt, string slotId)
|
||||
{
|
||||
if (!Resolve(parentEnt, ref parentEnt.Comp, logMissing: false))
|
||||
return null;
|
||||
|
||||
Containers.EnsureContainer<ContainerSlot>(parentEnt, GetOrganContainerId(slotId));
|
||||
var slot = new OrganSlot(slotId);
|
||||
parentEnt.Comp.Organs.Add(slotId, slot);
|
||||
return slot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create the specified organ slot on the specified parent if it exists.
|
||||
/// </summary>
|
||||
public bool TryCreateOrganSlot(
|
||||
EntityUid? parent,
|
||||
string slotId,
|
||||
[NotNullWhen(true)] out OrganSlot? slot,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
slot = null;
|
||||
|
||||
if (parent is null || !Resolve(parent.Value, ref part, logMissing: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Containers.EnsureContainer<ContainerSlot>(parent.Value, GetOrganContainerId(slotId));
|
||||
slot = new OrganSlot(slotId);
|
||||
return part.Organs.TryAdd(slotId, slot.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the slotId exists on the partId.
|
||||
/// </summary>
|
||||
public bool CanInsertOrgan(
|
||||
EntityUid partId,
|
||||
string slotId,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(partId, ref part) && part.Organs.ContainsKey(slotId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the specified organ slot exists on the partId.
|
||||
/// </summary>
|
||||
public bool CanInsertOrgan(
|
||||
EntityUid partId,
|
||||
OrganSlot slot,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return CanInsertOrgan(partId, slot.Id, part);
|
||||
}
|
||||
|
||||
public bool InsertOrgan(
|
||||
EntityUid partId,
|
||||
EntityUid organId,
|
||||
string slotId,
|
||||
BodyPartComponent? part = null,
|
||||
OrganComponent? organ = null)
|
||||
{
|
||||
if (!Resolve(organId, ref organ, logMissing: false)
|
||||
|| !Resolve(partId, ref part, logMissing: false)
|
||||
|| !CanInsertOrgan(partId, slotId, part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var containerId = GetOrganContainerId(slotId);
|
||||
|
||||
return Containers.TryGetContainer(partId, containerId, out var container)
|
||||
&& Containers.Insert(organId, container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the organ if it is inside of a body part.
|
||||
/// </summary>
|
||||
public bool RemoveOrgan(EntityUid organId, OrganComponent? organ = null)
|
||||
{
|
||||
if (!Containers.TryGetContainingContainer((organId, null, null), out var container))
|
||||
return false;
|
||||
|
||||
var parent = container.Owner;
|
||||
|
||||
return HasComp<BodyPartComponent>(parent)
|
||||
&& Containers.Remove(organId, container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add this organ to any matching slot on this body part.
|
||||
/// </summary>
|
||||
public bool AddOrganToFirstValidSlot(
|
||||
EntityUid partId,
|
||||
EntityUid organId,
|
||||
BodyPartComponent? part = null,
|
||||
OrganComponent? organ = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false)
|
||||
|| !Resolve(organId, ref organ, logMissing: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var slotId in part.Organs.Keys)
|
||||
{
|
||||
InsertOrgan(partId, organId, slotId, part, organ);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of Entity<<see cref="T"/>, <see cref="OrganComponent"/>>
|
||||
/// for each organ of the body
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component that we want to return</typeparam>
|
||||
/// <param name="entity">The body to check the organs of</param>
|
||||
public List<Entity<T, OrganComponent>> GetBodyOrganEntityComps<T>(
|
||||
Entity<BodyComponent?> entity)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return new List<Entity<T, OrganComponent>>();
|
||||
|
||||
var query = GetEntityQuery<T>();
|
||||
var list = new List<Entity<T, OrganComponent>>(3);
|
||||
foreach (var organ in GetBodyOrgans(entity.Owner, entity.Comp))
|
||||
{
|
||||
if (query.TryGetComponent(organ.Id, out var comp))
|
||||
list.Add((organ.Id, 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 body entity id to check 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 TryGetBodyOrganEntityComps<T>(
|
||||
Entity<BodyComponent?> entity,
|
||||
[NotNullWhen(true)] out List<Entity<T, OrganComponent>>? comps)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(entity.Owner, ref entity.Comp))
|
||||
{
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
comps = GetBodyOrganEntityComps<T>(entity);
|
||||
|
||||
if (comps.Count != 0)
|
||||
return true;
|
||||
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,803 +0,0 @@
|
||||
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.Movement.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public partial class SharedBodySystem
|
||||
{
|
||||
private static readonly ProtoId<DamageTypePrototype> BloodlossDamageType = "Bloodloss";
|
||||
private void InitializeParts()
|
||||
{
|
||||
// TODO: This doesn't handle comp removal on child ents.
|
||||
|
||||
// If you modify this also see the Body partial for root parts.
|
||||
SubscribeLocalEvent<BodyPartComponent, EntInsertedIntoContainerMessage>(OnBodyPartInserted);
|
||||
SubscribeLocalEvent<BodyPartComponent, EntRemovedFromContainerMessage>(OnBodyPartRemoved);
|
||||
}
|
||||
|
||||
private void OnBodyPartInserted(Entity<BodyPartComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
// Body part inserted into another body part.
|
||||
var insertedUid = args.Entity;
|
||||
var slotId = args.Container.ID;
|
||||
|
||||
if (ent.Comp.Body is null)
|
||||
return;
|
||||
|
||||
if (TryComp(insertedUid, out BodyPartComponent? part))
|
||||
{
|
||||
AddPart(ent.Comp.Body.Value, (insertedUid, part), slotId);
|
||||
RecursiveBodyUpdate((insertedUid, part), ent.Comp.Body.Value);
|
||||
}
|
||||
|
||||
if (TryComp(insertedUid, out OrganComponent? organ))
|
||||
AddOrgan((insertedUid, organ), ent.Comp.Body.Value, ent);
|
||||
}
|
||||
|
||||
private void OnBodyPartRemoved(Entity<BodyPartComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
// Body part removed from another body part.
|
||||
var removedUid = args.Entity;
|
||||
var slotId = args.Container.ID;
|
||||
|
||||
DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent.Comp.Body);
|
||||
DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent.Comp.Body);
|
||||
|
||||
if (TryComp(removedUid, out BodyPartComponent? part) && part.Body is not null)
|
||||
{
|
||||
RemovePart(part.Body.Value, (removedUid, part), slotId);
|
||||
RecursiveBodyUpdate((removedUid, part), null);
|
||||
}
|
||||
|
||||
if (TryComp(removedUid, out OrganComponent? organ))
|
||||
RemoveOrgan((removedUid, organ), ent);
|
||||
}
|
||||
|
||||
private void RecursiveBodyUpdate(Entity<BodyPartComponent> ent, EntityUid? bodyUid)
|
||||
{
|
||||
ent.Comp.Body = bodyUid;
|
||||
Dirty(ent, ent.Comp);
|
||||
|
||||
foreach (var slotId in ent.Comp.Organs.Keys)
|
||||
{
|
||||
if (!Containers.TryGetContainer(ent, GetOrganContainerId(slotId), out var container))
|
||||
continue;
|
||||
|
||||
foreach (var organ in container.ContainedEntities)
|
||||
{
|
||||
if (!TryComp(organ, out OrganComponent? organComp))
|
||||
continue;
|
||||
|
||||
Dirty(organ, organComp);
|
||||
|
||||
if (organComp.Body is { Valid: true } oldBodyUid)
|
||||
{
|
||||
var removedEv = new OrganRemovedFromBodyEvent(oldBodyUid, ent);
|
||||
RaiseLocalEvent(organ, ref removedEv);
|
||||
}
|
||||
|
||||
organComp.Body = bodyUid;
|
||||
if (bodyUid is not null)
|
||||
{
|
||||
var addedEv = new OrganAddedToBodyEvent(bodyUid.Value, ent);
|
||||
RaiseLocalEvent(organ, ref addedEv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var slotId in ent.Comp.Children.Keys)
|
||||
{
|
||||
if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container))
|
||||
continue;
|
||||
|
||||
foreach (var containedUid in container.ContainedEntities)
|
||||
{
|
||||
if (TryComp(containedUid, out BodyPartComponent? childPart))
|
||||
RecursiveBodyUpdate((containedUid, childPart), bodyUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AddPart(
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
{
|
||||
Dirty(partEnt, partEnt.Comp);
|
||||
partEnt.Comp.Body = bodyEnt;
|
||||
|
||||
var ev = new BodyPartAddedEvent(slotId, partEnt);
|
||||
RaiseLocalEvent(bodyEnt, ref ev);
|
||||
|
||||
AddLeg(partEnt, bodyEnt);
|
||||
}
|
||||
|
||||
protected virtual void RemovePart(
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
{
|
||||
Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false);
|
||||
Dirty(partEnt, partEnt.Comp);
|
||||
partEnt.Comp.Body = null;
|
||||
|
||||
var ev = new BodyPartRemovedEvent(slotId, partEnt);
|
||||
RaiseLocalEvent(bodyEnt, ref ev);
|
||||
|
||||
RemoveLeg(partEnt, bodyEnt);
|
||||
PartRemoveDamage(bodyEnt, partEnt);
|
||||
}
|
||||
|
||||
private void AddLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
|
||||
{
|
||||
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
if (legEnt.Comp.PartType == BodyPartType.Leg)
|
||||
{
|
||||
bodyEnt.Comp.LegEntities.Add(legEnt);
|
||||
UpdateMovementSpeed(bodyEnt);
|
||||
Dirty(bodyEnt, bodyEnt.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveLeg(Entity<BodyPartComponent> legEnt, Entity<BodyComponent?> bodyEnt)
|
||||
{
|
||||
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
if (legEnt.Comp.PartType != BodyPartType.Leg)
|
||||
return;
|
||||
|
||||
bodyEnt.Comp.LegEntities.Remove(legEnt);
|
||||
UpdateMovementSpeed(bodyEnt);
|
||||
Dirty(bodyEnt, bodyEnt.Comp);
|
||||
|
||||
if (bodyEnt.Comp.LegEntities.Count != 0)
|
||||
return;
|
||||
|
||||
if (!TryComp<StandingStateComponent>(bodyEnt, out var standingState)
|
||||
|| !standingState.Standing
|
||||
|| !Standing.Down(bodyEnt, standingState: standingState))
|
||||
return;
|
||||
|
||||
var ev = new DropHandItemsEvent();
|
||||
RaiseLocalEvent(bodyEnt, ref ev);
|
||||
}
|
||||
|
||||
private void PartRemoveDamage(Entity<BodyComponent?> bodyEnt, Entity<BodyPartComponent> partEnt)
|
||||
{
|
||||
if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
if (!_timing.ApplyingState
|
||||
&& partEnt.Comp.IsVital
|
||||
&& !GetBodyChildrenOfType(bodyEnt, partEnt.Comp.PartType, bodyEnt.Comp).Any()
|
||||
)
|
||||
{
|
||||
// TODO BODY SYSTEM KILL : remove this when wounding and required parts are implemented properly
|
||||
var damage = new DamageSpecifier(Prototypes.Index(BloodlossDamageType), 300);
|
||||
Damageable.ChangeDamage(bodyEnt.Owner, damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the parent body part to this if applicable.
|
||||
/// Doesn't validate if it's a part of body system.
|
||||
/// </summary>
|
||||
public EntityUid? GetParentPartOrNull(EntityUid uid)
|
||||
{
|
||||
if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
|
||||
return null;
|
||||
|
||||
var parent = container.Owner;
|
||||
|
||||
if (!HasComp<BodyPartComponent>(parent))
|
||||
return null;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the parent body part and slot to this if applicable.
|
||||
/// </summary>
|
||||
public (EntityUid Parent, string Slot)? GetParentPartAndSlotOrNull(EntityUid uid)
|
||||
{
|
||||
if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
|
||||
return null;
|
||||
|
||||
var slotId = GetPartSlotContainerIdFromContainer(container.ID);
|
||||
|
||||
if (string.IsNullOrEmpty(slotId))
|
||||
return null;
|
||||
|
||||
var parent = container.Owner;
|
||||
|
||||
if (!TryComp<BodyPartComponent>(parent, out var parentBody)
|
||||
|| !parentBody.Children.ContainsKey(slotId))
|
||||
return null;
|
||||
|
||||
return (parent, slotId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant parent body part to this if it exists.
|
||||
/// It won't exist if this is the root body part or if it's not in a body.
|
||||
/// </summary>
|
||||
public bool TryGetParentBodyPart(
|
||||
EntityUid partUid,
|
||||
[NotNullWhen(true)] out EntityUid? parentUid,
|
||||
[NotNullWhen(true)] out BodyPartComponent? parentComponent)
|
||||
{
|
||||
DebugTools.Assert(HasComp<BodyPartComponent>(partUid));
|
||||
parentUid = null;
|
||||
parentComponent = null;
|
||||
|
||||
if (Containers.TryGetContainingContainer((partUid, null, null), out var container) &&
|
||||
TryComp(container.Owner, out parentComponent))
|
||||
{
|
||||
parentUid = container.Owner;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Slots
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BodyPartSlot on the specified partUid.
|
||||
/// </summary>
|
||||
private BodyPartSlot? CreatePartSlot(
|
||||
EntityUid partUid,
|
||||
string slotId,
|
||||
BodyPartType partType,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partUid, ref part, logMissing: false))
|
||||
return null;
|
||||
|
||||
Containers.EnsureContainer<ContainerSlot>(partUid, GetPartSlotContainerId(slotId));
|
||||
var partSlot = new BodyPartSlot(slotId, partType);
|
||||
part.Children.Add(slotId, partSlot);
|
||||
Dirty(partUid, part);
|
||||
return partSlot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create a BodyPartSlot on the specified partUid.
|
||||
/// </summary>
|
||||
/// <returns>false if not relevant or can't add it.</returns>
|
||||
public bool TryCreatePartSlot(
|
||||
EntityUid? partId,
|
||||
string slotId,
|
||||
BodyPartType partType,
|
||||
[NotNullWhen(true)] out BodyPartSlot? slot,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
slot = null;
|
||||
|
||||
if (partId is null
|
||||
|| !Resolve(partId.Value, ref part, logMissing: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Containers.EnsureContainer<ContainerSlot>(partId.Value, GetPartSlotContainerId(slotId));
|
||||
slot = new BodyPartSlot(slotId, partType);
|
||||
|
||||
if (!part.Children.TryAdd(slotId, slot.Value))
|
||||
return false;
|
||||
|
||||
Dirty(partId.Value, part);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCreatePartSlotAndAttach(
|
||||
EntityUid parentId,
|
||||
string slotId,
|
||||
EntityUid childId,
|
||||
BodyPartType partType,
|
||||
BodyPartComponent? parent = null,
|
||||
BodyPartComponent? child = null)
|
||||
{
|
||||
return TryCreatePartSlot(parentId, slotId, partType, out _, parent)
|
||||
&& AttachPart(parentId, slotId, childId, parent, child);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RootPartManagement
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the partId is the root body container for the specified bodyId.
|
||||
/// </summary>
|
||||
public bool IsPartRoot(
|
||||
EntityUid bodyId,
|
||||
EntityUid partId,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(partId, ref part)
|
||||
&& Resolve(bodyId, ref body)
|
||||
&& Containers.TryGetContainingContainer(bodyId, partId, out var container)
|
||||
&& container.ID == BodyRootContainerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we can attach the partId to the bodyId as the root entity.
|
||||
/// </summary>
|
||||
public bool CanAttachToRoot(
|
||||
EntityUid bodyId,
|
||||
EntityUid partId,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(bodyId, ref body)
|
||||
&& Resolve(partId, ref part)
|
||||
&& body.RootContainer.ContainedEntity is null
|
||||
&& bodyId != part.Body;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the root part of this body if it exists.
|
||||
/// </summary>
|
||||
public (EntityUid Entity, BodyPartComponent BodyPart)? GetRootPartOrNull(EntityUid bodyId, BodyComponent? body = null)
|
||||
{
|
||||
if (!Resolve(bodyId, ref body)
|
||||
|| body.RootContainer.ContainedEntity is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (body.RootContainer.ContainedEntity.Value,
|
||||
Comp<BodyPartComponent>(body.RootContainer.ContainedEntity.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the partId can be attached to the parentId in the specified slot.
|
||||
/// </summary>
|
||||
public bool CanAttachPart(
|
||||
EntityUid parentId,
|
||||
BodyPartSlot slot,
|
||||
EntityUid partId,
|
||||
BodyPartComponent? parentPart = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(partId, ref part, logMissing: false)
|
||||
&& Resolve(parentId, ref parentPart, logMissing: false)
|
||||
&& CanAttachPart(parentId, slot.Id, partId, parentPart, part);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we can attach the specified partId to the parentId in the specified slot.
|
||||
/// </summary>
|
||||
public bool CanAttachPart(
|
||||
EntityUid parentId,
|
||||
string slotId,
|
||||
EntityUid partId,
|
||||
BodyPartComponent? parentPart = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(partId, ref part, logMissing: false)
|
||||
&& Resolve(parentId, ref parentPart, logMissing: false)
|
||||
&& parentPart.Children.TryGetValue(slotId, out var parentSlotData)
|
||||
&& part.PartType == parentSlotData.Type
|
||||
&& Containers.TryGetContainer(parentId, GetPartSlotContainerId(slotId), out var container)
|
||||
&& Containers.CanInsert(partId, container);
|
||||
}
|
||||
|
||||
public bool AttachPartToRoot(
|
||||
EntityUid bodyId,
|
||||
EntityUid partId,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(bodyId, ref body)
|
||||
&& Resolve(partId, ref part)
|
||||
&& CanAttachToRoot(bodyId, partId, body, part)
|
||||
&& Containers.Insert(partId, body.RootContainer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Attach/Detach
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a body part to the specified body part parent.
|
||||
/// </summary>
|
||||
public bool AttachPart(
|
||||
EntityUid parentPartId,
|
||||
string slotId,
|
||||
EntityUid partId,
|
||||
BodyPartComponent? parentPart = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(parentPartId, ref parentPart, logMissing: false)
|
||||
&& parentPart.Children.TryGetValue(slotId, out var slot)
|
||||
&& AttachPart(parentPartId, slot, partId, parentPart, part);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a body part to the specified body part parent.
|
||||
/// </summary>
|
||||
public bool AttachPart(
|
||||
EntityUid parentPartId,
|
||||
BodyPartSlot slot,
|
||||
EntityUid partId,
|
||||
BodyPartComponent? parentPart = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(parentPartId, ref parentPart, logMissing: false)
|
||||
|| !Resolve(partId, ref part, logMissing: false)
|
||||
|| !CanAttachPart(parentPartId, slot.Id, partId, parentPart, part)
|
||||
|| !parentPart.Children.ContainsKey(slot.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Containers.TryGetContainer(parentPartId, GetPartSlotContainerId(slot.Id), out var container))
|
||||
{
|
||||
DebugTools.Assert($"Unable to find body slot {slot.Id} for {ToPrettyString(parentPartId)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return Containers.Insert(partId, container);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Misc
|
||||
|
||||
public void UpdateMovementSpeed(
|
||||
EntityUid bodyId,
|
||||
BodyComponent? body = null,
|
||||
MovementSpeedModifierComponent? movement = null)
|
||||
{
|
||||
if (!Resolve(bodyId, ref body, ref movement, logMissing: false)
|
||||
|| body.RequiredLegs <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var walkSpeed = 0f;
|
||||
var sprintSpeed = 0f;
|
||||
var acceleration = 0f;
|
||||
foreach (var legEntity in body.LegEntities)
|
||||
{
|
||||
if (!TryComp<MovementBodyPartComponent>(legEntity, out var legModifier))
|
||||
continue;
|
||||
|
||||
walkSpeed += legModifier.WalkSpeed;
|
||||
sprintSpeed += legModifier.SprintSpeed;
|
||||
acceleration += legModifier.Acceleration;
|
||||
}
|
||||
walkSpeed /= body.RequiredLegs;
|
||||
sprintSpeed /= body.RequiredLegs;
|
||||
acceleration /= body.RequiredLegs;
|
||||
Movement.ChangeBaseSpeed(bodyId, walkSpeed, sprintSpeed, acceleration, movement);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Queries
|
||||
|
||||
/// <summary>
|
||||
/// Get all organs for the specified body part.
|
||||
/// </summary>
|
||||
public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid partId, BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
foreach (var slotId in part.Organs.Keys)
|
||||
{
|
||||
var containerSlotId = GetOrganContainerId(slotId);
|
||||
|
||||
if (!Containers.TryGetContainer(partId, containerSlotId, out var container))
|
||||
continue;
|
||||
|
||||
foreach (var containedEnt in container.ContainedEntities)
|
||||
{
|
||||
if (!TryComp(containedEnt, out OrganComponent? organ))
|
||||
continue;
|
||||
|
||||
yield return (containedEnt, organ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all BaseContainers for body parts on this entity and its child entities.
|
||||
/// </summary>
|
||||
public IEnumerable<BaseContainer> GetPartContainers(EntityUid id, BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(id, ref part, logMissing: false) ||
|
||||
part.Children.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var slotId in part.Children.Keys)
|
||||
{
|
||||
var containerSlotId = GetPartSlotContainerId(slotId);
|
||||
|
||||
if (!Containers.TryGetContainer(id, containerSlotId, out var container))
|
||||
continue;
|
||||
|
||||
yield return container;
|
||||
|
||||
foreach (var ent in container.ContainedEntities)
|
||||
{
|
||||
foreach (var childContainer in GetPartContainers(ent))
|
||||
{
|
||||
yield return childContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all body part components for this entity including itself.
|
||||
/// </summary>
|
||||
public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyPartChildren(
|
||||
EntityUid partId,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
yield return (partId, part);
|
||||
|
||||
foreach (var slotId in part.Children.Keys)
|
||||
{
|
||||
var containerSlotId = GetPartSlotContainerId(slotId);
|
||||
|
||||
if (Containers.TryGetContainer(partId, containerSlotId, out var container))
|
||||
{
|
||||
foreach (var containedEnt in container.ContainedEntities)
|
||||
{
|
||||
if (!TryComp(containedEnt, out BodyPartComponent? childPart))
|
||||
continue;
|
||||
|
||||
foreach (var value in GetBodyPartChildren(containedEnt, childPart))
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all body part slots for this entity.
|
||||
/// </summary>
|
||||
public IEnumerable<BodyPartSlot> GetAllBodyPartSlots(
|
||||
EntityUid partId,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
foreach (var (slotId, slot) in part.Children)
|
||||
{
|
||||
yield return slot;
|
||||
|
||||
var containerSlotId = GetOrganContainerId(slotId);
|
||||
|
||||
if (Containers.TryGetContainer(partId, containerSlotId, out var container))
|
||||
{
|
||||
foreach (var containedEnt in container.ContainedEntities)
|
||||
{
|
||||
if (!TryComp(containedEnt, out BodyPartComponent? childPart))
|
||||
continue;
|
||||
|
||||
foreach (var subSlot in GetAllBodyPartSlots(containedEnt, childPart))
|
||||
{
|
||||
yield return subSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the bodyId has any parts of this type.
|
||||
/// </summary>
|
||||
public bool BodyHasPartType(
|
||||
EntityUid bodyId,
|
||||
BodyPartType type,
|
||||
BodyComponent? body = null)
|
||||
{
|
||||
return GetBodyChildrenOfType(bodyId, type, body).Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the parentId has the specified childId.
|
||||
/// </summary>
|
||||
public bool PartHasChild(
|
||||
EntityUid parentId,
|
||||
EntityUid childId,
|
||||
BodyPartComponent? parent,
|
||||
BodyPartComponent? child)
|
||||
{
|
||||
if (!Resolve(parentId, ref parent, logMissing: false)
|
||||
|| !Resolve(childId, ref child, logMissing: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (foundId, _) in GetBodyPartChildren(parentId, parent))
|
||||
{
|
||||
if (foundId == childId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the bodyId has the specified partId.
|
||||
/// </summary>
|
||||
public bool BodyHasChild(
|
||||
EntityUid bodyId,
|
||||
EntityUid partId,
|
||||
BodyComponent? body = null,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
return Resolve(bodyId, ref body, logMissing: false)
|
||||
&& body.RootContainer.ContainedEntity is not null
|
||||
&& Resolve(partId, ref part, logMissing: false)
|
||||
&& TryComp(body.RootContainer.ContainedEntity, out BodyPartComponent? rootPart)
|
||||
&& PartHasChild(body.RootContainer.ContainedEntity.Value, partId, rootPart, part);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of ValueTuples of <see cref="T"/> and OrganComponent on each organ
|
||||
/// in the given part.
|
||||
/// </summary>
|
||||
/// <param name="uid">The part entity id to check on.</param>
|
||||
/// <param name="part">The part to check for organs on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
public List<(T Comp, OrganComponent Organ)> GetBodyPartOrganComponents<T>(
|
||||
EntityUid uid,
|
||||
BodyPartComponent? part = null)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(uid, ref part))
|
||||
return new List<(T Comp, OrganComponent Organ)>();
|
||||
|
||||
var query = GetEntityQuery<T>();
|
||||
var list = new List<(T Comp, OrganComponent Organ)>();
|
||||
|
||||
foreach (var organ in GetPartOrgans(uid, part))
|
||||
{
|
||||
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 part.
|
||||
/// </summary>
|
||||
/// <param name="uid">The part entity id to check on.</param>
|
||||
/// <param name="comps">The list of components.</param>
|
||||
/// <param name="part">The part to check for organs on.</param>
|
||||
/// <typeparam name="T">The component to check for.</typeparam>
|
||||
/// <returns>Whether any were found.</returns>
|
||||
public bool TryGetBodyPartOrganComponents<T>(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] out List<(T Comp, OrganComponent Organ)>? comps,
|
||||
BodyPartComponent? part = null)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(uid, ref part))
|
||||
{
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
comps = GetBodyPartOrganComponents<T>(uid, part);
|
||||
|
||||
if (comps.Count != 0)
|
||||
return true;
|
||||
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent body part and all immediate child body parts for the partId.
|
||||
/// </summary>
|
||||
public IEnumerable<EntityUid> GetBodyPartAdjacentParts(
|
||||
EntityUid partId,
|
||||
BodyPartComponent? part = null)
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
if (TryGetParentBodyPart(partId, out var parentUid, out _))
|
||||
yield return parentUid.Value;
|
||||
|
||||
foreach (var slotId in part.Children.Keys)
|
||||
{
|
||||
var container = Containers.GetContainer(partId, GetPartSlotContainerId(slotId));
|
||||
|
||||
foreach (var containedEnt in container.ContainedEntities)
|
||||
{
|
||||
yield return containedEnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid AdjacentId, T Component)> GetBodyPartAdjacentPartsComponents<T>(
|
||||
EntityUid partId,
|
||||
BodyPartComponent? part = null)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
yield break;
|
||||
|
||||
var query = GetEntityQuery<T>();
|
||||
foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part))
|
||||
{
|
||||
if (query.TryGetComponent(adjacentId, out var component))
|
||||
yield return (adjacentId, component);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetBodyPartAdjacentPartsComponents<T>(
|
||||
EntityUid partId,
|
||||
[NotNullWhen(true)] out List<(EntityUid AdjacentId, T Component)>? comps,
|
||||
BodyPartComponent? part = null)
|
||||
where T : IComponent
|
||||
{
|
||||
if (!Resolve(partId, ref part, logMissing: false))
|
||||
{
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var query = GetEntityQuery<T>();
|
||||
comps = new List<(EntityUid AdjacentId, T Component)>();
|
||||
|
||||
foreach (var adjacentId in GetBodyPartAdjacentParts(partId, part))
|
||||
{
|
||||
if (query.TryGetComponent(adjacentId, out var component))
|
||||
comps.Add((adjacentId, component));
|
||||
}
|
||||
|
||||
if (comps.Count != 0)
|
||||
return true;
|
||||
|
||||
comps = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Body.Systems;
|
||||
|
||||
public abstract partial class SharedBodySystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* See the body partial for how this works.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Container ID prefix for any body parts.
|
||||
/// </summary>
|
||||
public const string PartSlotContainerIdPrefix = "body_part_slot_";
|
||||
|
||||
/// <summary>
|
||||
/// Container ID for the ContainerSlot on the body entity itself.
|
||||
/// </summary>
|
||||
public const string BodyRootContainerId = "body_root_part";
|
||||
|
||||
/// <summary>
|
||||
/// Container ID prefix for any body organs.
|
||||
/// </summary>
|
||||
public const string OrganSlotContainerIdPrefix = "body_organ_slot_";
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] protected readonly IPrototypeManager Prototypes = default!;
|
||||
[Dependency] protected readonly DamageableSystem Damageable = default!;
|
||||
[Dependency] protected readonly MovementSpeedModifierSystem Movement = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem Containers = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem SharedTransform = default!;
|
||||
[Dependency] protected readonly StandingStateSystem Standing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeBody();
|
||||
InitializeParts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inverse of <see cref="GetPartSlotContainerId"/>
|
||||
/// </summary>
|
||||
protected static string? GetPartSlotContainerIdFromContainer(string containerSlotId)
|
||||
{
|
||||
// This is blursed
|
||||
var slotIndex = containerSlotId.IndexOf(PartSlotContainerIdPrefix, StringComparison.Ordinal);
|
||||
|
||||
if (slotIndex < 0)
|
||||
return null;
|
||||
|
||||
var slotId = containerSlotId.Remove(slotIndex, PartSlotContainerIdPrefix.Length);
|
||||
return slotId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the container Id for the specified slotId.
|
||||
/// </summary>
|
||||
public static string GetPartSlotContainerId(string slotId)
|
||||
{
|
||||
return PartSlotContainerIdPrefix + slotId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the container Id for the specified slotId.
|
||||
/// </summary>
|
||||
public static string GetOrganContainerId(string slotId)
|
||||
{
|
||||
return OrganSlotContainerIdPrefix + slotId;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Atmos.Rotting;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Changeling.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
@@ -29,7 +28,7 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
// Wait until the character has all their organs before we give them their loadout
|
||||
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedBodySystem)]);
|
||||
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
public static string GetJobPrototype(string? loadout)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
|
||||
namespace Content.Shared.Humanoid
|
||||
{
|
||||
public static class HumanoidVisualLayersExtension
|
||||
@@ -72,72 +69,5 @@ namespace Content.Shared.Humanoid
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public static HumanoidVisualLayers? ToHumanoidLayers(this BodyPartComponent part)
|
||||
{
|
||||
switch (part.PartType)
|
||||
{
|
||||
case BodyPartType.Other:
|
||||
break;
|
||||
case BodyPartType.Torso:
|
||||
return HumanoidVisualLayers.Chest;
|
||||
case BodyPartType.Tail:
|
||||
return HumanoidVisualLayers.Tail;
|
||||
case BodyPartType.Head:
|
||||
// use the Sublayers method to hide the rest of the parts,
|
||||
// if that's what you're looking for
|
||||
return HumanoidVisualLayers.Head;
|
||||
case BodyPartType.Arm:
|
||||
switch (part.Symmetry)
|
||||
{
|
||||
case BodyPartSymmetry.None:
|
||||
break;
|
||||
case BodyPartSymmetry.Left:
|
||||
return HumanoidVisualLayers.LArm;
|
||||
case BodyPartSymmetry.Right:
|
||||
return HumanoidVisualLayers.RArm;
|
||||
}
|
||||
|
||||
break;
|
||||
case BodyPartType.Hand:
|
||||
switch (part.Symmetry)
|
||||
{
|
||||
case BodyPartSymmetry.None:
|
||||
break;
|
||||
case BodyPartSymmetry.Left:
|
||||
return HumanoidVisualLayers.LHand;
|
||||
case BodyPartSymmetry.Right:
|
||||
return HumanoidVisualLayers.RHand;
|
||||
}
|
||||
|
||||
break;
|
||||
case BodyPartType.Leg:
|
||||
switch (part.Symmetry)
|
||||
{
|
||||
case BodyPartSymmetry.None:
|
||||
break;
|
||||
case BodyPartSymmetry.Left:
|
||||
return HumanoidVisualLayers.LLeg;
|
||||
case BodyPartSymmetry.Right:
|
||||
return HumanoidVisualLayers.RLeg;
|
||||
}
|
||||
|
||||
break;
|
||||
case BodyPartType.Foot:
|
||||
switch (part.Symmetry)
|
||||
{
|
||||
case BodyPartSymmetry.None:
|
||||
break;
|
||||
case BodyPartSymmetry.Left:
|
||||
return HumanoidVisualLayers.LFoot;
|
||||
case BodyPartSymmetry.Right:
|
||||
return HumanoidVisualLayers.RFoot;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Mobs.Components;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -28,7 +29,6 @@ public sealed class VomitSystem : EntitySystem
|
||||
[Dependency] private readonly ThirstSystem _thirst = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedPuddleSystem _puddle = default!;
|
||||
@@ -38,7 +38,7 @@ public sealed class VomitSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BodyComponent, TryVomitEvent>(TryBodyVomitSolution);
|
||||
SubscribeLocalEvent<StomachComponent, BodyRelayedEvent<TryVomitEvent>>(TryVomitSolution);
|
||||
}
|
||||
|
||||
private const float ChemMultiplier = 0.1f;
|
||||
@@ -50,24 +50,12 @@ public sealed class VomitSystem : EntitySystem
|
||||
private readonly SoundSpecifier _vomitSound = new SoundCollectionSpecifier(VomitCollection,
|
||||
AudioParams.Default.WithVariation(0.2f).WithVolume(-4f));
|
||||
|
||||
private void TryBodyVomitSolution(Entity<BodyComponent> ent, ref TryVomitEvent args)
|
||||
private void TryVomitSolution(Entity<StomachComponent> ent, ref BodyRelayedEvent<TryVomitEvent> args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
if (_solutionContainer.ResolveSolution(ent.Owner, StomachSystem.DefaultSolutionName, ref ent.Comp.Solution, out var sol))
|
||||
_solutionContainer.TryTransferSolution(ent.Comp.Solution.Value, args.Args.Sol, sol.AvailableVolume);
|
||||
|
||||
// Main requirement: You have a stomach
|
||||
var stomachList = _body.GetBodyOrganEntityComps<StomachComponent>((ent, null));
|
||||
if (stomachList.Count == 0)
|
||||
return;
|
||||
|
||||
// Empty the stomach out into it
|
||||
foreach (var stomach in stomachList)
|
||||
{
|
||||
if (_solutionContainer.ResolveSolution(stomach.Owner, StomachSystem.DefaultSolutionName, ref stomach.Comp1.Solution, out var sol))
|
||||
_solutionContainer.TryTransferSolution(stomach.Comp1.Solution.Value, args.Sol, sol.AvailableVolume);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
args.Args = args.Args with { Handled = true };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -57,7 +57,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
// Body Component Dependencies
|
||||
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly ReactiveSystem _reaction = default!;
|
||||
[Dependency] private readonly StomachSystem _stomach = default!;
|
||||
|
||||
@@ -178,7 +178,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
/// <param name="food">Entity being eaten</param>
|
||||
/// <param name="stomachs">Stomachs available to digest</param>
|
||||
/// <param name="popup">Should we also display popup text if it exists?</param>
|
||||
public bool IsDigestibleBy(EntityUid food, List<Entity<StomachComponent, OrganComponent>> stomachs, out bool popup)
|
||||
public bool IsDigestibleBy(EntityUid food, List<Entity<StomachComponent>> stomachs, out bool popup)
|
||||
{
|
||||
popup = false;
|
||||
var ev = new IsDigestibleEvent();
|
||||
@@ -195,7 +195,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
foreach (var ent in stomachs)
|
||||
{
|
||||
// We need one stomach that can digest our special food.
|
||||
if (_whitelistSystem.IsWhitelistPass(ent.Comp1.SpecialDigestible, food))
|
||||
if (_whitelistSystem.IsWhitelistPass(ent.Comp.SpecialDigestible, food))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -204,9 +204,9 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
foreach (var ent in stomachs)
|
||||
{
|
||||
// We need one stomach that can digest normal food.
|
||||
if (ent.Comp1.SpecialDigestible == null
|
||||
|| !ent.Comp1.IsSpecialDigestibleExclusive
|
||||
|| _whitelistSystem.IsWhitelistPass(ent.Comp1.SpecialDigestible, food))
|
||||
if (ent.Comp.SpecialDigestible == null
|
||||
|| !ent.Comp.IsSpecialDigestibleExclusive
|
||||
|| _whitelistSystem.IsWhitelistPass(ent.Comp.SpecialDigestible, food))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="food">Entity being eaten</param>
|
||||
/// <param name="stomach">Stomachs that is attempting to digest.</param>
|
||||
public bool IsDigestibleBy(EntityUid food, Entity<StomachComponent, OrganComponent> stomach)
|
||||
public bool IsDigestibleBy(EntityUid food, Entity<StomachComponent> stomach)
|
||||
{
|
||||
var ev = new IsDigestibleEvent();
|
||||
RaiseLocalEvent(food, ref ev);
|
||||
@@ -233,9 +233,9 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
return true;
|
||||
|
||||
if (ev.SpecialDigestion)
|
||||
return _whitelistSystem.IsWhitelistPass(stomach.Comp1.SpecialDigestible, food);
|
||||
return _whitelistSystem.IsWhitelistPass(stomach.Comp.SpecialDigestible, food);
|
||||
|
||||
if (stomach.Comp1.SpecialDigestible == null || !stomach.Comp1.IsSpecialDigestibleExclusive || _whitelistSystem.IsWhitelistPass(stomach.Comp1.SpecialDigestible, food))
|
||||
if (stomach.Comp.SpecialDigestible == null || !stomach.Comp.IsSpecialDigestibleExclusive || _whitelistSystem.IsWhitelistPass(stomach.Comp.SpecialDigestible, food))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -246,7 +246,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
var food = args.Ingested;
|
||||
var forceFed = args.User != entity.Owner;
|
||||
|
||||
if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(entity!, out var stomachs))
|
||||
if (!_body.TryGetOrgansWithComponent<StomachComponent>(entity!, out var stomachs))
|
||||
return;
|
||||
|
||||
// Can we digest the specific item we're trying to eat?
|
||||
@@ -311,7 +311,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
if (!CanConsume(args.User, entity, food, out var solution, out _))
|
||||
return;
|
||||
|
||||
if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(entity!, out var stomachs))
|
||||
if (!_body.TryGetOrgansWithComponent<StomachComponent>(entity!, out var stomachs))
|
||||
return;
|
||||
|
||||
var forceFed = args.User != entity.Owner;
|
||||
@@ -321,7 +321,7 @@ public sealed partial class IngestionSystem : EntitySystem
|
||||
foreach (var ent in stomachs)
|
||||
{
|
||||
var owner = ent.Owner;
|
||||
if (!_solutionContainer.ResolveSolution(owner, StomachSystem.DefaultSolutionName, ref ent.Comp1.Solution, out var stomachSol))
|
||||
if (!_solutionContainer.ResolveSolution(owner, StomachSystem.DefaultSolutionName, ref ent.Comp.Solution, out var stomachSol))
|
||||
continue;
|
||||
|
||||
if (stomachSol.AvailableVolume <= highestAvailable)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Standing;
|
||||
@@ -11,7 +10,6 @@ public sealed class LegsParalyzedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standingSystem = default!;
|
||||
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,7 +30,6 @@ public sealed class LegsParalyzedSystem : EntitySystem
|
||||
private void OnShutdown(EntityUid uid, LegsParalyzedComponent component, ComponentShutdown args)
|
||||
{
|
||||
_standingSystem.Stand(uid);
|
||||
_bodySystem.UpdateMovementSpeed(uid);
|
||||
}
|
||||
|
||||
private void OnBuckled(EntityUid uid, LegsParalyzedComponent component, ref BuckledEvent args)
|
||||
|
||||
@@ -5,9 +5,7 @@ microwave-component-interact-using-broken = It's broken!
|
||||
microwave-component-interact-using-container-full = Container is full
|
||||
microwave-component-interact-using-transfer-success = Transferred {$amount}u
|
||||
microwave-component-interact-using-transfer-fail = That won't work!
|
||||
microwave-component-suicide-multi-head-others-message = {$victim} is trying to cook their heads!
|
||||
microwave-component-suicide-others-message = {$victim} is trying to cook their head!
|
||||
microwave-component-suicide-multi-head-message = You cook your heads!
|
||||
microwave-component-suicide-message = You cook your head!
|
||||
microwave-component-interact-full = It's full.
|
||||
microwave-component-interact-item-too-big = { CAPITALIZE(THE($item)) } is too big to fit in the microwave!
|
||||
|
||||
@@ -1983,14 +1983,14 @@ entities:
|
||||
- type: Transform
|
||||
pos: 7.5,6.5
|
||||
parent: 16
|
||||
- proto: LeftArmSkeleton
|
||||
- proto: OrganSkeletonPersonArmLeft
|
||||
entities:
|
||||
- uid: 175
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 6.8180027,-5.7761726
|
||||
parent: 16
|
||||
- proto: LeftHandSkeleton
|
||||
- proto: OrganSkeletonPersonHandLeft
|
||||
entities:
|
||||
- uid: 174
|
||||
components:
|
||||
|
||||
55
Resources/Prototypes/Body/Animals/animal.yml
Normal file
55
Resources/Prototypes/Body/Animals/animal.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
- type: entity
|
||||
id: OrganAnimalMetabolizer
|
||||
abstract: true
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Animal ]
|
||||
|
||||
- type: entity
|
||||
parent: OrganBase
|
||||
id: OrganAnimal
|
||||
abstract: true
|
||||
suffix: Animal
|
||||
|
||||
- type: entity
|
||||
parent: OrganAnimal
|
||||
id: OrganAnimalInternal
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/organs.rsi
|
||||
|
||||
- type: entity
|
||||
parent: [ OrganBaseLungs, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganAnimalLungs
|
||||
|
||||
- type: entity
|
||||
parent: [ OrganBaseHeart, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganAnimalHeart
|
||||
|
||||
- type: entity
|
||||
parent: [ OrganBaseStomach, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganAnimalStomach
|
||||
|
||||
- type: entity
|
||||
parent: [ OrganBaseLiver, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganAnimalLiver
|
||||
|
||||
- type: entity
|
||||
parent: [ OrganBaseKidneys, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganAnimalKidneys
|
||||
|
||||
- type: entity
|
||||
id: BaseMobAnimal
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganAnimalLungs
|
||||
- id: OrganAnimalHeart
|
||||
- id: OrganAnimalStomach
|
||||
- id: OrganAnimalLiver
|
||||
- id: OrganAnimalKidneys
|
||||
34
Resources/Prototypes/Body/Animals/bloodsucker.yml
Normal file
34
Resources/Prototypes/Body/Animals/bloodsucker.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
- type: entity
|
||||
id: OrganBloodsucker
|
||||
abstract: true
|
||||
suffix: bloodsucker
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Bloodsucker ]
|
||||
|
||||
- type: entity
|
||||
id: OrganBloodsuckerStomach
|
||||
parent: [ OrganBloodsucker, OrganAnimalStomach ]
|
||||
|
||||
- type: entity
|
||||
id: OrganBloodsuckerLiver
|
||||
parent: [ OrganBloodsucker, OrganAnimalLiver ]
|
||||
|
||||
- type: entity
|
||||
id: OrganBloodsuckerHeart
|
||||
parent: [ OrganBloodsucker, OrganAnimalHeart ]
|
||||
|
||||
- type: entity
|
||||
id: BaseMobBloodsucker
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganAnimalLungs
|
||||
- id: OrganBloodsuckerHeart
|
||||
- id: OrganBloodsuckerStomach
|
||||
- id: OrganBloodsuckerLiver
|
||||
- id: OrganAnimalKidneys
|
||||
14
Resources/Prototypes/Body/Animals/hemocyanin.yml
Normal file
14
Resources/Prototypes/Body/Animals/hemocyanin.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
- type: entity
|
||||
id: BaseMobHemocyanin
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganAnimalLungs
|
||||
- id: OrganArachnidHeart
|
||||
- id: OrganAnimalStomach
|
||||
- id: OrganAnimalLiver
|
||||
- id: OrganAnimalKidneys
|
||||
14
Resources/Prototypes/Body/Animals/mothroach.yml
Normal file
14
Resources/Prototypes/Body/Animals/mothroach.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
- type: entity
|
||||
id: BaseMobMothroach
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganAnimalLungs
|
||||
- id: OrganAnimalHeart
|
||||
- id: OrganMothStomach
|
||||
- id: OrganAnimalLiver
|
||||
- id: OrganAnimalKidneys
|
||||
22
Resources/Prototypes/Body/Animals/rat.yml
Normal file
22
Resources/Prototypes/Body/Animals/rat.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
- type: entity
|
||||
parent: OrganAnimalLungs
|
||||
id: OrganRatLungs
|
||||
suffix: Rat
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Rat ]
|
||||
|
||||
- type: entity
|
||||
id: BaseMobRat
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganRatLungs
|
||||
- id: OrganAnimalHeart
|
||||
- id: OrganAnimalStomach
|
||||
- id: OrganAnimalLiver
|
||||
- id: OrganAnimalKidneys
|
||||
30
Resources/Prototypes/Body/Animals/ruminant.yml
Normal file
30
Resources/Prototypes/Body/Animals/ruminant.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
- type: entity
|
||||
parent: [ OrganBaseStomach, OrganAnimalInternal, OrganAnimalMetabolizer ]
|
||||
id: OrganRuminantStomach
|
||||
suffix: Ruminant
|
||||
components:
|
||||
- type: Stomach
|
||||
specialDigestible:
|
||||
tags:
|
||||
- Ruminant
|
||||
- Wheat
|
||||
- BananaPeel
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 80
|
||||
|
||||
- type: entity
|
||||
id: BaseMobRuminant
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganAnimalLungs
|
||||
- id: OrganAnimalHeart
|
||||
- id: OrganRuminantStomach
|
||||
- id: OrganAnimalLiver
|
||||
- id: OrganAnimalKidneys
|
||||
11
Resources/Prototypes/Body/Animals/slimes.yml
Normal file
11
Resources/Prototypes/Body/Animals/slimes.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
- type: entity
|
||||
id: BaseMobSlimes
|
||||
abstract: true
|
||||
components:
|
||||
- type: Body
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
body_organs: !type:AllSelector
|
||||
children:
|
||||
- id: OrganSlimePersonCore
|
||||
- id: OrganSlimePersonLungs
|
||||
@@ -1,162 +0,0 @@
|
||||
- type: entity
|
||||
id: BaseAnimalOrgan
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Organ
|
||||
- type: Edible
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/organs.rsi
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: FlavorProfile
|
||||
flavors:
|
||||
- chicken # everything kinda tastes like chicken
|
||||
- type: Tag
|
||||
tags:
|
||||
- Meat
|
||||
|
||||
- type: entity
|
||||
id: OrganAnimalLungs
|
||||
parent: BaseAnimalOrgan
|
||||
name: animal lungs
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: lung-l
|
||||
- state: lung-r
|
||||
- type: Organ
|
||||
- type: Lung
|
||||
- type: Metabolizer
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Animal ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
Lung:
|
||||
maxVol: 100.0
|
||||
canReact: false
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: lungs
|
||||
|
||||
- type: entity
|
||||
id: OrganAnimalStomach
|
||||
parent: BaseAnimalOrgan
|
||||
name: animal stomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: stomach
|
||||
- type: Organ
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 40
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Stomach
|
||||
- type: Metabolizer
|
||||
maxReagents: 3
|
||||
metabolizerTypes: [ Animal ]
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: stomach
|
||||
|
||||
- type: entity
|
||||
id: OrganMouseStomach
|
||||
parent: OrganAnimalStomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 30
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: stomach
|
||||
|
||||
- type: entity
|
||||
id: OrganAnimalLiver
|
||||
parent: BaseAnimalOrgan
|
||||
name: animal liver
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: liver
|
||||
- type: Organ
|
||||
- type: Metabolizer
|
||||
maxReagents: 1
|
||||
metabolizerTypes: [ Animal ]
|
||||
groups:
|
||||
- id: Alcohol
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: liver
|
||||
|
||||
- type: entity
|
||||
id: OrganAnimalHeart
|
||||
parent: BaseAnimalOrgan
|
||||
name: animal heart
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: heart-on
|
||||
- type: Organ
|
||||
- type: Metabolizer
|
||||
maxReagents: 2
|
||||
metabolizerTypes: [ Animal ]
|
||||
groups:
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: heart
|
||||
|
||||
- type: entity
|
||||
id: OrganAnimalKidneys
|
||||
parent: BaseAnimalOrgan
|
||||
name: animal kidneys
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: kidney-l
|
||||
- state: kidney-r
|
||||
- type: Organ
|
||||
- type: Metabolizer
|
||||
maxReagents: 5
|
||||
metabolizerTypes: [ Animal ]
|
||||
removeEmpty: true
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: kidneys
|
||||
@@ -1,23 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganBloodsuckerStomach
|
||||
parent: OrganAnimalStomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Bloodsucker ]
|
||||
|
||||
- type: entity
|
||||
id: OrganBloodsuckerLiver
|
||||
parent: OrganAnimalLiver
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Bloodsucker ]
|
||||
|
||||
- type: entity
|
||||
id: OrganBloodsuckerHeart
|
||||
parent: OrganAnimalHeart
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Bloodsucker ]
|
||||
@@ -1,21 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganAnimalRuminantStomach
|
||||
parent: OrganAnimalStomach
|
||||
name: ruminant stomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Stomach
|
||||
specialDigestible:
|
||||
tags:
|
||||
- Ruminant
|
||||
- Wheat
|
||||
- BananaPeel
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 80
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
@@ -1,67 +0,0 @@
|
||||
- type: entity
|
||||
id: SentientSlimesCore
|
||||
parent: [BaseItem, OrganHumanBrain]
|
||||
name: sentient slimes core
|
||||
description: "The source of incredible, unending gooeyness."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Slime/organs.rsi
|
||||
state: brain-slime
|
||||
- type: Stomach
|
||||
- type: Metabolizer
|
||||
maxReagents: 3
|
||||
metabolizerTypes: [ Slime ]
|
||||
removeEmpty: true
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
- id: Alcohol
|
||||
rateModifier: 2
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 30.0
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: GreyMatter
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
id: OrganSlimesLungs
|
||||
parent: BaseHumanOrgan
|
||||
name: slimes gas sacs
|
||||
description: "Collects nitrogen, which slime cells use for maintenance."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Slime/organs.rsi
|
||||
layers:
|
||||
- state: lung-l-slime
|
||||
- state: lung-r-slime
|
||||
- type: Lung
|
||||
alert: LowNitrogen
|
||||
- type: Metabolizer
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Slime ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100.0
|
||||
canReact: false
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: Slime
|
||||
Quantity: 5
|
||||
@@ -1,169 +0,0 @@
|
||||
- type: entity
|
||||
id: BaseArachnidOrgan
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/organs.rsi
|
||||
- type: Organ
|
||||
- type: Edible
|
||||
- type: Extractable
|
||||
grindableSolutionName: organ
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
maxVol: 10
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Meat
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidStomach
|
||||
parent: OrganAnimalStomach
|
||||
description: "Gross. This is hard to stomach."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/organs.rsi
|
||||
state: stomach
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: stomach
|
||||
- type: Stomach
|
||||
digestionDelay: 30
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Metabolizer
|
||||
updateInterval: 1.5
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidLungs
|
||||
parent: BaseArachnidOrgan
|
||||
name: lungs
|
||||
description: "Filters oxygen from an atmosphere... just more greedily."
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: lung-l
|
||||
- state: lung-r
|
||||
- type: Lung
|
||||
- type: Metabolizer
|
||||
updateInterval: 1.5
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Human ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100.0
|
||||
canReact: false
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidHeart
|
||||
parent: BaseArachnidOrgan
|
||||
name: heart
|
||||
description: "A disgustingly persistent little biological pump made for spiders."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: heart-on
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: heart
|
||||
- type: Metabolizer
|
||||
updateInterval: 1.5
|
||||
maxReagents: 2
|
||||
metabolizerTypes: [Arachnid]
|
||||
groups:
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidLiver
|
||||
parent: BaseHumanOrgan
|
||||
name: liver
|
||||
description: "Pairing suggestion: chianti and fava beans."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: liver
|
||||
- type: Sprite
|
||||
state: liver
|
||||
- type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol.
|
||||
updateInterval: 1.5
|
||||
maxReagents: 1
|
||||
metabolizerTypes: [Animal]
|
||||
groups:
|
||||
- id: Alcohol
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidKidneys
|
||||
parent: BaseHumanOrgan
|
||||
name: kidneys
|
||||
description: "Filters toxins from the bloodstream."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: kidney-l
|
||||
- state: kidney-r
|
||||
# The kidneys just remove anything that doesn't currently have any metabolisms, as a stopgap.
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: kidneys
|
||||
- type: Metabolizer
|
||||
updateInterval: 1.5
|
||||
maxReagents: 5
|
||||
metabolizerTypes: [Animal]
|
||||
removeEmpty: true
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidEyes
|
||||
parent: BaseArachnidOrgan
|
||||
name: eyes
|
||||
description: "Two was already too many."
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: eyeball-l
|
||||
- state: eyeball-r
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: eyeballs
|
||||
|
||||
- type: entity
|
||||
id: OrganArachnidTongue
|
||||
parent: BaseArachnidOrgan
|
||||
name: tongue
|
||||
description: "A fleshy muscle mostly used for lying."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: tongue
|
||||
@@ -1,203 +0,0 @@
|
||||
- type: entity
|
||||
id: BaseDionaOrgan
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Diona/organs.rsi
|
||||
- type: Organ
|
||||
- type: Edible
|
||||
- type: Extractable
|
||||
grindableSolutionName: organ
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
maxVol: 10
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: Cellulose
|
||||
Quantity: 5
|
||||
- type: FlavorProfile
|
||||
flavors:
|
||||
- people
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaBrain
|
||||
parent: [BaseDionaOrgan, OrganHumanBrain]
|
||||
name: brain
|
||||
description: "The central hub of a diona's pseudo-neurological activity, its root-like tendrils search for its former body."
|
||||
components:
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: brain
|
||||
- type: Sprite
|
||||
state: brain
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
maxVol: 10
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100
|
||||
canReact: False
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: GreyMatter
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaEyes
|
||||
parent: BaseDionaOrgan
|
||||
name: eyes
|
||||
description: "I see you!"
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: eyeball-l
|
||||
- state: eyeball-r
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaStomach
|
||||
parent: BaseDionaOrgan
|
||||
name: stomach
|
||||
description: "The diona's equivalent of a stomach, it reeks of asparagus and vinegar."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: stomach
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: Cellulose
|
||||
Quantity: 5
|
||||
- type: Stomach
|
||||
- type: Metabolizer
|
||||
maxReagents: 6
|
||||
metabolizerTypes: [ Plant ]
|
||||
removeEmpty: true
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
- id: Alcohol
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: stomach
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaLungs
|
||||
parent: BaseDionaOrgan
|
||||
name: lungs
|
||||
description: "A spongy mess of slimy, leaf-like structures. Capable of breathing both carbon dioxide and oxygen."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: lungs
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: lungs
|
||||
- type: Lung
|
||||
- type: Metabolizer
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Plant ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
maxVol: 10
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100
|
||||
canReact: False
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: Cellulose
|
||||
Quantity: 5
|
||||
|
||||
# Organs that turn into nymphs on removal
|
||||
- type: entity
|
||||
id: OrganDionaBrainNymph
|
||||
parent: OrganDionaBrain
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: brain
|
||||
description: "The source of incredible, unending intelligence. Honk."
|
||||
components:
|
||||
- type: Brain
|
||||
- type: Nymph # This will make the organs turn into a nymph when they're removed.
|
||||
entityPrototype: OrganDionaNymphBrain
|
||||
transferMind: true
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaStomachNymph
|
||||
parent: OrganDionaStomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: stomach
|
||||
description: "Gross. This is hard to stomach."
|
||||
components:
|
||||
- type: Nymph
|
||||
entityPrototype: OrganDionaNymphStomach
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaLungsNymph
|
||||
parent: OrganDionaLungs
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: lungs
|
||||
description: "Filters oxygen from an atmosphere, which is then sent into the bloodstream to be used as an electron carrier."
|
||||
components:
|
||||
- type: Nymph
|
||||
entityPrototype: OrganDionaNymphLungs
|
||||
|
||||
# Nymphs that the organs will turn into
|
||||
- type: entity
|
||||
id: OrganDionaNymphBrain
|
||||
parent: MobDionaNymph
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: diona nymph
|
||||
suffix: Brain
|
||||
description: Contains the brain of a formerly fully-formed Diona. Killing this would kill the Diona forever. You monster.
|
||||
components:
|
||||
- type: IsDeadIC
|
||||
- type: Body
|
||||
prototype: AnimalNymphBrain
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaNymphStomach
|
||||
parent: MobDionaNymphAccent
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: diona nymph
|
||||
suffix: Stomach
|
||||
description: Contains the stomach of a formerly fully-formed Diona. It doesn't taste any better for it.
|
||||
components:
|
||||
- type: IsDeadIC
|
||||
- type: Body
|
||||
prototype: AnimalNymphStomach
|
||||
|
||||
- type: entity
|
||||
id: OrganDionaNymphLungs
|
||||
parent: MobDionaNymphAccent
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: diona nymph
|
||||
suffix: Lungs
|
||||
description: Contains the lungs of a formerly fully-formed Diona. Breathtaking.
|
||||
components:
|
||||
- type: IsDeadIC
|
||||
- type: Body
|
||||
prototype: AnimalNymphLungs
|
||||
@@ -1,38 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganDwarfHeart
|
||||
parent: OrganHumanHeart
|
||||
name: dwarf heart
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [Dwarf]
|
||||
|
||||
- type: entity
|
||||
id: OrganDwarfLiver
|
||||
parent: OrganHumanLiver
|
||||
name: dwarf liver
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [Dwarf]
|
||||
|
||||
- type: entity
|
||||
id: OrganDwarfStomach
|
||||
parent: OrganHumanStomach
|
||||
name: dwarf stomach
|
||||
components:
|
||||
- type: Sprite
|
||||
state: stomach
|
||||
- type: Organ
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 75
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Stomach
|
||||
- type: Metabolizer
|
||||
# mm very yummy
|
||||
maxReagents: 5
|
||||
metabolizerTypes: [Dwarf]
|
||||
@@ -1,239 +0,0 @@
|
||||
- type: entity
|
||||
id: BaseHumanOrgan
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/organs.rsi
|
||||
- type: Organ
|
||||
- type: Edible
|
||||
- type: Extractable
|
||||
grindableSolutionName: organ
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: FlavorProfile
|
||||
flavors:
|
||||
- people
|
||||
- type: Tag
|
||||
tags:
|
||||
- Meat
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanBrain
|
||||
parent: BaseHumanOrgan
|
||||
name: brain
|
||||
description: "The source of incredible, unending intelligence. Honk."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: brain
|
||||
- type: Organ
|
||||
- type: Input
|
||||
context: "ghost"
|
||||
- type: Brain
|
||||
- type: InputMover
|
||||
- type: Examiner
|
||||
- type: BlockMovement
|
||||
- type: BadFood
|
||||
- type: Tag
|
||||
tags:
|
||||
- Meat
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: GreyMatter
|
||||
Quantity: 5
|
||||
- type: FlavorProfile
|
||||
flavors:
|
||||
- people
|
||||
- type: FoodSequenceElement
|
||||
entries:
|
||||
Burger: Brain
|
||||
Taco: Brain
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: brain
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanEyes
|
||||
parent: BaseHumanOrgan
|
||||
name: eyes
|
||||
description: "I see you!"
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: eyeball-l
|
||||
- state: eyeball-r
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: eyeballs
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanTongue
|
||||
parent: BaseHumanOrgan
|
||||
name: tongue
|
||||
description: "A fleshy muscle mostly used for lying."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: tongue
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanAppendix
|
||||
parent: BaseHumanOrgan
|
||||
name: appendix
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: appendix
|
||||
- state: appendix-inflamed
|
||||
visible: false
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanEars
|
||||
parent: BaseHumanOrgan
|
||||
name: ears
|
||||
description: "There are three parts to the ear. Inner, middle and outer. Only one of these parts should normally be visible."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: ears
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanLungs
|
||||
parent: BaseHumanOrgan
|
||||
name: lungs
|
||||
description: "Filters oxygen from an atmosphere, which is then sent into the bloodstream to be used as an electron carrier."
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: lung-l
|
||||
- state: lung-r
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: lungs
|
||||
- type: Lung
|
||||
- type: Metabolizer
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Human ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100.0
|
||||
canReact: false
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanHeart
|
||||
parent: BaseHumanOrgan
|
||||
name: heart
|
||||
description: "I feel bad for the heartless bastard who lost this."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: heart-on
|
||||
# The heart 'metabolizes' medicines and poisons that aren't filtered out by other organs.
|
||||
# This is done because these chemicals need to have some effect even if they aren't being filtered out of your body.
|
||||
# You're technically 'immune to poison' without a heart, but.. uhh, you'll have bigger problems on your hands.
|
||||
- type: Metabolizer
|
||||
maxReagents: 2
|
||||
metabolizerTypes: [Human]
|
||||
groups:
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: heart
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanStomach
|
||||
parent: BaseHumanOrgan
|
||||
name: stomach
|
||||
description: "Gross. This is hard to stomach."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: stomach
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: stomach
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Stomach
|
||||
# The stomach metabolizes stuff like foods and drinks.
|
||||
# TODO: Have it work off of the ent's solution container, and move this
|
||||
# to intestines instead.
|
||||
- type: Metabolizer
|
||||
# mm yummy
|
||||
maxReagents: 3
|
||||
metabolizerTypes: [Human]
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanLiver
|
||||
parent: BaseHumanOrgan
|
||||
name: liver
|
||||
description: "Pairing suggestion: chianti and fava beans."
|
||||
components:
|
||||
- type: Sprite
|
||||
state: liver
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: liver
|
||||
- type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol.
|
||||
maxReagents: 1
|
||||
metabolizerTypes: [Human]
|
||||
groups:
|
||||
- id: Alcohol
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanKidneys
|
||||
parent: BaseHumanOrgan
|
||||
name: kidneys
|
||||
description: "Filters toxins from the bloodstream."
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: kidney-l
|
||||
- state: kidney-r
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: kidneys
|
||||
# The kidneys just remove anything that doesn't currently have any metabolisms, as a stopgap.
|
||||
- type: Metabolizer
|
||||
maxReagents: 5
|
||||
metabolizerTypes: [Human]
|
||||
removeEmpty: true
|
||||
@@ -1,27 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganMothStomach
|
||||
parent: [OrganAnimalStomach, OrganHumanStomach]
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Stomach
|
||||
specialDigestible:
|
||||
tags:
|
||||
- ClothMade
|
||||
- Paper
|
||||
- Pill
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Metabolizer
|
||||
maxReagents: 3
|
||||
metabolizerTypes: [ Moth ]
|
||||
removeEmpty: true
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
@@ -1,24 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganRatLungs
|
||||
parent: OrganHumanLungs
|
||||
suffix: "rat"
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Rat ]
|
||||
|
||||
- type: entity
|
||||
id: OrganRatStomach
|
||||
parent: OrganAnimalStomach
|
||||
suffix: "rat"
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Sprite
|
||||
state: stomach
|
||||
@@ -1,23 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganReptilianStomach
|
||||
parent: OrganAnimalStomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Stomach
|
||||
specialDigestible:
|
||||
tags:
|
||||
- Fruit
|
||||
- ReptilianFood
|
||||
- Meat
|
||||
- Pill
|
||||
- Crayon
|
||||
- Paper
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
@@ -1,78 +0,0 @@
|
||||
- type: entity
|
||||
id: SentientSlimeCore
|
||||
parent: [BaseItem, OrganHumanBrain]
|
||||
name: sentient slime core
|
||||
description: "The source of incredible, unending gooeyness."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Slime/organs.rsi
|
||||
state: brain-slime
|
||||
- type: Stomach
|
||||
- type: Metabolizer
|
||||
maxReagents: 6
|
||||
metabolizerTypes: [ Slime ]
|
||||
removeEmpty: true
|
||||
groups:
|
||||
- id: Food
|
||||
- id: Drink
|
||||
- id: Medicine
|
||||
- id: Poison
|
||||
- id: Narcotic
|
||||
- id: Alcohol
|
||||
rateModifier: 2.5
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50.0
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: GreyMatter
|
||||
Quantity: 5
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Slime
|
||||
Quantity: 10
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: brain
|
||||
|
||||
|
||||
- type: entity
|
||||
id: OrganSlimeLungs
|
||||
parent: BaseHumanOrgan
|
||||
name: slime gas sacs
|
||||
description: "Collects nitrogen, which slime cells use for maintenance."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Slime/organs.rsi
|
||||
layers:
|
||||
- state: lung-l-slime
|
||||
- state: lung-r-slime
|
||||
- type: Lung
|
||||
alert: LowNitrogen
|
||||
- type: Metabolizer
|
||||
removeEmpty: true
|
||||
solutionOnBody: false
|
||||
solution: "Lung"
|
||||
metabolizerTypes: [ Slime ]
|
||||
groups:
|
||||
- id: Gas
|
||||
rateModifier: 100.0
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
organ:
|
||||
reagents:
|
||||
- ReagentId: Slime
|
||||
Quantity: 10
|
||||
Lung:
|
||||
maxVol: 100.0
|
||||
canReact: false
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: lungs
|
||||
@@ -1,98 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganVoxLungs
|
||||
parent: OrganHumanLungs
|
||||
description: "The blue, anaerobic lungs of a vox, they intake nitrogen to breathe. Any form of gaseous oxygen is lethally toxic if breathed in."
|
||||
suffix: "vox"
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [ Vox ]
|
||||
- type: Lung
|
||||
alert: LowNitrogen
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: lungs
|
||||
|
||||
- type: entity
|
||||
parent: OrganHumanStomach
|
||||
id: OrganVoxStomach
|
||||
name: stomach
|
||||
description: "A stomach that smells of ammonia."
|
||||
components:
|
||||
- type: Metabolizer #Skreeeee!
|
||||
metabolizerTypes: [Vox]
|
||||
- type: Stomach
|
||||
#Bird vs tags
|
||||
specialDigestible:
|
||||
tags:
|
||||
- Trash
|
||||
isSpecialDigestibleExclusive: false
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
|
||||
- type: entity
|
||||
parent: OrganHumanLiver
|
||||
id: OrganVoxLiver
|
||||
name: liver
|
||||
description: "Smells flammable."
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [Vox]
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
|
||||
- type: entity
|
||||
parent: OrganHumanHeart
|
||||
id: OrganVoxHeart
|
||||
name: heart
|
||||
description: "The strange heart of a vox."
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [Vox]
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
|
||||
- type: entity
|
||||
parent: OrganHumanKidneys
|
||||
id: OrganVoxKidneys
|
||||
name: kidney
|
||||
description: "Smells flammable."
|
||||
components:
|
||||
- type: Metabolizer
|
||||
metabolizerTypes: [Vox]
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
|
||||
- type: entity
|
||||
id: OrganVoxEyes
|
||||
parent: OrganHumanEyes
|
||||
name: eyes
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
- type: Item
|
||||
size: Small
|
||||
heldPrefix: eyeballs
|
||||
|
||||
- type: entity
|
||||
id: OrganVoxTongueA
|
||||
parent: OrganHumanTongue
|
||||
name: tongue
|
||||
description: "A fleshy muscle mostly used for screaming."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
- type: Item
|
||||
size: Small
|
||||
|
||||
- type: entity
|
||||
id: OrganVoxTongueB
|
||||
parent: OrganHumanTongue
|
||||
name: tongue
|
||||
description: "A fleshy muscle mostly used for screaming."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Vox/organs.rsi
|
||||
- type: Item
|
||||
size: Small
|
||||
@@ -1,14 +0,0 @@
|
||||
- type: entity
|
||||
id: OrganVulpkaninStomach
|
||||
parent: OrganAnimalStomach
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
stomach:
|
||||
maxVol: 50
|
||||
food:
|
||||
maxVol: 5
|
||||
reagents:
|
||||
- ReagentId: UncookedAnimalProteins
|
||||
Quantity: 5
|
||||
@@ -1,120 +0,0 @@
|
||||
# Just copypasta of some human basic body parts for interaction,
|
||||
# only differences for now is that limbs work in pairs,
|
||||
# they are unextractable and can't be spawned (no surgery on Animals!?).
|
||||
|
||||
- type: entity
|
||||
id: PartAnimal
|
||||
parent: BaseItem
|
||||
name: "animal body part"
|
||||
abstract: true
|
||||
components:
|
||||
# yes these sprites dont make sense i dont care its better than them being invisible
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: BodyPart
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
bodypart: !type:Container
|
||||
ents: []
|
||||
- type: StaticPrice
|
||||
price: 50
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 3
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: HandsAnimal
|
||||
name: animal hands
|
||||
parent: PartAnimal
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: l_hand
|
||||
- state: r_hand
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: LegsAnimal
|
||||
name: animal legs
|
||||
parent: PartAnimal
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: l_leg
|
||||
- state: r_leg
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: FeetAnimal
|
||||
name: animal feet
|
||||
parent: PartAnimal
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: r_foot
|
||||
- state: l_foot
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
|
||||
- type: entity
|
||||
id: TorsoAnimal
|
||||
name: animal torso
|
||||
parent: PartAnimal
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: torso_m
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 10
|
||||
- ReagentId: Blood
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
parent: PartAnimal
|
||||
id: LeftHandSmartCorgi
|
||||
name: corgi hand
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: l_hand
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
parent: PartAnimal
|
||||
id: RightHandSmartCorgi
|
||||
name: corgi hand
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: r_hand
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Right
|
||||
@@ -1,121 +0,0 @@
|
||||
# TODO: Add descriptions (many)
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartArachnid
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "arachnid body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 3
|
||||
- ReagentId: CopperBlood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: TorsoArachnid
|
||||
name: "arachnid torso"
|
||||
parent: [PartArachnid, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 10
|
||||
- ReagentId: CopperBlood
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
id: HeadArachnid
|
||||
name: "arachnid head"
|
||||
parent: [PartArachnid, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "head_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 5
|
||||
- ReagentId: CopperBlood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: LeftArmArachnid
|
||||
name: "left arachnid arm"
|
||||
parent: [PartArachnid, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmArachnid
|
||||
name: "right arachnid arm"
|
||||
parent: [PartArachnid, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandArachnid
|
||||
name: "left arachnid hand"
|
||||
parent: [PartArachnid, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandArachnid
|
||||
name: "right arachnid hand"
|
||||
parent: [PartArachnid, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegArachnid
|
||||
name: "left arachnid leg"
|
||||
parent: [PartArachnid, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "l_leg"
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: RightLegArachnid
|
||||
name: "right arachnid leg"
|
||||
parent: [PartArachnid, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "r_leg"
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: LeftFootArachnid
|
||||
name: "left arachnid foot"
|
||||
parent: [PartArachnid, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "l_foot"
|
||||
|
||||
- type: entity
|
||||
id: RightFootArachnid
|
||||
name: "right arachnid foot"
|
||||
parent: [PartArachnid, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Arachnid/parts.rsi
|
||||
state: "r_foot"
|
||||
@@ -1,123 +0,0 @@
|
||||
# TODO: Add descriptions (many)
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: BasePart
|
||||
parent: BaseItem
|
||||
name: "body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: BodyPart
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
bodypart: !type:Container
|
||||
ents: []
|
||||
- type: StaticPrice
|
||||
price: 100
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
|
||||
- type: entity
|
||||
id: BaseTorso
|
||||
name: "torso"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
|
||||
- type: entity
|
||||
id: BaseHead
|
||||
name: "head"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Head
|
||||
vital: true
|
||||
- type: Input
|
||||
context: "ghost"
|
||||
|
||||
- type: entity
|
||||
id: BaseLeftArm
|
||||
name: "left arm"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Arm
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: BaseRightArm
|
||||
name: "right arm"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Arm
|
||||
symmetry: Right
|
||||
|
||||
- type: entity
|
||||
id: BaseLeftHand
|
||||
name: "left hand"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: BaseRightHand
|
||||
name: "right hand"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Right
|
||||
|
||||
- type: entity
|
||||
id: BaseLeftLeg
|
||||
name: "left leg"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Left
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: BaseRightLeg
|
||||
name: "right leg"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Right
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: BaseLeftFoot
|
||||
name: "left foot"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: BaseRightFoot
|
||||
name: "right foot"
|
||||
parent: BasePart
|
||||
abstract: true
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Right
|
||||
@@ -1,97 +0,0 @@
|
||||
- type: entity
|
||||
id: PartDiona
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "diona body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Diona/parts.rsi
|
||||
|
||||
- type: entity
|
||||
id: TorsoDiona
|
||||
name: "diona torso"
|
||||
parent: [PartDiona, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "torso_m"
|
||||
|
||||
- type: entity
|
||||
id: HeadDiona
|
||||
name: "diona head"
|
||||
parent: [PartDiona, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "head_m"
|
||||
|
||||
- type: entity
|
||||
id: LeftArmDiona
|
||||
name: "left diona arm"
|
||||
parent: [PartDiona, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmDiona
|
||||
name: "right diona arm"
|
||||
parent: [PartDiona, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandDiona
|
||||
name: "left diona hand"
|
||||
parent: [PartDiona, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandDiona
|
||||
name: "right diona hand"
|
||||
parent: [PartDiona, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegDiona
|
||||
name: "left diona leg"
|
||||
parent: [PartDiona, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "l_leg"
|
||||
|
||||
- type: entity
|
||||
id: RightLegDiona
|
||||
name: "right diona leg"
|
||||
parent: [PartDiona, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "r_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Right
|
||||
|
||||
- type: entity
|
||||
id: LeftFootDiona
|
||||
name: "left diona foot"
|
||||
parent: [PartDiona, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "l_foot"
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: RightFootDiona
|
||||
name: "right diona foot"
|
||||
parent: [PartDiona, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
state: "r_foot"
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Right
|
||||
@@ -1,117 +0,0 @@
|
||||
- type: entity
|
||||
id: PartGingerbread
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "gingerbread body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 3
|
||||
- ReagentId: Sugar
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: TorsoGingerbread
|
||||
name: "gingerbread torso"
|
||||
parent: [PartGingerbread, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 10
|
||||
- ReagentId: Sugar
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
id: HeadGingerbread
|
||||
name: "gingerbread head"
|
||||
parent: [PartGingerbread, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "head_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 5
|
||||
- ReagentId: Sugar
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: LeftArmGingerbread
|
||||
name: "left gingerbread arm"
|
||||
parent: [PartGingerbread, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmGingerbread
|
||||
name: "right gingerbread arm"
|
||||
parent: [PartGingerbread, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandGingerbread
|
||||
name: "left gingerbread hand"
|
||||
parent: [PartGingerbread, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandGingerbread
|
||||
name: "right gingerbread hand"
|
||||
parent: [PartGingerbread, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegGingerbread
|
||||
name: "left gingerbread leg"
|
||||
parent: [PartGingerbread, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "l_leg"
|
||||
|
||||
- type: entity
|
||||
id: RightLegGingerbread
|
||||
name: "right gingerbread leg"
|
||||
parent: [PartGingerbread, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "r_leg"
|
||||
|
||||
- type: entity
|
||||
id: LeftFootGingerbread
|
||||
name: "left gingerbread foot"
|
||||
parent: [PartGingerbread, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "l_foot"
|
||||
|
||||
- type: entity
|
||||
id: RightFootGingerbread
|
||||
name: "right gingerbread foot"
|
||||
parent: [PartGingerbread, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Gingerbread/parts.rsi
|
||||
state: "r_foot"
|
||||
@@ -1,119 +0,0 @@
|
||||
# TODO: Add descriptions (many)
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartHuman
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "human body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 3
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: TorsoHuman
|
||||
name: "human torso"
|
||||
parent: [PartHuman, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 10
|
||||
- ReagentId: Blood
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
id: HeadHuman
|
||||
name: "human head"
|
||||
parent: [PartHuman, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "head_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 5
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: LeftArmHuman
|
||||
name: "left human arm"
|
||||
parent: [PartHuman, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmHuman
|
||||
name: "right human arm"
|
||||
parent: [PartHuman, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandHuman
|
||||
name: "left human hand"
|
||||
parent: [PartHuman, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandHuman
|
||||
name: "right human hand"
|
||||
parent: [PartHuman, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegHuman
|
||||
name: "left human leg"
|
||||
parent: [PartHuman, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "l_leg"
|
||||
|
||||
- type: entity
|
||||
id: RightLegHuman
|
||||
name: "right human leg"
|
||||
parent: [PartHuman, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "r_leg"
|
||||
|
||||
- type: entity
|
||||
id: LeftFootHuman
|
||||
name: "left human foot"
|
||||
parent: [PartHuman, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "l_foot"
|
||||
|
||||
- type: entity
|
||||
id: RightFootHuman
|
||||
name: "right human foot"
|
||||
parent: [PartHuman, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Human/parts.rsi
|
||||
state: "r_foot"
|
||||
@@ -1,120 +0,0 @@
|
||||
# TODO: Add descriptions (many)
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartMoth
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "moth body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 3
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: TorsoMoth
|
||||
name: "moth torso"
|
||||
parent: [PartMoth, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 10
|
||||
- ReagentId: Blood
|
||||
Quantity: 20
|
||||
|
||||
|
||||
- type: entity
|
||||
id: HeadMoth
|
||||
name: "moth head"
|
||||
parent: [PartMoth, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "head_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 5
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: LeftArmMoth
|
||||
name: "left moth arm"
|
||||
parent: [PartMoth, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmMoth
|
||||
name: "right moth arm"
|
||||
parent: [PartMoth, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandMoth
|
||||
name: "left moth hand"
|
||||
parent: [PartMoth, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandMoth
|
||||
name: "right moth hand"
|
||||
parent: [PartMoth, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegMoth
|
||||
name: "left moth leg"
|
||||
parent: [PartMoth, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "l_leg"
|
||||
|
||||
- type: entity
|
||||
id: RightLegMoth
|
||||
name: "right moth leg"
|
||||
parent: [PartMoth, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "r_leg"
|
||||
|
||||
- type: entity
|
||||
id: LeftFootMoth
|
||||
name: "left moth foot"
|
||||
parent: [PartMoth, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "l_foot"
|
||||
|
||||
- type: entity
|
||||
id: RightFootMoth
|
||||
name: "right moth foot"
|
||||
parent: [PartMoth, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Moth/parts.rsi
|
||||
state: "r_foot"
|
||||
@@ -1,20 +0,0 @@
|
||||
# Just copypasta of some animal basic body parts for interaction,
|
||||
# It's basically as animals except a different torso with different organs
|
||||
- type: entity
|
||||
id: TorsoRat
|
||||
name: "animal torso"
|
||||
parent: PartAnimal
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
# TODO get a proper rat king & servant torso sprite.
|
||||
# currently their torso is just a small dead rat....
|
||||
- type: Sprite
|
||||
sprite: Mobs/Animals/mouse.rsi
|
||||
state: splat-0
|
||||
@@ -1,119 +0,0 @@
|
||||
# TODO: Add descriptions (many)
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartReptilian
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "reptilian body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 3
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: TorsoReptilian
|
||||
name: "reptilian torso"
|
||||
parent: [PartReptilian, BaseTorso]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 10
|
||||
- ReagentId: Blood
|
||||
Quantity: 20
|
||||
|
||||
- type: entity
|
||||
id: HeadReptilian
|
||||
name: "reptilian head"
|
||||
parent: [PartReptilian, BaseHead]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "head_m"
|
||||
- type: Extractable
|
||||
juiceSolution:
|
||||
reagents:
|
||||
- ReagentId: Fat
|
||||
Quantity: 5
|
||||
- ReagentId: Blood
|
||||
Quantity: 10
|
||||
|
||||
- type: entity
|
||||
id: LeftArmReptilian
|
||||
name: "left reptilian arm"
|
||||
parent: [PartReptilian, BaseLeftArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "l_arm"
|
||||
|
||||
- type: entity
|
||||
id: RightArmReptilian
|
||||
name: "right reptilian arm"
|
||||
parent: [PartReptilian, BaseRightArm]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "r_arm"
|
||||
|
||||
- type: entity
|
||||
id: LeftHandReptilian
|
||||
name: "left reptilian hand"
|
||||
parent: [PartReptilian, BaseLeftHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "l_hand"
|
||||
|
||||
- type: entity
|
||||
id: RightHandReptilian
|
||||
name: "right reptilian hand"
|
||||
parent: [PartReptilian, BaseRightHand]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "r_hand"
|
||||
|
||||
- type: entity
|
||||
id: LeftLegReptilian
|
||||
name: "left reptilian leg"
|
||||
parent: [PartReptilian, BaseLeftLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "l_leg"
|
||||
|
||||
- type: entity
|
||||
id: RightLegReptilian
|
||||
name: "right reptilian leg"
|
||||
parent: [PartReptilian, BaseRightLeg]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "r_leg"
|
||||
|
||||
- type: entity
|
||||
id: LeftFootReptilian
|
||||
name: "left reptilian foot"
|
||||
parent: [PartReptilian, BaseLeftFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "l_foot"
|
||||
|
||||
- type: entity
|
||||
id: RightFootReptilian
|
||||
name: "right reptilian foot"
|
||||
parent: [PartReptilian, BaseRightFoot]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Reptilian/parts.rsi
|
||||
state: "r_foot"
|
||||
@@ -1,144 +0,0 @@
|
||||
- type: entity
|
||||
id: PartSilicon
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Robotics/cyborg_parts.rsi
|
||||
- type: Icon
|
||||
sprite: Objects/Specific/Robotics/cyborg_parts.rsi
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: BodyPart
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
bodypart: !type:Container
|
||||
ents: []
|
||||
- type: StaticPrice
|
||||
price: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- type: PhysicalComposition
|
||||
materialComposition:
|
||||
Steel: 25
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- Cyborgs
|
||||
- Robotics
|
||||
|
||||
- type: entity
|
||||
id: LeftArmBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg left arm
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
- type: Sprite
|
||||
state: borg_l_arm
|
||||
- type: Icon
|
||||
state: borg_l_arm
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgArm
|
||||
- BorgLArm
|
||||
- type: Item
|
||||
heldPrefix: borg-arm
|
||||
|
||||
- type: entity
|
||||
id: RightArmBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg right arm
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Right
|
||||
- type: Sprite
|
||||
state: borg_r_arm
|
||||
- type: Icon
|
||||
state: borg_r_arm
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgArm
|
||||
- BorgRArm
|
||||
- type: Item
|
||||
heldPrefix: borg-arm
|
||||
|
||||
- type: entity
|
||||
id: LeftLegBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg left leg
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Left
|
||||
- type: Sprite
|
||||
state: borg_l_leg
|
||||
- type: Icon
|
||||
state: borg_l_leg
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgLeg
|
||||
- BorgLLeg
|
||||
- type: Item
|
||||
heldPrefix: borg-leg
|
||||
|
||||
- type: entity
|
||||
id: RightLegBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg right leg
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Right
|
||||
- type: Sprite
|
||||
state: borg_r_leg
|
||||
- type: Icon
|
||||
state: borg_r_leg
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgLeg
|
||||
- BorgRLeg
|
||||
- type: Item
|
||||
heldPrefix: borg-leg
|
||||
|
||||
- type: entity
|
||||
id: LightHeadBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg head
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Head
|
||||
- type: Sprite
|
||||
state: borg_head
|
||||
- type: Icon
|
||||
state: borg_head
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgHead
|
||||
- type: Item
|
||||
heldPrefix: borg-head
|
||||
|
||||
- type: entity
|
||||
id: TorsoBorg
|
||||
parent: PartSilicon
|
||||
name: cyborg torso
|
||||
components:
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
- type: Sprite
|
||||
state: borg_chest
|
||||
- type: Icon
|
||||
state: borg_chest
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
- BorgTorso
|
||||
- type: Item
|
||||
heldPrefix: borg-chest
|
||||
@@ -1,196 +0,0 @@
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartSkeleton
|
||||
parent: BaseItem
|
||||
name: "skeleton body part"
|
||||
abstract: true
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: BodyPart
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
bodypart: !type:Container
|
||||
ents: []
|
||||
- type: StaticPrice
|
||||
price: 20
|
||||
- type: Tag
|
||||
tags:
|
||||
- Trash
|
||||
|
||||
- type: entity
|
||||
id: TorsoSkeleton
|
||||
name: "skeleton torso"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "torso_m"
|
||||
- type: BodyPart
|
||||
partType: Torso
|
||||
|
||||
- type: entity
|
||||
id: HeadSkeleton
|
||||
name: "skull"
|
||||
description: Alas poor Yorick...
|
||||
parent: [ PartSkeleton, BaseMob ]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "skull_icon"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "skull_icon"
|
||||
- type: BodyPart
|
||||
partType: Head
|
||||
- type: BlockMovement
|
||||
- type: Input
|
||||
context: "human"
|
||||
- type: Speech
|
||||
speechVerb: Skeleton
|
||||
speechSounds: Alto
|
||||
- type: SkeletonAccent
|
||||
- type: Actions
|
||||
- type: Vocal
|
||||
sounds:
|
||||
Male: Skeleton
|
||||
Female: Skeleton
|
||||
Unsexed: Skeleton
|
||||
- type: Emoting
|
||||
- type: Grammar
|
||||
attributes:
|
||||
proper: true
|
||||
- type: Examiner
|
||||
- type: DoAfter
|
||||
- type: MobState
|
||||
allowedStates:
|
||||
- Alive
|
||||
- type: Tag
|
||||
tags:
|
||||
- MindTransferTarget
|
||||
- Head
|
||||
|
||||
- type: entity
|
||||
id: LeftArmSkeleton
|
||||
name: "left skeleton arm"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_arm"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_arm"
|
||||
- type: BodyPart
|
||||
partType: Arm
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: RightArmSkeleton
|
||||
name: "right skeleton arm"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_arm"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_arm"
|
||||
- type: BodyPart
|
||||
partType: Arm
|
||||
symmetry: Right
|
||||
|
||||
- type: entity
|
||||
id: LeftHandSkeleton
|
||||
name: "left skeleton hand"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_hand"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_hand"
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: RightHandSkeleton
|
||||
name: "right skeleton hand"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_hand"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_hand"
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
symmetry: Right
|
||||
|
||||
- type: entity
|
||||
id: LeftLegSkeleton
|
||||
name: "left skeleton leg"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_leg"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Left
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: RightLegSkeleton
|
||||
name: "right skeleton leg"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_leg"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
symmetry: Right
|
||||
- type: MovementBodyPart
|
||||
|
||||
- type: entity
|
||||
id: LeftFootSkeleton
|
||||
name: "left skeleton foot"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_foot"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "l_foot"
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Left
|
||||
|
||||
- type: entity
|
||||
id: RightFootSkeleton
|
||||
name: "right skeleton foot"
|
||||
parent: PartSkeleton
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_foot"
|
||||
- type: Icon
|
||||
sprite: Mobs/Species/Skeleton/parts.rsi
|
||||
state: "r_foot"
|
||||
- type: BodyPart
|
||||
partType: Foot
|
||||
symmetry: Right
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user