Pulling rework (#20906)
* Pulling rework Fixing up the FOUR systems managing pulling, all the shitcode, and also making it nicer ingame. * More pulling cleanup * stats * More cleanup * First draft * More pulling * weh * Fix puller * Pulling working * Fix merge * Dunked * Self-merge time
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
|
||||
namespace Content.Shared.Pulling.Systems
|
||||
{
|
||||
public sealed class SharedPullableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPullingSystem _pullSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedPullableComponent, MoveInputEvent>(OnRelayMoveInput);
|
||||
}
|
||||
|
||||
private void OnRelayMoveInput(EntityUid uid, SharedPullableComponent component, ref MoveInputEvent args)
|
||||
{
|
||||
var entity = args.Entity;
|
||||
if (_mobState.IsIncapacitated(entity) || !_blocker.CanMove(entity)) return;
|
||||
|
||||
_pullSystem.TryStopPull(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Pulling.Systems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class SharedPullerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPullingStateManagementSystem _why = default!;
|
||||
[Dependency] private readonly SharedPullingSystem _pullSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedPullerComponent, PullStartedMessage>(PullerHandlePullStarted);
|
||||
SubscribeLocalEvent<SharedPullerComponent, PullStoppedMessage>(PullerHandlePullStopped);
|
||||
SubscribeLocalEvent<SharedPullerComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
|
||||
SubscribeLocalEvent<SharedPullerComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<SharedPullerComponent, ComponentShutdown>(OnPullerShutdown);
|
||||
}
|
||||
|
||||
private void OnPullerShutdown(EntityUid uid, SharedPullerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_why.ForceDisconnectPuller(component);
|
||||
}
|
||||
|
||||
private void OnVirtualItemDeleted(EntityUid uid, SharedPullerComponent component, VirtualItemDeletedEvent args)
|
||||
{
|
||||
if (component.Pulling == null)
|
||||
return;
|
||||
|
||||
if (component.Pulling == args.BlockingEntity)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<SharedPullableComponent>(args.BlockingEntity, out var comp))
|
||||
{
|
||||
_pullSystem.TryStopPull(comp, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PullerHandlePullStarted(
|
||||
EntityUid uid,
|
||||
SharedPullerComponent component,
|
||||
PullStartedMessage args)
|
||||
{
|
||||
if (args.Puller.Owner != uid)
|
||||
return;
|
||||
|
||||
_alertsSystem.ShowAlert(component.Owner, AlertType.Pulling);
|
||||
|
||||
RefreshMovementSpeed(component);
|
||||
}
|
||||
|
||||
private void PullerHandlePullStopped(
|
||||
EntityUid uid,
|
||||
SharedPullerComponent component,
|
||||
PullStoppedMessage args)
|
||||
{
|
||||
if (args.Puller.Owner != uid)
|
||||
return;
|
||||
|
||||
var euid = component.Owner;
|
||||
if (_alertsSystem.IsShowingAlert(euid, AlertType.Pulling))
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(euid):user} stopped pulling {ToPrettyString(args.Pulled.Owner):target}");
|
||||
_alertsSystem.ClearAlert(euid, AlertType.Pulling);
|
||||
|
||||
RefreshMovementSpeed(component);
|
||||
}
|
||||
|
||||
private void OnRefreshMovespeed(EntityUid uid, SharedPullerComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier);
|
||||
}
|
||||
|
||||
private void RefreshMovementSpeed(SharedPullerComponent component)
|
||||
{
|
||||
_movementSpeedModifierSystem.RefreshMovementSpeedModifiers(component.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Pulling
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the core of pulling state management.
|
||||
/// Because pulling state is such a mess to get right, all writes to pulling state must go through this class.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class SharedPullingStateManagementSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedPullableComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SharedPullableComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.Puller != null)
|
||||
ForceRelationship(null, component);
|
||||
}
|
||||
|
||||
// A WARNING:
|
||||
// The following 2 functions are the most internal part of the pulling system's relationship management.
|
||||
// They do not expect to be cancellable.
|
||||
private void ForceDisconnect(SharedPullerComponent puller, SharedPullableComponent pullable)
|
||||
{
|
||||
var pullerPhysics = EntityManager.GetComponent<PhysicsComponent>(puller.Owner);
|
||||
var pullablePhysics = EntityManager.GetComponent<PhysicsComponent>(pullable.Owner);
|
||||
|
||||
// MovingTo shutdown
|
||||
ForceSetMovingTo(pullable, null);
|
||||
|
||||
// Joint shutdown
|
||||
if (!_timing.ApplyingState && // During state-handling, joint component will handle its own state.
|
||||
pullable.PullJointId != null &&
|
||||
TryComp(puller.Owner, out JointComponent? jointComp))
|
||||
{
|
||||
if (jointComp.GetJoints.TryGetValue(pullable.PullJointId, out var j))
|
||||
_jointSystem.RemoveJoint(j);
|
||||
}
|
||||
pullable.PullJointId = null;
|
||||
|
||||
// State shutdown
|
||||
puller.Pulling = null;
|
||||
pullable.Puller = null;
|
||||
|
||||
// Messaging
|
||||
var message = new PullStoppedMessage(pullerPhysics, pullablePhysics);
|
||||
|
||||
RaiseLocalEvent(puller.Owner, message, broadcast: false);
|
||||
|
||||
if (Initialized(pullable.Owner))
|
||||
RaiseLocalEvent(pullable.Owner, message, true);
|
||||
|
||||
// Networking
|
||||
Dirty(puller);
|
||||
Dirty(pullable);
|
||||
}
|
||||
|
||||
public void ForceRelationship(SharedPullerComponent? puller, SharedPullableComponent? pullable)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
;
|
||||
if (pullable != null && puller != null && (puller.Pulling == pullable.Owner))
|
||||
{
|
||||
// Already done
|
||||
return;
|
||||
}
|
||||
|
||||
// Start by disconnecting the pullable from whatever it is currently connected to.
|
||||
var pullableOldPullerE = pullable?.Puller;
|
||||
if (pullableOldPullerE != null)
|
||||
{
|
||||
ForceDisconnect(EntityManager.GetComponent<SharedPullerComponent>(pullableOldPullerE.Value), pullable!);
|
||||
}
|
||||
|
||||
// Continue with the puller.
|
||||
var pullerOldPullableE = puller?.Pulling;
|
||||
if (pullerOldPullableE != null)
|
||||
{
|
||||
ForceDisconnect(puller!, EntityManager.GetComponent<SharedPullableComponent>(pullerOldPullableE.Value));
|
||||
}
|
||||
|
||||
// And now for the actual connection (if any).
|
||||
|
||||
if (puller != null && pullable != null)
|
||||
{
|
||||
var pullerPhysics = EntityManager.GetComponent<PhysicsComponent>(puller.Owner);
|
||||
var pullablePhysics = EntityManager.GetComponent<PhysicsComponent>(pullable.Owner);
|
||||
pullable.PullJointId = $"pull-joint-{pullable.Owner}";
|
||||
|
||||
// State startup
|
||||
puller.Pulling = pullable.Owner;
|
||||
pullable.Puller = puller.Owner;
|
||||
|
||||
// joint state handling will manage its own state
|
||||
if (!_timing.ApplyingState)
|
||||
{
|
||||
// Joint startup
|
||||
var union = _physics.GetHardAABB(puller.Owner).Union(_physics.GetHardAABB(pullable.Owner, body: pullablePhysics));
|
||||
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
|
||||
|
||||
var joint = _jointSystem.CreateDistanceJoint(pullablePhysics.Owner, pullerPhysics.Owner, id: pullable.PullJointId);
|
||||
joint.CollideConnected = false;
|
||||
// This maximum has to be there because if the object is constrained too closely, the clamping goes backwards and asserts.
|
||||
joint.MaxLength = Math.Max(1.0f, length);
|
||||
joint.Length = length * 0.75f;
|
||||
joint.MinLength = 0f;
|
||||
joint.Stiffness = 1f;
|
||||
}
|
||||
|
||||
// Messaging
|
||||
var message = new PullStartedMessage(pullerPhysics, pullablePhysics);
|
||||
|
||||
RaiseLocalEvent(puller.Owner, message, broadcast: false);
|
||||
RaiseLocalEvent(pullable.Owner, message, true);
|
||||
|
||||
// Networking
|
||||
Dirty(puller);
|
||||
Dirty(pullable);
|
||||
}
|
||||
}
|
||||
|
||||
// For OnRemove use only.
|
||||
public void ForceDisconnectPuller(SharedPullerComponent puller)
|
||||
{
|
||||
// DO NOT ADD ADDITIONAL LOGIC IN THIS FUNCTION. Do it in ForceRelationship.
|
||||
ForceRelationship(puller, null);
|
||||
}
|
||||
|
||||
// For OnRemove use only.
|
||||
public void ForceDisconnectPullable(SharedPullableComponent pullable)
|
||||
{
|
||||
// DO NOT ADD ADDITIONAL LOGIC IN THIS FUNCTION. Do it in ForceRelationship.
|
||||
ForceRelationship(null, pullable);
|
||||
}
|
||||
|
||||
public void ForceSetMovingTo(SharedPullableComponent pullable, EntityCoordinates? movingTo)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (pullable.MovingTo == movingTo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow setting a MovingTo if there's no puller.
|
||||
// The other half of this guarantee (shutting down a MovingTo if the puller goes away) is enforced in ForceRelationship.
|
||||
if (pullable.Puller == null && movingTo != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pullable.MovingTo = movingTo;
|
||||
Dirty(pullable);
|
||||
|
||||
if (movingTo == null)
|
||||
{
|
||||
RaiseLocalEvent(pullable.Owner, new PullableStopMovingMessage(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseLocalEvent(pullable.Owner, new PullableMoveMessage(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes if the entity needs a hand in order to be able to pull objects.
|
||||
/// </summary>
|
||||
public void ChangeHandRequirement(EntityUid uid, bool needsHands, SharedPullerComponent? comp)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
comp.NeedsHands = needsHands;
|
||||
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Pulling
|
||||
{
|
||||
public abstract partial class SharedPullingSystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public bool CanPull(EntityUid puller, EntityUid pulled)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<SharedPullerComponent>(puller, out var comp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (comp.NeedsHands && !_handsSystem.TryGetEmptyHand(puller, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_blocker.CanInteract(puller, pulled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(pulled, out var physics))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (physics.BodyType == BodyType.Static)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (puller == pulled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(_containerSystem.IsEntityInContainer(puller) || _containerSystem.IsEntityInContainer(pulled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent(puller, out BuckleComponent? buckle))
|
||||
{
|
||||
// Prevent people pulling the chair they're on, etc.
|
||||
if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pulled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var getPulled = new BeingPulledAttemptEvent(puller, pulled);
|
||||
RaiseLocalEvent(pulled, getPulled, true);
|
||||
var startPull = new StartPullAttemptEvent(puller, pulled);
|
||||
RaiseLocalEvent(puller, startPull, true);
|
||||
return (!startPull.Cancelled && !getPulled.Cancelled);
|
||||
}
|
||||
|
||||
public bool TogglePull(EntityUid puller, SharedPullableComponent pullable)
|
||||
{
|
||||
if (pullable.Puller == puller)
|
||||
{
|
||||
return TryStopPull(pullable);
|
||||
}
|
||||
return TryStartPull(puller, pullable.Owner);
|
||||
}
|
||||
|
||||
// -- Core attempted actions --
|
||||
|
||||
public bool TryStopPull(SharedPullableComponent pullable, EntityUid? user = null)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return false;
|
||||
|
||||
if (!pullable.BeingPulled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var msg = new StopPullingEvent(user);
|
||||
RaiseLocalEvent(pullable.Owner, msg, true);
|
||||
|
||||
if (msg.Cancelled) return false;
|
||||
|
||||
// Stop pulling confirmed!
|
||||
|
||||
if (TryComp<PhysicsComponent>(pullable.Owner, out var pullablePhysics))
|
||||
{
|
||||
_physics.SetFixedRotation(pullable.Owner, pullable.PrevFixedRotation, body: pullablePhysics);
|
||||
}
|
||||
|
||||
_pullSm.ForceRelationship(null, pullable);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryStartPull(EntityUid puller, EntityUid pullable)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(puller, out SharedPullerComponent? pullerComp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!EntityManager.TryGetComponent(pullable, out SharedPullableComponent? pullableComp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return TryStartPull(pullerComp, pullableComp);
|
||||
}
|
||||
|
||||
// The main "start pulling" function.
|
||||
public bool TryStartPull(SharedPullerComponent puller, SharedPullableComponent pullable)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return false;
|
||||
|
||||
if (puller.Pulling == pullable.Owner)
|
||||
return true;
|
||||
|
||||
// Pulling a new object : Perform sanity checks.
|
||||
|
||||
if (!CanPull(puller.Owner, pullable.Owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(puller.Owner, out var pullerPhysics))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(pullable.Owner, out var pullablePhysics))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the puller is not currently pulling anything.
|
||||
// If this isn't done, then it happens too late, and the start/stop messages go out of order,
|
||||
// and next thing you know it thinks it's not pulling anything even though it is!
|
||||
|
||||
var oldPullable = puller.Pulling;
|
||||
if (oldPullable != null)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(oldPullable.Value, out SharedPullableComponent? oldPullableComp))
|
||||
{
|
||||
if (!TryStopPull(oldPullableComp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("Well now you've done it, haven't you? Someone transferred pulling (onto {0}) while presently pulling something that has no Pullable component (on {1})!", pullable.Owner, oldPullable);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the pullable is not currently being pulled.
|
||||
// Same sort of reasons as before.
|
||||
|
||||
var oldPuller = pullable.Puller;
|
||||
if (oldPuller != null)
|
||||
{
|
||||
if (!TryStopPull(pullable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with pulling process.
|
||||
|
||||
var pullAttempt = new PullAttemptEvent(pullerPhysics, pullablePhysics);
|
||||
|
||||
RaiseLocalEvent(puller.Owner, pullAttempt, broadcast: false);
|
||||
|
||||
if (pullAttempt.Cancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(pullable.Owner, pullAttempt, true);
|
||||
|
||||
if (pullAttempt.Cancelled)
|
||||
return false;
|
||||
|
||||
_interaction.DoContactInteraction(pullable.Owner, puller.Owner);
|
||||
|
||||
_pullSm.ForceRelationship(puller, pullable);
|
||||
pullable.PrevFixedRotation = pullablePhysics.FixedRotation;
|
||||
_physics.SetFixedRotation(pullable.Owner, pullable.FixedRotationOnPull, body: pullablePhysics);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(puller.Owner):user} started pulling {ToPrettyString(pullable.Owner):target}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryMoveTo(SharedPullableComponent pullable, EntityCoordinates to)
|
||||
{
|
||||
if (pullable.Puller == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.HasComponent<PhysicsComponent>(pullable.Owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_pullSm.ForceSetMovingTo(pullable, to);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void StopMoveTo(SharedPullableComponent pullable)
|
||||
{
|
||||
_pullSm.ForceSetMovingTo(pullable, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.Pulling
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public abstract partial class SharedPullingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPullingStateManagementSystem _pullSm = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A mapping of pullers to the entity that they are pulling.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, EntityUid> _pullers =
|
||||
new();
|
||||
|
||||
private readonly HashSet<SharedPullableComponent> _moving = new();
|
||||
private readonly HashSet<SharedPullableComponent> _stoppedMoving = new();
|
||||
|
||||
public IReadOnlySet<SharedPullableComponent> Moving => _moving;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<PullStartedMessage>(OnPullStarted);
|
||||
SubscribeLocalEvent<PullStoppedMessage>(OnPullStopped);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert);
|
||||
SubscribeLocalEvent<SharedPullableComponent, JointRemovedEvent>(OnJointRemoved);
|
||||
SubscribeLocalEvent<SharedPullableComponent, CollisionChangeEvent>(OnPullableCollisionChange);
|
||||
|
||||
SubscribeLocalEvent<SharedPullableComponent, PullStartedMessage>(PullableHandlePullStarted);
|
||||
SubscribeLocalEvent<SharedPullableComponent, PullStoppedMessage>(PullableHandlePullStopped);
|
||||
|
||||
SubscribeLocalEvent<SharedPullableComponent, GetVerbsEvent<Verb>>(AddPullVerbs);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject))
|
||||
.Register<SharedPullingSystem>();
|
||||
}
|
||||
|
||||
private void OnPullableCollisionChange(EntityUid uid, SharedPullableComponent component, ref CollisionChangeEvent args)
|
||||
{
|
||||
if (component.PullJointId != null && !args.CanCollide)
|
||||
{
|
||||
_joints.RemoveJoint(uid, component.PullJointId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJointRemoved(EntityUid uid, SharedPullableComponent component, JointRemovedEvent args)
|
||||
{
|
||||
if (component.Puller != args.OtherEntity)
|
||||
return;
|
||||
|
||||
// Do we have some other join with our Puller?
|
||||
// or alternatively:
|
||||
// TODO track the relevant joint.
|
||||
|
||||
if (TryComp(uid, out JointComponent? joints))
|
||||
{
|
||||
foreach (var jt in joints.GetJoints.Values)
|
||||
{
|
||||
if (jt.BodyAUid == component.Puller || jt.BodyBUid == component.Puller)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No more joints with puller -> force stop pull.
|
||||
_pullSm.ForceDisconnectPullable(component);
|
||||
}
|
||||
|
||||
private void AddPullVerbs(EntityUid uid, SharedPullableComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
// Are they trying to pull themselves up by their bootstraps?
|
||||
if (args.User == args.Target)
|
||||
return;
|
||||
|
||||
//TODO VERB ICONS add pulling icon
|
||||
if (component.Puller == args.User)
|
||||
{
|
||||
Verb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling"),
|
||||
Act = () => TryStopPull(component, args.User),
|
||||
DoContactInteraction = false // pulling handle its own contact interaction.
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
else if (CanPull(args.User, args.Target))
|
||||
{
|
||||
Verb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("pulling-verb-get-data-text"),
|
||||
Act = () => TryStartPull(args.User, args.Target),
|
||||
DoContactInteraction = false // pulling handle its own contact interaction.
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
// Raise a "you are being pulled" alert if the pulled entity has alerts.
|
||||
private void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args)
|
||||
{
|
||||
if (args.Pulled.Owner != uid)
|
||||
return;
|
||||
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Pulled);
|
||||
}
|
||||
|
||||
private void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args)
|
||||
{
|
||||
if (args.Pulled.Owner != uid)
|
||||
return;
|
||||
|
||||
_alertsSystem.ClearAlert(uid, AlertType.Pulled);
|
||||
}
|
||||
|
||||
public bool IsPulled(EntityUid uid, SharedPullableComponent? component = null)
|
||||
{
|
||||
return Resolve(uid, ref component, false) && component.BeingPulled;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_moving.ExceptWith(_stoppedMoving);
|
||||
_stoppedMoving.Clear();
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_pullers.Clear();
|
||||
_moving.Clear();
|
||||
_stoppedMoving.Clear();
|
||||
}
|
||||
|
||||
private void OnPullStarted(PullStartedMessage message)
|
||||
{
|
||||
SetPuller(message.Puller.Owner, message.Pulled.Owner);
|
||||
}
|
||||
|
||||
private void OnPullStopped(PullStoppedMessage message)
|
||||
{
|
||||
RemovePuller(message.Puller.Owner);
|
||||
}
|
||||
|
||||
protected void OnPullableMove(EntityUid uid, SharedPullableComponent component, PullableMoveMessage args)
|
||||
{
|
||||
_moving.Add(component);
|
||||
}
|
||||
|
||||
protected void OnPullableStopMove(EntityUid uid, SharedPullableComponent component, PullableStopMovingMessage args)
|
||||
{
|
||||
_stoppedMoving.Add(component);
|
||||
}
|
||||
|
||||
// TODO: When Joint networking is less shitcodey fix this to use a dedicated joints message.
|
||||
private void HandleContainerInsert(EntInsertedIntoContainerMessage message)
|
||||
{
|
||||
if (TryComp(message.Entity, out SharedPullableComponent? pullable))
|
||||
{
|
||||
TryStopPull(pullable);
|
||||
}
|
||||
|
||||
if (TryComp(message.Entity, out SharedPullerComponent? puller))
|
||||
{
|
||||
if (puller.Pulling == null) return;
|
||||
|
||||
if (!TryComp(puller.Pulling.Value, out SharedPullableComponent? pulling))
|
||||
return;
|
||||
|
||||
TryStopPull(pulling);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (session?.AttachedEntity is not { } player ||
|
||||
!player.IsValid())
|
||||
return false;
|
||||
|
||||
if (!TryGetPulled(player, out var pulled))
|
||||
return false;
|
||||
|
||||
if (!TryComp(pulled.Value, out SharedPullableComponent? pullable))
|
||||
return false;
|
||||
|
||||
if (_containerSystem.IsEntityInContainer(player))
|
||||
return false;
|
||||
|
||||
TryMoveTo(pullable, coords);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetPuller(EntityUid puller, EntityUid pulled)
|
||||
{
|
||||
_pullers[puller] = pulled;
|
||||
}
|
||||
|
||||
private bool RemovePuller(EntityUid puller)
|
||||
{
|
||||
return _pullers.Remove(puller);
|
||||
}
|
||||
|
||||
public EntityUid GetPulled(EntityUid by)
|
||||
{
|
||||
return _pullers.GetValueOrDefault(by);
|
||||
}
|
||||
|
||||
public bool TryGetPulled(EntityUid by, [NotNullWhen(true)] out EntityUid? pulled)
|
||||
{
|
||||
return (pulled = GetPulled(by)) != null;
|
||||
}
|
||||
|
||||
public bool IsPulling(EntityUid puller)
|
||||
{
|
||||
return _pullers.ContainsKey(puller);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user