Finally, the function DisableAndDestroy is in charge of deactivating and destroying the effects kept within the array and the ground actor also
void AKSpell::OnImpact(const FHitResult& HitResult)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT(“AKSpell::OnImpact - Role=%s HitResult.ImpactPoint=%s HitResult.ImpactNormal=%s”), NETROLETOSTRING(Role), *HitResult.ImpactPoint.ToString(), *HitResult.ImpactNormal.ToString());
if (Role == ROLE_Authority && !bTouchedGround)
{
TouchedGround(HitResult);
// Since we have loop effects in the emitters
// we cannot destroy the particles only when we are on the server
// we need also to deactivate from the client
// otherwise the effect still on the clients
}
}
void AKSpell::TouchedGround(const FHitResult& Impact)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT(“AKSpell::TouchedGround - Role=%s”), NETROLETOSTRING(Role));
if (ParticleComp)
{
ParticleComp->Deactivate();
}
// Keep track of the time this spell touch the ground
TouchTime = GetWorld()->GetTimeSeconds();
// effects and damage origin shouldn't be placed inside mesh at impact point
NudgedImpactLocation = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - NudgedImpactLocation=%f"), *NudgedImpactLocation.ToString());
// Apply the spell damage at impact
if (WeaponConfig.Spell.SpellImpactDamage > 0 && WeaponConfig.Spell.SpellRadius > 0 && WeaponConfig.DamageType)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - apply damage at impact World time=%f"), TouchTime);
UGameplayStatics::ApplyRadialDamage(this, WeaponConfig.Spell.SpellImpactDamage, NudgedImpactLocation, WeaponConfig.Spell.SpellRadius, WeaponConfig.DamageType, TArray<AActor*>(), this, MyController.Get(), WeaponConfig.FullDamage);
}
// Apply alterations at impact
if (WeaponConfig.Alterations.Num() > 0 && WeaponConfig.Spell.SpellRadius > 0 && WeaponConfig.DamageType)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - apply alterations at impact World time=%f"), TouchTime);
UKAttributeHelper::ApplyRadialAlterations(this, SpellGuid, WeaponConfig.Alterations, NudgedImpactLocation, WeaponConfig.Spell.SpellRadius, WeaponConfig.DamageType, TArray<AActor*>(), this, MyController.Get());
}
// Start the timer to destroy at the life time end
if (WeaponConfig.Spell.SpellLife > 0)
{
// Start the timer to disable and destroy after the spell life time
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - launch timer to DisableAndDestroy after %f seconds"), WeaponConfig.Spell.SpellLife);
FTimerHandle TempHandle2;
GetWorldTimerManager().SetTimer(TempHandle2, this, &AKSpell::DisableAndDestroy, WeaponConfig.Spell.SpellLife, false);
}
else
{
// if no life, we can disable and destroy
DisableAndDestroy();
}
// Start the timer to apply recurring spell damage
if (WeaponConfig.Spell.SpellLife > 0 && WeaponConfig.Spell.SpellRecurringDamageInterval > 0 &&
WeaponConfig.Spell.SpellRecurringDamage > 0 && WeaponConfig.Spell.SpellRadius > 0 && WeaponConfig.DamageType)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - launch timer to apply recurring damage World time=%f"), TouchTime);
FTimerHandle TempHandle;
GetWorldTimerManager().SetTimer(TempHandle, this, &AKSpell::ApplyRecurringDamage, WeaponConfig.Spell.SpellRecurringDamageInterval, false);
}
// Do we have the spell effect ?
if (GroundTemplate)
{
// If we have a distance gap defined we will need to spawn several actors
if (WeaponConfig.Spell.DistanceGapInRadius > 0.0f)
{
// Initialize
DistanceFromCenter = 0.0f;
NumPoints = 1;
// Call a loop timer function to spawn the effect actors
GetWorldTimerManager().SetTimer(SpawnEffectActors_Handle, FTimerDelegate::CreateUObject(this, &AKSpell::SpawnEffectActors, Impact), 0.2f, true);
}
else // Otherwise, we have only one effect to spawn
{
FTransform const SpawnTransform(FRotator(0, 0, 0), NudgedImpactLocation);
AKWeaponFireGroundEffect* EffectActor = GetWorld()->SpawnActorDeferred<AKWeaponFireGroundEffect>(GroundTemplate, SpawnTransform);
if (EffectActor)
{
EffectActor->SurfaceHit = Impact;
// Set its life time
EffectActor->GroundEffectLifeTime = WeaponConfig.Spell.SpellLife;
// Set its color based on the damage type
EffectActor->SetColor(GetWeaponConfigDamageTypeColor(WeaponConfig));
UGameplayStatics::FinishSpawningActor(EffectActor, SpawnTransform);
// Add the actor to the array
EffectActors.Add(EffectActor);
}
}
}
else
{
UE_LOG(LogKSGMWeapon, Warning, TEXT("AKSpell::TouchedGround - GroundTemplate is not set."));
}
// Do we have a ground actor ?
// For an actor only spawned on the server side
// never on the client.
if (Role == ROLE_Authority && GroundActor)
{
// If we have a distance gap defined we will need to spawn several actors
if (WeaponConfig.Spell.DistanceGapInRadius > 0.0f)
{
// Initialize
GDistanceFromCenter = 0.0f;
GNumPoints = 1;
// Call a loop timer function to spawn the ground actors
GetWorldTimerManager().SetTimer(SpawnGroundActors_Handle, FTimerDelegate::CreateUObject(this, &AKSpell::SpawnGroundActors, Impact), 0.2f, true);
}
else // Otherwise, we have only one actor to spawn
{
// We apply a scale to fill the area since we have only one actor
FTransform const SpawnTransform(FRotator(0, 0, 0), NudgedImpactLocation, FVector(WeaponConfig.Spell.SpellRadius / GroundActorBoundSize, WeaponConfig.Spell.SpellRadius / GroundActorBoundSize, 1.0f));
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - GetActorLocation=%s"), *SpawnTransform.GetLocation().ToString());
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - GetActorScale=%s"), *SpawnTransform.GetScale3D().ToString());
AKGroundActor* GActor = GetWorld()->SpawnActorDeferred<AKGroundActor>(GroundActor, SpawnTransform);
if (GActor)
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::TouchedGround - Ground Actor spawned=%p"), GActor);
// Do we have deferred collision
if (GActor->DeferredCollisionTime > 0.f)
{
GActor->SetActorEnableCollision(false);
SpawnedGroundActors.Add(GActor);
}
UGameplayStatics::FinishSpawningActor(GActor, SpawnTransform);
// Add the actor to the array
GroundActors.Add(GActor);
// Do we have collisions to sets back
if (SpawnedGroundActors.Num() > 0)
{
// Start the timer to enable back the collision
FTimerHandle TempHandle2;
GetWorldTimerManager().SetTimer(TempHandle2, this, &AKSpell::SetBackCollision2GroundActors, GActor->DeferredCollisionTime, false);
}
}
}
}
bTouchedGround = true;
}
void AKSpell::SpawnEffectActors(const FHitResult Impact)
{
FVector Distance(DistanceFromCenter, 0.0f, 0.0f);
int32 count = EffectActors.Num();
int32 DeltaPoints = NumPoints - count;
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT(“AKSpell::SpawnEffectActors - Role=%s DistanceFromCenter=%f count=%i NumPoints=%i DeltaPoints=%i”), NETROLETOSTRING(Role), DistanceFromCenter, count, NumPoints, DeltaPoints);
for (int32 i = 0; i < DeltaPoints; ++i)
{
float Percent = (float)i / (float)DeltaPoints;
float Angle = FMath::Lerp(0.0f, 360.0f, Percent);
float AngleDeg = FRotator::ClampAxis(Angle * 180.f / PI);
// Randomize the angle to avoid linear effects
Angle = FMath::RandRange(Angle - (360.0f / DeltaPoints / 4), Angle + (360.0f / DeltaPoints / 4));
FVector VertexDir = (i > 0) ? Distance.RotateAngleAxis(Angle, FVector(0.f, 0.f, 1.f)) : FVector(DistanceFromCenter,0.f,0.f);
//VertexDir.Normalize();
FVector FinalImpactLocation = NudgedImpactLocation + VertexDir;
// Set the rotation to zero
FTransform const SpawnTransform(FRotator(0,0,0), FinalImpactLocation);
AKWeaponFireGroundEffect* EffectActor = GetWorld()->SpawnActorDeferred<AKWeaponFireGroundEffect>(GroundTemplate, SpawnTransform);
if (EffectActor)
{
EffectActor->SurfaceHit = Impact;
// Set its life time
EffectActor->GroundEffectLifeTime = WeaponConfig.Spell.SpellLife;
// Set its color based on the damage type
EffectActor->SetColor(GetWeaponConfigDamageTypeColor(WeaponConfig));
UGameplayStatics::FinishSpawningActor(EffectActor, SpawnTransform);
// Add the actor to the array
count = EffectActors.Add(EffectActor) + 1;
// Check if we have to increase the distance from the center
// and calculate the new number of points to reach
if (count >= NumPoints)
{
DistanceFromCenter += WeaponConfig.Spell.DistanceGapInRadius;
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::SpawnEffectActors - DistanceFromCenter=%f WeaponConfig.Spell.DistanceGapInRadius=%f"), DistanceFromCenter, WeaponConfig.Spell.DistanceGapInRadius);
NumPoints += (int32)(DistanceFromCenter / WeaponConfig.Spell.DistanceGapInRadius) * 4;
}
}
}
// Do we reach the end of the circle
if (DistanceFromCenter >= WeaponConfig.Spell.SpellRadius)
{
GetWorldTimerManager().ClearTimer(SpawnEffectActors_Handle);
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::SpawnEffectActors - Reach radius DistanceFromCenter=%f WeaponConfig.Spell.SpellRadius=%f Count=%i"), DistanceFromCenter, WeaponConfig.Spell.SpellRadius, EffectActors.Num());
}
}
void AKSpell::SpawnGroundActors(const FHitResult Impact)
{
FVector Distance(GDistanceFromCenter, 0.0f, 0.0f);
int32 count = EffectActors.Num();
int32 DeltaPoints = GNumPoints - count;
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT(“AKSpell::SpawnGroundActors - Role=%s DistanceFromCenter=%f count=%i NumPoints=%i DeltaPoints=%i”), NETROLETOSTRING(Role), GDistanceFromCenter, count, GNumPoints, DeltaPoints);
float DeferredCollisionTime = 0.0f;
for (int32 i = 0; i < DeltaPoints; ++i)
{
float Percent = (float)i / (float)DeltaPoints;
float Angle = FMath::Lerp(0.0f, 360.0f, Percent);
float AngleDeg = FRotator::ClampAxis(Angle * 180.f / PI);
// Randomize the angle to avoid linear effects
Angle = FMath::RandRange(Angle - (360.0f / DeltaPoints / 4), Angle + (360.0f / DeltaPoints / 4));
FVector VertexDir = (i > 0) ? Distance.RotateAngleAxis(Angle, FVector(0.f, 0.f, 1.f)) : FVector(GDistanceFromCenter, 0.f, 0.f);
FVector FinalImpactLocation = NudgedImpactLocation + VertexDir;
// Set the rotation to zero
// Don't modify the scale since we have several actors to fill the spell area
FTransform const SpawnTransform(FRotator(0, 0, 0), FinalImpactLocation);
AKGroundActor* GActor = GetWorld()->SpawnActorDeferred<AKGroundActor>(GroundActor, SpawnTransform);
if (GActor)
{
// Do we have deferred collision
if ((DeferredCollisionTime = GActor->DeferredCollisionTime) > 0.f)
{
GActor->SetActorCollision(false);
SpawnedGroundActors.Add(GActor);
}
UGameplayStatics::FinishSpawningActor(GActor, SpawnTransform);
// Add the actor to the array
count = GroundActors.Add(GActor) + 1;
// Check if we have to increase the distance from the center
// and calculate the new number of points to reach
if (count >= GNumPoints)
{
GDistanceFromCenter += WeaponConfig.Spell.DistanceGapInRadius;
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::SpawnGroundActors - DistanceFromCenter=%f WeaponConfig.Spell.DistanceGapInRadius=%f"), GDistanceFromCenter, WeaponConfig.Spell.DistanceGapInRadius);
GNumPoints += (int32)(GDistanceFromCenter / WeaponConfig.Spell.DistanceGapInRadius) * 4;
}
}
}
// After having spwaned ground actors in this cycle
// Do we have collisions to sets back
if (SpawnedGroundActors.Num() > 0)
{
// Start the timer to enable back the collision
FTimerHandle TempHandle2;
GetWorldTimerManager().SetTimer(TempHandle2, this, &AKSpell::SetBackCollision2GroundActors, DeferredCollisionTime, false);
}
// Do we reach the end of the circle
if (GDistanceFromCenter >= WeaponConfig.Spell.SpellRadius)
{
GetWorldTimerManager().ClearTimer(SpawnGroundActors_Handle);
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT("AKSpell::SpawnGroundActors - Reach radius DistanceFromCenter=%f WeaponConfig.Spell.SpellRadius=%f Count=%i"), GDistanceFromCenter, WeaponConfig.Spell.SpellRadius, GroundActors.Num());
}
}
void AKSpell::DisableAndDestroy()
{
UE_LOG(LogKSGMWeapon, VeryVerbose, TEXT(“AKSpell::DisableAndDestroy - Role=%s”), NETROLETOSTRING(Role));
UAudioComponent* ProjAudioComp = FindComponentByClass();
if (ProjAudioComp && ProjAudioComp->IsPlaying())
{
ProjAudioComp->FadeOut(0.1f, 0.f);
}
MovementComp->StopMovementImmediately();
// give clients some time to show the effect in case no recurring damage
// in case we had no recurring damages
if (WeaponConfig.Spell.SpellLife > 0 && WeaponConfig.Spell.SpellRecurringDamageInterval > 0 &&
WeaponConfig.Spell.SpellRecurringDamage > 0 && WeaponConfig.Spell.SpellRadius > 0 && WeaponConfig.DamageType)
SetLifeSpan( 2.0f );
// We can deactivate and destroy all the created effects
int32 count = EffectActors.Num();
for (int32 idx = 0; idx < count; ++idx)
{
AKWeaponFireGroundEffect* EffectActor = EffectActors[idx];
if (EffectActor)
EffectActor->DeactivateAndDestroy();
}
EffectActors.Reset();
// and destroy the ground actors
count = GroundActors.Num();
for (int32 idx = 0; idx < count; ++idx)
{
AActor* GActor = GroundActors[idx];
UE_LOG(LogKSGMWeapon, Verbose, TEXT("AKSpell::DisableAndDestroy - Destroy GActor=%p"), GActor);
if (GActor)
GActor->Destroy();
}
GroundActors.Reset();
}