//----------------------------------------------------------- // //----------------------------------------------------------- class NiceZombieBossController extends KFMonsterController; var NavigationPoint HidingSpots; var float WaitAnimTimeout; // How long until the Anim we are waiting for is completed; Hack so the server doesn't get stuck in idle when its doing the Rage anim var int AnimWaitChannel; // The channel we are waiting to end in WaitForAnim var name AnimWaitingFor; // The animation we are waiting to end in WaitForAnim, mostly used for debugging var bool bAlreadyFoundEnemy; // The Boss has already found an enemy at least once function bool CanKillMeYet() { return false; } function TimedFireWeaponAtEnemy() { if ( (Enemy == none) || FireWeaponAt(Enemy) ) SetCombatTimer(); else SetTimer(0.01, True); } // Overridden to support a quick initial attack to get the boss to the players quickly function FightEnemy(bool bCanCharge) { if( KFM.bShotAnim ) { GoToState('WaitForAnim'); Return; } if (KFM.MeleeRange != KFM.default.MeleeRange) KFM.MeleeRange = KFM.default.MeleeRange; if ( Enemy == none || Enemy.Health <= 0 ) FindNewEnemy(); if ( (Enemy == FailedHuntEnemy) && (Level.TimeSeconds == FailedHuntTime) ) { // if ( Enemy.Controller.bIsPlayer ) // FindNewEnemy(); if ( Enemy == FailedHuntEnemy ) { GoalString = "FAILED HUNT - HANG OUT"; if ( EnemyVisible() ) bCanCharge = false; } } if ( !EnemyVisible() ) { // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm if( bAlreadyFoundEnemy || NiceZombieBoss(Pawn).SneakCount > 2 ) { bAlreadyFoundEnemy = true; GoalString = "Hunt"; GotoState('ZombieHunt'); } else { // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm NiceZombieBoss(Pawn).SneakCount++; GoalString = "InitialHunt"; GotoState('InitialHunting'); } return; } // see enemy - decide whether to charge it or strafe around/stand and fire Target = Enemy; GoalString = "Charge"; PathFindState = 2; DoCharge(); } // Get the boss to the players quickly after initial spawn state InitialHunting extends Hunting { event SeePlayer(Pawn SeenPlayer) { super.SeePlayer(SeenPlayer); bAlreadyFoundEnemy = true; GoalString = "Hunt"; GotoState('ZombieHunt'); } function BeginState() { local float ZDif; // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm NiceZombieBoss(Pawn).SneakCount++; if( Pawn.CollisionRadius>27 || Pawn.CollisionHeight>46 ) { ZDif = Pawn.CollisionHeight-44; Pawn.SetCollisionSize(24,44); Pawn.MoveSmooth(vect(0,0,-1)*ZDif); } super.BeginState(); } function EndState() { local float ZDif; if( Pawn.CollisionRadius!=Pawn.Default.CollisionRadius || Pawn.CollisionHeight!=Pawn.Default.CollisionHeight ) { ZDif = Pawn.Default.CollisionRadius-44; Pawn.MoveSmooth(vect(0,0,1)*ZDif); Pawn.SetCollisionSize(Pawn.Default.CollisionRadius,Pawn.Default.CollisionHeight); } super.EndState(); } } state ZombieCharge { function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) { return false; } // I suspect this function causes bloats to get confused function bool TryStrafe(vector sideDir) { return false; } function Timer() { Disable('NotifyBump'); Target = Enemy; TimedFireWeaponAtEnemy(); } WaitForAnim: if ( Monster(Pawn).bShotAnim ) { Goto('Moving'); } if ( !FindBestPathToward(Enemy, false,true) ) GotoState('ZombieRestFormation'); Moving: MoveToward(Enemy); WhatToDoNext(17); if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); } state RunSomewhere { Ignores HearNoise,DamageAttitudeTo,Tick,EnemyChanged,Startle; function BeginState() { HidingSpots = none; Enemy = none; SetTimer(0.1,True); } event SeePlayer(Pawn SeenPlayer) { SetEnemy(SeenPlayer); } function Timer() { if( Enemy==none ) Return; Target = Enemy; KFM.RangedAttack(Target); } Begin: if( Pawn.Physics==PHYS_Falling ) WaitForLanding(); While( KFM.bShotAnim ) Sleep(0.25); if( HidingSpots==none ) HidingSpots = FindRandomDest(); if( HidingSpots==none ) NiceZombieBoss(Pawn).BeginHealing(); if( ActorReachable(HidingSpots) ) { MoveTarget = HidingSpots; HidingSpots = none; } else FindBestPathToward(HidingSpots,True,False); if( MoveTarget==none ) NiceZombieBoss(Pawn).BeginHealing(); if( Enemy!=none && VSize(Enemy.Location-Pawn.Location)<100 ) MoveToward(MoveTarget,Enemy,,False); else MoveToward(MoveTarget,MoveTarget,,False); if( HidingSpots==none || !PlayerSeesMe() ) NiceZombieBoss(Pawn).BeginHealing(); GoTo'Begin'; } State SyrRetreat { Ignores HearNoise,DamageAttitudeTo,Tick,EnemyChanged,Startle; function BeginState() { HidingSpots = none; Enemy = none; SetTimer(0.1,True); } event SeePlayer(Pawn SeenPlayer) { SetEnemy(SeenPlayer); } function Timer() { if( Enemy==none ) Return; Target = Enemy; KFM.RangedAttack(Target); } function FindHideSpot() { local NavigationPoint N,BN; local float Dist,BDist,MDist; local vector EnemyDir; if( Enemy==none ) { HidingSpots = FindRandomDest(); Return; } EnemyDir = Normal(Enemy.Location-Pawn.Location); For( N=Level.NavigationPointList; N!=none; N=N.NextNavigationPoint ) { MDist = VSize(N.Location-Pawn.Location); if( MDist<2500 && !FastTrace(N.Location,Enemy.Location) && FindPathToward(N)!=none ) { Dist = VSize(N.Location-Enemy.Location)/FMax(MDist/800.f,1.5); if( (EnemyDir Dot Normal(Enemy.Location-N.Location))<0.2 ) Dist/=10; if( BN==none || BDist