diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
index 41fac83c598..72df18390f2 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
@@ -15,8 +15,8 @@
-
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index 40d1836c08a..f1041eb4166 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 2814cef6506..e01c1e10998 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 e79b26f89dd..f5ccb4ae51c 100644
--- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
+++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
@@ -158,6 +158,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 7c13f019520..489a346f2a4 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -108,6 +108,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