Skip to content

Commit

Permalink
Port Grab Intent From Goob (#1856)
Browse files Browse the repository at this point in the history
# Description
After months, Grab intent is finally ported to EE, as a result of a 4
hour Adderall induced code binge.

##  This PR is more shit than code.
Required for CQC, an attempt to port that will come later.
@Erisfiregamer1 requires this for
[Changelings](#1855).

Thanks to Gus for the Goobstation pr, and to Spatison for the original
port on WWDP
Tests on my local repo worked.
# TODO
* [ ]  Await review
* [ ]  pain

# Media

![dqt2naw4ox651](https://github.com/user-attachments/assets/9a97cea7-d2c8-47df-85e1-de243409bbe6)
# Changelog
🆑 Eagle
 
* add: Ported Grab Intent from Goobstation

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced pulling and grabbing interactions now feature multiple stages
that impact how actions and collisions feel.
- Virtual item handling during throws and drops has been refined for
more dynamic in-game outcomes.
- Alert visuals have been updated to provide nuanced feedback depending
on the intensity of pulls and grabs.
- Player movement and breathing mechanics have been fine-tuned for more
realistic behavior.
- New localization strings deliver clearer, context-sensitive
notifications for grab-related actions.
- Introduced a new component and system for managing entities thrown
while grabbed, including damage handling and visual effects.
- New event classes enhance interaction handling for virtual items
during grabbing actions.

- **Bug Fixes**
- Improved logic for stopping pull actions to ignore grab states when
necessary.

- **Chores**
- Added metadata for new textures related to alerts in the user
interface.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: VMSolidus <[email protected]>
  • Loading branch information
Eagle-0 and VMSolidus authored Mar 2, 2025
1 parent 3b30c0a commit 18722e8
Show file tree
Hide file tree
Showing 27 changed files with 967 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Content.Server/Alert/Click/StopPulling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void AlertClicked(EntityUid player)
if (entManager.TryGetComponent(player, out PullerComponent? puller) &&
entManager.TryGetComponent(puller.Pulling, out PullableComponent? pullableComp))
{
ps.TryStopPull(puller.Pulling.Value, pullableComp, user: player);
ps.TryLowerGrabStage(puller.Pulling.Value, player, true); // Spacious Edit, kill me now
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion Content.Server/Body/Systems/RespiratorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Content.Shared.Movement.Pulling.Components; // Goobstation
using Content.Shared.Movement.Pulling.Systems; // Goobstation

namespace Content.Server.Body.Systems;

Expand Down Expand Up @@ -53,6 +55,15 @@ public override void Initialize()
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}

// Goobstation start
// Can breathe check for grab
public bool CanBreathe(EntityUid uid, RespiratorComponent respirator)
{
if(respirator.Saturation < respirator.SuffocationThreshold)
return false;
return !TryComp<PullableComponent>(uid, out var pullable) || pullable.GrabStage != GrabStage.Suffocate;
}
// Goobstation end
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
Expand Down Expand Up @@ -98,7 +109,7 @@ public override void Update(float frameTime)
}
}

if (respirator.Saturation < respirator.SuffocationThreshold)
if (!CanBreathe(uid, respirator)) // Goobstation edit
{
if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown)
{
Expand Down
30 changes: 28 additions & 2 deletions Content.Server/Hands/Systems/HandsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Content.Shared.CombatMode;
using Content.Shared.Damage.Systems;
using Content.Shared.Explosion;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
Expand Down Expand Up @@ -94,7 +95,7 @@ private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent a

// Break any pulls
if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable))
_pullingSystem.TryStopPull(puller.Pulling.Value, pullable);
_pullingSystem.TryStopPull(puller.Pulling.Value, pullable, ignoreGrab: true); // Goobstation edit added check for grab

var offsetRandomCoordinates = _transformSystem.GetMoverCoordinates(args.Target).Offset(_random.NextVector2(1f, 1.5f));
if (!ThrowHeldItem(args.Target, offsetRandomCoordinates))
Expand Down Expand Up @@ -201,6 +202,20 @@ private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates co
if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player))
return false;

// Goobstation start
if (TryGetActiveItem(player, out var item) && TryComp<VirtualItemComponent>(item, out var virtComp))
{
var userEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true);
RaiseLocalEvent(player, userEv);

var targEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true);
RaiseLocalEvent(virtComp.BlockingEntity, targEv);

if (userEv.Cancelled || targEv.Cancelled)
return false;
}
// Goobstation end

return ThrowHeldItem(player, coordinates);
}

Expand All @@ -214,6 +229,18 @@ public bool ThrowHeldItem(EntityUid player, EntityCoordinates coordinates, float
hands.ActiveHandEntity is not { } throwEnt ||
!_actionBlockerSystem.CanThrow(player, throwEnt))
return false;
// Goobstation start added throwing for grabbed mobs, mnoved direction.
var direction = _transformSystem.ToMapCoordinates(coordinates).Position - _transformSystem.GetWorldPosition(player);

if (TryComp<VirtualItemComponent>(throwEnt, out var virt))
{
var userEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction);
RaiseLocalEvent(player, userEv);

var targEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction);
RaiseLocalEvent(virt.BlockingEntity, targEv);
}
// Goobstation end

if (_timing.CurTime < hands.NextThrowTime)
return false;
Expand All @@ -229,7 +256,6 @@ hands.ActiveHandEntity is not { } throwEnt ||
throwEnt = splitStack.Value;
}

var direction = coordinates.ToMapPos(EntityManager, _transformSystem) - Transform(player).WorldPosition;
if (direction == Vector2.Zero)
return true;

Expand Down
2 changes: 1 addition & 1 deletion Content.Shared/Administration/SharedAdminFrozenSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private void OnStartup(EntityUid uid, AdminFrozenComponent component, ComponentS
{
if (TryComp<PullableComponent>(uid, out var pullable))
{
_pulling.TryStopPull(uid, pullable);
_pulling.TryStopPull(uid, pullable, ignoreGrab: true); // Goobstation edit
}

UpdateCanMove(uid, component, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryA

if (TryComp<PullableComponent>(uid, out var pullable) && pullable.Puller != null)
{
_pulling.TryStopPull(uid, pullable);
_pulling.TryStopPull(uid, pullable, ignoreGrab: true); // goobstation edit
}

// TODO: Anchoring snaps rn anyway!
Expand Down
12 changes: 7 additions & 5 deletions Content.Shared/Cuffs/SharedCuffableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Popups;
using Content.Shared.Pulling.Events;
using Content.Shared.Rejuvenate;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Stunnable;
using Content.Shared.Timing;
using Content.Shared.Verbs;
Expand Down Expand Up @@ -342,7 +344,6 @@ private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuf
{
component.Used = true;
_audio.PlayPredicted(component.EndCuffSound, uid, user);

_popup.PopupEntity(Loc.GetString("handcuff-component-cuff-observer-success-message",
("user", Identity.Name(user, EntityManager)), ("target", Identity.Name(target, EntityManager))),
target, Filter.Pvs(target, entityManager: EntityManager)
Expand Down Expand Up @@ -778,9 +779,10 @@ private sealed partial class UnCuffDoAfterEvent : SimpleDoAfterEvent
{
}

[Serializable, NetSerializable]
private sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent
{
}
}
}

[Serializable, NetSerializable]
public sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent // Goob Edit moved out of class made public
{
}
46 changes: 45 additions & 1 deletion Content.Shared/Hands/HandEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public PickupAnimationEvent(NetEntity itemUid,
}
}

// Goobstation start
// Added virtual items for grab intent, this is heavily edited please do not bulldoze.
/// <summary>
/// Raised directed on both the blocking entity and user when
/// a virtual hand item is deleted.
Expand All @@ -148,14 +150,56 @@ public sealed class VirtualItemDeletedEvent : EntityEventArgs
{
public EntityUid BlockingEntity;
public EntityUid User;
public EntityUid VirtualItem;

public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user)
public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem)
{
BlockingEntity = blockingEntity;
User = user;
VirtualItem = virtualItem;
}
}

/// <summary>
/// Raised directed on both the blocking entity and user when
/// a virtual hand item is thrown (at least attempted to).
/// </summary>
public sealed class VirtualItemThrownEvent : EntityEventArgs
{
public EntityUid BlockingEntity;
public EntityUid User;
public EntityUid VirtualItem;
public Vector2 Direction;
public VirtualItemThrownEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem, Vector2 direction)
{
BlockingEntity = blockingEntity;
User = user;
VirtualItem = virtualItem;
Direction = direction;
}
}

/// <summary>
/// Raised directed on both the blocking entity and user when
/// user tries to drop it by keybind.
/// Cancellable.
/// </summary>
public sealed class VirtualItemDropAttemptEvent : CancellableEntityEventArgs
{
public EntityUid BlockingEntity;
public EntityUid User;
public EntityUid VirtualItem;
public bool Throw;
public VirtualItemDropAttemptEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem, bool thrown)
{
BlockingEntity = blockingEntity;
User = user;
VirtualItem = virtualItem;
Throw = thrown;
}
}
// Goobstation end

/// <summary>
/// Raised when putting an entity into a hand slot
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullW

var pos = Transform(user).Coordinates;
virtualItem = Spawn(VirtualItem, pos);
var virtualItemComp = Comp<VirtualItemComponent>(virtualItem.Value);
var virtualItemComp = EnsureComp<VirtualItemComponent>(virtualItem.Value); // Goobstation
virtualItemComp.BlockingEntity = blockingEnt;
Dirty(virtualItem.Value, virtualItemComp);
return true;
Expand All @@ -230,10 +230,10 @@ public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullW
/// </summary>
public void DeleteVirtualItem(Entity<VirtualItemComponent> item, EntityUid user)
{
var userEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user);
var userEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user, item.Owner); // Goobstation
RaiseLocalEvent(user, userEv);

var targEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user);
var targEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user, item.Owner); // Goobstation
RaiseLocalEvent(item.Comp.BlockingEntity, targEv);

if (TerminatingOrDeleted(item))
Expand Down
8 changes: 7 additions & 1 deletion Content.Shared/Item/HeldSpeedModifierSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private void OnGotUnequippedHand(Entity<HeldSpeedModifierComponent> ent, ref Got
_movementSpeedModifier.RefreshMovementSpeedModifiers(args.User);
}

private void OnRefreshMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component, HeldRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
public (float,float) GetHeldMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component)
{
var walkMod = component.WalkModifier;
var sprintMod = component.SprintModifier;
Expand All @@ -39,6 +39,12 @@ private void OnRefreshMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierCom
sprintMod = clothingSpeedModifier.SprintModifier;
}

return (walkMod, sprintMod);
}

private void OnRefreshMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component, HeldRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
var (walkMod, sprintMod) = GetHeldMovementSpeedModifiers(uid, component);
args.Args.ModifySpeed(walkMod, sprintMod);
}
}
21 changes: 21 additions & 0 deletions Content.Shared/Movement/Pulling/Components/PullableComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Shared.Alert;
using Content.Shared.Movement.Pulling.Systems; // Goobstation
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

Expand Down Expand Up @@ -39,6 +40,25 @@ public sealed partial class PullableComponent : Component
[AutoNetworkedField, DataField]
public bool PrevFixedRotation;

[DataField]
public Dictionary<GrabStage, short> PulledAlertAlertSeverity = new()
{
{ GrabStage.No, 0 },
{ GrabStage.Soft, 1 },
{ GrabStage.Hard, 2 },
{ GrabStage.Suffocate, 3 },
};

[AutoNetworkedField, DataField]
public GrabStage GrabStage = GrabStage.No;

[AutoNetworkedField, DataField]
public float GrabEscapeChance = 1f;

[AutoNetworkedField]
public TimeSpan NextEscapeAttempt = TimeSpan.Zero;
// Goobstation end

/// <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.
Expand All @@ -47,4 +67,5 @@ public sealed partial class PullableComponent : Component
public bool BeingActivelyPushed = false;
[DataField]
public ProtoId<AlertPrototype> PulledAlert = "Pulled";

}
67 changes: 67 additions & 0 deletions Content.Shared/Movement/Pulling/Components/PullerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,71 @@ public sealed partial class PullerComponent : Component
public float MaxPushRange = 2f;
[DataField]
public ProtoId<AlertPrototype> PullingAlert = "Pulling";

// Goobstation start
// Added Grab variables

[DataField]
public Dictionary<GrabStage, short> PullingAlertSeverity = new()
{
{ GrabStage.No, 0 },
{ GrabStage.Soft, 1 },
{ GrabStage.Hard, 2 },
{ GrabStage.Suffocate, 3 },
};

[DataField, AutoNetworkedField]
public GrabStage GrabStage = GrabStage.No;

[DataField, AutoNetworkedField]
public GrabStageDirection GrabStageDirection = GrabStageDirection.Increase;

[AutoNetworkedField]
public TimeSpan NextStageChange;

[DataField]
public TimeSpan StageChangeCooldown = TimeSpan.FromSeconds(1.5f);

[DataField]
public Dictionary<GrabStage, float> EscapeChances = new()
{
{ GrabStage.No, 1f },
{ GrabStage.Soft, 0.7f },
{ GrabStage.Hard, 0.4f },
{ GrabStage.Suffocate, 0.1f },
};

[DataField]
public float SuffocateGrabStaminaDamage = 10f;

[DataField]
public float GrabThrowDamageModifier = 1f;

[ViewVariables]
public List<EntityUid> GrabVirtualItems = new();

[ViewVariables]
public Dictionary<GrabStage, int> GrabVirtualItemStageCount = new()
{
{ GrabStage.Suffocate, 1 },
};

[DataField]
public float StaminaDamageOnThrown = 120f;

[DataField]
public float GrabThrownSpeed = 7f;

[DataField]
public float ThrowingDistance = 4f;

[DataField]
public float SoftGrabSpeedModifier = 0.9f;

[DataField]
public float HardGrabSpeedModifier = 0.7f;

[DataField]
public float ChokeGrabSpeedModifier = 0.4f;
// Goobstation end
}
Loading

0 comments on commit 18722e8

Please sign in to comment.