//============================================================================= // KFMGA_Target //============================================================================= // Actor used as an individual target in any of the target minigames. Calls // back into the base target game that spawned it when it takes valid // damage. //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Dan Weiss //============================================================================= class KFMGA_Target extends Actor implements(KFInterface_MinigameTarget); /** Visual component of the target */ var() StaticMeshComponent Mesh; /** Collision cylinder*/ var() CylinderComponent CollisionCylinder; /** How many hits before the game is notified of "death" */ var() repnotify int NumHits; /** Rigged target game that spawned this target. Used to sync owner chain on remote clients */ var repnotify KFMG_TargetGame SpawnerOwner; /** Index this was added in to make sure arrays stay fully in sync */ var repnotify int SpawnerOwnerIndex; /** Whether or not to ignore radius damage sources (ex: grenades) */ var() bool bIgnoreRadiusDamage; /** Whether or not target is active */ var repnotify bool bActiveTarget; /** List of acceptable damage types */ var array< class > AcceptedDamageTypes; /** Name of bone we are based on */ var name AttachBoneName; //---------------------- // Client FX var() ParticleSystem DeathVFXTemplate; var() AkEvent DeathSFX; var() AkComponent DeathSFXComponent; var() AkEvent InvalidHitSFX; replication { if (bNetDirty) SpawnerOwner, SpawnerOwnerIndex, bActiveTarget, NumHits; } simulated event ReplicatedEvent(name VarName) { super.ReplicatedEvent(VarName); //Allow the target to manage its own state in the main rig's targets array. if (VarName == 'SpawnerOwner' || VarName == 'SpawnerOwnerIndex') { if (SpawnerOwner != none && SpawnerOwnerIndex >= 0) { AddToOwnerArray(); } } else if (VarName == 'bActiveTarget') { if (bActiveTarget) { SetActive(); } else { SetInactive(); } } else if (VarName == 'NumHits') { if (NumHits == 0) { PlayDeath(); } } } /** Called via repnotify on clients to sync with main spawner's target array */ simulated function AddToOwnerArray() { if (SpawnerOwner.MinigameTargets.Find(self) == INDEX_NONE) { SpawnerOwner.MinigameTargets[SpawnerOwnerIndex] = self; SpawnerOwner.UpdateBase(self); } } simulated function TakeRadiusDamage(Controller InstigatedBy, float BaseDamage, float DamageRadius, class DamageType, float Momentum, vector HurtOrigin, bool bFullDamage, Actor DamageCauser, optional float DamageFalloffExponent = 1.f) { if (!bIgnoreRadiusDamage || AcceptedDamageTypes.Find(DamageType) != INDEX_NONE) { TakeDamage(BaseDamage, InstigatedBy, HurtOrigin, vect(0,0,0), DamageType, , DamageCauser); } } /** The amount of damage is unimportant in this case. These targets will never be killed, but simply be reset * to their initial condition upon "death". Track number of hits and go into death condition once * specified hit count has been reached. */ simulated event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { local int i; if (!bActiveTarget) { return; } for (i = 0; i < AcceptedDamageTypes.Length; ++i) { //Valid hit if (DamageType == AcceptedDamageTypes[i] || ClassIsChildOf(DamageType, AcceptedDamageTypes[i])) { ValidHit(EventInstigator, DamageCauser); return; } } InvalidHit(EventInstigator, DamageCauser); } function ValidHit(Controller EventInstigator, Actor HitActor) { local KFMG_TargetGame TargetGame; if (Role == ROLE_Authority) { --NumHits; if (NumHits <= 0) { PlayDeath(); SetInactive(); TargetGame = KFMG_TargetGame(Owner); if (TargetGame != none) { TargetGame.TargetHit(self, EventInstigator); } } } } simulated function InvalidHit(Controller EventInstigator, Actor HitActor) { if (DeathSFXComponent != none) { DeathSFXComponent.PlayEvent(InvalidHitSFX, , false, true); } } /** Do any resets necessary here, including health and visual items. */ simulated event Reset() { super.Reset(); SetRelativeLocation(vect(0, 0, 0)); NumHits = default.NumHits; } simulated event SetActive() { bActiveTarget = true; SetHidden(false); } simulated event SetInactive() { bActiveTarget = false; SetHidden(true); } simulated event Finalize(); simulated event PlayDeath() { if (WorldInfo.NetMode != NM_DedicatedServer && !IsAlive()) { if (DeathVFXTemplate != none) { WorldInfo.MyEmitterPool.SpawnEmitter(DeathVFXTemplate, Location); } if (DeathSFXComponent != none) { DeathSFXComponent.PlayEvent(DeathSFX, , false, true); } } } simulated function bool IsAlive() { return NumHits > 0; } defaultproperties { NumHits = 1 bStatic = false Begin Object class=CylinderComponent Name=Cylinder0 CollisionRadius = 25.0 CollisionHeight = 25.0 BlockNonZeroExtent = true BlockZeroExtent = true BlockActors = true CollideActors = true End Object Components.Add(Cylinder0) CollisionCylinder = Cylinder0 CollisionComponent = Cylinder0 Begin Object class=StaticMeshComponent Name=Mesh0 End Object Mesh = Mesh0 Components.Add(Mesh0) Begin Object class=AkComponent Name=DeathSFX0 End Object DeathSFXComponent=DeathSFX0 Components.Add(DeathSFX0) RemoteRole = ROLE_SimulatedProxy NetPriority = +00002.500000 bCollideActors = true bBlockActors = false bProjTarget = true bCanBeDamaged = true CollisionType = COLLIDE_TouchAll bIgnoreRadiusDamage = true SpawnerOwnerIndex = -1 AcceptedDamageTypes.Add(class'KFDT_Ballistic_9mm') bAlwaysRelevant = true }