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.

StepMechanism
LT heldSLTags.Events.Weapon.SecondaryMode → activates GA_SidearmMode
While heldGA_SidearmMode applies SLTags.States.Weapon.SecondaryMode (ActivationOwnedTag)
Primary fireGA_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 releaseend 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 an ESLWeaponClass { 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 CarriedWeapons slot logic (unchanged).

#Inventory — Dedicated Sidearm Slot

New replicated reference, parallel to CarriedWeapons:

  • TObjectPtr<ASLWeaponActor> SidearmWeapon on ASLCharacterBase, ReplicatedUsing=OnRep_Sidearm.
  • Always non-null after spawn. Populated in LoadDefaultLoadout() from a DefaultSidearm on the
  • game mode / loadout (alongside the existing default loadout weapons).

  • DropWeapon / death-drop iterate CarriedWeapons only — 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 USLWeaponsComponent via an ASC tag callback on States.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_L or reuse
  • hand_l with an offset).


#Animation

#ABP Architecture

Each sidearm has two separate ABPs with distinct jobs — do not conflate them:

ABPSkeletonDrivesSet via
ABP_MC_FP_<Sidearm> / ABP_MC_TP_<Sidearm>Character skeletonCharacter's left arm poseAnimLayerClass on data asset mesh data
ABP_<Sidearm>_WeaponWeapon skeletonWeapon 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

PhaseMechanism
SecondaryMode entersDraw state in ALI_SL_Sidearm blends in. bIsSecondaryMode drives entry/exit.
While heldMain ABP holds the aimed idle via the linked sidearm layer.
RT firesSidearmState.bIsFiring drives Fire state in ABP_Pistol_Weapon and the character arm layer.
SecondaryMode exitsSidearm 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

StateMechanism
SidearmWeapon refReplicated UPROPERTY + OnRep_Sidearm (mirror OnRep_CarriedWeapons)
Secondary mode activeStates.Weapon.SecondaryMode GAS tag (replicates) → FSLAnimState.bIsSecondaryMode
Sidearm fireExisting fire pipeline (server RPC + trace), not AnimNotify
Sidearm mesh visibilityDriven locally off bIsSecondaryMode on all clients

#Build Order (proposed)

  1. C++ data + slot ✅ — bIsSidearm, SidearmDrawDuration on data asset; SidearmWeapon replicated slot;
  2. DefaultSidearmClass in loadout; FPSidearmMesh/TPSidearmMesh components; all sidearm tags; bIsSidearmActive + SidearmState.bIsFiring in BuildAnimSnapshots.

  1. GAS + input ✅ — GA_SidearmMode created, CDO configured, granted in ability set.
  2. Input wired (SidearmActionStartSidearmDraw/StopSidearmDraw). WeaponsComponent tag callback, socket attachment, ABP linking all done in UpdateSidearmMesh.

  1. Draw delay ✅ — SidearmDrawing tag + SidearmDrawDuration on data asset.
  2. BP graph applies SidearmDrawing on activate, removes after delay. Sidearm fire ability blocks on SidearmDrawing. See Docs/Risks.md RISK-001.

  1. ABP interfaces — create ALI_SL_Sidearm; create ABP_MC_FP_Pistol / ABP_MC_TP_Pistol
  2. implementing it; link into main FP + TP ABPs. Add Lowered layer to ALI_SL_Weapon; implement in AR + Shotgun ABPs.

  1. Animation content — per sidearm type: draw, idle, fire (FP + TP). Draw anim length
  2. should match SidearmDrawDuration on the data asset.

  1. HUD — sidearm ammo indicator (fixed, separate from USLAmmoStrip).
  1. 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)

  1. HUD treatment — third slot in USLAmmoStrip, or a separate always-on sidearm indicator?
  1. Movement while sidearm is up — any speed/handling change, or identical to primary?
  1. 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.