diff --git a/Content.Shared/Climbing/Components/BonkableComponent.cs b/Content.Shared/Climbing/Components/BonkableComponent.cs index cc85e1c5626..5e97396fbad 100644 --- a/Content.Shared/Climbing/Components/BonkableComponent.cs +++ b/Content.Shared/Climbing/Components/BonkableComponent.cs @@ -15,7 +15,7 @@ public sealed partial class BonkableComponent : Component /// Chance of bonk triggering if the user is clumsy. /// [DataField("bonkClumsyChance")] - public float BonkClumsyChance = 0.75f; + public float BonkClumsyChance = 0.5f; /// /// Sound to play when bonking. @@ -42,5 +42,5 @@ public sealed partial class BonkableComponent : Component /// How long it takes to bonk. /// [DataField("bonkDelay")] - public float BonkDelay = 0.8f; + public float BonkDelay = 1.5f; } diff --git a/Content.Shared/Climbing/Components/ClimbingComponent.cs b/Content.Shared/Climbing/Components/ClimbingComponent.cs index 89320eabc89..1ab861b1f70 100644 --- a/Content.Shared/Climbing/Components/ClimbingComponent.cs +++ b/Content.Shared/Climbing/Components/ClimbingComponent.cs @@ -4,9 +4,21 @@ namespace Content.Shared.Climbing.Components; +/// +/// 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 +/// is true. Use to control whether +/// the entity can force other entities onto surfaces. +/// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class ClimbingComponent : Component { + /// + /// Whether the owner is able to climb onto things by their own action. + /// + [DataField, AutoNetworkedField] + public bool CanClimb = true; + /// /// Whether the owner is climbing on a climbable entity. /// diff --git a/Content.Shared/Climbing/Events/AttemptClimbEvent.cs b/Content.Shared/Climbing/Events/AttemptClimbEvent.cs new file mode 100644 index 00000000000..d38e27de386 --- /dev/null +++ b/Content.Shared/Climbing/Events/AttemptClimbEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Climbing.Events; + +[ByRefEvent] +public record struct AttemptClimbEvent(EntityUid User, EntityUid Climber, EntityUid Climbable) +{ + public bool Cancelled; +} diff --git a/Content.Shared/Climbing/Systems/BonkSystem.cs b/Content.Shared/Climbing/Systems/BonkSystem.cs index e91d1eadbec..c5cbb973766 100644 --- a/Content.Shared/Climbing/Systems/BonkSystem.cs +++ b/Content.Shared/Climbing/Systems/BonkSystem.cs @@ -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; @@ -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; @@ -30,42 +30,54 @@ public sealed partial class BonkSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDragDrop); SubscribeLocalEvent(OnBonkDoAfter); + SubscribeLocalEvent(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) @@ -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(args.Dragged) || !HasComp(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(climber) || !HasComp(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, @@ -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(args.Climber) || !HasComp(args.User)) + return; + + if (TryStartBonk(uid, args.User, args.Climber, component)) + args.Cancelled = true; } [Serializable, NetSerializable] diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs index 7c760f2c5de..6a2976a8387 100644 --- a/Content.Shared/Climbing/Systems/ClimbSystem.cs +++ b/Content.Shared/Climbing/Systems/ClimbSystem.cs @@ -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; @@ -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 _); @@ -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? @@ -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, @@ -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); @@ -400,10 +411,8 @@ public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid tar return false; } - if (!HasComp(user) - || !TryComp(user, out BodyComponent? body) - || !_bodySystem.BodyHasPartType(user, BodyPartType.Leg, body) - || !_bodySystem.BodyHasPartType(user, BodyPartType.Foot, body)) + if (!TryComp(user, out var climbingComp) + || !climbingComp.CanClimb) { reason = Loc.GetString("comp-climbable-cant-climb"); return false; @@ -439,7 +448,7 @@ public bool CanVault(ClimbableComponent component, EntityUid user, EntityUid dra if (!HasComp(dragged)) { - reason = Loc.GetString("comp-climbable-cant-climb"); + reason = Loc.GetString("comp-climbable-target-cant-climb", ("moved-user", Identity.Entity(dragged, EntityManager))); return false; } diff --git a/Resources/Locale/en-US/bonk/components/bonkable-component.ftl b/Resources/Locale/en-US/bonk/components/bonkable-component.ftl index e66eb91dd93..560b10c46ec 100644 --- a/Resources/Locale/en-US/bonk/components/bonkable-component.ftl +++ b/Resources/Locale/en-US/bonk/components/bonkable-component.ftl @@ -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) } diff --git a/Resources/Locale/en-US/climbing/climbable-component.ftl b/Resources/Locale/en-US/climbing/climbable-component.ftl index b614e669153..baff6f15649 100644 --- a/Resources/Locale/en-US/climbing/climbable-component.ftl +++ b/Resources/Locale/en-US/climbing/climbable-component.ftl @@ -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 @@ -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!