Pullability partial ECS refactor, monkey-gibbing error fix (#4695)

* Migrate lots of pulling logic out of the components and clean it up

* It's buggy, but move more code out of the pullable component

* Pulling system now throws less errors than it did before the refactor

* Finally finish the big parts of refactoring pullability

* Pulling: Handle disconnect properly if the pull joint has been removed by physics shutdown

* Port 2b68449328 to this branch

* Changes as per PR reviews

* Port e801ffac45 (SharedJointSystem) and fix issues encountered during testing

SharedJointSystem.

Also, MinLength and Stiffness appear to have suddenly gained enough meaning that pulling is broken unless they're set.
This commit is contained in:
20kdc
2021-10-04 16:10:54 +01:00
committed by GitHub
parent dacbfffe7c
commit 2a8486a384
16 changed files with 483 additions and 360 deletions

View File

@@ -1,6 +1,8 @@
using Content.Shared.Alert;
using Content.Shared.Pulling.Components;
using Content.Shared.Pulling;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Alert.Click
@@ -14,7 +16,11 @@ namespace Content.Server.Alert.Click
{
public void AlertClicked(ClickAlertEventArgs args)
{
args.Player.GetComponentOrNull<SharedPullableComponent>()?.TryStopPull(args.Player);
var ps = EntitySystem.Get<SharedPullingSystem>();
if (args.Player.TryGetComponent<SharedPullableComponent>(out var playerPullable))
{
ps.TryStopPull(playerPullable);
}
}
}
}

View File

@@ -16,11 +16,13 @@ namespace Content.Server.Alert.Click
{
public void AlertClicked(ClickAlertEventArgs args)
{
EntitySystem
.Get<SharedPullingSystem>()
.GetPulled(args.Player)?
.GetComponentOrNull<SharedPullableComponent>()?
.TryStopPull();
var ps = EntitySystem.Get<SharedPullingSystem>();
var playerTargetPullable = ps.GetPulled(args.Player)?
.GetComponentOrNull<SharedPullableComponent>();
if (playerTargetPullable != null)
{
ps.TryStopPull(playerTargetPullable);
}
}
}
}

View File

@@ -268,7 +268,7 @@ namespace Content.Server.Buckle.Components
{
if (ownerPullable.Puller != null)
{
ownerPullable.TryStopPull();
EntitySystem.Get<PullingSystem>().TryStopPull(ownerPullable);
}
}
@@ -277,7 +277,7 @@ namespace Content.Server.Buckle.Components
if (toPullable.Puller == Owner)
{
// can't pull it and buckle to it at the same time
toPullable.TryStopPull();
EntitySystem.Get<PullingSystem>().TryStopPull(toPullable);
}
}

View File

@@ -79,7 +79,7 @@ namespace Content.Server.Construction.Components
{
if (pullableComponent.Puller != null)
{
pullableComponent.TryStopPull();
EntitySystem.Get<PullingSystem>().TryStopPull(pullableComponent);
}
}

View File

@@ -166,7 +166,7 @@ namespace Content.Server.Hands.Components
|| puller.Pulling == null || !puller.Pulling.TryGetComponent(out PullableComponent? pullable))
return false;
return pullable.TryStopPull();
return _entitySystemManager.GetEntitySystem<PullingSystem>().TryStopPull(pullable);
}
#endregion

View File

@@ -47,6 +47,7 @@ namespace Content.Server.Interaction
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
public override void Initialize()
{
@@ -313,7 +314,7 @@ namespace Content.Server.Interaction
if (!pulledObject.TryGetComponent(out PullableComponent? pull))
return false;
return pull.TogglePull(userEntity);
return _pullSystem.TogglePull(userEntity, pull);
}
/// <summary>

View File

@@ -51,6 +51,10 @@ namespace Content.Server.Physics.Controllers
foreach (var pullable in _pullableSystem.Moving)
{
// There's a 1-frame delay between stopping moving something and it leaving the Moving set.
// This can include if leaving the Moving set due to not being pulled anymore,
// or due to being deleted.
if (pullable.Deleted)
{
continue;
@@ -61,12 +65,17 @@ namespace Content.Server.Physics.Controllers
continue;
}
DebugTools.AssertNotNull(pullable.Puller);
if (pullable.Puller == null)
{
continue;
}
// Now that's over with...
var pullerPosition = pullable.Puller!.Transform.MapPosition;
if (pullable.MovingTo.Value.MapId != pullerPosition.MapId)
{
pullable.MovingTo = null;
_pullableSystem.StopMoveTo(pullable);
continue;
}
@@ -74,22 +83,23 @@ namespace Content.Server.Physics.Controllers
physics.BodyType == BodyType.Static ||
pullable.MovingTo.Value.MapId != pullable.Owner.Transform.MapID)
{
pullable.MovingTo = null;
_pullableSystem.StopMoveTo(pullable);
continue;
}
var movingPosition = pullable.MovingTo.Value.Position;
var ownerPosition = pullable.Owner.Transform.MapPosition.Position;
if (movingPosition.EqualsApprox(ownerPosition, MaximumSettleDistance) && (physics.LinearVelocity.Length < MaximumSettleVelocity))
var diff = movingPosition - ownerPosition;
var diffLength = diff.Length;
if ((diffLength < MaximumSettleDistance) && (physics.LinearVelocity.Length < MaximumSettleVelocity))
{
physics.LinearVelocity = Vector2.Zero;
pullable.MovingTo = null;
_pullableSystem.StopMoveTo(pullable);
continue;
}
var diff = movingPosition - ownerPosition;
var diffLength = diff.Length;
var impulseModifierLerp = Math.Min(1.0f, Math.Max(0.0f, (physics.Mass - AccelModifierLowMass) / (AccelModifierHighMass - AccelModifierLowMass)));
var impulseModifier = MathHelper.Lerp(AccelModifierLow, AccelModifierHigh, impulseModifierLerp);
var multiplier = diffLength < 1 ? impulseModifier * diffLength : impulseModifier;
@@ -102,6 +112,7 @@ namespace Content.Server.Physics.Controllers
var scaling = (SettleShutdownDistance - diffLength) / SettleShutdownDistance;
accel -= physics.LinearVelocity * SettleShutdownMultiplier * scaling;
}
physics.WakeBody();
physics.ApplyLinearImpulse(accel * physics.Mass * frameTime);
}
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Pulling.Components;
using Content.Shared.Pulling;
using Content.Shared.Verbs;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
@@ -50,11 +51,11 @@ namespace Content.Server.Pulling
// Why no reason? Because they're supposed to be performed in TryStartPull.
if (component.Puller == user)
{
component.TryStopPull();
EntitySystem.Get<SharedPullingSystem>().TryStopPull(component);
}
else
{
component.TryStartPull(user);
EntitySystem.Get<SharedPullingSystem>().TryStartPull(component.Owner, user);
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Content.Server.Pulling
return;
}
pullable.TryStopPull();
TryStopPull(pullable);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Content.Shared.Physics.Pull;
using Robust.Shared.Analyzers;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -12,311 +13,32 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Pulling.Components
{
// Before you try to add another type than SharedPullingStateManagementSystem, consider the can of worms you may be opening!
[NetworkedComponent()]
[Friend(typeof(SharedPullingStateManagementSystem))]
public abstract class SharedPullableComponent : Component
{
public override string Name => "Pullable";
// At this point this field exists solely for the component dependency (which is mandatory).
[ComponentDependency] private readonly PhysicsComponent? _physics = default!;
/// <summary>
/// Only set in Puller->set! Only set in unison with _pullerPhysics!
/// </summary>
private IEntity? _puller;
public IPhysBody? PullerPhysics { get; private set; }
private DistanceJoint? _pullJoint;
public float? MaxDistance => _pullJoint?.MaxLength;
private MapCoordinates? _movingTo;
public float? MaxDistance => PullJoint?.MaxLength;
/// <summary>
/// The current entity pulling this component.
/// Setting this performs the entire setup process for pulling.
/// Ideally, alter using TryStartPull and TryStopPull.
/// </summary>
public virtual IEntity? Puller
{
get => _puller;
set
{
if (_puller == value)
{
return;
}
var eventBus = Owner.EntityManager.EventBus;
// TODO: JESUS
// New value. Abandon being pulled by any existing object.
if (_puller != null)
{
var oldPuller = _puller;
var oldPullerPhysics = PullerPhysics;
if (_puller.TryGetComponent(out SharedPullerComponent? puller))
{
puller.Pulling = null;
}
_puller = null;
Dirty();
PullerPhysics = null;
if (_physics != null && oldPullerPhysics != null)
{
var message = new PullStoppedMessage(oldPullerPhysics, _physics);
eventBus.RaiseLocalEvent(oldPuller.Uid, message, broadcast: false);
if (Owner.LifeStage <= EntityLifeStage.MapInitialized)
eventBus.RaiseLocalEvent(Owner.Uid, message);
_physics.WakeBody();
}
// else-branch warning is handled below
}
// Now that is settled, prepare to be pulled by a new object.
if (_physics == null)
{
Logger.WarningS("c.go.c.pulling", "Well now you've done it, haven't you? SharedPullableComponent on {0} didn't have an IPhysBody.", Owner);
return;
}
if (value == null)
{
MovingTo = null;
}
else
{
// Pulling a new object : Perform sanity checks.
if (!_canStartPull(value))
{
return;
}
if (!value.TryGetComponent<PhysicsComponent>(out var pullerPhysics))
{
return;
}
if (!value.TryGetComponent<SharedPullerComponent>(out var valuePuller))
{
return;
}
// 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 oldPulling = valuePuller.Pulling;
if (oldPulling != null)
{
if (oldPulling.TryGetComponent(out SharedPullableComponent? pullable))
{
pullable.TryStopPull();
}
else
{
Logger.WarningS("c.go.c.pulling", "Well now you've done it, haven't you? Someone transferred pulling to this component (on {0}) while presently pulling something that has no Pullable component (on {1})!", Owner, oldPulling);
return;
}
}
// Continue with pulling process.
var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics);
eventBus.RaiseLocalEvent(value.Uid, pullAttempt, broadcast: false);
if (pullAttempt.Cancelled)
{
return;
}
eventBus.RaiseLocalEvent(Owner.Uid, pullAttempt);
if (pullAttempt.Cancelled)
{
return;
}
// Pull start confirm
valuePuller.Pulling = Owner;
_puller = value;
Dirty();
PullerPhysics = pullerPhysics;
var message = new PullStartedMessage(PullerPhysics, _physics);
eventBus.RaiseLocalEvent(_puller.Uid, message, broadcast: false);
eventBus.RaiseLocalEvent(Owner.Uid, message);
var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB());
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
_physics.WakeBody();
_pullJoint = EntitySystem.Get<SharedJointSystem>().CreateDistanceJoint(Owner.Uid, _puller.Uid, id:$"pull-joint-{_physics.Owner.Uid}");
// _physics.BodyType = BodyType.Kinematic; // TODO: Need to consider their original bodytype
_pullJoint.CollideConnected = false;
_pullJoint.Length = length * 0.75f;
_pullJoint.MinLength = 0f;
_pullJoint.MaxLength = length;
_pullJoint.Stiffness = 1f;
}
// Code here will not run if pulling a new object was attempted and failed because of the returns from the refactor.
}
}
public IEntity? Puller { get; set; }
/// <summary>
/// The pull joint.
/// SharedPullingStateManagementSystem should be writing this. This means probably not you.
/// </summary>
public DistanceJoint? PullJoint { get; set; }
public bool BeingPulled => Puller != null;
public MapCoordinates? MovingTo
{
get => _movingTo;
set
{
if (_movingTo == value)
{
return;
}
_movingTo = value;
if (value == null)
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PullableStopMovingMessage());
}
else
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PullableMoveMessage());
}
}
}
/// <summary>
/// Sanity-check pull. This is called from Puller setter, so it will never deny a pull that's valid by setting Puller.
/// It might allow an impossible pull (i.e: puller has no PhysicsComponent somehow).
/// Ultimately this is only used separately to stop TryStartPull from cancelling a pull for no reason.
/// </summary>
private bool _canStartPull(IEntity puller)
{
if (!puller.HasComponent<SharedPullerComponent>())
{
return false;
}
if (!EntitySystem.Get<SharedPullingSystem>().CanPull(puller, Owner))
{
return false;
}
if (_physics == null)
{
return false;
}
if (_physics.BodyType == BodyType.Static)
{
return false;
}
if (puller == Owner)
{
return false;
}
if (!puller.IsInSameOrNoContainer(Owner))
{
return false;
}
return true;
}
public bool TryStartPull(IEntity puller)
{
if (!_canStartPull(puller))
{
return false;
}
TryStopPull();
Puller = puller;
if(Puller != puller)
{
return false;
}
return true;
}
public bool TryStopPull(IEntity? user = null)
{
if (!BeingPulled)
{
return false;
}
var msg = new StopPullingEvent(user?.Uid);
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, msg);
if (msg.Cancelled) return false;
if (_physics != null && _pullJoint != null)
{
EntitySystem.Get<SharedJointSystem>().RemoveJoint(_pullJoint);
}
if (user != null && user.TryGetComponent<SharedPullerComponent>(out var puller))
{
puller.Pulling = null;
}
_pullJoint = null;
Puller = null;
return true;
}
public bool TogglePull(IEntity puller)
{
if (BeingPulled)
{
if (Puller == puller)
{
return TryStopPull();
}
else
{
TryStopPull();
return TryStartPull(puller);
}
}
return TryStartPull(puller);
}
public bool TryMoveTo(MapCoordinates to)
{
if (Puller == null)
{
return false;
}
if (_physics == null)
{
return false;
}
MovingTo = to;
return true;
}
public MapCoordinates? MovingTo { get; set; }
public override ComponentState GetComponentState(ICommonSession player)
{
@@ -347,11 +69,19 @@ namespace Content.Shared.Pulling.Components
Puller = entity;
}
protected override void Shutdown()
{
EntitySystem.Get<SharedPullingStateManagementSystem>().ForceDisconnectPullable(this);
base.Shutdown();
}
protected override void OnRemove()
{
TryStopPull();
MovingTo = null;
if (Puller != null)
{
// This is absolute paranoia but it's also absolutely necessary. Too many puller state bugs. - 20kdc
Logger.ErrorS("c.go.c.pulling", "PULLING STATE CORRUPTION IMMINENT IN PULLABLE {0} - OnRemove called when Puller is set!", Owner);
}
base.OnRemove();
}
}

View File

@@ -1,49 +1,40 @@
using Content.Shared.Movement.Components;
using Content.Shared.Pulling;
using Content.Shared.Movement.Components;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
using Robust.Shared.Log;
using Component = Robust.Shared.GameObjects.Component;
namespace Content.Shared.Pulling.Components
{
[RegisterComponent]
[Friend(typeof(SharedPullingStateManagementSystem))]
public class SharedPullerComponent : Component, IMoveSpeedModifier
{
public override string Name => "Puller";
private IEntity? _pulling;
// Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed
public float WalkSpeedModifier => Pulling == null ? 1.0f : 0.75f;
public float WalkSpeedModifier => _pulling == null ? 1.0f : 0.75f;
public float SprintSpeedModifier => _pulling == null ? 1.0f : 0.75f;
public float SprintSpeedModifier => Pulling == null ? 1.0f : 0.75f;
[ViewVariables]
public IEntity? Pulling
public IEntity? Pulling { get; set; }
protected override void Shutdown()
{
get => _pulling;
set
{
if (_pulling == value)
{
return;
}
_pulling = value;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? speed))
{
speed.RefreshMovementSpeedModifiers();
}
}
EntitySystem.Get<SharedPullingStateManagementSystem>().ForceDisconnectPuller(this);
base.Shutdown();
}
protected override void OnRemove()
{
if (Pulling != null &&
Pulling.TryGetComponent(out SharedPullableComponent? pullable))
if (Pulling != null)
{
pullable.TryStopPull();
// This is absolute paranoia but it's also absolutely necessary. Too many puller state bugs. - 20kdc
Logger.ErrorS("c.go.c.pulling", "PULLING STATE CORRUPTION IMMINENT IN PULLER {0} - OnRemove called when Pulling is set!", Owner);
}
base.OnRemove();
}
}

View File

@@ -9,6 +9,7 @@ namespace Content.Shared.Pulling.Systems
public class SharedPullableSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly SharedPullingSystem _pullSystem = default!;
public override void Initialize()
{
@@ -20,7 +21,7 @@ namespace Content.Shared.Pulling.Systems
{
var entity = args.Session.AttachedEntity;
if (entity == null || !_blocker.CanMove(entity)) return;
component.TryStopPull();
_pullSystem.TryStopPull(component);
}
}
}

View File

@@ -1,15 +1,19 @@
using Content.Shared.Alert;
using Content.Shared.Hands;
using Content.Shared.Movement.Components;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Shared.Pulling.Systems
{
[UsedImplicitly]
public sealed class SharedPullerSystem : EntitySystem
{
[Dependency] private readonly SharedPullingSystem _pullSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -28,7 +32,7 @@ namespace Content.Shared.Pulling.Systems
{
if (EntityManager.TryGetComponent<SharedPullableComponent>(args.BlockingEntity, out var comp))
{
comp.TryStopPull(EntityManager.GetEntity(uid));
_pullSystem.TryStopPull(comp, EntityManager.GetEntity(uid));
}
}
}
@@ -43,6 +47,8 @@ namespace Content.Shared.Pulling.Systems
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulling);
RefreshMovementSpeed(component);
}
private static void PullerHandlePullStopped(
@@ -55,6 +61,17 @@ namespace Content.Shared.Pulling.Systems
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulling);
RefreshMovementSpeed(component);
}
private static void RefreshMovementSpeed(SharedPullerComponent component)
{
// Before changing how this is updated, please see SharedPullerComponent
if (component.Owner.TryGetComponent<MovementSpeedModifierComponent>(out var speed))
{
speed.RefreshMovementSpeedModifiers();
}
}
}
}

View File

@@ -0,0 +1,168 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.GameTicking;
using Content.Shared.Input;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components;
using Content.Shared.Pulling.Events;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Players;
using Robust.Shared.Utility;
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 class SharedPullingStateManagementSystem : EntitySystem
{
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
// 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 = puller.Owner.GetComponent<PhysicsComponent>();
var pullablePhysics = pullable.Owner.GetComponent<PhysicsComponent>();
// MovingTo shutdown
ForceSetMovingTo(pullable, null);
// Joint shutdown
if (puller.Owner.TryGetComponent<JointComponent>(out var jointComp))
{
if (jointComp.GetJoints.Contains(pullable.PullJoint!))
{
_jointSystem.RemoveJoint(pullable.PullJoint!);
}
}
pullable.PullJoint = null;
// State shutdown
puller.Pulling = null;
pullable.Puller = null;
// Messaging
var message = new PullStoppedMessage(pullerPhysics, pullablePhysics);
RaiseLocalEvent(puller.Owner.Uid, message, broadcast: false);
if (pullable.Owner.LifeStage <= EntityLifeStage.MapInitialized)
RaiseLocalEvent(pullable.Owner.Uid, message);
// Networking
puller.Dirty();
pullable.Dirty();
}
public void ForceRelationship(SharedPullerComponent? puller, SharedPullableComponent? pullable)
{
if ((puller != null) && (puller.Pulling == pullable))
{
// Already done
return;
}
// Start by disconnecting the pullable from whatever it is currently connected to.
var pullableOldPullerE = pullable?.Puller;
if (pullableOldPullerE != null)
{
ForceDisconnect(pullableOldPullerE.GetComponent<SharedPullerComponent>(), pullable!);
}
// Continue with the puller.
var pullerOldPullableE = puller?.Pulling;
if (pullerOldPullableE != null)
{
ForceDisconnect(puller!, pullerOldPullableE.GetComponent<SharedPullableComponent>());
}
// And now for the actual connection (if any).
if ((puller != null) && (pullable != null))
{
var pullerPhysics = puller.Owner.GetComponent<PhysicsComponent>();
var pullablePhysics = pullable.Owner.GetComponent<PhysicsComponent>();
// State startup
puller.Pulling = pullable.Owner;
pullable.Puller = puller.Owner;
// Joint startup
var union = pullerPhysics.GetWorldAABB().Union(pullablePhysics.GetWorldAABB());
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
pullable.PullJoint = _jointSystem.CreateDistanceJoint(pullablePhysics.Owner.Uid, pullerPhysics.Owner.Uid, id:$"pull-joint-{pullablePhysics.Owner.Uid}");
pullable.PullJoint.CollideConnected = false;
pullable.PullJoint.Length = length * 0.75f;
pullable.PullJoint.MinLength = 0f;
pullable.PullJoint.MaxLength = length;
pullable.PullJoint.Stiffness = 1f;
// Messaging
var message = new PullStartedMessage(pullerPhysics, pullablePhysics);
RaiseLocalEvent(puller.Owner.Uid, message, broadcast: false);
RaiseLocalEvent(pullable.Owner.Uid, message);
// Networking
puller.Dirty();
pullable.Dirty();
}
}
// 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, MapCoordinates? movingTo)
{
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;
if (movingTo == null)
{
RaiseLocalEvent(pullable.Owner.Uid, new PullableStopMovingMessage());
}
else
{
RaiseLocalEvent(pullable.Owner.Uid, new PullableMoveMessage());
}
}
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
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.Pulling.Events;
using Content.Shared.Rotatable;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Log;
namespace Content.Shared.Pulling
{
public abstract partial class SharedPullingSystem : EntitySystem
{
public bool CanPull(IEntity puller, IEntity pulled)
{
if (!puller.HasComponent<SharedPullerComponent>())
{
return false;
}
if (!pulled.TryGetComponent<IPhysBody>(out var _physics))
{
return false;
}
if (_physics.BodyType == BodyType.Static)
{
return false;
}
if (puller == pulled)
{
return false;
}
if (!puller.IsInSameOrNoContainer(pulled))
{
return false;
}
var startPull = new StartPullAttemptEvent(puller, pulled);
RaiseLocalEvent(puller.Uid, startPull);
return !startPull.Cancelled;
}
public bool TogglePull(IEntity puller, SharedPullableComponent pullable)
{
if (pullable.Puller == puller)
{
return TryStopPull(pullable);
}
return TryStartPull(puller, pullable.Owner);
}
// -- Core attempted actions --
public bool TryStopPull(SharedPullableComponent pullable, IEntity? user = null)
{
if (!pullable.BeingPulled)
{
return false;
}
var msg = new StopPullingEvent(user?.Uid);
RaiseLocalEvent(pullable.Owner.Uid, msg);
if (msg.Cancelled) return false;
_pullSm.ForceRelationship(null, pullable);
return true;
}
public bool TryStartPull(IEntity puller, IEntity pullable)
{
if (!puller.TryGetComponent<SharedPullerComponent>(out var pullerComp))
{
return false;
}
if (!pullable.TryGetComponent<SharedPullableComponent>(out var pullableComp))
{
return false;
}
return TryStartPull(pullerComp, pullableComp);
}
// The main "start pulling" function.
public bool TryStartPull(SharedPullerComponent puller, SharedPullableComponent pullable)
{
if (puller.Pulling == pullable)
return true;
// Pulling a new object : Perform sanity checks.
if (!EntitySystem.Get<SharedPullingSystem>().CanPull(puller.Owner, pullable.Owner))
{
return false;
}
if (!puller.Owner.TryGetComponent<PhysicsComponent>(out var pullerPhysics))
{
return false;
}
if (!pullable.Owner.TryGetComponent<PhysicsComponent>(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 (oldPullable.TryGetComponent<SharedPullableComponent>(out var oldPullableComp))
{
if (!TryStopPull(oldPullableComp))
{
return false;
}
}
else
{
Logger.WarningS("c.go.c.pulling", "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 PullAttemptMessage(pullerPhysics, pullablePhysics);
RaiseLocalEvent(puller.Owner.Uid, pullAttempt, broadcast: false);
if (pullAttempt.Cancelled)
{
return false;
}
RaiseLocalEvent(pullable.Owner.Uid, pullAttempt);
if (pullAttempt.Cancelled)
{
return false;
}
_pullSm.ForceRelationship(puller, pullable);
return true;
}
public bool TryMoveTo(SharedPullableComponent pullable, MapCoordinates to)
{
if (pullable.Puller == null)
{
return false;
}
if (!pullable.Owner.HasComponent<PhysicsComponent>())
{
return false;
}
_pullSm.ForceSetMovingTo(pullable, to);
return true;
}
public void StopMoveTo(SharedPullableComponent pullable)
{
_pullSm.ForceSetMovingTo(pullable, null);
}
}
}

View File

@@ -16,12 +16,15 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.IoC;
namespace Content.Shared.Pulling
{
[UsedImplicitly]
public abstract class SharedPullingSystem : EntitySystem
public abstract partial class SharedPullingSystem : EntitySystem
{
[Dependency] private readonly SharedPullingStateManagementSystem _pullSm = default!;
/// <summary>
/// A mapping of pullers to the entity that they are pulling.
/// </summary>
@@ -101,12 +104,6 @@ namespace Content.Shared.Pulling
private void OnPullStarted(PullStartedMessage message)
{
if (_pullers.TryGetValue(message.Puller.Owner, out var pulled) &&
pulled.TryGetComponent(out SharedPullableComponent? pulledComponent))
{
pulledComponent.TryStopPull();
}
SetPuller(message.Puller.Owner, message.Pulled.Owner);
}
@@ -128,11 +125,20 @@ namespace Content.Shared.Pulling
private void PullerMoved(ref MoveEvent ev)
{
var puller = ev.Sender;
if (!TryGetPulled(ev.Sender, out var pulled))
{
return;
}
// The pulled object may have already been deleted.
// TODO: Work out why. Monkey + meat spike is a good test for this,
// assuming you're still pulling the monkey when it gets gibbed.
if (pulled.Deleted)
{
return;
}
if (!pulled.TryGetComponent(out IPhysBody? physics))
{
return;
@@ -144,7 +150,7 @@ namespace Content.Shared.Pulling
if (pulled.TryGetComponent(out SharedPullableComponent? pullable))
{
pullable.MovingTo = null;
_pullSm.ForceSetMovingTo(pullable, null);
}
}
@@ -153,7 +159,7 @@ namespace Content.Shared.Pulling
{
if (message.Entity.TryGetComponent(out SharedPullableComponent? pullable))
{
pullable.TryStopPull();
TryStopPull(pullable);
}
if (message.Entity.TryGetComponent(out SharedPullerComponent? puller))
@@ -165,7 +171,7 @@ namespace Content.Shared.Pulling
return;
}
pulling.TryStopPull();
TryStopPull(pulling);
}
}
@@ -188,7 +194,7 @@ namespace Content.Shared.Pulling
return false;
}
pullable.TryMoveTo(coords.ToMap(EntityManager));
TryMoveTo(pullable, coords.ToMap(EntityManager));
return false;
}
@@ -238,12 +244,5 @@ namespace Content.Shared.Pulling
pulled.Transform.WorldRotation = newAngle;
}
}
public bool CanPull(IEntity puller, IEntity pulled)
{
var startPull = new StartPullAttemptEvent(puller, pulled);
RaiseLocalEvent(puller.Uid, startPull);
return !startPull.Cancelled;
}
}
}