diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 1630d4cb10db..0e9b295f718d 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Random; using Robust.Shared.Timing; using System.Numerics; +using Content.Shared.Gibbing.Components; using Content.Shared.Movement.Systems; using Robust.Shared.Audio.Systems; @@ -106,7 +107,17 @@ protected override void RemovePart( _humanoidSystem.SetLayersVisibility(bodyUid, layers, false, true, humanoid); } - public override HashSet GibBody(EntityUid bodyId, bool gibOrgans = false, BodyComponent? body = null, bool deleteItems = false, bool deleteBrain = false) + public override HashSet GibBody( + EntityUid bodyId, + bool gibOrgans = false, + BodyComponent? body = null , + bool deleteItems = false, + bool launchGibs = true, + Vector2? splatDirection = null, + float splatModifier = 1, + Angle splatCone = default, + SoundSpecifier? gibSoundOverride = null + ) { if (!Resolve(bodyId, ref body, false)) return new HashSet(); @@ -118,28 +129,8 @@ public override HashSet GibBody(EntityUid bodyId, bool gibOrgans = fa if (xform.MapUid == null) return new HashSet(); - var gibs = base.GibBody(bodyId, gibOrgans, body, deleteItems, deleteBrain); - - var coordinates = xform.Coordinates; - var filter = Filter.Pvs(bodyId, entityManager: EntityManager); - var audio = AudioParams.Default.WithVariation(0.025f); - - _audio.PlayStatic(body.GibSound, filter, coordinates, true, audio); - - foreach (var entity in gibs) - { - if (deleteItems) - { - if (!HasComp(entity) || deleteBrain) - { - QueueDel(entity); - } - } - else - { - SharedTransform.SetCoordinates(entity, coordinates.Offset(_random.NextVector2(.3f))); - } - } + var gibs = base.GibBody(bodyId, gibOrgans, body, deleteItems, launchGibs: launchGibs, + splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone); RaiseLocalEvent(bodyId, new BeingGibbedEvent(gibs)); QueueDel(bodyId); diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs index 09fdc260d7cd..6606f284327e 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -106,7 +106,7 @@ public void FinishCrushing(Entity(contained, out var body)) Del(contained); - var gibs = _body.GibBody(contained, body: body, gibOrgans: true, deleteBrain: true); + var gibs = _body.GibBody(contained, body: body, gibOrgans: true); foreach (var gib in gibs) { ContainerSystem.Insert((gib, null, null, null), crusher.OutputContainer); diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs index e5e4edddb070..85b6758d78a1 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -5,8 +5,13 @@ using Content.Shared.Body.Part; using Content.Shared.Body.Prototypes; using Content.Shared.DragDrop; +using Content.Shared.Gibbing.Components; +using Content.Shared.Gibbing.Events; +using Content.Shared.Gibbing.Systems; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Utility; @@ -23,7 +28,10 @@ public partial class SharedBodySystem */ [Dependency] private readonly InventorySystem _inventory = default!; - + [Dependency] private readonly GibbingSystem _gibbingSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + private const float GibletLaunchImpulse = 8; + private const float GibletLaunchImpulseVariance = 3; private void InitializeBody() { // Body here to handle root body parts. @@ -263,29 +271,45 @@ public IEnumerable GetBodyAllSlots(EntityUid bodyId, BodyComponent } } - public virtual HashSet GibBody(EntityUid bodyId, bool gibOrgans = false, - BodyComponent? body = null, bool deleteItems = false, bool deleteBrain = false) + public virtual HashSet GibBody( + EntityUid bodyId, + bool gibOrgans = false, + BodyComponent? body = null , + bool deleteItems = false, + bool launchGibs = true, + Vector2? splatDirection = null, + float splatModifier = 1, + Angle splatCone = default, + SoundSpecifier? gibSoundOverride = null + ) { var gibs = new HashSet(); if (!Resolve(bodyId, ref body, false)) return gibs; + var root = GetRootPartOrNull(bodyId, body); + if (root != null && TryComp(root.Value.Entity, out GibbableComponent? gibbable)) + { + gibSoundOverride ??= gibbable.GibSound; + } var parts = GetBodyChildren(bodyId, body).ToArray(); gibs.EnsureCapacity(parts.Length); - foreach (var part in parts) { - SharedTransform.AttachToGridOrMap(part.Id); - gibs.Add(part.Id); + + _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, GibType.Gib, GibContentsOption.Skip, ref gibs, + playAudio: false, launchGibs:true, launchDirection:splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier, + launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone); if (!gibOrgans) continue; foreach (var organ in GetPartOrgans(part.Id, part.Component)) { - SharedTransform.AttachToGridOrMap(organ.Id); - gibs.Add(organ.Id); + _gibbingSystem.TryGibEntityWithRef(bodyId, organ.Id, GibType.Drop, GibContentsOption.Skip, + ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier, + launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone); } } if(TryComp(bodyId, out var inventory)) @@ -296,6 +320,7 @@ public virtual HashSet GibBody(EntityUid bodyId, bool gibOrgans = fal gibs.Add(item); } } + _audioSystem.PlayPredicted(gibSoundOverride, Transform(bodyId).Coordinates, null); return gibs; } } diff --git a/Content.Shared/Gibbing/Components/GibbableComponent.cs b/Content.Shared/Gibbing/Components/GibbableComponent.cs new file mode 100644 index 000000000000..9c7501f0282f --- /dev/null +++ b/Content.Shared/Gibbing/Components/GibbableComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Gibbing.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Gibbing.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(GibbingSystem))] +public sealed partial class GibbableComponent : Component +{ + /// + /// Giblet entity prototypes to randomly select from when spawning additional giblets + /// + [DataField, AutoNetworkedField] + public List GibPrototypes = new(); + + /// + /// Number of giblet entities to spawn in addition to entity contents + /// + [DataField, AutoNetworkedField] + public int GibCount; + + /// + /// Sound to be played when this entity is gibbed, only played when playsound is true on the gibbing function + /// + [DataField, AutoNetworkedField] + public SoundSpecifier? GibSound = new SoundCollectionSpecifier("gib", AudioParams.Default.WithVariation(0.025f)); + + /// + /// Max distance giblets can be dropped from an entity when NOT using physics-based scattering + /// + [DataField, AutoNetworkedField] + public float GibScatterRange = 0.3f; +} diff --git a/Content.Shared/Gibbing/Events/GibbingEvents.cs b/Content.Shared/Gibbing/Events/GibbingEvents.cs new file mode 100644 index 000000000000..949b10eab9a8 --- /dev/null +++ b/Content.Shared/Gibbing/Events/GibbingEvents.cs @@ -0,0 +1,50 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Gibbing.Events; + + + +/// +/// Called just before we actually gib the target entity +/// +/// The entity being gibed +/// What type of gibbing is occuring +/// Containers we are allow to gib +/// Containers we are allow not allowed to gib +[ByRefEvent] public record struct AttemptEntityContentsGibEvent( + EntityUid Target, + GibContentsOption GibType, + List? AllowedContainers, + List? ExcludedContainers + ); + + +/// +/// Called just before we actually gib the target entity +/// +/// The entity being gibed +/// how many giblets to spawn +/// What type of gibbing is occuring +[ByRefEvent] public record struct AttemptEntityGibEvent(EntityUid Target, int GibletCount, GibType GibType); + +/// +/// Called immediately after we gib the target entity +/// +/// The entity being gibbed +/// Any entities that are spilled out (if any) +[ByRefEvent] public record struct EntityGibbedEvent(EntityUid Target, List DroppedEntities); + +[Serializable, NetSerializable] +public enum GibType : byte +{ + Skip, + Drop, + Gib, +} + +public enum GibContentsOption : byte +{ + Skip, + Drop, + Gib +} diff --git a/Content.Shared/Gibbing/Systems/GibbingSystem.cs b/Content.Shared/Gibbing/Systems/GibbingSystem.cs new file mode 100644 index 000000000000..5d00b2a49128 --- /dev/null +++ b/Content.Shared/Gibbing/Systems/GibbingSystem.cs @@ -0,0 +1,341 @@ +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using Content.Shared.Gibbing.Components; +using Content.Shared.Gibbing.Events; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.Gibbing.Systems; + +public sealed class GibbingSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + //TODO: (future optimization) implement a system that "caps" giblet entities by deleting the oldest ones once we reach a certain limit, customizable via CVAR + + /// + /// Attempt to gib a specified entity. That entity must have a gibable components. This method is NOT recursive will only + /// work on the target and any entities it contains (depending on gibContentsOption) + /// + /// The outermost entity we care about, used to place the dropped items + /// Target entity/comp we wish to gib + /// What type of gibing are we performing + /// What type of gibing do we perform on any container contents? + /// a hashset containing all the entities that have been dropped/created + /// How much to multiply the random spread on dropped giblets(if we are dropping them!) + /// Should we play audio + /// A list of containerIds on the target that permit gibing + /// A list of containerIds on the target that DO NOT permit gibing + /// The cone we are launching giblets in (if we are launching them!) + /// Should we launch giblets or just drop them + /// The direction to launch giblets (if we are launching them!) + /// The impluse to launch giblets at(if we are launching them!) + /// /// Should we log if we are missing a gibbableComp when we call this function + /// The variation in giblet launch impulse (if we are launching them!) + /// True if successful, false if not + public bool TryGibEntity(EntityUid outerEntity, Entity gibbable, GibType gibType, + GibContentsOption gibContentsOption, + out HashSet droppedEntities, bool launchGibs = true, + Vector2 launchDirection = default, float launchImpulse = 0f, float launchImpulseVariance = 0f, + Angle launchCone = default, + float randomSpreadMod = 1.0f, bool playAudio = true, List? allowedContainers = null, + List? excludedContainers = null, bool logMissingGibable = false) + { + droppedEntities = new(); + return TryGibEntityWithRef(outerEntity, gibbable, gibType, gibContentsOption, ref droppedEntities, + launchGibs, launchDirection, launchImpulse, launchImpulseVariance, launchCone, randomSpreadMod, playAudio, + allowedContainers, excludedContainers, logMissingGibable); + } + + + /// + /// Attempt to gib a specified entity. That entity must have a gibable components. This method is NOT recursive will only + /// work on the target and any entities it contains (depending on gibContentsOption) + /// + /// The outermost entity we care about, used to place the dropped items + /// Target entity/comp we wish to gib + /// What type of gibing are we performing + /// What type of gibing do we perform on any container contents? + /// a hashset containing all the entities that have been dropped/created + /// How much to multiply the random spread on dropped giblets(if we are dropping them!) + /// Should we play audio + /// A list of containerIds on the target that permit gibing + /// A list of containerIds on the target that DO NOT permit gibing + /// The cone we are launching giblets in (if we are launching them!) + /// Should we launch giblets or just drop them + /// The direction to launch giblets (if we are launching them!) + /// The impluse to launch giblets at(if we are launching them!) + /// The variation in giblet launch impulse (if we are launching them!) + /// Should we log if we are missing a gibbableComp when we call this function + /// True if successful, false if not + public bool TryGibEntityWithRef( + EntityUid outerEntity, + Entity gibbable, + GibType gibType, + GibContentsOption gibContentsOption, + ref HashSet droppedEntities, + bool launchGibs = true, + Vector2? launchDirection = null, + float launchImpulse = 0f, + float launchImpulseVariance = 0f, + Angle launchCone = default, + float randomSpreadMod = 1.0f, + bool playAudio = true, + List? allowedContainers = null, + List? excludedContainers = null, + bool logMissingGibable = false) + { + if (!Resolve(gibbable, ref gibbable.Comp, logMissing: false)) + { + DropEntity(gibbable, Transform(outerEntity), randomSpreadMod, ref droppedEntities, + launchGibs, launchDirection, launchImpulse, launchImpulseVariance, launchCone); + if (logMissingGibable) + { + Log.Warning($"{ToPrettyString(gibbable)} does not have a GibbableComponent! " + + $"This is not required but may cause issues contained items to not be dropped."); + } + + return false; + } + + if (gibType == GibType.Skip && gibContentsOption == GibContentsOption.Skip) + return true; + if (launchGibs) + { + randomSpreadMod = 0; + } + + var parentXform = Transform(outerEntity); + HashSet validContainers = new(); + var gibContentsAttempt = + new AttemptEntityContentsGibEvent(gibbable, gibContentsOption, allowedContainers, excludedContainers); + RaiseLocalEvent(gibbable, ref gibContentsAttempt); + + foreach (var container in _containerSystem.GetAllContainers(gibbable)) + { + var valid = true; + if (allowedContainers != null) + valid = allowedContainers.Contains(container.ID); + if (excludedContainers != null) + valid = valid && !excludedContainers.Contains(container.ID); + if (valid) + validContainers.Add(container); + } + + switch (gibContentsOption) + { + case GibContentsOption.Skip: + break; + case GibContentsOption.Drop: + { + foreach (var container in validContainers) + { + foreach (var ent in container.ContainedEntities) + { + DropEntity(new Entity(ent, null), parentXform, randomSpreadMod, + ref droppedEntities, launchGibs, + launchDirection, launchImpulse, launchImpulseVariance, launchCone); + } + } + + break; + } + case GibContentsOption.Gib: + { + foreach (var container in _containerSystem.GetAllContainers(gibbable)) + { + foreach (var ent in container.ContainedEntities) + { + GibEntity(new Entity(ent, null), parentXform, randomSpreadMod, + ref droppedEntities, launchGibs, + launchDirection, launchImpulse, launchImpulseVariance, launchCone); + } + } + + break; + } + } + + switch (gibType) + { + case GibType.Skip: + break; + case GibType.Drop: + { + DropEntity(gibbable, parentXform, randomSpreadMod, ref droppedEntities, launchGibs, + launchDirection, launchImpulse, launchImpulseVariance, launchCone); + break; + } + case GibType.Gib: + { + GibEntity(gibbable, parentXform, randomSpreadMod, ref droppedEntities, launchGibs, + launchDirection, launchImpulse, launchImpulseVariance, launchCone); + break; + } + } + + if (playAudio) + { + _audioSystem.PlayPredicted(gibbable.Comp.GibSound, parentXform.Coordinates, null); + } + + if (gibType == GibType.Gib) + QueueDel(gibbable); + return true; + } + + private void DropEntity(Entity gibbable, TransformComponent parentXform, float randomSpreadMod, + ref HashSet droppedEntities, bool flingEntity, Vector2? scatterDirection, float scatterImpulse, + float scatterImpulseVariance, Angle scatterCone) + { + var gibCount = 0; + if (Resolve(gibbable, ref gibbable.Comp, logMissing: false)) + { + gibCount = gibbable.Comp.GibCount; + } + + var gibAttemptEvent = new AttemptEntityGibEvent(gibbable, gibCount, GibType.Drop); + RaiseLocalEvent(gibbable, ref gibAttemptEvent); + switch (gibAttemptEvent.GibType) + { + case GibType.Skip: + return; + case GibType.Gib: + GibEntity(gibbable, parentXform, randomSpreadMod, ref droppedEntities, flingEntity, scatterDirection, + scatterImpulse, scatterImpulseVariance, scatterCone, deleteTarget: false); + return; + } + + _transformSystem.AttachToGridOrMap(gibbable); + _transformSystem.SetCoordinates(gibbable, parentXform.Coordinates); + _transformSystem.SetWorldRotation(gibbable, _random.NextAngle()); + droppedEntities.Add(gibbable); + if (flingEntity) + { + FlingDroppedEntity(gibbable, scatterDirection, scatterImpulse, scatterImpulseVariance, scatterCone); + } + + var gibbedEvent = new EntityGibbedEvent(gibbable, new List {gibbable}); + RaiseLocalEvent(gibbable, ref gibbedEvent); + } + + private List GibEntity(Entity gibbable, TransformComponent parentXform, + float randomSpreadMod, + ref HashSet droppedEntities, bool flingEntity, Vector2? scatterDirection, float scatterImpulse, + float scatterImpulseVariance, Angle scatterCone, bool deleteTarget = true) + { + var localGibs = new List(); + var gibCount = 0; + var gibProtoCount = 0; + if (Resolve(gibbable, ref gibbable.Comp, logMissing: false)) + { + gibCount = gibbable.Comp.GibCount; + gibProtoCount = gibbable.Comp.GibPrototypes.Count; + } + + var gibAttemptEvent = new AttemptEntityGibEvent(gibbable, gibCount, GibType.Drop); + RaiseLocalEvent(gibbable, ref gibAttemptEvent); + switch (gibAttemptEvent.GibType) + { + case GibType.Skip: + return localGibs; + case GibType.Drop: + DropEntity(gibbable, parentXform, randomSpreadMod, ref droppedEntities, flingEntity, + scatterDirection, scatterImpulse, scatterImpulseVariance, scatterCone); + localGibs.Add(gibbable); + return localGibs; + } + + if (gibbable.Comp != null && gibProtoCount > 0) + { + if (flingEntity) + { + for (var i = 0; i < gibAttemptEvent.GibletCount; i++) + { + if (!TryCreateRandomGiblet(gibbable.Comp, parentXform.Coordinates, false, out var giblet, + randomSpreadMod)) + continue; + FlingDroppedEntity(giblet.Value, scatterDirection, scatterImpulse, scatterImpulseVariance, + scatterCone); + droppedEntities.Add(giblet.Value); + } + } + else + { + for (var i = 0; i < gibAttemptEvent.GibletCount; i++) + { + if (TryCreateRandomGiblet(gibbable.Comp, parentXform.Coordinates, false, out var giblet, + randomSpreadMod)) + droppedEntities.Add(giblet.Value); + } + } + } + + _transformSystem.AttachToGridOrMap(gibbable, Transform(gibbable)); + if (flingEntity) + { + FlingDroppedEntity(gibbable, scatterDirection, scatterImpulse, scatterImpulseVariance, scatterCone); + } + + var gibbedEvent = new EntityGibbedEvent(gibbable, localGibs); + RaiseLocalEvent(gibbable, ref gibbedEvent); + if (deleteTarget) + QueueDel(gibbable); + return localGibs; + } + + + public bool TryCreateRandomGiblet(Entity gibbable, [NotNullWhen(true)] out EntityUid? gibletEntity, + float randomSpreadModifier = 1.0f, bool playSound = true) + { + gibletEntity = null; + return Resolve(gibbable, ref gibbable.Comp) && TryCreateRandomGiblet(gibbable.Comp, Transform(gibbable).Coordinates, + playSound, out gibletEntity, randomSpreadModifier); + } + + public bool TryCreateAndFlingRandomGiblet(Entity gibbable, [NotNullWhen(true)] out EntityUid? gibletEntity, + Vector2 scatterDirection, float force, float scatterImpulseVariance, Angle scatterCone = default, + bool playSound = true) + { + gibletEntity = null; + if (!Resolve(gibbable, ref gibbable.Comp) || + !TryCreateRandomGiblet(gibbable.Comp, Transform(gibbable).Coordinates, playSound, out gibletEntity)) + return false; + FlingDroppedEntity(gibletEntity.Value, scatterDirection, force, scatterImpulseVariance, scatterCone); + return true; + } + + private void FlingDroppedEntity(EntityUid target, Vector2? direction, float impulse, float impulseVariance, + Angle scatterConeAngle) + { + var scatterAngle = direction?.ToAngle() ?? _random.NextAngle(); + var scatterVector = _random.NextAngle(scatterAngle - scatterConeAngle / 2, scatterAngle + scatterConeAngle / 2) + .ToVec() * (impulse + _random.NextFloat(impulseVariance)); + _physicsSystem.ApplyLinearImpulse(target, scatterVector); + } + + private bool TryCreateRandomGiblet(GibbableComponent gibbable, EntityCoordinates coords, + bool playSound, [NotNullWhen(true)] out EntityUid? gibletEntity, float? randomSpreadModifier = null) + { + gibletEntity = null; + if (gibbable.GibPrototypes.Count == 0) + return false; + gibletEntity = Spawn(gibbable.GibPrototypes[_random.Next(0, gibbable.GibPrototypes.Count)], + randomSpreadModifier == null + ? coords + : coords.Offset(_random.NextVector2(gibbable.GibScatterRange * randomSpreadModifier.Value))); + if (playSound) + _audioSystem.PlayPredicted(gibbable.GibSound, coords, null); + _transformSystem.SetWorldRotation(gibletEntity.Value, _random.NextAngle()); + return true; + } +} diff --git a/Resources/Prototypes/Body/Organs/Animal/animal.yml b/Resources/Prototypes/Body/Organs/Animal/animal.yml index 358fc74bca60..2f50821df353 100644 --- a/Resources/Prototypes/Body/Organs/Animal/animal.yml +++ b/Resources/Prototypes/Body/Organs/Animal/animal.yml @@ -1,5 +1,5 @@ - type: entity - id: BaseAnimalOrgan + id: BaseAnimalOrganUnGibbable parent: BaseItem abstract: true components: @@ -23,6 +23,12 @@ tags: - Meat +- type: entity + id: BaseAnimalOrgan + parent: BaseAnimalOrganUnGibbable + abstract: true + components: + - type: Gibbable - type: entity id: OrganAnimalLungs diff --git a/Resources/Prototypes/Body/Organs/human.yml b/Resources/Prototypes/Body/Organs/human.yml index 6abf168a9e75..fe33c11029f0 100644 --- a/Resources/Prototypes/Body/Organs/human.yml +++ b/Resources/Prototypes/Body/Organs/human.yml @@ -1,5 +1,5 @@ - type: entity - id: BaseHumanOrgan + id: BaseHumanOrganUnGibbable parent: BaseItem abstract: true components: @@ -27,9 +27,16 @@ tags: - Meat +- type: entity + id: BaseHumanOrgan + parent: BaseHumanOrganUnGibbable + abstract: true + components: + - type: Gibbable + - type: entity id: OrganHumanBrain - parent: BaseHumanOrgan + parent: BaseHumanOrganUnGibbable name: brain description: "The source of incredible, unending intelligence. Honk." components: diff --git a/Resources/Prototypes/Body/Parts/animal.yml b/Resources/Prototypes/Body/Parts/animal.yml index cf65fd21360d..4db026b40fbc 100644 --- a/Resources/Prototypes/Body/Parts/animal.yml +++ b/Resources/Prototypes/Body/Parts/animal.yml @@ -23,6 +23,7 @@ - type: Tag tags: - Trash + - type: Gibbable - type: Extractable juiceSolution: reagents: diff --git a/Resources/Prototypes/Body/Parts/base.yml b/Resources/Prototypes/Body/Parts/base.yml index a27059f59f30..836d0f140afe 100644 --- a/Resources/Prototypes/Body/Parts/base.yml +++ b/Resources/Prototypes/Body/Parts/base.yml @@ -9,6 +9,7 @@ - type: Damageable damageContainer: Biological - type: BodyPart + - type: Gibbable - type: ContainerContainer containers: bodypart: !type:Container diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index 3f41f88ee549..1b378c62332f 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -14,6 +14,7 @@ ents: [] - type: StaticPrice price: 20 + - type: Gibbable - type: Tag tags: - Trash diff --git a/Resources/Prototypes/Body/Parts/terminator.yml b/Resources/Prototypes/Body/Parts/terminator.yml index dec0c99f7c20..58530da959c6 100644 --- a/Resources/Prototypes/Body/Parts/terminator.yml +++ b/Resources/Prototypes/Body/Parts/terminator.yml @@ -16,6 +16,7 @@ containers: bodypart: !type:Container ents: [] + - type: Gibbable - type: StaticPrice price: 200