From f323fb76442a8cc4c9c66b7631cc37114f3dc8e7 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sun, 23 Oct 2022 00:46:28 +0200 Subject: [PATCH] ECS and cleanup body system, merge body templates and presets into body prototypes (#11991) Co-authored-by: Jezithyr --- .../Body/Components/BodyComponent.cs | 21 - .../Body/Components/BodyPartComponent.cs | 11 - Content.Client/Body/Systems/BodySystem.cs | 7 + Content.Client/Body/UI/BodyScannerDisplay.cs | 70 +-- .../Commands/HideMechanismsCommand.cs | 12 +- .../Commands/ShowMechanismsCommand.cs | 8 +- .../Tests/Body/LegTest.cs | 21 +- .../Tests/Body/LungTest.cs | 17 +- .../Tests/Body/SaveLoadReparentTest.cs | 148 ++++++ .../Tests/Buckle/BuckleTest.cs | 32 +- .../Tests/Disposal/DisposalUnitTest.cs | 13 +- .../Components/ActionBlocking/HandCuffTest.cs | 20 +- .../Commands/AddBodyPartCommand.cs | 21 +- .../Commands/AddMechanismCommand.cs | 22 +- .../Commands/RemoveBodyPartCommand.cs | 13 +- .../Commands/RemoveMechanismCommand.cs | 13 +- .../Systems/AdminVerbSystem.Smites.cs | 39 +- Content.Server/Bed/BedSystem.cs | 26 +- .../Body/Commands/AddHandCommand.cs | 24 +- .../Body/Commands/AttachBodyPartCommand.cs | 43 +- .../Body/Commands/DestroyMechanismCommand.cs | 12 +- .../Body/Commands/RemoveHandCommand.cs | 11 +- .../Body/Components/BeingGibbedEvent.cs | 11 + .../Body/Components/BodyComponent.cs | 162 ------- .../Body/Components/BodyPartComponent.cs | 59 --- .../Body/Components/BodyScannerComponent.cs | 2 +- Content.Server/Body/Systems/BodySystem.cs | 225 +++++---- Content.Server/Body/Systems/BrainSystem.cs | 16 +- .../Body/Systems/MetabolizerSystem.cs | 53 +- .../Body/Systems/RespiratorSystem.cs | 17 +- Content.Server/Body/Systems/StomachSystem.cs | 48 +- Content.Server/Cargo/Systems/PricingSystem.cs | 11 +- Content.Server/Climbing/ClimbSystem.cs | 12 +- .../Destructible/DestructibleSystem.cs | 8 +- .../Thresholds/Behaviors/GibBehavior.cs | 4 +- .../Components/DisposalHolderComponent.cs | 3 +- Content.Server/Drone/DroneSystem.cs | 36 +- .../ImmovableRod/ImmovableRodSystem.cs | 8 +- .../Kitchen/EntitySystems/MicrowaveSystem.cs | 42 +- .../Kitchen/EntitySystems/SharpSystem.cs | 8 +- Content.Server/Magic/MagicSystem.cs | 10 +- Content.Server/Medical/VomitSystem.cs | 18 +- .../EntityStorageLayingDownOverrideSystem.cs | 9 +- Content.Server/Morgue/MorgueSystem.cs | 8 +- .../Nutrition/EntitySystems/DrinkSystem.cs | 12 +- .../Nutrition/EntitySystems/FoodSystem.cs | 16 +- Content.Server/Recycling/RecyclerSystem.cs | 12 +- .../Salvage/SalvageMobRestrictionsSystem.cs | 9 +- .../EntitySystems/EntityStorageSystem.cs | 2 +- Content.Server/Toilet/ToiletSystem.cs | 6 +- .../Body/Components/BodyComponent.cs | 36 ++ .../Body/Components/BodyComponentState.cs | 18 + .../Body/Components/MechanismComponent.cs | 78 --- .../Body/Components/SharedBodyComponent.cs | 457 ------------------ .../Components/SharedBodyPartComponent.cs | 345 ------------- .../Body/Events/MechanismBodyEvents.cs | 32 +- Content.Shared/Body/Organ/OrganComponent.cs | 17 + .../Body/Organ/OrganComponentState.cs | 16 + Content.Shared/Body/Organ/OrganSlot.cs | 20 + .../Body/Part/BodyPartCompatibility.cs | 18 - Content.Shared/Body/Part/BodyPartComponent.cs | 44 ++ .../Body/Part/BodyPartComponentState.cs | 34 ++ Content.Shared/Body/Part/BodyPartSlot.cs | 115 +---- Content.Shared/Body/Part/BodyPartSymmetry.cs | 2 +- Content.Shared/Body/Part/BodyPartType.cs | 2 +- Content.Shared/Body/Part/IBodyPartAdded.cs | 9 +- Content.Shared/Body/Part/IBodyPartRemoved.cs | 8 +- .../Body/Prototypes/BodyPresetPrototype.cs | 27 -- .../Body/Prototypes/BodyPrototype.cs | 54 +++ .../Prototypes/BodyPrototypeSerializer.cs | 191 ++++++++ .../Body/Prototypes/BodyTemplatePrototype.cs | 86 ---- .../Body/Systems/SharedBodySystem.Body.cs | 213 ++++++++ .../Body/Systems/SharedBodySystem.Organs.cs | 230 +++++++++ .../Body/Systems/SharedBodySystem.Parts.cs | 359 ++++++++++++++ .../Body/Systems/SharedBodySystem.cs | 26 + .../EntityCoordinatesExtensions.cs | 9 +- .../Disposal/SharedDisposalUnitSystem.cs | 4 +- .../Humanoid/HumanoidVisualLayersExtension.cs | 6 +- .../SharedMedicalScannerComponent.cs | 2 +- .../Locale/en-US/body/behavior/reassemble.ftl | 4 - .../body/components/bodypart-component.ftl | 6 - .../body/components/mechanism-component.ftl | 6 - .../biological-surgery-data-component.ftl | 11 - .../components/surgery-tool-component.ftl | 2 - .../Locale/en-US/body/surgery/surgery.ftl | 4 - .../Prototypes/Body/Mechanisms/tests.yml | 23 - Resources/Prototypes/Body/Organs/animal.yml | 90 ++++ .../Body/{Mechanisms => Organs}/human.yml | 42 +- .../Body/{Mechanisms => Organs}/rat.yml | 0 .../Body/{Mechanisms => Organs}/reptilian.yml | 0 .../Body/{Mechanisms => Organs}/slime.yml | 8 +- .../Body/{Mechanisms => Organs}/vox.yml | 0 Resources/Prototypes/Body/Parts/animal.yml | 114 ----- Resources/Prototypes/Body/Parts/human.yml | 49 -- Resources/Prototypes/Body/Parts/rat.yml | 8 - Resources/Prototypes/Body/Parts/reptilian.yml | 49 -- Resources/Prototypes/Body/Parts/silicon.yml | 4 - Resources/Prototypes/Body/Parts/skeleton.yml | 22 +- Resources/Prototypes/Body/Parts/slime.yml | 41 -- Resources/Prototypes/Body/Parts/vox.yml | 50 -- Resources/Prototypes/Body/Presets/animal.yml | 7 - Resources/Prototypes/Body/Presets/bot.yml | 5 - Resources/Prototypes/Body/Presets/drone.yml | 10 - Resources/Prototypes/Body/Presets/human.yml | 14 - Resources/Prototypes/Body/Presets/primate.yml | 8 - Resources/Prototypes/Body/Presets/rat.yml | 7 - .../Prototypes/Body/Presets/reptilian.yml | 14 - .../Prototypes/Body/Presets/skeleton.yml | 14 - Resources/Prototypes/Body/Presets/slime.yml | 14 - Resources/Prototypes/Body/Presets/vox.yml | 14 - .../Prototypes/Body/Prototypes/a_ghost.yml | 22 + .../Prototypes/Body/Prototypes/animal.yml | 21 + Resources/Prototypes/Body/Prototypes/bot.yml | 7 + .../Prototypes/Body/Prototypes/drone.yml | 27 ++ .../Prototypes/Body/Prototypes/human.yml | 49 ++ .../Prototypes/Body/Prototypes/primate.yml | 24 + Resources/Prototypes/Body/Prototypes/rat.yml | 21 + .../Prototypes/Body/Prototypes/reptilian.yml | 49 ++ .../Prototypes/Body/Prototypes/skeleton.yml | 40 ++ .../Prototypes/Body/Prototypes/slime.yml | 43 ++ Resources/Prototypes/Body/Prototypes/vox.yml | 49 ++ .../Prototypes/Body/Templates/aghost.yml | 14 - .../Prototypes/Body/Templates/animal.yml | 14 - Resources/Prototypes/Body/Templates/bot.yml | 6 - Resources/Prototypes/Body/Templates/drone.yml | 11 - .../Prototypes/Body/Templates/humanoid.yml | 31 -- .../Prototypes/Body/Templates/primate.yml | 16 - .../Prototypes/Body/Templates/quadrupedal.yml | 31 -- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 3 +- .../Entities/Mobs/NPCs/regalrat.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 3 +- .../Entities/Mobs/NPCs/simplemob.yml | 3 +- .../Entities/Mobs/Player/admin_ghost.yml | 3 +- .../Entities/Mobs/Player/silicon.yml | 3 +- .../Prototypes/Entities/Mobs/Species/base.yml | 6 +- .../Entities/Mobs/Species/dwarf.yml | 3 +- .../Entities/Mobs/Species/reptilian.yml | 3 +- .../Entities/Mobs/Species/skeleton.yml | 3 +- .../Entities/Mobs/Species/slime.yml | 3 +- .../Prototypes/Entities/Mobs/Species/vox.yml | 6 +- 140 files changed, 2478 insertions(+), 2571 deletions(-) delete mode 100644 Content.Client/Body/Components/BodyComponent.cs delete mode 100644 Content.Client/Body/Components/BodyPartComponent.cs create mode 100644 Content.Client/Body/Systems/BodySystem.cs create mode 100644 Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs create mode 100644 Content.Server/Body/Components/BeingGibbedEvent.cs delete mode 100644 Content.Server/Body/Components/BodyComponent.cs delete mode 100644 Content.Server/Body/Components/BodyPartComponent.cs create mode 100644 Content.Shared/Body/Components/BodyComponent.cs create mode 100644 Content.Shared/Body/Components/BodyComponentState.cs delete mode 100644 Content.Shared/Body/Components/MechanismComponent.cs delete mode 100644 Content.Shared/Body/Components/SharedBodyComponent.cs delete mode 100644 Content.Shared/Body/Components/SharedBodyPartComponent.cs create mode 100644 Content.Shared/Body/Organ/OrganComponent.cs create mode 100644 Content.Shared/Body/Organ/OrganComponentState.cs create mode 100644 Content.Shared/Body/Organ/OrganSlot.cs delete mode 100644 Content.Shared/Body/Part/BodyPartCompatibility.cs create mode 100644 Content.Shared/Body/Part/BodyPartComponent.cs create mode 100644 Content.Shared/Body/Part/BodyPartComponentState.cs delete mode 100644 Content.Shared/Body/Prototypes/BodyPresetPrototype.cs create mode 100644 Content.Shared/Body/Prototypes/BodyPrototype.cs create mode 100644 Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs delete mode 100644 Content.Shared/Body/Prototypes/BodyTemplatePrototype.cs create mode 100644 Content.Shared/Body/Systems/SharedBodySystem.Body.cs create mode 100644 Content.Shared/Body/Systems/SharedBodySystem.Organs.cs create mode 100644 Content.Shared/Body/Systems/SharedBodySystem.Parts.cs create mode 100644 Content.Shared/Body/Systems/SharedBodySystem.cs delete mode 100644 Resources/Locale/en-US/body/behavior/reassemble.ftl delete mode 100644 Resources/Locale/en-US/body/components/bodypart-component.ftl delete mode 100644 Resources/Locale/en-US/body/components/mechanism-component.ftl delete mode 100644 Resources/Locale/en-US/body/surgery/biological-surgery-data-component.ftl delete mode 100644 Resources/Locale/en-US/body/surgery/components/surgery-tool-component.ftl delete mode 100644 Resources/Locale/en-US/body/surgery/surgery.ftl delete mode 100644 Resources/Prototypes/Body/Mechanisms/tests.yml create mode 100644 Resources/Prototypes/Body/Organs/animal.yml rename Resources/Prototypes/Body/{Mechanisms => Organs}/human.yml (86%) rename Resources/Prototypes/Body/{Mechanisms => Organs}/rat.yml (100%) rename Resources/Prototypes/Body/{Mechanisms => Organs}/reptilian.yml (100%) rename Resources/Prototypes/Body/{Mechanisms => Organs}/slime.yml (88%) rename Resources/Prototypes/Body/{Mechanisms => Organs}/vox.yml (100%) delete mode 100644 Resources/Prototypes/Body/Presets/animal.yml delete mode 100644 Resources/Prototypes/Body/Presets/bot.yml delete mode 100644 Resources/Prototypes/Body/Presets/drone.yml delete mode 100644 Resources/Prototypes/Body/Presets/human.yml delete mode 100644 Resources/Prototypes/Body/Presets/primate.yml delete mode 100644 Resources/Prototypes/Body/Presets/rat.yml delete mode 100644 Resources/Prototypes/Body/Presets/reptilian.yml delete mode 100644 Resources/Prototypes/Body/Presets/skeleton.yml delete mode 100644 Resources/Prototypes/Body/Presets/slime.yml delete mode 100644 Resources/Prototypes/Body/Presets/vox.yml create mode 100644 Resources/Prototypes/Body/Prototypes/a_ghost.yml create mode 100644 Resources/Prototypes/Body/Prototypes/animal.yml create mode 100644 Resources/Prototypes/Body/Prototypes/bot.yml create mode 100644 Resources/Prototypes/Body/Prototypes/drone.yml create mode 100644 Resources/Prototypes/Body/Prototypes/human.yml create mode 100644 Resources/Prototypes/Body/Prototypes/primate.yml create mode 100644 Resources/Prototypes/Body/Prototypes/rat.yml create mode 100644 Resources/Prototypes/Body/Prototypes/reptilian.yml create mode 100644 Resources/Prototypes/Body/Prototypes/skeleton.yml create mode 100644 Resources/Prototypes/Body/Prototypes/slime.yml create mode 100644 Resources/Prototypes/Body/Prototypes/vox.yml delete mode 100644 Resources/Prototypes/Body/Templates/aghost.yml delete mode 100644 Resources/Prototypes/Body/Templates/animal.yml delete mode 100644 Resources/Prototypes/Body/Templates/bot.yml delete mode 100644 Resources/Prototypes/Body/Templates/drone.yml delete mode 100644 Resources/Prototypes/Body/Templates/humanoid.yml delete mode 100644 Resources/Prototypes/Body/Templates/primate.yml delete mode 100644 Resources/Prototypes/Body/Templates/quadrupedal.yml diff --git a/Content.Client/Body/Components/BodyComponent.cs b/Content.Client/Body/Components/BodyComponent.cs deleted file mode 100644 index d82ac656f8..0000000000 --- a/Content.Client/Body/Components/BodyComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Body.Components; -using Content.Shared.DragDrop; -using Robust.Shared.GameObjects; - -namespace Content.Client.Body.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedBodyComponent))] - public sealed class BodyComponent : SharedBodyComponent, IDraggable - { - bool IDraggable.CanStartDrag(StartDragDropEvent args) - { - return true; - } - - bool IDraggable.CanDrop(CanDropEvent args) - { - return true; - } - } -} diff --git a/Content.Client/Body/Components/BodyPartComponent.cs b/Content.Client/Body/Components/BodyPartComponent.cs deleted file mode 100644 index 9dd47352c9..0000000000 --- a/Content.Client/Body/Components/BodyPartComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Body.Components; -using Robust.Shared.GameObjects; - -namespace Content.Client.Body.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedBodyPartComponent))] - public sealed class BodyPartComponent : SharedBodyPartComponent - { - } -} diff --git a/Content.Client/Body/Systems/BodySystem.cs b/Content.Client/Body/Systems/BodySystem.cs new file mode 100644 index 0000000000..bab785525b --- /dev/null +++ b/Content.Client/Body/Systems/BodySystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Body.Systems; + +namespace Content.Client.Body.Systems; + +public sealed class BodySystem : SharedBodySystem +{ +} diff --git a/Content.Client/Body/UI/BodyScannerDisplay.cs b/Content.Client/Body/UI/BodyScannerDisplay.cs index ecf4cab158..0bfd30f3de 100644 --- a/Content.Client/Body/UI/BodyScannerDisplay.cs +++ b/Content.Client/Body/UI/BodyScannerDisplay.cs @@ -1,11 +1,10 @@ using System.Linq; using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Body.Systems; using Content.Shared.Damage; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.ItemList; @@ -14,7 +13,8 @@ namespace Content.Client.Body.UI public sealed class BodyScannerDisplay : DefaultWindow { private EntityUid? _currentEntity; - private SharedBodyPartComponent? _currentBodyPart; + private BodyPartComponent? _currentBodyPart; + private readonly Dictionary _bodyPartsList = new(); public BodyScannerDisplay(BodyScannerBoundUserInterface owner) { @@ -106,75 +106,79 @@ namespace Content.Client.Body.UI { _currentEntity = entity; BodyPartList.Clear(); + _bodyPartsList.Clear(); - var body = IoCManager.Resolve().GetComponentOrNull(_currentEntity); - - if (body == null) + var bodySystem = IoCManager.Resolve().GetEntitySystem(); + var i = 0; + foreach (var part in bodySystem.GetBodyChildren(_currentEntity)) { - return; - } - - foreach (var (part, _) in body.Parts) - { - BodyPartList.AddItem(Loc.GetString(part.Name)); + _bodyPartsList[i++] = part.Component.ParentSlot!; + BodyPartList.AddItem(Loc.GetString(part.Component.Name)); } } public void BodyPartOnItemSelected(ItemListSelectedEventArgs args) { - if (!IoCManager.Resolve().TryGetComponent(_currentEntity, out var body)) - { - return; - } + var entMan = IoCManager.Resolve(); - var slot = body.SlotAt(args.ItemIndex); - _currentBodyPart = body.PartAt(args.ItemIndex).Key; + _currentBodyPart = entMan.GetComponentOrNull(_bodyPartsList[args.ItemIndex].Child); - if (slot.Part != null) + if (_currentBodyPart is {ParentSlot.Id: var slotId} part) { - UpdateBodyPartBox(slot.Part, slot.Id); + UpdateBodyPartBox(part, slotId); } } - private void UpdateBodyPartBox(SharedBodyPartComponent part, string slotName) + private void UpdateBodyPartBox(BodyPartComponent part, string slotName) { var entMan = IoCManager.Resolve(); - BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(entMan.GetComponent(part.Owner).EntityName)}"; + BodyPartLabel.Text = + $"{Loc.GetString(slotName)}: {Loc.GetString(entMan.GetComponent(part.Owner).EntityName)}"; // TODO BODY Part damage if (entMan.TryGetComponent(part.Owner, out DamageableComponent? damageable)) { - BodyPartHealth.Text = Loc.GetString("body-scanner-display-body-part-damage-text",("damage", damageable.TotalDamage)); + BodyPartHealth.Text = Loc.GetString("body-scanner-display-body-part-damage-text", + ("damage", damageable.TotalDamage)); } MechanismList.Clear(); - foreach (var mechanism in part.Mechanisms) + var bodySystem = entMan.System(); + foreach (var organ in bodySystem.GetPartOrgans(part.Owner, part)) { - MechanismList.AddItem(mechanism.Name); + var organName = entMan.GetComponent(organ.Id).EntityName; + MechanismList.AddItem(organName); } } // TODO BODY Guaranteed this is going to crash when a part's mechanisms change. This part is left as an exercise for the reader. public void MechanismOnItemSelected(ItemListSelectedEventArgs args) { - UpdateMechanismBox(_currentBodyPart?.Mechanisms.ElementAt(args.ItemIndex)); + if (_currentBodyPart == null) + { + UpdateMechanismBox(null); + return; + } + + var bodySystem = IoCManager.Resolve().System(); + var organ = bodySystem.GetPartOrgans(_currentBodyPart.Owner, _currentBodyPart).ElementAt(args.ItemIndex); + UpdateMechanismBox(organ.Id); } - private void UpdateMechanismBox(MechanismComponent? mechanism) + private void UpdateMechanismBox(EntityUid? organ) { // TODO BODY Improve UI - if (mechanism == null) + if (organ == null) { MechanismInfoLabel.SetMessage(""); return; } // TODO BODY Mechanism description - var message = - Loc.GetString( - $"{mechanism.Name}\nHealth: {mechanism.CurrentDurability}/{mechanism.MaxDurability}"); - + var entMan = IoCManager.Resolve(); + var organName = entMan.GetComponent(organ.Value).EntityName; + var message = Loc.GetString($"{organName}"); MechanismInfoLabel.SetMessage(message); } } diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs index b8cbeb21a7..88100a0f8c 100644 --- a/Content.Client/Commands/HideMechanismsCommand.cs +++ b/Content.Client/Commands/HideMechanismsCommand.cs @@ -1,10 +1,8 @@ -using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; using Robust.Client.Console; using Robust.Client.GameObjects; using Robust.Shared.Console; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Client.Commands { @@ -17,18 +15,18 @@ namespace Content.Client.Commands public void Execute(IConsoleShell shell, string argStr, string[] args) { var entityManager = IoCManager.Resolve(); - var mechanisms = entityManager.EntityQuery(true); + var organs = entityManager.EntityQuery(true); - foreach (var mechanism in mechanisms) + foreach (var part in organs) { - if (!entityManager.TryGetComponent(mechanism.Owner, out SpriteComponent? sprite)) + if (!entityManager.TryGetComponent(part.Owner, out SpriteComponent? sprite)) { continue; } sprite.ContainerOccluded = false; - var tempParent = mechanism.Owner; + var tempParent = part.Owner; while (tempParent.TryGetContainer(out var container)) { if (!container.ShowContents) diff --git a/Content.Client/Commands/ShowMechanismsCommand.cs b/Content.Client/Commands/ShowMechanismsCommand.cs index 1221d97ebf..f86aee8714 100644 --- a/Content.Client/Commands/ShowMechanismsCommand.cs +++ b/Content.Client/Commands/ShowMechanismsCommand.cs @@ -1,9 +1,7 @@ -using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; using Robust.Client.Console; using Robust.Client.GameObjects; using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Client.Commands { @@ -19,9 +17,9 @@ namespace Content.Client.Commands public void Execute(IConsoleShell shell, string argStr, string[] args) { var entityManager = IoCManager.Resolve(); - var mechanisms = entityManager.EntityQuery(true); + var organs = entityManager.EntityQuery(true); - foreach (var mechanism in mechanisms) + foreach (var mechanism in organs) { if (entityManager.TryGetComponent(mechanism.Owner, out SpriteComponent? sprite)) { diff --git a/Content.IntegrationTests/Tests/Body/LegTest.cs b/Content.IntegrationTests/Tests/Body/LegTest.cs index 01469422cb..d959db5da1 100644 --- a/Content.IntegrationTests/Tests/Body/LegTest.cs +++ b/Content.IntegrationTests/Tests/Body/LegTest.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Rotation; @@ -12,7 +12,7 @@ using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests.Body { [TestFixture] - [TestOf(typeof(SharedBodyComponent))] + [TestOf(typeof(BodyPartComponent))] [TestOf(typeof(BodyComponent))] public sealed class LegTest { @@ -23,16 +23,15 @@ namespace Content.IntegrationTests.Tests.Body components: - type: Appearance - type: Body - template: HumanoidTemplate - preset: HumanPreset - centerSlot: torso + prototype: Human - type: StandingState "; [Test] public async Task RemoveLegsFallTest() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; AppearanceComponent appearance = null; @@ -44,18 +43,20 @@ namespace Content.IntegrationTests.Tests.Body var mapId = mapManager.CreateMap(); var entityManager = IoCManager.Resolve(); - var human = entityManager.SpawnEntity("HumanBodyAndAppearanceDummy", new MapCoordinates(Vector2.Zero, mapId)); + var human = entityManager.SpawnEntity("HumanBodyAndAppearanceDummy", + new MapCoordinates(Vector2.Zero, mapId)); - Assert.That(entityManager.TryGetComponent(human, out SharedBodyComponent body)); + Assert.That(entityManager.TryGetComponent(human, out BodyComponent body)); Assert.That(entityManager.TryGetComponent(human, out appearance)); Assert.That(!appearance.TryGetData(RotationVisuals.RotationState, out RotationState _)); - var legs = body.GetPartsOfType(BodyPartType.Leg); + var bodySystem = entityManager.System(); + var legs = bodySystem.GetBodyChildrenOfType(body.Owner, BodyPartType.Leg, body); foreach (var leg in legs) { - body.RemovePart(leg); + bodySystem.DropPart(leg.Id, leg.Component); } }); diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 1e75cd27a2..e93ee7af59 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -22,9 +22,7 @@ namespace Content.IntegrationTests.Tests.Body components: - type: SolutionContainerManager - type: Body - template: HumanoidTemplate - preset: HumanPreset - centerSlot: torso + prototype: Human - type: MobState thresholds: 0: Alive @@ -49,7 +47,8 @@ namespace Content.IntegrationTests.Tests.Body public async Task AirConsistencyTest() { // --- Setup - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; await server.WaitIdleAsync(); @@ -62,7 +61,7 @@ namespace Content.IntegrationTests.Tests.Body MapId mapId; EntityUid? grid = null; - SharedBodyComponent body = default; + BodyComponent body = default; EntityUid human = default; GridAtmosphereComponent relevantAtmos = default; float startingMoles = 0.0f; @@ -127,7 +126,8 @@ namespace Content.IntegrationTests.Tests.Body [Test] public async Task NoSuffocationTest() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var mapLoader = server.ResolveDependency(); @@ -155,7 +155,7 @@ namespace Content.IntegrationTests.Tests.Body var coordinates = new EntityCoordinates(grid.Value, center); human = entityManager.SpawnEntity("HumanBodyDummy", coordinates); - Assert.True(entityManager.HasComponent(human)); + Assert.True(entityManager.HasComponent(human)); Assert.True(entityManager.TryGetComponent(human, out respirator)); Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold); }); @@ -167,7 +167,8 @@ namespace Content.IntegrationTests.Tests.Body await server.WaitRunTicks(increment); await server.WaitAssertion(() => { - Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold, $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); + Assert.False(respirator.SuffocationCycles > respirator.SuffocationCycleThreshold, + $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); }); } diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs new file mode 100644 index 0000000000..3361864adb --- /dev/null +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -0,0 +1,148 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using NUnit.Framework; +using Robust.Server.Maps; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Body; + +[TestFixture] +public sealed class SaveLoadReparentTest +{ + private const string Prototypes = @" +- type: entity + name: HumanBodyDummy + id: HumanBodyDummy + components: + - type: Body + prototype: Human +"; + + [Test] + public async Task Test() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + { + NoClient = true, + ExtraPrototypes = Prototypes + }); + var server = pairTracker.Pair.Server; + + var entities = server.ResolveDependency(); + var maps = server.ResolveDependency(); + var mapLoader = server.ResolveDependency(); + var bodySystem = entities.System(); + + await server.WaitAssertion(() => + { + var mapId = maps.CreateMap(); + maps.CreateGrid(mapId); + var human = entities.SpawnEntity("HumanBodyDummy", new MapCoordinates(0, 0, mapId)); + + Assert.That(entities.HasComponent(human), Is.True); + + var parts = bodySystem.GetBodyChildren(human).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.ParentSlot, Is.Not.Null); + Assert.That(component.ParentSlot.Parent, Is.Not.EqualTo(default(EntityUid))); + Assert.That(component.ParentSlot.Child, Is.EqualTo(id)); + }); + + foreach (var (slotId, slot) in component.Children) + { + Assert.Multiple(() => + { + Assert.That(slot.Id, Is.EqualTo(slotId)); + Assert.That(slot.Parent, Is.Not.EqualTo(default(EntityUid))); + }); + } + } + + foreach (var (id, component) in organs) + { + Assert.Multiple(() => + { + Assert.That(component.Body, Is.EqualTo(human)); + Assert.That(component.ParentSlot, Is.Not.Null); + Assert.That(component.ParentSlot.Parent, Is.Not.EqualTo(default(EntityUid))); + Assert.That(component.ParentSlot.Child, Is.EqualTo(id)); + }); + } + + Assert.That(entities + .EntityQuery() + .Where(e => entities.GetComponent(e.Owner).EntityPrototype!.Name == + "HumanBodyDummy"), Is.Not.Empty); + + const string mapPath = $"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"; + + mapLoader.SaveMap(mapId, mapPath); + maps.DeleteMap(mapId); + + mapId = maps.CreateMap(); + mapLoader.LoadMap(mapId, mapPath); + + var query = entities + .EntityQuery() + .Where(e => entities.GetComponent(e.Owner).EntityPrototype!.Name == "HumanBodyDummy") + .ToArray(); + + Assert.That(query, Is.Not.Empty); + foreach (var body in query) + { + human = body.Owner; + parts = bodySystem.GetBodyChildren(human).ToArray(); + organs = bodySystem.GetBodyOrgans(human).ToArray(); + + 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.ParentSlot, Is.Not.Null); + Assert.That(component.ParentSlot.Parent, Is.Not.EqualTo(default(EntityUid))); + Assert.That(component.ParentSlot.Child, Is.EqualTo(id)); + }); + + foreach (var (slotId, slot) in component.Children) + { + Assert.Multiple(() => + { + Assert.That(slot.Id, Is.EqualTo(slotId)); + Assert.That(slot.Parent, Is.Not.EqualTo(default(EntityUid))); + }); + } + } + + foreach (var (id, component) in organs) + { + Assert.Multiple(() => + { + Assert.That(component.Body, Is.EqualTo(human)); + Assert.That(component.ParentSlot, Is.Not.Null); + Assert.That(component.ParentSlot.Parent, Is.Not.EqualTo(default(EntityUid))); + Assert.That(component.ParentSlot.Child, Is.EqualTo(id)); + }); + } + } + }); + } +} diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index e193c77fb4..9fec6fe485 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Content.Server.Body.Systems; using Content.Server.Buckle.Components; using Content.Server.Hands.Components; using Content.Shared.ActionBlocker; @@ -9,8 +10,6 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Standing; using NUnit.Framework; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.Buckle { @@ -31,9 +30,7 @@ namespace Content.IntegrationTests.Tests.Buckle - type: Buckle - type: Hands - type: Body - template: HumanoidTemplate - preset: HumanPreset - centerSlot: torso + prototype: Human - type: StandingState - type: entity @@ -48,10 +45,12 @@ namespace Content.IntegrationTests.Tests.Buckle components: - type: Item "; + [Test] public async Task BuckleUnbuckleCooldownRangeTest() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{ExtraPrototypes = Prototypes}); + await using var pairTracker = + await PoolManager.GetServerClient(new PoolSettings {ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); @@ -96,7 +95,10 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.False(actionBlocker.CanMove(human)); Assert.False(actionBlocker.CanChangeDirection(human)); Assert.False(standingState.Down(human)); - Assert.That((entityManager.GetComponent(human).WorldPosition - entityManager.GetComponent(chair).WorldPosition).Length, Is.LessThanOrEqualTo(buckle.BuckleOffset.Length)); + Assert.That( + (entityManager.GetComponent(human).WorldPosition - + entityManager.GetComponent(chair).WorldPosition).Length, + Is.LessThanOrEqualTo(buckle.BuckleOffset.Length)); // Side effects of buckling for the strap Assert.That(strap.BuckledEntities, Does.Contain(human)); @@ -166,7 +168,8 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.False(buckle.ToggleBuckle(human, chair)); // Move near the chair - entityManager.GetComponent(human).WorldPosition = entityManager.GetComponent(chair).WorldPosition + (0.5f, 0); + entityManager.GetComponent(human).WorldPosition = + entityManager.GetComponent(chair).WorldPosition + (0.5f, 0); // In range Assert.True(buckle.TryBuckle(human, chair)); @@ -206,7 +209,8 @@ namespace Content.IntegrationTests.Tests.Buckle [Test] public async Task BuckledDyingDropItemsTest() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); @@ -215,7 +219,7 @@ namespace Content.IntegrationTests.Tests.Buckle EntityUid human = default; BuckleComponent buckle = null; HandsComponent hands = null; - SharedBodyComponent body = null; + BodyComponent body = null; await server.WaitIdleAsync(); @@ -260,12 +264,13 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.NotNull(hand.HeldEntity); } - var legs = body.GetPartsOfType(BodyPartType.Leg); + var bodySystem = entityManager.System(); + var legs = bodySystem.GetBodyChildrenOfType(body.Owner, BodyPartType.Leg, body); // Break our guy's kneecaps foreach (var leg in legs) { - body.RemovePart(leg); + bodySystem.DropPart(leg.Id, leg.Component); } }); @@ -291,7 +296,8 @@ namespace Content.IntegrationTests.Tests.Buckle [Test] public async Task ForceUnbuckleBuckleTest() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index a4fb8ae46d..650780c05f 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -9,7 +9,6 @@ using Content.Shared.Disposal; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Map; using Robust.Shared.Reflection; namespace Content.IntegrationTests.Tests.Disposal @@ -78,9 +77,7 @@ namespace Content.IntegrationTests.Tests.Disposal id: HumanDummy components: - type: Body - template: HumanoidTemplate - preset: HumanPreset - centerSlot: torso + prototype: Human - type: MobState - type: Damageable damageContainer: Biological @@ -127,7 +124,8 @@ namespace Content.IntegrationTests.Tests.Disposal [Test] public async Task Test() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); @@ -147,7 +145,8 @@ namespace Content.IntegrationTests.Tests.Disposal human = entityManager.SpawnEntity("HumanDummy", coordinates); wrench = entityManager.SpawnEntity("WrenchDummy", coordinates); disposalUnit = entityManager.SpawnEntity("DisposalUnitDummy", coordinates); - disposalTrunk = entityManager.SpawnEntity("DisposalTrunkDummy", IoCManager.Resolve().GetComponent(disposalUnit).MapPosition); + disposalTrunk = entityManager.SpawnEntity("DisposalTrunkDummy", + IoCManager.Resolve().GetComponent(disposalUnit).MapPosition); // Test for components existing ref DisposalUnitComponent? comp = ref unit!; @@ -197,7 +196,7 @@ namespace Content.IntegrationTests.Tests.Disposal // Remove power need Assert.True(entityManager.TryGetComponent(disposalUnit, out ApcPowerReceiverComponent power)); power!.NeedsPower = false; - unit.Powered = true;//Power state changed event doesn't get fired smh + unit.Powered = true; //Power state changed event doesn't get fired smh // Flush with a mob and an item Flush(unit, true, human, wrench); diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs index 2e62801318..5c1d23c188 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs @@ -26,9 +26,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking - type: Cuffable - type: Hands - type: Body - template: HumanoidTemplate - preset: HumanPreset - centerSlot: torso + prototype: Human - type: entity name: HandcuffsDummy @@ -36,10 +34,12 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking components: - type: Handcuff "; + [Test] public async Task Test() { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings + {NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; EntityUid human; @@ -63,18 +63,21 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking cuffs = entityManager.SpawnEntity("HandcuffsDummy", coordinates); secondCuffs = entityManager.SpawnEntity("HandcuffsDummy", coordinates); - entityManager.GetComponent(human).WorldPosition = entityManager.GetComponent(otherHuman).WorldPosition; + entityManager.GetComponent(human).WorldPosition = + entityManager.GetComponent(otherHuman).WorldPosition; // Test for components existing - Assert.True(entityManager.TryGetComponent(human, out cuffed!), $"Human has no {nameof(CuffableComponent)}"); + Assert.True(entityManager.TryGetComponent(human, out cuffed!), + $"Human has no {nameof(CuffableComponent)}"); Assert.True(entityManager.TryGetComponent(human, out hands!), $"Human has no {nameof(HandsComponent)}"); - Assert.True(entityManager.TryGetComponent(human, out SharedBodyComponent? _), $"Human has no {nameof(SharedBodyComponent)}"); + Assert.True(entityManager.TryGetComponent(human, out BodyComponent? _), $"Human has no {nameof(BodyComponent)}"); Assert.True(entityManager.TryGetComponent(cuffs, out HandcuffComponent? _), $"Handcuff has no {nameof(HandcuffComponent)}"); Assert.True(entityManager.TryGetComponent(secondCuffs, out HandcuffComponent? _), $"Second handcuffs has no {nameof(HandcuffComponent)}"); // Test to ensure cuffed players register the handcuffs cuffed.TryAddNewCuffs(human, cuffs); - Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed"); + Assert.True(cuffed.CuffedHandCount > 0, + "Handcuffing a player did not result in their hands being cuffed"); // Test to ensure a player with 4 hands will still only have 2 hands cuffed AddHand(cuffed.Owner); @@ -86,7 +89,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking // Test to give a player with 4 hands 2 sets of cuffs cuffed.TryAddNewCuffs(human, secondCuffs); Assert.True(cuffed.CuffedHandCount == 4, "Player doesn't have correct amount of hands cuffed"); - }); await pairTracker.CleanReturnAsync(); diff --git a/Content.Server/Administration/Commands/AddBodyPartCommand.cs b/Content.Server/Administration/Commands/AddBodyPartCommand.cs index e32e8060cb..0dbe1e2606 100644 --- a/Content.Server/Administration/Commands/AddBodyPartCommand.cs +++ b/Content.Server/Administration/Commands/AddBodyPartCommand.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Shared.Administration; using Robust.Shared.Console; @@ -19,35 +19,28 @@ namespace Content.Server.Administration.Commands return; } - if (!EntityUid.TryParse(args[0], out var entityUid)) + if (!EntityUid.TryParse(args[0], out var childId)) { shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); return; } - if (!EntityUid.TryParse(args[1], out var storageUid)) + if (!EntityUid.TryParse(args[1], out var parentId)) { shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); return; } var entityManager = IoCManager.Resolve(); + var bodySystem = entityManager.System(); - if (entityManager.TryGetComponent(storageUid, out var storage) - && entityManager.TryGetComponent(entityUid, out var bodyPart)) + if (bodySystem.TryCreatePartSlotAndAttach(parentId, args[3], childId)) { - if (storage.TryAddPart(args[3], bodyPart)) - { - shell.WriteLine($@"Added {entityUid} to {storageUid}."); - } - else - { - shell.WriteError($@"Could not add {entityUid} to {storageUid}."); - } + shell.WriteLine($@"Added {childId} to {parentId}."); } else { - shell.WriteError("Could not insert."); + shell.WriteError($@"Could not add {childId} to {parentId}."); } } } diff --git a/Content.Server/Administration/Commands/AddMechanismCommand.cs b/Content.Server/Administration/Commands/AddMechanismCommand.cs index cbc66bddeb..73ec8ffd81 100644 --- a/Content.Server/Administration/Commands/AddMechanismCommand.cs +++ b/Content.Server/Administration/Commands/AddMechanismCommand.cs @@ -1,6 +1,5 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Shared.Administration; -using Content.Shared.Body.Components; using Robust.Shared.Console; namespace Content.Server.Administration.Commands @@ -20,35 +19,28 @@ namespace Content.Server.Administration.Commands return; } - if (!EntityUid.TryParse(args[0], out var entityUid)) + if (!EntityUid.TryParse(args[0], out var organId)) { shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); return; } - if (!EntityUid.TryParse(args[1], out var storageUid)) + if (!EntityUid.TryParse(args[1], out var partId)) { shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); return; } var entityManager = IoCManager.Resolve(); + var bodySystem = entityManager.System(); - if (entityManager.TryGetComponent(storageUid, out var storage) - && entityManager.TryGetComponent(entityUid, out var bodyPart)) + if (bodySystem.AddOrganToFirstValidSlot(organId, partId)) { - if (storage.TryAddMechanism(bodyPart)) - { - shell.WriteLine($@"Added {entityUid} to {storageUid}."); - } - else - { - shell.WriteError($@"Could not add {entityUid} to {storageUid}."); - } + shell.WriteLine($@"Added {organId} to {partId}."); } else { - shell.WriteError("Could not insert."); + shell.WriteError($@"Could not add {organId} to {partId}."); } } } diff --git a/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs b/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs index 6b2197ce83..fbfa62ad6a 100644 --- a/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs +++ b/Content.Server/Administration/Commands/RemoveBodyPartCommand.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Shared.Administration; using Robust.Shared.Console; @@ -26,16 +26,11 @@ namespace Content.Server.Administration.Commands } var entityManager = IoCManager.Resolve(); + var bodySystem = entityManager.System(); - if (!entityManager.TryGetComponent(entityUid, out var transform)) return; - - var parent = transform.ParentUid; - - if (entityManager.TryGetComponent(parent, out var body) && - entityManager.TryGetComponent(entityUid, out var part)) + if (bodySystem.DropPart(entityUid)) { - body.RemovePart(part); - + shell.WriteLine($"Removed body part {entityManager.ToPrettyString(entityUid)}."); } else { diff --git a/Content.Server/Administration/Commands/RemoveMechanismCommand.cs b/Content.Server/Administration/Commands/RemoveMechanismCommand.cs index 99e9b61f79..37ccc97fdd 100644 --- a/Content.Server/Administration/Commands/RemoveMechanismCommand.cs +++ b/Content.Server/Administration/Commands/RemoveMechanismCommand.cs @@ -1,6 +1,5 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Shared.Administration; -using Content.Shared.Body.Components; using Robust.Shared.Console; namespace Content.Server.Administration.Commands @@ -27,15 +26,11 @@ namespace Content.Server.Administration.Commands } var entityManager = IoCManager.Resolve(); + var bodySystem = entityManager.System(); - if (!entityManager.TryGetComponent(entityUid, out var transform)) return; - - var parent = transform.ParentUid; - - if (entityManager.TryGetComponent(parent, out var body) && - entityManager.TryGetComponent(entityUid, out var part)) + if (bodySystem.DropOrgan(entityUid)) { - body.RemoveMechanism(part); + shell.WriteLine($"Removed organ {entityManager.ToPrettyString(entityUid)}"); } else { diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 4ce281f657..b9204a6d14 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -103,10 +103,7 @@ public sealed partial class AdminVerbSystem 4, 1, 2, maxTileBreak: 0), // it gibs, damage doesn't need to be high. CancellationToken.None); - if (TryComp(args.Target, out SharedBodyComponent? body)) - { - body.Gib(); - } + _bodySystem.GibBody(args.Target); }, Impact = LogImpact.Extreme, Message = Loc.GetString("admin-smite-explode-description") @@ -308,15 +305,15 @@ public sealed partial class AdminVerbSystem Act = () => { _vomitSystem.Vomit(args.Target, -1000, -1000); // You feel hollow! - var organs = _bodySystem.GetComponentsOnMechanisms(args.Target, body); + var organs = _bodySystem.GetBodyOrganComponents(args.Target, body); var baseXform = Transform(args.Target); - foreach (var (xform, mechanism) in organs) + foreach (var (xform, organ) in organs) { if (HasComp(xform.Owner) || HasComp(xform.Owner)) continue; - mechanism.Part?.RemoveMechanism(mechanism); - xform.Coordinates = baseXform.Coordinates.Offset(_random.NextVector2(0.5f,0.75f)); + var coordinates = baseXform.Coordinates.Offset(_random.NextVector2(0.5f, 0.75f)); + _bodySystem.DropOrganAt(organ.Owner, coordinates, organ); } _popupSystem.PopupEntity(Loc.GetString("admin-smite-vomit-organs-self"), args.Target, @@ -337,10 +334,9 @@ public sealed partial class AdminVerbSystem Act = () => { var baseXform = Transform(args.Target); - foreach (var part in body.GetPartsOfType(BodyPartType.Hand)) + foreach (var part in _bodySystem.GetBodyChildrenOfType(args.Target, BodyPartType.Hand)) { - body.RemovePart(part); - Transform(part.Owner).Coordinates = baseXform.Coordinates; + _bodySystem.DropPartAt(part.Id, baseXform.Coordinates, part.Component); } _popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target, Filter.Entities(args.Target), PopupType.LargeCaution); @@ -360,10 +356,9 @@ public sealed partial class AdminVerbSystem Act = () => { var baseXform = Transform(args.Target); - foreach (var part in body.GetPartsOfType(BodyPartType.Hand)) + foreach (var part in _bodySystem.GetBodyChildrenOfType(body.Owner, BodyPartType.Hand, body)) { - body.RemovePart(part); - Transform(part.Owner).Coordinates = baseXform.Coordinates; + _bodySystem.DropPartAt(part.Id, baseXform.Coordinates, part.Component); break; } _popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target, @@ -383,13 +378,9 @@ public sealed partial class AdminVerbSystem IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/stomach.png", Act = () => { - foreach (var part in body.Parts) + foreach (var (component, _) in _bodySystem.GetBodyOrganComponents(args.Target, body)) { - foreach (var mechanism in part.Key.Mechanisms) - { - if (HasComp(mechanism.Owner)) - QueueDel(mechanism.Owner); - } + QueueDel(component.Owner); } _popupSystem.PopupEntity(Loc.GetString("admin-smite-stomach-removal-self"), args.Target, @@ -407,13 +398,9 @@ public sealed partial class AdminVerbSystem IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/lung-r.png", Act = () => { - foreach (var part in body.Parts) + foreach (var (component, _) in _bodySystem.GetBodyOrganComponents(args.Target, body)) { - foreach (var mechanism in part.Key.Mechanisms) - { - if (HasComp(mechanism.Owner)) - QueueDel(mechanism.Owner); - } + QueueDel(component.Owner); } _popupSystem.PopupEntity(Loc.GetString("admin-smite-lung-removal-self"), args.Target, diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index f4d36c1113..a3c809c385 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -1,19 +1,19 @@ -using Content.Shared.Damage; +using Content.Server.Actions; using Content.Server.Bed.Components; -using Content.Server.Buckle.Components; -using Content.Server.Body.Systems; -using Content.Shared.Buckle.Components; -using Content.Shared.Body.Components; -using Content.Shared.Bed; -using Content.Shared.Bed.Sleep; using Content.Server.Bed.Sleep; +using Content.Server.Body.Systems; +using Content.Server.Buckle.Components; +using Content.Server.MobState; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; -using Content.Shared.Emag.Systems; -using Content.Server.Actions; -using Content.Server.Construction; -using Content.Server.MobState; using Content.Shared.Actions.ActionTypes; +using Content.Shared.Bed; +using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Components; +using Content.Shared.Buckle.Components; +using Content.Shared.Damage; +using Content.Shared.Emag.Systems; +using Content.Server.Construction; using Robust.Shared.Prototypes; namespace Content.Server.Bed @@ -26,6 +26,7 @@ namespace Content.Server.Bed [Dependency] private readonly SleepingSystem _sleepingSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + public override void Initialize() { base.Initialize(); @@ -94,7 +95,7 @@ namespace Content.Server.Bed { // In testing this also received an unbuckle event when the bed is destroyed // So don't worry about that - if (!HasComp(args.BuckledEntity)) + if (!HasComp(args.BuckledEntity)) return; if (!this.IsPowered(uid, EntityManager)) @@ -143,4 +144,3 @@ namespace Content.Server.Bed } } } - diff --git a/Content.Server/Body/Commands/AddHandCommand.cs b/Content.Server/Body/Commands/AddHandCommand.cs index d1771b9a22..11fcedcdb2 100644 --- a/Content.Server/Body/Commands/AddHandCommand.cs +++ b/Content.Server/Body/Commands/AddHandCommand.cs @@ -1,6 +1,9 @@ +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.Server.Player; using Robust.Shared.Console; using Robust.Shared.Prototypes; @@ -118,7 +121,7 @@ namespace Content.Server.Body.Commands } } - if (!entityManager.TryGetComponent(entity, out SharedBodyComponent? body)) + if (!entityManager.TryGetComponent(entity, out BodyComponent? body) || body.Root == null) { var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; @@ -127,14 +130,25 @@ namespace Content.Server.Body.Commands return; } - if (!entityManager.TryGetComponent(hand, out SharedBodyPartComponent? part)) + if (!entityManager.TryGetComponent(hand, out BodyPartComponent? part)) { - shell.WriteLine($"Hand entity {hand} does not have a {nameof(SharedBodyPartComponent)} component."); + shell.WriteLine($"Hand entity {hand} does not have a {nameof(BodyPartComponent)} component."); return; } - var slot = part.GetHashCode().ToString(); - body.SetPart(slot, part); + var bodySystem = entityManager.System(); + + 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, attachAt.Component, part)) + { + shell.WriteError($"Couldn't create a slot with id {slotId} on entity {entityManager.ToPrettyString(entity)}"); + return; + } shell.WriteLine($"Added hand to entity {entityManager.GetComponent(entity).EntityName}"); } diff --git a/Content.Server/Body/Commands/AttachBodyPartCommand.cs b/Content.Server/Body/Commands/AttachBodyPartCommand.cs index 8bfdb6cfc5..de52cf633a 100644 --- a/Content.Server/Body/Commands/AttachBodyPartCommand.cs +++ b/Content.Server/Body/Commands/AttachBodyPartCommand.cs @@ -1,6 +1,9 @@ +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.Server.Player; using Robust.Shared.Console; @@ -18,7 +21,7 @@ namespace Content.Server.Body.Commands var player = shell.Player as IPlayerSession; var entityManager = IoCManager.Resolve(); - EntityUid entity; + EntityUid bodyId; EntityUid partUid; switch (args.Length) @@ -42,7 +45,7 @@ namespace Content.Server.Body.Commands return; } - entity = player.AttachedEntity.Value; + bodyId = player.AttachedEntity.Value; break; case 2: @@ -64,16 +67,16 @@ namespace Content.Server.Body.Commands return; } - entity = entityUid; + bodyId = entityUid; break; default: shell.WriteLine(Help); return; } - if (!entityManager.TryGetComponent(entity, out SharedBodyComponent? body)) + if (!entityManager.TryGetComponent(bodyId, out BodyComponent? body)) { - shell.WriteLine($"Entity {entityManager.GetComponent(entity).EntityName} with uid {entity} does not have a {nameof(SharedBodyComponent)} component."); + shell.WriteLine($"Entity {entityManager.GetComponent(bodyId).EntityName} with uid {bodyId} does not have a {nameof(BodyComponent)}."); return; } @@ -83,19 +86,39 @@ namespace Content.Server.Body.Commands return; } - if (!entityManager.TryGetComponent(partUid, out SharedBodyPartComponent? part)) + if (!entityManager.TryGetComponent(partUid, out BodyPartComponent? part)) { - shell.WriteLine($"Entity {entityManager.GetComponent(partUid).EntityName} with uid {args[0]} does not have a {nameof(SharedBodyPartComponent)} component."); + shell.WriteLine($"Entity {entityManager.GetComponent(partUid).EntityName} with uid {args[0]} does not have a {nameof(BodyPartComponent)}."); return; } - if (body.HasPart(part)) + var bodySystem = entityManager.System(); + if (bodySystem.BodyHasChild(bodyId, partUid, body, part)) { - shell.WriteLine($"Body part {entityManager.GetComponent(partUid).EntityName} with uid {partUid} is already attached to entity {entityManager.GetComponent(entity).EntityName} with uid {entity}"); + shell.WriteLine($"Body part {entityManager.GetComponent(partUid).EntityName} with uid {partUid} is already attached to entity {entityManager.GetComponent(bodyId).EntityName} with uid {bodyId}"); return; } - body.SetPart($"AttachBodyPartVerb-{partUid}", part); + var slotId = $"AttachBodyPartVerb-{partUid}"; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (bodySystem.TryCreateBodyRootSlot(bodyId, slotId, out var rootSlot, body)) + { + bodySystem.DropPart(partUid, part); + bodySystem.AttachPart(partUid, rootSlot, part); + } + else + { + var attachAt = bodySystem.GetBodyChildren(bodyId, body).First(); + + if (!bodySystem.TryCreatePartSlotAndAttach(attachAt.Id, slotId, partUid, attachAt.Component, part)) + { + shell.WriteError($"Could not create slot {slotId} on entity {entityManager.ToPrettyString(bodyId)}"); + return; + } + } + + shell.WriteLine($"Attached part {entityManager.ToPrettyString(partUid)} to {entityManager.ToPrettyString(bodyId)}"); } } } diff --git a/Content.Server/Body/Commands/DestroyMechanismCommand.cs b/Content.Server/Body/Commands/DestroyMechanismCommand.cs index 09ba077436..8780797d9b 100644 --- a/Content.Server/Body/Commands/DestroyMechanismCommand.cs +++ b/Content.Server/Body/Commands/DestroyMechanismCommand.cs @@ -1,4 +1,5 @@ using Content.Server.Administration; +using Content.Server.Body.Systems; using Content.Shared.Administration; using Content.Shared.Body.Components; using Robust.Server.Player; @@ -35,7 +36,8 @@ namespace Content.Server.Body.Commands return; } - if (!IoCManager.Resolve().TryGetComponent(attached, out SharedBodyComponent? body)) + var entityManager = IoCManager.Resolve(); + if (!entityManager.TryGetComponent(attached, out BodyComponent? body)) { var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; @@ -45,13 +47,13 @@ namespace Content.Server.Body.Commands } var mechanismName = string.Join(" ", args).ToLowerInvariant(); + var bodySystem = entityManager.System(); - foreach (var (part, _) in body.Parts) - foreach (var mechanism in part.Mechanisms) + foreach (var organ in bodySystem.GetBodyOrgans(body.Owner, body)) { - if (mechanism.Name.ToLowerInvariant() == mechanismName) + if (organ.Component.Name.ToLowerInvariant() == mechanismName) { - part.DeleteMechanism(mechanism); + bodySystem.DeleteOrgan(organ.Id, organ.Component); shell.WriteLine($"Mechanism with name {mechanismName} has been destroyed."); return; } diff --git a/Content.Server/Body/Commands/RemoveHandCommand.cs b/Content.Server/Body/Commands/RemoveHandCommand.cs index ba73121efe..e4f8ec66f9 100644 --- a/Content.Server/Body/Commands/RemoveHandCommand.cs +++ b/Content.Server/Body/Commands/RemoveHandCommand.cs @@ -1,5 +1,6 @@ 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; @@ -31,7 +32,8 @@ namespace Content.Server.Body.Commands return; } - if (!IoCManager.Resolve().TryGetComponent(player.AttachedEntity, out SharedBodyComponent? body)) + var entityManager = IoCManager.Resolve(); + if (!entityManager.TryGetComponent(player.AttachedEntity, out BodyComponent? body)) { var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; @@ -40,15 +42,16 @@ namespace Content.Server.Body.Commands return; } - var hand = body.GetPartsOfType(BodyPartType.Hand).FirstOrDefault(); + var bodySystem = entityManager.System(); + var hand = bodySystem.GetBodyChildrenOfType(player.AttachedEntity, BodyPartType.Hand, body).FirstOrDefault(); - if (hand == null) + if (hand == default) { shell.WriteLine("You have no hands."); } else { - body.RemovePart(hand); + bodySystem.DropPart(hand.Id, hand.Component); } } } diff --git a/Content.Server/Body/Components/BeingGibbedEvent.cs b/Content.Server/Body/Components/BeingGibbedEvent.cs new file mode 100644 index 0000000000..66b52af47b --- /dev/null +++ b/Content.Server/Body/Components/BeingGibbedEvent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.Body.Components; + +public sealed class BeingGibbedEvent : EntityEventArgs +{ + public readonly HashSet GibbedParts; + + public BeingGibbedEvent(HashSet gibbedParts) + { + GibbedParts = gibbedParts; + } +} diff --git a/Content.Server/Body/Components/BodyComponent.cs b/Content.Server/Body/Components/BodyComponent.cs deleted file mode 100644 index 6aa1ac866f..0000000000 --- a/Content.Server/Body/Components/BodyComponent.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Content.Server.Humanoid; -using Content.Shared.Audio; -using Content.Shared.Body.Components; -using Content.Shared.Body.Part; -using Content.Shared.Humanoid; -using Content.Shared.Random.Helpers; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Player; - -namespace Content.Server.Body.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedBodyComponent))] - public sealed class BodyComponent : SharedBodyComponent - { - [Dependency] private readonly IEntityManager _entMan = default!; - - private Container _partContainer = default!; - - [DataField("gibSound")] private SoundSpecifier _gibSound = new SoundCollectionSpecifier("gib"); - - protected override bool CanAddPart(string slotId, SharedBodyPartComponent part) - { - return base.CanAddPart(slotId, part) && - _partContainer.CanInsert(part.Owner); - } - - protected override void OnAddPart(BodyPartSlot slot, SharedBodyPartComponent part) - { - base.OnAddPart(slot, part); - - _partContainer.Insert(part.Owner); - - if (_entMan.TryGetComponent(Owner, out var humanoid)) - { - var layer = part.ToHumanoidLayers(); - if (layer != null) - { - var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value); - _entMan.System().SetLayersVisibility(Owner, layers, true, true, humanoid); - } - } - } - - protected override void OnRemovePart(BodyPartSlot slot, SharedBodyPartComponent part) - { - base.OnRemovePart(slot, part); - - _partContainer.ForceRemove(part.Owner); - part.Owner.RandomOffset(0.25f); - - if (_entMan.TryGetComponent(Owner, out var humanoid)) - { - var layer = part.ToHumanoidLayers(); - if (layer != null) - { - var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value); - _entMan.System().SetLayersVisibility(Owner, layers, false, true, humanoid); - } - } - } - - protected override void Initialize() - { - base.Initialize(); - - _partContainer = Owner.EnsureContainer($"{Name}-{nameof(BodyComponent)}"); - var preset = Preset; - - if (preset != null) - { - foreach (var slot in Slots) - { - // Using MapPosition instead of Coordinates here prevents - // a crash within the character preview menu in the lobby - var entity = _entMan.SpawnEntity(preset.PartIDs[slot.Id], _entMan.GetComponent(Owner).MapPosition); - - if (!_entMan.TryGetComponent(entity, out SharedBodyPartComponent? part)) - { - Logger.Error($"Entity {slot.Id} does not have a {nameof(SharedBodyPartComponent)} component."); - continue; - } - - SetPart(slot.Id, part); - } - } - } - - protected override void Startup() - { - base.Startup(); - - // This is ran in Startup as entities spawned in Initialize - // are not synced to the client since they are assumed to be - // identical on it - foreach (var (part, _) in Parts) - { - part.Dirty(); - } - } - - public override HashSet Gib(bool gibParts = false) - { - var gibs = base.Gib(gibParts); - - var xform = _entMan.GetComponent(Owner); - var coordinates = xform.Coordinates; - - // These have already been forcefully removed from containers so run it here. - foreach (var part in gibs) - { - _entMan.EventBus.RaiseLocalEvent(part, new PartGibbedEvent(Owner, gibs), true); - } - - SoundSystem.Play(_gibSound.GetSound(), Filter.Pvs(Owner, entityManager: _entMan), coordinates, AudioHelpers.WithVariation(0.025f)); - - if (_entMan.TryGetComponent(Owner, out ContainerManagerComponent? container)) - { - foreach (var cont in container.GetAllContainers()) - { - foreach (var ent in cont.ContainedEntities) - { - cont.ForceRemove(ent); - _entMan.GetComponent(ent).Coordinates = coordinates; - ent.RandomOffset(0.25f); - } - } - } - - _entMan.EventBus.RaiseLocalEvent(Owner, new BeingGibbedEvent(gibs), false); - _entMan.QueueDeleteEntity(Owner); - - return gibs; - } - } - - public sealed class BeingGibbedEvent : EntityEventArgs - { - public readonly HashSet GibbedParts; - - public BeingGibbedEvent(HashSet gibbedParts) - { - GibbedParts = gibbedParts; - } - } - - /// - /// An event raised on all the parts of an entity when it's gibbed - /// - public sealed class PartGibbedEvent : EntityEventArgs - { - public EntityUid EntityToGib; - public readonly HashSet GibbedParts; - - public PartGibbedEvent(EntityUid entityToGib, HashSet gibbedParts) - { - EntityToGib = entityToGib; - GibbedParts = gibbedParts; - } - } -} diff --git a/Content.Server/Body/Components/BodyPartComponent.cs b/Content.Server/Body/Components/BodyPartComponent.cs deleted file mode 100644 index f9ea0334d3..0000000000 --- a/Content.Server/Body/Components/BodyPartComponent.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Content.Shared.Body.Components; -using Content.Shared.Random.Helpers; -using Robust.Shared.Containers; - -namespace Content.Server.Body.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedBodyPartComponent))] - public sealed class BodyPartComponent : SharedBodyPartComponent - { - [Dependency] private readonly IEntityManager _entMan = default!; - - private Container _mechanismContainer = default!; - - public override bool CanAddMechanism(MechanismComponent mechanism) - { - return base.CanAddMechanism(mechanism) && - _mechanismContainer.CanInsert(mechanism.Owner); - } - - protected override void OnAddMechanism(MechanismComponent mechanism) - { - base.OnAddMechanism(mechanism); - - _mechanismContainer.Insert(mechanism.Owner); - } - - protected override void OnRemoveMechanism(MechanismComponent mechanism) - { - base.OnRemoveMechanism(mechanism); - - _mechanismContainer.Remove(mechanism.Owner); - mechanism.Owner.RandomOffset(0.25f); - } - - public void MapInitialize() - { - base.Initialize(); - - _mechanismContainer = Owner.EnsureContainer(ContainerId); - - // This is ran in Startup as entities spawned in Initialize - // are not synced to the client since they are assumed to be - // identical on it - foreach (var mechanismId in MechanismIds) - { - var entity = _entMan.SpawnEntity(mechanismId, _entMan.GetComponent(Owner).MapPosition); - - if (!_entMan.TryGetComponent(entity, out MechanismComponent? mechanism)) - { - Logger.Error($"Entity {mechanismId} does not have a {nameof(MechanismComponent)} component."); - continue; - } - - TryAddMechanism(mechanism, true); - } - } - } -} diff --git a/Content.Server/Body/Components/BodyScannerComponent.cs b/Content.Server/Body/Components/BodyScannerComponent.cs index c04eb9a52a..2ec8e7aeb2 100644 --- a/Content.Server/Body/Components/BodyScannerComponent.cs +++ b/Content.Server/Body/Components/BodyScannerComponent.cs @@ -26,7 +26,7 @@ namespace Content.Server.Body.Components /// /// Copy BodyTemplate and BodyPart data into a common data class that the client can read. /// - private BodyScannerUIState InterfaceState(SharedBodyComponent body) + private BodyScannerUIState InterfaceState(BodyComponent body) { return new(body.Owner); } diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index fca8f0d166..b1728e7411 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -1,119 +1,148 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Body.Components; using Content.Server.GameTicking; +using Content.Server.Humanoid; using Content.Server.Kitchen.Components; using Content.Server.Mind.Components; using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Body.Systems; +using Content.Shared.Humanoid; using Content.Shared.MobState.Components; using Content.Shared.Movement.Events; +using Content.Shared.Random.Helpers; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.Player; using Robust.Shared.Timing; -namespace Content.Server.Body.Systems +namespace Content.Server.Body.Systems; + +public sealed class BodySystem : SharedBodySystem { - public sealed class BodySystem : EntitySystem + [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + [Dependency] private readonly HumanoidSystem _humanoidSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() { - [Dependency] private readonly GameTicker _ticker = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; + base.Initialize(); - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnRelayMoveInput); - SubscribeLocalEvent(OnApplyMetabolicMultiplier); - SubscribeLocalEvent(OnBeingMicrowaved); - SubscribeLocalEvent((_, c, _) => c.MapInitialize()); - } + SubscribeLocalEvent(OnRelayMoveInput); + SubscribeLocalEvent(OnApplyMetabolicMultiplier); + SubscribeLocalEvent(OnBeingMicrowaved); + } - private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args) + private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args) + { + if (EntityManager.TryGetComponent(uid, out var mobState) && + mobState.IsDead() && + EntityManager.TryGetComponent(uid, out var mind) && + mind.HasMind) { - if (EntityManager.TryGetComponent(uid, out var mobState) && - mobState.IsDead() && - EntityManager.TryGetComponent(uid, out var mind) && - mind.HasMind) + if (!mind.Mind!.TimeOfDeath.HasValue) { - if (!mind.Mind!.TimeOfDeath.HasValue) - { - mind.Mind.TimeOfDeath = _gameTiming.RealTime; - } - - _ticker.OnGhostAttempt(mind.Mind!, true); - } - } - - private void OnApplyMetabolicMultiplier(EntityUid uid, BodyComponent component, ApplyMetabolicMultiplierEvent args) - { - foreach (var (part, _) in component.Parts) - foreach (var mechanism in part.Mechanisms) - { - RaiseLocalEvent(mechanism.Owner, args, false); - } - } - - private void OnBeingMicrowaved(EntityUid uid, BodyComponent component, BeingMicrowavedEvent args) - { - if (args.Handled) - return; - - // Don't microwave animals, kids - Transform(uid).AttachToGridOrMap(); - component.Gib(); - - args.Handled = true; - } - - /// - /// Returns a list of ValueTuples of and MechanismComponent on each mechanism - /// in the given body. - /// - /// The entity to check for the component on. - /// The body to check for mechanisms on. - /// The component to check for. - public List<(T Comp, MechanismComponent Mech)> GetComponentsOnMechanisms(EntityUid uid, - SharedBodyComponent? body=null) where T : Component - { - if (!Resolve(uid, ref body)) - return new(); - - var query = EntityManager.GetEntityQuery(); - var list = new List<(T Comp, MechanismComponent Mech)>(3); - foreach (var (part, _) in body.Parts) - foreach (var mechanism in part.Mechanisms) - { - if (query.TryGetComponent(mechanism.Owner, out var comp)) - list.Add((comp, mechanism)); + mind.Mind.TimeOfDeath = _gameTiming.RealTime; } - return list; - } - - /// - /// Tries to get a list of ValueTuples of and MechanismComponent on each mechanism - /// in the given body. - /// - /// The entity to check for the component on. - /// The list of components. - /// The body to check for mechanisms on. - /// The component to check for. - /// Whether any were found. - public bool TryGetComponentsOnMechanisms(EntityUid uid, - [NotNullWhen(true)] out List<(T Comp, MechanismComponent Mech)>? comps, - SharedBodyComponent? body=null) where T: Component - { - if (!Resolve(uid, ref body)) - { - comps = null; - return false; - } - - comps = GetComponentsOnMechanisms(uid, body); - - if (comps.Count == 0) - { - comps = null; - return false; - } - - return true; + _ticker.OnGhostAttempt(mind.Mind!, true); } } + + private void OnApplyMetabolicMultiplier(EntityUid uid, BodyComponent component, + ApplyMetabolicMultiplierEvent args) + { + foreach (var organ in GetBodyOrgans(uid, component)) + { + RaiseLocalEvent(organ.Id, args); + } + } + + private void OnBeingMicrowaved(EntityUid uid, BodyComponent component, BeingMicrowavedEvent args) + { + if (args.Handled) + return; + + // Don't microwave animals, kids + Transform(uid).AttachToGridOrMap(); + GibBody(uid, false, component); + + args.Handled = true; + } + + public override bool AttachPart( + EntityUid? partId, + BodyPartSlot slot, + [NotNullWhen(true)] BodyPartComponent? part = null) + { + if (!base.AttachPart(partId, slot, part)) + return false; + + if (part.Body is { } body && + TryComp(body, out var humanoid)) + { + var layer = part.ToHumanoidLayers(); + if (layer != null) + { + var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value); + _humanoidSystem.SetLayersVisibility(body, layers, true, true, humanoid); + } + } + + return true; + } + + public override bool DropPart(EntityUid? partId, BodyPartComponent? part = null) + { + var oldBody = CompOrNull(partId)?.Body; + + if (!base.DropPart(partId, part)) + return false; + + if (oldBody == null || !TryComp(oldBody, out var humanoid)) + return true; + + var layer = part.ToHumanoidLayers(); + if (layer == null) + return true; + + var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value); + _humanoidSystem.SetLayersVisibility(oldBody.Value, layers, false, true, humanoid); + return true; + } + + public override HashSet GibBody(EntityUid? bodyId, bool gibOrgans = false, BodyComponent? body = null) + { + if (bodyId == null || !Resolve(bodyId.Value, ref body, false)) + return new HashSet(); + + var gibs = base.GibBody(bodyId, gibOrgans, body); + + var xform = Transform(bodyId.Value); + var coordinates = xform.Coordinates; + var filter = Filter.Pvs(bodyId.Value, entityManager: EntityManager); + var audio = AudioParams.Default.WithVariation(0.025f); + + _audio.Play(body.GibSound, filter, coordinates, audio); + + if (TryComp(bodyId, out ContainerManagerComponent? container)) + { + foreach (var cont in container.GetAllContainers()) + { + foreach (var ent in cont.ContainedEntities) + { + cont.ForceRemove(ent); + Transform(ent).Coordinates = coordinates; + ent.RandomOffset(0.25f); + } + } + } + + RaiseLocalEvent(bodyId.Value, new BeingGibbedEvent(gibs)); + QueueDel(bodyId.Value); + + return gibs; + } } diff --git a/Content.Server/Body/Systems/BrainSystem.cs b/Content.Server/Body/Systems/BrainSystem.cs index 39fb6f3798..52b743eebc 100644 --- a/Content.Server/Body/Systems/BrainSystem.cs +++ b/Content.Server/Body/Systems/BrainSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Ghost.Components; using Content.Server.Mind.Components; using Content.Shared.Body.Components; using Content.Shared.Body.Events; +using Content.Shared.Body.Organ; using Content.Shared.Movement.Components; namespace Content.Server.Body.Systems @@ -13,21 +14,22 @@ namespace Content.Server.Body.Systems { base.Initialize(); - SubscribeLocalEvent((uid, _, args) => HandleMind((args.Body).Owner, uid)); - SubscribeLocalEvent((uid, _, args) => HandleMind((args.Part).Owner, uid)); - SubscribeLocalEvent((uid, _, args) => HandleMind((args.Body).Owner, uid)); + SubscribeLocalEvent((uid, _, args) => HandleMind(args.Body, uid)); + SubscribeLocalEvent((uid, _, args) => HandleMind(args.Part, uid)); + SubscribeLocalEvent((uid, _, args) => HandleMind(args.Body, uid)); SubscribeLocalEvent(OnRemovedFromBody); - SubscribeLocalEvent((uid, _, args) => HandleMind(uid, (args.Old).Owner)); - SubscribeLocalEvent((uid, _, args) => HandleMind((args.OldBody).Owner, uid)); + SubscribeLocalEvent((uid, _, args) => HandleMind(uid, args.Old)); + SubscribeLocalEvent((uid, _, args) => HandleMind(args.OldBody, uid)); } private void OnRemovedFromBody(EntityUid uid, BrainComponent component, RemovedFromBodyEvent args) { // This one needs to be special, okay? - if (!EntityManager.TryGetComponent(uid, out MechanismComponent? mech)) + if (!EntityManager.TryGetComponent(uid, out OrganComponent? organ) || + organ.ParentSlot is not {Parent: var parent}) return; - HandleMind((mech.Part!).Owner, (args.Old).Owner); + HandleMind(parent, args.Old); } private void HandleMind(EntityUid newEntity, EntityUid oldEntity) diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index 6dc07dd7cb..152e1ae6bf 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -3,7 +3,7 @@ using Content.Server.Body.Components; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Administration.Logs; -using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; @@ -18,6 +18,7 @@ namespace Content.Server.Body.Systems [UsedImplicitly] public sealed class MetabolizerSystem : EntitySystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; @@ -37,30 +38,27 @@ namespace Content.Server.Body.Systems { _solutionContainerSystem.EnsureSolution(uid, component.SolutionName); } - else + else if (CompOrNull(uid)?.Body is { } body) { - if (EntityManager.TryGetComponent(uid, out var mech)) - { - if (mech.Body != null) - { - _solutionContainerSystem.EnsureSolution((mech.Body).Owner, component.SolutionName); - } - } + _solutionContainerSystem.EnsureSolution(body, component.SolutionName); } } - private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent component, ApplyMetabolicMultiplierEvent args) + private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent component, + ApplyMetabolicMultiplierEvent args) { if (args.Apply) { component.UpdateFrequency *= args.Multiplier; return; } + component.UpdateFrequency /= args.Multiplier; // Reset the accumulator properly if (component.AccumulatedFrametime >= component.UpdateFrequency) component.AccumulatedFrametime = component.UpdateFrequency; } + public override void Update(float frameTime) { base.Update(frameTime); @@ -78,33 +76,27 @@ namespace Content.Server.Body.Systems } } - private void TryMetabolize(EntityUid uid, MetabolizerComponent? meta=null, MechanismComponent? mech=null) + private void TryMetabolize(EntityUid uid, MetabolizerComponent? meta = null, OrganComponent? organ = null) { if (!Resolve(uid, ref meta)) return; - Resolve(uid, ref mech, false); + Resolve(uid, ref organ, false); // First step is get the solution we actually care about Solution? solution = null; EntityUid? solutionEntityUid = null; - EntityUid? bodyEntityUid = mech?.Body?.Owner; SolutionContainerManagerComponent? manager = null; if (meta.SolutionOnBody) { - if (mech != null) + if (organ?.Body is { } body) { - var body = mech.Body; - - if (body != null) - { - if (!Resolve((body).Owner, ref manager, false)) - return; - _solutionContainerSystem.TryGetSolution((body).Owner, meta.SolutionName, out solution, manager); - solutionEntityUid = body.Owner; - } + if (!Resolve(body, ref manager, false)) + return; + _solutionContainerSystem.TryGetSolution(body, meta.SolutionName, out solution, manager); + solutionEntityUid = body; } } else @@ -133,7 +125,8 @@ namespace Content.Server.Body.Systems if (proto.Metabolisms == null) { if (meta.RemoveEmpty) - _solutionContainerSystem.TryRemoveReagent(solutionEntityUid.Value, solution, reagent.ReagentId, FixedPoint2.New(1)); + _solutionContainerSystem.TryRemoveReagent(solutionEntityUid.Value, solution, reagent.ReagentId, + FixedPoint2.New(1)); continue; } @@ -170,7 +163,7 @@ namespace Content.Server.Body.Systems continue; } - var actualEntity = bodyEntityUid != null ? bodyEntityUid.Value : solutionEntityUid.Value; + var actualEntity = organ?.Body ?? solutionEntityUid.Value; var args = new ReagentEffectArgs(actualEntity, (meta).Owner, solution, proto, mostToRemove, EntityManager, null, entry); @@ -192,16 +185,20 @@ namespace Content.Server.Body.Systems // remove a certain amount of reagent if (mostToRemove > FixedPoint2.Zero) - _solutionContainerSystem.TryRemoveReagent(solutionEntityUid.Value, solution, reagent.ReagentId, mostToRemove); + _solutionContainerSystem.TryRemoveReagent(solutionEntityUid.Value, solution, reagent.ReagentId, + mostToRemove); } } } + public sealed class ApplyMetabolicMultiplierEvent : EntityEventArgs { // The entity whose metabolism is being modified - public EntityUid Uid; + public EntityUid Uid; + // What the metabolism's update rate will be multiplied by - public float Multiplier; + public float Multiplier; + // Apply this multiplier or ignore / reset it? public bool Apply; } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 989c08b9f3..dd4232dab4 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.MobState.Components; using Content.Shared.MobState.EntitySystems; using JetBrains.Annotations; using Robust.Shared.Player; @@ -42,8 +41,7 @@ namespace Content.Server.Body.Systems { base.Update(frameTime); - foreach (var (respirator, body) in - EntityManager.EntityQuery()) + foreach (var (respirator, body) in EntityManager.EntityQuery()) { var uid = respirator.Owner; @@ -91,12 +89,13 @@ namespace Content.Server.Body.Systems respirator.SuffocationCycles = 0; } } - public void Inhale(EntityUid uid, SharedBodyComponent? body=null) + + public void Inhale(EntityUid uid, BodyComponent? body = null) { if (!Resolve(uid, ref body, false)) return; - var organs = _bodySystem.GetComponentsOnMechanisms(uid, body); + var organs = _bodySystem.GetBodyOrganComponents(uid, body); // Inhale gas var ev = new InhaleLocationEvent(); @@ -121,12 +120,12 @@ namespace Content.Server.Body.Systems } } - public void Exhale(EntityUid uid, SharedBodyComponent? body=null) + public void Exhale(EntityUid uid, BodyComponent? body = null) { if (!Resolve(uid, ref body, false)) return; - var organs = _bodySystem.GetComponentsOnMechanisms(uid, body); + var organs = _bodySystem.GetBodyOrganComponents(uid, body); // exhale gas @@ -187,7 +186,8 @@ namespace Content.Server.Body.Systems Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation); } - private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component, ApplyMetabolicMultiplierEvent args) + private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component, + ApplyMetabolicMultiplierEvent args) { if (args.Apply) { @@ -197,6 +197,7 @@ namespace Content.Server.Body.Systems component.MinSaturation *= args.Multiplier; return; } + // This way we don't have to worry about it breaking if the stasis bed component is destroyed component.CycleDelay /= args.Multiplier; component.Saturation /= args.Multiplier; diff --git a/Content.Server/Body/Systems/StomachSystem.cs b/Content.Server/Body/Systems/StomachSystem.cs index e3122e41eb..2bc716ce14 100644 --- a/Content.Server/Body/Systems/StomachSystem.cs +++ b/Content.Server/Body/Systems/StomachSystem.cs @@ -1,7 +1,7 @@ using Content.Server.Body.Components; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; -using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; using Content.Shared.Chemistry.Components; using Robust.Shared.Utility; @@ -9,6 +9,7 @@ namespace Content.Server.Body.Systems { public sealed class StomachSystem : EntitySystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; public const string DefaultSolutionName = "stomach"; @@ -21,12 +22,8 @@ namespace Content.Server.Body.Systems public override void Update(float frameTime) { - foreach (var (stomach, mech, sol) - in EntityManager.EntityQuery(false)) + foreach (var (stomach, organ, sol)in EntityManager.EntityQuery()) { - if (mech.Body == null) - continue; - stomach.AccumulatedFrameTime += frameTime; if (stomach.AccumulatedFrameTime < stomach.UpdateInterval) @@ -35,12 +32,11 @@ namespace Content.Server.Body.Systems stomach.AccumulatedFrameTime -= stomach.UpdateInterval; // Get our solutions - if (!_solutionContainerSystem.TryGetSolution((stomach).Owner, DefaultSolutionName, - out var stomachSolution, sol)) + if (!_solutionContainerSystem.TryGetSolution(stomach.Owner, DefaultSolutionName, + out var stomachSolution, sol)) continue; - if (!_solutionContainerSystem.TryGetSolution((mech.Body).Owner, stomach.BodySolutionName, - out var bodySolution)) + if (organ.Body is not { } body || !_solutionContainerSystem.TryGetSolution(body, stomach.BodySolutionName, out var bodySolution)) continue; var transferSolution = new Solution(); @@ -71,23 +67,25 @@ namespace Content.Server.Body.Systems } // Transfer everything to the body solution! - _solutionContainerSystem.TryAddSolution((mech.Body).Owner, bodySolution, transferSolution); + _solutionContainerSystem.TryAddSolution(body, bodySolution, transferSolution); } } - private void OnApplyMetabolicMultiplier(EntityUid uid, StomachComponent component, ApplyMetabolicMultiplierEvent args) - { - if (args.Apply) + private void OnApplyMetabolicMultiplier(EntityUid uid, StomachComponent component, + ApplyMetabolicMultiplierEvent args) { - component.UpdateInterval *= args.Multiplier; - return; + if (args.Apply) + { + component.UpdateInterval *= args.Multiplier; + return; + } + + // This way we don't have to worry about it breaking if the stasis bed component is destroyed + component.UpdateInterval /= args.Multiplier; + // Reset the accumulator properly + if (component.AccumulatedFrameTime >= component.UpdateInterval) + component.AccumulatedFrameTime = component.UpdateInterval; } - // This way we don't have to worry about it breaking if the stasis bed component is destroyed - component.UpdateInterval /= args.Multiplier; - // Reset the accumulator properly - if (component.AccumulatedFrameTime >= component.UpdateInterval) - component.AccumulatedFrameTime = component.UpdateInterval; - } private void OnComponentInit(EntityUid uid, StomachComponent component, ComponentInit args) { @@ -96,7 +94,7 @@ namespace Content.Server.Body.Systems } public bool CanTransferSolution(EntityUid uid, Solution solution, - SolutionContainerManagerComponent? solutions=null) + SolutionContainerManagerComponent? solutions = null) { if (!Resolve(uid, ref solutions, false)) return false; @@ -112,8 +110,8 @@ namespace Content.Server.Body.Systems } public bool TryTransferSolution(EntityUid uid, Solution solution, - StomachComponent? stomach=null, - SolutionContainerManagerComponent? solutions=null) + StomachComponent? stomach = null, + SolutionContainerManagerComponent? solutions = null) { if (!Resolve(uid, ref stomach, ref solutions, false)) return false; diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index 1aca4b9d8b..cc6645821d 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -1,10 +1,11 @@ using System.Linq; using Content.Server.Administration; -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Server.Cargo.Components; -using Content.Shared.Materials; using Content.Server.Stack; using Content.Shared.Administration; +using Content.Shared.Body.Components; +using Content.Shared.Materials; using Content.Shared.MobState.Components; using Robust.Shared.Console; using Robust.Shared.Containers; @@ -22,6 +23,8 @@ public sealed class PricingSystem : EntitySystem [Dependency] private readonly IConsoleHost _consoleHost = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; + /// public override void Initialize() { @@ -84,8 +87,8 @@ public sealed class PricingSystem : EntitySystem return; } - var partList = body.Slots.ToList(); - var totalPartsPresent = partList.Sum(x => x.Part != null ? 1 : 0); + var partList = _bodySystem.GetBodyAllSlots(uid, body).ToList(); + var totalPartsPresent = partList.Sum(x => x.Child != null ? 1 : 0); var totalParts = partList.Count; var partRatio = totalPartsPresent / (double) totalParts; diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index d106a9d71f..015cd28eee 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -1,10 +1,9 @@ +using Content.Server.Body.Systems; using Content.Server.Climbing.Components; using Content.Server.DoAfter; using Content.Server.Interaction; -using Content.Server.Interaction.Components; using Content.Server.Popups; using Content.Server.Stunnable; -using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Body.Part; @@ -15,7 +14,6 @@ using Content.Shared.Damage; using Content.Shared.DragDrop; using Content.Shared.GameTicking; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Verbs; @@ -30,7 +28,6 @@ using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; -using SharpZstd.Interop; namespace Content.Server.Climbing; @@ -39,6 +36,7 @@ public sealed class ClimbSystem : SharedClimbSystem { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly FixtureSystem _fixtureSystem = default!; @@ -286,9 +284,9 @@ public sealed class ClimbSystem : SharedClimbSystem } if (!HasComp(user) - || !TryComp(user, out SharedBodyComponent? body) - || !body.HasPartOfType(BodyPartType.Leg) - || !body.HasPartOfType(BodyPartType.Foot)) + || !TryComp(user, out BodyComponent? body) + || !_bodySystem.BodyHasChildOfType(user, BodyPartType.Leg, body) + || !_bodySystem.BodyHasChildOfType(user, BodyPartType.Foot, body)) { reason = Loc.GetString("comp-climbable-cant-climb"); return false; diff --git a/Content.Server/Destructible/DestructibleSystem.cs b/Content.Server/Destructible/DestructibleSystem.cs index a751e85945..212f2b1efa 100644 --- a/Content.Server/Destructible/DestructibleSystem.cs +++ b/Content.Server/Destructible/DestructibleSystem.cs @@ -1,18 +1,19 @@ +using Content.Server.Body.Systems; +using Content.Server.Chemistry.EntitySystems; using Content.Server.Construction; using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; using Content.Server.Destructible.Thresholds.Triggers; using Content.Server.Explosion.EntitySystems; +using Content.Server.Fluids.EntitySystems; using Content.Server.Stack; using Content.Shared.Damage; +using Content.Shared.Destructible; using Content.Shared.FixedPoint; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Shared.Destructible; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Fluids.EntitySystems; namespace Content.Server.Destructible { @@ -23,6 +24,7 @@ namespace Content.Server.Destructible public new IEntityManager EntityManager => base.EntityManager; [Dependency] public readonly AudioSystem AudioSystem = default!; + [Dependency] public readonly BodySystem BodySystem = default!; [Dependency] public readonly ConstructionSystem ConstructionSystem = default!; [Dependency] public readonly ExplosionSystem ExplosionSystem = default!; [Dependency] public readonly StackSystem StackSystem = default!; diff --git a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs index 4d85d79ffe..50ceba19b5 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs @@ -11,9 +11,9 @@ namespace Content.Server.Destructible.Thresholds.Behaviors public void Execute(EntityUid owner, DestructibleSystem system) { - if (system.EntityManager.TryGetComponent(owner, out SharedBodyComponent? body)) + if (system.EntityManager.TryGetComponent(owner, out BodyComponent? body)) { - body.Gib(_recursive); + system.BodySystem.GibBody(owner, _recursive, body); } } } diff --git a/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs b/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs index e4602de4c2..3302da190d 100644 --- a/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs +++ b/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs @@ -1,6 +1,5 @@ using Content.Server.Atmos; using Content.Server.Disposal.Tube.Components; -using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Item; using Robust.Shared.Containers; @@ -74,7 +73,7 @@ namespace Content.Server.Disposal.Unit.Components } return _entMan.HasComponent(entity) || - _entMan.HasComponent(entity); + _entMan.HasComponent(entity); } public bool TryInsert(EntityUid entity) diff --git a/Content.Server/Drone/DroneSystem.cs b/Content.Server/Drone/DroneSystem.cs index ad78d73825..37e04b7a21 100644 --- a/Content.Server/Drone/DroneSystem.cs +++ b/Content.Server/Drone/DroneSystem.cs @@ -1,23 +1,24 @@ -using Content.Shared.Drone; +using Content.Server.Body.Systems; using Content.Server.Drone.Components; -using Content.Shared.MobState; -using Content.Shared.MobState.Components; -using Content.Shared.Interaction.Events; -using Content.Shared.Interaction.Components; -using Content.Shared.Examine; -using Content.Shared.Tag; -using Content.Shared.Throwing; -using Content.Shared.Item; -using Content.Shared.Emoting; -using Content.Shared.Body.Components; -using Content.Shared.IdentityManagement; -using Content.Shared.Popups; -using Content.Server.Popups; -using Content.Server.Mind.Components; using Content.Server.Ghost.Components; using Content.Server.Ghost.Roles.Components; +using Content.Server.Mind.Components; +using Content.Server.Popups; using Content.Server.Tools.Innate; using Content.Server.UserInterface; +using Content.Shared.Body.Components; +using Content.Shared.Drone; +using Content.Shared.Emoting; +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction.Components; +using Content.Shared.Interaction.Events; +using Content.Shared.Item; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; +using Content.Shared.Popups; +using Content.Shared.Tag; +using Content.Shared.Throwing; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -25,6 +26,7 @@ namespace Content.Server.Drone { public sealed class DroneSystem : SharedDroneSystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; @@ -83,8 +85,8 @@ namespace Content.Server.Drone if (TryComp(uid, out var innate)) _innateToolSystem.Cleanup(uid, innate); - if (TryComp(uid, out var body)) - body.Gib(); + if (TryComp(uid, out var body)) + _bodySystem.GibBody(uid, body: body); Del(uid); } } diff --git a/Content.Server/ImmovableRod/ImmovableRodSystem.cs b/Content.Server/ImmovableRod/ImmovableRodSystem.cs index f980419de8..988748b0d4 100644 --- a/Content.Server/ImmovableRod/ImmovableRodSystem.cs +++ b/Content.Server/ImmovableRod/ImmovableRodSystem.cs @@ -1,11 +1,11 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Server.Popups; +using Content.Shared.Body.Components; using Content.Shared.Examine; using Content.Shared.Popups; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Player; using Robust.Shared.Random; @@ -16,6 +16,8 @@ public sealed class ImmovableRodSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IMapManager _map = default!; + + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly PopupSystem _popup = default!; public override void Update(float frameTime) @@ -96,7 +98,7 @@ public sealed class ImmovableRodSystem : EntitySystem _popup.PopupEntity(Loc.GetString("immovable-rod-penetrated-mob", ("rod", uid), ("mob", ent)), uid, Filter.Pvs(uid), PopupType.LargeCaution); - body.Gib(); + _bodySystem.GibBody(ent, body: body); } QueueDel(ent); diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 0c8e219c0e..05dda98cd3 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -1,22 +1,22 @@ using System.Linq; +using Content.Server.Body.Systems; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.Construction; -using Content.Server.Kitchen.Components; -using Content.Shared.Destructible; -using Content.Shared.Interaction; -using Content.Shared.Item; -using Content.Shared.Kitchen.Components; -using Robust.Shared.Player; -using Content.Shared.Interaction.Events; -using Content.Shared.Body.Components; -using Content.Shared.Body.Part; using Content.Server.Hands.Systems; +using Content.Server.Kitchen.Components; using Content.Server.Power.Components; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; +using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Destructible; using Content.Shared.FixedPoint; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Item; using Content.Shared.Kitchen; +using Content.Shared.Kitchen.Components; using Content.Shared.Popups; using Content.Shared.Power; using Content.Shared.Tag; @@ -24,11 +24,13 @@ using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; +using Robust.Shared.Player; namespace Content.Server.Kitchen.EntitySystems { public sealed class MicrowaveSystem : EntitySystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly RecipeManager _recipeManager = default!; @@ -182,29 +184,19 @@ namespace Content.Server.Kitchen.EntitySystems var victim = args.Victim; var headCount = 0; - if (TryComp(victim, out var body)) + if (TryComp(victim, out var body)) { - var headSlots = body.GetSlotsOfType(BodyPartType.Head); + var headSlots = _bodySystem.GetBodyChildrenOfType(victim, BodyPartType.Head, body); - foreach (var slot in headSlots) + foreach (var part in headSlots) { - var part = slot.Part; - - if (part == null || - !body.TryDropPart(slot, out var dropped)) + if (!_bodySystem.OrphanPart(part.Id, part.Component)) { continue; } - foreach (var droppedPart in dropped.Values) - { - if (droppedPart.PartType != BodyPartType.Head) - { - continue; - } - component.Storage.Insert(droppedPart.Owner); - headCount++; - } + component.Storage.Insert(part.Id); + headCount++; } } diff --git a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs index c0036369c6..395ebffe52 100644 --- a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.DoAfter; +using Content.Server.Body.Systems; +using Content.Server.DoAfter; using Content.Server.Kitchen.Components; using Content.Server.MobState; using Content.Shared.Body.Components; @@ -16,6 +17,7 @@ namespace Content.Server.Kitchen.EntitySystems; public sealed class SharpSystem : EntitySystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly ContainerSystem _containerSystem = default!; @@ -95,7 +97,7 @@ public sealed class SharpSystem : EntitySystem popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f))); } - var hasBody = TryComp(ev.Entity, out var body); + var hasBody = TryComp(ev.Entity, out var body); // only show a big popup when butchering living things. var popupType = PopupType.Small; @@ -107,7 +109,7 @@ public sealed class SharpSystem : EntitySystem if (hasBody) { - body!.Gib(); + _bodySystem.GibBody(body!.Owner, body: body); } else { diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index c125020e88..3f0f88e286 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -1,12 +1,10 @@ using System.Threading; using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Server.Coordinates.Helpers; -using Content.Server.Decals; using Content.Server.DoAfter; using Content.Server.Doors.Components; using Content.Server.Magic.Events; -using Content.Server.Popups; -using Content.Server.Spawners.Components; using Content.Server.Weapons.Ranged.Systems; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; @@ -35,6 +33,7 @@ public sealed class MagicSystem : EntitySystem [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; @@ -281,7 +280,7 @@ public sealed class MagicSystem : EntitySystem if (!TryComp(ev.Target, out var body)) return; - var ents = body.Gib(true); + var ents = _bodySystem.GibBody(ev.Target, true, body); if (!ev.DeleteNonBrainParts) return; @@ -289,8 +288,7 @@ public sealed class MagicSystem : EntitySystem foreach (var part in ents) { // just leaves a brain and clothes - if ((HasComp(part) || HasComp(part)) - && !HasComp(part)) + if (HasComp(part) && !HasComp(part)) { QueueDel(part); } diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index ef14593183..8b36103065 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -1,16 +1,16 @@ -using Content.Server.Nutrition.Components; -using Content.Server.Stunnable; -using Content.Server.Nutrition.EntitySystems; using Content.Server.Body.Components; -using Content.Server.Fluids.Components; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Popups; using Content.Server.Body.Systems; -using Content.Shared.StatusEffect; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Fluids.Components; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; +using Content.Server.Popups; +using Content.Server.Stunnable; using Content.Shared.Audio; +using Content.Shared.IdentityManagement; +using Content.Shared.StatusEffect; using Robust.Shared.Audio; using Robust.Shared.Player; -using Content.Shared.IdentityManagement; namespace Content.Server.Medical { @@ -29,7 +29,7 @@ namespace Content.Server.Medical public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = -40f) { /// Main requirement: You have a stomach - var stomachList = _bodySystem.GetComponentsOnMechanisms(uid); + var stomachList = _bodySystem.GetBodyOrganComponents(uid); if (stomachList.Count == 0) { return; diff --git a/Content.Server/Morgue/EntityStorageLayingDownOverrideSystem.cs b/Content.Server/Morgue/EntityStorageLayingDownOverrideSystem.cs index d200952522..18e0d8e958 100644 --- a/Content.Server/Morgue/EntityStorageLayingDownOverrideSystem.cs +++ b/Content.Server/Morgue/EntityStorageLayingDownOverrideSystem.cs @@ -1,13 +1,15 @@ +using Content.Server.Body.Systems; using Content.Server.Morgue.Components; -using Content.Shared.Standing; using Content.Server.Storage.Components; using Content.Shared.Body.Components; +using Content.Shared.Standing; namespace Content.Server.Morgue; public sealed class EntityStorageLayingDownOverrideSystem : EntitySystem { [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly BodySystem _body = default!; public override void Initialize() { @@ -16,10 +18,11 @@ public sealed class EntityStorageLayingDownOverrideSystem : EntitySystem SubscribeLocalEvent(OnBeforeClose); } - private void OnBeforeClose(EntityUid uid, EntityStorageLayingDownOverrideComponent component, StorageBeforeCloseEvent args) + private void OnBeforeClose(EntityUid uid, EntityStorageLayingDownOverrideComponent component, + StorageBeforeCloseEvent args) { foreach (var ent in args.Contents) - if (HasComp(ent) && !_standing.IsDown(ent)) + if (HasComp(ent) && !_standing.IsDown(ent)) args.Contents.Remove(ent); } } diff --git a/Content.Server/Morgue/MorgueSystem.cs b/Content.Server/Morgue/MorgueSystem.cs index 78f6678c0f..f7c1ead1fe 100644 --- a/Content.Server/Morgue/MorgueSystem.cs +++ b/Content.Server/Morgue/MorgueSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Morgue.Components; -using Content.Shared.Morgue; -using Content.Shared.Examine; -using Robust.Server.GameObjects; using Content.Server.Storage.Components; using Content.Shared.Body.Components; +using Content.Shared.Examine; +using Content.Shared.Morgue; +using Robust.Server.GameObjects; namespace Content.Server.Morgue; @@ -61,7 +61,7 @@ public sealed class MorgueSystem : EntitySystem foreach (var ent in storage.Contents.ContainedEntities) { - if (!hasMob && HasComp(ent)) + if (!hasMob && HasComp(ent)) hasMob = true; if (HasComp(ent)) diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 56013a669e..37dae47d85 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -55,7 +55,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent>(AddDrinkVerb); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnTransferAttempt); - SubscribeLocalEvent(OnDrink); + SubscribeLocalEvent(OnDrink); SubscribeLocalEvent(OnDrinkCancelled); } @@ -225,7 +225,7 @@ namespace Content.Server.Nutrition.EntitySystems return true; } - if (!EntityManager.HasComponent(target)) + if (!EntityManager.HasComponent(target)) return false; if (!drink.Opened) @@ -286,7 +286,7 @@ namespace Content.Server.Nutrition.EntitySystems /// /// Raised directed at a victim when someone has force fed them a drink. /// - private void OnDrink(EntityUid uid, SharedBodyComponent body, DrinkEvent args) + private void OnDrink(EntityUid uid, BodyComponent body, DrinkEvent args) { if (args.Drink.Deleted) return; @@ -297,7 +297,7 @@ namespace Content.Server.Nutrition.EntitySystems var forceDrink = uid != args.User; - if (!_bodySystem.TryGetComponentsOnMechanisms(uid, out var stomachs, body)) + if (!_bodySystem.TryGetBodyOrganComponents(uid, out var stomachs, body)) { _popupSystem.PopupEntity( forceDrink ? @@ -380,8 +380,8 @@ namespace Content.Server.Nutrition.EntitySystems if (uid == ev.User || !ev.CanInteract || !ev.CanAccess || - !EntityManager.TryGetComponent(ev.User, out SharedBodyComponent? body) || - !_bodySystem.TryGetComponentsOnMechanisms(ev.User, out var stomachs, body)) + !EntityManager.TryGetComponent(ev.User, out BodyComponent? body) || + !_bodySystem.TryGetBodyOrganComponents(ev.User, out var stomachs, body)) return; if (EntityManager.TryGetComponent(uid, out var mobState) && mobState.IsAlive()) diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index fad24a461d..8c9033e1c3 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -1,3 +1,4 @@ +using System.Threading; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; @@ -20,7 +21,6 @@ using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Utility; -using System.Threading; namespace Content.Server.Nutrition.EntitySystems { @@ -48,7 +48,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent(OnUseFoodInHand); SubscribeLocalEvent(OnFeedFood); SubscribeLocalEvent>(AddEatVerb); - SubscribeLocalEvent(OnFeed); + SubscribeLocalEvent(OnFeed); SubscribeLocalEvent(OnFeedCancelled); SubscribeLocalEvent(OnInventoryIngestAttempt); } @@ -88,7 +88,7 @@ namespace Content.Server.Nutrition.EntitySystems return false; // Target can't be fed - if (!EntityManager.HasComponent(target)) + if (!EntityManager.HasComponent(target)) return false; if (!_solutionContainerSystem.TryGetSolution(food.Owner, food.SolutionName, out var foodSolution)) @@ -145,14 +145,14 @@ namespace Content.Server.Nutrition.EntitySystems } - private void OnFeed(EntityUid uid, SharedBodyComponent body, FeedEvent args) + private void OnFeed(EntityUid uid, BodyComponent body, FeedEvent args) { if (args.Food.Deleted) return; args.Food.CancelToken = null; - if (!_bodySystem.TryGetComponentsOnMechanisms(uid, out var stomachs, body)) + if (!_bodySystem.TryGetBodyOrganComponents(uid, out var stomachs, body)) return; var transferAmount = args.Food.TransferAmount != null @@ -243,8 +243,8 @@ namespace Content.Server.Nutrition.EntitySystems if (uid == ev.User || !ev.CanInteract || !ev.CanAccess || - !EntityManager.TryGetComponent(ev.User, out SharedBodyComponent? body) || - !_bodySystem.TryGetComponentsOnMechanisms(ev.User, out var stomachs, body)) + !EntityManager.TryGetComponent(ev.User, out BodyComponent? body) || + !_bodySystem.TryGetBodyOrganComponents(ev.User, out var stomachs, body)) return; if (EntityManager.TryGetComponent(uid, out var mobState) && mobState.IsAlive()) @@ -279,7 +279,7 @@ namespace Content.Server.Nutrition.EntitySystems if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution)) return; - if (!_bodySystem.TryGetComponentsOnMechanisms(target, out var stomachs, body)) + if (!_bodySystem.TryGetBodyOrganComponents(target, out var stomachs, body)) return; if (food.UsesRemaining <= 0) diff --git a/Content.Server/Recycling/RecyclerSystem.cs b/Content.Server/Recycling/RecyclerSystem.cs index ee515b557f..06eb407259 100644 --- a/Content.Server/Recycling/RecyclerSystem.cs +++ b/Content.Server/Recycling/RecyclerSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Audio; +using Content.Server.Body.Systems; using Content.Server.GameTicking; using Content.Server.Players; using Content.Server.Popups; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Recycling.Components; using Content.Shared.Audio; @@ -14,7 +14,6 @@ using Content.Shared.Recycling; using Content.Shared.Tag; using Robust.Server.GameObjects; using Robust.Shared.Audio; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -25,6 +24,7 @@ namespace Content.Server.Recycling { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AmbientSoundSystem _ambience = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly TagSystem _tags = default!; @@ -57,9 +57,9 @@ namespace Content.Server.Recycling victim, Filter.Pvs(victim, entityManager: EntityManager).RemoveWhereAttachedEntity(e => e == victim)); - if (TryComp(victim, out var body)) + if (TryComp(victim, out var body)) { - body.Gib(true); + _bodySystem.GibBody(victim, true, body); } Bloodstain(component); @@ -104,7 +104,7 @@ namespace Content.Server.Recycling // Mobs are a special case! if (CanGib(component, entity)) { - Comp(entity).Gib(true); + _bodySystem.GibBody(entity, true, Comp(entity)); Bloodstain(component); return; } @@ -123,7 +123,7 @@ namespace Content.Server.Recycling private bool CanGib(RecyclerComponent component, EntityUid entity) { - return HasComp(entity) && !component.Safe && + return HasComp(entity) && !component.Safe && this.IsPowered(component.Owner, EntityManager); } diff --git a/Content.Server/Salvage/SalvageMobRestrictionsSystem.cs b/Content.Server/Salvage/SalvageMobRestrictionsSystem.cs index 6f95dff536..76b7d2dbed 100644 --- a/Content.Server/Salvage/SalvageMobRestrictionsSystem.cs +++ b/Content.Server/Salvage/SalvageMobRestrictionsSystem.cs @@ -1,13 +1,16 @@ -using Content.Shared.Damage; -using Content.Server.Body.Components; +using Content.Server.Body.Systems; using Content.Server.MobState; +using Content.Shared.Body.Components; +using Content.Shared.Damage; namespace Content.Server.Salvage; public sealed class SalvageMobRestrictionsSystem : EntitySystem { + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + public override void Initialize() { base.Initialize(); @@ -55,7 +58,7 @@ public sealed class SalvageMobRestrictionsSystem : EntitySystem if (bodyQuery.TryGetComponent(target, out var body)) { // Just because. - body.Gib(); + _bodySystem.GibBody(target, body: body); } else if (damageQuery.TryGetComponent(target, out var damageableComponent)) { diff --git a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs index cd7cf0e66f..d78dfa4baa 100644 --- a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs @@ -326,7 +326,7 @@ public sealed class EntityStorageSystem : EntitySystem if (attemptEvent.Cancelled) return false; - var targetIsMob = HasComp(toInsert); + var targetIsMob = HasComp(toInsert); var storageIsItem = HasComp(container); var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp(toInsert); diff --git a/Content.Server/Toilet/ToiletSystem.cs b/Content.Server/Toilet/ToiletSystem.cs index 1b0975784f..ec6291d883 100644 --- a/Content.Server/Toilet/ToiletSystem.cs +++ b/Content.Server/Toilet/ToiletSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Body.Systems; using Content.Server.Buckle.Components; using Content.Server.Buckle.Systems; using Content.Server.Popups; @@ -24,6 +25,7 @@ namespace Content.Server.Toilet { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SecretStashSystem _secretStash = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -48,8 +50,8 @@ namespace Content.Server.Toilet return; // Check that victim has a head - if (EntityManager.TryGetComponent(args.Victim, out var body) && - body.HasPartOfType(BodyPartType.Head)) + if (EntityManager.TryGetComponent(args.Victim, out var body) && + _bodySystem.BodyHasChildOfType(args.Victim, BodyPartType.Head, body)) { var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others", ("victim", Identity.Entity(args.Victim, EntityManager)), ("owner", uid)); diff --git a/Content.Shared/Body/Components/BodyComponent.cs b/Content.Shared/Body/Components/BodyComponent.cs new file mode 100644 index 0000000000..052f084c61 --- /dev/null +++ b/Content.Shared/Body/Components/BodyComponent.cs @@ -0,0 +1,36 @@ +using Content.Shared.Body.Part; +using Content.Shared.Body.Prototypes; +using Content.Shared.Body.Systems; +using Content.Shared.DragDrop; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Body.Components; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedBodySystem))] +public sealed class BodyComponent : Component, IDraggable +{ + [ViewVariables] + [DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public readonly string? Prototype; + + [ViewVariables] + [DataField("root")] + public BodyPartSlot Root = default!; + + [ViewVariables] + [DataField("gibSound")] + public SoundSpecifier GibSound = new SoundCollectionSpecifier("gib"); + + bool IDraggable.CanStartDrag(StartDragDropEvent args) + { + return true; + } + + bool IDraggable.CanDrop(CanDropEvent args) + { + return true; + } +} diff --git a/Content.Shared/Body/Components/BodyComponentState.cs b/Content.Shared/Body/Components/BodyComponentState.cs new file mode 100644 index 0000000000..32ea98e936 --- /dev/null +++ b/Content.Shared/Body/Components/BodyComponentState.cs @@ -0,0 +1,18 @@ +using Content.Shared.Body.Part; +using Robust.Shared.Audio; +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Components; + +[Serializable, NetSerializable] +public sealed class BodyComponentState : ComponentState +{ + public readonly BodyPartSlot Root; + public readonly SoundSpecifier GibSound; + + public BodyComponentState(BodyPartSlot root, SoundSpecifier gibSound) + { + Root = root; + GibSound = gibSound; + } +} diff --git a/Content.Shared/Body/Components/MechanismComponent.cs b/Content.Shared/Body/Components/MechanismComponent.cs deleted file mode 100644 index 34cd683d7f..0000000000 --- a/Content.Shared/Body/Components/MechanismComponent.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Shared.Body.Events; -using Content.Shared.Body.Part; -using Robust.Shared.Serialization; - -namespace Content.Shared.Body.Components -{ - [RegisterComponent] - public sealed class MechanismComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - private SharedBodyPartComponent? _part; - - public SharedBodyComponent? Body => Part?.Body; - - public SharedBodyPartComponent? Part - { - get => _part; - set - { - if (_part == value) - { - return; - } - - var old = _part; - _part = value; - - if (old != null) - { - if (old.Body == null) - { - _entMan.EventBus.RaiseLocalEvent(Owner, new RemovedFromPartEvent(old), true); - } - else - { - _entMan.EventBus.RaiseLocalEvent(Owner, new RemovedFromPartInBodyEvent(old.Body, old), true); - } - } - - if (value != null) - { - if (value.Body == null) - { - _entMan.EventBus.RaiseLocalEvent(Owner, new AddedToPartEvent(value), true); - } - else - { - _entMan.EventBus.RaiseLocalEvent(Owner, new AddedToPartInBodyEvent(value.Body, value), true); - } - } - } - } - - [DataField("maxDurability")] public int MaxDurability { get; set; } = 10; - - [DataField("currentDurability")] public int CurrentDurability { get; set; } = 10; - - [DataField("destroyThreshold")] public int DestroyThreshold { get; set; } = -10; - - // TODO BODY: Surgery description and adding a message to the examine tooltip of the entity that owns this mechanism - // TODO BODY - [DataField("resistance")] public int Resistance { get; set; } = 0; - - // TODO BODY OnSizeChanged - /// - /// Determines whether this - /// can fit into a . - /// - [DataField("size")] public int Size { get; set; } = 1; - - /// - /// What kind of this - /// can be easily installed into. - /// - [DataField("compatibility")] - public BodyPartCompatibility Compatibility { get; set; } = BodyPartCompatibility.Universal; - } -} diff --git a/Content.Shared/Body/Components/SharedBodyComponent.cs b/Content.Shared/Body/Components/SharedBodyComponent.cs deleted file mode 100644 index f6febaf012..0000000000 --- a/Content.Shared/Body/Components/SharedBodyComponent.cs +++ /dev/null @@ -1,457 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Shared.Body.Part; -using Content.Shared.Body.Prototypes; -using Content.Shared.Damage; -using Content.Shared.Damage.Prototypes; -using Content.Shared.Standing; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.Body.Components -{ - // TODO BODY Damage methods for collections of IDamageableComponents - - [NetworkedComponent()] - public abstract class SharedBodyComponent : Component, ISerializationHooks - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - [ViewVariables] - [DataField("template", required: true)] - private string? TemplateId { get; } - - [ViewVariables] - [DataField("preset", required: true)] - private string? PresetId { get; } - - [ViewVariables] - public BodyTemplatePrototype? Template => TemplateId == null - ? null - : _prototypeManager.Index(TemplateId); - - [ViewVariables] - public BodyPresetPrototype? Preset => PresetId == null - ? null - : _prototypeManager.Index(PresetId); - - [ViewVariables] - private Dictionary SlotIds { get; } = new(); - - [ViewVariables] - private Dictionary SlotParts { get; } = new(); - - [ViewVariables] - public IEnumerable Slots => SlotIds.Values; - - [ViewVariables] - public IEnumerable> Parts => SlotParts; - - public BodyPartSlot? CenterSlot => - Template?.CenterSlot is { } centerSlot - ? SlotIds.GetValueOrDefault(centerSlot) - : null; - - public SharedBodyPartComponent? CenterPart => CenterSlot?.Part; - - protected override void Initialize() - { - base.Initialize(); - - // TODO BODY BeforeDeserialization - // TODO BODY Move to template or somewhere else - if (TemplateId != null) - { - var template = _prototypeManager.Index(TemplateId); - - foreach (var (id, partType) in template.Slots) - { - SetSlot(id, partType); - } - - foreach (var (slotId, connectionIds) in template.Connections) - { - var connections = connectionIds.Select(id => SlotIds[id]); - SlotIds[slotId].SetConnectionsInternal(connections); - } - } - } - - protected override void OnRemove() - { - foreach (var slot in SlotIds.Values) - { - slot.Shutdown(); - } - - base.OnRemove(); - } - - private BodyPartSlot SetSlot(string id, BodyPartType type) - { - var slot = new BodyPartSlot(id, type); - - SlotIds[id] = slot; - slot.PartAdded += part => OnAddPart(slot, part); - slot.PartRemoved += part => OnRemovePart(slot, part); - - return slot; - } - - private Dictionary GetHangingParts(BodyPartSlot from) - { - var hanging = new Dictionary(); - - foreach (var connection in from.Connections) - { - if (connection.Part != null && - !ConnectedToCenter(connection.Part)) - { - hanging.Add(connection, connection.Part); - } - } - - return hanging; - } - - protected virtual bool CanAddPart(string slotId, SharedBodyPartComponent part) - { - if (!SlotIds.TryGetValue(slotId, out var slot) || - slot.CanAddPart(part)) - { - return false; - } - - return true; - } - - protected virtual void OnAddPart(BodyPartSlot slot, SharedBodyPartComponent part) - { - SlotParts[part] = slot; - part.Body = this; - - var argsAdded = new BodyPartAddedEventArgs(slot.Id, part); - - // TODO: Body refactor. Somebody is doing it - // EntitySystem.Get().BodyPartAdded(Owner, argsAdded); - foreach (var component in IoCManager.Resolve().GetComponents(Owner).ToArray()) - { - component.BodyPartAdded(argsAdded); - } - - // TODO BODY Sort this duplicate out - OnBodyChanged(); - } - - protected virtual void OnRemovePart(BodyPartSlot slot, SharedBodyPartComponent part) - { - SlotParts.Remove(part); - - foreach (var connectedSlot in slot.Connections) - { - if (connectedSlot.Part != null && - !ConnectedToCenter(connectedSlot.Part)) - { - RemovePart(connectedSlot.Part); - } - } - - part.Body = null; - - var args = new BodyPartRemovedEventArgs(slot.Id, part); - - - // TODO: Body refactor. Somebody is doing it - // EntitySystem.Get().BodyPartRemoved(Owner, args); - foreach (var component in IoCManager.Resolve().GetComponents(Owner)) - { - component.BodyPartRemoved(args); - } - - // creadth: fall down if no legs - if (part.PartType == BodyPartType.Leg && - GetPartsOfType(BodyPartType.Leg).ToArray().Length == 0) - { - EntitySystem.Get().Down(Owner); - } - - if (part.IsVital && SlotParts.Count(x => x.Value.PartType == part.PartType) == 0) - { - // TODO BODY SYSTEM KILL : Find a more elegant way of killing em than just dumping bloodloss damage. - var damage = new DamageSpecifier(_prototypeManager.Index("Bloodloss"), 300); - EntitySystem.Get().TryChangeDamage(part.Owner, damage); - } - - OnBodyChanged(); - } - - // TODO BODY Sensible templates - public bool TryAddPart(string slotId, SharedBodyPartComponent part) - { - DebugTools.AssertNotNull(part); - DebugTools.AssertNotNull(slotId); - - if (!CanAddPart(slotId, part)) - { - return false; - } - - return SlotIds.TryGetValue(slotId, out var slot) && - slot.TryAddPart(part); - } - - public void SetPart(string slotId, SharedBodyPartComponent part) - { - if (!SlotIds.TryGetValue(slotId, out var slot)) - { - slot = SetSlot(slotId, part.PartType); - SlotIds[slotId] = slot; - } - - slot.SetPart(part); - } - - public bool HasPart(SharedBodyPartComponent part) - { - DebugTools.AssertNotNull(part); - - return SlotParts.ContainsKey(part); - } - - public bool RemovePart(SharedBodyPartComponent part) - { - DebugTools.AssertNotNull(part); - - return SlotParts.TryGetValue(part, out var slot) && - slot.RemovePart(); - } - - public bool TryDropPart(BodyPartSlot slot, [NotNullWhen(true)] out Dictionary? dropped) - { - DebugTools.AssertNotNull(slot); - - if (!SlotIds.TryGetValue(slot.Id, out var ownedSlot) || - ownedSlot != slot || - slot.Part == null) - { - dropped = null; - return false; - } - - var oldPart = slot.Part; - dropped = GetHangingParts(slot); - - if (!slot.RemovePart()) - { - dropped = null; - return false; - } - - dropped[slot] = oldPart; - return true; - } - - public bool ConnectedToCenter(SharedBodyPartComponent part) - { - return TryGetSlot(part, out var result) && - ConnectedToCenterPartRecursion(result); - } - - private bool ConnectedToCenterPartRecursion(BodyPartSlot slot, HashSet? searched = null) - { - searched ??= new HashSet(); - - if (Template?.CenterSlot == null) - { - return false; - } - - if (slot.Part == CenterPart) - { - return true; - } - - searched.Add(slot); - - foreach (var connection in slot.Connections) - { - if (!searched.Contains(connection) && - ConnectedToCenterPartRecursion(connection, searched)) - { - return true; - } - } - - return false; - } - - public BodyPartSlot? GetSlot(SharedBodyPartComponent part) - { - return SlotParts.GetValueOrDefault(part); - } - - public bool TryGetSlot(SharedBodyPartComponent part, [NotNullWhen(true)] out BodyPartSlot? slot) - { - return (slot = GetSlot(part)) != null; - } - - public IEnumerable GetSlotsOfType(BodyPartType type) - { - foreach (var slot in SlotIds.Values) - { - if (slot.PartType == type) - { - yield return slot; - } - } - } - - public bool HasPartOfType(BodyPartType type) - { - foreach (var _ in GetPartsOfType(type)) - { - return true; - } - - return false; - } - - public IEnumerable GetPartsOfType(BodyPartType type) - { - foreach (var slot in GetSlotsOfType(type)) - { - if (slot.Part != null) - { - yield return slot.Part; - } - } - } - - private void OnBodyChanged() - { - Dirty(); - } - - // TODO BODY optimize this - public BodyPartSlot SlotAt(int index) - { - return SlotIds.Values.ElementAt(index); - } - - public KeyValuePair PartAt(int index) - { - return SlotParts.ElementAt(index); - } - - public override ComponentState GetComponentState() - { - var parts = new (string slot, EntityUid partId)[SlotParts.Count]; - - var i = 0; - foreach (var (part, slot) in SlotParts) - { - parts[i] = (slot.Id, part.Owner); - i++; - } - - return new BodyComponentState(parts); - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not BodyComponentState state) - { - return; - } - - var newParts = state.Parts(); - - foreach (var (oldPart, slot) in SlotParts) - { - if (!newParts.TryGetValue(slot.Id, out var newPart) || - newPart != oldPart) - { - RemovePart(oldPart); - } - } - - foreach (var (slotId, newPart) in newParts) - { - if (!SlotIds.TryGetValue(slotId, out var slot) || - slot.Part != newPart) - { - SetPart(slotId, newPart); - } - } - } - - public virtual HashSet Gib(bool gibParts = false) - { - var entMgr = IoCManager.Resolve(); - var metaQuery = entMgr.GetEntityQuery(); - var gibs = new HashSet(); - foreach (var part in SlotParts.Keys) - { - if (!metaQuery.TryGetComponent(part.Owner, out var meta) || - meta.EntityLifeStage >= EntityLifeStage.Terminating) - { - SlotParts.Remove(part); - continue; - } - gibs.Add(part.Owner); - RemovePart(part); - - if (gibParts) - gibs.UnionWith(part.Gib()); - } - - return gibs; - } - } - - [Serializable, NetSerializable] - public sealed class BodyComponentState : ComponentState - { - private Dictionary? _parts; - - public readonly (string slot, EntityUid partId)[] PartIds; - - public BodyComponentState((string slot, EntityUid partId)[] partIds) - { - PartIds = partIds; - } - - public Dictionary Parts(IEntityManager? entityManager = null) - { - if (_parts != null) - { - return _parts; - } - - entityManager ??= IoCManager.Resolve(); - - var parts = new Dictionary(PartIds.Length); - - foreach (var (slot, partId) in PartIds) - { - if (!entityManager.EntityExists(partId)) - { - continue; - } - - if (!entityManager.TryGetComponent(partId, out SharedBodyPartComponent? part)) - { - continue; - } - - parts[slot] = part; - } - - return _parts = parts; - } - } -} diff --git a/Content.Shared/Body/Components/SharedBodyPartComponent.cs b/Content.Shared/Body/Components/SharedBodyPartComponent.cs deleted file mode 100644 index fcf67afbc5..0000000000 --- a/Content.Shared/Body/Components/SharedBodyPartComponent.cs +++ /dev/null @@ -1,345 +0,0 @@ -using System.Linq; -using Content.Shared.Body.Events; -using Content.Shared.Body.Part; -using Robust.Shared.GameStates; -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.Body.Components -{ - [NetworkedComponent()] - public abstract class SharedBodyPartComponent : Component - { - public const string ContainerId = "bodypart"; - - [Dependency] private readonly IEntityManager _entMan = default!; - private SharedBodyComponent? _body; - - // TODO BODY Remove - [DataField("mechanisms")] - private readonly List _mechanismIds = new(); - public IReadOnlyList MechanismIds => _mechanismIds; - - [ViewVariables] - private readonly HashSet _mechanisms = new(); - - [ViewVariables] - public SharedBodyComponent? Body - { - get => _body; - set - { - if (_body == value) - { - return; - } - - var old = _body; - _body = value; - - if (old != null) - { - RemovedFromBody(old); - } - - if (value != null) - { - AddedToBody(value); - } - } - } - - /// - /// that this is considered - /// to be. - /// For example, . - /// - [ViewVariables] - [DataField("partType")] - public BodyPartType PartType { get; private set; } = BodyPartType.Other; - - /// - /// Determines how many mechanisms can be fit inside this - /// . - /// - [ViewVariables] [DataField("size")] public int Size { get; private set; } = 1; - - [ViewVariables] public int SizeUsed { get; private set; } - - // TODO BODY size used - // TODO BODY surgerydata - - /// - /// What types of BodyParts this can easily attach to. - /// For the most part, most limbs aren't universal and require extra work to - /// attach between types. - /// - [ViewVariables] - [DataField("compatibility")] - public BodyPartCompatibility Compatibility = BodyPartCompatibility.Universal; - - // TODO BODY Mechanisms occupying different parts at the body level - [ViewVariables] - public IReadOnlyCollection Mechanisms => _mechanisms; - - // TODO BODY Replace with a simulation of organs - /// - /// Whether or not the owning will die if all - /// s of this type are removed from it. - /// - [ViewVariables] - [DataField("vital")] - public bool IsVital = false; - - [ViewVariables] - [DataField("symmetry")] - public BodyPartSymmetry Symmetry = BodyPartSymmetry.None; - - protected virtual void OnAddMechanism(MechanismComponent mechanism) - { - var prototypeId = _entMan.GetComponent(mechanism.Owner).EntityPrototype!.ID; - - if (!_mechanismIds.Contains(prototypeId)) - { - _mechanismIds.Add(prototypeId); - } - - mechanism.Part = this; - SizeUsed += mechanism.Size; - - Dirty(); - } - - protected virtual void OnRemoveMechanism(MechanismComponent mechanism) - { - _mechanismIds.Remove(_entMan.GetComponent(mechanism.Owner).EntityPrototype!.ID); - mechanism.Part = null; - SizeUsed -= mechanism.Size; - - Dirty(); - } - - public override ComponentState GetComponentState() - { - var mechanismIds = new EntityUid[_mechanisms.Count]; - - var i = 0; - foreach (var mechanism in _mechanisms) - { - mechanismIds[i] = mechanism.Owner; - i++; - } - - return new BodyPartComponentState(mechanismIds); - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not BodyPartComponentState state) - { - return; - } - - var newMechanisms = state.Mechanisms(); - - foreach (var mechanism in _mechanisms.ToArray()) - { - if (!newMechanisms.Contains(mechanism)) - { - RemoveMechanism(mechanism); - } - } - - foreach (var mechanism in newMechanisms) - { - if (!_mechanisms.Contains(mechanism)) - { - TryAddMechanism(mechanism, true); - } - } - } - - public virtual bool CanAddMechanism(MechanismComponent mechanism) - { - return SizeUsed + mechanism.Size <= Size; - } - - /// - /// Tries to add a to this part. - /// - /// The mechanism to add. - /// - /// Whether or not to check if the mechanism is compatible. - /// Passing true does not guarantee it to be added, for example if - /// it was already added before. - /// - /// true if added, false otherwise even if it was already added. - public bool TryAddMechanism(MechanismComponent mechanism, bool force = false) - { - DebugTools.AssertNotNull(mechanism); - - if (!force && !CanAddMechanism(mechanism)) - { - return false; - } - - if (!_mechanisms.Add(mechanism)) - { - return false; - } - - OnAddMechanism(mechanism); - - return true; - } - - /// - /// Tries to remove the given from this part. - /// - /// The mechanism to remove. - /// True if it was removed, false otherwise. - public bool RemoveMechanism(MechanismComponent mechanism) - { - DebugTools.AssertNotNull(mechanism); - - if (!_mechanisms.Remove(mechanism)) - { - return false; - } - - OnRemoveMechanism(mechanism); - - return true; - } - - /// - /// Tries to remove the given from this - /// part and drops it at the specified coordinates. - /// - /// The mechanism to remove. - /// The coordinates to drop it at. - /// True if it was removed, false otherwise. - public bool RemoveMechanism(MechanismComponent mechanism, EntityCoordinates coordinates) - { - if (RemoveMechanism(mechanism)) - { - _entMan.GetComponent(mechanism.Owner).Coordinates = coordinates; - return true; - } - - return false; - } - - /// - /// Tries to destroy the given from - /// this part. - /// The mechanism won't be deleted if it is not in this body part. - /// - /// - /// True if the mechanism was in this body part and destroyed, - /// false otherwise. - /// - public bool DeleteMechanism(MechanismComponent mechanism) - { - DebugTools.AssertNotNull(mechanism); - - if (!RemoveMechanism(mechanism)) - { - return false; - } - - _entMan.DeleteEntity(mechanism.Owner); - return true; - } - - private void AddedToBody(SharedBodyComponent body) - { - OnAddedToBody(body); - - foreach (var mechanism in _mechanisms) - { - _entMan.EventBus.RaiseLocalEvent(mechanism.Owner, new AddedToBodyEvent(body), true); - } - } - - private void RemovedFromBody(SharedBodyComponent old) - { - if (_entMan.TryGetComponent(Owner, out var transformComponent)) - { - transformComponent.AttachToGridOrMap(); - } - - OnRemovedFromBody(old); - - foreach (var mechanism in _mechanisms) - { - _entMan.EventBus.RaiseLocalEvent(mechanism.Owner, new RemovedFromBodyEvent(old), true); - } - } - - protected virtual void OnAddedToBody(SharedBodyComponent body) { } - - protected virtual void OnRemovedFromBody(SharedBodyComponent old) { } - - /// - /// Gibs the body part. - /// - public virtual HashSet Gib() - { - var gibs = new HashSet(); - - foreach (var mechanism in _mechanisms.ToArray()) - { - gibs.Add(mechanism.Owner); - RemoveMechanism(mechanism); - } - - return gibs; - } - } - - [Serializable, NetSerializable] - public sealed class BodyPartComponentState : ComponentState - { - [NonSerialized] private List? _mechanisms; - - public readonly EntityUid[] MechanismIds; - - public BodyPartComponentState(EntityUid[] mechanismIds) - { - MechanismIds = mechanismIds; - } - - public List Mechanisms(IEntityManager? entityManager = null) - { - if (_mechanisms != null) - { - return _mechanisms; - } - - IoCManager.Resolve(ref entityManager); - - var mechanisms = new List(MechanismIds.Length); - - foreach (var id in MechanismIds) - { - if (!entityManager.EntityExists(id)) - { - continue; - } - - if (!entityManager.TryGetComponent(id, out MechanismComponent? mechanism)) - { - continue; - } - - mechanisms.Add(mechanism); - } - - return _mechanisms = mechanisms; - } - } -} diff --git a/Content.Shared/Body/Events/MechanismBodyEvents.cs b/Content.Shared/Body/Events/MechanismBodyEvents.cs index f14b346621..917d687a0f 100644 --- a/Content.Shared/Body/Events/MechanismBodyEvents.cs +++ b/Content.Shared/Body/Events/MechanismBodyEvents.cs @@ -1,6 +1,4 @@ -using Content.Shared.Body.Components; - -namespace Content.Shared.Body.Events +namespace Content.Shared.Body.Events { // All of these events are raised on a mechanism entity when added/removed to a body in different // ways. @@ -10,9 +8,9 @@ namespace Content.Shared.Body.Events /// public sealed class AddedToBodyEvent : EntityEventArgs { - public SharedBodyComponent Body; + public EntityUid Body; - public AddedToBodyEvent(SharedBodyComponent body) + public AddedToBodyEvent(EntityUid body) { Body = body; } @@ -23,9 +21,9 @@ namespace Content.Shared.Body.Events /// public sealed class AddedToPartEvent : EntityEventArgs { - public SharedBodyPartComponent Part; + public EntityUid Part; - public AddedToPartEvent(SharedBodyPartComponent part) + public AddedToPartEvent(EntityUid part) { Part = part; } @@ -36,10 +34,10 @@ namespace Content.Shared.Body.Events /// public sealed class AddedToPartInBodyEvent : EntityEventArgs { - public SharedBodyComponent Body; - public SharedBodyPartComponent Part; + public EntityUid Body; + public EntityUid Part; - public AddedToPartInBodyEvent(SharedBodyComponent body, SharedBodyPartComponent part) + public AddedToPartInBodyEvent(EntityUid body, EntityUid part) { Body = body; Part = part; @@ -51,9 +49,9 @@ namespace Content.Shared.Body.Events /// public sealed class RemovedFromBodyEvent : EntityEventArgs { - public SharedBodyComponent Old; + public EntityUid Old; - public RemovedFromBodyEvent(SharedBodyComponent old) + public RemovedFromBodyEvent(EntityUid old) { Old = old; } @@ -64,9 +62,9 @@ namespace Content.Shared.Body.Events /// public sealed class RemovedFromPartEvent : EntityEventArgs { - public SharedBodyPartComponent Old; + public EntityUid Old; - public RemovedFromPartEvent(SharedBodyPartComponent old) + public RemovedFromPartEvent(EntityUid old) { Old = old; } @@ -77,10 +75,10 @@ namespace Content.Shared.Body.Events /// public sealed class RemovedFromPartInBodyEvent : EntityEventArgs { - public SharedBodyComponent OldBody; - public SharedBodyPartComponent OldPart; + public EntityUid OldBody; + public EntityUid OldPart; - public RemovedFromPartInBodyEvent(SharedBodyComponent oldBody, SharedBodyPartComponent oldPart) + public RemovedFromPartInBodyEvent(EntityUid oldBody, EntityUid oldPart) { OldBody = oldBody; OldPart = oldPart; diff --git a/Content.Shared/Body/Organ/OrganComponent.cs b/Content.Shared/Body/Organ/OrganComponent.cs new file mode 100644 index 0000000000..a01c8d8537 --- /dev/null +++ b/Content.Shared/Body/Organ/OrganComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Body.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedBodySystem))] +public sealed class OrganComponent : Component +{ + [ViewVariables] + [DataField("body")] + public EntityUid? Body; + + [ViewVariables] + [DataField("parent")] + public OrganSlot? ParentSlot; +} diff --git a/Content.Shared/Body/Organ/OrganComponentState.cs b/Content.Shared/Body/Organ/OrganComponentState.cs new file mode 100644 index 0000000000..821c2c2e5b --- /dev/null +++ b/Content.Shared/Body/Organ/OrganComponentState.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Organ; + +[Serializable, NetSerializable] +public sealed class OrganComponentState : ComponentState +{ + public readonly EntityUid? Body; + public readonly OrganSlot? Parent; + + public OrganComponentState(EntityUid? body, OrganSlot? parent) + { + Body = body; + Parent = parent; + } +} diff --git a/Content.Shared/Body/Organ/OrganSlot.cs b/Content.Shared/Body/Organ/OrganSlot.cs new file mode 100644 index 0000000000..924e4f8aaf --- /dev/null +++ b/Content.Shared/Body/Organ/OrganSlot.cs @@ -0,0 +1,20 @@ +using Content.Shared.Body.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Organ; + +[Serializable, NetSerializable] +[Access(typeof(SharedBodySystem))] +[DataRecord] +public sealed record OrganSlot(string Id, EntityUid Parent) +{ + public EntityUid? Child { get; set; } + + // Rider doesn't suggest explicit properties during deconstruction without this + public void Deconstruct(out EntityUid? child, out string id, out EntityUid parent) + { + child = Child; + id = Id; + parent = Parent; + } +} diff --git a/Content.Shared/Body/Part/BodyPartCompatibility.cs b/Content.Shared/Body/Part/BodyPartCompatibility.cs deleted file mode 100644 index 1b15d63ba2..0000000000 --- a/Content.Shared/Body/Part/BodyPartCompatibility.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Body.Components; -using Robust.Shared.Serialization; - -namespace Content.Shared.Body.Part -{ - //TODO: This should be a prototype. --DrSmugleaf - /// - /// Determines whether two s can connect. - /// - [Serializable, NetSerializable] - public enum BodyPartCompatibility - { - Universal = 0, - Biological, - Mechanical, - Slime, - } -} diff --git a/Content.Shared/Body/Part/BodyPartComponent.cs b/Content.Shared/Body/Part/BodyPartComponent.cs new file mode 100644 index 0000000000..ed4fdb7b59 --- /dev/null +++ b/Content.Shared/Body/Part/BodyPartComponent.cs @@ -0,0 +1,44 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Part; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedBodySystem))] +public sealed class BodyPartComponent : Component +{ + [ViewVariables] + [DataField("body")] + public EntityUid? Body; + + [ViewVariables] + [DataField("parent")] + public BodyPartSlot? ParentSlot; + + [ViewVariables] + [DataField("children")] + public Dictionary Children = new(); + + [ViewVariables] + [DataField("organs")] + public Dictionary Organs = new(); + + [ViewVariables] + [DataField("partType")] + public BodyPartType PartType = BodyPartType.Other; + + // TODO BODY Replace with a simulation of organs + /// + /// Whether or not the owning will die if all + /// s of this type are removed from it. + /// + [ViewVariables] + [DataField("vital")] + public bool IsVital; + + [ViewVariables] + [DataField("symmetry")] + public BodyPartSymmetry Symmetry = BodyPartSymmetry.None; +} diff --git a/Content.Shared/Body/Part/BodyPartComponentState.cs b/Content.Shared/Body/Part/BodyPartComponentState.cs new file mode 100644 index 0000000000..bd3cf55efd --- /dev/null +++ b/Content.Shared/Body/Part/BodyPartComponentState.cs @@ -0,0 +1,34 @@ +using Content.Shared.Body.Organ; +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Part; + +[Serializable, NetSerializable] +public sealed class BodyPartComponentState : ComponentState +{ + public readonly EntityUid? Body; + public readonly BodyPartSlot? ParentSlot; + public readonly Dictionary Children; + public readonly Dictionary Organs; + public readonly BodyPartType PartType; + public readonly bool IsVital; + public readonly BodyPartSymmetry Symmetry; + + public BodyPartComponentState( + EntityUid? body, + BodyPartSlot? parentSlot, + Dictionary children, + Dictionary organs, + BodyPartType partType, + bool isVital, + BodyPartSymmetry symmetry) + { + ParentSlot = parentSlot; + Children = children; + Organs = organs; + PartType = partType; + IsVital = isVital; + Symmetry = symmetry; + Body = body; + } +} diff --git a/Content.Shared/Body/Part/BodyPartSlot.cs b/Content.Shared/Body/Part/BodyPartSlot.cs index c3193be82f..da80a6d832 100644 --- a/Content.Shared/Body/Part/BodyPartSlot.cs +++ b/Content.Shared/Body/Part/BodyPartSlot.cs @@ -1,104 +1,21 @@ -using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Robust.Shared.Serialization; -namespace Content.Shared.Body.Part +namespace Content.Shared.Body.Part; + +[Serializable, NetSerializable] +[Access(typeof(SharedBodySystem))] +[DataRecord] +public sealed record BodyPartSlot(string Id, EntityUid Parent, BodyPartType? Type) { - public sealed class BodyPartSlot + public EntityUid? Child { get; set; } + + // Rider doesn't suggest explicit properties during deconstruction without this + public void Deconstruct(out EntityUid? child, out string id, out EntityUid parent, out BodyPartType? type) { - public BodyPartSlot(string id, BodyPartType partType, IEnumerable connections) - { - Id = id; - PartType = partType; - Connections = new HashSet(connections); - } - - public BodyPartSlot(string id, BodyPartType partType) - { - Id = id; - PartType = partType; - Connections = new HashSet(); - } - - /// - /// The ID of this slot. - /// - [ViewVariables] - public string Id { get; } - - /// - /// The part type that this slot accepts. - /// - [ViewVariables] - public BodyPartType PartType { get; } - - /// - /// The part currently in this slot, if any. - /// - [ViewVariables] - public SharedBodyPartComponent? Part { get; private set; } - - /// - /// List of slots that this slot connects to. - /// - [ViewVariables] - public HashSet Connections { get; private set; } - - public event Action? PartAdded; - - public event Action? PartRemoved; - - internal void SetConnectionsInternal(IEnumerable connections) - { - Connections = new HashSet(connections); - } - - public bool CanAddPart(SharedBodyPartComponent part) - { - return Part == null && part.PartType == PartType; - } - - public bool TryAddPart(SharedBodyPartComponent part) - { - if (!CanAddPart(part)) - { - return false; - } - - SetPart(part); - return true; - } - - public void SetPart(SharedBodyPartComponent part) - { - if (Part != null) - { - RemovePart(); - } - - Part = part; - PartAdded?.Invoke(part); - } - - public bool RemovePart() - { - if (Part == null) - { - return false; - } - - var old = Part; - Part = null; - - PartRemoved?.Invoke(old); - - return true; - } - - public void Shutdown() - { - Part = null; - Connections.Clear(); - PartAdded = null; - PartRemoved = null; - } + child = Child; + id = Id; + parent = Parent; + type = Type; } } diff --git a/Content.Shared/Body/Part/BodyPartSymmetry.cs b/Content.Shared/Body/Part/BodyPartSymmetry.cs index 52e2e7f71d..aa00869823 100644 --- a/Content.Shared/Body/Part/BodyPartSymmetry.cs +++ b/Content.Shared/Body/Part/BodyPartSymmetry.cs @@ -4,7 +4,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.Body.Part { /// - /// Defines the symmetry of a . + /// Defines the symmetry of a . /// [Serializable, NetSerializable] public enum BodyPartSymmetry diff --git a/Content.Shared/Body/Part/BodyPartType.cs b/Content.Shared/Body/Part/BodyPartType.cs index 7f90070859..acaee76fd8 100644 --- a/Content.Shared/Body/Part/BodyPartType.cs +++ b/Content.Shared/Body/Part/BodyPartType.cs @@ -4,7 +4,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.Body.Part { /// - /// Defines the type of a . + /// Defines the type of a . /// [Serializable, NetSerializable] public enum BodyPartType diff --git a/Content.Shared/Body/Part/IBodyPartAdded.cs b/Content.Shared/Body/Part/IBodyPartAdded.cs index 8e02d34cdd..a2219905f9 100644 --- a/Content.Shared/Body/Part/IBodyPartAdded.cs +++ b/Content.Shared/Body/Part/IBodyPartAdded.cs @@ -1,5 +1,4 @@ using Content.Shared.Body.Components; -using Robust.Shared.Serialization; namespace Content.Shared.Body.Part { @@ -10,18 +9,16 @@ namespace Content.Shared.Body.Part public interface IBodyPartAdded : IComponent { /// - /// Called when a is added to the + /// Called when a is added to the /// entity owning this component. /// /// Information about the part that was added. void BodyPartAdded(BodyPartAddedEventArgs args); } - - [Serializable, NetSerializable] public sealed class BodyPartAddedEventArgs : EventArgs { - public BodyPartAddedEventArgs(string slot, SharedBodyPartComponent part) + public BodyPartAddedEventArgs(string slot, BodyPartComponent part) { Slot = slot; Part = part; @@ -35,6 +32,6 @@ namespace Content.Shared.Body.Part /// /// The part that was added. /// - public SharedBodyPartComponent Part { get; } + public BodyPartComponent Part { get; } } } diff --git a/Content.Shared/Body/Part/IBodyPartRemoved.cs b/Content.Shared/Body/Part/IBodyPartRemoved.cs index 500d205560..0a88f35f03 100644 --- a/Content.Shared/Body/Part/IBodyPartRemoved.cs +++ b/Content.Shared/Body/Part/IBodyPartRemoved.cs @@ -1,5 +1,4 @@ using Content.Shared.Body.Components; -using Robust.Shared.Serialization; namespace Content.Shared.Body.Part { @@ -10,17 +9,16 @@ namespace Content.Shared.Body.Part public interface IBodyPartRemoved { /// - /// Called when a is removed from the + /// Called when a is removed from the /// entity owning this component. /// /// Information about the part that was removed. void BodyPartRemoved(BodyPartRemovedEventArgs args); } - [Serializable, NetSerializable] public sealed class BodyPartRemovedEventArgs : EventArgs { - public BodyPartRemovedEventArgs(string slot, SharedBodyPartComponent part) + public BodyPartRemovedEventArgs(string slot, BodyPartComponent part) { Slot = slot; Part = part; @@ -34,6 +32,6 @@ namespace Content.Shared.Body.Part /// /// The part that was removed. /// - public SharedBodyPartComponent Part { get; } + public BodyPartComponent Part { get; } } } diff --git a/Content.Shared/Body/Prototypes/BodyPresetPrototype.cs b/Content.Shared/Body/Prototypes/BodyPresetPrototype.cs deleted file mode 100644 index 0af30a9dfc..0000000000 --- a/Content.Shared/Body/Prototypes/BodyPresetPrototype.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; - -namespace Content.Shared.Body.Prototypes -{ - /// - /// Defines the parts used in a body. - /// - [Prototype("bodyPreset")] - [Serializable, NetSerializable] - public sealed class BodyPresetPrototype : IPrototype - { - [ViewVariables] - [IdDataFieldAttribute] - public string ID { get; } = default!; - - [DataField("partIDs")] - private Dictionary _partIDs = new(); - - [ViewVariables] - [DataField("name")] - public string Name { get; } = string.Empty; - - [ViewVariables] - public Dictionary PartIDs => new(_partIDs); - } -} diff --git a/Content.Shared/Body/Prototypes/BodyPrototype.cs b/Content.Shared/Body/Prototypes/BodyPrototype.cs new file mode 100644 index 0000000000..3cffe46e6e --- /dev/null +++ b/Content.Shared/Body/Prototypes/BodyPrototype.cs @@ -0,0 +1,54 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Body.Prototypes; + +[Prototype("body")] +public sealed class BodyPrototype : IPrototype +{ + [IdDataField] public string ID { get; } = default!; + + private string _name = string.Empty; + + [DataField("name")] + public string Name + { + get => _name; + private set => _name = Loc.GetString(value); + } + + [DataField("root")] public string Root { get; } = string.Empty; + + [DataField("slots")] public Dictionary Slots { get; } = new(); + + private BodyPrototype() { } + + public BodyPrototype(string id, string name, string root, Dictionary slots) + { + ID = id; + Name = name; + Root = root; + Slots = slots; + } +} + +[DataRecord] +public sealed record BodyPrototypeSlot +{ + [DataField("part", required: true)] public readonly string Part = default!; + public readonly HashSet Connections = new(); + public readonly Dictionary Organs = new(); + + public BodyPrototypeSlot(string part, HashSet? connections, Dictionary? organs) + { + Part = part; + Connections = connections ?? new HashSet(); + Organs = organs ?? new Dictionary(); + } + + public void Deconstruct(out string part, out HashSet connections, out Dictionary organs) + { + part = Part; + connections = Connections; + organs = Organs; + } +} diff --git a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs new file mode 100644 index 0000000000..c7b4981632 --- /dev/null +++ b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs @@ -0,0 +1,191 @@ +using System.Linq; +using Content.Shared.Body.Organ; +using Content.Shared.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Content.Shared.Body.Prototypes; + +[TypeSerializer] +public sealed class BodyPrototypeSerializer : ITypeReader +{ + private (ValidationNode Node, List Connections) ValidateSlot(ISerializationManager serializationManager, MappingDataNode slot, string slotId, IDependencyCollection dependencies) + { + var nodes = new List(); + var prototypes = dependencies.Resolve(); + if (!slot.TryGet("part", out ValueDataNode? part)) + { + nodes.Add(new ErrorNode(slot, $"No part value data node found in root slot {slotId}")); + } + else if (!prototypes.HasIndex(part.Value)) + { + nodes.Add(new ErrorNode(slot, $"No entity prototype found with id {part.Value} for root slot {slotId}")); + } + + var connections = new List(); + if (slot.TryGet("connections", out SequenceDataNode? connectionsNode)) + { + foreach (var node in connectionsNode) + { + if (node is not ValueDataNode connection) + { + nodes.Add(new ErrorNode(node, $"Connection is not a value data node")); + continue; + } + + connections.Add(connection.Value); + } + } + + if (slot.TryGet("organs", out MappingDataNode? organsNode)) + { + foreach (var (key, value) in organsNode) + { + if (key is not ValueDataNode) + { + nodes.Add(new ErrorNode(key, $"Key is not a value data node")); + continue; + } + + if (value is not ValueDataNode organ) + { + nodes.Add(new ErrorNode(value, $"Value is not a value data node")); + continue; + } + + if (!prototypes.TryIndex(organ.Value, out EntityPrototype? organPrototype)) + { + nodes.Add(new ErrorNode(value, $"No organ entity prototype found with id {organ.Value}")); + continue; + } + + if (!organPrototype.HasComponent()) + { + 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(); + if (!node.TryGet("id", out ValueDataNode? id)) + nodes.Add(new ErrorNode(node, "No id value data node found")); + + if (!node.TryGet("root", out ValueDataNode? root)) + nodes.Add(new ErrorNode(node, $"No root value data node found")); + + if (!node.TryGet("slots", out MappingDataNode? slots)) + { + nodes.Add(new ErrorNode(node, $"No slots mapping data node found")); + } + else if (root != null) + { + if (!slots.TryGet(root.Value, out MappingDataNode? _)) + { + nodes.Add(new ErrorNode(slots, $"No slot found with id {root.Value}")); + return new ValidatedSequenceNode(nodes); + } + + foreach (var (key, value) in slots) + { + if (key is not ValueDataNode) + { + nodes.Add(new ErrorNode(key, $"Key is not a value data node")); + continue; + } + + if (value is not MappingDataNode slot) + { + nodes.Add(new ErrorNode(value, $"Slot is not a mapping data node")); + continue; + } + + var result = ValidateSlot(serializationManager, slot, root.Value, dependencies); + nodes.Add(result.Node); + + foreach (var connection in result.Connections) + { + if (!slots.TryGet(connection, out MappingDataNode? _)) + nodes.Add(new ErrorNode(slots, $"No slot found with id {connection}")); + } + } + } + + return new ValidatedSequenceNode(nodes); + } + + public BodyPrototype Read(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies, + bool skipHook, ISerializationContext? context = null, BodyPrototype? value = default) + { + var id = node.Get("id").Value; + var name = node.Get("name").Value; + var root = node.Get("root").Value; + var slotNodes = node.Get("slots"); + var allConnections = new Dictionary? Connections, Dictionary? Organs)>(); + + foreach (var (keyNode, valueNode) in slotNodes) + { + var slotId = ((ValueDataNode) keyNode).Value; + var slot = ((MappingDataNode) valueNode); + var part = slot.Get("part").Value; + + HashSet? connections = null; + if (slot.TryGet("connections", out SequenceDataNode? slotConnectionsNode)) + { + connections = new HashSet(); + + foreach (var connection in slotConnectionsNode.Cast()) + { + connections.Add(connection.Value); + } + } + + Dictionary? organs = null; + if (slot.TryGet("organs", out MappingDataNode? slotOrgansNode)) + { + organs = new Dictionary(); + + foreach (var (organKeyNode, organValueNode) in slotOrgansNode) + { + organs.Add(((ValueDataNode) organKeyNode).Value, ((ValueDataNode) organValueNode).Value); + } + } + + allConnections.Add(slotId, (part, connections, organs)); + } + + foreach (var (slotId, (_, connections, _)) in allConnections) + { + if (connections == null) + continue; + + foreach (var connection in connections) + { + var other = allConnections[connection]; + other.Connections ??= new HashSet(); + other.Connections.Add(slotId); + allConnections[connection] = other; + } + } + + var slots = new Dictionary(); + foreach (var (slotId, (part, connections, organs)) in allConnections) + { + var slot = new BodyPrototypeSlot(part, connections, organs); + slots.Add(slotId, slot); + } + + return new BodyPrototype(id, name, root, slots); + } +} diff --git a/Content.Shared/Body/Prototypes/BodyTemplatePrototype.cs b/Content.Shared/Body/Prototypes/BodyTemplatePrototype.cs deleted file mode 100644 index 64e03a4869..0000000000 --- a/Content.Shared/Body/Prototypes/BodyTemplatePrototype.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Content.Shared.Body.Part; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; - -namespace Content.Shared.Body.Prototypes -{ - /// - /// Defines the layout of a body. - /// - [Prototype("bodyTemplate")] - [Serializable, NetSerializable] - public sealed class BodyTemplatePrototype : IPrototype, ISerializationHooks - { - [DataField("slots")] - private Dictionary _slots = new(); - - [DataField("connections")] - private Dictionary> _rawConnections = new(); - - [DataField("layers")] - private Dictionary _layers = new(); - - [DataField("mechanismLayers")] - private Dictionary _mechanismLayers = new(); - - [ViewVariables] - [IdDataFieldAttribute] - public string ID { get; } = default!; - - [ViewVariables] - [DataField("name")] - public string Name { get; } = string.Empty; - - [ViewVariables] - [DataField("centerSlot")] - public string CenterSlot { get; } = string.Empty; - - [ViewVariables] - public Dictionary Slots => new(_slots); - - [ViewVariables] - public Dictionary> Connections { get; set; } = new(); - - [ViewVariables] - public Dictionary Layers => new(_layers); - - [ViewVariables] - public Dictionary MechanismLayers => new(_mechanismLayers); - - void ISerializationHooks.AfterDeserialization() - { - //Our prototypes don't force the user to define a BodyPart connection twice. E.g. Head: Torso v.s. Torso: Head. - //The user only has to do one. We want it to be that way in the code, though, so this cleans that up. - var cleanedConnections = new Dictionary>(); - - foreach (var targetSlotName in _slots.Keys) - { - var tempConnections = new HashSet(); - foreach (var (slotName, slotConnections) in _rawConnections) - { - if (slotName == targetSlotName) - { - foreach (var connection in slotConnections) - { - if (!tempConnections.Contains(connection)) - { - tempConnections.Add(connection); - } - } - } - else if (slotConnections.Contains(targetSlotName)) - { - tempConnections.Add(slotName); - } - } - - if (tempConnections.Count > 0) - { - cleanedConnections.Add(targetSlotName, tempConnections); - } - } - - Connections = cleanedConnections; - } - } -} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs new file mode 100644 index 0000000000..94997c152e --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -0,0 +1,213 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Part; +using Content.Shared.Body.Prototypes; +using Content.Shared.Coordinates; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Systems; + +public partial class SharedBodySystem +{ + public void InitializeBody() + { + SubscribeLocalEvent(OnBodyMapInit); + SubscribeLocalEvent(OnBodyInit); + + SubscribeLocalEvent(OnBodyGetState); + SubscribeLocalEvent(OnBodyHandleState); + } + + private void OnBodyMapInit(EntityUid bodyId, BodyComponent body, MapInitEvent args) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (body.Prototype == null || body.Root != null) + return; + + var prototype = Prototypes.Index(body.Prototype); + InitBody(body, prototype); + } + + private void OnBodyInit(EntityUid bodyId, BodyComponent body, ComponentInit args) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (body.Prototype == null || body.Root != null) + return; + + var prototype = Prototypes.Index(body.Prototype); + InitBody(body, prototype); + } + + private void OnBodyGetState(EntityUid uid, BodyComponent body, ref ComponentGetState args) + { + args.State = new BodyComponentState(body.Root, body.GibSound); + } + + private void OnBodyHandleState(EntityUid uid, BodyComponent body, ref ComponentHandleState args) + { + if (args.Current is not BodyComponentState state) + return; + + body.Root = state.Root; + body.GibSound = state.GibSound; + } + + public bool TryCreateBodyRootSlot( + EntityUid? bodyId, + string slotId, + [NotNullWhen(true)] out BodyPartSlot? slot, + BodyComponent? body = null) + { + slot = null; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (bodyId == null || + !Resolve(bodyId.Value, ref body, false) || + body.Root != null) + return false; + + slot = new BodyPartSlot(slotId, bodyId.Value, null); + body.Root = slot; + + return true; + } + + private void InitBody(BodyComponent body, BodyPrototype prototype) + { + var root = prototype.Slots[prototype.Root]; + var bodyId = Spawn(root.Part, body.Owner.ToCoordinates()); + var partComponent = Comp(bodyId); + var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType); + body.Root = slot; + partComponent.Body = bodyId; + + Containers.EnsureContainer(body.Owner, BodyContainerId); + + AttachPart(bodyId, slot, partComponent); + InitPart(partComponent, prototype, prototype.Root); + } + + private void InitPart(BodyPartComponent parent, BodyPrototype prototype, string slotId, HashSet? initialized = null) + { + initialized ??= new HashSet(); + + if (initialized.Contains(slotId)) + return; + + initialized.Add(slotId); + + var (_, connections, organs) = prototype.Slots[slotId]; + connections = new HashSet(connections); + connections.ExceptWith(initialized); + + var coordinates = parent.Owner.ToCoordinates(); + var subConnections = new List<(BodyPartComponent child, string slotId)>(); + + Containers.EnsureContainer(parent.Owner, BodyContainerId); + + foreach (var connection in connections) + { + var childSlot = prototype.Slots[connection]; + var childPart = Spawn(childSlot.Part, coordinates); + var childPartComponent = Comp(childPart); + var slot = CreatePartSlot(connection, parent.Owner, childPartComponent.PartType, parent); + if (slot == null) + { + Logger.Error($"Could not create slot for connection {connection} in body {prototype.ID}"); + continue; + } + + AttachPart(childPart, slot, childPartComponent); + subConnections.Add((childPartComponent, connection)); + } + + foreach (var (organSlotId, organId) in organs) + { + var organ = Spawn(organId, coordinates); + var organComponent = Comp(organ); + + var slot = CreateOrganSlot(organSlotId, parent.Owner, parent); + if (slot == null) + { + Logger.Error($"Could not create slot for connection {organSlotId} in body {prototype.ID}"); + continue; + } + + InsertOrgan(organ, slot, organComponent); + } + + foreach (var connection in subConnections) + { + InitPart(connection.child, prototype, connection.slotId, initialized); + } + } + public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildren(EntityUid? id, BodyComponent? body = null) + { + if (id == null || + !Resolve(id.Value, ref body, false) || + !TryComp(body.Root.Child, out BodyPartComponent? part)) + yield break; + + yield return (body.Root.Child.Value, part); + + foreach (var child in GetPartChildren(body.Root.Child)) + { + yield return child; + } + } + + public IEnumerable<(EntityUid Id, OrganComponent Component)> GetBodyOrgans(EntityUid? bodyId, BodyComponent? body = null) + { + if (bodyId == null || !Resolve(bodyId.Value, ref body, false)) + yield break; + + foreach (var part in GetBodyChildren(bodyId, body)) + { + foreach (var organ in GetPartOrgans(part.Id, part.Component)) + { + yield return organ; + } + } + } + + public IEnumerable GetBodyAllSlots(EntityUid? bodyId, BodyComponent? body = null) + { + if (bodyId == null || !Resolve(bodyId.Value, ref body, false)) + yield break; + + foreach (var slot in GetPartAllSlots(body.Root.Child)) + { + yield return slot; + } + } + + public virtual HashSet GibBody(EntityUid? partId, bool gibOrgans = false, + BodyComponent? body = null) + { + if (partId == null || !Resolve(partId.Value, ref body, false)) + return new HashSet(); + + var parts = GetBodyChildren(partId, body).ToArray(); + var gibs = new HashSet(parts.Length); + + foreach (var part in parts) + { + DropPart(part.Id, part.Component); + gibs.Add(part.Id); + + if (!gibOrgans) + continue; + + foreach (var organ in GetPartOrgans(part.Id, part.Component)) + { + DropOrgan(organ.Id, organ.Component); + gibs.Add(organ.Id); + } + } + + return gibs; + } +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs new file mode 100644 index 0000000000..ed67e88da1 --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs @@ -0,0 +1,230 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Body.Components; +using Content.Shared.Body.Events; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Part; +using Content.Shared.Random.Helpers; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Map; + +namespace Content.Shared.Body.Systems; + +public partial class SharedBodySystem +{ + private void InitializeOrgans() + { + SubscribeLocalEvent(OnOrganGetState); + SubscribeLocalEvent(OnOrganHandleState); + } + + private OrganSlot? CreateOrganSlot(string slotId, EntityUid parent, BodyPartComponent? part = null) + { + if (!Resolve(parent, ref part, false)) + return null; + + var slot = new OrganSlot(slotId, parent); + part.Organs.Add(slotId, slot); + + return slot; + } + + private bool CanInsertOrgan(EntityUid? organId, OrganSlot slot, OrganComponent? organ = null) + { + return organId != null && + slot.Child == null && + Resolve(organId.Value, ref organ, false) && + Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container) && + container.CanInsert(organId.Value); + } + + private void OnOrganGetState(EntityUid uid, OrganComponent organ, ref ComponentGetState args) + { + args.State = new OrganComponentState(organ.Body, organ.ParentSlot); + } + + private void OnOrganHandleState(EntityUid uid, OrganComponent organ, ref ComponentHandleState args) + { + if (args.Current is not OrganComponentState state) + return; + + organ.Body = state.Body; + organ.ParentSlot = state.Parent; + } + + public bool InsertOrgan(EntityUid? organId, OrganSlot slot, OrganComponent? organ = null) + { + if (organId == null || + !Resolve(organId.Value, ref organ, false) || + !CanInsertOrgan(organId, slot, organ)) + return false; + + DropOrgan(slot.Child); + DropOrgan(organId, organ); + + var container = Containers.EnsureContainer(slot.Parent, BodyContainerId); + if (!container.Insert(organId.Value)) + return false; + + slot.Child = organId; + organ.ParentSlot = slot; + organ.Body = CompOrNull(slot.Parent)?.Body; + + Dirty(slot.Parent); + Dirty(organId.Value); + + if (organ.Body == null) + { + RaiseLocalEvent(organId.Value, new AddedToPartEvent(slot.Parent)); + } + else + { + RaiseLocalEvent(organId.Value, new AddedToPartInBodyEvent(organ.Body.Value, slot.Parent)); + } + + return true; + } + + public bool AddOrganToFirstValidSlot( + EntityUid? childId, + EntityUid? parentId, + OrganComponent? child = null, + BodyPartComponent? parent = null) + { + if (childId == null || + !Resolve(childId.Value, ref child, false) || + parentId == null || + !Resolve(parentId.Value, ref parent, false)) + return false; + + foreach (var slot in parent.Organs.Values) + { + if (slot.Child == null) + continue; + + InsertOrgan(childId, slot, child); + return true; + } + + return false; + } + + public bool DropOrgan(EntityUid? organId, OrganComponent? organ = null) + { + if (organId == null || + !Resolve(organId.Value, ref organ, false) || + organ.ParentSlot is not { } slot) + return false; + + var oldParent = CompOrNull(organ.ParentSlot.Parent); + + slot.Child = null; + organ.ParentSlot = null; + organ.Body = null; + + if (Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container)) + container.Remove(organId.Value); + + if (TryComp(organId, out TransformComponent? transform)) + transform.AttachToGridOrMap(); + + organ.Owner.RandomOffset(0.25f); + + if (oldParent == null) + return true; + + if (oldParent.Body != null) + { + RaiseLocalEvent(organId.Value, new RemovedFromPartInBodyEvent(oldParent.Body.Value, oldParent.Owner)); + } + else + { + RaiseLocalEvent(organId.Value, new RemovedFromPartEvent(oldParent.Owner)); + } + + return true; + } + + public bool DropOrganAt(EntityUid? organId, EntityCoordinates dropAt, OrganComponent? organ = null) + { + if (organId == null || !DropOrgan(organId, organ)) + return false; + + if (TryComp(organId.Value, out TransformComponent? transform)) + transform.Coordinates = dropAt; + + return true; + } + + public bool DeleteOrgan(EntityUid? id, OrganComponent? part = null) + { + if (id == null || !Resolve(id.Value, ref part, false)) + return false; + + DropOrgan(id, part); + + if (Deleted(id.Value)) + return false; + + Del(id.Value); + return true; + } + + /// + /// Returns a list of ValueTuples of and OrganComponent on each organ + /// in the given body. + /// + /// The entity to check for the component on. + /// The body to check for organs on. + /// The component to check for. + public List<(T Comp, OrganComponent Organ)> GetBodyOrganComponents( + EntityUid uid, + BodyComponent? body = null) + where T : Component + { + if (!Resolve(uid, ref body)) + return new List<(T Comp, OrganComponent Organ)>(); + + var query = EntityManager.GetEntityQuery(); + var list = new List<(T Comp, OrganComponent Organ)>(3); + foreach (var organ in GetBodyOrgans(uid, body)) + { + if (query.TryGetComponent(organ.Id, out var comp)) + list.Add((comp, organ.Component)); + } + + return list; + } + + /// + /// Tries to get a list of ValueTuples of and OrganComponent on each organs + /// in the given body. + /// + /// The entity to check for the component on. + /// The list of components. + /// The body to check for organs on. + /// The component to check for. + /// Whether any were found. + public bool TryGetBodyOrganComponents( + EntityUid uid, + [NotNullWhen(true)] out List<(T Comp, OrganComponent Organ)>? comps, + BodyComponent? body = null) + where T : Component + { + if (!Resolve(uid, ref body)) + { + comps = null; + return false; + } + + comps = GetBodyOrganComponents(uid, body); + + if (comps.Count == 0) + { + comps = null; + return false; + } + + return true; + } +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs new file mode 100644 index 0000000000..f92e3c1a5c --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs @@ -0,0 +1,359 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.Body.Components; +using Content.Shared.Body.Events; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Part; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Random.Helpers; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Map; + +namespace Content.Shared.Body.Systems; + +public partial class SharedBodySystem +{ + private void InitializeParts() + { + SubscribeLocalEvent(OnPartRemoved); + SubscribeLocalEvent(OnPartGetState); + SubscribeLocalEvent(OnPartHandleState); + } + + private void OnPartGetState(EntityUid uid, BodyPartComponent part, ref ComponentGetState args) + { + args.State = new BodyPartComponentState( + part.Body, + part.ParentSlot, + part.Children, + part.Organs, + part.PartType, + part.IsVital, + part.Symmetry + ); + } + + private void OnPartHandleState(EntityUid uid, BodyPartComponent part, ref ComponentHandleState args) + { + if (args.Current is not BodyPartComponentState state) + return; + + part.Body = state.Body; + part.ParentSlot = state.ParentSlot; + part.Children = state.Children; + part.Organs = state.Organs; + part.PartType = state.PartType; + part.IsVital = state.IsVital; + part.Symmetry = state.Symmetry; + } + + private void OnPartRemoved(EntityUid uid, BodyPartComponent part, ComponentRemove args) + { + if (part.ParentSlot is { } slot) + { + slot.Child = null; + Dirty(slot.Parent); + } + + foreach (var childSlot in part.Children.Values.ToArray()) + { + DropPart(childSlot.Child); + } + } + + private BodyPartSlot? CreatePartSlot( + string slotId, + EntityUid parent, + BodyPartType partType, + BodyPartComponent? part = null) + { + if (!Resolve(parent, ref part, false)) + return null; + + var slot = new BodyPartSlot(slotId, parent, partType); + part.Children.Add(slotId, slot); + + return slot; + } + + public bool TryCreatePartSlot( + EntityUid? parentId, + string id, + [NotNullWhen(true)] out BodyPartSlot? slot, + BodyPartComponent? parent = null) + { + slot = null; + + if (parentId == null || + !Resolve(parentId.Value, ref parent, false)) + return false; + + slot = new BodyPartSlot(id, parentId.Value, null); + if (!parent.Children.TryAdd(id, slot)) + { + slot = null; + return false; + } + + return true; + } + + public bool TryCreatePartSlotAndAttach( + EntityUid? parentId, + string id, + EntityUid? childId, + BodyPartComponent? parent = null, + BodyPartComponent? child = null) + { + return TryCreatePartSlot(parentId, id, out var slot, parent) && AttachPart(childId, slot, child); + } + + public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetPartChildren(EntityUid? id, BodyPartComponent? part = null) + { + if (id == null || !Resolve(id.Value, ref part, false)) + yield break; + + foreach (var slot in part.Children.Values) + { + if (!TryComp(slot.Child, out BodyPartComponent? childPart)) + continue; + + yield return (slot.Child.Value, childPart); + + foreach (var subChild in GetPartChildren(slot.Child, childPart)) + { + yield return subChild; + } + } + } + + public IEnumerable<(EntityUid Id, OrganComponent Component)> GetPartOrgans(EntityUid? partId, BodyPartComponent? part = null) + { + if (partId == null || !Resolve(partId.Value, ref part, false)) + yield break; + + foreach (var slot in part.Organs.Values) + { + if (!TryComp(slot.Child, out OrganComponent? organ)) + continue; + + yield return (slot.Child.Value, organ); + } + } + + public IEnumerable GetPartAllSlots(EntityUid? partId, BodyPartComponent? part = null) + { + if (partId == null || + !Resolve(partId.Value, ref part, false)) + yield break; + + foreach (var slot in part.Children.Values) + { + yield return slot; + + if (!TryComp(slot.Child, out BodyComponent? childPart)) + continue; + + foreach (var subChild in GetBodyAllSlots(slot.Child, childPart)) + { + yield return subChild; + } + } + } + + public bool CanAttachPart([NotNullWhen(true)] EntityUid? partId, BodyPartSlot slot, BodyPartComponent? part = null) + { + return partId != null && + slot.Child == null && + Resolve(partId.Value, ref part, false) && + (slot.Type == null || slot.Type == part.PartType) && + Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container) && + container.CanInsert(partId.Value); + } + + public virtual bool AttachPart( + EntityUid? partId, + BodyPartSlot slot, + [NotNullWhen(true)] BodyPartComponent? part = null) + { + if (partId == null || + !Resolve(partId.Value, ref part, false) || + !CanAttachPart(partId, slot, part)) + return false; + + DropPart(slot.Child); + DropPart(partId, part); + + var container = Containers.EnsureContainer(slot.Parent, BodyContainerId); + if (!container.Insert(partId.Value)) + return false; + + slot.Child = partId; + part.ParentSlot = slot; + + if (TryComp(slot.Parent, out BodyPartComponent? parentPart)) + { + part.Body = parentPart.Body; + } + else if (TryComp(slot.Parent, out BodyComponent? parentBody)) + { + part.Body = parentBody.Owner; + } + else + { + part.Body = null; + } + + Dirty(slot.Parent); + Dirty(partId.Value); + + if (part.Body is { } newBody) + { + var argsAdded = new BodyPartAddedEventArgs(slot.Id, part); + + // TODO: Body refactor. Somebody is doing it + // EntitySystem.Get().BodyPartAdded(Owner, argsAdded); + foreach (var component in AllComps(newBody).ToArray()) + { + component.BodyPartAdded(argsAdded); + } + + foreach (var organ in GetPartOrgans(partId, part)) + { + RaiseLocalEvent(organ.Id, new AddedToBodyEvent(newBody), true); + } + + Dirty(newBody); + } + + return true; + } + + public virtual bool DropPart(EntityUid? partId, [NotNullWhen(true)] BodyPartComponent? part = null) + { + if (partId == null || + !Resolve(partId.Value, ref part, false) || + part.ParentSlot is not { } slot) + return false; + + var oldBody = part.Body; + + slot.Child = null; + part.ParentSlot = null; + part.Body = null; + + if (Containers.TryGetContainer(slot.Parent, BodyContainerId, out var container)) + container.Remove(partId.Value); + + if (TryComp(partId, out TransformComponent? transform)) + transform.AttachToGridOrMap(); + + part.Owner.RandomOffset(0.25f); + + if (oldBody != null) + { + var args = new BodyPartRemovedEventArgs(slot.Id, part); + foreach (var component in AllComps(oldBody.Value)) + { + component.BodyPartRemoved(args); + } + + if (part.PartType == BodyPartType.Leg && + !GetBodyChildrenOfType(oldBody, BodyPartType.Leg).Any()) + { + Standing.Down(oldBody.Value); + } + + if (part.IsVital && !GetBodyChildrenOfType(oldBody, part.PartType).Any()) + { + // TODO BODY SYSTEM KILL : Find a more elegant way of killing em than just dumping bloodloss damage. + var damage = new DamageSpecifier(Prototypes.Index("Bloodloss"), 300); + Damageable.TryChangeDamage(part.Owner, damage); + } + + foreach (var organSlot in part.Organs.Values) + { + if (organSlot.Child is not { } child) + continue; + + RaiseLocalEvent(child, new RemovedFromBodyEvent(oldBody.Value), true); + } + } + + Dirty(slot.Parent); + Dirty(partId.Value); + + return true; + } + + public bool DropPartAt(EntityUid? partId, EntityCoordinates dropAt, BodyPartComponent? part = null) + { + if (partId == null || !DropPart(partId, part)) + return false; + + if (TryComp(partId.Value, out TransformComponent? transform)) + transform.Coordinates = dropAt; + + return true; + } + + public bool OrphanPart(EntityUid? partId, BodyPartComponent? part = null) + { + if (partId == null || !Resolve(partId.Value, ref part, false)) + return false; + + DropPart(partId, part); + + foreach (var slot in part.Children.Values) + { + DropPart(slot.Child); + } + + return false; + } + + public bool DeletePart(EntityUid? id, BodyPartComponent? part = null) + { + if (id == null || !Resolve(id.Value, ref part, false)) + return false; + + DropPart(id, part); + + if (Deleted(id.Value)) + return false; + + Del(id.Value); + return true; + } + + public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType(EntityUid? bodyId, BodyPartType type, BodyComponent? body = null) + { + foreach (var part in GetBodyChildren(bodyId, body)) + { + if (part.Component.PartType == type) + yield return part; + } + } + + public bool BodyHasChildOfType(EntityUid? bodyId, BodyPartType type, BodyComponent? body = null) + { + return GetBodyChildrenOfType(bodyId, type, body).Any(); + } + + public bool BodyHasChild( + EntityUid? parentId, + EntityUid? childId, + BodyComponent? parent = null, + BodyPartComponent? child = null) + { + if (parentId == null || + !Resolve(parentId.Value, ref parent, false) || + childId == null || + !Resolve(childId.Value, ref child, false)) + return false; + + return child.ParentSlot?.Child == parentId; + } +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.cs b/Content.Shared/Body/Systems/SharedBodySystem.cs new file mode 100644 index 0000000000..fa54886b1c --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.cs @@ -0,0 +1,26 @@ +using Content.Shared.Damage; +using Content.Shared.Standing; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Body.Systems; + +public abstract partial class SharedBodySystem : EntitySystem +{ + private const string BodyContainerId = "BodyContainer"; + + [Dependency] protected readonly IPrototypeManager Prototypes = default!; + + [Dependency] protected readonly SharedContainerSystem Containers = default!; + [Dependency] protected readonly DamageableSystem Damageable = default!; + [Dependency] protected readonly StandingStateSystem Standing = default!; + + public override void Initialize() + { + base.Initialize(); + + InitializeBody(); + InitializeParts(); + InitializeOrgans(); + } +} diff --git a/Content.Shared/Coordinates/EntityCoordinatesExtensions.cs b/Content.Shared/Coordinates/EntityCoordinatesExtensions.cs index 8ac3fbb43a..0e02bf99f5 100644 --- a/Content.Shared/Coordinates/EntityCoordinatesExtensions.cs +++ b/Content.Shared/Coordinates/EntityCoordinatesExtensions.cs @@ -4,14 +4,19 @@ namespace Content.Shared.Coordinates { public static class EntityCoordinatesExtensions { + public static EntityCoordinates ToCoordinates(this EntityUid id) + { + return new EntityCoordinates(id, new Vector2(0, 0)); + } + public static EntityCoordinates ToCoordinates(this EntityUid id, Vector2 offset) { - return new(id, offset); + return new EntityCoordinates(id, offset); } public static EntityCoordinates ToCoordinates(this EntityUid id, float x, float y) { - return new(id, x, y); + return new EntityCoordinates(id, x, y); } public static EntityCoordinates ToCoordinates(this IMapGrid grid, Vector2 offset) diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs index 0b06fdef09..3fc690a0e7 100644 --- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs @@ -6,9 +6,7 @@ using Content.Shared.MobState.Components; using Content.Shared.MobState.EntitySystems; using Content.Shared.Throwing; using JetBrains.Annotations; -using Robust.Shared.Physics; using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Timing; @@ -65,7 +63,7 @@ namespace Content.Shared.Disposal // TODO: Probably just need a disposable tag. if (!EntityManager.TryGetComponent(entity, out ItemComponent? storable) && - !EntityManager.HasComponent(entity)) + !EntityManager.HasComponent(entity)) { return false; } diff --git a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs index e734464e90..457795102d 100644 --- a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs +++ b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs @@ -60,7 +60,7 @@ namespace Content.Shared.Humanoid } } - public static HumanoidVisualLayers? ToHumanoidLayers(this SharedBodyPartComponent part) + public static HumanoidVisualLayers? ToHumanoidLayers(this BodyPartComponent part) { switch (part.PartType) { @@ -85,6 +85,7 @@ namespace Content.Shared.Humanoid case BodyPartSymmetry.Right: return HumanoidVisualLayers.RArm; } + break; case BodyPartType.Hand: switch (part.Symmetry) @@ -96,6 +97,7 @@ namespace Content.Shared.Humanoid case BodyPartSymmetry.Right: return HumanoidVisualLayers.RHand; } + break; case BodyPartType.Leg: switch (part.Symmetry) @@ -107,6 +109,7 @@ namespace Content.Shared.Humanoid case BodyPartSymmetry.Right: return HumanoidVisualLayers.RLeg; } + break; case BodyPartType.Foot: switch (part.Symmetry) @@ -118,6 +121,7 @@ namespace Content.Shared.Humanoid case BodyPartSymmetry.Right: return HumanoidVisualLayers.RFoot; } + break; } diff --git a/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs b/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs index b49cc40abb..1b1805a1f1 100644 --- a/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs +++ b/Content.Shared/MedicalScanner/SharedMedicalScannerComponent.cs @@ -25,7 +25,7 @@ namespace Content.Shared.MedicalScanner public bool CanInsert(EntityUid entity) { - return IoCManager.Resolve().HasComponent(entity); + return IoCManager.Resolve().HasComponent(entity); } bool IDragDropOn.CanDragDropOn(DragDropEvent eventArgs) diff --git a/Resources/Locale/en-US/body/behavior/reassemble.ftl b/Resources/Locale/en-US/body/behavior/reassemble.ftl deleted file mode 100644 index b13ac67ad0..0000000000 --- a/Resources/Locale/en-US/body/behavior/reassemble.ftl +++ /dev/null @@ -1,4 +0,0 @@ -reassemble-action = Reassemble -reassemble-description = Reassemble the pieces of your body. -reassemble-fail = Parts are missing! -reassemble-success = {CAPITALIZE(THE($user))} was put back together. \ No newline at end of file diff --git a/Resources/Locale/en-US/body/components/bodypart-component.ftl b/Resources/Locale/en-US/body/components/bodypart-component.ftl deleted file mode 100644 index 03e6e1e10d..0000000000 --- a/Resources/Locale/en-US/body/components/bodypart-component.ftl +++ /dev/null @@ -1,6 +0,0 @@ -## Entity - -bodypart-component-no-way-to-install-message = You see no way to install the {$partName} -bodypart-component-no-way-to-attach-message = You see no useful way to attach the {$partName} anymore. -bodypart-component-attach-success-message = You attach {$partName} -bodypart-component-attach-fail-message = You can't attach {$partName} diff --git a/Resources/Locale/en-US/body/components/mechanism-component.ftl b/Resources/Locale/en-US/body/components/mechanism-component.ftl deleted file mode 100644 index d9e522ebce..0000000000 --- a/Resources/Locale/en-US/body/components/mechanism-component.ftl +++ /dev/null @@ -1,6 +0,0 @@ -## Entity - -mechanism-component-cannot-fit-message = You can't fit it in! -mechanism-component-no-way-to-install-message = You see no way to install the {$partName} -mechanism-component-no-useful-way-to-use-message = You see no useful way to use the {$partName} anymore. -mechanism-component-jam-inside-message = You jam {$ownerName} inside {$them}. \ No newline at end of file diff --git a/Resources/Locale/en-US/body/surgery/biological-surgery-data-component.ftl b/Resources/Locale/en-US/body/surgery/biological-surgery-data-component.ftl deleted file mode 100644 index 49aa5bbb04..0000000000 --- a/Resources/Locale/en-US/body/surgery/biological-surgery-data-component.ftl +++ /dev/null @@ -1,11 +0,0 @@ -biological-surgery-data-component-has-incision-not-clamped-message = The skin on their {$bodyPart} has an incision, but it is prone to bleeding. -biological-surgery-data-component-has-clamped-incision-not-retracted-message = The skin on their {$bodyPart} has an incision, but it is not retracted. -biological-surgery-data-component-has-fully-open-incision-message = There is an incision on their {$bodyPart}. -biological-surgery-data-component-part-is-loose-message = {$owner}'s {$bodyPart} is loose. -biological-surgery-data-component-open-skin-message = Cut open the skin... -biological-surgery-data-component-clamp-vessels-message = Clamp the vessels... -biological-surgery-data-component-retract-skin-message = Retracting the skin... -biological-surgery-data-component-cauterize-incision-message = Cauterizing the incision... -biological-surgery-data-component-loosen-organ-message = Loosening the organ... -biological-surgery-data-component-remove-organ-message = Removing the organ... -biological-surgery-data-component-remove-bodypart-message = Sawing off the limb! \ No newline at end of file diff --git a/Resources/Locale/en-US/body/surgery/components/surgery-tool-component.ftl b/Resources/Locale/en-US/body/surgery/components/surgery-tool-component.ftl deleted file mode 100644 index e156491bf0..0000000000 --- a/Resources/Locale/en-US/body/surgery/components/surgery-tool-component.ftl +++ /dev/null @@ -1,2 +0,0 @@ -surgery-tool-component-not-useful-message = You see no useful way to use {$bodyPart}. -surgery-tool-component-not-useful-anymore-message = You see no useful way to use {$bodyPart} anymore. \ No newline at end of file diff --git a/Resources/Locale/en-US/body/surgery/surgery.ftl b/Resources/Locale/en-US/body/surgery/surgery.ftl deleted file mode 100644 index f370d19357..0000000000 --- a/Resources/Locale/en-US/body/surgery/surgery.ftl +++ /dev/null @@ -1,4 +0,0 @@ -## UI - -surgery-window-title = Surgery -surgery-window-not-available-button-text = N/A \ No newline at end of file diff --git a/Resources/Prototypes/Body/Mechanisms/tests.yml b/Resources/Prototypes/Body/Mechanisms/tests.yml deleted file mode 100644 index f4b05185ed..0000000000 --- a/Resources/Prototypes/Body/Mechanisms/tests.yml +++ /dev/null @@ -1,23 +0,0 @@ -- type: entity - id: MechanismEMPStriker - name: "EMP striker" - description: "When activated, this arm implant will apply a small EMP on the target of a physical strike for 10 watts per use." - noSpawn: true - components: - - type: Sprite - - type: Icon - - type: Mechanism - size: 4 - compatibility: Universal - -- type: entity - id: MechanismHonkModule - name: "HONK module 3000" - description: "Mandatory implant for all clowns after the Genevo Convention of 2459." - noSpawn: true - components: - - type: Sprite - - type: Icon - - type: Mechanism - size: 3 - compatibility: Universal diff --git a/Resources/Prototypes/Body/Organs/animal.yml b/Resources/Prototypes/Body/Organs/animal.yml new file mode 100644 index 0000000000..930abfad3e --- /dev/null +++ b/Resources/Prototypes/Body/Organs/animal.yml @@ -0,0 +1,90 @@ +- type: entity + id: BaseAnimalOrgan + parent: BaseItem + abstract: true + components: + - type: Organ + - type: Sprite + netsync: false + sprite: Mobs/Species/Human/organs.rsi + - type: StaticPrice + price: 50 + + +- type: entity + id: OrganAnimalLungs + parent: BaseAnimalOrgan + name: lungs + noSpawn: true + components: + - type: Organ + - type: Lung + - type: Metabolizer + removeEmpty: true + solutionOnBody: false + solution: "Lung" + metabolizerTypes: [ Animal ] + groups: + - id: Gas + rateModifier: 100.0 + +- type: entity + id: OrganAnimalStomach + parent: BaseAnimalOrgan + name: stomach + noSpawn: true + components: + - type: Organ + - type: SolutionContainerManager + solutions: + stomach: + maxVol: 100 + - type: Stomach + maxVolume: 10 + - type: Metabolizer + maxReagents: 3 + metabolizerTypes: [ Animal ] + groups: + - id: Food + - id: Drink + +- type: entity + id: OrganAnimalLiver + parent: BaseAnimalOrgan + name: liver + noSpawn: true + components: + - type: Organ + - type: Metabolizer + maxReagents: 1 + metabolizerTypes: [ Animal ] + groups: + - id: Alcohol + rateModifier: 0.1 + +- type: entity + id: OrganAnimalHeart + parent: BaseAnimalOrgan + name: heart + noSpawn: true + components: + - type: Organ + - type: Metabolizer + maxReagents: 2 + metabolizerTypes: [ Animal ] + groups: + - id: Medicine + - id: Poison + - id: Narcotic + +- type: entity + id: OrganAnimalKidneys + parent: BaseAnimalOrgan + name: kidneys + noSpawn: true + components: + - type: Organ + - type: Metabolizer + maxReagents: 5 + metabolizerTypes: [ Animal ] + removeEmpty: true diff --git a/Resources/Prototypes/Body/Mechanisms/human.yml b/Resources/Prototypes/Body/Organs/human.yml similarity index 86% rename from Resources/Prototypes/Body/Mechanisms/human.yml rename to Resources/Prototypes/Body/Organs/human.yml index 84fa4f6a5f..6c10aa2002 100644 --- a/Resources/Prototypes/Body/Mechanisms/human.yml +++ b/Resources/Prototypes/Body/Organs/human.yml @@ -6,7 +6,7 @@ - type: Sprite netsync: false sprite: Mobs/Species/Human/organs.rsi - - type: Mechanism + - type: Organ - type: Food - type: Extractable grindableSolutionName: organ @@ -25,9 +25,7 @@ components: - type: Sprite state: brain - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: Input context: "ghost" - type: InputMover @@ -47,9 +45,7 @@ layers: - state: eyeball-l - state: eyeball-r - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: entity id: OrganHumanTongue @@ -59,9 +55,7 @@ components: - type: Sprite state: tongue - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: entity id: OrganHumanAppendix @@ -73,9 +67,7 @@ - state: appendix - state: appendix-inflamed visible: false - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: entity id: OrganHumanEars @@ -85,9 +77,7 @@ components: - type: Sprite state: ears - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: entity id: OrganHumanLungs @@ -99,9 +89,7 @@ layers: - state: lung-l - state: lung-r - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: Lung - type: Metabolizer removeEmpty: true @@ -120,9 +108,7 @@ components: - type: Sprite state: heart-on - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ # 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. @@ -142,9 +128,7 @@ components: - type: Sprite state: stomach - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: SolutionContainerManager solutions: stomach: @@ -169,9 +153,7 @@ components: - type: Sprite state: liver - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol. maxReagents: 1 metabolizerTypes: [Human] @@ -189,9 +171,7 @@ layers: - state: kidney-l - state: kidney-r - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ # The kidneys just remove anything that doesn't currently have any metabolisms, as a stopgap. - type: Metabolizer maxReagents: 5 diff --git a/Resources/Prototypes/Body/Mechanisms/rat.yml b/Resources/Prototypes/Body/Organs/rat.yml similarity index 100% rename from Resources/Prototypes/Body/Mechanisms/rat.yml rename to Resources/Prototypes/Body/Organs/rat.yml diff --git a/Resources/Prototypes/Body/Mechanisms/reptilian.yml b/Resources/Prototypes/Body/Organs/reptilian.yml similarity index 100% rename from Resources/Prototypes/Body/Mechanisms/reptilian.yml rename to Resources/Prototypes/Body/Organs/reptilian.yml diff --git a/Resources/Prototypes/Body/Mechanisms/slime.yml b/Resources/Prototypes/Body/Organs/slime.yml similarity index 88% rename from Resources/Prototypes/Body/Mechanisms/slime.yml rename to Resources/Prototypes/Body/Organs/slime.yml index ed650946b3..13c3c3bd31 100644 --- a/Resources/Prototypes/Body/Mechanisms/slime.yml +++ b/Resources/Prototypes/Body/Organs/slime.yml @@ -8,9 +8,7 @@ netsync: false sprite: Mobs/Species/Human/organs.rsi state: brain - - type: Mechanism - size: 5 - compatibility: Slime + - type: Organ - type: Brain - type: Stomach - type: Metabolizer @@ -34,9 +32,7 @@ layers: - state: lung-l - state: lung-r - - type: Mechanism - size: 1 - compatibility: Biological + - type: Organ - type: Lung - type: Metabolizer removeEmpty: true diff --git a/Resources/Prototypes/Body/Mechanisms/vox.yml b/Resources/Prototypes/Body/Organs/vox.yml similarity index 100% rename from Resources/Prototypes/Body/Mechanisms/vox.yml rename to Resources/Prototypes/Body/Organs/vox.yml diff --git a/Resources/Prototypes/Body/Parts/animal.yml b/Resources/Prototypes/Body/Parts/animal.yml index 4f915ca87e..98e7bf5e6d 100644 --- a/Resources/Prototypes/Body/Parts/animal.yml +++ b/Resources/Prototypes/Body/Parts/animal.yml @@ -27,8 +27,6 @@ components: - type: BodyPart partType: Hand - size: 1 - compatibility: Biological symmetry: Left - type: entity @@ -39,8 +37,6 @@ components: - type: BodyPart partType: Leg - size: 1 - compatibility: Biological - type: entity id: FeetAnimal @@ -50,8 +46,6 @@ components: - type: BodyPart partType: Foot - size: 1 - compatibility: Biological - type: entity id: TorsoAnimal @@ -61,113 +55,5 @@ components: - type: BodyPart partType: Torso - size: 7 - compatibility: Biological - mechanisms: - - OrganAnimalLungs - - OrganAnimalStomach - - OrganAnimalLiver - - OrganAnimalHeart - - OrganAnimalKidneys - type: Damageable damageContainer: Biological - -- type: entity - id: BaseAnimalOrgan - parent: BaseItem - abstract: true - components: - - type: Mechanism - - type: Sprite - netsync: false - sprite: Mobs/Species/Human/organs.rsi - - type: StaticPrice - price: 50 - -- type: entity - id: OrganAnimalLungs - parent: BaseAnimalOrgan - name: lungs - noSpawn: true - components: - - type: Mechanism - size: 1 - compatibility: Biological - - type: Lung - - type: Metabolizer - removeEmpty: true - solutionOnBody: false - solution: "Lung" - metabolizerTypes: [ Animal ] - groups: - - id: Gas - rateModifier: 100.0 - -- type: entity - id: OrganAnimalStomach - parent: BaseAnimalOrgan - name: stomach - noSpawn: true - components: - - type: Mechanism - size: 1 - compatibility: Biological - - type: SolutionContainerManager - solutions: - stomach: - maxVol: 100 - - type: Stomach - maxVolume: 10 - - type: Metabolizer - maxReagents: 3 - metabolizerTypes: [Animal] - groups: - - id: Food - - id: Drink - -- type: entity - id: OrganAnimalLiver - parent: BaseAnimalOrgan - name: liver - noSpawn: true - components: - - type: Mechanism - size: 1 - compatibility: Biological - - type: Metabolizer - maxReagents: 1 - metabolizerTypes: [Animal] - groups: - - id: Alcohol - rateModifier: 0.1 - -- type: entity - id: OrganAnimalHeart - parent: BaseAnimalOrgan - name: heart - noSpawn: true - components: - - type: Mechanism - size: 1 - compatibility: Biological - - type: Metabolizer - maxReagents: 2 - metabolizerTypes: [Animal] - groups: - - id: Medicine - - id: Poison - - id: Narcotic - -- type: entity - id: OrganAnimalKidneys - parent: BaseAnimalOrgan - name: kidneys - noSpawn: true - components: - - type: Mechanism - size: 1 - compatibility: Biological - - type: Metabolizer - maxReagents: 5 - metabolizerTypes: [Animal] - removeEmpty: true diff --git a/Resources/Prototypes/Body/Parts/human.yml b/Resources/Prototypes/Body/Parts/human.yml index aba69223e9..be8a9e4edc 100644 --- a/Resources/Prototypes/Body/Parts/human.yml +++ b/Resources/Prototypes/Body/Parts/human.yml @@ -30,16 +30,6 @@ state: "torso_m" - type: BodyPart partType: Torso - size: 14 - compatibility: Biological - mechanisms: - - OrganHumanHeart - - OrganHumanLungs - - OrganHumanStomach - - OrganHumanLiver - - OrganHumanKidneys -# criticalThreshold: 100 -# deadThreshold: 150 - type: entity id: HeadHuman @@ -55,14 +45,7 @@ state: "head_m" - type: BodyPart partType: Head - size: 7 - compatibility: Biological vital: true - mechanisms: - - OrganHumanBrain - - OrganHumanEyes -# criticalThreshold: 50 -# deadThreshold: 120 - type: Input context: "ghost" - type: MovementSpeedModifier @@ -88,11 +71,7 @@ state: "l_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Left -# criticalThreshold: 40 -# deadThreshold: 80 - type: entity id: RightArmHuman @@ -108,11 +87,7 @@ state: "r_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Right -# criticalThreshold: 40 -# deadThreshold: 80 - type: entity id: LeftHandHuman @@ -128,11 +103,7 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightHandHuman @@ -148,11 +119,7 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: LeftLegHuman @@ -168,11 +135,7 @@ state: "l_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Left -# criticalThreshold: 45 -# deadThreshold: 90 - type: entity id: RightLegHuman @@ -188,11 +151,7 @@ state: "r_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Right -# criticalThreshold: 45 -# deadThreshold: 90 - type: entity id: LeftFootHuman @@ -208,11 +167,7 @@ state: "l_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightFootHuman @@ -228,8 +183,4 @@ state: "r_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 diff --git a/Resources/Prototypes/Body/Parts/rat.yml b/Resources/Prototypes/Body/Parts/rat.yml index eef2f9e421..c151da09fd 100644 --- a/Resources/Prototypes/Body/Parts/rat.yml +++ b/Resources/Prototypes/Body/Parts/rat.yml @@ -8,13 +8,5 @@ components: - type: BodyPart partType: Torso - size: 7 - compatibility: Biological - mechanisms: - - OrganRatLungs - - OrganRatStomach - - OrganAnimalLiver - - OrganAnimalHeart - - OrganAnimalKidneys - type: Damageable damageContainer: Biological diff --git a/Resources/Prototypes/Body/Parts/reptilian.yml b/Resources/Prototypes/Body/Parts/reptilian.yml index 9e2e74ea4c..a98faf6c21 100644 --- a/Resources/Prototypes/Body/Parts/reptilian.yml +++ b/Resources/Prototypes/Body/Parts/reptilian.yml @@ -28,16 +28,6 @@ state: "torso_m" - type: BodyPart partType: Torso - size: 14 - compatibility: Biological - mechanisms: - - OrganAnimalHeart - - OrganHumanLungs - - OrganReptilianStomach - - OrganAnimalLiver - - OrganHumanKidneys -# criticalThreshold: 100 -# deadThreshold: 150 - type: entity id: HeadReptilian @@ -53,14 +43,7 @@ state: "head_m" - type: BodyPart partType: Head - size: 7 - compatibility: Biological vital: true - mechanisms: - - OrganHumanBrain - - OrganHumanEyes -# criticalThreshold: 50 -# deadThreshold: 120 - type: Input context: "ghost" - type: MovementSpeedModifier @@ -86,11 +69,7 @@ state: "l_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Left -# criticalThreshold: 40 -# deadThreshold: 80 - type: entity id: RightArmReptilian @@ -106,11 +85,7 @@ state: "r_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Right -# criticalThreshold: 40 -# deadThreshold: 80 - type: entity id: LeftHandReptilian @@ -126,11 +101,7 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightHandReptilian @@ -146,11 +117,7 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: LeftLegReptilian @@ -166,11 +133,7 @@ state: "l_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Left -# criticalThreshold: 45 -# deadThreshold: 90 - type: entity id: RightLegReptilian @@ -186,11 +149,7 @@ state: "r_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Right -# criticalThreshold: 45 -# deadThreshold: 90 - type: entity id: LeftFootReptilian @@ -206,11 +165,7 @@ state: "l_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightFootReptilian @@ -226,8 +181,4 @@ state: "r_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 diff --git a/Resources/Prototypes/Body/Parts/silicon.yml b/Resources/Prototypes/Body/Parts/silicon.yml index 633fd8c1e9..e52448deae 100644 --- a/Resources/Prototypes/Body/Parts/silicon.yml +++ b/Resources/Prototypes/Body/Parts/silicon.yml @@ -28,8 +28,6 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological ##Does this do anything? Revisit when surgery is in symmetry: Left - type: Tag tags: @@ -51,8 +49,6 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Right - type: Tag tags: diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index da1753b219..00f672b5f6 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -29,8 +29,6 @@ state: "torso_m" - type: BodyPart partType: Torso - size: 14 - compatibility: Biological - type: entity id: HeadSkeleton @@ -47,8 +45,6 @@ state: "skull_icon" - type: BodyPart partType: Head - size: 7 - compatibility: Biological - type: Input context: "human" - type: MovementSpeedModifier @@ -88,8 +84,6 @@ state: "l_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Left - type: entity @@ -106,8 +100,6 @@ state: "r_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Right - type: entity @@ -124,8 +116,6 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Left - type: entity @@ -142,8 +132,6 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Right - type: entity @@ -160,7 +148,7 @@ state: "l_leg" - type: BodyPart partType: Leg - size: 6 + symmetry: Left - type: entity id: RightLegSkeleton @@ -176,8 +164,6 @@ state: "r_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Right - type: entity @@ -194,8 +180,6 @@ state: "l_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Left - type: entity @@ -212,6 +196,4 @@ state: "r_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological - symmetry: Right \ No newline at end of file + symmetry: Right diff --git a/Resources/Prototypes/Body/Parts/slime.yml b/Resources/Prototypes/Body/Parts/slime.yml index 6b2092aaab..0ec7e611c4 100644 --- a/Resources/Prototypes/Body/Parts/slime.yml +++ b/Resources/Prototypes/Body/Parts/slime.yml @@ -29,13 +29,6 @@ state: "torso_m" - type: BodyPart partType: Torso - size: 14 - compatibility: Slime - mechanisms: - - SentientSlimeCore - - OrganSlimeLungs -# criticalThreshold: 100 -# deadThreshold: 150 - type: entity id: HeadSlime @@ -51,11 +44,7 @@ state: "head_m" - type: BodyPart partType: Head - size: 7 - compatibility: Slime vital: true - # criticalThreshold: 50 - # deadThreshold: 120 - type: Input context: "ghost" - type: MovementSpeedModifier @@ -81,11 +70,7 @@ state: "l_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Slime symmetry: Left - # criticalThreshold: 40 - # deadThreshold: 80 - type: entity id: RightArmSlime @@ -101,11 +86,7 @@ state: "r_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Slime symmetry: Right - # criticalThreshold: 40 - # deadThreshold: 80 - type: entity id: LeftHandSlime @@ -121,11 +102,7 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Slime symmetry: Left - # criticalThreshold: 30 - # deadThreshold: 60 - type: entity id: RightHandSlime @@ -141,11 +118,7 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Slime symmetry: Right - # criticalThreshold: 30 - # deadThreshold: 60 - type: entity id: LeftLegSlime @@ -161,8 +134,6 @@ state: "l_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Slime symmetry: Left - type: entity @@ -179,11 +150,7 @@ state: "r_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Slime symmetry: Right - # criticalThreshold: 45 - # deadThreshold: 90 - type: entity id: LeftFootSlime @@ -199,11 +166,7 @@ state: "l_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Slime symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightFootSlime @@ -219,8 +182,4 @@ state: "r_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Slime symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 diff --git a/Resources/Prototypes/Body/Parts/vox.yml b/Resources/Prototypes/Body/Parts/vox.yml index bae1861ee1..e820511455 100644 --- a/Resources/Prototypes/Body/Parts/vox.yml +++ b/Resources/Prototypes/Body/Parts/vox.yml @@ -30,17 +30,6 @@ state: "torso_m" - type: BodyPart partType: Torso - size: 14 - compatibility: Biological - mechanisms: - - OrganHumanHeart - - OrganVoxLungs - - OrganHumanStomach - - OrganHumanLiver - - OrganHumanKidneys - # TODO BODY DettachableDamageableComponent? -# criticalThreshold: 100 -# deadThreshold: 150 - type: entity id: HeadVox @@ -56,14 +45,7 @@ state: "head_m" - type: BodyPart partType: Head - size: 7 - compatibility: Biological vital: true - mechanisms: - - OrganHumanBrain - - OrganHumanEyes - # criticalThreshold: 50 - # deadThreshold: 120 - type: Input context: "ghost" - type: MovementSpeedModifier @@ -89,11 +71,7 @@ state: "l_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Left - # criticalThreshold: 40 - # deadThreshold: 80 - type: entity id: RightArmVox @@ -109,11 +87,7 @@ state: "r_arm" - type: BodyPart partType: Arm - size: 5 - compatibility: Biological symmetry: Right - # criticalThreshold: 40 - # deadThreshold: 80 - type: entity id: LeftHandVox @@ -129,11 +103,7 @@ state: "l_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Left - # criticalThreshold: 30 - # deadThreshold: 60 - type: entity id: RightHandVox @@ -149,11 +119,7 @@ state: "r_hand" - type: BodyPart partType: Hand - size: 3 - compatibility: Biological symmetry: Right - # criticalThreshold: 30 - # deadThreshold: 60 - type: entity id: LeftLegVox @@ -169,11 +135,7 @@ state: "l_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Left - # criticalThreshold: 45 - # deadThreshold: 90 - type: entity id: RightLegVox @@ -189,11 +151,7 @@ state: "r_leg" - type: BodyPart partType: Leg - size: 6 - compatibility: Biological symmetry: Right - # criticalThreshold: 45 - # deadThreshold: 90 - type: entity id: LeftFootVox @@ -209,11 +167,7 @@ state: "l_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Left -# criticalThreshold: 30 -# deadThreshold: 60 - type: entity id: RightFootVox @@ -229,8 +183,4 @@ state: "r_foot" - type: BodyPart partType: Foot - size: 2 - compatibility: Biological symmetry: Right -# criticalThreshold: 30 -# deadThreshold: 60 diff --git a/Resources/Prototypes/Body/Presets/animal.yml b/Resources/Prototypes/Body/Presets/animal.yml deleted file mode 100644 index dc54809c69..0000000000 --- a/Resources/Prototypes/Body/Presets/animal.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: bodyPreset - name: "animal preset" - id: AnimalPreset - partIDs: - legs: LegsAnimal - feet: FeetAnimal - torso: TorsoAnimal diff --git a/Resources/Prototypes/Body/Presets/bot.yml b/Resources/Prototypes/Body/Presets/bot.yml deleted file mode 100644 index 752509b454..0000000000 --- a/Resources/Prototypes/Body/Presets/bot.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: bodyPreset - name: "bot" - id: BotPreset - partIDs: - hand 1: LeftArmBorg diff --git a/Resources/Prototypes/Body/Presets/drone.yml b/Resources/Prototypes/Body/Presets/drone.yml deleted file mode 100644 index 214acc4b3e..0000000000 --- a/Resources/Prototypes/Body/Presets/drone.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: bodyPreset - name: "drone" - id: DronePreset - partIDs: - hand 1: LeftArmBorg - hand 2: LeftArmBorg - hand 3: LeftArmBorg - hand 4: LeftArmBorg - hand 5: RightArmBorg - hand 6: RightArmBorg diff --git a/Resources/Prototypes/Body/Presets/human.yml b/Resources/Prototypes/Body/Presets/human.yml deleted file mode 100644 index d4619f693d..0000000000 --- a/Resources/Prototypes/Body/Presets/human.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyPreset - name: "human" - id: HumanPreset - partIDs: - head: HeadHuman - torso: TorsoHuman - right arm: RightArmHuman - left arm: LeftArmHuman - right hand: RightHandHuman - left hand: LeftHandHuman - right leg: RightLegHuman - left leg: LeftLegHuman - right foot: RightFootHuman - left foot: LeftFootHuman diff --git a/Resources/Prototypes/Body/Presets/primate.yml b/Resources/Prototypes/Body/Presets/primate.yml deleted file mode 100644 index 743046c607..0000000000 --- a/Resources/Prototypes/Body/Presets/primate.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: bodyPreset - name: "primate preset" - id: PrimatePreset - partIDs: - hands: HandsAnimal - legs: LegsAnimal - feet: FeetAnimal - torso: TorsoAnimal diff --git a/Resources/Prototypes/Body/Presets/rat.yml b/Resources/Prototypes/Body/Presets/rat.yml deleted file mode 100644 index c48b5ec88a..0000000000 --- a/Resources/Prototypes/Body/Presets/rat.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: bodyPreset - name: "animal preset" - id: RatPreset - partIDs: - legs: LegsAnimal - feet: FeetAnimal - torso: TorsoRat diff --git a/Resources/Prototypes/Body/Presets/reptilian.yml b/Resources/Prototypes/Body/Presets/reptilian.yml deleted file mode 100644 index cc1d09150c..0000000000 --- a/Resources/Prototypes/Body/Presets/reptilian.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyPreset - name: "reptilian" - id: ReptilianPreset - partIDs: - head: HeadReptilian - torso: TorsoReptilian - right arm: RightArmReptilian - left arm: LeftArmReptilian - right hand: RightHandReptilian - left hand: LeftHandReptilian - right leg: RightLegReptilian - left leg: LeftLegReptilian - right foot: RightFootReptilian - left foot: LeftFootReptilian diff --git a/Resources/Prototypes/Body/Presets/skeleton.yml b/Resources/Prototypes/Body/Presets/skeleton.yml deleted file mode 100644 index 07d372c2eb..0000000000 --- a/Resources/Prototypes/Body/Presets/skeleton.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyPreset - name: "skeleton" - id: SkeletonPreset - partIDs: - head: HeadSkeleton - torso: TorsoSkeleton - right arm: RightArmSkeleton - left arm: LeftArmSkeleton - right hand: RightHandSkeleton - left hand: LeftHandSkeleton - right leg: RightLegSkeleton - left leg: LeftLegSkeleton - right foot: RightFootSkeleton - left foot: LeftFootSkeleton diff --git a/Resources/Prototypes/Body/Presets/slime.yml b/Resources/Prototypes/Body/Presets/slime.yml deleted file mode 100644 index c7a4f853ff..0000000000 --- a/Resources/Prototypes/Body/Presets/slime.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyPreset - name: "slime" - id: SlimePreset - partIDs: - head: HeadSlime - torso: TorsoSlime - right arm: RightArmSlime - left arm: LeftArmSlime - right hand: RightHandSlime - left hand: LeftHandSlime - right leg: RightLegSlime - left leg: LeftLegSlime - right foot: RightFootSlime - left foot: LeftFootSlime diff --git a/Resources/Prototypes/Body/Presets/vox.yml b/Resources/Prototypes/Body/Presets/vox.yml deleted file mode 100644 index 5659aa51be..0000000000 --- a/Resources/Prototypes/Body/Presets/vox.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyPreset - name: "vox" - id: VoxPreset - partIDs: - head: HeadVox - torso: TorsoVox - right arm: RightArmVox - left arm: LeftArmVox - right hand: RightHandVox - left hand: LeftHandVox - right leg: RightLegVox - left leg: LeftLegVox - right foot: RightFootVox - left foot: LeftFootVox diff --git a/Resources/Prototypes/Body/Prototypes/a_ghost.yml b/Resources/Prototypes/Body/Prototypes/a_ghost.yml new file mode 100644 index 0000000000..f04ed70197 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/a_ghost.yml @@ -0,0 +1,22 @@ +- type: body + id: Aghost + name: "aGhost" + root: torso + slots: + torso: + part: TorsoHuman + connections: + - right arm + - left arm + right arm: + part: RightArmHuman + connections: + - right hand + left arm: + part: LeftArmHuman + connections: + - left hand + right hand: + part: RightHandHuman + left hand: + part: LeftHandHuman diff --git a/Resources/Prototypes/Body/Prototypes/animal.yml b/Resources/Prototypes/Body/Prototypes/animal.yml new file mode 100644 index 0000000000..aeea15df6d --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/animal.yml @@ -0,0 +1,21 @@ +- type: body + id: Animal + name: "animal" + root: torso + slots: + torso: + part: TorsoAnimal + connections: + - legs + organs: + lungs: OrganAnimalLungs + stomach: OrganAnimalStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal diff --git a/Resources/Prototypes/Body/Prototypes/bot.yml b/Resources/Prototypes/Body/Prototypes/bot.yml new file mode 100644 index 0000000000..848db2a4fd --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/bot.yml @@ -0,0 +1,7 @@ +- type: body + id: Bot + name: "bot" + root: hand 1 + slots: + hand 1: + part: LeftArmBorg diff --git a/Resources/Prototypes/Body/Prototypes/drone.yml b/Resources/Prototypes/Body/Prototypes/drone.yml new file mode 100644 index 0000000000..00acba9646 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/drone.yml @@ -0,0 +1,27 @@ +- type: body + id: Drone + name: "drone" + root: hand 1 + slots: + hand 1: + part: LeftArmBorg + connections: + - hand 2 + hand 2: + part: LeftArmBorg + connections: + - hand 3 + hand 3: + part: LeftArmBorg + connections: + - hand 4 + hand 4: + part: LeftArmBorg + connections: + - hand 5 + hand 5: + part: RightArmBorg + connections: + - hand 6 + hand 6: + part: RightArmBorg diff --git a/Resources/Prototypes/Body/Prototypes/human.yml b/Resources/Prototypes/Body/Prototypes/human.yml new file mode 100644 index 0000000000..587ccffe80 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/human.yml @@ -0,0 +1,49 @@ +- type: body + id: Human + name: "human" + root: torso + slots: + head: + part: HeadHuman + connections: + - torso + organs: + brain: OrganHumanBrain + eyes: OrganHumanEyes + torso: + part: TorsoHuman + connections: + - left arm + - right arm + - left leg + - right leg + organs: + heart: OrganHumanHeart + lungs: OrganHumanLungs + stomach: OrganHumanStomach + liver: OrganHumanLiver + kidneys: OrganHumanKidneys + right arm: + part: RightArmHuman + connections: + - right hand + left arm: + part: LeftArmHuman + connections: + - left hand + right hand: + part: RightHandHuman + left hand: + part: LeftHandHuman + right leg: + part: RightLegHuman + connections: + - right foot + left leg: + part: LeftLegHuman + connections: + - left foot + right foot: + part: RightFootHuman + left foot: + part: LeftFootHuman diff --git a/Resources/Prototypes/Body/Prototypes/primate.yml b/Resources/Prototypes/Body/Prototypes/primate.yml new file mode 100644 index 0000000000..2af9273be4 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/primate.yml @@ -0,0 +1,24 @@ +- type: body + id: Primate + name: "primate" + root: torso + slots: + torso: + part: TorsoAnimal + connections: + - hands + - legs + organs: + lungs: OrganAnimalLungs + stomach: OrganAnimalStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + hands: + part: HandsAnimal + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal diff --git a/Resources/Prototypes/Body/Prototypes/rat.yml b/Resources/Prototypes/Body/Prototypes/rat.yml new file mode 100644 index 0000000000..fe77288994 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/rat.yml @@ -0,0 +1,21 @@ +- type: body + id: Rat + name: "animal" + root: torso + slots: + torso: + part: TorsoRat + connections: + - legs + organs: + lungs: OrganRatLungs + stomach: OrganRatStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal diff --git a/Resources/Prototypes/Body/Prototypes/reptilian.yml b/Resources/Prototypes/Body/Prototypes/reptilian.yml new file mode 100644 index 0000000000..0e35effd56 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/reptilian.yml @@ -0,0 +1,49 @@ +- type: body + name: "reptilian" + id: Reptilian + root: torso + slots: + head: + part: HeadReptilian + connections: + - torso + organs: + brain: OrganHumanBrain + eyes: OrganHumanEyes + torso: + part: TorsoReptilian + organs: + heart: OrganAnimalHeart + lungs: OrganHumanLungs + stomach: OrganReptilianStomach + liver: OrganAnimalLiver + kidneys: OrganHumanKidneys + connections: + - left arm + - right arm + - left leg + - right leg + right arm: + part: RightArmReptilian + connections: + - right hand + left arm: + part: LeftArmReptilian + connections: + - left hand + right hand: + part: RightHandReptilian + left hand: + part: LeftHandReptilian + right leg: + part: RightLegReptilian + connections: + - right foot + left leg: + part: LeftLegReptilian + connections: + - left foot + right foot: + part: RightFootReptilian + left foot: + part: LeftFootReptilian diff --git a/Resources/Prototypes/Body/Prototypes/skeleton.yml b/Resources/Prototypes/Body/Prototypes/skeleton.yml new file mode 100644 index 0000000000..e608186369 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/skeleton.yml @@ -0,0 +1,40 @@ +- type: body + id: Skeleton + name: "skeleton" + root: torso + slots: + head: + part: HeadSkeleton + connections: + - torso + torso: + part: TorsoSkeleton + connections: + - left arm + - right arm + - left leg + - right leg + right arm: + part: RightArmSkeleton + connections: + - right hand + left arm: + part: LeftArmSkeleton + connections: + - left hand + right hand: + part: RightHandSkeleton + left hand: + part: LeftHandSkeleton + right leg: + part: RightLegSkeleton + connections: + - right foot + left leg: + part: LeftLegSkeleton + connections: + - left foot + right foot: + part: RightFootSkeleton + left foot: + part: LeftFootSkeleton diff --git a/Resources/Prototypes/Body/Prototypes/slime.yml b/Resources/Prototypes/Body/Prototypes/slime.yml new file mode 100644 index 0000000000..c9d746879b --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/slime.yml @@ -0,0 +1,43 @@ +- type: body + id: Slime + name: "slime" + root: torso + slots: + head: + part: HeadSlime + connections: + - torso + torso: + part: TorsoSlime + connections: + - left arm + - right arm + - left leg + - right leg + organs: + core: SentientSlimeCore + lungs: OrganSlimeLungs + right arm: + part: RightArmSlime + connections: + - right hand + left arm: + part: LeftArmSlime + connections: + - left hand + right hand: + part: RightHandSlime + left hand: + part: LeftHandSlime + right leg: + part: RightLegSlime + connections: + - right foot + left leg: + part: LeftLegSlime + connections: + - left foot + right foot: + part: RightFootSlime + left foot: + part: LeftFootSlime diff --git a/Resources/Prototypes/Body/Prototypes/vox.yml b/Resources/Prototypes/Body/Prototypes/vox.yml new file mode 100644 index 0000000000..f81f452fe1 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/vox.yml @@ -0,0 +1,49 @@ +- type: body + id: Vox + name: "vox" + root: torso + slots: + head: + part: HeadVox + connections: + - torso + organs: + brain: OrganHumanBrain + eyes: OrganHumanEyes + torso: + part: TorsoVox + connections: + - left arm + - right arm + - left leg + - right leg + organs: + heart: OrganHumanHeart + lungs: OrganVoxLungs + stomach: OrganHumanStomach + liver: OrganHumanLiver + kidneys: OrganHumanKidneys + right arm: + part: RightArmVox + connections: + - right hand + left arm: + part: LeftArmVox + connections: + - left hand + right hand: + part: RightHandVox + left hand: + part: LeftHandVox + right leg: + part: RightLegVox + connections: + - right foot + left leg: + part: LeftLegVox + connections: + - left foot + right foot: + part: RightFootVox + left foot: + part: LeftFootVox diff --git a/Resources/Prototypes/Body/Templates/aghost.yml b/Resources/Prototypes/Body/Templates/aghost.yml deleted file mode 100644 index 84d27bc5cc..0000000000 --- a/Resources/Prototypes/Body/Templates/aghost.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: bodyTemplate - id: AGhostTemplate - name: "humanoid" - centerSlot: "torso" - slots: - left arm: Arm - left hand: Hand - right arm: Arm - right hand: Hand - connections: - left arm: - - left hand - right arm: - - right hand diff --git a/Resources/Prototypes/Body/Templates/animal.yml b/Resources/Prototypes/Body/Templates/animal.yml deleted file mode 100644 index c3c246753d..0000000000 --- a/Resources/Prototypes/Body/Templates/animal.yml +++ /dev/null @@ -1,14 +0,0 @@ -# I know I've skipped few parts -- type: bodyTemplate - id: AnimalTemplate - name: "animal template" - centerSlot: "torso" - slots: - torso: Torso - legs: Leg - feet: Foot - connections: - torso: - - legs - legs: - - feet diff --git a/Resources/Prototypes/Body/Templates/bot.yml b/Resources/Prototypes/Body/Templates/bot.yml deleted file mode 100644 index 4e5eef65a2..0000000000 --- a/Resources/Prototypes/Body/Templates/bot.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: bodyTemplate - id: BotTemplate - name: "bot" - centerSlot: "torso" - slots: - hand 1: hand diff --git a/Resources/Prototypes/Body/Templates/drone.yml b/Resources/Prototypes/Body/Templates/drone.yml deleted file mode 100644 index 046b79764c..0000000000 --- a/Resources/Prototypes/Body/Templates/drone.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: bodyTemplate - id: DroneTemplate - name: "drone" - centerSlot: "torso" - slots: - hand 1: hand - hand 2: hand - hand 3: hand - hand 4: hand - hand 5: hand - hand 6: hand diff --git a/Resources/Prototypes/Body/Templates/humanoid.yml b/Resources/Prototypes/Body/Templates/humanoid.yml deleted file mode 100644 index f840fd681a..0000000000 --- a/Resources/Prototypes/Body/Templates/humanoid.yml +++ /dev/null @@ -1,31 +0,0 @@ -- type: bodyTemplate - id: HumanoidTemplate - name: "humanoid" - centerSlot: "torso" - slots: - head: Head - torso: Torso - left arm: Arm - left hand: Hand - right arm: Arm - right hand: Hand - left leg: Leg - left foot: Foot - right leg: Leg - right foot: Foot - connections: - head: - - torso - torso: - - left arm - - right arm - - left leg - - right leg - left arm: - - left hand - right arm: - - right hand - left leg: - - left foot - right leg: - - right foot diff --git a/Resources/Prototypes/Body/Templates/primate.yml b/Resources/Prototypes/Body/Templates/primate.yml deleted file mode 100644 index 359361013d..0000000000 --- a/Resources/Prototypes/Body/Templates/primate.yml +++ /dev/null @@ -1,16 +0,0 @@ -# I know I've skipped few parts -- type: bodyTemplate - id: PrimateTemplate - name: "primate template" - centerSlot: "torso" - slots: - torso: Torso - hands: Hand - legs: Leg - feet: Foot - connections: - torso: - - hands - - legs - legs: - - feet diff --git a/Resources/Prototypes/Body/Templates/quadrupedal.yml b/Resources/Prototypes/Body/Templates/quadrupedal.yml deleted file mode 100644 index 6d32ea27bd..0000000000 --- a/Resources/Prototypes/Body/Templates/quadrupedal.yml +++ /dev/null @@ -1,31 +0,0 @@ -- type: bodyTemplate - id: QuadrupedalTemplate - name: "quadrupedal" - centerSlot: "torso" - slots: - head: Head - torso: Torso - left front leg: Leg - left front paw: Foot - right front leg: Leg - right front paw: Foot - left hind leg: Leg - left hind paw: Foot - right hind leg: Leg - right hind paw: Foot - connections: - head: - - torso - torso: - - left front leg - - right front leg - - left hind leg - - right hind paw - left front leg: - - left front paw - right front leg: - - right front paw - left hind leg: - - left hind paw - right hind leg: - - right hind paw diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 8dbde8f98c..5c81702c41 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -708,8 +708,7 @@ - type: Speech speechSounds: Monkey - type: Body - template: PrimateTemplate - preset: PrimatePreset + prototype: Primate - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 35a12f2fe8..174522f3ad 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -49,8 +49,7 @@ Slash: 12 Piercing: 8 - type: Body - template: AnimalTemplate - preset: RatPreset + prototype: Rat - type: Appearance - type: DamageStateVisuals rotate: true @@ -207,8 +206,7 @@ Slash: 5 Piercing: 3 - type: Body - template: AnimalTemplate - preset: RatPreset + prototype: Rat - type: Appearance - type: DamageStateVisuals rotate: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 0cb379a132..09982d608b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -89,8 +89,7 @@ - type: InputMover - type: MobMover - type: Body - template: BotTemplate - preset: BotPreset + prototype: Bot - type: entity parent: MobSiliconBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index db3d6e48f9..eef2e0cd5b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -114,8 +114,7 @@ - TemporaryBlindness - Pacified - type: Body - template: AnimalTemplate - preset: AnimalPreset + prototype: Animal - type: Examiner - type: MeleeWeapon hidden: true diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 5fc76d03cd..1a2ececc42 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -24,8 +24,7 @@ ignorePaused: true bodyType: Kinematic - type: Body - template: AGhostTemplate - preset: HumanPreset + prototype: Aghost - type: Access groups: - AllAccess diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 42ebc29af8..c03bec348f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -51,8 +51,7 @@ - type: Hands showInHands: false - type: Body - template: DroneTemplate - preset: DronePreset + prototype: Drone - type: DoAfter - type: Pullable - type: Examiner diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 5470937e73..dcfb2c7982 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -175,8 +175,7 @@ - type: Humanoid species: Human - type: Body - template: HumanoidTemplate - preset: HumanPreset + prototype: Human - type: Damageable damageContainer: Biological - type: RadiationReceiver @@ -366,8 +365,7 @@ - type: Humanoid species: Human - type: Body - template: HumanoidTemplate - preset: HumanPreset + prototype: Human - type: Damageable damageContainer: Biological - type: MobState diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index dea5c5dd96..733de0f758 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -23,8 +23,7 @@ drawdepth: Mobs scale: 1, 0.8 - type: Body - template: HumanoidTemplate - preset: HumanPreset + prototype: Human - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index fe5b6611cd..f282651851 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -13,8 +13,7 @@ sprite: Mobs/Species/Reptilian/parts.rsi state: full - type: Body - template: HumanoidTemplate - preset: ReptilianPreset + prototype: Reptilian - type: LizardAccent - type: Speech speechSounds: Lizard diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 2f98743252..71cabfab5f 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -11,8 +11,7 @@ sprite: Mobs/Species/Skeleton/parts.rsi state: full - type: Body - template: HumanoidTemplate - preset: SkeletonPreset + prototype: Skeleton gibSound: /Audio/Effects/bone_rattle.ogg - type: Damageable damageContainer: Biological diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 7e81f547bb..be38a22c4c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -10,8 +10,7 @@ sprite: Mobs/Species/Slime/parts.rsi state: full - type: Body - template: HumanoidTemplate - preset: SlimePreset + prototype: Slime - type: Humanoid species: SlimePerson - type: Speech diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index 03193c010e..d5963eb15d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -80,8 +80,7 @@ - map: [ "pocket1" ] - map: [ "pocket2" ] - type: Body - template: HumanoidTemplate - preset: VoxPreset + prototype: Vox # Vox nitrogen stuff is handled in their metabolism - type: Respirator damage: @@ -110,6 +109,5 @@ - type: Humanoid species: Vox - type: Body - template: HumanoidTemplate - preset: VoxPreset + prototype: Vox