mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
* DisposalHolder lifecycle calls * shorter, no goober comment * scammed by tests * scammed, yet again, by tests
359 lines
12 KiB
C#
359 lines
12 KiB
C#
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.Disposal.Components;
|
|
using Content.Shared.Disposal.Tube;
|
|
using Content.Shared.Disposal.Unit;
|
|
using Content.Shared.Explosion;
|
|
using Content.Shared.Eye;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Player;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Content.Shared.Disposal.Holder;
|
|
|
|
/// <summary>
|
|
/// This sytem handles the insertion, movement, and exiting of entities
|
|
/// through the disposals system.
|
|
/// </summary>
|
|
public abstract partial class SharedDisposalHolderSystem : EntitySystem
|
|
{
|
|
[Dependency] private INetManager _net = default!;
|
|
[Dependency] private DamageableSystem _damageable = default!;
|
|
[Dependency] private DisposalTubeSystem _disposalTube = default!;
|
|
[Dependency] private SharedAudioSystem _audio = default!;
|
|
[Dependency] private SharedContainerSystem _container = default!;
|
|
[Dependency] private SharedEyeSystem _eye = default!;
|
|
[Dependency] private SharedPhysicsSystem _physics = default!;
|
|
[Dependency] private SharedTransformSystem _xform = default!;
|
|
|
|
[Dependency] private EntityQuery<TransformComponent> _xformQuery;
|
|
|
|
/// <summary>
|
|
/// Allowed characters for tagging disposed entities.
|
|
/// </summary>
|
|
public static readonly Regex TagRegex = new("^[a-zA-Z0-9, ]*$", RegexOptions.Compiled);
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnComponentStartup(Entity<DisposalHolderComponent> ent, ref ComponentStartup args)
|
|
{
|
|
// Ensure the holder will have its container
|
|
ent.Comp.Container = _container.EnsureContainer<Container>(ent, nameof(DisposalHolderComponent));
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnComponentRemove(Entity<DisposalHolderComponent> ent, ref ComponentRemove args)
|
|
{
|
|
if (ent.Comp.Container is not { } container)
|
|
return;
|
|
|
|
// Inform the contained entities that they aren't in disposals anymore.
|
|
foreach (var contained in container.ContainedEntities)
|
|
DetachEntity(contained);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnExploded(Entity<DisposalHolderComponent> ent, ref BeforeExplodeEvent args)
|
|
{
|
|
if (ent.Comp.Container == null)
|
|
return;
|
|
|
|
args.Contents.AddRange(ent.Comp.Container.ContainedEntities);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnActorTransition(Entity<ActorComponent> ent, ref DisposalSystemTransitionEvent args)
|
|
{
|
|
// Refreshes visibility mask of a player, leading to OnGetVisibility being called
|
|
_eye.RefreshVisibilityMask(ent.Owner);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnGetVisibility(Entity<BeingDisposedComponent> entity, ref GetVisMaskEvent ev)
|
|
{
|
|
// Prevents mispredictions by allowing players in the disposal system
|
|
// to be sent any entities that are hidden under subfloors
|
|
if (HasComp<BeingDisposedComponent>(ev.Entity))
|
|
ev.VisibilityMask |= (int)VisibilityFlags.Subfloor;
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnDisposedRemovedFromContainer(Entity<BeingDisposedComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
|
{
|
|
if (args.Container.Owner == ent.Comp.Holder)
|
|
DetachEntity(ent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejects all entities inside a disposal holder from the disposals system.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
public virtual void Exit(Entity<DisposalHolderComponent> ent)
|
|
{
|
|
// Handled by the server
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to assigns a disposal holder to a new disposal tube, updating the trajectory of the holder.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
/// <param name="tube">The tube the holder is attempting to enter.</param>
|
|
/// <returns>True if the holder can enter the tube.</returns>
|
|
/// <remarks>
|
|
/// This function will call <see cref="Exit"> on any critical failure.
|
|
/// </remarks>
|
|
public bool TryEnterTube(Entity<DisposalHolderComponent> ent, Entity<DisposalTubeComponent?> tube)
|
|
{
|
|
if (!Resolve(tube, ref tube.Comp, false))
|
|
return false;
|
|
|
|
if (ent.Comp.IsExiting)
|
|
return false;
|
|
|
|
if (ent.Comp.CurrentTube == tube)
|
|
return false;
|
|
|
|
var ev = new GetDisposalsNextDirectionEvent(ent);
|
|
RaiseLocalEvent(tube, ref ev);
|
|
|
|
// If the next direction to move is invalid, exit immediately
|
|
if (ev.Next == Direction.Invalid)
|
|
{
|
|
Exit(ent);
|
|
return false;
|
|
}
|
|
|
|
var xform = Transform(ent);
|
|
|
|
// Attempt to damage entities when changing direction
|
|
if (ent.Comp.CurrentDirection != Direction.Invalid && ent.Comp.CurrentDirection != ev.Next)
|
|
{
|
|
// Apply damage
|
|
if (ent.Comp.Container != null && ent.Comp.AccumulatedDamage < ent.Comp.MaxAllowedDamage)
|
|
{
|
|
foreach (var held in ent.Comp.Container.ContainedEntities)
|
|
{
|
|
_damageable.ChangeDamage(held, ent.Comp.DamageOnTurn);
|
|
}
|
|
|
|
ent.Comp.AccumulatedDamage += ent.Comp.DamageOnTurn.GetTotal();
|
|
}
|
|
|
|
// Play clang sound
|
|
if (_net.IsServer)
|
|
{
|
|
_audio.PlayPvs(ent.Comp.ClangSound, xform.Coordinates);
|
|
}
|
|
|
|
// If the disposed entity re-entered a suspect pipe,
|
|
// it is likely caught in a loop and should try to escape
|
|
if (ent.Comp.SuspectPipes.Contains(tube))
|
|
{
|
|
ent.Comp.CanEscape = true;
|
|
}
|
|
|
|
// Calculate the change in the direction of travel, scaled so that
|
|
// -1 is a 90 degree left turn and 1 is a 90 degree right turn
|
|
var delta = 2 * Angle.ShortestDistance(ent.Comp.CurrentDirection.ToAngle(), ev.Next.ToAngle()).Theta / Math.PI;
|
|
ent.Comp.DirectionBias += (float)Math.Clamp(delta, -1, 1);
|
|
|
|
// If the updated travel direction bias exceeds the allowed threshold,
|
|
// the pipe is marked as suspect.
|
|
if (Math.Abs(ent.Comp.DirectionBias) >= ent.Comp.DirectionBiasThreshold)
|
|
{
|
|
ent.Comp.SuspectPipes.Add(tube);
|
|
ent.Comp.DirectionBias = 0;
|
|
}
|
|
|
|
Dirty(ent);
|
|
|
|
// Check if the holder can escape the current pipe
|
|
if (TryEscaping(ent, (tube, tube.Comp)))
|
|
return false;
|
|
}
|
|
|
|
// Update trajectory
|
|
ent.Comp.CurrentDirection = ev.Next;
|
|
ent.Comp.CurrentTube = tube;
|
|
ent.Comp.NextTube = _disposalTube.GetTubeInDirection((tube, tube.Comp), ent.Comp.CurrentDirection);
|
|
|
|
// Update rotation
|
|
_xform.SetLocalRotationNoLerp(ent, ent.Comp.CurrentDirection.ToAngle(), xform);
|
|
|
|
Dirty(ent);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links an entity with a disposal holder.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
/// <param name="uid">The entity being linked.</param>
|
|
public void AttachEntity(Entity<DisposalHolderComponent> ent, EntityUid uid)
|
|
{
|
|
var comp = EnsureComp<BeingDisposedComponent>(uid);
|
|
|
|
if (comp.Holder == ent.Owner)
|
|
return;
|
|
|
|
comp.Holder = ent;
|
|
Dirty(uid, comp);
|
|
|
|
var ev = new DisposalSystemTransitionEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks an entity from its disposal holder.
|
|
/// </summary>
|
|
/// <param name="uid">The entity being unlinked.</param>
|
|
public void DetachEntity(EntityUid uid)
|
|
{
|
|
RemComp<BeingDisposedComponent>(uid);
|
|
|
|
var ev = new DisposalSystemTransitionEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a tag to a disposal holder.
|
|
/// </summary>
|
|
/// <param name="ent">The diposal holder.</param>
|
|
/// <param name="tag">The tag.</param>
|
|
public void AddTag(Entity<DisposalHolderComponent> ent, string tag)
|
|
{
|
|
ent.Comp.Tags.Add(tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a tag from a disposal holder.
|
|
/// </summary>
|
|
/// <param name="ent">The diposal holder.</param>
|
|
/// <param name="tag">The tag.</param>
|
|
public void RemoveTag(Entity<DisposalHolderComponent> ent, string tag)
|
|
{
|
|
ent.Comp.Tags.Remove(tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the tags on a disposal holder has any overlap with specified list of tags.
|
|
/// </summary>
|
|
/// <param name="ent">The diposal holder.</param>
|
|
/// <param name="tag">The list of tags.</param>
|
|
/// <returns>True if the disposal holder has one of the listed tags.</returns>
|
|
public bool TagsOverlap(Entity<DisposalHolderComponent> ent, HashSet<string> tags)
|
|
{
|
|
return ent.Comp.Tags.Overlaps(tags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if specified tag is valid for disposal holders.
|
|
/// </summary>
|
|
/// <param name="tag">The tag.</param>
|
|
/// <returns>True if the tag is valid.</returns>
|
|
public bool TagIsValid(string tag)
|
|
{
|
|
return TagRegex.IsMatch(tag);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
var query = EntityQueryEnumerator<DisposalHolderComponent, TransformComponent>();
|
|
while (query.MoveNext(out var uid, out var holder, out var xform))
|
|
{
|
|
// Remove any disposal holders that were somehow emptied
|
|
if (holder.Container?.Count == 0)
|
|
{
|
|
PredictedQueueDel(uid);
|
|
continue;
|
|
}
|
|
|
|
UpdateDisposalHolder((uid, holder));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs an update on the trajectory of a disposal holder.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
private void UpdateDisposalHolder(Entity<DisposalHolderComponent> ent)
|
|
{
|
|
var currentTube = ent.Comp.CurrentTube;
|
|
var nextTube = ent.Comp.NextTube;
|
|
|
|
if (!Exists(currentTube) || !Exists(nextTube))
|
|
{
|
|
Exit(ent);
|
|
return;
|
|
}
|
|
|
|
var gridUid = _xformQuery.GetComponent(currentTube.Value).GridUid;
|
|
|
|
if (gridUid == null)
|
|
return;
|
|
|
|
// Apply a linear velocity to a disposal holder which
|
|
// will direct it toward the next tube on its route
|
|
var origin = _xform.GetWorldPosition(_xformQuery.GetComponent(currentTube.Value));
|
|
var destination = _xform.GetWorldPosition(_xformQuery.GetComponent(nextTube.Value));
|
|
var entCoords = _xform.GetWorldPosition(_xformQuery.GetComponent(ent));
|
|
|
|
// How far off are we from our destination?
|
|
var entDestDiff = destination - entCoords;
|
|
|
|
// If we're really close, don't bother updating our velocity
|
|
if (entDestDiff.Length() > 1e-3)
|
|
{
|
|
// Set velocity
|
|
var velocity = entDestDiff.Normalized() * ent.Comp.TraversalSpeed;
|
|
_physics.SetLinearVelocity(ent, velocity);
|
|
|
|
// Determine whether the disposal holder should update its route,
|
|
// based on its current position with respect to its target and origin
|
|
var originDestDiff = destination - origin;
|
|
var originEntDiff = entCoords - origin;
|
|
|
|
if (originEntDiff.Length() < originDestDiff.Length())
|
|
return;
|
|
}
|
|
|
|
// Attempt to enter the next tube
|
|
if (TryEnterTube(ent, nextTube.Value))
|
|
{
|
|
UpdateDisposalHolder(ent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Expels the atmos of a disposal holder back into its surrounding environment.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
protected virtual void ExpelAtmos(Entity<DisposalHolderComponent> ent)
|
|
{
|
|
// Handled by the server
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transfer the atmos of a disposal unit into the disposal holder it is launching.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
/// <param name="unit">The disposal unit.</param>
|
|
public virtual void TransferAtmos(Entity<DisposalHolderComponent> ent, Entity<DisposalUnitComponent> unit)
|
|
{
|
|
// Handled by the server
|
|
}
|
|
|
|
/// <summary>
|
|
/// The disposal tube holder attempts to escape the disposals system.
|
|
/// </summary>
|
|
/// <param name="ent">The disposal holder.</param>
|
|
/// <param name="tube">The disposal tube the holder is attempting to escape.</param>
|
|
/// <returns> True if the disposal holder escaped the disposal tube.</returns>
|
|
protected virtual bool TryEscaping(Entity<DisposalHolderComponent> ent, Entity<DisposalTubeComponent> tube)
|
|
{
|
|
// Handled by the server
|
|
|
|
return false;
|
|
}
|
|
}
|