1
0
KF2-Dev-Scripts/KFGame/Classes/KFFracturedMeshActor.uc
2020-12-13 18:01:13 +03:00

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
}