807 lines
27 KiB
Ucode
807 lines
27 KiB
Ucode
/**
|
|
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
|
*/
|
|
class GameExplosionActor extends Actor
|
|
abstract
|
|
config(Weapon)
|
|
native;
|
|
|
|
/** True if explosion has occurred. */
|
|
var protected transient bool bHasExploded;
|
|
|
|
/** True if this actor can explode more than once and doesn't die after an explosion.
|
|
* Used by placeable actors whose explosions are triggered via matinee
|
|
*/
|
|
var() protected bool bExplodeMoreThanOnce;
|
|
|
|
/** The actual light used for the explosion. */
|
|
var protected transient PointLightComponent ExplosionLight;
|
|
|
|
`if(`__TW_)
|
|
/** Temp data for light flickering. */
|
|
var protected transient float LastLightBrightness;
|
|
var protected transient float LightFlickerIntensity;
|
|
var protected transient float LightFlickerInterpSpeed;
|
|
/** Temp data for light fading. */
|
|
var protected transient float LightFadeStartTime;
|
|
`endif // __TW_
|
|
|
|
/** Radial blur for explosion */
|
|
var protected transient RadialBlurComponent ExplosionRadialBlur;
|
|
|
|
/** Temp data for light fading. */
|
|
var protected transient float LightFadeTime;
|
|
var protected transient float LightFadeTimeRemaining;
|
|
var protected transient float LightInitialBrightness;
|
|
|
|
/** Temp data for radial blur fading. */
|
|
var protected transient float RadialBlurFadeTime;
|
|
var protected transient float RadialBlurFadeTimeRemaining;
|
|
var protected transient float RadialBlurMaxBlurAmount;
|
|
|
|
/** Temp reference to the explosion template, used for delayed damage */
|
|
var GameExplosion ExplosionTemplate;
|
|
|
|
/**
|
|
* If TRUE, take the Explosion ParticleSystem lifespan into account when determining
|
|
* the lifespan of the GameExplosionActor. This is useful in cases where the GEA
|
|
* needs to do further processing while the particle system is active.
|
|
* For example, in the case of a smoke grenade, you would want to ensure that the
|
|
* explosion actor stayed around long enough to properly trigger coughing, etc. when
|
|
* a pawn enters/exits the smoke area.
|
|
*/
|
|
var bool bTrackExplosionParticleSystemLifespan;
|
|
|
|
/** Used to push physics when explosion goes off. */
|
|
var protected RB_RadialImpulseComponent RadialImpulseComponent;
|
|
|
|
/** player responsible for damage */
|
|
var Controller InstigatorController;
|
|
|
|
/** This the saved off hit actor and location from the GetPhysicalMaterial trace so we can see if it is a FluidSurfaceActor and then apply some forces to it **/
|
|
var Actor HitActorFromPhysMaterialTrace;
|
|
var vector HitLocationFromPhysMaterialTrace;
|
|
|
|
/** Are we attached to something? Used to attach FX for stuff like the smoke grenade. */
|
|
var Actor Attachee;
|
|
var Controller AttacheeController;
|
|
|
|
/** Minimum dot product for explosion to be able to affect a point. Used as an optimization for directional explosions. */
|
|
var transient float DirectionalExplosionMinDot;
|
|
/** Forward dir for directional explosions. */
|
|
var transient vector ExplosionDirection;
|
|
|
|
/** Toggles debug explosion rendering. */
|
|
`if(`__TW_)
|
|
var config bool bDrawDebug;
|
|
`else
|
|
var bool bDrawDebug;
|
|
`endif // __TW_
|
|
|
|
replication
|
|
{
|
|
if (bNetInitial)
|
|
ExplosionDirection;
|
|
}
|
|
|
|
cpptext
|
|
{
|
|
virtual void TickSpecial(FLOAT DeltaSeconds);
|
|
}
|
|
|
|
`if(`__TW_)
|
|
// Custom line trace for more specific TraceFlags control
|
|
function Actor TraceExplosive( vector TraceEnd, vector TraceStart )
|
|
{
|
|
return StaticTraceExplosive( TraceEnd, TraceStart, self );
|
|
}
|
|
native static function Actor StaticTraceExplosive(vector TraceEnd, vector TraceStart, Actor SourceActor);
|
|
`endif
|
|
|
|
event PreBeginPlay()
|
|
{
|
|
Super.PreBeginPlay();
|
|
|
|
if (Instigator != None && InstigatorController == None)
|
|
{
|
|
InstigatorController = Instigator.Controller;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal. Tries to find a physical material for the surface the explosion occurred upon.
|
|
* @note: It sucks doing an extra trace here. We could conceivably pass the physical material info around
|
|
* by changing the lower level physics code (e.g. processHitWall), but that's a big engine-level change.
|
|
*/
|
|
simulated protected function PhysicalMaterial GetPhysicalMaterial()
|
|
{
|
|
local PhysicalMaterial Retval;
|
|
local vector TraceStart, TraceDest, OutHitNorm, ExploNormal;
|
|
local TraceHitInfo OutHitInfo;
|
|
|
|
// here we have to do an additional trace shooting straight down to see if we are under water.
|
|
TraceStart = Location + (vect(0,0,1) * 256.f);
|
|
TraceDest = Location - (vect(0,0,1) * 16.f);
|
|
|
|
HitActorFromPhysMaterialTrace = Trace(HitLocationFromPhysMaterialTrace, OutHitNorm, TraceDest, TraceStart, TRUE, vect(0,0,0), OutHitInfo, TRACEFLAG_Bullet|TRACEFLAG_PhysicsVolumes);
|
|
//DrawDebugLine( TraceStart, TraceDest, 0, 255, 0, TRUE);
|
|
//`log("EXPLOSION SURFACE:"@HitActorFromPhysMaterialTrace);
|
|
//DrawDebugCoordinateSystem( TraceStart, Rotation, 10.0f, TRUE );
|
|
|
|
if( FluidSurfaceActor(HitActorFromPhysMaterialTrace) != None )
|
|
{
|
|
Retval = OutHitInfo.PhysMaterial;
|
|
return Retval;
|
|
}
|
|
|
|
ExploNormal = vector(Rotation);
|
|
TraceStart = Location + (ExploNormal * 8.f);
|
|
TraceDest = TraceStart - (ExploNormal * 64.f);
|
|
|
|
HitActorFromPhysMaterialTrace = Trace(HitLocationFromPhysMaterialTrace, OutHitNorm, TraceDest, TraceStart, TRUE, vect(0,0,0), OutHitInfo, TRACEFLAG_Bullet);
|
|
//DrawDebugLine( TraceStart, TraceDest, 0, 255, 0, TRUE);
|
|
//DrawDebugCoordinateSystem( TraceStart, Rotation, 10.0f, TRUE );
|
|
|
|
if( HitActorFromPhysMaterialTrace != None )
|
|
{
|
|
Retval = OutHitInfo.PhysMaterial;
|
|
}
|
|
|
|
return Retval;
|
|
}
|
|
|
|
simulated function bool DoFullDamageToActor(Actor Victim)
|
|
{
|
|
return (Victim.bStatic || Victim.IsA('KActor') || Victim.IsA('InterpActor') || Victim.IsA('FracturedStaticMeshPart'));
|
|
}
|
|
|
|
simulated protected function bool IsBehindExplosion(Actor A)
|
|
{
|
|
if (ExplosionTemplate.bDirectionalExplosion && !IsZero(ExplosionDirection))
|
|
{
|
|
// @todo, for certain types of actors (e.g. large actors), we may want to test a location other than the
|
|
// actor's location. Like a cone/bbox check or something.
|
|
// @todo, maybe use Actor's bbox center, like damage does below?
|
|
return (ExplosionDirection dot Normal(A.Location - Location)) < DirectionalExplosionMinDot;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Returns distance from bounding box to point
|
|
*/
|
|
final static native function float BoxDistanceToPoint(vector Start, Box BBox);
|
|
|
|
/**
|
|
* Does damage modeling and application for explosions
|
|
* @PARAM bCauseDamage if true cause damage to actors within damage radius
|
|
* @PARAM bCauseEffects if true apply other affects to actors within appropriate radii
|
|
* @RETURN TRUE if at least one Pawn victim got hurt. (This is only valid if bCauseDamage == TRUE)
|
|
*/
|
|
protected simulated function bool DoExplosionDamage(bool bCauseDamage, bool bCauseEffects)
|
|
{
|
|
`if(`__TW_)
|
|
local Actor Victim, HitActor;
|
|
local vector BBoxCenter;
|
|
`else
|
|
local Actor Victim, HitActor;
|
|
local vector HitL, HitN, Dir, BBoxCenter;//, BBoxExtent;
|
|
`endif
|
|
local bool bDamageBlocked, bDoFullDamage, bCauseFractureEffects, bCausePawnEffects, bCauseDamageEffects, bHurtSomeone, bAdjustRadiusDamage;
|
|
local float ColRadius, ColHeight, CheckRadius, VictimDist, VictimColRadius, VictimColHeight;
|
|
local array<Actor> VictimsList;
|
|
local Box BBox;
|
|
local Controller ModInstigator;
|
|
local GamePawn VictimPawn;
|
|
local FracturedStaticMeshActor FracActor;
|
|
local byte WantPhysChunksAndParticles;
|
|
local TraceHitInfo HitInfo;
|
|
local KActorFromStatic NewKActor;
|
|
local StaticMeshComponent HitStaticMesh;
|
|
|
|
bAdjustRadiusDamage = true;
|
|
|
|
// can pre-calculate this condition now
|
|
bCauseFractureEffects = bCauseEffects && WorldInfo.NetMode != NM_DedicatedServer && ExplosionTemplate.bCausesFracture;
|
|
bCauseEffects = bCauseEffects && WorldInfo.NetMode != NM_Client;
|
|
bHurtSomeone = FALSE;
|
|
|
|
// determine radius to check
|
|
CheckRadius = GetEffectCheckRadius(bCauseDamage, bCauseFractureEffects, bCauseEffects);
|
|
if ( CheckRadius > 0.0 )
|
|
{
|
|
foreach CollidingActors(class'Actor', Victim, CheckRadius, Location, ExplosionTemplate.bUseOverlapCheck,,HitInfo )
|
|
{
|
|
// check for static mesh that can become dynamic
|
|
if ( Victim.bWorldGeometry )
|
|
{
|
|
HitStaticMesh = StaticMeshComponent(HitInfo.HitComponent);
|
|
`if(`__TW_)
|
|
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() &&
|
|
!WorldInfo.bDropDetail && WorldInfo.GetDetailMode() > DM_Low )
|
|
`else
|
|
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() )
|
|
`endif
|
|
{
|
|
NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh);
|
|
if ( NewKActor != None )
|
|
{
|
|
Victim = NewKActor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for things that are not yourself and not world geom
|
|
if ( Victim != Self
|
|
&& (!Victim.bWorldGeometry || Victim.bCanBeDamaged)
|
|
&& (NavigationPoint(Victim) == None)
|
|
&& Victim != ExplosionTemplate.ActorToIgnoreForDamage
|
|
&& (!ExplosionTemplate.bIgnoreInstigator || Victim != Instigator)
|
|
&& !ClassIsChildOf(Victim.Class, ExplosionTemplate.ActorClassToIgnoreForDamage)
|
|
&& !IsBehindExplosion(Victim) )
|
|
{
|
|
// If attached to a pawn and victim is a pawn on other team
|
|
VictimPawn = GamePawn(Victim);
|
|
|
|
// check if visible, unless physics object
|
|
// note: using bbox center instead of location, because that's what visiblecollidingactors does
|
|
Victim.GetComponentsBoundingBox(BBox);
|
|
|
|
// adjust distance if using overlap check
|
|
if ( ExplosionTemplate.bUseOverlapCheck )
|
|
{
|
|
VictimDist = BoxDistanceToPoint(Location, BBox);
|
|
}
|
|
else
|
|
{
|
|
VictimDist = VSize(Location - Victim.Location);
|
|
}
|
|
|
|
if (ExplosionTemplate.bDoCylinderCheck)
|
|
{
|
|
Victim.GetBoundingCylinder(ColRadius, ColHeight);
|
|
Instigator.GetBoundingCylinder(VictimColRadius, VictimColHeight);
|
|
|
|
// If the distance between them is more than the size of half the height of both cilinders, don't consider as target
|
|
if (Abs(Victim.Location.Z - Instigator.Location.Z) >= (ColHeight * 0.5f) + (VictimColHeight * 0.5f))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Do fracturing
|
|
if( bCauseFractureEffects && (VictimPawn == None) )
|
|
{
|
|
FracActor = FracturedStaticMeshActor(Victim);
|
|
if ( (FracActor != None)
|
|
&& (VictimDist < ExplosionTemplate.FractureMeshRadius)
|
|
&& (FracActor.Physics == PHYS_None)
|
|
&& FracActor.IsFracturedByDamageType(ExplosionTemplate.MyDamageType)
|
|
&& FracActor.FractureEffectIsRelevant( false, Instigator, WantPhysChunksAndParticles) )
|
|
{
|
|
// Let kismet know that we were hit by an explosion
|
|
FracActor.NotifyHitByExplosion(InstigatorController, ExplosionTemplate.Damage, ExplosionTemplate.MyDamageType);
|
|
|
|
FracActor.BreakOffPartsInRadius(Location, ExplosionTemplate.FractureMeshRadius, ExplosionTemplate.FracturePartVel, WantPhysChunksAndParticles == 1 ? true : false);
|
|
}
|
|
}
|
|
|
|
bCausePawnEffects = bCauseEffects && (VictimPawn != None) && !VictimPawn.InGodMode();
|
|
bCauseDamageEffects = bCauseDamage && (VictimDist < ExplosionTemplate.DamageRadius);
|
|
|
|
// skip line check for some objects
|
|
if ( DoFullDamageToActor(Victim) )
|
|
{
|
|
bDamageBlocked = FALSE;
|
|
bDoFullDamage = TRUE; // force full damage for these objects
|
|
}
|
|
else if ( bCausePawnEffects || bCauseDamageEffects )
|
|
{
|
|
BBoxCenter = (BBox.Min + BBox.Max) * 0.5f;
|
|
`if(`__TW_)
|
|
HitActor = TraceExplosive(BBoxCenter, Location + vect(0, 0, 20));
|
|
`else
|
|
HitActor = Trace(HitL, HitN, BBoxCenter, Location + vect(0, 0, 20), FALSE,,,TRACEFLAG_Bullet);
|
|
`endif
|
|
bDamageBlocked = (HitActor != None && HitActor != Victim);
|
|
//`endif
|
|
bDoFullDamage = FALSE;
|
|
}
|
|
|
|
if (ExplosionTemplate.bAlwaysFullDamage)
|
|
{
|
|
bAdjustRadiusDamage = false;
|
|
bDoFullDamage = true;
|
|
}
|
|
|
|
if ( !bDamageBlocked )
|
|
{
|
|
if ( bCauseDamageEffects )
|
|
{
|
|
// apply damage
|
|
ModInstigator = InstigatorController;
|
|
|
|
// Same team check always returns FALSE if PRI is None
|
|
if (AttacheeController != None && AttacheeController.PlayerReplicationInfo != None && VictimPawn != None && !WorldInfo.GRI.OnSameTeam(AttacheeController, VictimPawn.Controller))
|
|
{
|
|
ModInstigator = AttacheeController; // Make the instigator the base pawn's controller
|
|
}
|
|
|
|
`if(`__TW_)
|
|
Victim.TakeRadiusDamage(ModInstigator, GetDamageFor(Victim), ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType, ExplosionTemplate.MomentumTransferScale, Location, bDoFullDamage, (Owner != None) ? Owner : self, ExplosionTemplate.DamageFalloffExponent, bAdjustRadiusDamage);
|
|
`else
|
|
Victim.TakeRadiusDamage(ModInstigator, ExplosionTemplate.Damage, ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType, ExplosionTemplate.MomentumTransferScale, Location, bDoFullDamage, (Owner != None) ? Owner : self, ExplosionTemplate.DamageFalloffExponent, bAdjustRadiusDamage);
|
|
`endif
|
|
|
|
VictimsList[VictimsList.Length] = Victim;
|
|
|
|
if( Victim.IsA('Pawn') )
|
|
{
|
|
bHurtSomeone = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( bCausePawnEffects )
|
|
{
|
|
SpecialPawnEffectsFor(VictimPawn, VictimDist);
|
|
}
|
|
else if (bCauseEffects)
|
|
{
|
|
SpecialCringeEffectsFor(Victim, VictimDist);
|
|
}
|
|
}
|
|
}
|
|
`if(`__TW_)
|
|
//Allow the explosion to handle behavior related to actors in range that are ignored
|
|
HandleIgnoredVictim(Victim);
|
|
`endif
|
|
}
|
|
|
|
if (ExplosionTemplate.bFullDamageToAttachee && VictimsList.Find(Attachee) == INDEX_NONE)
|
|
{
|
|
Victim = Attachee;
|
|
|
|
Victim.GetBoundingCylinder(ColRadius, ColHeight);
|
|
|
|
`if(`__TW_)
|
|
Victim.TakeRadiusDamage(InstigatorController, GetDamageFor(Victim), ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType,
|
|
ExplosionTemplate.MomentumTransferScale, Location, true, (Owner != None) ? Owner : self);
|
|
`else
|
|
Dir = Normal(Victim.Location - Location);
|
|
Victim.TakeDamage( ExplosionTemplate.Damage, InstigatorController, Victim.Location - 0.5 * (ColHeight + ColRadius) * dir,
|
|
(ExplosionTemplate.MomentumTransferScale * Dir), ExplosionTemplate.MyDamageType,, (Owner != None) ? Owner : self );
|
|
`endif
|
|
}
|
|
}
|
|
return bHurtSomeone;
|
|
}
|
|
|
|
`if(`__TW_)
|
|
function HandleIgnoredVictim(Actor Victim);
|
|
`endif
|
|
|
|
/** Return the desired radius to check for actors which get effects from explosion */
|
|
function float GetEffectCheckRadius(bool bCauseDamage, bool bCauseFractureEffects, bool bCauseEffects)
|
|
{
|
|
local float CheckRadius;
|
|
|
|
if ( bCauseFractureEffects )
|
|
{
|
|
CheckRadius = ExplosionTemplate.FractureMeshRadius;
|
|
}
|
|
if ( bCauseDamage )
|
|
{
|
|
CheckRadius = FMax(CheckRadius, ExplosionTemplate.DamageRadius);
|
|
}
|
|
if ( bCauseEffects )
|
|
{
|
|
CheckRadius = FMax(CheckRadius, ExplosionTemplate.KnockDownRadius);
|
|
CheckRadius = FMax(CheckRadius, ExplosionTemplate.CringeRadius);
|
|
}
|
|
return CheckRadius;
|
|
}
|
|
|
|
`if(`__TW_)
|
|
/** Gets explosion damage for specific target (allows children to override) */
|
|
simulated function float GetDamageFor( Actor Victim )
|
|
{
|
|
return ExplosionTemplate.Damage;
|
|
}
|
|
`endif
|
|
|
|
/**
|
|
* Handle making pawns cringe or fall down from nearby explosions. Server only.
|
|
*/
|
|
protected function SpecialPawnEffectsFor(GamePawn VictimPawn, float VictimDist);
|
|
|
|
/**
|
|
* Handle applying cringe to non-pawn actors
|
|
*
|
|
* @param Victim - the actor hit
|
|
*
|
|
* @param VictimDist - the distance the victim was from the blast
|
|
*/
|
|
protected function SpecialCringeEffectsFor(Actor Victim, float VictimDist);
|
|
|
|
/**
|
|
* 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);
|
|
|
|
simulated function SpawnExplosionParticleSystem(ParticleSystem Template);
|
|
|
|
simulated function SpawnExplosionDecal();
|
|
|
|
simulated function SpawnExplosionFogVolume();
|
|
|
|
/**
|
|
* @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.
|
|
**/
|
|
simulated function Explode(GameExplosion NewExplosionTemplate, optional vector Direction)
|
|
{
|
|
local float HowLongToLive;
|
|
local PhysicalMaterial PhysMat;
|
|
local bool bHurtSomeone;
|
|
|
|
// copy our significant data
|
|
ExplosionTemplate = NewExplosionTemplate;
|
|
if (ExplosionTemplate.bDirectionalExplosion)
|
|
{
|
|
ExplosionDirection = Normal(Direction);
|
|
DirectionalExplosionMinDot = Cos(ExplosionTemplate.DirectionalExplosionAngleDeg * DegToRad);
|
|
}
|
|
|
|
// by default, live just long enough to go boom
|
|
HowLongToLive = LifeSpan + ExplosionTemplate.DamageDelay + 0.01f;
|
|
|
|
if (!bHasExploded || bExplodeMoreThanOnce )
|
|
{
|
|
// maybe find the physical material and extract the properties we need
|
|
if (ExplosionTemplate.bAllowPerMaterialFX)
|
|
{
|
|
PhysMat = GetPhysicalMaterial();
|
|
|
|
`if(`__TW_)
|
|
// Go ahead and update anyway, if its none or not so that we KNOW that it's none and can handle appropriately
|
|
UpdateExplosionTemplateWithPerMaterialFX(PhysMat);
|
|
`else
|
|
if (PhysMat != None)
|
|
{
|
|
UpdateExplosionTemplateWithPerMaterialFX(PhysMat);
|
|
}
|
|
`endif // __TW_
|
|
}
|
|
|
|
// spawn explosion effects
|
|
if( WorldInfo.NetMode != NM_DedicatedServer )
|
|
{
|
|
if( ExplosionTemplate.ParticleEmitterTemplate != none )
|
|
{
|
|
SpawnExplosionParticleSystem(ExplosionTemplate.ParticleEmitterTemplate);
|
|
if (bTrackExplosionParticleSystemLifespan == TRUE)
|
|
{
|
|
// Let the particle system contribute to life span determination...
|
|
HowLongToLive = FMax(ExplosionTemplate.ParticleEmitterTemplate.GetMaxLifespan(0.0f) + 0.1f, HowLongToLive);
|
|
}
|
|
}
|
|
|
|
// spawn a decal
|
|
SpawnExplosionDecal();
|
|
|
|
// turn on the light
|
|
`if(`__TW_)
|
|
if (ExplosionTemplate.ExploLight != None && WorldInfo.bAllowExplosionLights && !WorldInfo.bDropDetail )
|
|
`else
|
|
if (ExplosionTemplate.ExploLight != None)
|
|
`endif
|
|
{
|
|
if( ExplosionLight != None )
|
|
{
|
|
// If there is already an explosion light, detach it
|
|
DetachComponent(ExplosionLight);
|
|
}
|
|
|
|
// construct a copy of the PLC, turn it on
|
|
ExplosionLight = new(self) class'PointLightComponent' (ExplosionTemplate.ExploLight);
|
|
if (ExplosionLight != None)
|
|
{
|
|
AttachComponent(ExplosionLight);
|
|
ExplosionLight.SetEnabled(TRUE);
|
|
SetTimer(ExplosionTemplate.ExploLightFadeOutTime);
|
|
LightFadeTime = ExplosionTemplate.ExploLightFadeOutTime;
|
|
LightFadeTimeRemaining = LightFadeTime;
|
|
`if(`__TW_)
|
|
LightFadeStartTime = ExplosionTemplate.ExploLightStartFadeOutTime;
|
|
LightFlickerIntensity = ExplosionTemplate.ExploLightFlickerIntensity;
|
|
LightFlickerInterpSpeed = ExplosionTemplate.ExploLightFlickerInterpSpeed;
|
|
HowLongToLive = FMax( LightFadeTime + LightFadeStartTime + 0.2f, HowLongToLive );
|
|
`else
|
|
HowLongToLive = FMax( LightFadeTime + 0.2f, HowLongToLive );
|
|
`endif // __TW_
|
|
LightInitialBrightness = ExplosionTemplate.ExploLight.Brightness;
|
|
}
|
|
}
|
|
|
|
// radial blur
|
|
if (ExplosionTemplate.ExploRadialBlur != None)
|
|
{
|
|
if ((ExplosionTemplate.bPerformRadialBlurRelevanceCheck == false) || ImpactEffectIsRelevant(Instigator, Location+vect(0,0,1), false, 4000.0f, 350.0f, true))
|
|
{
|
|
if( ExplosionRadialBlur != None )
|
|
{
|
|
// If there is already a radial blur, detach it
|
|
DetachComponent(ExplosionRadialBlur);
|
|
}
|
|
|
|
ExplosionRadialBlur = new(self) class'RadialBlurComponent' (ExplosionTemplate.ExploRadialBlur);
|
|
if (ExplosionRadialBlur != None)
|
|
{
|
|
AttachComponent(ExplosionRadialBlur);
|
|
RadialBlurFadeTime = ExplosionTemplate.ExploRadialBlurFadeOutTime;
|
|
RadialBlurFadeTimeRemaining = RadialBlurFadeTime;
|
|
RadialBlurMaxBlurAmount = ExplosionTemplate.ExploRadialBlurMaxBlur;
|
|
SetTimer(FMax(RadialBlurFadeTime,LightFadeTime));
|
|
HowLongToLive = FMax( RadialBlurFadeTime + 0.2f, HowLongToLive );
|
|
}
|
|
}
|
|
}
|
|
|
|
// cam shakes
|
|
DoExplosionCameraEffects();
|
|
|
|
// Apply impulse to physics stuff (before we do fracture)
|
|
`if(`__TW_)
|
|
// fixed grenade log spam
|
|
if (RadialImpulseComponent == None)
|
|
{
|
|
}
|
|
else
|
|
`endif
|
|
if (ExplosionTemplate.MyDamageType != None && ExplosionTemplate.MyDamageType.default.RadialDamageImpulse > 0.0)
|
|
{
|
|
RadialImpulseComponent.ImpulseRadius = FMax(ExplosionTemplate.DamageRadius, ExplosionTemplate.KnockDownRadius);
|
|
RadialImpulseComponent.ImpulseStrength = ExplosionTemplate.MyDamageType.default.RadialDamageImpulse;
|
|
RadialImpulseComponent.bVelChange = ExplosionTemplate.MyDamageType.default.bRadialDamageVelChange;
|
|
RadialImpulseComponent.ImpulseFalloff = RIF_Constant;
|
|
//`log("AA"@ExplosionTemplate.MyDamageType@RadialImpulseComponent.ImpulseStrength@RadialImpulseComponent.ImpulseRadius);
|
|
RadialImpulseComponent.FireImpulse(Location);
|
|
}
|
|
|
|
SpawnExplosionFogVolume();
|
|
|
|
if( FluidSurfaceActor(HitActorFromPhysMaterialTrace) != none )
|
|
{
|
|
FluidSurfaceActor(HitActorFromPhysMaterialTrace).FluidComponent.ApplyForce( HitLocationFromPhysMaterialTrace, 1024.0f, 20.0f, FALSE );
|
|
}
|
|
}
|
|
|
|
// do damage
|
|
// delay the damage if necessary,
|
|
bHurtSomeone = FALSE;
|
|
if ( ExplosionTemplate.Damage > 0.0 )
|
|
{
|
|
if (ExplosionTemplate.DamageDelay > 0.0)
|
|
{
|
|
// cause effects now, damage later
|
|
DoExplosionDamage(false, true);
|
|
SetTimer( ExplosionTemplate.DamageDelay, FALSE, nameof(DelayedExplosionDamage) );
|
|
}
|
|
else
|
|
{
|
|
// otherwise apply immediately
|
|
bHurtSomeone = DoExplosionDamage(true, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DoExplosionDamage(false, true);
|
|
}
|
|
|
|
// play the sound
|
|
if( WorldInfo.NetMode != NM_DedicatedServer )
|
|
{
|
|
if( bHurtSomeone && ExplosionTemplate.ExplosionSoundHurtSomeone != None)
|
|
{
|
|
//`log( "Playing Explosion Sound (debug left in to test distance)" @ ExplosionTemplate.ExplosionSound );
|
|
`if(`__TW_WWISE_)
|
|
// sound location can't be the same as sound player location, so add a little offset to make Wwise happy
|
|
PlaySoundBase( ExplosionTemplate.ExplosionSoundHurtSomeone, TRUE, TRUE, FALSE, Location + vect(0,0,0.1), TRUE );
|
|
`else
|
|
PlaySound( ExplosionTemplate.ExplosionSoundHurtSomeone, TRUE, TRUE, FALSE, Location, TRUE );
|
|
`endif // __TW_WWISE_
|
|
}
|
|
else if( ExplosionTemplate.ExplosionSound != None )
|
|
{
|
|
//`log( "Playing Explosion Sound (debug left in to test distance)" @ ExplosionTemplate.ExplosionSound );
|
|
`if(`__TW_WWISE_)
|
|
// sound location can't be the same as sound player location, so add a little offset to make Wwise happy
|
|
PlaySoundBase( ExplosionTemplate.ExplosionSound, TRUE, TRUE, FALSE, Location + vect(0,0,0.1), TRUE );
|
|
`else
|
|
PlaySound( ExplosionTemplate.ExplosionSound, TRUE, TRUE, FALSE, Location, TRUE );
|
|
`endif // __TW_WWISE_
|
|
}
|
|
}
|
|
|
|
if( Role == Role_Authority )
|
|
{
|
|
MakeNoise(1.0);
|
|
}
|
|
|
|
`if(`notdefined(FINAL_RELEASE))
|
|
if (bDrawDebug)
|
|
{
|
|
DrawDebug();
|
|
}
|
|
`endif
|
|
|
|
bHasExploded = TRUE;
|
|
|
|
// done with it
|
|
if (!bPendingDelete && !bDeleteMe)
|
|
{
|
|
// Live forever if this actor can explode more than once
|
|
LifeSpan = bExplodeMoreThanOnce ? 0.0 : HowLongToLive;
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function DelayedExplosionDamage()
|
|
{
|
|
DoExplosionDamage(true, false);
|
|
}
|
|
|
|
simulated function DrawDebug()
|
|
{
|
|
local Color C;
|
|
local float Angle;
|
|
|
|
// 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);
|
|
//DrawDebugLine(Location, Location + HitNormal*16, 255, 255, 255, TRUE);
|
|
}
|
|
}
|
|
|
|
simulated function DoExplosionCameraEffects()
|
|
{
|
|
local CameraShake Shake;
|
|
local float ShakeScale;
|
|
local PlayerController PC;
|
|
|
|
// do camera shake(s)
|
|
// note: intentionally letting directional explosions still shake everything
|
|
foreach WorldInfo.LocalPlayerControllers(class'PlayerController', PC)
|
|
{
|
|
if (PC.PlayerCamera != None)
|
|
{
|
|
Shake = ChooseCameraShake(Location, PC);
|
|
if (Shake != None)
|
|
{
|
|
ShakeScale = PC.PlayerCamera.CalcRadialShakeScale(PC.PlayerCamera, Location, ExplosionTemplate.CamShakeInnerRadius, ExplosionTemplate.CamShakeOuterRadius, ExplosionTemplate.CamShakeFalloff);
|
|
|
|
if (ExplosionTemplate.bOrientCameraShakeTowardsEpicenter)
|
|
{
|
|
PC.ClientPlayCameraShake(Shake, ShakeScale, ExplosionTemplate.bAutoControllerVibration, CAPS_UserDefined, rotator(Location - PC.ViewTarget.Location));
|
|
}
|
|
else
|
|
{
|
|
PC.ClientPlayCameraShake(Shake, ShakeScale, ExplosionTemplate.bAutoControllerVibration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// do lens effects
|
|
SpawnCameraLensEffects();
|
|
}
|
|
|
|
/**
|
|
* Spawns the camera lens effect(s) if needed by this explosion
|
|
*/
|
|
simulated function SpawnCameraLensEffects()
|
|
{
|
|
local PlayerController PC;
|
|
|
|
if (ExplosionTemplate.CameraLensEffect != None)
|
|
{
|
|
foreach WorldInfo.LocalPlayerControllers(class'PlayerController', PC)
|
|
{
|
|
// splatter some blood on their camera if they are a human and decently close
|
|
if ( PC.Pawn != None &&
|
|
VSize(PC.Pawn.Location - Location) < ExplosionTemplate.CameraLensEffectRadius &&
|
|
PC.IsAimingAt(self, 0.1) && // if we are semi looking in the direction of the explosion
|
|
!IsBehindExplosion(PC.Pawn) )
|
|
{
|
|
PC.ClientSpawnCameraLensEffect(ExplosionTemplate.CameraLensEffect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal. When using directional camera shakes, used to determine which anim to use.
|
|
* @todo: nativise for speed?
|
|
*/
|
|
protected simulated function CameraShake ChooseCameraShake(vector Epicenter, PlayerController PC)
|
|
{
|
|
local vector CamX, CamY, CamZ, ToEpicenter;
|
|
local float FwdDot, RtDot;
|
|
local CameraShake ChosenShake;
|
|
local Rotator NoPitchRot;
|
|
|
|
if (ExplosionTemplate.bOrientCameraShakeTowardsEpicenter)
|
|
{
|
|
return ExplosionTemplate.CamShake;
|
|
}
|
|
// expected to be false much of the time, so maybe bypass the math
|
|
else if ( (ExplosionTemplate.CamShake_Left != None) || (ExplosionTemplate.CamShake_Right != None) || (ExplosionTemplate.CamShake_Rear != None) )
|
|
{
|
|
ToEpicenter = Epicenter - PC.PlayerCamera.Location;
|
|
ToEpicenter.Z = 0.f;
|
|
ToEpicenter = Normal(ToEpicenter);
|
|
NoPitchRot = PC.PlayerCamera.Rotation;
|
|
NoPitchRot.Pitch = 0.f;
|
|
GetAxes(NoPitchRot, CamX, CamY, CamZ);
|
|
|
|
FwdDot = CamX dot ToEpicenter;
|
|
if (FwdDot > 0.707f)
|
|
{
|
|
// use forward
|
|
ChosenShake = ExplosionTemplate.CamShake;
|
|
}
|
|
else if (FwdDot > -0.707f)
|
|
{
|
|
// need to determine r or l
|
|
RtDot = CamY dot ToEpicenter;
|
|
ChosenShake = (RtDot > 0.f) ? ExplosionTemplate.CamShake_Right : ExplosionTemplate.CamShake_Left;
|
|
}
|
|
else
|
|
{
|
|
// use back
|
|
ChosenShake = ExplosionTemplate.CamShake_Rear;
|
|
}
|
|
}
|
|
|
|
if (ChosenShake == None)
|
|
{
|
|
// fall back to forward
|
|
ChosenShake = ExplosionTemplate.CamShake;
|
|
}
|
|
|
|
return ChosenShake;
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
RemoteRole=ROLE_None
|
|
|
|
bExplodeMoreThanOnce = False;
|
|
|
|
Begin Object Class=RB_RadialImpulseComponent Name=ImpulseComponent0
|
|
End Object
|
|
RadialImpulseComponent=ImpulseComponent0
|
|
Components.Add(ImpulseComponent0)
|
|
|
|
//bDebug=TRUE
|
|
}
|