From ddeea645ef1053640c0dea4c045d58033b623390 Mon Sep 17 00:00:00 2001
From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Date: Sun, 25 Jan 2026 23:15:33 +0100
Subject: [PATCH] Add an option for hold-to-attack in settings (#42596)
* Initial commit
* Separate ranged and melee
* Move to controls tab
---
.../Options/UI/Tabs/AccessibilityTab.xaml | 4 ++--
.../Options/UI/Tabs/KeyRebindTab.xaml.cs | 23 +++++++++++++------
.../Weapons/Melee/MeleeWeaponSystem.cs | 5 +++-
.../Weapons/Ranged/Systems/GunSystem.cs | 5 ++++
Content.Shared/CCVar/CCVars.Interactions.cs | 13 +++++++++++
.../Ranged/Events/RequestShootEvent.cs | 17 ++++++++++++++
.../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 ++
.../en-US/escape-menu/ui/options-menu.ftl | 3 +++
8 files changed, 62 insertions(+), 10 deletions(-)
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
index b617ba03491..a4d63c0d356 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
@@ -16,8 +16,8 @@
-
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index ef7f50d927a..5e1bcd59857 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -36,12 +36,6 @@ namespace Content.Client.Options.UI.Tabs
private readonly List _deferCommands = new();
- private void HandleToggleUSQWERTYCheckbox(BaseButton.ButtonToggledEventArgs args)
- {
- _cfg.SetCVar(CVars.DisplayUSQWERTYHotkeys, args.Pressed);
- _cfg.SaveToFile();
- }
-
private void InitToggleWalk()
{
if (_cfg.GetCVar(CCVars.ToggleWalk))
@@ -150,8 +144,23 @@ namespace Content.Client.Options.UI.Tabs
KeybindsContainer.AddChild(newCheckBox);
}
+ void AddToggleCvarCheckBox(string checkBoxName, CVarDef cvar)
+ {
+ CheckBox newCheckBox = new CheckBox() { Text = Loc.GetString(checkBoxName) };
+ newCheckBox.Pressed = _cfg.GetCVar(cvar);
+ newCheckBox.OnToggled += (e) =>
+ {
+ _cfg.SetCVar(cvar, e.Pressed);
+ _cfg.SaveToFile();
+ };
+
+ KeybindsContainer.AddChild(newCheckBox);
+ }
+
AddHeader("ui-options-header-general");
- AddCheckBox("ui-options-hotkey-keymap", _cfg.GetCVar(CVars.DisplayUSQWERTYHotkeys), HandleToggleUSQWERTYCheckbox);
+ AddToggleCvarCheckBox("ui-options-hotkey-keymap", CVars.DisplayUSQWERTYHotkeys);
+ AddToggleCvarCheckBox("ui-options-hold-to-attack-melee", CCVars.ControlHoldToAttackMelee);
+ AddToggleCvarCheckBox("ui-options-hold-to-attack-ranged", CCVars.ControlHoldToAttackRanged);
AddHeader("ui-options-header-movement");
AddButton(EngineKeyFunctions.MoveUp);
diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
index 011c4e81d84..420e18748f0 100644
--- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
+++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Client.Gameplay;
+using Content.Shared.CCVar;
using Content.Shared.CombatMode;
using Content.Shared.Effects;
using Content.Shared.Hands.Components;
@@ -14,6 +15,7 @@ using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.State;
+using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Player;
@@ -31,6 +33,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly MapSystem _map = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
private EntityQuery _xformQuery;
@@ -76,7 +79,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
- if (weapon.AutoAttack || useDown != BoundKeyState.Down && altDown != BoundKeyState.Down)
+ if (weapon.AutoAttack || useDown != BoundKeyState.Down && altDown != BoundKeyState.Down || _cfg.GetCVar(CCVars.ControlHoldToAttackMelee))
{
if (weapon.Attacking)
{
diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
index a2d05e67d6f..a79065229cc 100644
--- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
+++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
@@ -4,6 +4,7 @@ using Content.Client.Gameplay;
using Content.Client.Items;
using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera;
+using Content.Shared.CCVar;
using Content.Shared.CombatMode;
using Content.Shared.Damage;
using Content.Shared.Weapons.Hitscan.Components;
@@ -19,6 +20,7 @@ using Robust.Client.Player;
using Robust.Client.State;
using Robust.Shared.Animations;
using Robust.Shared.Audio;
+using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -38,6 +40,7 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IStateManager _state = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
@@ -203,11 +206,13 @@ public sealed partial class GunSystem : SharedGunSystem
Log.Debug($"Sending shoot request tick {Timing.CurTick} / {Timing.CurTime}");
+
RaisePredictiveEvent(new RequestShootEvent
{
Target = target,
Coordinates = GetNetCoordinates(coordinates),
Gun = GetNetEntity(gun),
+ Continuous = _cfg.GetCVar(CCVars.ControlHoldToAttackRanged),
});
}
diff --git a/Content.Shared/CCVar/CCVars.Interactions.cs b/Content.Shared/CCVar/CCVars.Interactions.cs
index fcefa73a96a..401fabfa297 100644
--- a/Content.Shared/CCVar/CCVars.Interactions.cs
+++ b/Content.Shared/CCVar/CCVars.Interactions.cs
@@ -70,4 +70,17 @@ public sealed partial class CCVars
///
public static readonly CVarDef NestedStorage =
CVarDef.Create("control.nested_storage", true, CVar.REPLICATED | CVar.SERVER);
+
+ ///
+ /// If enabled, melee weapons that have click-to-attack patterns (unarmed, slow weapons) will continue attacking if the button is held.
+ ///
+ public static readonly CVarDef ControlHoldToAttackMelee =
+ CVarDef.Create("control.hold_to_attack_melee", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+ ///
+ /// If enabled, ranged weapons that have click-to-attack patterns (burst and semi-auto guns) will continue attacking if the button is held.
+ ///
+ public static readonly CVarDef ControlHoldToAttackRanged =
+ CVarDef.Create("control.hold_to_attack_ranged", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+
}
diff --git a/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs b/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs
index f5c4dd72b4f..7e521f0bae6 100644
--- a/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs
+++ b/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs
@@ -9,7 +9,24 @@ namespace Content.Shared.Weapons.Ranged.Events;
[Serializable, NetSerializable]
public sealed class RequestShootEvent : EntityEventArgs
{
+ ///
+ /// The gun shooting.
+ ///
public NetEntity Gun;
+
+ ///
+ /// The location the player is shooting at.
+ ///
public NetCoordinates Coordinates;
+
+ ///
+ /// The target the player is shooting at, if any.
+ ///
public NetEntity? Target;
+
+ ///
+ /// If the client wants to continuously shoot.
+ /// If true, the gun will continue firing until a stop event is sent from the client.
+ ///
+ public bool Continuous;
}
diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
index 192d5f57ad4..3408fcb78eb 100644
--- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
+++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
@@ -167,6 +167,8 @@ public abstract partial class SharedGunSystem : EntitySystem
gun.Comp.ShootCoordinates = GetCoordinates(msg.Coordinates);
gun.Comp.Target = GetEntity(msg.Target);
AttemptShoot(user.Value, gun);
+ if (msg.Continuous)
+ gun.Comp.ShotCounter = 0;
}
private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args)
diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
index 67ebeee5832..77ff2fb2103 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -109,6 +109,9 @@ ui-options-hud-layout = HUD layout:
## Controls menu
+ui-options-hold-to-attack-melee = Hold to attack (melee)
+ui-options-hold-to-attack-ranged = Hold to attack (ranged)
+
ui-options-binds-reset-all = Reset ALL keybinds
ui-options-binds-explanation = Click to change binding, right-click to clear
ui-options-unbound = Unbound