Weapons · Updated Jun 16, 2026
Weapon Action Lock — States.Weapon.Busy
Weapon Action Lock — States.Weapon.Busy
A single gameplay-tag lock that blocks weapon actions while an exclusive action is in progress. GAS-native (no custom arbitration code): exclusive actions apply Busy; everything blocks on Busy. Switching also blocks on Firing.
#The tag
SLTags.States.Weapon.Busy — native, declared in SLTags.h/.cpp (same family as Firing, SidearmActive, SidearmDrawing). Added 2026-06-13.
#Wiring matrix (ability CDOs)
| Ability | ActivationOwnedTags | ActivationBlockedTags |
|---|---|---|
Fire — PrimaryFireBase + AR/Pistol/Shotgun/Placeholder | Firing | Busy (+ Dead, Meleeing, [SidearmActive on AR/Shotgun]) |
BP_GA_SL_SidearmMode (draw/hold) | SidearmActive | Busy, RefireLock (+ Dead, Meleeing) |
BP_GA_SL_Melee | Meleeing, Busy | Busy (+ Dead, Meleeing) |
| Equip FP/TP (AR/Shotgun/Placeholder) | Busy (+ Equipping on TP) | RefireLock (+ Dead, [Equipping on TP]) |
SidearmModeand equip block onRefireLock, notFiring(see "Post-fire switch lock" below).
| Draw-delay / Holster / Grenade / Reload (when built) | Busy | Busy, Firing |
|---|
Notes:
- Fire blocks on
Busyvia inheritance for Pistol & Placeholder (they don't override
PrimaryFireBase's blocked list); AR & Shotgun override, so Busy was added explicitly.
- Fire does NOT own
Busy— it has its ownFiringtag, so auto-fire never self-blocks.
- Equip owns
Busybut does NOT block onBusy. Reason: a single weapon equip grants/runs
both an FP and a TP equip ability (EquipAbilityClass + FPEquipAbilityClass, granted in USLWeaponsComponent). If both blocked on Busy, whichever activated first would set Busy and cancel the other → broken equip. Owning Busy is concurrency-safe (additive); only blocking on it is the hazard. Equip blocks Firing (no swap mid-fire) which is the goal.
SidearmModedoes NOT ownBusy— it's a hold-state (LT held = sidearm out), not a
transient action. Owning Busy would block pistol fire while LT is held. The future draw delay (a transient) is what should own Busy.
#What it delivers
- No fire / melee / draw during an equip or melee.
- No drawing the sidearm or swapping weapons while firing (
SidearmMode+ equips block
Firing) — fixes the "switch to pistol mid-AR-burst → pistol full-autos" path.
- Pistol still fires while LT held.
- Grenade (when built) applies
Busy→ locks all weapon actions during the throw.
#Post-fire switch lock — RefireLock (split from Firing)
Firing (the fire ability's ActivationOwnedTag) does three jobs, all tied to PostFireDelay: drives the recovery/pump animation (bIsFiring), gates re-fire (the ability stays alive), and — until this change — blocked switching. To let the player switch to the sidearm (or cycle) before a single-shot weapon can re-fire, the switch-block was split off onto its own tag:
States.Weapon.RefireLock— applied byUSLGameplayAbility_Firewhile firing.SidearmModeand
the equip abilities block on RefireLock (not Firing).
FSLWeaponFireMode::RefireLockDuration— seconds the lock is held after a SingleShot.<= 0
(default) = full PostFireDelay (legacy: switch unlocks with re-fire). Set shorter to allow an early switch (e.g. interrupt a shotgun pump with a quick pistol draw). FullAuto holds it for the whole burst.
Firingis unchanged — animation and re-fire gating still run for the fullPostFireDelay.
Implemented as a loose tag (not ActivationOwnedTags) so it can be released early; fire is LocalPredicted, so it's applied on the owning client + server (the only machines that gate switching) — no replication needed. Cleared in EndAbility (idempotent with the early-release timer).
#Weapon swap while sidearm active — guarded in C++, NOT via Busy
Cycling/picking up a main weapon while the sidearm is drawn is blocked in ASLCharacterBase::RequestEquip_Implementation (early-return on WeaponsComponent->IsSidearmActive()), which is the authority-side chokepoint for both Server_CycleWeapon and pickup auto-equip.
Why not a gameplay tag? Weapon swapping is not gated by an ability activation. RequestEquip does the real state change (FinalizeEquip — swaps EquippedWeapon, grants fire abilities, replicates) directly in C++, then separately fires the equip event for the animation. An ActivationBlockedTags/Busy entry on the equip ability would only block the animation, leaving the actual swap to proceed → mismatched state (new main weapon equipped underneath the pistol, wrong weapon on holster). So the guard must be in the swap path, not on the ability.
Consequence: picking up a weapon while the sidearm is out adds it to inventory but does not auto-equip it — holster first. Spawn/respawn first-equip is unaffected (sidearm isn't active then).
#Caveats / open items
Firingmust be held across the whole burst. If the auto-fire ability re-activates per
shot rather than running one activation with an internal loop, Firing pulses and a draw/swap could slip through a between-shots gap. Verify in PIE.
- Equip-during-draw/grenade is currently allowed (equip doesn't block
Busy). If FP/TP
equip are confirmed to never overlap, Busy can be re-added to equip's blocked tags for stricter behavior.
CancelAbilitiesWithTag/ActivationBlockedTags/BlockAbilitiesWithTagonly block new
activations — they do not cancel a running ability. To interrupt a running action (e.g. grenade cancelling auto-fire), cancel it explicitly. See feedback_gas_networking_patterns.
#Editing these via the MCP bridge
GameplayTagContainer.gameplay_tags is read-only from Python, and string→FGameplayTag coercion fails. To mint a registered tag: build a container with import_text( '(GameplayTags=((TagName="...")))') then break_gameplay_tag_container. Add/remove with GameplayTagLibrary.add_gameplay_tag / remove_gameplay_tag (return a new container), then set_editor_property on the CDO → compile_blueprint → save_asset.