From 14f949c3112e903cd9adc1a8355a3aa3a9c1fcab Mon Sep 17 00:00:00 2001
From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Date: Mon, 18 Aug 2025 14:22:36 +0200
Subject: [PATCH] Jumpability collisions (#39710)
---
.../Components/ActiveLeaperComponent.cs | 16 ++++
.../Components/JumpAbilityComponent.cs | 31 ++++++++
.../Systems/SharedJumpAbilitySystem.cs | 78 ++++++++++++++++++-
.../en-US/jump-ability/jump-ability.ftl | 1 +
.../Prototypes/Entities/Mobs/Player/clone.yml | 1 +
5 files changed, 126 insertions(+), 1 deletion(-)
create mode 100644 Content.Shared/Movement/Components/ActiveLeaperComponent.cs
create mode 100644 Resources/Locale/en-US/jump-ability/jump-ability.ftl
diff --git a/Content.Shared/Movement/Components/ActiveLeaperComponent.cs b/Content.Shared/Movement/Components/ActiveLeaperComponent.cs
new file mode 100644
index 0000000000..cb5a237af4
--- /dev/null
+++ b/Content.Shared/Movement/Components/ActiveLeaperComponent.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Movement.Components;
+
+///
+/// Marker component given to the users of the if they are meant to collide with environment.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ActiveLeaperComponent : Component
+{
+ ///
+ /// The duration to stun the owner on collide with environment.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan KnockdownDuration;
+}
diff --git a/Content.Shared/Movement/Components/JumpAbilityComponent.cs b/Content.Shared/Movement/Components/JumpAbilityComponent.cs
index 6da9161578..a6e33d5b87 100644
--- a/Content.Shared/Movement/Components/JumpAbilityComponent.cs
+++ b/Content.Shared/Movement/Components/JumpAbilityComponent.cs
@@ -2,6 +2,7 @@ using Content.Shared.Actions;
using Content.Shared.Movement.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Movement.Components;
@@ -13,6 +14,18 @@ namespace Content.Shared.Movement.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedJumpAbilitySystem))]
public sealed partial class JumpAbilityComponent : Component
{
+ ///
+ /// The action prototype that allows you to jump.
+ ///
+ [DataField]
+ public EntProtoId Action = "ActionGravityJump";
+
+ ///
+ /// Entity to hold the action prototype.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? ActionEntity;
+
///
/// How far you will jump (in tiles).
///
@@ -25,11 +38,29 @@ public sealed partial class JumpAbilityComponent : Component
[DataField, AutoNetworkedField]
public float JumpThrowSpeed = 10f;
+ ///
+ /// Whether this entity can collide with another entity, leading to it getting knocked down.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanCollide = false;
+
+ ///
+ /// The duration of the knockdown in case of a collision from CanCollide.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan CollideKnockdown = TimeSpan.FromSeconds(2);
+
///
/// This gets played whenever the jump action is used.
///
[DataField, AutoNetworkedField]
public SoundSpecifier? JumpSound;
+
+ ///
+ /// The popup to show if the entity is unable to perform a jump.
+ ///
+ [DataField, AutoNetworkedField]
+ public LocId? JumpFailedPopup = "jump-ability-failure";
}
public sealed partial class GravityJumpEvent : InstantActionEvent;
diff --git a/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs b/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs
index ac720cae68..598e4b564a 100644
--- a/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs
+++ b/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs
@@ -1,7 +1,14 @@
+using Content.Shared.Actions;
+using Content.Shared.Actions.Components;
+using Content.Shared.Cloning.Events;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
+using Content.Shared.Popups;
+using Content.Shared.Standing;
+using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Robust.Shared.Audio.Systems;
+using Robust.Shared.Physics.Events;
namespace Content.Shared.Movement.Systems;
@@ -10,18 +17,64 @@ public sealed partial class SharedJumpAbilitySystem : EntitySystem
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly SharedStunSystem _stun = default!;
+ [Dependency] private readonly StandingStateSystem _standing = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnShutdown);
+
SubscribeLocalEvent(OnGravityJump);
+
+ SubscribeLocalEvent(OnLeaperCollide);
+ SubscribeLocalEvent(OnLeaperLand);
+ SubscribeLocalEvent(OnLeaperStopThrow);
+
+ SubscribeLocalEvent(OnClone);
+ }
+
+ private void OnInit(Entity entity, ref MapInitEvent args)
+ {
+ if (!TryComp(entity, out ActionsComponent? comp))
+ return;
+
+ _actions.AddAction(entity, ref entity.Comp.ActionEntity, entity.Comp.Action, component: comp);
+ }
+
+ private void OnShutdown(Entity entity, ref ComponentShutdown args)
+ {
+ _actions.RemoveAction(entity.Owner, entity.Comp.ActionEntity);
+ }
+
+ private void OnLeaperCollide(Entity entity, ref StartCollideEvent args)
+ {
+ _stun.TryKnockdown(entity.Owner, entity.Comp.KnockdownDuration, force: true);
+ RemCompDeferred(entity);
+ }
+
+ private void OnLeaperLand(Entity entity, ref LandEvent args)
+ {
+ RemCompDeferred(entity);
+ }
+
+ private void OnLeaperStopThrow(Entity entity, ref StopThrowEvent args)
+ {
+ RemCompDeferred(entity);
}
private void OnGravityJump(Entity entity, ref GravityJumpEvent args)
{
- if (_gravity.IsWeightless(args.Performer))
+ if (_gravity.IsWeightless(args.Performer) || _standing.IsDown(args.Performer))
+ {
+ if (entity.Comp.JumpFailedPopup != null)
+ _popup.PopupClient(Loc.GetString(entity.Comp.JumpFailedPopup.Value), args.Performer, args.Performer);
return;
+ }
var xform = Transform(args.Performer);
var throwing = xform.LocalRotation.ToWorldVec() * entity.Comp.JumpDistance;
@@ -30,6 +83,29 @@ public sealed partial class SharedJumpAbilitySystem : EntitySystem
_throwing.TryThrow(args.Performer, direction, entity.Comp.JumpThrowSpeed);
_audio.PlayPredicted(entity.Comp.JumpSound, args.Performer, args.Performer);
+
+ if (entity.Comp.CanCollide)
+ {
+ EnsureComp(entity, out var leaperComp);
+ leaperComp.KnockdownDuration = entity.Comp.CollideKnockdown;
+ Dirty(entity.Owner, leaperComp);
+ }
+
args.Handled = true;
}
+
+ private void OnClone(Entity ent, ref CloningEvent args)
+ {
+ if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name))
+ return;
+
+ var targetComp = Factory.GetComponent();
+ targetComp.Action = ent.Comp.Action;
+ targetComp.CanCollide = ent.Comp.CanCollide;
+ targetComp.JumpSound = ent.Comp.JumpSound;
+ targetComp.CollideKnockdown = ent.Comp.CollideKnockdown;
+ targetComp.JumpDistance = ent.Comp.JumpDistance;
+ targetComp.JumpThrowSpeed = ent.Comp.JumpThrowSpeed;
+ AddComp(args.CloneUid, targetComp, true);
+ }
}
diff --git a/Resources/Locale/en-US/jump-ability/jump-ability.ftl b/Resources/Locale/en-US/jump-ability/jump-ability.ftl
new file mode 100644
index 0000000000..a9737455a3
--- /dev/null
+++ b/Resources/Locale/en-US/jump-ability/jump-ability.ftl
@@ -0,0 +1 @@
+jump-ability-failure = You cannot jump right now.
diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml
index 907b694db9..43174cfffe 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml
@@ -121,6 +121,7 @@
- Rootable # diona
- Sericulture # arachnids
- MovementSpeedModifier # moths when weightless
+ - JumpAbility # vulp leaping
copyEquipment: null
copyInternalStorage: false
copyImplants: false