Class HardPat extends ZombieBoss; var transient float GiveUpTime; var byte MissilesLeft; var bool bEndGameBoss,bMovingChaingunAttack; var(Sounds) sound SaveMeSound; replication { reliable if( ROLE==ROLE_AUTHORITY ) bMovingChaingunAttack; } simulated function PostBeginPlay() { super.PostBeginPlay(); SyringeCount = 3; // no healing for mid-game bosses Health *= 0.75; // less hp for mid-game bosses } function bool MakeGrandEntry() { Health = HealthMax; // restore original hp bEndGameBoss = true; SyringeCount = 0; // restore healing for end-game boss return Super.MakeGrandEntry(); } function Died(Controller Killer, class damageType, vector HitLocation) { Super(KFMonster).Died(Killer,damageType,HitLocation); if ( bEndGameBoss ) KFGameType(Level.Game).DoBossDeath(); } simulated function bool HitCanInterruptAction() { return (!bWaitForAnim && !bShotAnim); } function RangedAttack(Actor A) { local float D; local bool bOnlyE; local bool bDesireChainGun; // Randomly make him want to chaingun more if( Controller.LineOfSightTo(A) && FRand() < 0.15 && LastChainGunTime1500 && Pawn(A)!=None && FRand() < 0.5 ) { SetAnimAction('MeleeImpale'); } else { SetAnimAction('MeleeClaw'); //PlaySound(sound'Claw2s', SLOT_None); KFTODO: Replace this } } else if( Level.TimeSeconds>LastSneakedTime ) { if( FRand() < 0.3 ) { // Wait another 20-40 to try this again LastSneakedTime = Level.TimeSeconds+20.f+FRand()*20; Return; } SetAnimAction('transition'); GoToState('SneakAround'); } else if( bChargingPlayer && (bOnlyE || D<200) ) Return; else if( !bDesireChainGun && !bChargingPlayer && (D<300 || (D<700 && bOnlyE)) && (Level.TimeSeconds - LastChargeTime > (5.0 + 5.0 * FRand())) ) // Don't charge again for a few seconds { SetAnimAction('transition'); GoToState('Charging'); } else if( LastMissileTime500 || SyringeCount>=2) ) { if( !Controller.LineOfSightTo(A) || FRand() > 0.75 ) { LastMissileTime = Level.TimeSeconds+FRand() * 5; Return; } LastMissileTime = Level.TimeSeconds + 10 + FRand() * 15; bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('PreFireMissile'); HandleWaitForAnim('PreFireMissile'); GoToState('FireMissile'); } else if ( !bWaitForAnim && !bShotAnim && LastChainGunTime 0.85 ) { LastChainGunTime = Level.TimeSeconds+FRand()*4; Return; } LastChainGunTime = Level.TimeSeconds + 5 + FRand() * 10; bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('PreFireMG'); HandleWaitForAnim('PreFireMG'); if ( bEndGameBoss ) MGFireCounter = Rand(60) + 35*(SyringeCount+1); else MGFireCounter = Rand(60) + 60; GoToState('FireChaingun'); } } simulated function bool AnimNeedsWait(name TestAnim) { if( TestAnim == 'FireMG' ) return !bMovingChaingunAttack; return Super.AnimNeedsWait(TestAnim); } simulated final function rotator GetBoneTransRot() { local rotator R; R.Yaw = 24576+Rotation.Yaw; R.Pitch = 16384+Rotation.Pitch; return R; } simulated function int DoAnimAction( name AnimName ) { if( AnimName=='FireMG' && bMovingChaingunAttack ) { AnimBlendParams(1, 1.0, 0.0,, FireRootBone, True); //SetBoneDirection(FireRootBone,GetBoneTransRot(),,1,1); PlayAnim('FireMG',, 0.f, 1); return 1; } else if( AnimName=='FireEndMG' ) { //SetBoneDirection(FireRootBone,rot(0,0,0),,0,0); AnimBlendParams(1, 0); } return Super.DoAnimAction( AnimName ); } simulated function AnimEnd(int Channel) { local name Sequence; local float Frame, Rate; if( Level.NetMode==NM_Client && bMinigunning ) { GetAnimParams( Channel, Sequence, Frame, Rate ); if( Sequence != 'PreFireMG' && Sequence != 'FireMG' ) { //SetBoneDirection(FireRootBone,rot(0,0,0),,0,0); Super(KFMonster).AnimEnd(Channel); return; } if( bMovingChaingunAttack ) DoAnimAction('FireMG'); else { PlayAnim('FireMG'); bWaitForAnim = true; bShotAnim = true; IdleTime = Level.TimeSeconds; } } else { //SetBoneDirection(FireRootBone,rot(0,0,0),,0,0); Super(KFMonster).AnimEnd(Channel); } } // Fix: Don't spawn needle before last stage. simulated function NotifySyringeA() { if( Level.NetMode!=NM_Client ) { if( SyringeCount<3 ) SyringeCount++; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } if( Level.NetMode!=NM_DedicatedServer ) DropNeedle(); } simulated function NotifySyringeC() { if( Level.NetMode!=NM_DedicatedServer ) { CurrentNeedle = Spawn(Class'BossHPNeedle'); CurrentNeedle.Velocity = vect(-45,300,-90) >> Rotation; DropNeedle(); } } simulated function ZombieCrispUp() // Don't become crispy. { bAshen = true; bCrispified = true; SetBurningBehavior(); } state KnockDown { Ignores RangedAttack; Begin: if ( Health > 0 ) { Sleep(GetAnimDuration('KnockDown')); CloakBoss(); PlaySound(SaveMeSound, SLOT_Misc, 2.0,,500.0); if ( KFGameType(Level.Game).FinalSquadNum == SyringeCount ) { KFGameType(Level.Game).AddBossBuddySquad(); } GotoState('Escaping'); } else { GotoState(''); } } state FireChaingun { function BeginState() { Super.BeginState(); bMovingChaingunAttack = (SyringeCount>=2); bChargingPlayer = bEndGameBoss && SyringeCount>=3 && FRand()<0.4f; bCanStrafe = true; } function EndState() { bChargingPlayer = false; Super.EndState(); bMovingChaingunAttack = false; bCanStrafe = false; } function Tick( float Delta ) { Super(KFMonster).Tick(Delta); if( bChargingPlayer ) GroundSpeed = OriginalGroundSpeed * 2.3; else GroundSpeed = OriginalGroundSpeed * 1.15; } function AnimEnd( int Channel ) { if( MGFireCounter <= 0 ) { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } else if( bMovingChaingunAttack ) { if( bFireAtWill && Channel!=1 ) return; if( Controller.Target!=None ) Controller.Focus = Controller.Target; bShotAnim = false; bFireAtWill = True; SetAnimAction('FireMG'); } else { if ( Controller.Enemy != none ) { if ( Controller.LineOfSightTo(Controller.Enemy) && FastTrace(GetBoneCoords('tip').Origin,Controller.Enemy.Location)) { MGLostSightTimeout = 0.0; Controller.Focus = Controller.Enemy; Controller.FocalPoint = Controller.Enemy.Location; } else { MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35); Controller.Focus = None; } Controller.Target = Controller.Enemy; } else { MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35); Controller.Focus = None; } if( !bFireAtWill ) { MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5); } else if ( FRand() < 0.03 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none ) { // Randomly send out a message about Patriarch shooting chain gun(3% chance) PlayerController(Controller.Enemy.Controller).Speech('AUTO', 9, ""); } bFireAtWill = True; bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireMG'); bWaitForAnim = true; } } function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) { local float EnemyDistSq, DamagerDistSq; global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType); if( bMovingChaingunAttack || Health<=0 ) return; // if someone close up is shooting us, just charge them if( InstigatedBy != none ) { DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location); if( (ChargeDamage > 200 && DamagerDistSq < (500 * 500)) || DamagerDistSq < (100 * 100) ) { SetAnimAction('transition'); GoToState('Charging'); return; } } if( Controller.Enemy != none && InstigatedBy != none && InstigatedBy != Controller.Enemy ) { EnemyDistSq = VSizeSquared(Location - Controller.Enemy.Location); DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location); } if( InstigatedBy != none && (DamagerDistSq < EnemyDistSq || Controller.Enemy == none) ) { MonsterController(Controller).ChangeEnemy(InstigatedBy,Controller.CanSee(InstigatedBy)); Controller.Target = InstigatedBy; Controller.Focus = InstigatedBy; if( DamagerDistSq < (500 * 500) ) { SetAnimAction('transition'); GoToState('Charging'); } } } Begin: While( True ) { if( !bMovingChaingunAttack ) Acceleration = vect(0,0,0); if( MGLostSightTimeout > 0 && Level.TimeSeconds > MGLostSightTimeout ) { Acceleration = vect(0,0,0); bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } if( MGFireCounter <= 0 ) { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } // Give some randomness to the patriarch's firing (constantly fire after first stage passed) if( Level.TimeSeconds > MGFireDuration && SyringeCount==0 ) { if( AmbientSound != MiniGunSpinSound ) { SoundVolume=185; SoundRadius=200; AmbientSound = MiniGunSpinSound; } Sleep(0.5 + FRand() * 0.75); MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5); } else { if( bFireAtWill ) FireMGShot(); Sleep(0.05); } } } state FireMissile { function RangedAttack(Actor A) { if( SyringeCount>=2 ) { Controller.Target = A; Controller.Focus = A; } } function BeginState() { if ( bEndGameBoss ) MissilesLeft = SyringeCount+Rand(SyringeCount); else MissilesLeft = 1+Rand(3); Acceleration = vect(0,0,0); } function AnimEnd( int Channel ) { local vector Start; local Rotator R; Start = GetBoneCoords('tip').Origin; if( Controller.Target==None ) Controller.Target = Controller.Enemy; if ( !SavedFireProperties.bInitialized ) { SavedFireProperties.AmmoClass = MyAmmo.Class; SavedFireProperties.ProjectileClass = Class'BossLAWProj'; SavedFireProperties.WarnTargetPct = 0.15; SavedFireProperties.MaxRange = 10000; SavedFireProperties.bTossed = False; SavedFireProperties.bLeadTarget = True; SavedFireProperties.bInitialized = true; } SavedFireProperties.bInstantHit = (SyringeCount<1); SavedFireProperties.bTrySplash = (SyringeCount>=2); R = AdjustAim(SavedFireProperties,Start,100); PlaySound(RocketFireSound,SLOT_Interact,2.0,,TransientSoundRadius,,false); Spawn(Class'BossLAWProj',,,Start,R); bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMissile'); HandleWaitForAnim('FireEndMissile'); // Randomly send out a message about Patriarch shooting a rocket(5% chance) if ( FRand() < 0.05 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none ) { PlayerController(Controller.Enemy.Controller).Speech('AUTO', 10, ""); } if( MissilesLeft==0 ) GoToState(''); else { --MissilesLeft; GoToState(,'SecondMissile'); } } Begin: while ( true ) { Acceleration = vect(0,0,0); Sleep(0.1); } SecondMissile: Acceleration = vect(0,0,0); Sleep(0.5f); AnimEnd(0); } State Escaping // Added god-mode if bEndGameBoss { Ignores RangedAttack; function BeginState() { GiveUpTime = Level.TimeSeconds+20.f+FRand()*20.f; Super.BeginState(); if ( bEndGameBoss ) { bBlockActors = false; // While escaping Pat doesn't blow up pipebombs -- PooSH MotionDetectorThreat=0; } } function EndState() { Super.EndState(); if( Health>0 ) bBlockActors = true; MotionDetectorThreat = default.MotionDetectorThreat; } function Tick( float Delta ) { if( Level.TimeSeconds>GiveUpTime ) { BeginHealing(); return; } if( !bChargingPlayer ) { bChargingPlayer = true; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } GroundSpeed = OriginalGroundSpeed * 2.5; Global.Tick(Delta); } function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) { if ( !bEndGameBoss ) global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType); } } State SneakAround { function BeginState() { super.BeginState(); SneakStartTime = Level.TimeSeconds+10.f+FRand()*15.f; } function EndState() { super.EndState(); LastSneakedTime = Level.TimeSeconds+20.f+FRand()*30.f; if( Controller!=None && Controller.IsInState('PatFindWay') ) Controller.GoToState('ZombieHunt'); } function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) { global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType); if( Health<=0 ) return; // if someone close up is shooting us, just charge them if( InstigatedBy!=none && VSizeSquared(Location - InstigatedBy.Location)<62500 ) GoToState('Charging'); } Begin: CloakBoss(); if( SyringeCount>=2 && FRand()<0.6 ) HardPatController(Controller).FindPathAround(); While( true ) { Sleep(0.5); if( !bCloaked && !bShotAnim ) CloakBoss(); if( !Controller.IsInState('PatFindWay') ) { if( Level.TimeSeconds>SneakStartTime ) GoToState(''); if( !Controller.IsInState('WaitForAnim') && !Controller.IsInState('ZombieHunt') ) Controller.GoToState('ZombieHunt'); } else SneakStartTime = Level.TimeSeconds+30.f; } } defaultproperties { SaveMeSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_SaveMe' RocketFireSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_FireRocket' MiniGunFireSound=Sound'KF_BasePatriarch.Attack.Kev_MG_GunfireLoop' MiniGunSpinSound=Sound'KF_BasePatriarch.Attack.Kev_MG_TurbineFireLoop' MeleeImpaleHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Impale' MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Fist' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Jump' DetachedArmClass=Class'KFChar.SeveredArmPatriarch' DetachedLegClass=Class'KFChar.SeveredLegPatriarch' DetachedHeadClass=Class'KFChar.SeveredHeadPatriarch' DetachedSpecialArmClass=Class'KFChar.SeveredRocketArmPatriarch' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Death' ScoringValue=1000 MenuName="Hard Pat" ControllerClass=Class'ScrnZedPack.HardPatController' AmbientSound=Sound'KF_BasePatriarch.Idle.Kev_IdleLoop' Mesh=SkeletalMesh'KF_Freaks_Trip.Patriarch_Freak' LODBias=4.000000 Skins(0)=Combiner'KF_Specimens_Trip_T.gatling_cmb' Skins(1)=Combiner'KF_Specimens_Trip_T.patriarch_cmb' }