RealtimeStyleTransferRuntime/Source/LyraGame/Weapons/LyraGameplayAbility_RangedW...

602 lines
22 KiB
C++
Raw Permalink Normal View History

2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraGameplayAbility_RangedWeapon.h"
#include "Weapons/LyraRangedWeaponInstance.h"
#include "Physics/LyraCollisionChannels.h"
#include "LyraLogChannels.h"
#include "AIController.h"
#include "Messages/LyraVerbMessage.h"
#include "NativeGameplayTags.h"
#include "GameFramework/GameplayMessageSubsystem.h"
#include "Weapons/LyraWeaponStateComponent.h"
#include "Teams/LyraTeamSubsystem.h"
#include "AbilitySystemComponent.h"
#include "GameFramework/PlayerController.h"
#include "AbilitySystem/LyraGameplayEffectContext.h"
#include "AbilitySystem/LyraGameplayAbilityTargetData_SingleTargetHit.h"
#include "DrawDebugHelpers.h"
namespace LyraConsoleVariables
{
static float DrawBulletTracesDuration = 0.0f;
static FAutoConsoleVariableRef CVarDrawBulletTraceDuraton(
TEXT("lyra.Weapon.DrawBulletTraceDuration"),
DrawBulletTracesDuration,
TEXT("Should we do debug drawing for bullet traces (if above zero, sets how long (in seconds))"),
ECVF_Default);
static float DrawBulletHitDuration = 0.0f;
static FAutoConsoleVariableRef CVarDrawBulletHits(
TEXT("lyra.Weapon.DrawBulletHitDuration"),
DrawBulletHitDuration,
TEXT("Should we do debug drawing for bullet impacts (if above zero, sets how long (in seconds))"),
ECVF_Default);
static float DrawBulletHitRadius = 3.0f;
static FAutoConsoleVariableRef CVarDrawBulletHitRadius(
TEXT("lyra.Weapon.DrawBulletHitRadius"),
DrawBulletHitRadius,
TEXT("When bullet hit debug drawing is enabled (see DrawBulletHitDuration), how big should the hit radius be? (in uu)"),
ECVF_Default);
}
// Weapon fire will be blocked/canceled if the player has this tag
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_WeaponFireBlocked, "Ability.Weapon.NoFiring");
//////////////////////////////////////////////////////////////////////
FVector VRandConeNormalDistribution(const FVector& Dir, const float ConeHalfAngleRad, const float Exponent)
{
if (ConeHalfAngleRad > 0.f)
{
const float ConeHalfAngleDegrees = FMath::RadiansToDegrees(ConeHalfAngleRad);
// consider the cone a concatenation of two rotations. one "away" from the center line, and another "around" the circle
// apply the exponent to the away-from-center rotation. a larger exponent will cluster points more tightly around the center
const float FromCenter = FMath::Pow(FMath::FRand(), Exponent);
const float AngleFromCenter = FromCenter * ConeHalfAngleDegrees;
const float AngleAround = FMath::FRand() * 360.0f;
FRotator Rot = Dir.Rotation();
FQuat DirQuat(Rot);
FQuat FromCenterQuat(FRotator(0.0f, AngleFromCenter, 0.0f));
FQuat AroundQuat(FRotator(0.0f, 0.0, AngleAround));
FQuat FinalDirectionQuat = DirQuat * AroundQuat * FromCenterQuat;
FinalDirectionQuat.Normalize();
return FinalDirectionQuat.RotateVector(FVector::ForwardVector);
}
else
{
return Dir.GetSafeNormal();
}
}
ULyraGameplayAbility_RangedWeapon::ULyraGameplayAbility_RangedWeapon(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SourceBlockedTags.AddTag(TAG_WeaponFireBlocked);
}
ULyraRangedWeaponInstance* ULyraGameplayAbility_RangedWeapon::GetWeaponInstance() const
{
return Cast<ULyraRangedWeaponInstance>(GetAssociatedEquipment());
}
bool ULyraGameplayAbility_RangedWeapon::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
bool bResult = Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
if (bResult)
{
if (GetWeaponInstance() == nullptr)
{
UE_LOG(LogLyraAbilitySystem, Error, TEXT("Weapon ability %s cannot be activated because there is no associated ranged weapon (equipment instance=%s but needs to be derived from %s)"),
*GetPathName(),
*GetPathNameSafe(GetAssociatedEquipment()),
*ULyraRangedWeaponInstance::StaticClass()->GetName());
bResult = false;
}
}
return bResult;
}
int32 ULyraGameplayAbility_RangedWeapon::FindFirstPawnHitResult(const TArray<FHitResult>& HitResults)
{
for (int32 Idx = 0; Idx < HitResults.Num(); ++Idx)
{
const FHitResult& CurHitResult = HitResults[Idx];
if (CurHitResult.HitObjectHandle.DoesRepresentClass(APawn::StaticClass()))
{
// If we hit a pawn, we're good
return Idx;
}
else
{
AActor* HitActor = CurHitResult.HitObjectHandle.FetchActor();
if ((HitActor != nullptr) && (HitActor->GetAttachParentActor() != nullptr) && (Cast<APawn>(HitActor->GetAttachParentActor()) != nullptr))
{
// If we hit something attached to a pawn, we're good
return Idx;
}
}
}
return INDEX_NONE;
}
void ULyraGameplayAbility_RangedWeapon::AddAdditionalTraceIgnoreActors(FCollisionQueryParams& TraceParams) const
{
if (AActor* Avatar = GetAvatarActorFromActorInfo())
{
// Ignore any actors attached to the avatar doing the shooting
TArray<AActor*> AttachedActors;
Avatar->GetAttachedActors(/*out*/ AttachedActors);
TraceParams.AddIgnoredActors(AttachedActors);
}
}
ECollisionChannel ULyraGameplayAbility_RangedWeapon::DetermineTraceChannel(FCollisionQueryParams& TraceParams, bool bIsSimulated) const
{
return Lyra_TraceChannel_Weapon;
}
FHitResult ULyraGameplayAbility_RangedWeapon::WeaponTrace(const FVector& StartTrace, const FVector& EndTrace, float SweepRadius, bool bIsSimulated, OUT TArray<FHitResult>& OutHitResults) const
{
TArray<FHitResult> HitResults;
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(WeaponTrace), /*bTraceComplex=*/ true, /*IgnoreActor=*/ GetAvatarActorFromActorInfo());
TraceParams.bReturnPhysicalMaterial = true;
AddAdditionalTraceIgnoreActors(TraceParams);
//TraceParams.bDebugQuery = true;
const ECollisionChannel TraceChannel = DetermineTraceChannel(TraceParams, bIsSimulated);
if (SweepRadius > 0.0f)
{
GetWorld()->SweepMultiByChannel(HitResults, StartTrace, EndTrace, FQuat::Identity, TraceChannel, FCollisionShape::MakeSphere(SweepRadius), TraceParams);
}
else
{
GetWorld()->LineTraceMultiByChannel(HitResults, StartTrace, EndTrace, TraceChannel, TraceParams);
}
FHitResult Hit(ForceInit);
if (HitResults.Num() > 0)
{
// Filter the output list to prevent multiple hits on the same actor;
// this is to prevent a single bullet dealing damage multiple times to
// a single actor if using an overlap trace
for (FHitResult& CurHitResult : HitResults)
{
auto Pred = [&CurHitResult](const FHitResult& Other)
{
return Other.HitObjectHandle == CurHitResult.HitObjectHandle;
};
if (!OutHitResults.ContainsByPredicate(Pred))
{
OutHitResults.Add(CurHitResult);
}
}
Hit = OutHitResults.Last();
}
else
{
Hit.TraceStart = StartTrace;
Hit.TraceEnd = EndTrace;
}
return Hit;
}
FVector ULyraGameplayAbility_RangedWeapon::GetWeaponTargetingSourceLocation() const
{
// Use Pawn's location as a base
APawn* const AvatarPawn = Cast<APawn>(GetAvatarActorFromActorInfo());
check(AvatarPawn);
const FVector SourceLoc = AvatarPawn->GetActorLocation();
const FQuat SourceRot = AvatarPawn->GetActorQuat();
FVector TargetingSourceLocation = SourceLoc;
//@TODO: Add an offset from the weapon instance and adjust based on pawn crouch/aiming/etc...
return TargetingSourceLocation;
}
FTransform ULyraGameplayAbility_RangedWeapon::GetTargetingTransform(APawn* SourcePawn, ELyraAbilityTargetingSource Source) const
{
check(SourcePawn);
AController* SourcePawnController = SourcePawn->GetController();
ULyraWeaponStateComponent* WeaponStateComponent = (SourcePawnController != nullptr) ? SourcePawnController->FindComponentByClass<ULyraWeaponStateComponent>() : nullptr;
// The caller should determine the transform without calling this if the mode is custom!
check(Source != ELyraAbilityTargetingSource::Custom);
const FVector ActorLoc = SourcePawn->GetActorLocation();
FQuat AimQuat = SourcePawn->GetActorQuat();
AController* Controller = SourcePawn->Controller;
FVector SourceLoc;
double FocalDistance = 1024.0f;
FVector FocalLoc;
FVector CamLoc;
FRotator CamRot;
bool bFoundFocus = false;
if ((Controller != nullptr) && ((Source == ELyraAbilityTargetingSource::CameraTowardsFocus) || (Source == ELyraAbilityTargetingSource::PawnTowardsFocus) || (Source == ELyraAbilityTargetingSource::WeaponTowardsFocus)))
{
// Get camera position for later
bFoundFocus = true;
APlayerController* PC = Cast<APlayerController>(Controller);
if (PC != nullptr)
{
PC->GetPlayerViewPoint(/*out*/ CamLoc, /*out*/ CamRot);
}
else
{
SourceLoc = GetWeaponTargetingSourceLocation();
CamLoc = SourceLoc;
CamRot = Controller->GetControlRotation();
}
// Determine initial focal point to
FVector AimDir = CamRot.Vector().GetSafeNormal();
FocalLoc = CamLoc + (AimDir * FocalDistance);
// Move the start and focal point up in front of pawn
if (PC)
{
const FVector WeaponLoc = GetWeaponTargetingSourceLocation();
CamLoc = FocalLoc + (((WeaponLoc - FocalLoc) | AimDir) * AimDir);
FocalLoc = CamLoc + (AimDir * FocalDistance);
}
//Move the start to be the HeadPosition of the AI
else if (AAIController* AIController = Cast<AAIController>(Controller))
{
CamLoc = SourcePawn->GetActorLocation() + FVector(0, 0, SourcePawn->BaseEyeHeight);
}
if (Source == ELyraAbilityTargetingSource::CameraTowardsFocus)
{
// If we're camera -> focus then we're done
return FTransform(CamRot, CamLoc);
}
}
if ((Source == ELyraAbilityTargetingSource::WeaponForward) || (Source == ELyraAbilityTargetingSource::WeaponTowardsFocus))
{
SourceLoc = GetWeaponTargetingSourceLocation();
}
else
{
// Either we want the pawn's location, or we failed to find a camera
SourceLoc = ActorLoc;
}
if (bFoundFocus && ((Source == ELyraAbilityTargetingSource::PawnTowardsFocus) || (Source == ELyraAbilityTargetingSource::WeaponTowardsFocus)))
{
// Return a rotator pointing at the focal point from the source
return FTransform((FocalLoc - SourceLoc).Rotation(), SourceLoc);
}
// If we got here, either we don't have a camera or we don't want to use it, either way go forward
return FTransform(AimQuat, SourceLoc);
}
FHitResult ULyraGameplayAbility_RangedWeapon::DoSingleBulletTrace(const FVector& StartTrace, const FVector& EndTrace, float SweepRadius, bool bIsSimulated, OUT TArray<FHitResult>& OutHits) const
{
#if ENABLE_DRAW_DEBUG
if (LyraConsoleVariables::DrawBulletTracesDuration > 0.0f)
{
static float DebugThickness = 1.0f;
DrawDebugLine(GetWorld(), StartTrace, EndTrace, FColor::Red, false, LyraConsoleVariables::DrawBulletTracesDuration, 0, DebugThickness);
}
#endif // ENABLE_DRAW_DEBUG
FHitResult Impact;
// Trace and process instant hit if something was hit
// First trace without using sweep radius
if (FindFirstPawnHitResult(OutHits) == INDEX_NONE)
{
Impact = WeaponTrace(StartTrace, EndTrace, /*SweepRadius=*/ 0.0f, bIsSimulated, /*out*/ OutHits);
}
if (FindFirstPawnHitResult(OutHits) == INDEX_NONE)
{
// If this weapon didn't hit anything with a line trace and supports a sweep radius, try that
if (SweepRadius > 0.0f)
{
TArray<FHitResult> SweepHits;
Impact = WeaponTrace(StartTrace, EndTrace, SweepRadius, bIsSimulated, /*out*/ SweepHits);
// If the trace with sweep radius enabled hit a pawn, check if we should use its hit results
const int32 FirstPawnIdx = FindFirstPawnHitResult(SweepHits);
if (SweepHits.IsValidIndex(FirstPawnIdx))
{
// If we had a blocking hit in our line trace that occurs in SweepHits before our
// hit pawn, we should just use our initial hit results since the Pawn hit should be blocked
bool bUseSweepHits = true;
for (int32 Idx = 0; Idx < FirstPawnIdx; ++Idx)
{
const FHitResult& CurHitResult = SweepHits[Idx];
auto Pred = [&CurHitResult](const FHitResult& Other)
{
return Other.HitObjectHandle == CurHitResult.HitObjectHandle;
};
if (CurHitResult.bBlockingHit && OutHits.ContainsByPredicate(Pred))
{
bUseSweepHits = false;
break;
}
}
if (bUseSweepHits)
{
OutHits = SweepHits;
}
}
}
}
return Impact;
}
void ULyraGameplayAbility_RangedWeapon::PerformLocalTargeting(OUT TArray<FHitResult>& OutHits)
{
APawn* const AvatarPawn = Cast<APawn>(GetAvatarActorFromActorInfo());
ULyraRangedWeaponInstance* WeaponData = GetWeaponInstance();
if (AvatarPawn && AvatarPawn->IsLocallyControlled() && WeaponData)
{
FRangedWeaponFiringInput InputData;
InputData.WeaponData = WeaponData;
InputData.bCanPlayBulletFX = (AvatarPawn->GetNetMode() != NM_DedicatedServer);
//@TODO: Should do more complicated logic here when the player is close to a wall, etc...
const FTransform TargetTransform = GetTargetingTransform(AvatarPawn, ELyraAbilityTargetingSource::CameraTowardsFocus);
InputData.AimDir = TargetTransform.GetUnitAxis(EAxis::X);
InputData.StartTrace = TargetTransform.GetTranslation();
InputData.EndAim = InputData.StartTrace + InputData.AimDir * WeaponData->GetMaxDamageRange();
#if ENABLE_DRAW_DEBUG
if (LyraConsoleVariables::DrawBulletTracesDuration > 0.0f)
{
static float DebugThickness = 2.0f;
DrawDebugLine(GetWorld(), InputData.StartTrace, InputData.StartTrace + (InputData.AimDir * 100.0f), FColor::Yellow, false, LyraConsoleVariables::DrawBulletTracesDuration, 0, DebugThickness);
}
#endif
TraceBulletsInCartridge(InputData, /*out*/ OutHits);
}
}
void ULyraGameplayAbility_RangedWeapon::TraceBulletsInCartridge(const FRangedWeaponFiringInput& InputData, OUT TArray<FHitResult>& OutHits)
{
ULyraRangedWeaponInstance* WeaponData = InputData.WeaponData;
check(WeaponData);
const int32 BulletsPerCartridge = WeaponData->GetBulletsPerCartridge();
for (int32 BulletIndex = 0; BulletIndex < BulletsPerCartridge; ++BulletIndex)
{
const float BaseSpreadAngle = WeaponData->GetCalculatedSpreadAngle();
const float SpreadAngleMultiplier = WeaponData->GetCalculatedSpreadAngleMultiplier();
const float ActualSpreadAngle = BaseSpreadAngle * SpreadAngleMultiplier;
const float HalfSpreadAngleInRadians = FMath::DegreesToRadians(ActualSpreadAngle * 0.5f);
const FVector BulletDir = VRandConeNormalDistribution(InputData.AimDir, HalfSpreadAngleInRadians, WeaponData->GetSpreadExponent());
const FVector EndTrace = InputData.StartTrace + (BulletDir * WeaponData->GetMaxDamageRange());
FVector HitLocation = EndTrace;
TArray<FHitResult> AllImpacts;
FHitResult Impact = DoSingleBulletTrace(InputData.StartTrace, EndTrace, WeaponData->GetBulletTraceSweepRadius(), /*bIsSimulated=*/ false, /*out*/ AllImpacts);
const AActor* HitActor = Impact.GetActor();
if (HitActor)
{
#if ENABLE_DRAW_DEBUG
if (LyraConsoleVariables::DrawBulletHitDuration > 0.0f)
{
DrawDebugPoint(GetWorld(), Impact.ImpactPoint, LyraConsoleVariables::DrawBulletHitRadius, FColor::Red, false, LyraConsoleVariables::DrawBulletHitRadius);
}
#endif
if (AllImpacts.Num() > 0)
{
OutHits.Append(AllImpacts);
}
HitLocation = Impact.ImpactPoint;
}
// Make sure there's always an entry in OutHits so the direction can be used for tracers, etc...
if (OutHits.Num() == 0)
{
if (!Impact.bBlockingHit)
{
// Locate the fake 'impact' at the end of the trace
Impact.Location = EndTrace;
Impact.ImpactPoint = EndTrace;
}
OutHits.Add(Impact);
}
}
}
void ULyraGameplayAbility_RangedWeapon::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
// Bind target data callback
UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
check(MyAbilityComponent);
OnTargetDataReadyCallbackDelegateHandle = MyAbilityComponent->AbilityTargetDataSetDelegate(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey()).AddUObject(this, &ThisClass::OnTargetDataReadyCallback);
// Update the last firing time
ULyraRangedWeaponInstance* WeaponData = GetWeaponInstance();
check(WeaponData);
WeaponData->UpdateFiringTime();
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}
void ULyraGameplayAbility_RangedWeapon::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
if (IsEndAbilityValid(Handle, ActorInfo))
{
if (ScopeLockCount > 0)
{
WaitingToExecute.Add(FPostLockDelegate::CreateUObject(this, &ThisClass::EndAbility, Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled));
return;
}
UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
check(MyAbilityComponent);
// When ability ends, consume target data and remove delegate
MyAbilityComponent->AbilityTargetDataSetDelegate(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey()).Remove(OnTargetDataReadyCallbackDelegateHandle);
MyAbilityComponent->ConsumeClientReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey());
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
}
void ULyraGameplayAbility_RangedWeapon::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& InData, FGameplayTag ApplicationTag)
{
UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
check(MyAbilityComponent);
if (const FGameplayAbilitySpec* AbilitySpec = MyAbilityComponent->FindAbilitySpecFromHandle(CurrentSpecHandle))
{
FScopedPredictionWindow ScopedPrediction(MyAbilityComponent);
// Take ownership of the target data to make sure no callbacks into game code invalidate it out from under us
FGameplayAbilityTargetDataHandle LocalTargetDataHandle(MoveTemp(const_cast<FGameplayAbilityTargetDataHandle&>(InData)));
const bool bShouldNotifyServer = CurrentActorInfo->IsLocallyControlled() && !CurrentActorInfo->IsNetAuthority();
if (bShouldNotifyServer)
{
MyAbilityComponent->CallServerSetReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey(), LocalTargetDataHandle, ApplicationTag, MyAbilityComponent->ScopedPredictionKey);
}
const bool bIsTargetDataValid = true;
bool bProjectileWeapon = false;
#if WITH_SERVER_CODE
if (!bProjectileWeapon)
{
if (AController* Controller = GetControllerFromActorInfo())
{
if (Controller->GetLocalRole() == ROLE_Authority)
{
// Confirm hit markers
if (ULyraWeaponStateComponent* WeaponStateComponent = Controller->FindComponentByClass<ULyraWeaponStateComponent>())
{
TArray<uint8> HitReplaces;
for (uint8 i = 0; (i < LocalTargetDataHandle.Num()) && (i < 255); ++i)
{
if (FGameplayAbilityTargetData_SingleTargetHit* SingleTargetHit = static_cast<FGameplayAbilityTargetData_SingleTargetHit*>(LocalTargetDataHandle.Get(i)))
{
if (SingleTargetHit->bHitReplaced)
{
HitReplaces.Add(i);
}
}
}
WeaponStateComponent->ClientConfirmTargetData(LocalTargetDataHandle.UniqueId, bIsTargetDataValid, HitReplaces);
}
}
}
}
#endif //WITH_SERVER_CODE
// See if we still have ammo
if (bIsTargetDataValid && CommitAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo))
{
// We fired the weapon, add spread
ULyraRangedWeaponInstance* WeaponData = GetWeaponInstance();
check(WeaponData);
WeaponData->AddSpread();
// Let the blueprint do stuff like apply effects to the targets
OnRangedWeaponTargetDataReady(LocalTargetDataHandle);
}
else
{
UE_LOG(LogLyraAbilitySystem, Warning, TEXT("Weapon ability %s failed to commit (bIsTargetDataValid=%d)"), *GetPathName(), bIsTargetDataValid ? 1 : 0);
K2_EndAbility();
}
}
// We've processed the data
MyAbilityComponent->ConsumeClientReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey());
}
void ULyraGameplayAbility_RangedWeapon::StartRangedWeaponTargeting()
{
check(CurrentActorInfo);
AActor* AvatarActor = CurrentActorInfo->AvatarActor.Get();
check(AvatarActor);
UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
check(MyAbilityComponent);
AController* Controller = GetControllerFromActorInfo();
check(Controller);
ULyraWeaponStateComponent* WeaponStateComponent = Controller->FindComponentByClass<ULyraWeaponStateComponent>();
FScopedPredictionWindow ScopedPrediction(MyAbilityComponent, CurrentActivationInfo.GetActivationPredictionKey());
TArray<FHitResult> FoundHits;
PerformLocalTargeting(/*out*/ FoundHits);
// Fill out the target data from the hit results
FGameplayAbilityTargetDataHandle TargetData;
TargetData.UniqueId = WeaponStateComponent ? WeaponStateComponent->GetUnconfirmedServerSideHitMarkerCount() : 0;
if (FoundHits.Num() > 0)
{
const int32 CartridgeID = FMath::Rand();
for (const FHitResult& FoundHit : FoundHits)
{
FLyraGameplayAbilityTargetData_SingleTargetHit* NewTargetData = new FLyraGameplayAbilityTargetData_SingleTargetHit();
NewTargetData->HitResult = FoundHit;
NewTargetData->CartridgeID = CartridgeID;
TargetData.Add(NewTargetData);
}
}
// Send hit marker information
const bool bProjectileWeapon = false;
if (!bProjectileWeapon && (WeaponStateComponent != nullptr))
{
WeaponStateComponent->AddUnconfirmedServerSideHitMarkers(TargetData, FoundHits);
}
// Process the target data immediately
OnTargetDataReadyCallback(TargetData, FGameplayTag());
}