Skip to content

Commit

Permalink
Mirror: Fix bypassing vaulting clumsy check with verb action. (#314)
Browse files Browse the repository at this point in the history
## Mirror of PR #24977: [Fix bypassing vaulting clumsy check with verb
action.](space-wizards/space-station-14#24977)
from <img src="https://avatars.githubusercontent.com/u/10567778?v=4"
alt="space-wizards" width="22"/>
[space-wizards](https://github.com/space-wizards)/[space-station-14](https://github.com/space-wizards/space-station-14)

###### `225bc3c5aeffbef6286b607b02cd24a8ad75a437`

PR opened by <img
src="https://avatars.githubusercontent.com/u/85356?v=4" width="16"/><a
href="https://github.com/Tayrtahn"> Tayrtahn</a> at 2024-02-05 21:32:50
UTC

---

PR changed 7 files with 101 additions and 39 deletions.

The PR had the following labels:
- Status: Needs Review


---

<details open="true"><summary><h1>Original Body</h1></summary>

> <!-- Please read these guidelines before opening your PR:
https://docs.spacestation14.io/en/getting-started/pr-guideline -->
> <!-- The text between the arrows are comments - they will not be
visible on your PR. -->
> 
> ## About the PR
> <!-- What did you change in this PR? -->
> Stops clumsy characters being able to climb/vault onto tables and such
by using alt-click or the verb menu instead of drag-dropping.
> 
> This also fixes a separate bug where, when a clumsy character managed
to pass the bonk check, the action would just cancel and the character
didn't actually climb the object. Passing the check will now let the
character climb.
> 
> Additionally, this fixes a oddity where the ability for mobs to climb
was determined by whether or not they had feet/legs, which was arbitrary
and problematic for creatures like slimes (which should be able to ooze
up onto a table). This check could also be bypassed by using the verb
instead of drag-dropping, so that was fixed too. The ability for mobs to
climb is now controlled in the prototypes.
> 
> The default chance of a clumsy character bonking on a climbable has
also been reduced from 75% to 50% to compensate somewhat.
> 
> ## Why / Balance
> <!-- Why was it changed? Link any discussions or issues here. Please
discuss how this would affect game balance. -->
> Fixes #24951
> Fixes #17423
> Fixes #25951
> 
> ## Technical details
> <!-- If this is a code change, summarize at high level how your new
code works. This makes it easier to review. -->
> ClimbSystem now raises AttemptClimbEvent as part of TryClimb, and
aborts climbing if it gets cancelled. This makes sure that all code
paths go through a clumsy check. BonkSystem now listens for
AttemptClimbEvents and responds to them instead of trying to intercept
DragDropEvents.
> CanVault now gets checked in TryClimb as well, to prevent bypassing
it.
> 
> The logic for climbing capabilities is now:
> - The presence of ClimbingComponent makes an entity able to be placed
on surfaces like tables.
> - ClimbingComponent.CanClimb controls whether it can climb onto
surfaces by drag-drop or verb.
> 
> The new field defaults to true to minimize changes to existing
behavior. Some mobs will have gained the ability to climb when
previously they couldn't, but that should be less impactful than the
opposite and can be resolved by YML changes in the future.
> 
> ## Media
> <!-- 
> PRs which make ingame changes (adding clothing, items, new features,
etc) are required to have media attached that showcase the changes.
> Small fixes/refactors are exempt.
> Any media may be used in SS14 progress reports, with clear credit
given.
> 
> If you're unsure whether your PR will require media, ask a maintainer.
> 
> Check the box below to confirm that you have in fact seen this (put an
X in the brackets, like [X]):
> -->
> 
> - [x] I have added screenshots/videos to this PR showcasing its
changes ingame, **or** this PR does not require an ingame showcase
> 
> ## Breaking changes
> <!--
> List any breaking changes, including namespace, public
class/method/field changes, prototype renames; and provide instructions
for fixing them. This will be pasted in #codebase-changes.
> -->
> 
> 
> **Changelog**
> <!--
> Make players aware of new features and changes that could affect how
they play the game by adding a Changelog entry. Please read the
Changelog guidelines located at:
https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
> -->
> 
> <!--
> Make sure to take this Changelog template out of the comment block in
order for it to show up.
> 🆑
> - add: Added fun!
> - remove: Removed fun!
> - tweak: Changed fun!
> - fix: Fixed fun!
> -->
> 🆑
> - fix: Clumsy characters can no longer avoid bonking their heads by
using the Climb verb.
> - tweak: Clumsy characters are less likely to bonk their heads when
trying to climb.


</details>

Co-authored-by: SimpleStation14 <Unknown>
  • Loading branch information
SimpleStation14 committed May 11, 2024
1 parent d26a99e commit 9d059cf
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 39 deletions.
4 changes: 2 additions & 2 deletions Content.Shared/Climbing/Components/BonkableComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed partial class BonkableComponent : Component
/// Chance of bonk triggering if the user is clumsy.
/// </summary>
[DataField("bonkClumsyChance")]
public float BonkClumsyChance = 0.75f;
public float BonkClumsyChance = 0.5f;

/// <summary>
/// Sound to play when bonking.
Expand All @@ -42,5 +42,5 @@ public sealed partial class BonkableComponent : Component
/// How long it takes to bonk.
/// </summary>
[DataField("bonkDelay")]
public float BonkDelay = 0.8f;
public float BonkDelay = 1.5f;
}
12 changes: 12 additions & 0 deletions Content.Shared/Climbing/Components/ClimbingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@

namespace Content.Shared.Climbing.Components;

/// <summary>
/// Indicates that this entity is able to be placed on top of surfaces like tables.
/// Does not by itself allow the entity to carry out the action of climbing, unless
/// <see cref="CanClimb"/> is true. Use <see cref="CanForceClimb"/> to control whether
/// the entity can force other entities onto surfaces.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class ClimbingComponent : Component
{
/// <summary>
/// Whether the owner is able to climb onto things by their own action.
/// </summary>
[DataField, AutoNetworkedField]
public bool CanClimb = true;

/// <summary>
/// Whether the owner is climbing on a climbable entity.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions Content.Shared/Climbing/Events/AttemptClimbEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Content.Shared.Climbing.Events;

[ByRefEvent]
public record struct AttemptClimbEvent(EntityUid User, EntityUid Climber, EntityUid Climbable)
{
public bool Cancelled;
}
73 changes: 52 additions & 21 deletions Content.Shared/Climbing/Systems/BonkSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Content.Shared.CCVar;
using Content.Shared.Climbing.Components;
using Content.Shared.Climbing.Events;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
Expand All @@ -9,7 +10,6 @@
using Content.Shared.Interaction.Components;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
Expand All @@ -30,42 +30,54 @@ public sealed partial class BonkSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BonkableComponent, DragDropTargetEvent>(OnDragDrop);
SubscribeLocalEvent<BonkableComponent, BonkDoAfterEvent>(OnBonkDoAfter);
SubscribeLocalEvent<BonkableComponent, AttemptClimbEvent>(OnAttemptClimb);
}

private void OnBonkDoAfter(EntityUid uid, Components.BonkableComponent component, BonkDoAfterEvent args)
private void OnBonkDoAfter(EntityUid uid, BonkableComponent component, BonkDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
if (args.Handled || args.Cancelled || args.Args.Used == null)
return;

TryBonk(args.Args.User, uid, component);
TryBonk(args.Args.Used.Value, uid, component, source: args.Args.User);

args.Handled = true;
}


public bool TryBonk(EntityUid user, EntityUid bonkableUid, Components.BonkableComponent? bonkableComponent = null)
public bool TryBonk(EntityUid user, EntityUid bonkableUid, BonkableComponent? bonkableComponent = null, EntityUid? source = null)
{
if (!Resolve(bonkableUid, ref bonkableComponent, false))
return false;

if (!_cfg.GetCVar(CCVars.GameTableBonk))
{
// Not set to always bonk, try clumsy roll.
if (!_interactionSystem.TryRollClumsy(user, bonkableComponent.BonkClumsyChance))
return false;
}

// BONK!
var userName = Identity.Entity(user, EntityManager);
var bonkableName = Identity.Entity(bonkableUid, EntityManager);

_popupSystem.PopupEntity(Loc.GetString("bonkable-success-message-others", ("user", userName), ("bonkable", bonkableName)), user, Filter.PvsExcept(user), true);
if (user == source)
{
// Non-local, non-bonking players
_popupSystem.PopupEntity(Loc.GetString("bonkable-success-message-others", ("user", userName), ("bonkable", bonkableName)), user, Filter.PvsExcept(user), true);
// Local, bonking player
_popupSystem.PopupClient(Loc.GetString("bonkable-success-message-user", ("user", userName), ("bonkable", bonkableName)), user, user);
}
else if (source != null)
{
// Local, non-bonking player (dragger)
_popupSystem.PopupClient(Loc.GetString("bonkable-success-message-others", ("user", userName), ("bonkable", bonkableName)), user, source.Value);
// Non-local, non-bonking players
_popupSystem.PopupEntity(Loc.GetString("bonkable-success-message-others", ("user", userName), ("bonkable", bonkableName)), user, Filter.Pvs(user).RemoveWhereAttachedEntity(e => e == user || e == source.Value), true);
// Non-local, bonking player
_popupSystem.PopupEntity(Loc.GetString("bonkable-success-message-user", ("user", userName), ("bonkable", bonkableName)), user, user);
}



_popupSystem.PopupEntity(Loc.GetString("bonkable-success-message-user", ("user", userName), ("bonkable", bonkableName)), user, user);
if (source != null)
_audioSystem.PlayPredicted(bonkableComponent.BonkSound, bonkableUid, source);
else
_audioSystem.PlayPvs(bonkableComponent.BonkSound, bonkableUid);

_audioSystem.PlayPvs(bonkableComponent.BonkSound, bonkableUid);
_stunSystem.TryParalyze(user, TimeSpan.FromSeconds(bonkableComponent.BonkTime), true);

if (bonkableComponent.BonkDamage is { } bonkDmg)
Expand All @@ -75,12 +87,22 @@ public bool TryBonk(EntityUid user, EntityUid bonkableUid, Components.BonkableCo

}

private void OnDragDrop(EntityUid uid, Components.BonkableComponent component, ref DragDropTargetEvent args)
private bool TryStartBonk(EntityUid uid, EntityUid user, EntityUid climber, BonkableComponent? bonkableComponent = null)
{
if (args.Handled || !HasComp<ClumsyComponent>(args.Dragged) || !HasComp<HandsComponent>(args.User))
return;
if (!Resolve(uid, ref bonkableComponent, false))
return false;

var doAfterArgs = new DoAfterArgs(EntityManager, args.Dragged, component.BonkDelay, new BonkDoAfterEvent(), uid, target: uid)
if (!HasComp<ClumsyComponent>(climber) || !HasComp<HandsComponent>(user))
return false;

if (!_cfg.GetCVar(CCVars.GameTableBonk))
{
// Not set to always bonk, try clumsy roll.
if (!_interactionSystem.TryRollClumsy(climber, bonkableComponent.BonkClumsyChance))
return false;
}

var doAfterArgs = new DoAfterArgs(EntityManager, user, bonkableComponent.BonkDelay, new BonkDoAfterEvent(), uid, target: uid, used: climber)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
Expand All @@ -89,7 +111,16 @@ private void OnDragDrop(EntityUid uid, Components.BonkableComponent component, r

_doAfter.TryStartDoAfter(doAfterArgs);

args.Handled = true;
return true;
}

private void OnAttemptClimb(EntityUid uid, BonkableComponent component, AttemptClimbEvent args)
{
if (args.Cancelled || !HasComp<ClumsyComponent>(args.Climber) || !HasComp<HandsComponent>(args.User))
return;

if (TryStartBonk(uid, args.User, args.Climber, component))
args.Cancelled = true;
}

[Serializable, NetSerializable]
Expand Down
31 changes: 20 additions & 11 deletions Content.Shared/Climbing/Systems/ClimbSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Body.Systems;
using Content.Shared.Buckle.Components;
using Content.Shared.Climbing.Components;
Expand Down Expand Up @@ -151,7 +149,6 @@ private void OnCanDragDropOn(EntityUid uid, ClimbableComponent component, ref Ca
if (args.Handled)
return;


var canVault = args.User == args.Dragged
? CanVault(component, args.User, uid, out _)
: CanVault(component, args.User, args.Dragged, uid, out _);
Expand All @@ -169,7 +166,7 @@ private void AddClimbableVerb(EntityUid uid, ClimbableComponent component, GetVe
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
return;

if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing || !climbingComponent.CanClimb)
return;

// TODO VERBS ICON add a climbing icon?
Expand Down Expand Up @@ -198,14 +195,28 @@ public bool TryClimb(
{
id = null;

if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing))
if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing, false))
return false;

var canVault = user == entityToMove
? CanVault(comp, user, climbable, out var reason)
: CanVault(comp, user, entityToMove, climbable, out reason);
if (!canVault)
{
_popupSystem.PopupClient(reason, user, user);
return false;
}

// Note, IsClimbing does not mean a DoAfter is active, it means the target has already finished a DoAfter and
// is currently on top of something..
if (climbing.IsClimbing)
return true;

var ev = new AttemptClimbEvent(user, entityToMove, climbable);
RaiseLocalEvent(climbable, ref ev);
if (ev.Cancelled)
return false;

var args = new DoAfterArgs(EntityManager, user, comp.ClimbDelay, new ClimbDoAfterEvent(),
entityToMove,
target: climbable,
Expand Down Expand Up @@ -245,7 +256,7 @@ private void Climb(EntityUid uid, EntityUid user, EntityUid climbable, bool sile
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var worldDirection = _xformSystem.GetWorldPosition(climbable) - worldPos;
var distance = worldDirection.Length();
var parentRot = (worldRot - xform.LocalRotation);
var parentRot = worldRot - xform.LocalRotation;
// Need direction relative to climber's parent.
var localDirection = (-parentRot).RotateVec(worldDirection);

Expand Down Expand Up @@ -400,10 +411,8 @@ public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid tar
return false;
}

if (!HasComp<ClimbingComponent>(user)
|| !TryComp(user, out BodyComponent? body)
|| !_bodySystem.BodyHasPartType(user, BodyPartType.Leg, body)
|| !_bodySystem.BodyHasPartType(user, BodyPartType.Foot, body))
if (!TryComp<ClimbingComponent>(user, out var climbingComp)
|| !climbingComp.CanClimb)
{
reason = Loc.GetString("comp-climbable-cant-climb");
return false;
Expand Down Expand Up @@ -439,7 +448,7 @@ public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid dra

if (!HasComp<ClimbingComponent>(dragged))
{
reason = Loc.GetString("comp-climbable-cant-climb");
reason = Loc.GetString("comp-climbable-target-cant-climb", ("moved-user", Identity.Entity(dragged, EntityManager)));
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
bonkable-success-message-others = { CAPITALIZE(THE($user)) } bonks { POSS-ADJ($user) } head against { $bonkable }
bonkable-success-message-others = { CAPITALIZE(THE($user)) } bonks { POSS-ADJ($user) } head against { THE($bonkable) }
bonkable-success-message-user = You bonk your head against { THE($bonkable) }
11 changes: 7 additions & 4 deletions Resources/Locale/en-US/climbing/climbable-component.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ comp-climbable-user-climbs = You jump onto { THE($climbable) }!
# Shown to others when $user climbs on $climbable
comp-climbable-user-climbs-other = { CAPITALIZE(THE($user)) } jumps onto { THE($climbable) }!
# Shown to you when your character force someone to climb on $climbable
comp-climbable-user-climbs-force = You force { CAPITALIZE(THE($moved-user)) } onto { THE($climbable) }!
# Shown to you when your character forces someone to climb on $climbable
comp-climbable-user-climbs-force = You force { THE($moved-user) } onto { THE($climbable) }!
# Shown to others when someone force other $moved-user to climb on $climbable
# Shown to others when someone forces other $moved-user to climb on $climbable
comp-climbable-user-climbs-force-other = { CAPITALIZE(THE($user)) } forces { THE($moved-user) } onto { THE($climbable) }!
# Shown to you when your character is far away from climbable
Expand All @@ -24,5 +24,8 @@ comp-climbable-cant-reach = You can't reach there!
# Shown to you when your character can't interact with climbable for some reason
comp-climbable-cant-interact = You can't do that!
# Shown to you when your character can't climb
# Shown to you when your character isn't able to climb by their own actions
comp-climbable-cant-climb = You are incapable of climbing!
# Shown to you when your character tries to force someone else who can't climb onto a climbable
comp-climbable-target-cant-climb = { CAPITALIZE(THE($moved-user)) } can't go there!

0 comments on commit 9d059cf

Please sign in to comment.