1
0
KF2-Dev-Scripts/KFGame/Classes/KFExplosionActor.uc

448 lines
14 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// KFExplosionActor
//=============================================================================
// Used by projectiles and kismet to spawn an explosion
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFExplosionActor extends GameExplosionActor
dependson(KFImpactEffectInfo)
dependson(KFLightPool);
/** Stores the ImpactEffect based on the PhysicalMaterial **/
var MaterialImpactEffect MyImpactEffect;
var byte NumPawnsKilled;
/** If < 1.f, reduces damage when stacked with other explosives of the same type */
var const float DamageScalePerStack;
/** Priority of this explosion's light in the pool */
var const LightPoolPriority ExplosionLightPriority;
/**
* @todo break this up into the same methods that <Game>Weapon uses (SpawnImpactEffects, SpawnImpactSounds, SpawnImpactDecal) as they are all
* orthogonal and so indiv subclasses can choose to have base functionality or override
*
* @param Direction For bDirectionalExplosion=true explosions, this is the forward direction of the blast.
* Overridden to add the ability to spawn fragments from the explosion
**/
simulated function Explode(GameExplosion NewExplosionTemplate, optional vector Direction)
{
local ParticleSystem ParticleTemplateOverride;
local KFGameExplosion TemplateExplosion;
TemplateExplosion = KFGameExplosion(NewExplosionTemplate);
if( WorldInfo.NetMode != NM_DedicatedServer )
{
// If we're not doing per material fx, grab the default
if( !NewExplosionTemplate.bAllowPerMaterialFX && TemplateExplosion != none && TemplateExplosion.ExplosionEffects != none )
{
ParticleTemplateOverride = TemplateExplosion.ExplosionEffects.DefaultImpactEffect.ParticleTemplate;
if ( ParticleTemplateOverride != None )
{
NewExplosionTemplate.ParticleEmitterTemplate = ParticleTemplateOverride;
}
}
}
super.Explode(NewExplosionTemplate, Direction);
if( TemplateExplosion != none && TemplateExplosion.NumShards > 0 && TemplateExplosion.ShardClass != none )
{
SpawnShards(NewExplosionTemplate, TemplateExplosion.NumShards, TemplateExplosion.ShardClass);
}
// Register explosion light with lightpool
if( ExplosionLight != none && ExplosionLight.bAttached && ExplosionLight.bEnabled )
{
`LightPool.RegisterPointLight( ExplosionLight, ExplosionLightPriority );
}
}
/** During explosion, spawn additional shard/shrapnel projectiles */
simulated function SpawnShards(GameExplosion NewExplosionTemplate, int NumShards, class<Projectile> ShardClass, optional int PitchShardMin=10, optional int PitchShardMax=35)
{
local vector SpawnPos;
local actor HitActor;
local rotator rot;
local int i;
local Projectile NewChunk;
local vector HitLocation, HitNormal;
local int YawShardPosition;
local int YawShardIncrement;
// Disperse NumShards across 360 degrees, YawShardPosition is increased by this amount with each shard
YawShardIncrement = 360 / NumShards;
SpawnPos = NewExplosionTemplate.HitLocation + 10 * NewExplosionTemplate.HitNormal;
HitActor = Trace(HitLocation, HitNormal, SpawnPos, NewExplosionTemplate.HitLocation, false);
if (HitActor != None)
{
SpawnPos = HitLocation;
}
else
{
HitNormal = NewExplosionTemplate.HitNormal;
}
// Spawn the shards only on the server
if ( Instigator != none && Instigator.Role == ROLE_Authority )
{
if(bDrawDebug)
{
DrawDebugLine(SpawnPos,SpawnPos + HitNormal * 1000.0,0,255,0,TRUE);
}
// Spawn the number of shards we need
for (i = 0; i < NumShards; i++)
{
rot.Pitch = RandRange( PitchShardMin, PitchShardMax ) * DegToUnrRot;
YawShardPosition += YawShardIncrement;
rot.Yaw = YawShardPosition * DegToUnrRot;
NewChunk = Spawn(ShardClass,Instigator != none ? Instigator.Weapon : self,, SpawnPos, rot);
if(bDrawDebug)
{
DrawDebugLine(SpawnPos,SpawnPos + normal(vector(rot)) * 500.0,255,0,0,TRUE);
}
if (NewChunk != None)
{
NewChunk.Init(vector(rot));
}
}
}
}
/**
* Internal. Extract what data we can from the physical material-based effects system
* and stuff it into the ExplosionTemplate.
* Data in the physical material will take precedence.
*
* We are also going to be checking for relevance here as when any of these params are "none" / invalid we do not
* play those effects in Explode(). So this way we avoid any work on looking things up in the physmaterial
*
*/
simulated protected function UpdateExplosionTemplateWithPerMaterialFX(PhysicalMaterial PhysMaterial)
{
// Set a default impact effect if there isn't a physical material
if( PhysMaterial == none )
{
MyImpactEffect = KFGameExplosion(ExplosionTemplate).ExplosionEffects.DefaultImpactEffect;
}
else if( WorldInfo.MyImpactEffectManager != none ) // none on dedicated server
{
`ImpactEffectManager.GetImpactEffect(PhysMaterial, MyImpactEffect,KFGameExplosion(ExplosionTemplate).ExplosionEffects);
}
if( MyImpactEffect.ParticleTemplate != none )
{
ExplosionTemplate.ParticleEmitterTemplate = MyImpactEffect.ParticleTemplate;
}
}
simulated function SpawnExplosionParticleSystem(ParticleSystem Template)
{
// If the template is none, grab the default
if( !ExplosionTemplate.bAllowPerMaterialFX && Template == none )
{
Template = KFGameExplosion(ExplosionTemplate).ExplosionEffects.DefaultImpactEffect.ParticleTemplate;
}
WorldInfo.MyEmitterPool.SpawnEmitter(Template, Location, rotator(ExplosionTemplate.HitNormal), None);
}
simulated function SetSyncToMuzzleLocation(bool bSync)
{
//empty in this class
}
simulated function SpawnExplosionDecal()
{
local MaterialInterface MI;
local MaterialInstanceTimeVarying MITV_Decal;
local int DecalMaterialsLength;
local float DecalSize, DecalThickness;
local KFGameExplosion KFExplosionTemplate;
if( WorldInfo.bDropDetail )
{
return;
}
// If the template is none, grab the default
if( !ExplosionTemplate.bAllowPerMaterialFX )
{
KFExplosionTemplate = KFGameExplosion(ExplosionTemplate);
if( KFExplosionTemplate == none || KFExplosionTemplate.ExplosionEffects == none )
{
return;
}
MyImpactEffect = KFExplosionTemplate.ExplosionEffects.DefaultImpactEffect;
}
// if we have a decal to spawn on impact
DecalMaterialsLength = MyImpactEffect.DecalMaterials.length;
if( DecalMaterialsLength > 0 )
{
MI = MyImpactEffect.DecalMaterials[Rand(DecalMaterialsLength)];
if( MI != None )
{
DecalSize = RandRange(MyImpactEffect.DecalMinSize, MyImpactEffect.DecalMaxSize);
//Always extend decal thickness for explosions
DecalThickness = DecalSize * 2.f;
if( MaterialInstanceTimeVarying(MI) != none )
{
MITV_Decal = new(self) class'MaterialInstanceTimeVarying';
MITV_Decal.SetParent( MI );
WorldInfo.ExplosionDecalManager.SpawnDecal(MITV_Decal, ExplosionTemplate.HitLocation, rotator(-ExplosionTemplate.HitNormal), DecalSize, DecalSize, DecalThickness, FALSE,(MyImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0) );
//here we need to see if we are an MITV and then set the burn out times to occur
MITV_Decal.SetScalarStartTime( MyImpactEffect.DecalDissolveParamName, MyImpactEffect.DecalDuration );
}
else
{
WorldInfo.ExplosionDecalManager.SpawnDecal( MI, ExplosionTemplate.HitLocation, rotator(-ExplosionTemplate.HitNormal), DecalSize, DecalSize, DecalThickness, true,
(MyImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0),,,,,,, MyImpactEffect.DecalDuration );
}
}
}
}
/**
* Handle making pawns ignite, cringe or fall down from nearby explosions. Server only.
*/
protected function SpecialPawnEffectsFor(GamePawn VictimPawn, float VictimDist)
{
//local KFPawn_Monster KFM;
//local class<KFDamageType> KFDT;
local KFPawn_Human HumanVictim;
local KFGameExplosion KFExplosionTemplate;
// Victim may have just been killed by damage - if so don't cringe
if (VictimPawn.bRespondToExplosions && VictimPawn.Health > 0)
{
// Disabled for now. All effects are played via the AfflictionHandler
}
if ( VictimPawn != none )
{
if ( VictimPawn.bPlayedDeath && KFPawn( VictimPawn ).TimeOfDeath == WorldInfo.TimeSeconds )
{
NumPawnsKilled++;
}
}
// support for healing humans on explosion impact
HumanVictim = KFPawn_Human(VictimPawn);
KFExplosionTemplate = KFGameExplosion(ExplosionTemplate);
if (HumanVictim != none && KFExplosionTemplate != none && KFExplosionTemplate.HealingAmount > 0)
{
HumanVictim.HealDamage(KFExplosionTemplate.HealingAmount, InstigatorController, KFExplosionTemplate.HealingDamageType);
}
}
protected function bool KnockdownPawn(BaseAiPawn Victim, float DistFromExplosion)
{
local KFPawn KFP;
KFP = KFPawn(Victim);
if (KFP != None)
{
// Note we double the Radius. This ensures the applied force at the furthest possible
// knockdown is 50% of the KnockdownStrength. Going ragdoll with zero explosion force tends to feel odd.
KFP.LastHitBy = InstigatorController; // so proper kill credit is given if this knocks the Pawn outside the world
KFP.Knockdown(, vect(1,1,1), Location, ExplosionTemplate.DamageRadius * 2.0, ExplosionTemplate.KnockDownStrength);
}
return (Victim.Physics == PHYS_RigidBody);
}
protected function bool StumblePawn(BaseAiPawn Victim, float DistFromExplosion)
{
local KFPawn KFP;
KFP = KFPawn(Victim);
if (KFP != None)
{
KFP.DoSpecialMove(SM_Stumble,,, class'KFSM_Stumble'.static.PackBodyHitSMFlags(KFP, Normal(KFP.Location - ExplosionTemplate.HitLocation)));
return KFP.IsDoingSpecialMove(SM_Stumble);
}
return false;
}
protected simulated function bool DoExplosionDamage(bool bCauseDamage, bool bCauseEffects)
{
local bool bReturn;
NumPawnsKilled = 0;
bReturn = super.DoExplosionDamage( bCauseDamage, bCauseEffects );
// Maybe trigger a dramatic event for multi kills with explosives
if( Role == ROLE_Authority && KFGameInfo(WorldInfo.Game) != none )
{
if( NumPawnsKilled >= 4 )
{
KFGameInfo(WorldInfo.Game).DramaticEvent(0.05);
}
else if ( NumPawnsKilled >= 2 )
{
KFGameInfo(WorldInfo.Game).DramaticEvent(0.03);
}
}
return bReturn;
}
simulated function DrawDebug()
{
local Color C;
local float Angle;
local float ClotKillRadius, HalfFalloffRadius;
FlushPersistentDebugLines();
// debug spheres
if (ExplosionTemplate.bDirectionalExplosion)
{
C.R = 255;
C.G = 128;
C.B = 16;
C.A = 255;
Angle = ExplosionTemplate.DirectionalExplosionAngleDeg * DegToRad;
DrawDebugCone(Location, ExplosionDirection, ExplosionTemplate.DamageRadius, Angle, Angle, 8, C, TRUE);
}
else
{
DrawDebugSphere(Location, ExplosionTemplate.DamageRadius, 10, 255, 128, 16, TRUE);
ClotKillRadius = ExplosionTemplate.DamageRadius * (1.f - FClamp((100 / ExplosionTemplate.Damage) ** (1/ExplosionTemplate.DamageFallOffExponent), 0.f, 1.f));
DrawDebugSphere(Location, ClotKillRadius, 10, 255, 0, 0, TRUE);
HalfFalloffRadius = ExplosionTemplate.DamageRadius * (1.f - FClamp((0.5 ** (1.f/ExplosionTemplate.DamageFalloffExponent)), 0.f, 1.f));
DrawDebugSphere( Location, HalfFalloffRadius, 10, 255, 63, 0, true );
}
}
/** Add number of stacks and return damage scale between 0-1 */
simulated function float CalcStackingDamageScale(KFPawn Victim, float MinDamageInterval)
{
local int i, ExistingIdx;
local float DamageMod;
local GameExplosionActor OtherExplosion;
local ExplosiveStackInfo NewStackInfo;
if ( DamageScalePerStack >= 1.f || Victim == None )
{
return 1.f; // no modifier
}
DamageMod = 1.f;
ExistingIdx = INDEX_NONE;
for (i = Victim.RecentExplosiveStacks.Length-1; i >= 0; --i)
{
OtherExplosion = Victim.RecentExplosiveStacks[i].Explosion;
// If self, keep index for later
if ( OtherExplosion == self )
{
ExistingIdx = i;
}
if ( OtherExplosion == None )
{
// cleanup destroyed actors
Victim.RecentExplosiveStacks.Remove(i, 1);
// We need to decrement ExistingIdx if it was a higher index than the one we just removed
if( ExistingIdx != INDEX_NONE && ExistingIdx > i )
{
--ExistingIdx;
}
}
else if ( OtherExplosion.ExplosionTemplate.MyDamageType == ExplosionTemplate.MyDamageType
&& `TimeSince(Victim.RecentExplosiveStacks[i].LastHitTime) < MinDamageInterval )
{
// found match!
DamageMod *= DamageScalePerStack;
}
}
// If damage mod is non-zero add a stack
if ( DamageMod > 0.f )
{
if ( ExistingIdx != INDEX_NONE )
{
Victim.RecentExplosiveStacks[ExistingIdx].LastHitTime = WorldInfo.TimeSeconds;
}
else
{
NewStackInfo.Explosion = self;
NewStackInfo.LastHitTime = WorldInfo.TimeSeconds;
Victim.RecentExplosiveStacks.AddItem(NewStackInfo);
}
}
return DamageMod;
}
/** Improved cone collision for pawns */
simulated protected function bool IsBehindExplosion(Actor A)
{
local KFPawn P;
local vector TorsoLoc;
if (ExplosionTemplate.bDirectionalExplosion && !IsZero(ExplosionDirection))
{
if ( !Super.IsBehindExplosion(A) )
{
return FALSE;
}
// Extra collision cone at torso
P = KFPawn(A);
if ( P != None && P.TorsoBoneName != '' )
{
TorsoLoc = P.Mesh.GetBoneLocation(P.TorsoBoneName);
return (ExplosionDirection dot Normal(TorsoLoc - Location)) < DirectionalExplosionMinDot;
}
}
return FALSE;
}
/** Level was reset without reloading */
function Reset()
{
Destroy();
}
defaultproperties
{
//DurationOfDecal=24.0
//DecalDissolveParamName="DissolveAmount"
//ExplosionDecal=MaterialInstanceConstant'FX_Impacts_MAT.FX_Grenade_Impact_DM'
// Disable the RadialImpulseComponent. Collision detection is hit & miss
// and it fights with KFPawn Impulse system [aladenberger 1/24/2014]
ExplosionLightPriority=LPP_High
RadialImpulseComponent=None
Components.Remove(ImpulseComponent0)
DamageScalePerStack=1.f
}