/** * 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 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 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 }