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