Decouple gibbing from the body system (#42405)

* Decouple gibbing from the body system

* allow gibs that don't drop giblets

* pass through user

* prediction gon

* comment

* destructible

* playpvs

* very very very very very very very minor cleanup

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
pathetic meowmeow
2026-01-14 00:57:08 -05:00
committed by GitHub
parent 0af56cefcb
commit fb133494cc
37 changed files with 207 additions and 654 deletions

View File

@@ -1,43 +0,0 @@
#nullable enable
using Content.Server.Body.Systems;
using Robust.Shared.GameObjects;
namespace Content.IntegrationTests.Tests.Body;
[TestFixture]
public sealed class GibTest
{
[Test]
public async Task TestGib()
{
await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true });
var (server, client) = (pair.Server, pair.Client);
var map = await pair.CreateTestMap();
EntityUid target1 = default;
EntityUid target2 = default;
await server.WaitAssertion(() => target1 = server.EntMan.Spawn("MobHuman", map.MapCoords));
await server.WaitAssertion(() => target2 = server.EntMan.Spawn("MobHuman", map.MapCoords));
await pair.WaitCommand($"setoutfit {server.EntMan.GetNetEntity(target1)} CaptainGear");
await pair.WaitCommand($"setoutfit {server.EntMan.GetNetEntity(target2)} CaptainGear");
await pair.RunTicksSync(5);
var nuid1 = pair.ToClientUid(target1);
var nuid2 = pair.ToClientUid(target2);
Assert.That(client.EntMan.EntityExists(nuid1));
Assert.That(client.EntMan.EntityExists(nuid2));
await server.WaitAssertion(() => server.System<BodySystem>().GibBody(target1, gibOrgans: false));
await server.WaitAssertion(() => server.System<BodySystem>().GibBody(target2, gibOrgans: true));
await pair.RunTicksSync(5);
await pair.WaitCommand("dirty");
await pair.RunTicksSync(5);
Assert.That(!client.EntMan.EntityExists(nuid1));
Assert.That(!client.EntMan.EntityExists(nuid2));
await pair.CleanReturnAsync();
}
}

View File

@@ -0,0 +1,36 @@
#nullable enable
using Content.Shared.Gibbing;
using Robust.Shared.GameObjects;
namespace Content.IntegrationTests.Tests.Body;
[TestFixture]
public sealed class GibTest
{
[Test]
public async Task TestGib()
{
await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true });
var (server, client) = (pair.Server, pair.Client);
var map = await pair.CreateTestMap();
EntityUid target = default;
await server.WaitAssertion(() => target = server.EntMan.Spawn("MobHuman", map.MapCoords));
await pair.WaitCommand($"setoutfit {server.EntMan.GetNetEntity(target)} CaptainGear");
await pair.RunTicksSync(5);
var nuid = pair.ToClientUid(target);
Assert.That(client.EntMan.EntityExists(nuid));
await server.WaitAssertion(() => server.System<GibbingSystem>().Gib(target));
await pair.RunTicksSync(5);
await pair.WaitCommand("dirty");
await pair.RunTicksSync(5);
Assert.That(!client.EntMan.EntityExists(nuid));
await pair.CleanReturnAsync();
}
}

View File

@@ -29,6 +29,7 @@ using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Electrocution;
using Content.Shared.Gibbing;
using Content.Shared.Gravity;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
@@ -92,6 +93,7 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SuperBonkSystem _superBonkSystem = default!;
[Dependency] private readonly SlipperySystem _slipperySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
private readonly EntProtoId _actionViewLawsProtoId = "ActionViewLaws";
private readonly ProtoId<SiliconLawsetPrototype> _crewsimovLawset = "Crewsimov";
@@ -128,7 +130,7 @@ public sealed partial class AdminVerbSystem
4, 1, 2, args.Target, maxTileBreak: 0), // it gibs, damage doesn't need to be high.
CancellationToken.None);
_bodySystem.GibBody(args.Target);
_gibbing.Gib(args.Target);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", explodeName, Loc.GetString("admin-smite-explode-description")) // we do this so the description tells admins the Text to run it via console.

View File

@@ -10,6 +10,7 @@ using Content.Shared.Anomaly.Effects;
using Content.Shared.Body.Components;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Gibbing;
using Content.Shared.Mobs;
using Content.Shared.Popups;
using Content.Shared.Whitelist;
@@ -25,7 +26,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly AnomalySystem _anomaly = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
@@ -134,7 +135,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
if (!TryComp<BodyComponent>(ent, out var body))
return;
_body.GibBody(ent, true, body, splatModifier: 5f);
_gibbing.Gib(ent.Owner);
}
private void OnSeverityChanged(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySeverityChangedEvent args)

View File

@@ -3,6 +3,7 @@ using Content.Shared.Atmos;
using Content.Shared.Atmos.Rotting;
using Content.Shared.Body.Events;
using Content.Shared.Damage.Systems;
using Content.Shared.Gibbing;
using Content.Shared.Temperature.Components;
using Robust.Server.Containers;
using Robust.Shared.Physics.Components;
@@ -21,12 +22,12 @@ public sealed class RottingSystem : SharedRottingSystem
{
base.Initialize();
SubscribeLocalEvent<RottingComponent, BeingGibbedEvent>(OnGibbed);
SubscribeLocalEvent<RottingComponent, GibbedBeforeDeletionEvent>(OnGibbed);
SubscribeLocalEvent<TemperatureComponent, IsRottingEvent>(OnTempIsRotting);
}
private void OnGibbed(EntityUid uid, RottingComponent component, BeingGibbedEvent args)
private void OnGibbed(EntityUid uid, RottingComponent component, GibbedBeforeDeletionEvent args)
{
if (!TryComp<PhysicsComponent>(uid, out var physics))
return;

View File

@@ -92,40 +92,4 @@ public sealed class BodySystem : SharedBodySystem
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
_humanoidSystem.SetLayersVisibility((bodyEnt, humanoid), layers, visible: false);
}
public override HashSet<EntityUid> GibBody(
EntityUid bodyId,
bool gibOrgans = false,
BodyComponent? body = null,
bool launchGibs = true,
Vector2? splatDirection = null,
float splatModifier = 1,
Angle splatCone = default,
SoundSpecifier? gibSoundOverride = null
)
{
if (!Resolve(bodyId, ref body, logMissing: false)
|| TerminatingOrDeleted(bodyId)
|| EntityManager.IsQueuedForDeletion(bodyId))
{
return new HashSet<EntityUid>();
}
if (HasComp<GodmodeComponent>(bodyId))
return new HashSet<EntityUid>();
var xform = Transform(bodyId);
if (xform.MapUid is null)
return new HashSet<EntityUid>();
var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs,
splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone);
var ev = new BeingGibbedEvent(gibs);
RaiseLocalEvent(bodyId, ref ev);
QueueDel(bodyId);
return gibs;
}
}

View File

@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.Construction;
using Content.Server.Destructible.Thresholds;
using Content.Server.Destructible.Thresholds.Behaviors;
@@ -16,6 +15,7 @@ using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.Destructible.Thresholds.Triggers;
using Content.Shared.FixedPoint;
using Content.Shared.Gibbing;
using Content.Shared.Humanoid;
using Content.Shared.Trigger.Systems;
using JetBrains.Annotations;
@@ -34,7 +34,7 @@ namespace Content.Server.Destructible
[Dependency] public readonly AtmosphereSystem AtmosphereSystem = default!;
[Dependency] public readonly AudioSystem AudioSystem = default!;
[Dependency] public readonly BodySystem BodySystem = default!;
[Dependency] public readonly GibbingSystem Gibbing = default!;
[Dependency] public readonly ConstructionSystem ConstructionSystem = default!;
[Dependency] public readonly ExplosionSystem ExplosionSystem = default!;
[Dependency] public readonly StackSystem StackSystem = default!;

View File

@@ -14,10 +14,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
if (system.EntityManager.TryGetComponent(owner, out BodyComponent? body))
{
system.BodySystem.GibBody(owner, _recursive, body);
}
system.Gibbing.Gib(owner, _recursive);
}
}
}

View File

@@ -13,6 +13,7 @@ using Content.Shared.DoAfter;
using Content.Shared.Forensics;
using Content.Shared.Forensics.Components;
using Content.Shared.Forensics.Systems;
using Content.Shared.Gibbing;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
@@ -39,7 +40,7 @@ namespace Content.Server.Forensics
// The solution entities are spawned on MapInit as well, so we have to wait for that to be able to set the DNA in the bloodstream correctly without ResolveSolution failing
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit, after: new[] { typeof(BloodstreamSystem) });
SubscribeLocalEvent<ForensicsComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<ForensicsComponent, GibbedBeforeDeletionEvent>(OnBeingGibbed);
SubscribeLocalEvent<ForensicsComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<ForensicsComponent, GotRehydratedEvent>(OnRehydrated);
SubscribeLocalEvent<CleansForensicsComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(AbsorbentSystem) });
@@ -85,14 +86,14 @@ namespace Content.Server.Forensics
}
}
private void OnBeingGibbed(EntityUid uid, ForensicsComponent component, BeingGibbedEvent args)
private void OnBeingGibbed(Entity<ForensicsComponent> ent, ref GibbedBeforeDeletionEvent args)
{
string dna = Loc.GetString("forensics-dna-unknown");
if (TryComp(uid, out DnaComponent? dnaComp) && dnaComp.DNA != null)
if (TryComp(ent, out DnaComponent? dnaComp) && dnaComp.DNA != null)
dna = dnaComp.DNA;
foreach (EntityUid part in args.GibbedParts)
foreach (var part in args.Giblets)
{
var partComp = EnsureComp<ForensicsComponent>(part);
partComp.DNAs.Add(dna);

View File

@@ -2,12 +2,12 @@
using Content.Shared.Gibbing.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Systems;
using Content.Server.Body.Systems;
using Content.Shared.Gibbing;
namespace Content.Server.Gibbing.Systems;
public sealed class GibOnRoundEndSystem : EntitySystem
{
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
@@ -49,7 +49,7 @@ public sealed class GibOnRoundEndSystem : EntitySystem
if (gibComp.SpawnProto != null)
SpawnAtPosition(gibComp.SpawnProto, Transform(uid).Coordinates);
_body.GibBody(uid, splatModifier: 5f);
_gibbing.Gib(uid);
}
}
}

View File

@@ -1,9 +1,9 @@
using Content.Server.Body.Systems;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Damage.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Gibbing;
using Content.Shared.Guardian;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -31,7 +31,7 @@ namespace Content.Server.Guardian
[Dependency] private readonly SharedActionsSystem _actionSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
@@ -130,7 +130,7 @@ namespace Content.Server.Guardian
// Ensure held items are dropped before deleting guardian.
if (HasComp<HandsComponent>(guardian))
_bodySystem.GibBody(component.HostedGuardian.Value);
_gibbing.Gib(component.HostedGuardian.Value);
QueueDel(guardian);
QueueDel(component.ActionEntity);

View File

@@ -5,6 +5,7 @@ using Content.Server.Popups;
using Content.Shared.Body.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Examine;
using Content.Shared.Gibbing;
using Content.Shared.Popups;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
@@ -20,7 +21,7 @@ public sealed class ImmovableRodSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -125,7 +126,7 @@ public sealed class ImmovableRodSystem : EntitySystem
return;
}
_bodySystem.GibBody(ent, body: body);
_gibbing.Gib(ent);
return;
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Body.Events;
using Content.Shared.Gibbing;
using Content.Shared.Implants.Components;
using Content.Shared.Storage;
using Robust.Shared.Containers;
@@ -11,7 +12,7 @@ public sealed partial class ImplanterSystem
{
SubscribeLocalEvent<ImplantedComponent, ComponentInit>(OnImplantedInit);
SubscribeLocalEvent<ImplantedComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ImplantedComponent, BeingGibbedEvent>(OnGibbed);
SubscribeLocalEvent<ImplantedComponent, GibbedBeforeDeletionEvent>(OnGibbed);
}
private void OnImplantedInit(Entity<ImplantedComponent> ent, ref ComponentInit args)
@@ -26,7 +27,7 @@ public sealed partial class ImplanterSystem
_container.CleanContainer(ent.Comp.ImplantContainer);
}
private void OnGibbed(Entity<ImplantedComponent> ent, ref BeingGibbedEvent args)
private void OnGibbed(Entity<ImplantedComponent> ent, ref GibbedBeforeDeletionEvent args)
{
// Drop the storage implant contents before the implants are deleted by the body being gibbed
foreach (var implant in ent.Comp.ImplantContainer.ContainedEntities)

View File

@@ -1,9 +1,8 @@
using Content.Server.Body.Systems;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.Gibbing;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Kitchen;
@@ -24,7 +23,7 @@ namespace Content.Server.Kitchen.EntitySystems;
public sealed class SharpSystem : EntitySystem
{
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -134,7 +133,7 @@ public sealed class SharpSystem : EntitySystem
args.Args.User,
popupType);
_bodySystem.GibBody(args.Args.Target.Value); // does nothing if ent can't be gibbed
_gibbing.Gib(args.Args.Target.Value); // does nothing if ent can't be gibbed
_destructibleSystem.DestroyEntity(args.Args.Target.Value);
args.Handled = true;

View File

@@ -4,7 +4,6 @@ using Content.Server.Ghost;
using Content.Server.Popups;
using Content.Server.Stack;
using Content.Server.Wires;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
@@ -25,6 +24,7 @@ using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
using Content.Shared.Gibbing;
using Content.Shared.Humanoid;
namespace Content.Server.Materials;
@@ -39,7 +39,7 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
[Dependency] private readonly OpenableSystem _openable = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedBodySystem _body = default!; //bobby
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
@@ -112,7 +112,7 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
Filter.PvsExcept(victim, entityManager: EntityManager),
true);
_body.GibBody(victim, true);
_gibbing.Gib(victim);
_appearance.SetData(entity.Owner, RecyclerVisuals.Bloody, true);
args.Handled = true;
}
@@ -193,7 +193,7 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
_adminLogger.Add(LogType.Gib, logImpact, $"{ToPrettyString(item):victim} was gibbed by {ToPrettyString(uid):entity} ");
if (component.ReclaimSolutions)
SpawnChemicalsFromComposition(uid, item, completion, false, component, xform);
_body.GibBody(item, true);
_gibbing.Gib(item);
_appearance.SetData(uid, RecyclerVisuals.Bloody, true);
}
else

View File

@@ -1,5 +1,5 @@
using System.Linq;
using Content.Shared.Body.Events;
using Content.Shared.Gibbing;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Tag;
@@ -21,19 +21,19 @@ public sealed class TransferMindOnGibSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<TransferMindOnGibComponent, BeingGibbedEvent>(OnGib);
SubscribeLocalEvent<TransferMindOnGibComponent, GibbedBeforeDeletionEvent>(OnGib);
}
private void OnGib(EntityUid uid, TransferMindOnGibComponent component, BeingGibbedEvent args)
private void OnGib(Entity<TransferMindOnGibComponent> ent, ref GibbedBeforeDeletionEvent args)
{
if (!_mindSystem.TryGetMind(uid, out var mindId, out var mind))
if (!_mindSystem.TryGetMind(ent, out var mindId, out var mind))
return;
var validParts = args.GibbedParts.Where(p => _tag.HasTag(p, component.TargetTag)).ToHashSet();
var validParts = args.Giblets.Where(p => _tag.HasTag(p, ent.Comp.TargetTag)).ToHashSet();
if (!validParts.Any())
return;
var ent = _random.Pick(validParts);
_mindSystem.TransferTo(mindId, ent, mind: mind);
var transfer = _random.Pick(validParts);
_mindSystem.TransferTo(mindId, transfer, mind: mind);
}
}

View File

@@ -1002,7 +1002,7 @@ public sealed partial class ShuttleSystem
{
_logger.Add(LogType.Gib, LogImpact.Extreme, $"{ToPrettyString(ent):player} got gibbed by the shuttle" +
$" {ToPrettyString(uid)} arriving from FTL at {xform.Coordinates:coordinates}");
var gibs = _bobby.GibBody(ent, body: mob);
var gibs = _gibbing.Gib(ent);
_immuneEnts.UnionWith(gibs);
continue;
}

View File

@@ -1,5 +1,4 @@
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.Buckle.Systems;
using Content.Server.Parallax;
using Content.Server.Procedural;
@@ -9,6 +8,7 @@ using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared.Buckle.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Gibbing;
using Content.Shared.Light.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Salvage;
@@ -42,7 +42,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BiomeSystem _biomes = default!;
[Dependency] private readonly BodySystem _bobby = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly DamageableSystem _damageSys = default!;
[Dependency] private readonly DockingSystem _dockSystem = default!;

View File

@@ -1,6 +1,7 @@
using Content.Server.Body.Systems;
using Content.Server.Stack;
using Content.Shared.Body.Components;
using Content.Shared.Gibbing;
using Content.Shared.Storage.Components;
using Content.Shared.Whitelist;
using Content.Shared.Xenoarchaeology.Equipment;
@@ -14,7 +15,7 @@ namespace Content.Server.Xenoarchaeology.Equipment.Systems;
public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
@@ -44,7 +45,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
if (!TryComp<BodyComponent>(contained, out var body))
Del(contained);
var gibs = _body.GibBody(contained, body: body, gibOrgans: true);
var gibs = _gibbing.Gib(contained);
foreach (var gib in gibs)
{
ContainerSystem.Insert((gib, null, null, null), crusher.OutputContainer);

View File

@@ -1,7 +0,0 @@
namespace Content.Shared.Body.Events;
/// <summary>
/// Raised when a body gets gibbed, before it is deleted.
/// </summary>
[ByRefEvent]
public readonly record struct BeingGibbedEvent(HashSet<EntityUid> GibbedParts);

View File

@@ -11,6 +11,7 @@ using Content.Shared.EntityEffects.Effects.Solution;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Forensics.Components;
using Content.Shared.Gibbing;
using Content.Shared.HealthExaminable;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
@@ -50,7 +51,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
SubscribeLocalEvent<BloodstreamComponent, SolutionRelayEvent<ReactionAttemptEvent>>(OnReactionAttempt);
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<BloodstreamComponent, GibbedBeforeDeletionEvent>(OnBeingGibbed);
SubscribeLocalEvent<BloodstreamComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<BloodstreamComponent, MetabolismExclusionEvent>(OnMetabolismExclusion);
@@ -262,7 +263,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
}
}
private void OnBeingGibbed(Entity<BloodstreamComponent> ent, ref BeingGibbedEvent args)
private void OnBeingGibbed(Entity<BloodstreamComponent> ent, ref GibbedBeforeDeletionEvent args)
{
SpillAllSolutions(ent.AsNullable());
}

View File

@@ -5,11 +5,8 @@ using Content.Shared.Body.Organ;
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.Gibbing;
using Content.Shared.Inventory;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
@@ -27,7 +24,6 @@ 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;
@@ -42,6 +38,7 @@ public partial class SharedBodySystem
SubscribeLocalEvent<BodyComponent, ComponentInit>(OnBodyInit);
SubscribeLocalEvent<BodyComponent, MapInitEvent>(OnBodyMapInit);
SubscribeLocalEvent<BodyComponent, CanDragEvent>(OnBodyCanDrag);
SubscribeLocalEvent<BodyComponent, BeingGibbedEvent>(OnBeingGibbed);
}
private void OnBodyInserted(Entity<BodyComponent> ent, ref EntInsertedIntoContainerMessage args)
@@ -283,56 +280,22 @@ public partial class SharedBodySystem
}
}
public virtual HashSet<EntityUid> GibBody(
EntityUid bodyId,
bool gibOrgans = false,
BodyComponent? body = null,
bool launchGibs = true,
Vector2? splatDirection = null,
float splatModifier = 1,
Angle splatCone = default,
SoundSpecifier? gibSoundOverride = null)
private void OnBeingGibbed(Entity<BodyComponent> ent, ref BeingGibbedEvent args)
{
var gibs = new HashSet<EntityUid>();
if (!Resolve(bodyId, ref body, logMissing: 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);
var parts = GetBodyChildren(ent, ent).ToArray();
args.Giblets.EnsureCapacity(args.Giblets.Capacity + parts.Length);
foreach (var part in parts)
{
_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))
{
_gibbingSystem.TryGibEntityWithRef(bodyId, organ.Id, GibType.Drop, GibContentsOption.Skip,
ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier,
launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone);
args.Giblets.Add(organ.Id);
}
PredictedQueueDel(part.Id);
}
var bodyTransform = Transform(bodyId);
if (TryComp<InventoryComponent>(bodyId, out var inventory))
foreach (var item in _inventory.GetHandOrInventoryEntities(ent.Owner))
{
foreach (var item in _inventory.GetHandOrInventoryEntities(bodyId))
{
SharedTransform.DropNextTo(item, (bodyId, bodyTransform));
gibs.Add(item);
}
args.Giblets.Add(item);
}
_audioSystem.PlayPredicted(gibSoundOverride, bodyTransform.Coordinates, null);
return gibs;
}
}

View File

@@ -1,9 +1,9 @@
using Content.Shared.Actions;
using Content.Shared.Body.Events;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Devour.Components;
using Content.Shared.DoAfter;
using Content.Shared.Gibbing;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
@@ -33,7 +33,7 @@ public sealed class DevourSystem : EntitySystem
SubscribeLocalEvent<DevourerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DevourerComponent, DevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
SubscribeLocalEvent<DevourerComponent, GibbedBeforeDeletionEvent>(OnGibContents);
}
private void OnStartup(Entity<DevourerComponent> ent, ref ComponentStartup args)
@@ -127,13 +127,11 @@ public sealed class DevourSystem : EntitySystem
_audioSystem.PlayPredicted(ent.Comp.SoundDevour, ent.Owner, ent.Owner);
}
private void OnGibContents(Entity<DevourerComponent> ent, ref BeingGibbedEvent args)
private void OnGibContents(Entity<DevourerComponent> ent, ref GibbedBeforeDeletionEvent args)
{
if (ent.Comp.StomachStorageWhitelist == null)
return;
// For some reason we have two different systems that should handle gibbing,
// and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
_containerSystem.EmptyContainer(ent.Comp.Stomach);
}
}

View File

@@ -1,34 +0,0 @@
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
{
/// <summary>
/// Giblet entity prototypes to randomly select from when spawning additional giblets
/// </summary>
[DataField, AutoNetworkedField]
public List<EntProtoId> GibPrototypes = new();
/// <summary>
/// Number of giblet entities to spawn in addition to entity contents
/// </summary>
[DataField, AutoNetworkedField]
public int GibCount;
/// <summary>
/// Sound to be played when this entity is gibbed, only played when playsound is true on the gibbing function
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier? GibSound = new SoundCollectionSpecifier("gib", AudioParams.Default.WithVariation(0.025f));
/// <summary>
/// Max distance giblets can be dropped from an entity when NOT using physics-based scattering
/// </summary>
[DataField, AutoNetworkedField]
public float GibScatterRange = 0.3f;
}

View File

@@ -1,50 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Gibbing.Events;
/// <summary>
/// Called just before we actually gib the target entity
/// </summary>
/// <param name="Target">The entity being gibed</param>
/// <param name="GibType">What type of gibbing is occuring</param>
/// <param name="AllowedContainers">Containers we are allow to gib</param>
/// <param name="ExcludedContainers">Containers we are allow not allowed to gib</param>
[ByRefEvent] public record struct AttemptEntityContentsGibEvent(
EntityUid Target,
GibContentsOption GibType,
List<string>? AllowedContainers,
List<string>? ExcludedContainers
);
/// <summary>
/// Called just before we actually gib the target entity
/// </summary>
/// <param name="Target">The entity being gibed</param>
/// <param name="GibletCount">how many giblets to spawn</param>
/// <param name="GibType">What type of gibbing is occuring</param>
[ByRefEvent] public record struct AttemptEntityGibEvent(EntityUid Target, int GibletCount, GibType GibType);
/// <summary>
/// Called immediately after we gib the target entity
/// </summary>
/// <param name="Target">The entity being gibbed</param>
/// <param name="DroppedEntities">Any entities that are spilled out (if any)</param>
[ByRefEvent] public record struct EntityGibbedEvent(EntityUid Target, List<EntityUid> DroppedEntities);
[Serializable, NetSerializable]
public enum GibType : byte
{
Skip,
Drop,
Gib,
}
public enum GibContentsOption : byte
{
Skip,
Drop,
Gib
}

View File

@@ -0,0 +1,84 @@
using Content.Shared.Destructible;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
namespace Content.Shared.Gibbing;
public sealed class GibbingSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDestructibleSystem _destructible = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private static readonly SoundSpecifier? GibSound = new SoundCollectionSpecifier("gib", AudioParams.Default.WithVariation(0.025f));
/// <summary>
/// Gibs an entity.
/// </summary>
/// <param name="ent">The entity to gib.</param>
/// <param name="dropGiblets">Whether or not to drop giblets.</param>
/// <param name="user">The user gibbing the entity, if any.</param>
/// <returns>The set of giblets for this entity, if any.</returns>
public HashSet<EntityUid> Gib(EntityUid ent, bool dropGiblets = true, EntityUid? user = null)
{
// user is unused because of prediction woes, eventually it'll be used for audio
// BodySystem handles prediction rather poorly and causes client-sided bugs when we gib on the client
// This guard can be removed once it is gone and replaced by a prediction-safe system.
if (!_net.IsServer)
return new();
if (!_destructible.DestroyEntity(ent))
return new();
_audio.PlayPvs(GibSound, ent);
var gibbed = new HashSet<EntityUid>();
var beingGibbed = new BeingGibbedEvent(gibbed);
RaiseLocalEvent(ent, ref beingGibbed);
if (dropGiblets)
{
foreach (var giblet in gibbed)
{
_transform.DropNextTo(giblet, ent);
FlingDroppedEntity(giblet);
}
}
var beforeDeletion = new GibbedBeforeDeletionEvent(gibbed);
RaiseLocalEvent(ent, ref beforeDeletion);
return gibbed;
}
private const float GibletLaunchImpulse = 8;
private const float GibletLaunchImpulseVariance = 3;
private void FlingDroppedEntity(EntityUid target)
{
var impulse = GibletLaunchImpulse + _random.NextFloat(GibletLaunchImpulseVariance);
var scatterVec = _random.NextAngle().ToVec() * impulse;
_physics.ApplyLinearImpulse(target, scatterVec);
}
}
/// <summary>
/// Raised on an entity when it is being gibbed.
/// </summary>
/// <param name="Giblets">If a component wants to provide giblets to scatter, add them to this hashset.</param>
[ByRefEvent]
public readonly record struct BeingGibbedEvent(HashSet<EntityUid> Giblets);
/// <summary>
/// Raised on an entity when it is about to be deleted after being gibbed.
/// </summary>
/// <param name="Giblets">The set of giblets this entity produced.</param>
[ByRefEvent]
public readonly record struct GibbedBeforeDeletionEvent(HashSet<EntityUid> Giblets);

View File

@@ -1,344 +0,0 @@
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 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
/// <summary>
/// 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)
/// </summary>
/// <param name="outerEntity">The outermost entity we care about, used to place the dropped items</param>
/// <param name="gibbable">Target entity/comp we wish to gib</param>
/// <param name="gibType">What type of gibing are we performing</param>
/// <param name="gibContentsOption">What type of gibing do we perform on any container contents?</param>
/// <param name="droppedEntities">a hashset containing all the entities that have been dropped/created</param>
/// <param name="randomSpreadMod">How much to multiply the random spread on dropped giblets(if we are dropping them!)</param>
/// <param name="playAudio">Should we play audio</param>
/// <param name="allowedContainers">A list of containerIds on the target that permit gibing</param>
/// <param name="excludedContainers">A list of containerIds on the target that DO NOT permit gibing</param>
/// <param name="launchCone">The cone we are launching giblets in (if we are launching them!)</param>
/// <param name="launchGibs">Should we launch giblets or just drop them</param>
/// <param name="launchDirection">The direction to launch giblets (if we are launching them!)</param>
/// <param name="launchImpulse">The impluse to launch giblets at(if we are launching them!)</param>
/// /// <param name="logMissingGibable">Should we log if we are missing a gibbableComp when we call this function</param>
/// <param name="launchImpulseVariance">The variation in giblet launch impulse (if we are launching them!)</param>
/// <returns>True if successful, false if not</returns>
public bool TryGibEntity(Entity<TransformComponent?> outerEntity, Entity<GibbableComponent?> gibbable, GibType gibType,
GibContentsOption gibContentsOption,
out HashSet<EntityUid> droppedEntities, bool launchGibs = true,
Vector2 launchDirection = default, float launchImpulse = 0f, float launchImpulseVariance = 0f,
Angle launchCone = default,
float randomSpreadMod = 1.0f, bool playAudio = true, List<string>? allowedContainers = null,
List<string>? 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);
}
/// <summary>
/// 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)
/// </summary>
/// <param name="outerEntity">The outermost entity we care about, used to place the dropped items</param>
/// <param name="gibbable">Target entity/comp we wish to gib</param>
/// <param name="gibType">What type of gibing are we performing</param>
/// <param name="gibContentsOption">What type of gibing do we perform on any container contents?</param>
/// <param name="droppedEntities">a hashset containing all the entities that have been dropped/created</param>
/// <param name="randomSpreadMod">How much to multiply the random spread on dropped giblets(if we are dropping them!)</param>
/// <param name="playAudio">Should we play audio</param>
/// <param name="allowedContainers">A list of containerIds on the target that permit gibing</param>
/// <param name="excludedContainers">A list of containerIds on the target that DO NOT permit gibing</param>
/// <param name="launchCone">The cone we are launching giblets in (if we are launching them!)</param>
/// <param name="launchGibs">Should we launch giblets or just drop them</param>
/// <param name="launchDirection">The direction to launch giblets (if we are launching them!)</param>
/// <param name="launchImpulse">The impluse to launch giblets at(if we are launching them!)</param>
/// <param name="launchImpulseVariance">The variation in giblet launch impulse (if we are launching them!)</param>
/// <param name="logMissingGibable">Should we log if we are missing a gibbableComp when we call this function</param>
/// <returns>True if successful, false if not</returns>
public bool TryGibEntityWithRef(
Entity<TransformComponent?> outerEntity,
Entity<GibbableComponent?> gibbable,
GibType gibType,
GibContentsOption gibContentsOption,
ref HashSet<EntityUid> droppedEntities,
bool launchGibs = true,
Vector2? launchDirection = null,
float launchImpulse = 0f,
float launchImpulseVariance = 0f,
Angle launchCone = default,
float randomSpreadMod = 1.0f,
bool playAudio = true,
List<string>? allowedContainers = null,
List<string>? excludedContainers = null,
bool logMissingGibable = false)
{
if (!Resolve(gibbable, ref gibbable.Comp, logMissing: false))
{
DropEntity(gibbable, (outerEntity, 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;
}
HashSet<BaseContainer> 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<GibbableComponent?>(ent, null), outerEntity, randomSpreadMod,
ref droppedEntities, launchGibs,
launchDirection, launchImpulse, launchImpulseVariance, launchCone);
}
}
break;
}
case GibContentsOption.Gib:
{
foreach (var container in validContainers)
{
foreach (var ent in container.ContainedEntities)
{
GibEntity(new Entity<GibbableComponent?>(ent, null), outerEntity, randomSpreadMod,
ref droppedEntities, launchGibs,
launchDirection, launchImpulse, launchImpulseVariance, launchCone);
}
}
break;
}
}
switch (gibType)
{
case GibType.Skip:
break;
case GibType.Drop:
{
DropEntity(gibbable, outerEntity, randomSpreadMod, ref droppedEntities, launchGibs,
launchDirection, launchImpulse, launchImpulseVariance, launchCone);
break;
}
case GibType.Gib:
{
GibEntity(gibbable, outerEntity, randomSpreadMod, ref droppedEntities, launchGibs,
launchDirection, launchImpulse, launchImpulseVariance, launchCone);
break;
}
}
if (playAudio)
{
_audioSystem.PlayPredicted(gibbable.Comp.GibSound, outerEntity, null);
}
if (gibType == GibType.Gib)
PredictedQueueDel(gibbable.Owner);
return true;
}
private void DropEntity(Entity<GibbableComponent?> gibbable, Entity<TransformComponent?> parent, float randomSpreadMod,
ref HashSet<EntityUid> 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;
}
if (!Resolve(parent, ref parent.Comp, logMissing: false))
return;
var gibAttemptEvent = new AttemptEntityGibEvent(gibbable, gibCount, GibType.Drop);
RaiseLocalEvent(gibbable, ref gibAttemptEvent);
switch (gibAttemptEvent.GibType)
{
case GibType.Skip:
return;
case GibType.Gib:
GibEntity(gibbable, parent, randomSpreadMod, ref droppedEntities, flingEntity, scatterDirection,
scatterImpulse, scatterImpulseVariance, scatterCone, deleteTarget: false);
return;
}
_transformSystem.DropNextTo(gibbable.Owner, parent);
_transformSystem.SetWorldRotation(gibbable, _random.NextAngle());
droppedEntities.Add(gibbable);
if (flingEntity)
{
FlingDroppedEntity(gibbable, scatterDirection, scatterImpulse, scatterImpulseVariance, scatterCone);
}
var gibbedEvent = new EntityGibbedEvent(gibbable, new List<EntityUid> {gibbable});
RaiseLocalEvent(gibbable, ref gibbedEvent);
}
private List<EntityUid> GibEntity(Entity<GibbableComponent?> gibbable, Entity<TransformComponent?> parent,
float randomSpreadMod,
ref HashSet<EntityUid> droppedEntities, bool flingEntity, Vector2? scatterDirection, float scatterImpulse,
float scatterImpulseVariance, Angle scatterCone, bool deleteTarget = true)
{
var localGibs = new List<EntityUid>();
var gibCount = 0;
var gibProtoCount = 0;
if (Resolve(gibbable, ref gibbable.Comp, logMissing: false))
{
gibCount = gibbable.Comp.GibCount;
gibProtoCount = gibbable.Comp.GibPrototypes.Count;
}
if (!Resolve(parent, ref parent.Comp, logMissing: false))
return [];
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, parent, 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, parent.Comp.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, parent.Comp.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)
PredictedQueueDel(gibbable.Owner);
return localGibs;
}
public bool TryCreateRandomGiblet(Entity<GibbableComponent?> 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<GibbableComponent?> 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;
}
}

View File

@@ -1,11 +1,11 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Systems;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Examine;
using Content.Shared.Gibbing;
using Content.Shared.Hands;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
@@ -41,7 +41,7 @@ public sealed class SharedKitchenSpikeSystem : EntitySystem
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
@@ -317,7 +317,7 @@ public sealed class SharedKitchenSpikeSystem : EntitySystem
// Gib the victim if there is nothing else to butcher.
if (butcherable.SpawnedEntities.Count == 0)
{
_bodySystem.GibBody(args.Target.Value, true);
_gibbing.Gib(args.Target.Value);
var logSeverity = HasComp<HumanoidAppearanceComponent>(args.Target) ? LogImpact.Extreme : LogImpact.High;

View File

@@ -1,12 +1,11 @@
using System.Numerics;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Examine;
using Content.Shared.Gibbing;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
@@ -53,7 +52,7 @@ public abstract class SharedMagicSystem : EntitySystem
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedDoorSystem _door = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
@@ -390,11 +389,7 @@ public abstract class SharedMagicSystem : EntitySystem
var impulseVector = direction * 10000;
_physics.ApplyLinearImpulse(ev.Target, impulseVector);
if (!TryComp<BodyComponent>(ev.Target, out var body))
return;
_body.GibBody(ev.Target, true, body);
_gibbing.Gib(ev.Target);
}
// End Touch Spells

View File

@@ -4,6 +4,7 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Body.Events;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Gibbing;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
@@ -88,7 +89,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<BorgChassisComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<BorgChassisComponent, GibbedBeforeDeletionEvent>(OnBeingGibbed);
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
SubscribeLocalEvent<BorgChassisComponent, GetCharacterUnrevivableIcEvent>(OnGetUnrevivableIC);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
@@ -293,7 +294,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
SetActive(chassis, false, user: args.Origin);
}
private void OnBeingGibbed(Entity<BorgChassisComponent> chassis, ref BeingGibbedEvent args)
private void OnBeingGibbed(Entity<BorgChassisComponent> chassis, ref GibbedBeforeDeletionEvent args)
{
// Don't use the ItemSlotsSystem eject method since we don't want to play a sound and want we to eject the battery even if the slot is locked.
if (TryComp<PowerCellSlotComponent>(chassis, out var slotComp) &&

View File

@@ -1,6 +1,7 @@
using Content.Shared.Species.Components;
using Content.Shared.Actions;
using Content.Shared.Body.Systems;
using Content.Shared.Gibbing;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
@@ -12,7 +13,7 @@ namespace Content.Shared.Species;
public sealed partial class GibActionSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -26,7 +27,7 @@ public sealed partial class GibActionSystem : EntitySystem
private void OnMobStateChanged(EntityUid uid, GibActionComponent comp, MobStateChangedEvent args)
{
// When the mob changes state, check if they're dead and give them the action if so.
// When the mob changes state, check if they're dead and give them the action if so.
if (!TryComp<MobStateComponent>(uid, out var mobState))
return;
@@ -47,15 +48,15 @@ public sealed partial class GibActionSystem : EntitySystem
// If they aren't given the action, remove it.
_actionsSystem.RemoveAction(uid, comp.ActionEntity);
}
private void OnGibAction(EntityUid uid, GibActionComponent comp, GibActionEvent args)
{
// When they use the action, gib them.
_popupSystem.PopupClient(Loc.GetString(comp.PopupText, ("name", uid)), uid, uid);
_bodySystem.GibBody(uid, true);
_gibbing.Gib(uid, user: args.Performer);
}
public sealed partial class GibActionEvent : InstantActionEvent { }
public sealed partial class GibActionEvent : InstantActionEvent { }
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Body.Systems;
using Content.Shared.Gibbing;
using Content.Shared.Inventory;
using Content.Shared.Trigger.Components.Effects;
@@ -6,7 +6,7 @@ namespace Content.Shared.Trigger.Systems;
public sealed class GibOnTriggerSystem : XOnTriggerSystem<GibOnTriggerComponent>
{
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly GibbingSystem _gibbing = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
protected override void OnTrigger(Entity<GibOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
@@ -20,7 +20,7 @@ public sealed class GibOnTriggerSystem : XOnTriggerSystem<GibOnTriggerComponent>
}
}
_body.GibBody(target, true);
_gibbing.Gib(target, user: args.User);
args.Handled = true;
}
}

View File

@@ -1,5 +1,5 @@
- type: entity
id: BaseAnimalOrganUnGibbable
id: BaseAnimalOrgan
parent: BaseItem
abstract: true
components:
@@ -23,13 +23,6 @@
tags:
- Meat
- type: entity
id: BaseAnimalOrgan
parent: BaseAnimalOrganUnGibbable
abstract: true
components:
- type: Gibbable
- type: entity
id: OrganAnimalLungs
parent: BaseAnimalOrgan

View File

@@ -1,5 +1,5 @@
- type: entity
id: BaseHumanOrganUnGibbable
id: BaseHumanOrgan
parent: BaseItem
abstract: true
components:
@@ -27,16 +27,9 @@
tags:
- Meat
- type: entity
id: BaseHumanOrgan
parent: BaseHumanOrganUnGibbable
abstract: true
components:
- type: Gibbable
- type: entity
id: OrganHumanBrain
parent: BaseHumanOrganUnGibbable
parent: BaseHumanOrgan
name: brain
description: "The source of incredible, unending intelligence. Honk."
components:

View File

@@ -23,7 +23,6 @@
- type: Tag
tags:
- Trash
- type: Gibbable
- type: Extractable
juiceSolution:
reagents:

View File

@@ -9,7 +9,6 @@
- type: Damageable
damageContainer: Biological
- type: BodyPart
- type: Gibbable
- type: ContainerContainer
containers:
bodypart: !type:Container