From 9a099dfe9c00bbb2cf72523b26b72670abb54ea4 Mon Sep 17 00:00:00 2001
From: Evgencheg <73418250+Evgencheg@users.noreply.github.com>
Date: Sat, 21 Sep 2024 10:45:15 +0300
Subject: [PATCH] Revert "Revert "Physical Pulling (#883)""
---
.../Pulling/Components/PullableComponent.cs | 7 +
.../Pulling/Components/PullerComponent.cs | 38 ++++-
.../Movement/Pulling/Systems/PullingSystem.cs | 137 +++++++++++++++---
.../Entities/Mobs/Player/admin_ghost.yml | 2 +
4 files changed, 154 insertions(+), 30 deletions(-)
diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
index db889e7e3b..01ce0efaae 100644
--- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
+++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
@@ -36,4 +36,11 @@ public sealed partial class PullableComponent : Component
[Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)]
[AutoNetworkedField, DataField]
public bool PrevFixedRotation;
+
+ ///
+ /// Whether the entity is currently being actively pushed by the puller.
+ /// If true, the entity will be able to enter disposals upon colliding with them, and the like.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool BeingActivelyPushed = false;
}
diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
index 1fc9b731bd..c13baa28ef 100644
--- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
+++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
@@ -1,5 +1,6 @@
using Content.Shared.Movement.Pulling.Systems;
using Robust.Shared.GameStates;
+using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Movement.Pulling.Components;
@@ -11,16 +12,17 @@ namespace Content.Shared.Movement.Pulling.Components;
[Access(typeof(PullingSystem))]
public sealed partial class PullerComponent : Component
{
- // My raiding guild
///
- /// Next time the puller can throw what is being pulled.
- /// Used to avoid spamming it for infinite spin + velocity.
+ /// Next time the puller change where they are pulling the target towards.
///
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
- public TimeSpan NextThrow;
+ public TimeSpan NextPushTargetChange;
+
+ [DataField, AutoNetworkedField]
+ public TimeSpan NextPushStop;
[DataField]
- public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(1);
+ public TimeSpan PushChangeCooldown = TimeSpan.FromSeconds(0.1f), PushDuration = TimeSpan.FromSeconds(2f);
// Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed
public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f;
@@ -28,14 +30,38 @@ public sealed partial class PullerComponent : Component
public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f;
///
- /// Entity currently being pulled if applicable.
+ /// Entity currently being pulled/pushed if applicable.
///
[AutoNetworkedField, DataField]
public EntityUid? Pulling;
+ ///
+ /// The position the entity is currently being pulled towards.
+ ///
+ [AutoNetworkedField, DataField]
+ public EntityCoordinates? PushingTowards;
+
///
/// Does this entity need hands to be able to pull something?
///
[DataField]
public bool NeedsHands = true;
+
+ ///
+ /// The maximum acceleration of pushing, in meters per second squared.
+ ///
+ [DataField]
+ public float PushAcceleration = 0.3f;
+
+ ///
+ /// The maximum speed to which the pulled entity may be accelerated relative to the push direction, in meters per second.
+ ///
+ [DataField]
+ public float MaxPushSpeed = 1.2f;
+
+ ///
+ /// The maximum distance between the puller and the point towards which the puller may attempt to pull it, in meters.
+ ///
+ [DataField]
+ public float MaxPushRange = 2f;
}
diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
index 3c265d5a02..e6acabc33c 100644
--- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
+++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
@@ -8,16 +8,19 @@
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interaction;
+using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Movement.Systems;
+using Content.Shared.Projectiles;
using Content.Shared.Pulling.Events;
using Content.Shared.Throwing;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
+using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
@@ -34,6 +37,7 @@ public sealed class PullingSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _modifierSystem = default!;
@@ -43,7 +47,7 @@ public sealed class PullingSystem : EntitySystem
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
- [Dependency] private readonly ThrowingSystem _throwing = default!;
+ [Dependency] private readonly ThrownItemSystem _thrownItem = default!;
public override void Initialize()
{
@@ -57,7 +61,9 @@ public override void Initialize()
SubscribeLocalEvent(OnJointRemoved);
SubscribeLocalEvent>(AddPullVerbs);
SubscribeLocalEvent(OnPullableContainerInsert);
+ SubscribeLocalEvent(OnPullableCollide);
+ SubscribeLocalEvent(OnPullerMoveInput);
SubscribeLocalEvent(OnPullerContainerInsert);
SubscribeLocalEvent(OnPullerUnpaused);
SubscribeLocalEvent(OnVirtualItemDeleted);
@@ -69,6 +75,86 @@ public override void Initialize()
.Register();
}
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ CommandBinds.Unregister();
+ }
+
+ public override void Update(float frameTime)
+ {
+ if (_net.IsClient) // Client cannot predict this
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var puller, out var pullerComp, out var pullerPhysics, out var pullerXForm))
+ {
+ // If not pulling, reset the pushing cooldowns and exit
+ if (pullerComp.Pulling is not { } pulled || !TryComp(pulled, out var pulledComp))
+ {
+ pullerComp.PushingTowards = null;
+ pullerComp.NextPushTargetChange = TimeSpan.Zero;
+ continue;
+ }
+
+ pulledComp.BeingActivelyPushed = false; // Temporarily set to false; if the checks below pass, it will be set to true again
+
+ // If pulling but the pullee is invalid or is on a different map, stop pulling
+ var pulledXForm = Transform(pulled);
+ if (!TryComp(pulled, out var pulledPhysics)
+ || pulledPhysics.BodyType == BodyType.Static
+ || pulledXForm.MapUid != pullerXForm.MapUid)
+ {
+ StopPulling(pulled, pulledComp);
+ continue;
+ }
+
+ if (pullerComp.PushingTowards is null)
+ continue;
+
+ // If pushing but the target position is invalid, or the push action has expired or finished, stop pushing
+ if (pullerComp.NextPushStop < _timing.CurTime
+ || !(pullerComp.PushingTowards.Value.ToMap(EntityManager, _xformSys) is var pushCoordinates)
+ || pushCoordinates.MapId != pulledXForm.MapID)
+ {
+ pullerComp.PushingTowards = null;
+ pullerComp.NextPushTargetChange = TimeSpan.Zero;
+ continue;
+ }
+
+ // Actual force calculation. All the Vector2's below are in map coordinates.
+ var desiredDeltaPos = pushCoordinates.Position - Transform(pulled).Coordinates.ToMapPos(EntityManager, _xformSys);
+ if (desiredDeltaPos.LengthSquared() < 0.1f)
+ {
+ pullerComp.PushingTowards = null;
+ continue;
+ }
+
+ var velocityAndDirectionAngle = new Angle(pulledPhysics.LinearVelocity) - new Angle(desiredDeltaPos);
+ var currentRelativeSpeed = pulledPhysics.LinearVelocity.Length() * (float) Math.Cos(velocityAndDirectionAngle.Theta);
+ var desiredAcceleration = MathF.Max(0f, pullerComp.MaxPushSpeed - currentRelativeSpeed);
+
+ var desiredImpulse = pulledPhysics.Mass * desiredDeltaPos;
+ var maxSourceImpulse = MathF.Min(pullerComp.PushAcceleration, desiredAcceleration) * pullerPhysics.Mass;
+ var actualImpulse = desiredImpulse.LengthSquared() > maxSourceImpulse * maxSourceImpulse ? desiredDeltaPos.Normalized() * maxSourceImpulse : desiredImpulse;
+
+ // Ideally we'd want to apply forces instead of impulses, however...
+ // We cannot use ApplyForce here because it will be cleared on the next physics substep which will render it ultimately useless
+ // The alternative is to run this function on every physics substep, but that is way too expensive for such a minor system
+ _physics.ApplyLinearImpulse(pulled, actualImpulse);
+ _physics.ApplyLinearImpulse(puller, -actualImpulse);
+ pulledComp.BeingActivelyPushed = true;
+ }
+ query.Dispose();
+ }
+
+ private void OnPullerMoveInput(EntityUid uid, PullerComponent component, ref MoveInputEvent args)
+ {
+ // Stop pushing
+ component.PushingTowards = null;
+ component.NextPushStop = TimeSpan.Zero;
+ }
+
private void OnPullerContainerInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args)
{
if (ent.Comp.Pulling == null) return;
@@ -84,15 +170,26 @@ private void OnPullableContainerInsert(Entity ent, ref EntGot
TryStopPull(ent.Owner, ent.Comp);
}
- public override void Shutdown()
+ private void OnPullableCollide(Entity ent, ref StartCollideEvent args)
{
- base.Shutdown();
- CommandBinds.Unregister();
+ if (!ent.Comp.BeingActivelyPushed || args.OtherEntity == ent.Comp.Puller)
+ return;
+
+ // This component isn't actually needed anywhere besides the thrownitemsyste`m itself, so we just fake it
+ var fakeThrown = new ThrownItemComponent()
+ {
+ Owner = ent.Owner,
+ Animate = false,
+ Landed = false,
+ PlayLandSound = false,
+ Thrower = ent.Comp.Puller
+ };
+ _thrownItem.ThrowCollideInteraction(fakeThrown, ent, args.OtherEntity);
}
private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args)
{
- component.NextThrow += args.PausedTime;
+ component.NextPushTargetChange += args.PausedTime;
}
private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args)
@@ -234,31 +331,22 @@ public bool IsPulled(EntityUid uid, PullableComponent? component = null)
private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
- if (session?.AttachedEntity is not { } player ||
- !player.IsValid())
- {
- return false;
- }
-
- if (!TryComp(player, out var pullerComp))
+ if (session?.AttachedEntity is not { } player
+ || !player.IsValid()
+ || !TryComp(player, out var pullerComp))
return false;
var pulled = pullerComp.Pulling;
-
- if (!HasComp(pulled))
- return false;
-
- if (_containerSystem.IsEntityInContainer(player))
+ if (!HasComp(pulled)
+ || _containerSystem.IsEntityInContainer(player)
+ || _timing.CurTime < pullerComp.NextPushTargetChange)
return false;
- // Cooldown buddy
- if (_timing.CurTime < pullerComp.NextThrow)
- return false;
-
- pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown;
+ pullerComp.NextPushTargetChange = _timing.CurTime + pullerComp.PushChangeCooldown;
+ pullerComp.NextPushStop = _timing.CurTime + pullerComp.PushDuration;
// Cap the distance
- const float range = 2f;
+ var range = pullerComp.MaxPushRange;
var fromUserCoords = coords.WithEntityId(player, EntityManager);
var userCoords = new EntityCoordinates(player, Vector2.Zero);
@@ -268,8 +356,9 @@ private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinate
fromUserCoords = userCoords.Offset(userDirection.Normalized() * range);
}
+ pullerComp.PushingTowards = fromUserCoords;
Dirty(player, pullerComp);
- _throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false, doSpin: false);
+
return false;
}
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index 6c9ec2049f..e0300fa503 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -25,6 +25,8 @@
- type: GhostHearing
- type: Hands
- type: Puller
+ pushAcceleration: 1000000 # Will still be capped in max speed
+ maxPushRange: 20
- type: CombatMode
- type: Physics
ignorePaused: true