//============================================================================= // KFDestructibleActor //============================================================================= // Base class for all placeable destructibles //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Andrew "Strago" Ladenberger //============================================================================= class KFDestructibleActor extends Actor placeable //implements(Interface_NavMeshPathObject) native hidecategories(Physics, Debug, Object, Mobile) showcategories(Navigation); /********************************************************************************************* * Network ********************************************************************************************* */ enum EDestructibleRepType { /** Normal (unique net channel) server-side replication */ RT_ServerDefault, /** Use a single net channel to replicate many destructible actors */ RT_ServerOptimized, /** Client-only, no blocking collision */ RT_ClientSide, }; var() EDestructibleRepType ReplicationMode; struct native ReplicatedDamageModInfo { var byte ObjIdx; var byte ModIdx; var bool bPartial; structdefaultproperties { ObjIdx=255 ModIdx=255 } }; /** Packed dmg mod info for replication */ var repnotify ReplicatedDamageModInfo ReplicatedDamageMods[16]; /** True if the destructible is fully shut down */ var repnotify bool bShutDown; /** simplified network damage state */ var transient byte ReplicatedDamageFlags; /********************************************************************************************* * Damage ********************************************************************************************* */ /** The damage needed for a hit to be considered instakill */ var protected const int INSTAKILL_DAMAGE; /** List of damage types that instantly destroy this object */ var() array > InstaKillDamageType; /** List of damage types to always ignore */ var() array > IgnoreDamageType; /** These damage types do increase damage */ var const array< class > VulnerableDamageType; /** The amount to scale damage if vulnerable */ var const byte VulnerableMultiplier; /** If true, ignores all damage */ var() protected bool bIgnoreAllDamage; /** If true, will ignore all damage done by players */ var() protected bool bIgnorePlayerDamage; /** If true, will take bump damage from AI, in addition to just players */ var() protected bool bAllowBumpDamageFromAI; var transient float LastBumpCheckTime; /** If bSelfDestructAll == TRUE for a damage mod, the object will be killed after this delay */ var() const float SelfDestructAllDelay; /** When destroyed, disable collision on this blocking volume (MUST have bSelfDestructAll=TRUE) */ var() const BlockingVolume BlockingVolumeToDisable; /** Force destroy these actors on SelfDestructAll. Useful if we have nearby destructibles and need more precision than ExplosionMod */ var() const array DamageOtherActors; /** Used to attach all of the components only once */ var transient bool bComponentsSetUp; /** Used to update physics bodies only once */ var transient bool bInitRBPhysCalled; /** If set, TakeRadiusDamage is being executed */ var transient bool bIsRadiusDamage; /** Used by reset code to determine if actor is dirtied */ var transient bool bAnyDamageModApplied; /********************************************************************************************* * Nav Mesh (DEPRECATED) ********************************************************************************************* */ //enum ENavMeshObjectState //{ // ENavMeshObjectState_None, // ENavMeshObjectState_ObstacleOnly, // ENavMeshObjectState_PathObjectOnly, // ENavMeshObjectState_BothObstacleAndPathObject, //}; // //var(Navigation) KFNavMeshObstacle_Destructible MyNavMeshObstacle; //var(Navigation) KFNavMeshPathObject_Destructible MyNavMeshPathObject; // //var (Navigation) ENavMeshObjectState NavMeshObjectState; /********************************************************************************************* * Material Mods ********************************************************************************************* */ struct native MaterialReplaceMod { var() MaterialInterface NewMaterial; /** Index in the Materials array to replace with NewMaterial when this action is activated. */ var() int MaterialIndex; }; struct native MaterialScalarParamMod { var() MaterialInstanceConstant MatInst; var() Name ParamName; var() float ScalarVal; }; struct native MaterialTexParamMod { var() MaterialInstanceConstant MatInst; var() Name ParamName; var() Texture NewTexture; }; struct native MaterialVectorParamMod { var() MaterialInstanceConstant MatInst; var() Name ParamName; var() LinearColor VectorVal; }; struct native MaterialLightParamMod { var() int MatID; var() Name ParamName; var() Light LightActor; structdefaultproperties { ParamName=BrightnessMultiplier } }; /********************************************************************************************* * Physics actors (fragments) ********************************************************************************************* */ /** Parameters to define an actor spawn. */ struct native ActorSpawnParams { /** Static mesh to use for physics object */ var() StaticMesh RigidBodyMesh; /** Lower bound of random initial linear vel */ var() vector MinLinearVel; /** Upper bound of random initial linear vel */ var() vector MaxLinearVel; /** Lower bound of random initial angular vel */ var() vector MinAngularVel; /** Upper bound of random initial angular vel */ var() vector MaxAngularVel; /** Collision type to use for spawned rigid body */ var() ECollisionType CollisionType; /** RBChannel to use for collision */ var() ERBCollisionChannel RBChannel; /** Positional offset for actor spawn loc. Note this is relative to the subobject */ var() vector RelativeOffset; /** Rotational offset for actor spawn loc. Note this is relative to the subobject */ var() rotator RelativeRotation; /** How long before we forcibly destroy the spawned actor, in seconds. <= 0 means don't destroy. */ var() float LifeTimeSeconds; /** Do not spawn if no local players are within this range */ var() float MaxSpawnDist; /** (PERF WARNING) If set, the spawned KActor is network replicated */ var() bool bReplicated; /** Material changes */ var() const array MaterialReplacements; structdefaultproperties { LifeTimeSeconds=30.0f MaxSpawnDist=1500.f CollisionType=COLLIDE_NoCollision RBChannel=RBCC_EffectPhysics } }; /********************************************************************************************* * Effects ********************************************************************************************* */ struct native DestroyedEffectParams { /** Emitters to spawn */ var() ParticleSystem ParticleEffect; /** Position offset for detroyed effect. Note this is relative to the subobject */ var() vector RelativeOffset; /** Rotational offset for destroyed effect. Note this is relative to the subobject */ var() rotator RelativeRotation; /** Do not spawn if no local players are within this range */ var() float MaxSpawnDist; /** reference to the spawned particle system */ var transient ParticleSystemComponent PSC; structdefaultproperties { MaxSpawnDist=4000.f } }; /** Contains data for an explosion to spawn upon damage */ struct native ExplosionMod { /** Explosion archetype to spawn */ var() KFGameExplosion ExplosionTemplate; /** Explosion damage */ var float Damage; /** Explosion damage radius */ var float DamageRadius; /** Translation offset */ var() vector RelativeLocation; /** Rotation offset */ var() rotator RelativeRotation; /** The actor class to ignore for damage from this explosion **/ var class ActorClassToIgnoreForDamage; structdefaultproperties { RelativeLocation=(Z=10) } }; struct native SplashDamageParams { var() float BaseDamage; var() float DamageRadius; var() class DamageType; /** Translation offset - useful when other sub objects are in the way */ var() vector RelativeLocation; structdefaultproperties { DamageType=class'KFDT_Explosive' RelativeLocation=(Z=10) } }; /** describes a dependency of one subobject on a modification to another */ struct native ObjDamageModifierDependency { /** which subobject we're talking about */ var() Name DependentSubObjName; /** If dependent subobject is above this health, it will be damaged to this level */ var() int MaxHealthToAllow; /** Optimization to avoid searches. Gets filled in on PostBeginPlay() */ var transient int DependentSubObjIdx; structdefaultproperties { DependentSubObjIdx=-1 } }; /** Contains data for an explosion to spawn upon damage */ struct native LightDamageParams { /** Placed light actors to recieve this mod */ var() Light Light; /** New light animation settings */ var() const float MaxBrightness; var() const float MinBrightness; var() ELightAnimationTechnique AnimationType; var() float AnimationFrequency; /** If set, this light should be disabled */ var() bool bDisabled; structdefaultproperties { } }; /** A modification to be applied to a subobject after a certain damage threshold is reached. */ struct native ObjectDamageModifier { /** Name that can use used to descriptively identify this mod. */ var() const Name DamageModName; /** apply this Modifier at or below this health */ var() const float HealthThreshold; /** New static mesh to swap to, or None */ var() StaticMesh NewMesh; /** Material changes */ var() const array MaterialReplacements; var() const array MaterialScalarParams; var() const array MaterialTexParams; var() const array MaterialVectorParams; /** Sounds to play when applying this modifier */ var() const array Sounds; /** TRUE to kill the subobject at this stage */ var() const bool bSelfDestruct; /** TRUE to kill the object (all subobjects) at this stage */ var() const bool bSelfDestructAll; /** TRUE to stop any attached ambient sound */ var() const bool bStopAmbientSound; // maybe use this? ParticleSystem'Effects_Level_SP_5.SP_Hospital.Effects.P_Wooden_Barrier_Split' var() array ParticleEffects; /** Potential splash damage (if the subj explodes or something) */ var() const SplashDamageParams SplashDamage; /** actors that will be spawned */ var() const array ActorsToSpawn; /** list of subobjects that are also affected by this modifier */ var() const array DependentSubObjs; /** true when this stage has been applied */ var transient bool bApplied; /** index of the next actor to be spawned from ActorsToSpawn array */ var transient int NextSpawnActorIdx; /** Explosion archetype to spawn */ var() ExplosionMod ExploMod; /** Temporarily allow CanBecomeDynamic (bNeverBecomeDynamic=FALSE) on these actors */ var deprecated array BecomeDynamicActors; /** light actors that are effected by this damage mod */ var() const array LightParams; /** Whether this mod should apply actor blocking or not. @NOTE: Only used by undo mod */ var transient bool bMeshBlockActors; }; /** This sound is played all of the time the object is in the world. (good for radios and such) **/ var() protected AkBaseSoundObject AmbientSoundCue; var protected AkComponent AmbientSoundComponent; /** Cached squared distance of nearest local player */ var transient float ClosestLocalPlayerSqDist; /********************************************************************************************* * Sub-objects ********************************************************************************************* */ struct native DestructibleSubobject { /** name of the subobject. should be unique, but not currently enforced */ var() Name SubObjName; /** the mesh */ var() StaticMeshComponent Mesh; /** mods that can be applied to this subobject */ var() array DamageMods; /** hit points this subobject has by default */ var() float DefaultHealth; /** Light brightness material parameter for this subobject */ var() array MaterialLightParams; /** * This is a modifier that is autogenerated at initialization time which represents how to * get back to our original state * Used in MP to "heal" WDOs between rounds. */ var transient ObjectDamageModifier UndoMod; /** Index of damage mod that is currently spawning effects / actors */ var transient int ActiveDamageModIdx; /** Most recent damage type this subobject was hit with */ var transient class LastHitDamageType; /** Cached MICs that are generated for material params */ var transient array MICs; /** Current health of this SubObject (non-transient) */ var int Health; structdefaultproperties { Health=50 DefaultHealth=50 ActiveDamageModIdx=-1 } }; /** Array of objects that comprise this destructible object (MAX: 8) */ var(SubObjects) array SubObjects; /** Tick optimization -- store all damaged mods in a single-dimensional array */ var native private const init array DamagedSubObjects{FDestructibleSubobject}; /** SubObject array size limit based on ReplicationMode */ var(SubObjects) const editconst byte SubObjectLimit; /** SubObject array size limit based on ReplicationMode */ var(SubObjects) const editconst byte DamageModLimit; /** Helper object to display RelativeLocation and RelativeRotation */ var(SubObjects) editoronly transient Actor PreviewOffsetActor; /** Last value from PreviewOffsetActor */ var(SubObjects) editoronly transient vector PreviewRelativeLocation; var(SubObjects) editoronly transient rotator PreviewRelativeRotation; cpptext { private: /** Attaches all of the subojects' components */ void AttachSubobjectComponents(void); void SetupHealthVars(); void GenerateUndo(); void GenerateSubObjUndo(FDestructibleSubobject* SubObj); void UpdateDamageMods(); void SetupComplexityLimits(); void UpdateLightMatParams(); /** handle MICs for any material params */ UMaterialInstanceConstant* GetSubObjMIC(FDestructibleSubobject* SubObj, INT ElementIndex); void ClearSubObjMICs(INT ObjIdx); public: virtual void PostLoad(); virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent); virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive,AActor *SourceActor, DWORD TraceFlags); virtual void TickSpecial(FLOAT DeltaSeconds); virtual UBOOL Tick(FLOAT DeltaTime, enum ELevelTick TickType); /* Overridden to setup collision for damage meshes that will eventually exist */ virtual void InitRBPhys(); #if WITH_EDITOR virtual void CheckForErrors(); #endif #if WITH_EDITOR virtual INT AddMyMarker(AActor *S); #endif } /********************************************************************************************* * Replication ********************************************************************************************* */ replication { if (Role == ROLE_Authority) ReplicatedDamageMods, bShutDown; } simulated event ReplicatedEvent(name VarName) { local int Idx, ObjIdx, ModIdx; if (VarName == nameof(ReplicatedDamageMods)) { // look for unprocessed replicated damage mods for (Idx = 0; Idx < ArrayCount(ReplicatedDamageMods); Idx++) { if ( ReplicatedDamageMods[Idx].ObjIdx != 255 ) { ObjIdx = ReplicatedDamageMods[Idx].ObjIdx; ModIdx = ReplicatedDamageMods[Idx].ModIdx; if ( !SubObjects[ObjIdx].DamageMods[ModIdx].bApplied ) { ApplyDamageMod(ObjIdx, ModIdx, ReplicatedDamageMods[Idx].bPartial); } } } } else if (VarName == nameof(bShutDown)) { if (bShutDown) { ShutDownObject(); } else { UnShutDownObject(); } } } /** * Fills in an empty slot in the replicated damage mod array to be * sent to the clients for simulation. */ event ReplicateDamageMod(int ObjIdx, int ModIdx, bool bPartial) { local int Idx; if ( (ObjIdx < 0) || (ObjIdx > 255) || (ModIdx < 0) || (ModIdx > 255) ) { `warn(self$" replicatedamagemod out of bounds "$ObjIdx@ModIdx); return; } // find first available for (Idx = 0; Idx < ArrayCount(ReplicatedDamageMods); Idx++) { if (ReplicatedDamageMods[Idx].ObjIdx == 255) { ReplicatedDamageMods[Idx].ObjIdx = ObjIdx; ReplicatedDamageMods[Idx].ModIdx = ModIdx; ReplicatedDamageMods[Idx].bPartial = bPartial; break; } } // immediate net update bForceNetUpdate = TRUE; } /** Pack damage mod into a byte for ReplicateDamageMod */ function byte PackDamageMod(int ObjIdx, int ModIdx, bool bPartial) { // 1-4: ObjIdx, 5-6 ModIdx, 8 bPartial local byte DmgModFlags; DmgModFlags = ObjIdx + (ModIdx << 4); if ( bPartial ) { DmgModFlags = DmgModFlags | 128; } return DmgModFlags; } /** Pack damage mod into a byte for ReplicateDamageMod */ simulated function UnPackDamageMod(byte DmgModFlags, out int ObjIdx, out int ModIdx, out byte bPartial) { // 1-4: ObjIdx, 5-6 ModIdx, 8 bPartial ObjIdx = (DmgModFlags & 15); ModIdx = (DmgModFlags & 48); bPartial = (DmgModFlags & 128); } /********************************************************************************************* * Startup ********************************************************************************************* */ /** attach all subojects, generally get things ready for play */ simulated private native function SetupComponents(); /** one-time initializations. basically intended to provide some native PostBeginPlay code. */ simulated native protected function OneTimeInit(); /** * This starts our ambient sound */ simulated event PostBeginPlay() { OneTimeInit(); // if we have and AmbientSoundCue we want to go ahead and start playing it. if( ( AmbientSoundCue != None ) && ( AmbientSoundComponent != None ) ) { AttachComponent( AmbientSoundComponent ); AmbientSoundComponent.OcclusionUpdateInterval = 0.2f; AmbientSoundComponent.PlayEvent( AkEvent(AmbientSoundCue) ); } switch (ReplicationMode) { case RT_ClientSide: // turn off BlockActors collision SetCollision(, false); if ( BlockingVolumeToDisable != None ) { BlockingVolumeToDisable.SetCollision(false, false); } break; } 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; //InitNavigationHandle(); } } /********************************************************************************************* * Damage ********************************************************************************************* */ /** Apply damage to a subobject */ protected native function DamageSubObject(int ObjIdx, int Damage, Controller EventInstigator, class DamType); /** Returns TRUE if any subobject has health > 0. Should only be used on the server as a * catch-all for destructibles that don't have a damage mod with bSelfDestructAll set */ native function bool HasAnyHealth(); native function float GetCurrentHealth(); native function float GetMaxHealth(); /** Network: Server */ simulated function TakeRadiusDamage ( Controller InstigatedBy, float BaseDamage, float DamageRadius, class DamageType, float Momentum, vector HurtOrigin, bool bFullDamage, Actor DamageCauser, optional float DamageFalloffExponent=1.f ) { bIsRadiusDamage = true; Super.TakeRadiusDamage(InstigatedBy, BaseDamage, DamageRadius, DamageType, Momentum, HurtOrigin, bFullDamage, DamageCauser, DamageFalloffExponent); bIsRadiusDamage = false; } /** Network: Server */ event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { local int Idx; // Modify the damage based on the damage type AdjustDamage(Damage, EventInstigator, DamageType); // Bail if there is no damage if( Damage <= 0 ) { return; } // If this damage type should instakill, don't bother finding a HitIdx if ( Damage >= INSTAKILL_DAMAGE || bIsRadiusDamage ) { // no specific hit subobject, so hit them all equally for (Idx=0; Idx 0.f) { DamageSubObject(Idx, Damage, EventInstigator, DamageType); } } } else if ( ValidateHitComponent(EventInstigator, HitInfo, HitLocation, Momentum) ) { // Search and find which subobject got hit for (Idx=0; Idx 0.f) { // hit a subobject, damage it DamageSubObject(Idx, Damage, EventInstigator, DamageType); } //`log("Got Subobject!"); break; } } // If object is dead, unregister with navmesh if ( Role == ROLE_Authority && !HasAnyHealth() ) { // @todo: pathfinding //TurnOffNavigationObstacle(); } Super.TakeDamage(Damage, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser); } /** Adjust damage based on inventory, other attributes */ function AdjustDamage(out int InDamage, Controller EventInstigator, class DamageType) { local int Idx; local KFAIController KFAIInstigator; if (bIgnoreAllDamage) { InDamage = 0; return; } // Check if this damage should instakill for ( Idx = 0; Idx < InstaKillDamageType.length; ++Idx ) { if ( (DamageType == InstaKillDamageType[Idx]) || ClassIsChildOf(DamageType, InstaKillDamageType[Idx]) ) { InDamage = INSTAKILL_DAMAGE; return; } } // Check if this damage should be ignored completely for (Idx = 0; Idx < IgnoreDamageType.length; ++Idx) { if ( (DamageType == IgnoreDamageType[Idx]) || ClassIsChildOf(DamageType, IgnoreDamageType[Idx]) ) { InDamage = 0; return; } } // special case handling for players and zeds if ( EventInstigator != None ) { // The check below allows NPCs who are set to attack the destructible (see NotifyAttackTarget() in // KFAIController), insta-kill the destructible if their ActorEnemy matches the destructible. This // avoids the previous issue where damage was applied immediately on Bump, which resulted in the // destructible breaking before the NPC's attack animtaion sequence actually began. if( bAllowBumpDamageFromAI && !EventInstigator.bIsPlayer ) { KFAIInstigator = KFAIController(EventInstigator); if( KFAIInstigator != none && KFAIInstigator.ActorEnemy == self ) { InDamage = INSTAKILL_DAMAGE; return; } } // Check if this damage should be ignored completely (update: human team only) if ( bIgnorePlayerDamage && EventInstigator.bIsPlayer && EventInstigator.GetTeamNum() == 0) { InDamage = 0; return; } } if( bIsRadiusDamage && IsVulnerableTo(DamageType) ) { InDamage *= VulnerableMultiplier; } } /** 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; } /** Called from UKFReachSpec::PrepareForMove when DestructibleActor is ahead on NPC's path */ event bool InstaKillFromLargeNPCBumpDamage() { return ( InstaKillDamageType.Find(class'KFDT_NPCBump_Large') != INDEX_NONE ); } /** Called from UKFReachSpec::PrepareForMove when DestructibleActor is ahead on NPC's path */ event bool InstaKillFromStandardNPCBumpDamage() { return ( InstaKillDamageType.Find(class'KFDT_NPCBump') != INDEX_NONE ); } /** If we don't have a valid hit component (e.g. CSHD) trace to retrieve */ function bool ValidateHitComponent( Controller EventInstigator, out TraceHitInfo HitInfo, vector HitLocation, vector Momentum ) { local Actor TraceInstigator; local vector NewHitLocation, HitNormal; local vector Offset; if ( HitInfo.HitComponent == None ) { // Perform trace to retrieve hit info if ( Momentum == vect(0,0,0) ) { Momentum = Location - HitLocation; } Offset = 8.f * Normal(Momentum); if( EventInstigator != none ) { TraceInstigator = (EventInstigator.Pawn != none && EventInstigator.Pawn.Weapon != none) ? EventInstigator.Pawn.Weapon : EventInstigator.Pawn; } if( TraceInstigator == none ) { TraceInstigator = WorldInfo; } TraceInstigator.Trace( NewHitLocation, HitNormal, HitLocation + Offset, HitLocation - Offset, true,, HitInfo, TRACEFLAG_Bullet ); } return HitInfo.HitComponent != None; } event ApplySplashDamage(const out vector Origin, const out SplashDamageParams DamageParams) { // DEPRECATED. Use ExplosionTemplate instead //if (Role == ROLE_Authority) //{ // HurtRadius(DamageParams.BaseDamage, DamageParams.DamageRadius, DamageParams.DamageType, 1.f, Origin + DamageParams.RelativeLocation, self); //} } /** Fire the Destroyed kismet event, called when all subobjects are dead */ delegate OnDestroyed(KFDestructibleActor DActor); protected event TriggerDestroyedEvent(Controller EventInstigator) { //@note: This will always be called, unlike DelayedShutDownObject/ShutDownObject which is only called if bSelfDestructAll==TRUE OnDestroyed(self); TriggerEventClass(class'SeqEvent_Destroyed', EventInstigator); } function BumpedByMonster( KFPawn_Monster P, vector HitNormal ) { if( bAllowBumpDamageFromAI ) { if ( `TimeSince(LastBumpCheckTime) < 0.5f ) { return; } LastBumpCheckTime = WorldInfo.TimeSeconds; if( P.Controller != none ) { // Call TakeDamage with 0 because we only want to take bump damage if it is in the array of InstaKillDamageTypes TakeDamage( 0, P.Controller, P.Location, vect(0,0,0), P.BumpDamageType,, P ); } } } function int GetInstaKillDamage() { return INSTAKILL_DAMAGE; } /********************************************************************************************* * Effects ********************************************************************************************* */ /** Change the static mesh to one stored in a DamageMod * * @param ObjIdx index of subobject getting the mod * @param ModIdx index into mod array indicating which mod to apply */ simulated native protected function SetSubObjectStaticMesh(int ObjIdx, int ModIdx); /** * Apply one of the stored damage mods to this object. * * @param ObjIdx index of subobject getting the mod * @param ModIdx index into mod array indicating which mod to apply * @param bPartial TRUE for partial application, used if more than one mod triggers * in a frame. Only 1 mod will be fully applied (to prevent overlapping * sounds and whatnot. */ simulated native protected function ApplyDamageMod(int ObjIdx, int ModIdx, bool bPartial, optional Controller DamageInstigator); /** * This function will remove all of the currently attached decals from the object. * Basically, we want to have decals attach to these objects and then on state change (due to damage usually), we will * just detach them all with the big particle effect that occurs it should not be noticeable. **/ simulated native protected function RemoveDecals(); /** Spawns the explosion associated with the passed in damage mod */ simulated event Explosion(const out DestructibleSubobject SubObj, const out ObjectDamageModifier Mod) { local vector EffectLoc; local rotator EffectRot; local GameExplosionActor ExplosionActor; local GameExplosion ExplosionTemplate; //local StaticMeshActor SMActor; //local bool bOldNeverBecomeDynamic; // Temporarily enable CanBecomeDynamic and activate these actors. This is useful // for tables and other destructibles with objects stacked on top. //foreach Mod.BecomeDynamicActors(SMActor) //{ // bOldNeverBecomeDynamic = SMActor.StaticMeshComponent.bNeverBecomeDynamic; // SMActor.StaticMeshComponent.bNeverBecomeDynamic = false; // class'KActorFromStatic'.Static.MakeDynamic(SMActor.StaticMeshComponent); // SMActor.StaticMeshComponent.bNeverBecomeDynamic = bOldNeverBecomeDynamic; //} if( Mod.ExploMod.ExplosionTemplate != None ) { EffectLoc = Location + (Mod.ExploMod.RelativeLocation >> Rotation); EffectRot = Rotation + RelativeRotation; ExplosionActor = Spawn(class'KFExplosionActor', self,, EffectLoc, EffectRot); ExplosionTemplate = Mod.ExploMod.ExplosionTemplate; if (ExplosionActor != None) { // override explosion template /* ExplosionTemplate = new class'KFGameExplosion'(Mod.ExploMod.ExplosionTemplate); ExplosionTemplate.Damage = Mod.ExploMod.Damage; ExplosionTemplate.DamageRadius = Mod.ExploMod.DamageRadius; ExplosionTemplate.DamageFalloffExponent = 1.0f; ExplosionTemplate.ActorClassToIgnoreForDamage = Mod.ExploMod.ActorClassToIgnoreForDamage; ExplosionTemplate.ActorToIgnoreForDamage = self; ExplosionTemplate.DamageDelay = 0.01f; */ // Detect environment template and skip line check and damage falloff //if ( ExplosionTemplate.DamageFalloffExponent == 0.f // && ExplosionTemplate.ActorClassToIgnoreForDamage == class'Pawn' // && ExplosionTemplate.MyDamageType == class'KFDT_Environment_KActorImpulse' ) //{ // ExplosionActor.bAlwaysDoFullDamage = true; //} ExplosionActor.Explode(ExplosionTemplate); } } } /********************************************************************************************* * Destroy / UnDestroy ********************************************************************************************* */ /** Restores to it's pristine state. */ simulated native function UnDestroy(); /** "Turn off" this entire object after a delay */ simulated protected event DelayedShutDownObject() { local Actor Other; local StaticMeshActor OtherSM; SetCollision(false, false, bIgnoreEncroachers); // Disable associated blocking volume. Called when bSelfDestructAll is set // @note invalid if this is a clientside destructible if ( ReplicationMode != RT_ClientSide && BlockingVolumeToDisable != None ) { BlockingVolumeToDisable.SetCollision(false, false); } // Process DamageOtherActors foreach DamageOtherActors(Other) { if ( Other.bStatic && Other.bWorldGeometry ) { OtherSM = StaticMeshActor(Other); if ( OtherSM != None ) { // override bNeverBecomeDynamic on this StaticMeshComp OtherSM.StaticMeshComponent.bNeverBecomeDynamic = false; if ( OtherSM.StaticMeshComponent.CanBecomeDynamic() ) { Other = class'KActorFromStatic'.Static.MakeDynamic(OtherSM.StaticMeshComponent); if ( Other == None ) { // make dynamic failed, move on to next actor continue; } } } } Other.TakeRadiusDamage(None, 1000, 1000, class'KFDT_Environment_KActorImpulse', 1, Location, true, self); } SetTimer(0.2f, false, nameof(ShutDownObject)); } /** "Turn off" this entire object */ simulated function ShutDownObject() { local int Idx; bShutDown = true; // shut down all subobjects for (Idx=0; Idx