* 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:
pathetic meowmeow
2026-01-16 19:39:35 -05:00
committed by GitHub
parent 3cd407e7a6
commit d0c2734dad
198 changed files with 3861 additions and 8621 deletions

View File

@@ -1,7 +0,0 @@
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
public sealed class BodySystem : SharedBodySystem
{
}

View File

@@ -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...

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
{

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

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

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

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Body;
[RegisterComponent, NetworkedComponent]
[Access(typeof(GibbableOrganSystem))]
public sealed partial class GibbableOrganComponent : Component;

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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!

View File

@@ -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:

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

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

View File

@@ -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 ]

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

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

View File

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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

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

View File

@@ -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"

View File

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

View File

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