1
0
KF2-Dev-Scripts/KFGame/Classes/KFImpactEffectManager.uc
2021-09-03 00:46:08 +03:00

363 lines
13 KiB
Ucode

//=============================================================================
// KFImpactEffectManager
//=============================================================================
// Manages impact effects
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFImpactEffectManager extends Actor
native(Effect)
config(Game);
/** Information about hit effects for this class */
var KFImpactEffectInfo DefaultImpactEffects;
var float MaxImpactEffectDistance;
var float MaxDecalRangeSq;
var bool bAlignToSurfaceNormal;
var bool bSuppressSounds;
/** Decal Limits */
var globalconfig int MaxImpactEffectDecals;
/** Decal manager */
var transient DecalManager ImpactEffectDecalManager;
/** Data for most recent impact sound played (used to limit sounds) */
var transient AkEvent MostRecentImpactSound;
var transient vector MostRecentImpactLocation;
var transient float MostRecentImpactTime;
var transient int NumDuplicateImpactSounds;
/** Proximity within which to limit impact sounds */
var float MinRepeatImpactSoundDistanceSq;
/** Duration within which to limit impact sounds */
var float MinRepeatImpactSoundInterval;
/** Number of duplicate sounds to allow within time period and proximity */
var int MaxDuplicateImpactSounds;
event PostBeginPlay()
{
Super.PostBeginPlay();
// Create decal managers
if( WorldInfo.NetMode != NM_DedicatedServer )
{
// Wound decal manager
ImpactEffectDecalManager = Spawn(class'DecalManager', self,, vect(0,0,0), rot(0,0,0));
ImpactEffectDecalManager.MaxActiveDecals = MaxImpactEffectDecals;
}
}
/**
* Spawn any effects that occur at the impact point. It's called from the pawn.
*/
simulated function PlayImpactEffects(const vector HitLocation, const Pawn EffectInstigator, optional vector HitNormal, optional KFImpactEffectInfo CustomImpactEffects, optional bool bWorldImpactsOnly)
{
local vector NewHitLoc, FireDir, WaterHitNormal;
local Actor HitActor;
local TraceHitInfo HitInfo;
local MaterialImpactEffect ImpactEffect;
local KFImpactEffectInfo ImpactEffectInfo;
local KFFracturedMeshActor FracturedMeshActor;
local int i;
local bool bIsWeaponHandlingEffects;
// allow optional parameter to override impact effects
ImpactEffectInfo = (CustomImpactEffects != None) ? CustomImpactEffects : DefaultImpactEffects;
if( IsZero(HitNormal) && EffectInstigator != none )
{
HitNormal = Normal(EffectInstigator.Location - HitLocation);
}
FireDir = -1 * HitNormal;
if ( ImpactEffectInfo.BulletWhipSnd != None )
{
CheckBulletWhip(FireDir, HitLocation, EffectInstigator, ImpactEffectInfo);
}
if ( EffectInstigator != None && ImpactEffectIsRelevant(EffectInstigator, HitLocation, false, MaxImpactEffectDistance) )
{
if ( ImpactEffectInfo.bMakeSplash && !WorldInfo.bDropDetail && EffectInstigator.IsPlayerPawn() && EffectInstigator.IsLocallyControlled() )
{
HitActor = Trace(NewHitLoc, WaterHitNormal, HitLocation, EffectInstigator.Location+EffectInstigator.eyeheight*vect(0,0,1), true,, HitInfo, TRACEFLAG_PhysicsVolumes | TRACEFLAG_Bullet);
if ( WaterVolume(HitActor) != None )
{
WorldInfo.MyEmitterPool.SpawnEmitter(ImpactEffectInfo.SplashEffectTemplate, NewHitLoc, rotator(vect(0,0,1)));
}
}
bIsWeaponHandlingEffects = KFPawn(EffectInstigator).MyKFWeapon.bForceHandleImpacts;
// Trace using the Instigator as the TraceOwner so that melee weapons don't collide with Instigator
HitActor = EffectInstigator.Trace(NewHitLoc, HitNormal, (HitLocation - (HitNormal * 32)), HitLocation + (HitNormal * 32), !bWorldImpactsOnly,, HitInfo, TRACEFLAG_Bullet);
if( HitActor != none && HitActor.bCanBeDamaged && HitActor.IsA('Pawn') && !bIsWeaponHandlingEffects )
{
return; // pawns impacts are handled by the pawn (see PlayTakeHitEffects)
}
// handle "partial" remote client simulation for fracture meshes
if ( WorldInfo.NetMode == NM_Client && HitActor != none && HitActor.bCanBeDamaged && !EffectInstigator.IsLocallyControlled() )
{
FracturedMeshActor = KFFracturedMeshActor(HitActor);
if ( FracturedMeshActor != None )
{
FracturedMeshActor.SimulateRemoteHit(HitLocation, HitNormal, HitInfo);
}
}
// figure out the impact sound to use
GetImpactEffect(HitInfo.PhysMaterial, ImpactEffect, ImpactEffectInfo);
// Allow phys materials to override default impact effects
if( ImpactEffect == ImpactEffectInfo.DefaultImpactEffect
&& HitInfo.PhysMaterial != none )
{
// Impact effect override
if( HitInfo.PhysMaterial.ImpactEffect != none )
{
ImpactEffect.ParticleTemplate = HitInfo.PhysMaterial.ImpactEffect;
}
// Sound override
if( HitInfo.PhysMaterial.ImpactSound != none )
{
ImpactEffect.Sound = HitInfo.PhysMaterial.ImpactSound;
}
}
// Pawns handle their own hit effects
if ( HitActor != None &&
((Pawn(HitActor) == None || bIsWeaponHandlingEffects ) || Vehicle(HitActor) != None) &&
AllowImpactEffects(HitActor, HitLocation, HitNormal) )
{
if (ImpactEffect.ParticleTemplate != None)
{
if (!bAlignToSurfaceNormal)
{
HitNormal = normal(FireDir - ( 2 * HitNormal * (FireDir dot HitNormal) ) ) ;
}
WorldInfo.MyEmitterPool.SpawnEmitter(ImpactEffect.ParticleTemplate, HitLocation, rotator(HitNormal), HitActor);
}
//Spawn any global emitters
for (i = 0; i < ImpactEffectInfo.GlobalImpactEffectEffects.Length; ++i)
{
WorldInfo.MyEmitterPool.SpawnEmitter(ImpactEffectInfo.GlobalImpactEffectEffects[i], HitLocation, rotator(HitNormal), HitActor);
}
if ( !WorldInfo.bDropDetail
&& (Pawn(HitActor) == None)
&& (EffectInstigator != none && VSizeSQ(EffectInstigator.Location - HitLocation) < MaxDecalRangeSq)
&& (((WorldInfo.GetDetailMode() != DM_Low) && !class'Engine'.static.IsSplitScreen()) || (EffectInstigator.IsLocallyControlled()
&& EffectInstigator.IsHumanControlled())) )
{
SpawnImpactDecal(HitLocation, HitNormal, HitActor, ImpactEffect, HitInfo);
}
}
if( ShouldPlayImpactSound(ImpactEffect.Sound, HitLocation) )
{
PlayImpactSound(EffectInstigator, HitLocation, FireDir, HitActor, ImpactEffect.Sound, ImpactEffectInfo);
}
}
}
/** Determines whether impact sound should be played or not, based on most recent impact */
simulated function bool ShouldPlayImpactSound( AkBaseSoundObject HitSound, vector HitLocation )
{
if( HitSound == none || bSuppressSounds )
{
return false;
}
// limit the number of duplicate impact effects within a short period of time and close proximity
if( (HitSound != MostRecentImpactSound) ||
(VSizeSq(HitLocation - MostRecentImpactLocation) > MinRepeatImpactSoundDistanceSq) ||
(`TimeSince(MostRecentImpactTime) > MinRepeatImpactSoundInterval) )
{
MostRecentImpactSound = AkEvent(HitSound);
MostRecentImpactLocation = HitLocation;
MostRecentImpactTime = WorldInfo.TimeSeconds;
NumDuplicateImpactSounds = 0;
return true;
}
if( NumDuplicateImpactSounds + 1 <= MaxDuplicateImpactSounds )
{
NumDuplicateImpactSounds += 1;
return true;
}
return false;
}
/**
* Play sound effect associated with this impact
*/
simulated function PlayImpactSound(Pawn EffectInstigator, vector HitLocation, vector FireDir, Actor HitActor, AkBaseSoundObject ImpactSound, KFImpactEffectInfo ImpactEffectInfo)
{
local Vehicle V;
V = Vehicle(HitActor);
// if hit a vehicle controlled by the local player, always play it full volume
if (V != None && V.IsLocallyControlled() && V.IsHumanControlled())
{
PlayerController(V.Controller).PlaySoundBase(ImpactSound, true);
}
else
{
PlaySoundBase(ImpactSound, true,,, HitLocation);
}
}
simulated function CheckBulletWhip(vector FireDir, vector HitLocation, Pawn EffectInstigator, KFImpactEffectInfo ImpactEffectInfo)
{
local KFPlayerController PC;
ForEach LocalPlayerControllers(class'KFPlayerController', PC)
{
if ( !WorldInfo.GRI.OnSameTeam(Owner,PC) )
{
PC.CheckBulletWhip(ImpactEffectInfo.BulletWhipSnd, EffectInstigator.Location, FireDir, HitLocation, EffectInstigator);
}
}
}
/**
* Play sound effect associated with this impact
*/
simulated function SpawnImpactDecal(vector HitLocation, vector HitNormal, Actor HitActor, const out MaterialImpactEffect ImpactEffect, optional TraceHitInfo HitInfo )
{
local MaterialInterface MI;
local MaterialInstanceTimeVarying MITV_Decal;
local int DecalMaterialsLength;
local float DecalSize, DecalThickness;
// if we have a decal to spawn on impact
DecalMaterialsLength = ImpactEffect.DecalMaterials.length;
if( DecalMaterialsLength > 0 )
{
MI = ImpactEffect.DecalMaterials[Rand(DecalMaterialsLength)];
if( MI != None )
{
DecalSize = RandRange(ImpactEffect.DecalMinSize, ImpactEffect.DecalMaxSize);
if ( ShouldExtendDecalThickness(HitActor) )
{
DecalThickness = DecalSize * 2.2f;
}
else
{
DecalThickness = 10.f;
}
if( MaterialInstanceTimeVarying(MI) != none )
{
// hack, since they don't show up on terrain anyway
if ( Terrain(HitActor) == None )
{
MITV_Decal = new(self) class'MaterialInstanceTimeVarying';
MITV_Decal.SetParent( MI );
ImpactEffectDecalManager.SpawnDecal( MITV_Decal, HitLocation, rotator(-HitNormal), DecalSize,
DecalSize, DecalThickness, false, (ImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0), HitInfo.HitComponent, true, false, HitInfo.BoneName, HitInfo.Item, HitInfo.LevelIndex );
//here we need to see if we are an MITV and then set the burn out times to occur
MITV_Decal.SetScalarStartTime( ImpactEffect.DecalDissolveParamName, ImpactEffect.DecalDuration );
}
}
else
{
ImpactEffectDecalManager.SpawnDecal( MI, HitLocation, rotator(-HitNormal), DecalSize,
DecalSize, DecalThickness, false, (ImpactEffect.bNoDecalRotation) ? 0.f : (FRand() * 360.0), HitInfo.HitComponent, true, false, HitInfo.BoneName, HitInfo.Item, HitInfo.LevelIndex, ImpactEffect.DecalDuration );
}
}
}
}
/**
* On landscape with certain non-uniform scale (e.g. Zed Landing) decals have clipping
* issues. Increasing the thickness, and therefore bounds, resolves the issue at a
* small perf cost.
*/
static function bool ShouldExtendDecalThickness(Actor HitActor)
{
// using actor flags to reject most actors before casting
if ( HitActor != None && !HitActor.bGameRelevant && HitActor.bStatic && HitActor.IsA('Landscape') )
{
return true;
}
return false;
}
/**
* Returns the impact sound that should be used for hits on the given physical material
* UnrealScript returns structs by value, so use an out param instead
*/
static simulated function GetImpactEffect(PhysicalMaterial HitMaterial, out MaterialImpactEffect out_ImpactEffect, KFImpactEffectInfo ImpactEffectInfo )
{
local int i;
local KFPhysicalMaterialProperty PhysicalProperty;
if (HitMaterial != None)
{
PhysicalProperty = KFPhysicalMaterialProperty(HitMaterial.GetPhysicalMaterialProperty(class'KFPhysicalMaterialProperty'));
}
// @todo: add alt fire
if (ImpactEffectInfo != none)
{
if (PhysicalProperty != None)
{
i = ImpactEffectInfo.ImpactEffects.Find('MaterialType', PhysicalProperty.MaterialType);
if (i != -1)
{
out_ImpactEffect = ImpactEffectInfo.ImpactEffects[i];
return;
}
}
out_ImpactEffect = ImpactEffectInfo.DefaultImpactEffect;
}
}
simulated function bool AllowImpactEffects(Actor HitActor, vector HitLocation, vector HitNormal)
{
return !WorldInfo.bDropDetail && (PortalTeleporter(HitActor) == None);
}
/*********************************************************************************************
* Melee impacts
*********************************************************************************************/
/**
* Called by ProcessMeleeHit to spawn effects
* Network: Local Player and Server
*/
simulated function PlayMeleeImpact(ImpactInfo Impact, Pawn EffectInstigator, KFWeapon InstigatorWeap, AkEvent HitWorldSound, AkEvent HitPawnSound, CameraShake Shake)
{
// @todo: implement this using per material settings (see PlayImpactEffects)
}
defaultproperties
{
DefaultImpactEffects=KFImpactEffectInfo'FX_Impacts_ARCH.Light_bullet_impact'
MaxImpactEffectDistance=4000.0
bAlignToSurfaceNormal=true
MaxDecalRangeSQ=400000000.0
// Actor
bTickIsDisabled=true
MinRepeatImpactSoundDistanceSq=10000 // 1m
MinRepeatImpactSoundInterval=0
MaxDuplicateImpactSounds=0
}