352 lines
11 KiB
Ucode
352 lines
11 KiB
Ucode
//=============================================================================
|
|
// KFFracturedMeshActor
|
|
//=============================================================================
|
|
// A custom FracturedStaticMeshActor with basic replication support, so
|
|
// these actors can have blocking collision turned on/off in multiplayer
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// - Andrew "Strago" Ladenberger
|
|
//=============================================================================
|
|
class KFFracturedMeshActor extends FracturedStaticMeshActor
|
|
native(Effect)
|
|
showcategories(Navigation);
|
|
|
|
/** type of networking to use */
|
|
var() EDestructibleRepType ReplicationMode;
|
|
|
|
/** Amount of damage this actor can take before fully collapsing */
|
|
var() int TotalHealth;
|
|
var transient int DefaultTotalHealth;
|
|
|
|
/** Light brightness material parameter for this subobject */
|
|
var() MaterialLightParamMod MaterialLightParams;
|
|
|
|
/** When health reaches zero, should spawn FracturedMeshParts */
|
|
var() bool bSpawnPartsWhenDestroyed;
|
|
|
|
/** These damage types do increase damage */
|
|
var const array< class<DamageType> > VulnerableDamageType;
|
|
/** The amount to scale damage if vulnerable */
|
|
var const byte VulnerableMultiplier;
|
|
|
|
var repnotify transient bool bHasBeenDestroyed;
|
|
var repnotify transient bool bHasLostChunk;
|
|
|
|
replication
|
|
{
|
|
if ( bNetDirty )
|
|
bHasBeenDestroyed, bHasLostChunk;
|
|
}
|
|
|
|
/** Called when a variable with the property flag "RepNotify" is replicated */
|
|
simulated event ReplicatedEvent(name VarName)
|
|
{
|
|
if ( VarName == nameof(bHasBeenDestroyed) )
|
|
{
|
|
if( bHasBeenDestroyed )
|
|
{
|
|
BreakOffAllFragments();
|
|
}
|
|
}
|
|
else if ( VarName == nameof(bHasLostChunk) )
|
|
{
|
|
if( bHasLostChunk )
|
|
{
|
|
SetLoseChunkReplacementMaterial();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set network role based on type setting
|
|
*/
|
|
simulated event PreBeginPlay()
|
|
{
|
|
if (WorldInfo.NetMode == NM_Client)
|
|
{
|
|
// on the client, set role to Authority if we're a clientside only KActor
|
|
Role = (ReplicationMode == RT_ClientSide) ? ROLE_Authority : ROLE_SimulatedProxy;
|
|
}
|
|
else
|
|
{
|
|
// on the server, set role to SimulatedProxy (i.e. replicate it) only if not clientside
|
|
RemoteRole = (ReplicationMode == RT_ClientSide) ? ROLE_None : ROLE_SimulatedProxy;
|
|
}
|
|
|
|
DefaultTotalHealth = TotalHealth;
|
|
|
|
Super.PreBeginPlay();
|
|
}
|
|
|
|
/** TakeDamage will hide/spawn chunks when they get shot. */
|
|
simulated event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
|
|
{
|
|
local vector HitDir;
|
|
local vector HitNormal;
|
|
|
|
if ( bHasBeenDestroyed )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( HitInfo.HitComponent == None || HitInfo.Item == INDEX_None )
|
|
{
|
|
// Perform trace to retrieve hit info
|
|
if ( Momentum == vect(0,0,0) )
|
|
{
|
|
Momentum = Location - HitLocation;
|
|
}
|
|
// push trace line in both directions to fix projectiles missing [aladenberger 2/5/2014]
|
|
HitDir = Normal(Momentum);
|
|
TraceComponent(HitLocation, HitNormal, FracturedStaticMeshComponent, HitLocation+50*HitDir, HitLocation-50*HitDir,, HitInfo, true);
|
|
}
|
|
|
|
if ( IsVulnerableTo(DamageType) )
|
|
{
|
|
Damage *= VulnerableMultiplier;
|
|
}
|
|
|
|
Super.TakeDamage(Damage, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
|
|
|
|
if ( Role == ROLE_Authority )
|
|
{
|
|
TotalHealth -= Damage;
|
|
if ( TotalHealth <= 0 )
|
|
{
|
|
BreakOffAllFragments();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** If returns true, the destructible is vulnerable to this damage type damage */
|
|
function bool IsVulnerableTo(class<DamageType> DT)
|
|
{
|
|
local int Idx;
|
|
|
|
for (Idx = 0; Idx < VulnerableDamageType.length; ++Idx)
|
|
{
|
|
if ( ClassIsChildOf(DT, VulnerableDamageType[Idx]) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Overriden for replicated FractureMeshes and bHasLostChunk */
|
|
simulated function bool FractureEffectIsRelevant( bool bForceDedicated, Pawn EffectInstigator, out byte bWantPhysChunksAndParticles )
|
|
{
|
|
local bool bResult;
|
|
|
|
// If this is a replicated FractureMesh set bForceDedicated (works on dedicated/listen)
|
|
// This will force bHasLostChunk to be properly updated when the actor takes damage
|
|
if ( RemoteRole == ROLE_SimulatedProxy && !bHasLostChunk )
|
|
{
|
|
bResult = Super.FractureEffectIsRelevant(true, EffectInstigator, bWantPhysChunksAndParticles);
|
|
|
|
// Update bWantPhysChunksAndParticles since super won't do this when bForceDedicated is set
|
|
if( bResult && `TimeSince(LastRenderTime) > 0.5 )
|
|
{
|
|
bWantPhysChunksAndParticles = 0;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
return Super.FractureEffectIsRelevant(bForceDedicated, EffectInstigator, bWantPhysChunksAndParticles);
|
|
}
|
|
|
|
/** Override for replication */
|
|
simulated event SetLoseChunkReplacementMaterial()
|
|
{
|
|
Super.SetLoseChunkReplacementMaterial();
|
|
|
|
if( !bHasLostChunk )
|
|
{
|
|
bHasLostChunk = true;
|
|
|
|
if( WorldInfo.NetMode != NM_Client )
|
|
{
|
|
bNetDirty = true;
|
|
bForceNetUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Break off all pieces in one go. Also, handles network play (e.g. collision) */
|
|
simulated function BreakOffAllFragments(optional vector InVelocity)
|
|
{
|
|
local array<byte> FragmentVis;
|
|
local int i, NumPartsHidden;
|
|
local vector SpawnDir;
|
|
local FracturedStaticMesh FracMesh;
|
|
local FracturedStaticMeshPart FracPart;
|
|
local float PartScale;
|
|
local bool bWantPhysChunksAndParticles;
|
|
|
|
// Dirty actor for reset
|
|
bHasBeenDirtied = true;
|
|
|
|
// network
|
|
bHasBeenDestroyed = true;
|
|
bForceNetUpdate = true;
|
|
|
|
// turn off all collision at once - only do this if actor is replicated!
|
|
if ( ReplicationMode != RT_ClientSide )
|
|
{
|
|
SetCollision(,false);
|
|
}
|
|
// also disable rigid body collision
|
|
FracturedStaticMeshComponent.SetBlockRigidBody(false);
|
|
|
|
bWantPhysChunksAndParticles = bSpawnPartsWhenDestroyed && ActorEffectIsRelevant(None, false);
|
|
FracMesh = FracturedStaticMesh(FracturedStaticMeshComponent.StaticMesh);
|
|
|
|
// Iterate over all visible fragments spawning them
|
|
FragmentVis = FracturedStaticMeshComponent.GetVisibleFragments();
|
|
for(i=0; i<FragmentVis.length; i++)
|
|
{
|
|
// If this is a currently-visible, non-core fragment, spawn it off.
|
|
if((FragmentVis[i] != 0) && (i != FracturedStaticMeshComponent.GetCoreFragmentIndex()) && FracturedStaticMeshComponent.IsFragmentDestroyable(i))
|
|
{
|
|
if ( bWantPhysChunksAndParticles && FracMesh.bSpawnPhysicsChunks && WorldInfo.GetDetailMode() > DM_Low && !WorldInfo.bDropDetail )
|
|
{
|
|
SpawnDir = FracturedStaticMeshComponent.GetFragmentAverageExteriorNormal(i);
|
|
PartScale = FracMesh.ExplosionPhysicsChunkScaleMin + FRand() * (FracMesh.ExplosionPhysicsChunkScaleMax - FracMesh.ExplosionPhysicsChunkScaleMin);
|
|
// Spawn part- inherit this actors velocity
|
|
FracPart = SpawnPart(i, (0.5 * SpawnDir * FracMesh.ChunkLinVel) + Velocity + InVelocity, 0.5 * VRand() * FracMesh.ChunkAngVel, PartScale, TRUE);
|
|
|
|
if(FracPart != None)
|
|
{
|
|
// When something explodes we disallow collisions between all those parts.
|
|
FracPart.FracturedStaticMeshComponent.SetRBCollidesWithChannel(RBCC_FracturedMeshPart, FALSE);
|
|
}
|
|
}
|
|
|
|
NumPartsHidden++;
|
|
FragmentVis[i] = 0;
|
|
}
|
|
}
|
|
|
|
// switch to lose chunk material if we haven't already
|
|
SetLoseChunkReplacementMaterial();
|
|
|
|
if ( bWantPhysChunksAndParticles && NumPartsHidden > 0 )
|
|
{
|
|
PlayBreakOffAllParticles(FracMesh);
|
|
}
|
|
|
|
// Update the visibility of the actor being spawned off of
|
|
FracturedStaticMeshComponent.SetVisibleFragments(FragmentVis);
|
|
|
|
if( ExplosionFractureSound != none )
|
|
{
|
|
PlaySoundBase( ExplosionFractureSound );
|
|
}
|
|
}
|
|
|
|
/** Make sure that when pieces break off the lost chunk material is applied */
|
|
simulated event BreakOffPartsInRadius( vector Origin, float Radius, float RBStrength, bool bWantPhysChunksAndParticles )
|
|
{
|
|
super.BreakOffPartsInRadius( Origin, Radius, RBStrength, bWantPhysChunksAndParticles );
|
|
|
|
SetLoseChunkReplacementMaterial();
|
|
}
|
|
|
|
/** Play effects - copied from Super.TakeDamage */
|
|
simulated function PlayBreakOffAllParticles(FracturedStaticMesh FracMesh)
|
|
{
|
|
local ParticleSystem EffectPSys;
|
|
local Box MeshBox;
|
|
|
|
// Look for override first
|
|
if(OverrideFragmentDestroyEffects.length > 0)
|
|
{
|
|
// Pick randomly
|
|
EffectPSys = OverrideFragmentDestroyEffects[Rand(OverrideFragmentDestroyEffects.length)];
|
|
}
|
|
// No override array, try the mesh
|
|
else if(FracMesh.FragmentDestroyEffects.length > 0)
|
|
{
|
|
EffectPSys = FracMesh.FragmentDestroyEffects[Rand(FracMesh.FragmentDestroyEffects.length)];
|
|
}
|
|
|
|
// If we have an effect and a manager - spawn it
|
|
if(EffectPSys != None && WorldInfo.MyFractureManager != None)
|
|
{
|
|
MeshBox.Min = (FracturedStaticMeshComponent.Bounds.Origin - FracturedStaticMeshComponent.Bounds.BoxExtent);
|
|
MeshBox.Max = (FracturedStaticMeshComponent.Bounds.Origin + FracturedStaticMeshComponent.Bounds.BoxExtent);
|
|
WorldInfo.MyFractureManager.SpawnChunkDestroyEffect(EffectPSys, MeshBox, vect(0,0,0), FracMesh.FragmentDestroyEffectScale, FracturedStaticMeshComponent);
|
|
}
|
|
}
|
|
|
|
/** Called on client when a PlayImpactEffects trace from another player hits this FracturedMesh */
|
|
simulated function SimulateRemoteHit(vector HitLocation, vector Momentum, const out TraceHitInfo HitInfo)
|
|
{
|
|
// If !bHasLostChunk then the server probably didn't do any damage (e.g. Medic Dart) and therefore we shouldn't
|
|
// switch to LoseChunkReplacementMaterial. If it's already cracked knocking out an extra chunk is harmless
|
|
if ( bHasLostChunk )
|
|
{
|
|
TakeDamage(0, None, HitLocation, Momentum, class'KFDamageType', HitInfo);
|
|
}
|
|
}
|
|
|
|
/** Level was reset without reloading */
|
|
simulated function Reset()
|
|
{
|
|
if( !bHasBeenDirtied )
|
|
{
|
|
return;
|
|
}
|
|
|
|
super.Reset();
|
|
|
|
// Network
|
|
bHasBeenDestroyed = false;
|
|
bHasLostChunk = false;
|
|
bForceNetUpdate = true;
|
|
bNetDirty = true;
|
|
|
|
// Re-enable collision
|
|
if ( ReplicationMode != RT_ClientSide )
|
|
{
|
|
SetCollision( ,true );
|
|
}
|
|
|
|
// Reset total health (health for total destruction)
|
|
TotalHealth = DefaultTotalHealth;
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
Begin Object Class=SpriteComponent Name=Sprite
|
|
Sprite=Texture2D'EditorResources.Ambientcreatures'
|
|
HiddenGame=True
|
|
AlwaysLoadOnClient=False
|
|
AlwaysLoadOnServer=False
|
|
End Object
|
|
Components.Add(Sprite)
|
|
|
|
RemoteRole=ROLE_None
|
|
|
|
// network
|
|
bAlwaysRelevant=true
|
|
bOnlyDirtyReplication=true
|
|
bSkipActorPropertyReplication=true
|
|
NetUpdateFrequency=0.1
|
|
|
|
TotalHealth=5000
|
|
bSpawnPartsWhenDestroyed=true
|
|
|
|
// Suppress sound warning
|
|
bHasShownMissingSoundWarning=true
|
|
|
|
// Default multiplier if we are vulnerable to any damage types
|
|
VulnerableMultiplier=6
|
|
|
|
// We want chunks to disappear no matter where you are in the map
|
|
FractureCullMaxDistance=100000.0
|
|
}
|