Weapons · Updated Jun 16, 2026

Weapons System

Weapons System

Each weapon in SystemLink is represented by two objects working together:

  • ASLWeaponActor — the persistent world actor. Holds a reference to its data asset and owns CurrentAmmo. Exists in the world at all times, hidden when carried and unequipped.
  • USLWeaponDataAsset — the config. Defines fire modes, meshes, montages, IK, sway, lag, and all per-weapon tuning.
No reload. SystemLink uses a single ammo pool per weapon — CurrentAmmo is the only counter, no reserve, no magazine. MaxAmmo is set deliberately high (e.g. AR = 600, Shotgun = 60) so reload is unnecessary. Pickups refill CurrentAmmo directly, clamped to MaxAmmo. Docs/WeaponReloadPipeline.md is preserved as a "what if" reference only — see the banner at the top of that file.

USLWeaponsComponent is the weapon "brain" on the character — it reads config from the actor's data asset and drives everything from equipping to shot validation.

See WeaponActor.md for the full weapon actor lifecycle, ammo model, and API.

Adding a new weapon? Follow NewWeaponChecklist.md — end-to-end steps for tags, meshes, sockets, data asset, abilities, cues, animations, HUD, and pickup. Assault Rifle is the canonical reference.


#Data Asset — USLWeaponDataAsset

One data asset per weapon (e.g. DA_AssaultRifle). Contains everything needed to equip and fire the weapon.

#First & Third Person Mesh Data — FSLWeaponSkeletalMeshData

Two instances on every data asset: FirstPersonSkeletalMeshData and ThirdPersonSkeletalMeshData.

FieldPurpose
WeaponMeshSkeletal mesh asset for this view
AttachSocketNameSocket on the character mesh to attach the weapon to (e.g. hand_r)
AnimLayerClassAnim layer linked into the character mesh ABP for arm poses and IK
WeaponMeshAnimClassStandalone ABP set directly on the weapon mesh (bolt cycling, fire anims). Set to null for static/no-anim weapons — the previous weapon's class is explicitly cleared on equip.
EquipMontageAnimation montage to play when this weapon is equipped (FP montage on FirstPersonSkeletalMeshData, TP montage on ThirdPersonSkeletalMeshData)
RightHandBoneNameReference bone for left-hand IK BoneSpace conversion (typically hand_r)
LeftHandIKEffectorSocketNameWeapon mesh socket used as the left-hand IK end-effector target
LeftHandIKJointTargetSocketNameOptional weapon mesh socket for the IK pole vector / joint target
LeftHandIKAlpha0–1 blend weight for left-hand IK

#Fire Modes — FSLWeaponFireMode

Two instances on every data asset: PrimaryFireMode and SecondaryFireMode. Both are the same struct.

FieldPurpose
bEnabledWhether this fire mode is active
FireTypeHitscan or Projectile
FireModeFullAuto or SingleShot
FireAbilityClassGAS ability class granted to the ASC when this weapon is equipped
RoundsPerMinuteFull-auto fire rate; drives the repeat timer interval in the ability
MaxAmmoMaximum ammo capacity for this fire mode. ASLWeaponActor initializes CurrentAmmo to this value on spawn.
BaseDamageDamage applied per shot (used by both authority paths)
MaxRangeHitscan trace length in Unreal units
AmmoDecrementEffectGAS UGameplayEffect applied per shot on authority to decrement USLAmmoAttributeSet.CurrentAmmo by 1. Leave unset for infinite-ammo fire modes.
MuzzleSocketNameFP weapon mesh socket used for muzzle location (cosmetics + projectile spawn)
ProjectileClassActor class to spawn (Projectile fire type only)
FireCueTagGameplay Cue tag executed on every shot to drive replicated cosmetics — muzzle flash, sound, shell eject. Must start with GameplayCues.. Each weapon/fire mode can specify a different tag for independent VFX/SFX.
RecoilPitchKickDegrees of upward camera kick per shot
RecoilYawRangeMax random yaw deviation per shot (±this value)
RecoilRecoverySpeedInterp speed at which accumulated recoil returns to zero (shared by camera rotation and viewmodel position)
RecoilMaxAccumulationMax pitch accumulation in degrees (prevents runaway recoil)
RecoilKickbackDistanceViewmodel kick distance in cm per shot — weapon moves away from the camera
RecoilLateralRangeMax random lateral viewmodel shift in cm per shot (±this value)

#Equip Fields

FieldPurpose
EquipAbilityClassPer-weapon equip ability class (Server Initiated). Granted and activated by SetPendingEquipWeapon on the server. The trigger tag is read directly from its CDO — no separate tag field needed. Revoked on the next equip or unequip.
EquipCueTagGameplay Cue tag executed on equip to drive replicated cosmetics (holster sounds, attachment clanks). Must start with GameplayCues..

See WeaponEquip.md for the full equip flow, replication model, and step-by-step Blueprint implementation.

#Other Data Asset Fields

FieldPurpose
SwaySettingsPer-weapon sway tuning (idle/movement sway curves)
LagSettingsPer-weapon lag tuning (look-input rotational lag)
FPWeaponBlockingTraceDistanceForward trace distance for wall-proximity viewmodel pull-back
PrimaryReticleClassHUD reticle widget class shown in primary fire mode
SecondaryReticleClassHUD reticle widget class shown in secondary fire mode
AmmoWidgetClassHUD ammo display widget class shown when this weapon is equipped
SpreadSettingsMovement-based crosshair spread tuning (shared across fire modes)
WeaponIconMaterialOptional UMaterialInterface* used to tint or stylize the weapon's HUD icon at runtime

#Weapons Component — USLWeaponsComponent

Attached to ASLCharacterBase. Replicated. Does not tick by default — tick is enabled only when recoil recovery is active.

#Responsibilities

  • Holds FSLWeaponViewDriverFP and FSLWeaponViewDriverTP for per-view attachment and anim class linking
  • Manages FP/TP mesh attachment, anim layer linking, and equip/unequip state
  • Grants and revokes fire abilities on the ASC when the weapon changes
  • Initializes USLAmmoAttributeSet from the weapon actor on equip; writes ammo back on unequip
  • Broadcasts OnAmmoChanged(CurrentAmmo, MaxAmmo) to the HUD whenever ammo changes
  • Drives recoil — applies kick on fire, recovers toward zero each tick
  • Accumulates client-predicted shots and flushes them to the server for validation
  • Exposes sway, lag, IK, and obstruction calculations for animation blueprints
  • Broadcasts OnEquippedWeaponChanged to the HUD for reticle swaps
  • Calls OnAttachedToCharacter / OnDetachedFromCharacter on the weapon actor for per-weapon visual setup

#Initialization

Called by the character in BeginPlay:


WeaponsComponent->InitializeWeaponMeshes(TPCharMesh, FPCharMesh, TPWeaponMesh, FPWeaponMesh);


#HUD Integration

The weapons component exposes BlueprintAssignable delegates that drive all weapon-related HUD updates. Bind them in WBP_SL_HUDWidget (the USLHUDWidget BP subclass) — that is where the live weapon/reticle bindings actually are. Note BP_SL_PlayerHUD is the AHUD actor (ASLPlayerHUD); it only creates the widget and does NOT hold these bindings.

#Reticle

OnEquippedWeaponChanged(ASLWeaponActor* NewWeapon) — fires on all clients when the equipped weapon changes.

Bind it to call HUDWidget → SwapReticle(NewWeapon → WeaponData → PrimaryReticleClass, PC). The reticle lives in ReticleContainer (center-screen overlay in WBP_SL_HUD) and is an instance of USLReticle. Drive it each tick via:

  • OnSpreadChanged(float NewSpread) — fed from WeaponsComponent → GetCurrentSpread(PlayerSpeed)
  • OnTargetDetected(bool bIsHostile) — fed from WeaponsComponent → CheckForEnemyTarget(...)

#Ammo Display — Multi-Slot Strip

The HUD shows a stacked weapon-slot strip (Halo 3 style): the equipped weapon is on top (full size, full opacity), secondary weapons below (smaller, dimmed). All carried weapons show their live ammo counts simultaneously.

Class hierarchy:

  • USLAmmoWidget (C++) — base class for the per-weapon ammo display. One BlueprintImplementableEvent:
    • OnAmmoChanged(int32 CurrentAmmo, int32 MaxAmmo) — update counter text, drive low-ammo feedback
  • WBP_SL_Ammo_<WeaponName> (Blueprint) — per-weapon visual implementation; set on DA_SL_<WeaponName>.AmmoWidgetClass
  • USLAmmoStrip (C++) — base class for the strip container. Manages slot lifecycle, delegate binding, and RebuildSlots. BP subclass implements two hooks:
    • CreateSlotWidget(WeaponActor, bIsActive) → UUserWidget* — create and return a slot widget (typically an USLAmmoWidget subclass via AmmoWidgetClass; call SetWeapon(WeaponActor) to subscribe it to ammo updates)
    • SetSlotActive(SlotWidget, bIsActive) — drive scale/opacity/animation per slot state
  • WBP_SL_AmmoStrip (Blueprint) — subclass of USLAmmoStrip; implements the two BIE hooks above

Initialization: ASLPlayerCharacter::InitializeLocalPlayerHUD calls HUDWidget→InitializeAmmoStrip(Character) on every possession. The strip auto-binds to OnCarriedWeaponsChanged (rebuild on inventory change) and OnEquippedWeaponChanged (rebuild with new weapon on top). No wiring needed in BindWeaponDelegates.

Live ammo updates: Each slot's USLAmmoWidget is bound directly to its ASLWeaponActor::OnAmmoChanged delegate via SetWeapon(WeaponActor). Shots decrement via PostGameplayEffectExecute mirror writes; ammo pickups call WeaponActor::SetCurrentAmmo directly — both broadcast the delegate, both update the strip immediately.

#Timed Notifications

USLHUDWidget::ShowTimedNotification(WidgetClass, Data, Duration) places a transient notification in NotificationContainer and auto-dismisses it after Duration seconds (default 3.0). Any previously active notification is replaced immediately. Use this for ammo pickup feedback, weapon pickup confirmations, and any other short-lived HUD messages.

FSLNotificationData carries: Message, SubMessage, WeaponData, Icon, and Sound. Build one with USL_BlueprintLibrary::MakeNotificationDataFromWeapon(WeaponData, Message, SubMessage) — it auto-fills Icon from WeaponData->WeaponIcon and defaults SubMessage to the weapon display name if left empty.

Ammo pickup notifications route through Client_ShowAmmoPickupNotification(WeaponData) on ASLCharacterBase (server → owning client RPC) → OnAmmoPickupNotification(WeaponData) BlueprintImplementableEvent on the character → character BP calls ShowTimedNotification.


#Equip Flow

RequestEquip(WeaponActor) is called on the server with an ASLWeaponActor reference. SetPendingEquipWeapon finalizes all state immediately — no coordinator ability, no anim-notify dependency. EquippedWeapon on USLWeaponsComponent (now an ASLWeaponActor*) is the replicated source of truth for all machines including bots. The per-weapon equip ability (Server Initiated) handles animation only.

See WeaponEquip.md for the full flow, replication model, and Blueprint implementation guide.


#World Pickups

See WeaponPickup.md for the full pickup system design, prompt flow, and editor tasks.

Two paths on overlap:

  • Player already carries this weapon type — ammo tops up silently via USLWeaponsComponent::AddAmmo, pickup destroys itself
  • New weapon — prompt shown to owning client via Client_ShowPickupPrompt → OnShowPickupPrompt BP event; player presses interact to call Server_AcceptPickup, which grants the weapon and destroys the pickup

#Fire Flow

#Authority path (listen server host)


Input → SLTags::Events::Weapon::PrimaryFire sent to ASC

  └── GA_PrimaryFire activates (InstancedPerActor)

        └── CommitAbility (applies SLTags::States::Weapon::Firing)

        └── ExecuteFire()

              └── Gets camera view point and fire direction

              └── Calls OnLocallyPredictedShotFired(MuzzleLoc, FireDir) — BP event for local cosmetics (FP muzzle flash, sound, TP cosmetics for the shooting player)

              └── Calls USLWeaponsComponent::ApplyRecoilKick

              └── Calls ASC::ExecuteGameplayCue(FireMode.FireCueTag, CueParams) — replicated to all clients

              └── Line traces or spawns projectile, applies damage directly

        └── FullAuto: sets timer at RPM interval, repeats ExecuteFire()

        └── SingleShot: ends ability immediately

#Remote client path


Input → SLTags::Events::Weapon::PrimaryFire sent to ASC (locally predicted)

  └── GA_PrimaryFire activates

        └── ExecuteFire()

              └── Calls OnLocallyPredictedShotFired — local FP + TP cosmetics for the shooting player

              └── Calls ApplyRecoilKick

              └── Prediction trace: if hit → calls OnProjectileHitPredicted(Hit) for immediate impact FX

              └── Collects FSLShotInfo per pellet → after the pellet loop, calls USLWeaponsComponent::QueueShotsForValidation(PendingPellets)

                    └── Buffered until MaxUnvalidatedShots threshold or ShotValidationInterval timer

                    └── Flushes via Server_ProcessShots RPC (single RPC per trigger pull, even for shotgun blasts)

                          └── Server re-traces, applies damage, calls Multicast_OnShotsConfirmed

                                └── All clients receive OnShotsConfirmed — play confirmed impact VFX/audio


#Gameplay Cues

Gameplay Cues drive replicated cosmetics — effects that all clients need to see (TP muzzle flash, fire sound for remote players, shell ejection, etc.). Local cosmetics for the shooting player go in OnLocallyPredictedShotFired instead.

#Fire Cue Pattern

  1. Each fire mode on the weapon data asset has a FireCueTag (FGameplayTag, filtered to GameplayCues.*)
  1. ExecuteFire calls ASC::ExecuteGameplayCue(FireMode.FireCueTag, CueParams) on authority only
  1. GAS replicates the cue execution to all clients
  1. The matching GC_ Blueprint (GameplayCueNotify_Burst) handles muzzle flash, sound, and any other per-shot effects
  1. CueParams.Location = muzzle world position, CueParams.Normal = fire direction

#Equip Cue Pattern

  1. The weapon data asset has an EquipCueTag (FGameplayTag, filtered to GameplayCues.*)
  1. The per-weapon equip ability Blueprint calls ExecuteEquipCue(WeaponData) (provided by USLGameplayAbility_Equip) at the desired point in the animation
  1. GAS replicates the cue to all clients
  1. The matching GC_ Blueprint (GameplayCueNotify_Burst) handles equip sounds and any visual effects

#Adding a new weapon's fire cue

  1. In Project Settings → GameplayTags, add the tag (e.g. GameplayCues.Weapon.AssaultRifle.PrimaryFire)
  1. Create a GameplayCueNotify_Burst Blueprint in Content/SystemLink/AbilitySystem/Cues/Weapons/<WeaponName>/
  1. Set its Gameplay Cue Tag to the new tag
  1. Wire up Burst Particles and Burst Sounds — see the Authoring a Fire Cue section below for the required local-player guard
  1. Open the weapon's data asset and set PrimaryFireMode.FireCueTag to the same tag

#Authoring a Fire Cue (hard rule)

ASC->ExecuteGameplayCue multicasts to every client including the shooter. The shooter already played their FP muzzle flash and fire sound inside OnLocallyPredictedShotFired (zero-latency, locally predicted). If the GC blueprint plays those effects again on the local player, every shot double-plays.

Every fire GC_ Blueprint must early-return when the instigator is locally controlled. First thing inside OnExecute / OnBurst:


Event OnExecute (Cue Parameters)

│

├── Instigator = Cue Parameters → Instigator (cast to Pawn)

├── [IsValid(Instigator) && Instigator->IsLocallyControlled()] → Return

│

└── (continue with muzzle flash, fire sound, shell eject…)

This guard does not apply to ImpactCueTag blueprints — impacts are different. On the owning client, OnProjectileHitPredicted runs ONLY when the local trace confirms a hit; the server cue path then covers everyone else. The two paths don't overlap on the same impact for the same client. (If you find them overlapping, that's a bug in the GC blueprint, not the dispatch.)

The same hard rule applies if you add a StopFireCueTag blueprint — the local player already handled "stopped firing" feedback inline.

#Adding a new weapon's equip cue

  1. In Project Settings → GameplayTags, add the tag (e.g. GameplayCues.Weapon.AssaultRifle.Equip)
  1. Create a GameplayCueNotify_Burst Blueprint in Content/SystemLink/AbilitySystem/Cues/Weapons/<WeaponName>/
  1. Set its Gameplay Cue Tag to the new tag
  1. Open the weapon's data asset and set EquipCueTag to the same tag

The GAS Cue Manager scans Content/SystemLink/AbilitySystem/Cues/ recursively (configured in Config/DefaultGame.ini under AbilitySystemGlobals.GameplayCueNotifyPaths) and registers all GC_ assets automatically by tag — no manual registration needed. Subdirectories are fine.


#Shot Validation

Client-predicted shots are queued in PendingShotHistory and flushed to the server via Server_ProcessShots. The server re-traces each shot and applies damage if the trace confirms a hit. A sphere sweep fallback (HitValidationSweepRadius) provides latency forgiveness. Confirmed hits are broadcast to all clients via Multicast_OnShotsConfirmedOnShotsConfirmed BP event for impact VFX.

PropertyPurpose
MaxUnvalidatedShotsHow many shots to accumulate before flushing (default 1)
ShotValidationIntervalSafety-net flush timer in seconds (default 0.15)
HitValidationSweepRadiusSphere sweep fallback radius in UU (0 = strict line trace only)

#Recoil

ApplyRecoilKick is called per shot in ExecuteFire. It adds pitch and random yaw to AppliedRecoil (capped by RecoilMaxAccumulation) and feeds it to the player controller's input via AddPitchInput / AddYawInput. The component ticks while recoil is non-zero and interpolates AppliedRecoil back to zero at RecoilRecoverySpeed. GetRecoilMagnitude() is available to animation blueprints for TP additive poses.


#IK

Left-hand IK targets are calculated by USLWeaponsComponent and read each tick by the animation blueprints:

  • CalculateLeftHandIKTargets_FP() — first-person arm IK
  • CalculateLeftHandIKTargets_TP() — third-person arm IK

Both use the weapon mesh sockets defined in FSLWeaponSkeletalMeshData (LeftHandIKEffectorSocketName, LeftHandIKJointTargetSocketName). SetLeftHandIKOverride / ClearLeftHandIKOverride allow abilities (e.g. equip) to temporarily override the IK target.


#Sway & Lag

Called by animation blueprints each frame:

  • CalculateWeaponSwayTransform(LookX, LookY, DeltaTime, PlayerSpeed) — idle and movement sway driven by USLWeaponSwaySettings
  • CalculateWeaponLag(LookYawRate, LookPitchRate, DeltaTime, AdsAlpha) — rotational lag driven by USLWeaponLagSettings

Both return transforms/rotators that animation blueprints apply as additive offsets to the weapon mesh.


#View Drivers

FSLWeaponViewDriverFP and FSLWeaponViewDriverTP are internal structs owned by USLWeaponsComponent. They handle per-view weapon attachment (EquipWeapon / UnequipWeapon), anim class layer linking, and FP obstruction state (bIsFPWeaponObstructed, FPWeaponObstructionAlpha). Animation blueprints read obstruction state via AnimStateSnapshot.bIsFPWeaponObstructed and AnimStateSnapshot.FPWeaponObstructionAlpha on the character — these are populated each tick by ASLPlayerCharacter::CalculateFirstPersonWeaponBlocking. The legacy accessors IsFPWeaponObstructed() / FPWeaponObstructionAlpha() on the component are deprecated.