//============================================================================= // 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 > 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, 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 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 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 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 }