| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- 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(EntityUid 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(
- EntityUid 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, 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<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), parentXform, 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), 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<GibbableComponent?> gibbable, TransformComponent parentXform, 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;
- }
- 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<EntityUid> {gibbable});
- RaiseLocalEvent(gibbable, ref gibbedEvent);
- }
- private List<EntityUid> GibEntity(Entity<GibbableComponent?> gibbable, TransformComponent parentXform,
- 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;
- }
- 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<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;
- }
- }
|