diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
index 01ce0efaae..db889e7e3b 100644
--- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
+++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs
@@ -36,11 +36,4 @@ 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 c13baa28ef..1fc9b731bd 100644
--- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
+++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
@@ -1,6 +1,5 @@
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;
@@ -12,17 +11,16 @@ namespace Content.Shared.Movement.Pulling.Components;
[Access(typeof(PullingSystem))]
public sealed partial class PullerComponent : Component
{
+ // My raiding guild
///
- /// Next time the puller change where they are pulling the target towards.
+ /// Next time the puller can throw what is being pulled.
+ /// Used to avoid spamming it for infinite spin + velocity.
///
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
- public TimeSpan NextPushTargetChange;
-
- [DataField, AutoNetworkedField]
- public TimeSpan NextPushStop;
+ public TimeSpan NextThrow;
[DataField]
- public TimeSpan PushChangeCooldown = TimeSpan.FromSeconds(0.1f), PushDuration = TimeSpan.FromSeconds(2f);
+ public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(1);
// Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed
public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f;
@@ -30,38 +28,14 @@ public sealed partial class PullerComponent : Component
public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f;
///
- /// Entity currently being pulled/pushed if applicable.
+ /// Entity currently being pulled 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 e6acabc33c..3c265d5a02 100644
--- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
+++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
@@ -8,19 +8,16 @@
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;
@@ -37,7 +34,6 @@ 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!;
@@ -47,7 +43,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 ThrownItemSystem _thrownItem = default!;
+ [Dependency] private readonly ThrowingSystem _throwing = default!;
public override void Initialize()
{
@@ -61,9 +57,7 @@ public override void Initialize()
SubscribeLocalEvent(OnJointRemoved);
SubscribeLocalEvent>(AddPullVerbs);
SubscribeLocalEvent(OnPullableContainerInsert);
- SubscribeLocalEvent(OnPullableCollide);
- SubscribeLocalEvent(OnPullerMoveInput);
SubscribeLocalEvent(OnPullerContainerInsert);
SubscribeLocalEvent(OnPullerUnpaused);
SubscribeLocalEvent(OnVirtualItemDeleted);
@@ -75,86 +69,6 @@ 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;
@@ -170,26 +84,15 @@ private void OnPullableContainerInsert(Entity ent, ref EntGot
TryStopPull(ent.Owner, ent.Comp);
}
- private void OnPullableCollide(Entity ent, ref StartCollideEvent args)
+ public override void Shutdown()
{
- 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);
+ base.Shutdown();
+ CommandBinds.Unregister();
}
private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args)
{
- component.NextPushTargetChange += args.PausedTime;
+ component.NextThrow += args.PausedTime;
}
private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args)
@@ -331,22 +234,31 @@ 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()
- || !TryComp(player, out var pullerComp))
+ if (session?.AttachedEntity is not { } player ||
+ !player.IsValid())
+ {
+ return false;
+ }
+
+ if (!TryComp(player, out var pullerComp))
return false;
var pulled = pullerComp.Pulling;
- if (!HasComp(pulled)
- || _containerSystem.IsEntityInContainer(player)
- || _timing.CurTime < pullerComp.NextPushTargetChange)
+
+ if (!HasComp(pulled))
+ return false;
+
+ if (_containerSystem.IsEntityInContainer(player))
return false;
- pullerComp.NextPushTargetChange = _timing.CurTime + pullerComp.PushChangeCooldown;
- pullerComp.NextPushStop = _timing.CurTime + pullerComp.PushDuration;
+ // Cooldown buddy
+ if (_timing.CurTime < pullerComp.NextThrow)
+ return false;
+
+ pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown;
// Cap the distance
- var range = pullerComp.MaxPushRange;
+ const float range = 2f;
var fromUserCoords = coords.WithEntityId(player, EntityManager);
var userCoords = new EntityCoordinates(player, Vector2.Zero);
@@ -356,9 +268,8 @@ 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 e0300fa503..6c9ec2049f 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -25,8 +25,6 @@
- type: GhostHearing
- type: Hands
- type: Puller
- pushAcceleration: 1000000 # Will still be capped in max speed
- maxPushRange: 20
- type: CombatMode
- type: Physics
ignorePaused: true