//============================================================================= // KFPawn_ZedBloat //============================================================================= // Bloat //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class KFPawn_ZedBloat extends KFPawn_Monster; /** Socket used to attach puke effect */ var protected const name PukeSocketName; /** How far away the bloat's vomit can damage enemies */ var protected const float VomitRange; /** At a base level how much damage this zed's vomit will do */ var protected const int VomitDamage; /** Any pawn in this range will take damage when the bloat explodes */ var protected const float ExplodeRange; /** Set to TRUE when bloat has exploded */ var protected bool bHasExploded; /** Projectile to spawn for puke mine attack */ var protected const class PukeMineProjectileClass; /** Rotation offsets to use when spawning our 3 puke mins on death */ var protected array DeathPukeMineRotations; /** Number of puke mines to spawn when bloat is killed, as long as it wasn't obliterated */ var protected byte NumPukeMinesToSpawnOnDeath; /** Pre-death location and rotation */ var protected vector OldLocation; var protected rotator OldRotation; /** Set to TRUE when a non-explosive hitzone was injured */ var transient protected bool bWasDismembered; simulated function PostBeginPlay() { super.PostBeginPlay(); // Set the number of puke mines we're allowed to spawn on death if( WorldInfo.Game != none ) { NumPukeMinesToSpawnOnDeath = class(DifficultySettings).static.GetPukeMinesToSpawnOnDeath( self, WorldInfo.Game ); } } /** Script AnimNotify which makes the Bloat begin to discharge vomit */ function ANIMNOTIFY_PukeAttack() { Puke(); } /** Guess */ function Puke() { local Pawn P; local Vector PukeLocation, PukeDirection; local Rotator PukeRotation; local vector HitLocation, HitNormal, EndTrace, Momentum; local Actor HitActor; Mesh.GetSocketWorldLocationAndRotation( 'PukeSocket', PukeLocation, PukeRotation ); PukeDirection = Vector(Rotation); PukeDirection.Z = 0.f; foreach WorldInfo.AllPawns(class'Pawn', P, PukeLocation, VomitRange) { if( CanPukeOnTarget(P, PukeLocation, PukeDirection) ) { DealPukeDamage( P, PukeLocation ); } } // Extra handling for fracture mesh actors, etc EndTrace = PukeLocation + PukeDirection*VomitRange; HitActor = Trace( HitLocation, HitNormal, EndTrace, PukeLocation, true, vect(10,10,10) ); if( HitActor != none && HitActor.bCanBeDamaged && Pawn(HitActor) == none ) { Momentum = EndTrace - PukeLocation; Momentum.Z = 0.f; Momentum = Normal(Momentum); HitActor.TakeDamage( VomitDamage, Controller, HitLocation, Momentum, class'KFDT_BloatPuke',, self ); } } function DealPukeDamage( Pawn Victim, Vector Origin ) { local Vector VectToEnemy; VectToEnemy = Victim.Location - Origin; VectToEnemy.Z = 0.f; VectToEnemy = Normal( VectToEnemy ); Victim.TakeDamage( GetRallyBoostDamage(VomitDamage), Controller, Victim.Location, VectToEnemy, class'KFDT_BloatPuke',, self ); } function bool CanPukeOnTarget( Pawn PukeTarget, Vector PukeLocation, Vector PukeDirection) { local Vector VectToEnemy; local Box ActorBox; if( PukeTarget != none ) { VectToEnemy = PukeTarget.Location - PukeLocation; VectToEnemy.Z = 0.f; VectToEnemy = Normal( VectToEnemy ); PukeTarget.GetComponentsBoundingBox( ActorBox ); if( ( !PukeTarget.bWorldGeometry || PukeTarget.bCanBeDamaged ) && ActorBox.Min.Z < PukeLocation.Z && VectToEnemy dot PukeDirection > 0.5f ) // (0.5 = vomitarc) { // use the bounding box for the world trace to account for meshes with way off center origins if( PukeTarget.FastTrace( (ActorBox.Min + ActorBox.Max) * 0.5, PukeLocation,, true) ) { return true; } } } return false; } /** Override so you can always injure a bloats stomach and cause an explosion */ function bool CanInjureHitZone(class DamageType, int HitZoneIdx) { if( HitExplosiveBone( HitZones[HitZoneIdx].BoneName ) ) { return true; } if( super.CanInjureHitZone(DamageType, HitZoneIdx) ) { bWasDismembered = true; return true; } return false; } /** Overridden to only return TRUE if a non-explosive bone was hit */ simulated function bool HasInjuredHitZones() { return bWasDismembered || IsHeadless(); } /** This pawn has died. */ function bool Died(Controller Killer, class damageType, vector HitLocation) { OldLocation = Location; OldRotation = Rotation; return super.Died( Killer, damageType, HitLocation ); } // Override to deal explosive damage for the killing shot of an explosive bone function TakeHitZoneDamage(float Damage, class DamageType, int HitZoneIdx, vector InstigatorLocation) { local int HitZoneIndex; local name HitBoneName; super.TakeHitZoneDamage( Damage, DamageType, HitZoneIdx, InstigatorLocation ); // Only deal explosive damage on the killing shot if( Role == ROLE_Authority && bPlayedDeath && !bHasExploded && TimeOfDeath == WorldInfo.TimeSeconds ) { HitZoneIndex = HitFxInfo.HitBoneIndex; if ( HitZoneIndex != 255 && (InjuredHitZones & (1 << HitZoneIndex)) > 0 ) // INDEX_None -> 255 after byte conversion { HitBoneName = HitZones[HitZoneIndex].BoneName; if( HitExplosiveBone(HitBoneName) ) { DealExplosionDamage(); bHasExploded = true; // Spawn some puke mines SpawnPukeMinesOnDeath(); SoundGroupArch.PlayObliterationSound(self, false); } } } } /** Check if the bone we hit can cause an explosion */ function bool HitExplosiveBone( name HitBoneName ) { local byte JointIndex; local KFCharacterInfo_Monster MonsterInfo; MonsterInfo = GetCharacterMonsterInfo(); if ( MonsterInfo != none ) { for( JointIndex = 0; JointIndex < MonsterInfo.GoreJointSettings.length; JointIndex++ ) { if( MonsterInfo.GoreJointSettings[JointIndex].HitBoneName == HitBoneName && MonsterInfo.GoreJointSettings[JointIndex].HitExplosionGore.length > 0 ) { return true; } } } return false; } function DealExplosionDamage() { local Pawn P; local vector HitLocation, HitNormal; local Actor HitActor; // @note - At low ranges CollidingActors (no VisibleCollidingActors) is okay, // but AllPawns is constant and much faster 99% of the time. foreach WorldInfo.AllPawns( class'Pawn', P, Location, ExplodeRange ) { if ( P != Instigator ) { // Trace to make sure there are no obstructions. ie acquiring someone through a wall HitActor = Instigator.Trace(HitLocation, HitNormal, P.Location, Location, true); if ( HitActor == none || HitActor == P ) { DealPukeDamage(P, Location); } } } } /** Spawns a puke mine at the specified location and rotation. Network: SERVER */ function SpawnPukeMine( vector SpawnLocation, rotator SpawnRotation ) { local KFProjectile PukeMine; PukeMine = Spawn( PukeMineProjectileClass, ,, SpawnLocation, SpawnRotation,, true ); if( PukeMine != none ) { PukeMine.Init( vector(SpawnRotation) ); } } /** Spawns several puke mines when dying */ function SpawnPukeMinesOnDeath() { local int i; // Spawn puke mines while( NumPukeMinesToSpawnOnDeath > 0 && DeathPukeMineRotations.Length > 0 ) { i = Rand( DeathPukeMineRotations.Length ); SpawnPukeMine( OldLocation, Normalize(OldRotation + DeathPukeMineRotations[i]) ); DeathPukeMineRotations.Remove( i, 1 ); --NumPukeMinesToSpawnOnDeath; } } /** * Dialog **/ /** Returns (hardcoded) dialog event ID for when trader gives advice to player who was killed by this zed type */ static function int GetTraderAdviceID() { return 41;//TRAD_AdviceBloat } DefaultProperties { LocalizationKey=KFPawn_ZedBloat Begin Object Name=KFPawnSkeletalMeshComponent // Enabling kinematic for physics interaction while alive. (see also MinDistFactorForKinematicUpdate) bUpdateKinematicBonesFromAnimation=true bPerBoneMotionBlur=false End Object // --------------------------------------------- // Stats XPValues(0)=17 XPValues(1)=22 XPValues(2)=30 XPValues(3)=34 //BaseEyeHeight=1.f // --------------------------------------------- // Content MonsterArchPath="ZED_ARCH.ZED_Bloat_Archetype" PawnAnimInfo=KFPawnAnimInfo'ZED_Bloat_ANIM.Bloat_AnimGroup' DifficultySettings=class'KFDifficulty_Bloat' // --------------------------------------------- // Special Moves Begin Object Name=SpecialMoveHandler_0 SpecialMoveClasses(SM_Evade)=class'KFSM_Evade' SpecialMoveClasses(SM_Evade_Fear)=class'KFSM_Evade_Fear' SpecialMoveClasses(SM_Block)=class'KFSM_Block' End Object // for reference: Vulnerability=(default, head, legs, arms, special) IncapSettings(AF_Stun)= (Vulnerability=(0.5, 1.0, 0.5, 0.5, 0.5), Cooldown=5.0, Duration=1.5) IncapSettings(AF_Knockdown)=(Vulnerability=(1.0), Cooldown=1.0) IncapSettings(AF_Stumble)= (Vulnerability=(0.4), Cooldown=1.0) IncapSettings(AF_GunHit)= (Vulnerability=(0.35), Cooldown=0.1) IncapSettings(AF_MeleeHit)= (Vulnerability=(2.0), Cooldown=0.3) IncapSettings(AF_Poison)= (Vulnerability=(0.15), Cooldown=20.5, Duration=5.0) IncapSettings(AF_Microwave)=(Vulnerability=(4.0), Cooldown=5.0, Duration=8.0) IncapSettings(AF_FirePanic)=(Vulnerability=(1), Cooldown=5.0, Duration=3.7) //duration 8 IncapSettings(AF_EMP)= (Vulnerability=(2.5), Cooldown=5.0, Duration=3.0) IncapSettings(AF_Freeze)= (Vulnerability=(1.0), Cooldown=3.0, Duration=2.0) IncapSettings(AF_Snare)= (Vulnerability=(1.0, 2.0, 1.0, 1.0), Cooldown=5.5, Duration=3.0) IncapSettings(AF_Bleed)= (Vulnerability=(1.0)) IncapSettings(AF_Shrink)= (Vulnerability=(1.0)) ShrinkEffectModifier = 0.25f Begin Object Name=Afflictions_0 FireFullyCharredDuration=3.5 End Object ParryResistance=3 // --------------------------------------------- // Gameplay VomitRange=350.f VomitDamage=12 ExplodeRange=500.f Begin Object Name=MeleeHelper_0 BaseDamage=14.f MaxHitRange=250.f MomentumTransfer=25000.f MyDamageType=class'KFDT_Slashing_ZedWeak' End Object Health=405 HeadlessBleedOutTime=6.f // Override Head GoreHealth (aka HeadHealth) HitZones[HZI_HEAD]=(ZoneName=head, BoneName=Head, Limb=BP_Head, GoreHealth=75, DmgScale=1.0001, SkinID=1) HitZones.Add((ZoneName=rknife, BoneName=RightForearm, Limb=BP_RightArm, GoreHealth=20, DmgScale=0.2, SkinID=2)) HitZones.Add((ZoneName=lknife, BoneName=LeftForearm, Limb=BP_LeftArm, GoreHealth=20, DmgScale=0.2, SkinID=2)) DoshValue=17 Mass=130.f // Penetration PenetrationResistance=3.0 DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Submachinegun', DamageScale=(0.35))) //0.25 DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_AssaultRifle', DamageScale=(0.35))) //0.25 DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Shotgun', DamageScale=(0.25))) DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Handgun', DamageScale=(0.35))) //0.2 DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Rifle', DamageScale=(0.30))) DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing', DamageScale=(0.3))) DamageTypeModifiers.Add((DamageType=class'KFDT_Bludgeon', DamageScale=(0.3))) DamageTypeModifiers.Add((DamageType=class'KFDT_Fire', DamageScale=(1.0))) //1.2 //1.6 DamageTypeModifiers.Add((DamageType=class'KFDT_Microwave', DamageScale=(0.8))) DamageTypeModifiers.Add((DamageType=class'KFDT_Explosive', DamageScale=(0.5))) DamageTypeModifiers.Add((DamageType=class'KFDT_Piercing', DamageScale=(0.25))) DamageTypeModifiers.Add((DamageType=class'KFDT_Toxic', DamageScale=(0.25))) //Special Case damage resistance DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_9mm', DamageScale=(0.65))) DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_AR15', DamageScale=(0.40))) DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_HRGTeslauncher', DamageScale=(0.58))) // --------------------------------------------- // Block Settings MinBlockFOV=0.1f // --------------------------------------------- // Movement / Physics Begin Object Name=CollisionCylinder CollisionRadius=+0055.000000 // Mesh is pulled back by 20 UU End Object RotationRate=(Pitch=50000,Yaw=20000,Roll=50000) GroundSpeed=150.0f SprintSpeed=210.0f //260 PhysRagdollImpulseScale=1.5f KnockdownImpulseScale=2f //1.5 // --------------------------------------------- // AI / Navigation ControllerClass=class'KFAIController_ZedBloat' BumpDamageType=class'KFDT_NPCBump_Large' DamageRecoveryTimeHeavy=0.85f DamageRecoveryTimeMedium=1.0f // --------------------------------------------- // Puke Mines PukeMineProjectileClass=class'KFGameContent.KFProj_BloatPukeMine' DeathPukeMineRotations(0)=(Pitch=7000,Yaw=10480,Roll=0) DeathPukeMineRotations(1)=(Pitch=7000,Yaw=32767,Roll=0) DeathPukeMineRotations(2)=(Pitch=7000,Yaw=-10480,Roll=0) // --------------------------------------------- `if(`notdefined(ShippingPC)) DebugRadarTexture=Texture2D'UI_ZEDRadar_TEX.MapIcon_Bloat'; `endif // --------------------------------------------- // Spawning MinSpawnSquadSizeType=EST_Large bIsBloatClass=true // Only used in Volter Castle for now when the spawn volume has bForceUseMapReplacePawn set to true // If we need to reuse it more we'll have to connect map to zed here MapReplacePawnClass.Add(class'KFPawn_ZedHansClot') }