215 lines
8.2 KiB
C++
215 lines
8.2 KiB
C++
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
||
|
#include "LyraRangedWeaponInstance.h"
|
||
|
#include "NativeGameplayTags.h"
|
||
|
#include "GameFramework/Pawn.h"
|
||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||
|
#include "Camera/LyraCameraComponent.h"
|
||
|
#include "Physics/PhysicalMaterialWithTags.h"
|
||
|
|
||
|
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Lyra_Weapon_SteadyAimingCamera, "Lyra.Weapon.SteadyAimingCamera");
|
||
|
|
||
|
ULyraRangedWeaponInstance::ULyraRangedWeaponInstance(const FObjectInitializer& ObjectInitializer)
|
||
|
: Super(ObjectInitializer)
|
||
|
{
|
||
|
HeatToHeatPerShotCurve.EditorCurveData.AddKey(0.0f, 1.0f);
|
||
|
HeatToCoolDownPerSecondCurve.EditorCurveData.AddKey(0.0f, 2.0f);
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::PostLoad()
|
||
|
{
|
||
|
Super::PostLoad();
|
||
|
|
||
|
#if WITH_EDITOR
|
||
|
UpdateDebugVisualization();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if WITH_EDITOR
|
||
|
void ULyraRangedWeaponInstance::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
|
||
|
{
|
||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||
|
UpdateDebugVisualization();
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::UpdateDebugVisualization()
|
||
|
{
|
||
|
ComputeHeatRange(/*out*/ Debug_MinHeat, /*out*/ Debug_MaxHeat);
|
||
|
ComputeSpreadRange(/*out*/ Debug_MinSpreadAngle, /*out*/ Debug_MaxSpreadAngle);
|
||
|
Debug_CurrentHeat = CurrentHeat;
|
||
|
Debug_CurrentSpreadAngle = CurrentSpreadAngle;
|
||
|
Debug_CurrentSpreadAngleMultiplier = CurrentSpreadAngleMultiplier;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void ULyraRangedWeaponInstance::OnEquipped()
|
||
|
{
|
||
|
Super::OnEquipped();
|
||
|
|
||
|
// Start heat in the middle
|
||
|
float MinHeatRange;
|
||
|
float MaxHeatRange;
|
||
|
ComputeHeatRange(/*out*/ MinHeatRange, /*out*/ MaxHeatRange);
|
||
|
CurrentHeat = (MinHeatRange + MaxHeatRange) * 0.5f;
|
||
|
|
||
|
// Derive spread
|
||
|
CurrentSpreadAngle = HeatToSpreadCurve.GetRichCurveConst()->Eval(CurrentHeat);
|
||
|
|
||
|
// Default the multipliers to 1x
|
||
|
CurrentSpreadAngleMultiplier = 1.0f;
|
||
|
StandingStillMultiplier = 1.0f;
|
||
|
JumpFallMultiplier = 1.0f;
|
||
|
CrouchingMultiplier = 1.0f;
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::OnUnequipped()
|
||
|
{
|
||
|
Super::OnUnequipped();
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::Tick(float DeltaSeconds)
|
||
|
{
|
||
|
APawn* Pawn = GetPawn();
|
||
|
check(Pawn != nullptr);
|
||
|
|
||
|
const bool bMinSpread = UpdateSpread(DeltaSeconds);
|
||
|
const bool bMinMultipliers = UpdateMultipliers(DeltaSeconds);
|
||
|
|
||
|
bHasFirstShotAccuracy = bAllowFirstShotAccuracy && bMinMultipliers && bMinSpread;
|
||
|
|
||
|
#if WITH_EDITOR
|
||
|
UpdateDebugVisualization();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::ComputeHeatRange(float& MinHeat, float& MaxHeat)
|
||
|
{
|
||
|
float Min1;
|
||
|
float Max1;
|
||
|
HeatToHeatPerShotCurve.GetRichCurveConst()->GetTimeRange(/*out*/ Min1, /*out*/ Max1);
|
||
|
|
||
|
float Min2;
|
||
|
float Max2;
|
||
|
HeatToCoolDownPerSecondCurve.GetRichCurveConst()->GetTimeRange(/*out*/ Min2, /*out*/ Max2);
|
||
|
|
||
|
float Min3;
|
||
|
float Max3;
|
||
|
HeatToSpreadCurve.GetRichCurveConst()->GetTimeRange(/*out*/ Min3, /*out*/ Max3);
|
||
|
|
||
|
MinHeat = FMath::Min(FMath::Min(Min1, Min2), Min3);
|
||
|
MaxHeat = FMath::Max(FMath::Max(Max1, Max2), Max3);
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::ComputeSpreadRange(float& MinSpread, float& MaxSpread)
|
||
|
{
|
||
|
HeatToSpreadCurve.GetRichCurveConst()->GetValueRange(/*out*/ MinSpread, /*out*/ MaxSpread);
|
||
|
}
|
||
|
|
||
|
void ULyraRangedWeaponInstance::AddSpread()
|
||
|
{
|
||
|
// Sample the heat up curve
|
||
|
const float HeatPerShot = HeatToHeatPerShotCurve.GetRichCurveConst()->Eval(CurrentHeat);
|
||
|
CurrentHeat = ClampHeat(CurrentHeat + HeatPerShot);
|
||
|
|
||
|
// Map the heat to the spread angle
|
||
|
CurrentSpreadAngle = HeatToSpreadCurve.GetRichCurveConst()->Eval(CurrentHeat);
|
||
|
|
||
|
#if WITH_EDITOR
|
||
|
UpdateDebugVisualization();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
float ULyraRangedWeaponInstance::GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
|
||
|
{
|
||
|
const FRichCurve* Curve = DistanceDamageFalloff.GetRichCurveConst();
|
||
|
return Curve->HasAnyData() ? Curve->Eval(Distance) : 1.0f;
|
||
|
}
|
||
|
|
||
|
float ULyraRangedWeaponInstance::GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
|
||
|
{
|
||
|
float CombinedMultiplier = 1.0f;
|
||
|
if (const UPhysicalMaterialWithTags* PhysMatWithTags = Cast<const UPhysicalMaterialWithTags>(PhysicalMaterial))
|
||
|
{
|
||
|
for (const FGameplayTag MaterialTag : PhysMatWithTags->Tags)
|
||
|
{
|
||
|
if (const float* pTagMultiplier = MaterialDamageMultiplier.Find(MaterialTag))
|
||
|
{
|
||
|
CombinedMultiplier *= *pTagMultiplier;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return CombinedMultiplier;
|
||
|
}
|
||
|
|
||
|
bool ULyraRangedWeaponInstance::UpdateSpread(float DeltaSeconds)
|
||
|
{
|
||
|
const float TimeSinceFired = GetWorld()->TimeSince(LastFireTime);
|
||
|
|
||
|
if (TimeSinceFired > SpreadRecoveryCooldownDelay)
|
||
|
{
|
||
|
const float CooldownRate = HeatToCoolDownPerSecondCurve.GetRichCurveConst()->Eval(CurrentHeat);
|
||
|
CurrentHeat = ClampHeat(CurrentHeat - (CooldownRate * DeltaSeconds));
|
||
|
CurrentSpreadAngle = HeatToSpreadCurve.GetRichCurveConst()->Eval(CurrentHeat);
|
||
|
}
|
||
|
|
||
|
float MinSpread;
|
||
|
float MaxSpread;
|
||
|
ComputeSpreadRange(/*out*/ MinSpread, /*out*/ MaxSpread);
|
||
|
|
||
|
return FMath::IsNearlyEqual(CurrentSpreadAngle, MinSpread, KINDA_SMALL_NUMBER);
|
||
|
}
|
||
|
|
||
|
bool ULyraRangedWeaponInstance::UpdateMultipliers(float DeltaSeconds)
|
||
|
{
|
||
|
const float MultiplierNearlyEqualThreshold = 0.05f;
|
||
|
|
||
|
APawn* Pawn = GetPawn();
|
||
|
check(Pawn != nullptr);
|
||
|
UCharacterMovementComponent* CharMovementComp = Cast<UCharacterMovementComponent>(Pawn->GetMovementComponent());
|
||
|
|
||
|
// See if we are standing still, and if so, smoothly apply the bonus
|
||
|
const float PawnSpeed = Pawn->GetVelocity().Size();
|
||
|
const float MovementTargetValue = FMath::GetMappedRangeValueClamped(
|
||
|
/*InputRange=*/ FVector2D(StandingStillSpeedThreshold, StandingStillSpeedThreshold + StandingStillToMovingSpeedRange),
|
||
|
/*OutputRange=*/ FVector2D(SpreadAngleMultiplier_StandingStill, 1.0f),
|
||
|
/*Alpha=*/ PawnSpeed);
|
||
|
StandingStillMultiplier = FMath::FInterpTo(StandingStillMultiplier, MovementTargetValue, DeltaSeconds, TransitionRate_StandingStill);
|
||
|
const bool bStandingStillMultiplierAtMin = FMath::IsNearlyEqual(StandingStillMultiplier, SpreadAngleMultiplier_StandingStill, SpreadAngleMultiplier_StandingStill*0.1f);
|
||
|
|
||
|
// See if we are crouching, and if so, smoothly apply the bonus
|
||
|
const bool bIsCrouching = (CharMovementComp != nullptr) && CharMovementComp->IsCrouching();
|
||
|
const float CrouchingTargetValue = bIsCrouching ? SpreadAngleMultiplier_Crouching : 1.0f;
|
||
|
CrouchingMultiplier = FMath::FInterpTo(CrouchingMultiplier, CrouchingTargetValue, DeltaSeconds, TransitionRate_Crouching);
|
||
|
const bool bCrouchingMultiplierAtTarget = FMath::IsNearlyEqual(CrouchingMultiplier, CrouchingTargetValue, MultiplierNearlyEqualThreshold);
|
||
|
|
||
|
// See if we are in the air (jumping/falling), and if so, smoothly apply the penalty
|
||
|
const bool bIsJumpingOrFalling = (CharMovementComp != nullptr) && CharMovementComp->IsFalling();
|
||
|
const float JumpFallTargetValue = bIsJumpingOrFalling ? SpreadAngleMultiplier_JumpingOrFalling : 1.0f;
|
||
|
JumpFallMultiplier = FMath::FInterpTo(JumpFallMultiplier, JumpFallTargetValue, DeltaSeconds, TransitionRate_JumpingOrFalling);
|
||
|
const bool bJumpFallMultiplerIs1 = FMath::IsNearlyEqual(JumpFallMultiplier, 1.0f, MultiplierNearlyEqualThreshold);
|
||
|
|
||
|
// Determine if we are aiming down sights, and apply the bonus based on how far into the camera transition we are
|
||
|
float AimingAlpha = 0.0f;
|
||
|
if (const ULyraCameraComponent* CameraComponent = ULyraCameraComponent::FindCameraComponent(Pawn))
|
||
|
{
|
||
|
float TopCameraWeight;
|
||
|
FGameplayTag TopCameraTag;
|
||
|
CameraComponent->GetBlendInfo(/*out*/ TopCameraWeight, /*out*/ TopCameraTag);
|
||
|
|
||
|
AimingAlpha = (TopCameraTag == TAG_Lyra_Weapon_SteadyAimingCamera) ? TopCameraWeight : 0.0f;
|
||
|
}
|
||
|
const float AimingMultiplier = FMath::GetMappedRangeValueClamped(
|
||
|
/*InputRange=*/ FVector2D(0.0f, 1.0f),
|
||
|
/*OutputRange=*/ FVector2D(1.0f, SpreadAngleMultiplier_Aiming),
|
||
|
/*Alpha=*/ AimingAlpha);
|
||
|
const bool bAimingMultiplierAtTarget = FMath::IsNearlyEqual(AimingMultiplier, SpreadAngleMultiplier_Aiming, KINDA_SMALL_NUMBER);
|
||
|
|
||
|
// Combine all the multipliers
|
||
|
const float CombinedMultiplier = AimingMultiplier * StandingStillMultiplier * CrouchingMultiplier * JumpFallMultiplier;
|
||
|
CurrentSpreadAngleMultiplier = CombinedMultiplier;
|
||
|
|
||
|
// need to handle these spread multipliers indicating we are not at min spread
|
||
|
return bStandingStillMultiplierAtMin && bCrouchingMultiplierAtTarget && bJumpFallMultiplerIs1 && bAimingMultiplierAtTarget;
|
||
|
}
|