diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs
index d41f94d45c..c43e26b4a9 100644
--- a/Content.Server/Climbing/ClimbSystem.cs
+++ b/Content.Server/Climbing/ClimbSystem.cs
@@ -1,11 +1,14 @@
using Content.Server.Climbing.Components;
using Content.Server.DoAfter;
+using Content.Server.Interaction.Components;
using Content.Server.Popups;
using Content.Server.Stunnable;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Buckle.Components;
+using Content.Shared.CCVar;
using Content.Shared.Climbing;
using Content.Shared.Damage;
using Content.Shared.DragDrop;
@@ -16,17 +19,21 @@ using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Configuration;
using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player;
+using SharpZstd.Interop;
namespace Content.Server.Climbing;
[UsedImplicitly]
public sealed class ClimbSystem : SharedClimbSystem
{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
@@ -34,6 +41,7 @@ public sealed class ClimbSystem : SharedClimbSystem
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
+ [Dependency] private readonly AudioSystem _audioSystem = default!;
private const string ClimbingFixtureName = "climb";
private const int ClimbingCollisionGroup = (int) (CollisionGroup.TableLayer | CollisionGroup.LowImpassable);
@@ -80,6 +88,9 @@ public sealed class ClimbSystem : SharedClimbSystem
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanMove(args.User))
return;
+ if (component.Bonk && _cfg.GetCVar(CCVars.GameTableBonk))
+ return;
+
if (!TryComp(args.User, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
return;
@@ -102,6 +113,9 @@ public sealed class ClimbSystem : SharedClimbSystem
if (!TryComp(entityToMove, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
return;
+ if (TryBonk(component, user))
+ return;
+
_doAfterSystem.DoAfter(new DoAfterEventArgs(entityToMove, component.ClimbDelay, default, climbable)
{
BreakOnTargetMove = true,
@@ -112,6 +126,33 @@ public sealed class ClimbSystem : SharedClimbSystem
});
}
+ private bool TryBonk(ClimbableComponent component, EntityUid user)
+ {
+ if (!component.Bonk)
+ return false;
+
+ if (!_cfg.GetCVar(CCVars.GameTableBonk))
+ {
+ // Not set to always bonk, try clumsy roll.
+ if (!TryComp(user, out ClumsyComponent? clumsy))
+ return false;
+
+ if (!clumsy.RollClumsy(component.BonkClumsyChance))
+ return false;
+ }
+
+ // BONK!
+
+ _audioSystem.PlayPvs(component.BonkSound, component.Owner);
+
+ _stunSystem.TryKnockdown(user, TimeSpan.FromSeconds(component.BonkTime), true);
+
+ if (component.BonkDamage is { } bonkDmg)
+ _damageableSystem.TryChangeDamage(user, bonkDmg, true);
+
+ return true;
+ }
+
private void OnClimbFinished(EntityUid uid, ClimbingComponent climbing, ClimbFinishedEvent args)
{
Climb(uid, args.User, args.Climbable, climbing: climbing);
diff --git a/Content.Server/Climbing/Components/ClimbableComponent.cs b/Content.Server/Climbing/Components/ClimbableComponent.cs
index 67efd88c86..53cd673d5c 100644
--- a/Content.Server/Climbing/Components/ClimbableComponent.cs
+++ b/Content.Server/Climbing/Components/ClimbableComponent.cs
@@ -1,4 +1,7 @@
+using Content.Shared.CCVar;
using Content.Shared.Climbing;
+using Content.Shared.Damage;
+using Robust.Shared.Audio;
namespace Content.Server.Climbing.Components;
@@ -12,4 +15,36 @@ public sealed class ClimbableComponent : SharedClimbableComponent
[ViewVariables]
[DataField("delay")]
public float ClimbDelay = 0.8f;
+
+ ///
+ /// If set, people can bonk on this if is set or if they are clumsy.
+ ///
+ [ViewVariables] [DataField("bonk")] public bool Bonk = false;
+
+ ///
+ /// Chance of bonk triggering if the user is clumsy.
+ ///
+ [ViewVariables] [DataField("bonkClumsyChance")]
+ public float BonkClumsyChance = 0.75f;
+
+ ///
+ /// Sound to play when bonking.
+ ///
+ ///
+ [ViewVariables] [DataField("bonkSound")]
+ public SoundSpecifier? BonkSound;
+
+ ///
+ /// How long to stun players on bonk, in seconds.
+ ///
+ ///
+ [ViewVariables] [DataField("bonkTime")]
+ public float BonkTime = 2;
+
+ ///
+ /// How much damage to apply on bonk.
+ ///
+ ///
+ [ViewVariables] [DataField("bonkDamage")]
+ public DamageSpecifier? BonkDamage;
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 523f5164ba..717709c3ef 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -231,6 +231,12 @@ namespace Content.Shared.CCVar
public static readonly CVarDef PanicBunkerMinAccountAge =
CVarDef.Create("game.panic_bunker.min_account_age", 1440, CVar.SERVERONLY);
+ ///
+ /// Make people bonk when trying to climb certain objects like tables.
+ ///
+ public static readonly CVarDef GameTableBonk =
+ CVarDef.Create("game.table_bonk", false, CVar.SERVERONLY);
+
#if EXCEPTION_TOLERANCE
///
/// Amount of times round start must fail before the server is shut down.
diff --git a/Resources/Audio/Items/attributions.yml b/Resources/Audio/Items/attributions.yml
new file mode 100644
index 0000000000..84c0d544d0
--- /dev/null
+++ b/Resources/Audio/Items/attributions.yml
@@ -0,0 +1,9 @@
+- files: ["trayhit1.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Time immemorial"
+ source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/trayhit1.ogg"
+
+- files: ["trayhit2.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Time immemorial"
+ source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/trayhit2.ogg"
\ No newline at end of file
diff --git a/Resources/Audio/Items/trayhit1.ogg b/Resources/Audio/Items/trayhit1.ogg
new file mode 100644
index 0000000000..a2b5532a54
Binary files /dev/null and b/Resources/Audio/Items/trayhit1.ogg differ
diff --git a/Resources/Audio/Items/trayhit2.ogg b/Resources/Audio/Items/trayhit2.ogg
new file mode 100644
index 0000000000..066e44bf0b
Binary files /dev/null and b/Resources/Audio/Items/trayhit2.ogg differ
diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml
index 0a0072385d..4211265a68 100644
--- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml
+++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml
@@ -27,4 +27,10 @@
key: state
base: state_
- type: Climbable
+ bonk: true
+ bonkDamage:
+ types:
+ Blunt: 4
+ bonkSound: !type:SoundCollectionSpecifier
+ collection: TrayHit
- type: Clickable
diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml
index abd0d2da88..051d2c9ec6 100644
--- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml
+++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml
@@ -45,7 +45,7 @@
- type: Construction
graph: Table
node: TableFrame
-
+
- type: entity
id: CounterWoodFrame
parent: BaseStructure
@@ -216,6 +216,12 @@
- type: Construction
graph: Table
node: TableReinforced
+ - type: Climbable
+ # Reinforced tables are extra tough
+ bonkDamage:
+ types:
+ Blunt: 8
+ bonkTime: 3
- type: entity
id: TableGlass
@@ -490,7 +496,7 @@
- type: Construction
graph: Table
node: CounterWood
-
+
- type: entity
id: TableCounterMetal
parent: CounterMetalFrame
diff --git a/Resources/Prototypes/SoundCollections/tray.yml b/Resources/Prototypes/SoundCollections/tray.yml
new file mode 100644
index 0000000000..960b6dfe2c
--- /dev/null
+++ b/Resources/Prototypes/SoundCollections/tray.yml
@@ -0,0 +1,5 @@
+- type: soundCollection
+ id: TrayHit
+ files:
+ - /Audio/Items/trayhit1.ogg
+ - /Audio/Items/trayhit2.ogg