Weapons · Updated Jun 16, 2026
Secondary Weapon — Left-Hand Sidearm
Secondary Weapon — Left-Hand Sidearm
A dedicated sidearm (pistol) drawn in the left hand via a hold-to-aim chord. Replaces the older "secondary fire mode" alt-fire concept. The player always has a sidearm — it lives in its own dedicated slot, separate from the two main weapon slots (CarriedWeapons).
#Concept / Player Experience
- Pull and hold LT → the right-hand (primary) weapon lowers, a pistol is raised in the left hand.
- While LT is held, pull RT → fire the sidearm.
- Release LT → holster the pistol, raise the primary back up.
It is a "tactical sidearm draw," not dual-wield-while-running. The primary stays equipped (just lowered cosmetically) so re-raising is instant and loses no state.
#Sidearm Slot Rules (from design)
- Dedicated slot, separate from
CarriedWeapons(the 2 main slots). One sidearm at a time.
- Always populated — the player can never be without a sidearm. Spawn loadout includes a default.
- Swappable — picking up a sidearm-class weapon replaces the current one; the **replaced sidearm
drops as a world pickup** (others can grab it), same as primary-weapon swaps.
- Not droppable / not throwable — there is no "drop sidearm" input. You only change it by picking
up a replacement (which drops the old one).
#Input + GAS Chord (backbone — unchanged from prior design)
All driven by GAS tags; no input branching logic.
| Step | Mechanism |
|---|---|
| LT held | SLTags.Events.Weapon.SecondaryMode → activates GA_SidearmMode |
| While held | GA_SidearmMode applies SLTags.States.Weapon.SecondaryMode (ActivationOwnedTag) |
| Primary fire | GA_PrimaryFire gets ActivationBlockedTags = States.Weapon.SecondaryMode — can't fire primary while pistol is up |
| RT (sidearm fire) | Sidearm fire ability gets ActivationRequiredTags = States.Weapon.SecondaryMode + the same Events.Weapon.PrimaryFire trigger as the primary — RT fires whichever is allowed by the current mode |
| LT release | end GA_SidearmMode → tag removed → holster pistol, raise primary |
States.Weapon.SecondaryMode is replicated (like States.Character.Meleeing), so the TP pose ("pistol out, primary lowered") shows for all clients automatically — drive it from a FSLAnimState.bIsSecondaryMode snapshot bool, same pattern as bIsMeleeing.
Lesson carried from melee: do NOT drive sidearm-fire damage from an AnimNotify. Reuse the
existing fire pipeline (USLGameplayAbility_Fire), which is RPC/trace-based and server-reliable.
#Weapon Classification
Add to USLWeaponDataAsset:
bool bIsSidearm(or anESLWeaponClass { Primary, Sidearm }enum) — marks a weapon as
sidearm-class. bIsSidearm means "authored to work in the left hand (one-handed grip)," not "is a pistol." The fire behavior is irrelevant to the slot — the existing fire pipeline already handles hitscan, projectile, and multi-pellet, so any one-handed weapon works regardless of how it fires. A pistol (hitscan), plasma pistol (projectile), or throwing knives (projectile + throw anim) are all valid sidearms. The only true constraint is the one-handed left-hand animation grip.
Pickup logic (ASLWeaponPickup / USLWeaponsComponent) routes by class:
- Sidearm-class pickup → swaps the sidearm slot (old sidearm drops as a pickup).
- Primary-class pickup → existing
CarriedWeaponsslot logic (unchanged).
#Inventory — Dedicated Sidearm Slot
New replicated reference, parallel to CarriedWeapons:
TObjectPtr<ASLWeaponActor> SidearmWeapononASLCharacterBase,ReplicatedUsing=OnRep_Sidearm.
- Always non-null after spawn. Populated in
LoadDefaultLoadout()from aDefaultSidearmon the
game mode / loadout (alongside the existing default loadout weapons).
DropWeapon/ death-drop iterateCarriedWeaponsonly — the sidearm is excluded (non-droppable).
- Handle persistence: like all ability handles, any sidearm equip/fire ability handles live on
ASLPlayerState (survives pawn destruction on respawn). See feedback_ability_handles_playerstate.
#Meshes
Currently ASLCharacterBase has FPWeaponMesh / TPWeaponMesh for the right-hand weapon. Add:
FPSidearmMesh(left-hand FP) +TPSidearmMesh(left-hand TP) skeletal mesh components.
- Hidden by default; shown/hidden by
USLWeaponsComponentvia an ASC tag callback onStates.Weapon.SecondaryMode(fires on all clients — tag is replicated).
- Mesh assets come from the sidearm's
USLWeaponDataAsset(FirstPersonSkeletalMeshData/
ThirdPersonSkeletalMeshData) — reuse the existing per-view mesh data, just attach to left-hand sockets.
- New sockets on the FP arms + TP body skeletons for the left-hand grip (
Sidearm_Lor reuse
hand_l with an offset).
#Animation
#ABP Architecture
Each sidearm has two separate ABPs with distinct jobs — do not conflate them:
| ABP | Skeleton | Drives | Set via |
|---|---|---|---|
ABP_MC_FP_<Sidearm> / ABP_MC_TP_<Sidearm> | Character skeleton | Character's left arm pose | AnimLayerClass on data asset mesh data |
ABP_<Sidearm>_Weapon | Weapon skeleton | Weapon mesh animations (idle, fire/recoil) | WeaponMeshAnimClass on data asset mesh data |
ALI_SL_Sidearm is implemented by the character arm layer ABPs (ABP_MC_FP/TP_<Sidearm>), NOT the weapon mesh ABP. It is linked into FPMesh/TPMesh via LinkAnimClassLayers from OnRep_Sidearm — same pattern as ALI_SL_Weapon is linked on primary weapon equip. The weapon mesh ABP (ABP_<Sidearm>_Weapon) is set directly on FPSidearmMesh/TPSidearmMesh and knows nothing about the interface.
Character meshes (FPMesh / TPMesh)
ALI_SL_Weapon -> ABP_MC_FP_AR / ABP_MC_TP_AR / ... (right arm pose, + new Lowered layer)
ALI_SL_Sidearm -> ABP_MC_FP_Pistol / ABP_MC_TP_Pistol / ... (left arm pose)
Sidearm mesh components (FPSidearmMesh / TPSidearmMesh)
WeaponMeshAnimClass -> ABP_Pistol_Weapon / ABP_ThrowingKnife_Weapon / ... (weapon mesh anims)
ALI_SL_Weapon gets a new Lowered layer — each primary weapon character ABP implements the pose its right arm takes while the sidearm is drawn. AR lowered looks different from Shotgun lowered.
#Sidearm Types
Adding a new sidearm type requires two new ABPs and zero main ABP changes:
- Character arm layer (implements
ALI_SL_Sidearm):ABP_MC_FP_Pistol+ABP_MC_TP_Pistol
- Weapon mesh ABP (does NOT implement the interface):
ABP_Pistol_Weapon
Same pattern for throwing knife: ABP_MC_FP_ThrowingKnife + ABP_ThrowingKnife_Weapon.
#Per-Frame Flow
| Phase | Mechanism |
|---|---|
| SecondaryMode enters | Draw state in ALI_SL_Sidearm blends in. bIsSecondaryMode drives entry/exit. |
| While held | Main ABP holds the aimed idle via the linked sidearm layer. |
| RT fires | SidearmState.bIsFiring drives Fire state in ABP_Pistol_Weapon and the character arm layer. |
| SecondaryMode exits | Sidearm layer blends out. Primary arm returns to normal. |
#Slot Count
Single sidearm slot. The player commits to a sidearm via loadout or world pickup — no mid-combat cycle. Revisit if testing reveals it feels limiting.
#Anim State
Sidearm-specific data lives in a nested FSLSidearmAnimState struct embedded in FSLAnimState as SidearmState. This mirrors the FSLLeftHandIKTargets pattern — data scoped to a specific layer gets its own struct rather than flat fields on the top-level snapshot.
FSLAnimState
bIsSecondaryMode <- top-level; both ALI_SL_Weapon (Lowered) and ALI_SL_Sidearm read this
SidearmState (FSLSidearmAnimState)
bIsFiring <- sourced from States.Weapon.Firing tag (safe reuse — primary fire
is blocked by SecondaryMode, so Firing can only mean sidearm fire
in this context)
BuildAnimSnapshots() populates it from GAS tags — abilities never touch the anim state directly. The ALI_SL_Sidearm ABP reads AnimStateSnapshot.SidearmState.*. Future per-sidearm-type state (e.g. bIsCharging for a throwing knife charge) extends the struct without polluting the top level.
This is the largest new-animation feature so far — flag it for scheduling.
#Ammo + HUD
- Sidearm has its own ammo pool — it's a normal weapon actor with
CurrentAmmo(existing
per-weapon ammo system). No new ammo infrastructure.
- HUD: decide whether the sidearm shows as a third entry in
USLAmmoStrip, or a separate dedicated
sidearm indicator (it's always present, so a fixed indicator may read better than a slot in the cycle strip). See Open Questions.
#Replication Summary
| State | Mechanism |
|---|---|
SidearmWeapon ref | Replicated UPROPERTY + OnRep_Sidearm (mirror OnRep_CarriedWeapons) |
| Secondary mode active | States.Weapon.SecondaryMode GAS tag (replicates) → FSLAnimState.bIsSecondaryMode |
| Sidearm fire | Existing fire pipeline (server RPC + trace), not AnimNotify |
| Sidearm mesh visibility | Driven locally off bIsSecondaryMode on all clients |
#Build Order (proposed)
- C++ data + slot ✅ —
bIsSidearm,SidearmDrawDurationon data asset;SidearmWeaponreplicated slot;
DefaultSidearmClass in loadout; FPSidearmMesh/TPSidearmMesh components; all sidearm tags; bIsSidearmActive + SidearmState.bIsFiring in BuildAnimSnapshots.
- GAS + input ✅ —
GA_SidearmModecreated, CDO configured, granted in ability set.
Input wired (SidearmAction → StartSidearmDraw/StopSidearmDraw). WeaponsComponent tag callback, socket attachment, ABP linking all done in UpdateSidearmMesh.
- Draw delay ✅ —
SidearmDrawingtag +SidearmDrawDurationon data asset.
BP graph applies SidearmDrawing on activate, removes after delay. Sidearm fire ability blocks on SidearmDrawing. See Docs/Risks.md RISK-001.
- ABP interfaces — create
ALI_SL_Sidearm; createABP_MC_FP_Pistol/ABP_MC_TP_Pistol
implementing it; link into main FP + TP ABPs. Add Lowered layer to ALI_SL_Weapon; implement in AR + Shotgun ABPs.
- Animation content — per sidearm type: draw, idle, fire (FP + TP). Draw anim length
should match SidearmDrawDuration on the data asset.
- HUD — sidearm ammo indicator (fixed, separate from
USLAmmoStrip).
- Content — pistol fire cue. Throwing knife data asset/throw cue.
#Resolved Decisions
- Replaced sidearm on swap → drops as a world pickup (others can grab it), like primary swaps.
- Sidearm weapon variety → any
bIsSidearm-flagged one-handed weapon, regardless of fire
type. Constraint is the left-hand animation grip, not the weapon being a pistol. Pistol, plasma pistol, throwing knives (projectile) are all valid. Reuses the existing fire pipeline as-is.
- Single sidearm slot → player commits at loadout or world pickup. No mid-combat cycle.
- ABP split → two ABPs per sidearm type: character arm layer (implements
ALI_SL_Sidearm) +
weapon mesh ABP (does not implement the interface). Do not conflate them.
#Open Questions (defer to implementation)
- HUD treatment — third slot in
USLAmmoStrip, or a separate always-on sidearm indicator?
- Movement while sidearm is up — any speed/handling change, or identical to primary?
- Reload — does the sidearm reload independently, and can you reload it while in secondary mode?
#Relationship to Prior Design
Supersedes the "Secondary Fire Mode" entry in the Secondary Fire Design memory (alt-fire on the same weapon). The input chord and GAS-tag structure are reused verbatim; the mechanic changes from "alt-fire" to "left-hand sidearm draw". Update that memory when this lands.