Skip to content

Commit

Permalink
Merge pull request #173 from Lost-Paradise-Project/revert-170-master
Browse files Browse the repository at this point in the history
Revert "Revert "Physical Pulling (#883)""
  • Loading branch information
Evgencheg authored Sep 21, 2024
2 parents 757038a + 9a099df commit 3b96574
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,11 @@ public sealed partial class PullableComponent : Component
[Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)]
[AutoNetworkedField, DataField]
public bool PrevFixedRotation;

/// <summary>
/// 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.
/// </summary>
[DataField, AutoNetworkedField]
public bool BeingActivelyPushed = false;
}
38 changes: 32 additions & 6 deletions Content.Shared/Movement/Pulling/Components/PullerComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,31 +12,56 @@ namespace Content.Shared.Movement.Pulling.Components;
[Access(typeof(PullingSystem))]
public sealed partial class PullerComponent : Component
{
// My raiding guild
/// <summary>
/// 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.
/// </summary>
[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;

public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f;

/// <summary>
/// Entity currently being pulled if applicable.
/// Entity currently being pulled/pushed if applicable.
/// </summary>
[AutoNetworkedField, DataField]
public EntityUid? Pulling;

/// <summary>
/// The position the entity is currently being pulled towards.
/// </summary>
[AutoNetworkedField, DataField]
public EntityCoordinates? PushingTowards;

/// <summary>
/// Does this entity need hands to be able to pull something?
/// </summary>
[DataField]
public bool NeedsHands = true;

/// <summary>
/// The maximum acceleration of pushing, in meters per second squared.
/// </summary>
[DataField]
public float PushAcceleration = 0.3f;

/// <summary>
/// The maximum speed to which the pulled entity may be accelerated relative to the push direction, in meters per second.
/// </summary>
[DataField]
public float MaxPushSpeed = 1.2f;

/// <summary>
/// The maximum distance between the puller and the point towards which the puller may attempt to pull it, in meters.
/// </summary>
[DataField]
public float MaxPushRange = 2f;
}
137 changes: 113 additions & 24 deletions Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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!;
Expand All @@ -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()
{
Expand All @@ -57,7 +61,9 @@ public override void Initialize()
SubscribeLocalEvent<PullableComponent, JointRemovedEvent>(OnJointRemoved);
SubscribeLocalEvent<PullableComponent, GetVerbsEvent<Verb>>(AddPullVerbs);
SubscribeLocalEvent<PullableComponent, EntGotInsertedIntoContainerMessage>(OnPullableContainerInsert);
SubscribeLocalEvent<PullableComponent, StartCollideEvent>(OnPullableCollide);

SubscribeLocalEvent<PullerComponent, MoveInputEvent>(OnPullerMoveInput);
SubscribeLocalEvent<PullerComponent, EntGotInsertedIntoContainerMessage>(OnPullerContainerInsert);
SubscribeLocalEvent<PullerComponent, EntityUnpausedEvent>(OnPullerUnpaused);
SubscribeLocalEvent<PullerComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
Expand All @@ -69,6 +75,86 @@ public override void Initialize()
.Register<PullingSystem>();
}

public override void Shutdown()
{
base.Shutdown();
CommandBinds.Unregister<PullingSystem>();
}

public override void Update(float frameTime)
{
if (_net.IsClient) // Client cannot predict this
return;

var query = EntityQueryEnumerator<PullerComponent, PhysicsComponent, TransformComponent>();
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<PullableComponent>(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<PhysicsComponent>(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<PullerComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
if (ent.Comp.Pulling == null) return;
Expand All @@ -84,15 +170,26 @@ private void OnPullableContainerInsert(Entity<PullableComponent> ent, ref EntGot
TryStopPull(ent.Owner, ent.Comp);
}

public override void Shutdown()
private void OnPullableCollide(Entity<PullableComponent> ent, ref StartCollideEvent args)
{
base.Shutdown();
CommandBinds.Unregister<PullingSystem>();
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)
Expand Down Expand Up @@ -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<PullerComponent>(player, out var pullerComp))
if (session?.AttachedEntity is not { } player
|| !player.IsValid()
|| !TryComp<PullerComponent>(player, out var pullerComp))
return false;

var pulled = pullerComp.Pulling;

if (!HasComp<PullableComponent>(pulled))
return false;

if (_containerSystem.IsEntityInContainer(player))
if (!HasComp<PullableComponent>(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);

Expand All @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3b96574

Please sign in to comment.