Skip to content

Melee Drive · Guns · Singularity (Lyra / Plugify)

Status: DEFERRED · 2026-10-26 — resume after own Melee Drive foundation ships · tag-gate → GAS map added 2026-11-05
Authority: FS-CMB-002…006 · GDD One Flow · two verbs · singularity design
Host: Lyra 5.8 · Plugify: Melee Drive → TFAMelee; Flow+Singularity → TFASingularity; guns → TFAWeapons; camera/stance/ASC host → TFACombat spine — do not fork LyraGame (02-gfp-cookbook.md Plugify lock)

This file is self-contained. Implement from Bible + FS locks here — no external video or third-party action-system follow-along required.
Provenance (optional): tag-gate / action-DA patterns absorbed from a C++ Action System reference (Game Code Mastery); mapped onto Lyra GAS under TF Plugify. Do not add UACSAction / UACSActionComponent.

Park order: (1) Studio Melee Drive foundation → (2) resume this file (Motion Warp · ANS capsule/box · Counter · gun matrix). Flow / Singularity design stays locked; this is the parked implementer path.

Hit registration lock (2026-11-02): Hybrid auth FS-ARCH-013 · Demo = Anim Notify + reach capsule/box · Full polish = optional weapon-socket sweep (bUseSocketSweep). Spec: 2026-11-02-melee-hit-registration-design.md. ANS “box-trace” below = Demo capsule/box path — not range auto-connect.

Air Melee Drive lock (2026-11-02): Jump / double-jump air → Strike legal; reuse ground directional slash montages; disable root motion / Motion Warp while airborne. Downward slash = later art (not Demo exit). Spec: 2026-11-02-air-melee-drive-design.md · FS-CMB-002.

Motion Matching lock (2026-11-02): Demo + Full Pose Search: motion-matching-demo-and-full.md · kanban Demo demo-motion-matching · Full combat / melee. This Melee Drive doc stays montage + Motion Warp + notify hits for Melee Drive; Full strike choosers are §6 of the MM doc after Melee foundation.

TBD-056 — Flow toughness (no Block): Locked 2026-11-05 — Health-only dump. Shield/Armor chips that do not reduce Health do not dump Flow. Spec/FS-CMB-004.

Player fantasy: Terra Firma Dual Armory — Melee Drive and Precision Fire (guns) share one Kinetic Flow ring; Singularity is the climax; Ultimate stays separate. Includes Anim Notify hit detects and an Arkham-style Counter window with UI hooks.


0. Verification — generic melee pitfalls → TF locks

Section titled “0. Verification — generic melee pitfalls → TF locks”
Generic melee pitfall Terra Firma lock Adaptation
Patch host LyraGame.Build.cs / dump types in game module GFP packing FS-ARCH-010 / Plugify Melee types in TFAMelee; Flow/Singularity in TFASingularity; MotionWarping deps on the owning GFP
Custom freestyle action component as the combat runtime Lyra ASC + TFA naming Prefer UTFAMeleeDriveCombatComponent (helpers only), UTFAGameplayAbility_MeleeStrike, UTFAGameplayAbility_Counter — abilities are the verbs
Unbounded combo score FS-CMB-004 tiers Combo feeds Kinetic Flow tiers (0–15 / 15–30 / 30+ Singularity); Asymmetric credits (melee kill +2 · gun body +1 · headshot +2)
TakeDamage on hit Hybrid hit auth / GAS Apply Gameplay Effect via ASC (server validates); presentation may predict
Ambient magnet / Tick target pull FS-CMB-002 Warp only on explicit Attack press toward a scored target while grounded. Forbidden: ambient magnet · any root/warp while airborne
Counter missing / wrong warp target Extends Melee Drive Counter is melee verb; warp to ActiveAttackingEnemy; success ticks Flow +1 (or kill). Guns: §7
Guns on a second combo meter One Flow · two verbs §7 gun interaction matrix is required

0.5 Tag gates → GAS (Melee Drive foundation map)

Section titled “0.5 Tag gates → GAS (Melee Drive foundation map)”

Tag-gated start/stop, montage windows, and cancel rules are Gameplay Ability concerns on the hero ASC. UTFAMeleeDriveCombatComponent owns target pick, Counter window, and Flow credit helpers — not a second action runtime.

Pattern TF / Lyra GAS Bible / FS lock
Action data asset UGameplayAbility in TFAMelee (+ family DA for reach / montages) Plugify · FS-ARCH-010
Action component ASC on TFACombat spine + UTFAMeleeDriveCombatComponent GFP cookbook · no sibling #include
Action Tag Ability AbilityTags / InputTag via AbilitySet Lyra IMC
Granted active tags ActivationOwnedTags (or duration GE) e.g. State.Melee.Striking
Blocked tags ActivationBlockedTags e.g. ADS blocks Counter — §7
Cancel into CancelAbilitiesWithTag / cancel sets land mid-swing · stance switch
CanStart / tag satisfy CanActivateAbility + stance / grounded / Counter-window checks FS hybrid camera · FS-CMB-002
Start / Stop ActivateAbility / EndAbility server GE auth · FS-ARCH-013
Default / available actions Experience AbilitySet inject — not permanent LyraGame defaults Demo Experience
Play montage AbilityTask_PlayMontageAndWait + ANS hit window FS-ARCH-013 · FS-CMB-002
“Combo” / special-state attack Counter window + stance tags; Kinetic Flow is kill-tier meter, not an unbounded action combo score FS-CMB-004…005
Jump / dodge as actions Movement / parkour / Dodge abilities elsewhere — Melee Drive does not re-own Jump TFACombat / Carbon absorb
Kill → climax Message TFA.Combat.KillConfirmed (locked) → TFASingularity subscribes FS-CMB-005 · Plugify

Do not: ship a parallel action component next to ASC · Tick FindBestCombatTarget as a magnet · merge Ultimate into Flow · follow a Soulslike attribute/hitbox series as TF architecture (vitality, hit-reg, Firehand already locked elsewhere).

Pseudocode — tag gates (Strike vs Counter vs ADS)

Section titled “Pseudocode — tag gates (Strike vs Counter vs ADS)”
// On AbilitySet / GA defaults (editor or C++ ctor):
GA_MeleeStrike:
AbilityTags += Ability.Melee.Strike
ActivationOwnedTags += State.Melee.Striking
ActivationBlockedTags += State.Melee.Countering // optional mutex
// CancelAbilitiesWithTag as needed for soft cancels
GA_Counter:
AbilityTags += Ability.Melee.Counter
ActivationOwnedTags += State.Melee.Countering
ActivationBlockedTags += State.Weapon.ADS // §7 — suppress while ADS
ActivationRequiredTags+= State.Melee.CounterWindow // set when AI opens window
CanActivateAbility(GA):
if GA is Strike and owner is WallRunning → use grounded Melee Drive rules
if GA is Strike and airborne → ALLOW (same slash montage; no Motion Warp) // air lock
if GA is Counter and Melee Drive.bSuppressCounterWhileAiming and HasTag(ADS) → FAIL
if GA is Counter and not Melee Drive.bIsCounterWindowActive → FAIL
return Super / tag checks

Pseudocode — Activate Strike (details → §3–§4)

Section titled “Pseudocode — Activate Strike (details → §3–§4)”
ActivateAbility(MeleeStrike):
if not CommitAbility → EndAbility; return
ensure melee stance / TPP (hybrid camera message) // FS hybrid
Target = Melee Drive.FindBestCombatTarget(Range, Cone) // ONLY here — never Tick
if Target and Character is GROUNDED and MotionWarp:
Warp.AddOrUpdateWarpTarget(CombatTarget, offset toward Target)
else:
// airborne or no target: play slash in place — no warp / no root pull
PlayMontageAndWait(DirectionalSlash)
// ANS window → Melee Drive.ExecuteMeleeHitTrace → GE on authority (§3)
// confirmed kill → message KillConfirmed (do NOT AddFlowKillCredit only locally)
EndAbility:
clear warp target; clear State.Melee.Striking

Pseudocode — Flow credit / Singularity (details → §8)

Section titled “Pseudocode — Flow credit / Singularity (details → §8)”
// TFAMelee / TFAWeapons (server, after validated kill):
BroadcastMessage(TFA.Combat.KillConfirmed, {Instigator, Victim})
// TFASingularity (subscriber — no #include of Melee/Weapons):
OnKillConfirmed:
ComboScore += 1
Tier = tier_from(ComboScore) // 1: <15, 2: 15–29, 3: >=30
Broadcast FlowChanged for OmniVisor
if Tier == 3:
ActivateAbility(GA_TFA_SingularityTakedown) // auto, no player button
// on resolve → ResetFlow; Ultimate untouched (FS-CMB-006)
OnWhiffOrFailedCounter: // and TBD-056 Health-only damage-break
ResetFlow(+0.5s stagger)

Pseudocode — Experience grants (details → §6)

Section titled “Pseudocode — Experience grants (details → §6)”
// Demo / Ladder Experience Action Sets — not a parallel DefaultActions array
AbilitySet DAS_MeleeDriveCombat:
grant GA_MeleeStrike ← InputTag.Ability.Melee
grant GA_Counter ← InputTag.Ability.Counter
Hero components (Game Feature Action):
UTFAMeleeDriveCombatComponent
UMotionWarpingComponent
// Flow meter + Singularity GA live in TFASingularity GFP (message-driven)

Precision Fire (FPP) ──kill +1──┐
├──► Kinetic Flow ring ──► Overclocked ──► Singularity (TPP takedown)
Melee Drive (TPP) ──kill +1──┘
Counter (TPP) ──success +1 (or kill)─┘
Ultimate (Shiv) = separate meter — never merged with Flow
  • Melee Drive = Melee Drive: stick-aimed strike, optional Motion Warp to best cone target on Attack, reach capsule/box on Anim Notify (Demo); Full = Motion Matching clip choice + optional socket sweep — same notify auth.
  • Precision Fire = guns: manual aim, Firehand Bus; kills tick the same Flow.
  • Counter = timed intercept when an AI opens a window; Arkham prompt UI.
  • Singularity = auto at Flow Tier 3 — not a player button.

2. Module dependency (Plugify GFPs, not LyraGame)

Section titled “2. Module dependency (Plugify GFPs, not LyraGame)”

Do not replace LyraGame.Build.cs wholesale. Melee types live in TFAMelee.Build.cs; Flow/Singularity in TFASingularity.Build.cs; camera/stance/ASC host stays on TFACombat spine. Example (TFAMelee):

PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"GameplayAbilities",
"GameplayTags",
"GameplayTasks",
"MotionWarping", // Melee Drive strike / counter snap
"TFACore", // tags / messages / shared types
// LyraAbilitySystem / CommonGame as already required by your GFP
// Do NOT PublicDepend TFAWeapons or TFASingularity — Core messages only
});

Enable the Motion Warping plugin in the .uproject / experience enable list (same rule as other ADDON/vendor plugins — document in ch.01).


Owns: target pick, melee hit trace, Counter window helpers, interim Flow credit helpers (not Ultimate).

#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "TFAMeleeDriveCombatComponent.generated.h"
class UAbilitySystemComponent;
struct FGameplayEffectSpecHandle;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTFACounterWindowChanged, AActor*, AttackingEnemy);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTFAFlowChanged, int32, ComboScore, int32, FlowTier);
UCLASS(ClassGroup=(TFA), meta=(BlueprintSpawnableComponent))
class TFACOMBAT_API UTFAMeleeDriveCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UTFAMeleeDriveCombatComponent();
/** Best target in stick/facing cone — used on Attack press only (not ambient pull). */
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
AActor* FindBestCombatTarget(float MaxDistance, float MaxAngle) const;
/** Anim Notify → sweeping box along weapon sockets. Applies GAS GE on authority. */
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
void ExecuteMeleeHitTrace(FVector BoxHalfSize, FName SocketStart, FName SocketEnd,
TSubclassOf<UGameplayEffect> DamageEffectClass, float BaseDamageMagnitude);
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
void OpenCounterWindow(AActor* AttackingEnemy);
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
void CloseCounterWindow();
/** True if window open and enemy still valid — does not close window. */
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
bool AttemptCounter();
/** +1 Flow combo (melee kill, gun kill, or successful counter). May trip Singularity. */
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
void AddFlowKillCredit();
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive")
void ResetFlow(bool bApplyStagger);
UFUNCTION(BlueprintPure, Category = "TFA|MeleeDrive")
int32 GetCurrentCombo() const { return CurrentComboScore; }
UFUNCTION(BlueprintPure, Category = "TFA|MeleeDrive")
int32 GetFlowTier() const; // 1 / 2 / 3
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive")
FOnTFACounterWindowChanged OnCounterWindowOpened;
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive")
FOnTFACounterWindowChanged OnCounterWindowClosed;
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive")
FOnTFAFlowChanged OnFlowChanged;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TFA|MeleeDrive")
FGameplayTag CombatInProgressTag;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive")
int32 CurrentComboScore = 0;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive")
TObjectPtr<AActor> ActiveAttackingEnemy;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive")
bool bIsCounterWindowActive = false;
/** When true, gun ADS / Firehand suppress Counter input (see §7). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TFA|MeleeDrive|Guns")
bool bSuppressCounterWhileAiming = true;
};

TFAMeleeDriveCombatComponent.cpp (core excerpts)

Section titled “TFAMeleeDriveCombatComponent.cpp (core excerpts)”
#include "TFAMeleeDriveCombatComponent.h"
#include "GameFramework/Character.h"
#include "Kismet/KismetSystemLibrary.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GameplayEffectTypes.h"
UTFAMeleeDriveCombatComponent::UTFAMeleeDriveCombatComponent()
{
PrimaryComponentTick.bCanEverTick = false;
CombatInProgressTag = FGameplayTag::RequestGameplayTag(TEXT("State.Combat.InProgress"));
}
int32 UTFAMeleeDriveCombatComponent::GetFlowTier() const
{
if (CurrentComboScore >= 30) return 3; // Singularity gate
if (CurrentComboScore >= 15) return 2; // Overclocked
return 1;
}
void UTFAMeleeDriveCombatComponent::AddFlowKillCredit()
{
CurrentComboScore++;
OnFlowChanged.Broadcast(CurrentComboScore, GetFlowTier());
// Tier 3 → grant / activate GA_TFA_SingularityTakedown (server) — see FS-CMB-005
}
void UTFAMeleeDriveCombatComponent::ResetFlow(bool bApplyStagger)
{
CurrentComboScore = 0;
OnFlowChanged.Broadcast(0, 1);
// Optional: apply stagger GE 0.5s when bApplyStagger (whiff / damage) — FS-CMB-004
}
AActor* UTFAMeleeDriveCombatComponent::FindBestCombatTarget(float MaxDistance, float MaxAngle) const
{
ACharacter* OwnerChar = Cast<ACharacter>(GetOwner());
if (!OwnerChar) return nullptr;
FVector SearchOrigin = OwnerChar->GetActorLocation();
FVector InputDirection = OwnerChar->GetLastMovementInputVector();
if (InputDirection.IsNearlyZero())
{
InputDirection = OwnerChar->GetActorForwardVector();
}
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn));
TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(OwnerChar);
TArray<AActor*> OutActors;
UKismetSystemLibrary::SphereOverlapActors(
GetWorld(), SearchOrigin, MaxDistance, ObjectTypes, nullptr, ActorsToIgnore, OutActors);
AActor* BestTarget = nullptr;
float HighestScore = -1.f;
for (AActor* Actor : OutActors)
{
if (!Actor) continue;
FVector TargetDir = (Actor->GetActorLocation() - SearchOrigin).GetSafeNormal2D();
const float DotToTarget = FVector::DotProduct(InputDirection.GetSafeNormal2D(), TargetDir);
const float AngleToTarget = FMath::RadiansToDegrees(FMath::Acos(FMath::Clamp(DotToTarget, -1.f, 1.f)));
if (AngleToTarget > MaxAngle) continue;
const float DistanceNormalized = FVector::Dist(SearchOrigin, Actor->GetActorLocation()) / MaxDistance;
const float Score = DotToTarget * (1.f - DistanceNormalized);
if (Score > HighestScore)
{
HighestScore = Score;
BestTarget = Actor;
}
}
return BestTarget;
}
void UTFAMeleeDriveCombatComponent::ExecuteMeleeHitTrace(
FVector BoxHalfSize, FName SocketStart, FName SocketEnd,
TSubclassOf<UGameplayEffect> DamageEffectClass, float BaseDamageMagnitude)
{
ACharacter* OwnerChar = Cast<ACharacter>(GetOwner());
if (!OwnerChar || !OwnerChar->GetMesh() || !DamageEffectClass) return;
// Prefer authority for apply; client may predict VFX only (FS-ARCH-013)
if (OwnerChar->GetLocalRole() != ROLE_Authority && !OwnerChar->HasAuthority())
{
// LocalPredicted: still run trace for feel; server GE is source of truth
}
const FVector StartPos = OwnerChar->GetMesh()->GetSocketLocation(SocketStart);
const FVector EndPos = OwnerChar->GetMesh()->GetSocketLocation(SocketEnd);
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn));
TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(OwnerChar);
TArray<FHitResult> OutHits;
const bool bHit = UKismetSystemLibrary::BoxTraceMultiForObjects(
GetWorld(), StartPos, EndPos, BoxHalfSize, FRotator::ZeroRotator,
ObjectTypes, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, OutHits, true);
if (!bHit) return;
UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwnerChar);
if (!SourceASC) return;
TArray<AActor*> DamagedActors;
for (const FHitResult& Hit : OutHits)
{
AActor* HitActor = Hit.GetActor();
if (!HitActor || DamagedActors.Contains(HitActor)) continue;
DamagedActors.Add(HitActor);
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitActor);
if (!TargetASC) continue;
FGameplayEffectContextHandle Ctx = SourceASC->MakeEffectContext();
Ctx.AddSourceObject(OwnerChar);
Ctx.AddHitResult(Hit, true);
FGameplayEffectSpecHandle Spec = SourceASC->MakeOutgoingSpec(DamageEffectClass, 1.f, Ctx);
if (Spec.IsValid())
{
Spec.Data->SetSetByCallerMagnitude(
FGameplayTag::RequestGameplayTag(TEXT("Data.Damage")), BaseDamageMagnitude);
SourceASC->ApplyGameplayEffectSpecToTarget(*Spec.Data.Get(), TargetASC);
}
// On confirmed kill (death GE / vitality), call AddFlowKillCredit() once — shared with guns
}
}
void UTFAMeleeDriveCombatComponent::OpenCounterWindow(AActor* AttackingEnemy)
{
if (!AttackingEnemy) return;
ActiveAttackingEnemy = AttackingEnemy;
bIsCounterWindowActive = true;
OnCounterWindowOpened.Broadcast(ActiveAttackingEnemy);
}
void UTFAMeleeDriveCombatComponent::CloseCounterWindow()
{
OnCounterWindowClosed.Broadcast(ActiveAttackingEnemy);
ActiveAttackingEnemy = nullptr;
bIsCounterWindowActive = false;
}
bool UTFAMeleeDriveCombatComponent::AttemptCounter()
{
if (bIsCounterWindowActive && IsValid(ActiveAttackingEnemy))
{
return true;
}
ResetFlow(true); // failed counter = whiff-class reset
return false;
}

FS-CMB-002 note: FindBestCombatTarget must only be called from Attack / Counter ActivateAbility, never from Tick as a magnet.


4. Melee strike ability (Motion Warp on press)

Section titled “4. Melee strike ability (Motion Warp on press)”

Subclass ULyraGameplayAbility (or TFA wrapper) inside TFAMelee (not the thin Combat spine).

void UTFAGameplayAbility_MeleeStrike::ActivateAbility(...)
{
Super::ActivateAbility(...);
if (!CommitAbility(...)) { EndAbility(...); return; }
ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
UTFAMeleeDriveCombatComponent* Combat = Character
? Character->FindComponentByClass<UTFAMeleeDriveCombatComponent>() : nullptr;
UMotionWarpingComponent* Warp = Character
? Character->FindComponentByClass<UMotionWarpingComponent>() : nullptr;
// Camera: ensure melee stance / TPP (FS hybrid) before warp
// LoadoutComponent->SetActiveStance(Melee) or Gameplay Message
if (Combat && Warp)
{
if (AActor* Target = Combat->FindBestCombatTarget(StrikeRange, TargetAngleCone))
{
const FVector TargetLoc = Target->GetActorLocation();
const FVector Offset = (Character->GetActorLocation() - TargetLoc).GetSafeNormal2D() * TargetStrikingOffset;
Warp->AddOrUpdateWarpTargetFromLocationAndRotation(
MotionWarpTargetName, TargetLoc + Offset,
(TargetLoc - Character->GetActorLocation()).Rotation());
}
// Do NOT AddFlowKillCredit here — only on confirmed hit/kill (or counter success)
}
// Play montage + wait (AbilityTask_PlayMontageAndWait) …
}

Montage setup (editor):

  1. Notify State Motion Warping · Warp Target Name = CombatTarget · Skew Warp · span lunge frames only.
  2. Notify State ANS_MeleeHitDetection · tick → ExecuteMeleeHitTrace (sockets e.g. hand_r / weapon tip, box half-size, GE class).

void UTFAGameplayAbility_Counter::ActivateAbility(...)
{
Super::ActivateAbility(...);
ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
auto* Combat = Character ? Character->FindComponentByClass<UTFAMeleeDriveCombatComponent>() : nullptr;
auto* Warp = Character ? Character->FindComponentByClass<UMotionWarpingComponent>() : nullptr;
// Gun rule: if aiming and bSuppressCounterWhileAiming → EndAbility fail (see §7)
if (!Combat || !Combat->AttemptCounter() || !Warp)
{
EndAbility(..., true, true);
return;
}
AActor* Attacker = Combat->ActiveAttackingEnemy; // prefer stored window enemy over re-scan
if (Attacker)
{
const FVector WarpLoc = Attacker->GetActorLocation()
+ (Character->GetActorLocation() - Attacker->GetActorLocation()).GetSafeNormal2D() * 100.f;
Warp->AddOrUpdateWarpTargetFromLocationAndRotation(
MotionWarpTargetName, WarpLoc,
(Attacker->GetActorLocation() - Character->GetActorLocation()).Rotation());
}
Combat->CloseCounterWindow();
// On successful counter hit/kill → Combat->AddFlowKillCredit();
// Play CounterMontage …
}

Bugfix: Counter must warp to ActiveAttackingEnemy, not FindBestCombatTarget(600, 360) (that can pick the wrong pawn).


  1. Components on hero: UTFAMeleeDriveCombatComponent + UMotionWarpingComponent.
  2. DAS_MeleeDriveCombat (LyraAbilitySet):
    • MeleeStrike → InputTag.Ability.Melee (or Attack)
    • Counter → InputTag.Ability.Counter
  3. Inject set into Demo / Ladder Experience Action Sets (not permanent LyraGame default if you need Mod-off strip).
  4. W_CounterPrompt: bind OnCounterWindowOpened/Closed; project attacker to screen.
  5. AI wind-up: OpenCounterWindow(Self) at start; CloseCounterWindow at commit frame / interrupt.

OmniVisor: Flow ring reads OnFlowChanged (tiers). Ultimate glyph stays separate.


7. Gun play interaction (Asymmetric Flow) — required

Section titled “7. Gun play interaction (Asymmetric Flow) — required”

Approach A = Ult / Energy streak only (FS-CMB-007) — not this table.

Situation Camera / stance Melee Drive / Counter Flow
Hip / ADS gun body kill FPP Precision Fire Melee ability not active +1
Hip / ADS gun headshot kill FPP Precision Fire Melee ability not active +2
Press Melee while gun drawn Pull TPP (Square/X / Melee Strike) MeleeStrike ability + optional warp Melee kill +2
Hold ADS (bSuppressCounterWhileAiming) FPP Counter input ignored (locked — not buffered) —
Counter window open, player hip-firing Still may Counter if not ADS Counter forces TPP intercept Non-fatal +1 / fatal +2
Overclocked (Tier 2) Either verb +30% move applies to gun strafe and melee Shared
Singularity (Tier 3) Always TPP takedown Guns holstered / locked for sequence Reset to 0 after
Whiff melee / failed Counter / Health damage — — Flow 0 + 0.5 s stagger
Shield/Armor-only chip (no Health loss) — — Flow unchanged (TBD-056)
Gun dry-fire / miss FPP Not a Flow whiff No dump
// On Firehand confirmed kill (server) — production path:
BroadcastMessage(TFA.Combat.KillConfirmed, {Instigator, Victim, bHeadshot});
// TFASingularity applies Asymmetric credit — do NOT call MeleeDrive AddFlowKillCredit from Weapons
// When entering ADS:
if (MeleeDriveCombat->bSuppressCounterWhileAiming)
{
// Block InputTag.Ability.Counter via ASC tag State.Weapon.ADS
}
// When activating MeleeStrike from gun stance:
Loadout->RequestMeleeStance(); // camera director → TPP
// then MeleeStrike ActivateAbility as above

Do not: give guns a second / fourth combo meter · dump Flow on every missed shot · Motion-Warp the camera while in ADS · merge Ultimate charge into Flow · #include TFAMelee from TFAWeapons.


When GetFlowTier() becomes 3 on a kill credit:

  1. Server activates GA_TFA_SingularityTakedown (no player button).
  2. Camera forced TPP for Burst Storm (DA_TFA_SingularityBursts).
  3. On resolve → ResetFlow(false).
  4. Ultimate meter untouched (FS-CMB-006).

  1. Mix gun + melee kills under Asymmetric Flow to 30 without Health damage-break → one Singularity → Flow 0.
  2. All-gun or all-melee reaches same gate (gun needs more body kills).
  3. Counter window shows prompt; success credits Flow; fail dumps Flow.
  4. ADS ignores Counter.
  5. Shield-only chip does not dump Flow.
  6. No TakeDamage path for player melee power — GE only.
  7. KillConfirmed → TFASingularity; components under Plugify GFPs (TFAMelee · TFASingularity · TFAWeapons); spine on TFACombat.