class KFMonsterController extends MonsterController; // Custom Zombie Thinkerating // By : Alex var KFMonster KFM; var int MoanTime,ThreatTime; var bool ItsSet,bUseFreezeHack,bAboutToGetDoor,bTriggeredFirstEvent; var int TimeToSet; var int EvaluateTime; var float ChargeStart,LastCorpseTime; var byte NumAttmpts; var byte CorpseBiteCount; var KFDoorMover TargetDoor; var PlayerDeathMark TargetCorpse; var KActor KickTarget; var Actor InitialPathGoal; var byte PathFindState; // Used for alternative pathing: var Actor LastResult; var NavigationPoint BlockedWay,ExtraCostWay; var float NearMult, FarMult; // multipliers for startle collision distances var KFMonster AvoidMonster; // vehicle we are currently avoiding var actor StartleActor; // Actor we're moving away from because it startled us var vector GetOutOfTheWayDirection; // When told to get out of the way of an incoming shot, this is the direction the shot is coming from var vector GetOutOfTheWayOrigin; // When told to get out of the way of an incoming shot, this is where the shot game from struct KillAssistant { var Controller PC; var float Damage; }; var array KillAssistants; // List of Controllers who damaged the specimen /* Should this monster query the pawns he is attacking to assess threat priority? - NOTE: currently only enabled in story mode gametype*/ var bool bUseThreatAssessment; var PathNode ScriptedMoveTarget; function Restart() { KFM = KFMonster(Pawn); if( !KFM.bStartUpDisabled ) Super.Restart(); } function Reset() { StartleActor = none; Super.Reset(); } event bool NotifyBump(actor Other) { local Pawn P; Disable('NotifyBump'); P = Pawn(Other); if ( (P == None) || (P.Controller == None) || (Enemy == P) ) return false; if ( SetEnemy(P) ) { WhatToDoNext(4); return false; } if ( Enemy == P ) return false; if ( !AdjustAround(P) ) CancelCampFor(P.Controller); return false; } event SeePlayer(Pawn SeenPlayer) { if ( ((ChooseAttackCounter < 2) || (ChooseAttackTime != Level.TimeSeconds)) && SetEnemy(SeenPlayer) ) WhatToDoNext(3); if ( Enemy == SeenPlayer ) { VisibleEnemy = Enemy; EnemyVisibilityTime = Level.TimeSeconds; bEnemyIsVisible = true; } } // Overridden because we want our Zeds to be a bit more scared of fear spots function FearThisSpot(AvoidMarker aSpot) { if ( Skill > 1 + 2.0 * FRand() ) super(Controller).FearThisSpot(aSpot); } // Make the AI try and stay away from this Monster function AvoidThisMonster(KFMonster Feared) { GoalString = "VEHICLE AVOID!"; AvoidMonster = Feared; GotoState('MonsterAvoid'); } // This state currently only used in KFO-FrightYard to force Zeds into the toxic pit. // State is activated by KFVolume_ZedPit, when touched by a Zed. state ScriptedMoveTo { ignores TakeDamage, SeePlayer, HearNoise, SeeMonster, Bump, HitWall, Touch; function BeginState() { //log( self$" MoveToNodeGoal BeginState" ); } Begin: if( Pawn.Physics == PHYS_Falling ) { WaitForLanding(); } if( ActorReachable( ScriptedMoveTarget ) ) { MoveToward( ScriptedMoveTarget ); Goto( 'Begin' ); } else if( FindBestPathToward( ScriptedMoveTarget, false, false ) ) { MoveToward( MoveTarget ); Goto( 'Begin' ); } else { Sleep( 0.1f ); Goto( 'Begin' ); } } // State for being scared of something, the bot attempts to move away from it state MonsterAvoid { ignores EnemyNotVisible,SeePlayer,HearNoise; function AvoidThisMonster(KFMonster Feared) { GoalString = "AVOID MONSTER!"; // Switch to the new guy if he is closer if (VSizeSquared(Pawn.Location - Feared.Location) < VSizeSquared(Pawn.Location - AvoidMonster.Location)) { AvoidMonster = Feared; BeginState(); } } function BeginState() { SetTimer(0.4,true); } event Timer() { local vector dir, side; local float dist; if ( AvoidMonster == None || AvoidMonster.Velocity dot (Pawn.Location - AvoidMonster.Location) < 0) { WhatToDoNext(11); return; } Pawn.bIsWalking = false; Pawn.bWantsToCrouch = False; dir = Pawn.Location - AvoidMonster.Location; dist = VSize(dir); if (dist <= AvoidMonster.CollisionRadius*NearMult) HitTheDirt(); else if (dist < AvoidMonster.CollisionRadius*FarMult) { side = dir cross vect(0,0,1); // pick the shortest direction to move to if (side dot AvoidMonster.Velocity > 0) Destination = Pawn.Location + (-Normal(side) * (AvoidMonster.CollisionRadius*FarMult)); else Destination = Pawn.Location + (Normal(side) * AvoidMonster.CollisionRadius*FarMult); GoalString = "AVOID VEHICLE! Moving my arse.."; } } function HitTheDirt() { local vector dir, side; GoalString = "AVOID Monster! Jumping!!!"; dir = Pawn.Location - AvoidMonster.Location; side = dir cross vect(0,0,1); Pawn.Velocity = Pawn.AccelRate * Normal(side); // jump the other way if its shorter if (side dot AvoidMonster.Velocity > 0) Pawn.Velocity = -Pawn.Velocity; Pawn.Velocity.Z = Pawn.JumpZ; bPlannedJump=True; Pawn.SetPhysics(PHYS_Falling); } function EndState() { bTimerLoop = False; AvoidMonster=None; Focus=None; } Begin: WaitForLanding(); MoveTo(Destination,AvoidMonster,False); if (AvoidMonster == None || VSize(Pawn.Location - AvoidMonster.Location) > AvoidMonster.CollisionRadius*FarMult || AvoidMonster.Velocity dot (Pawn.Location - AvoidMonster.Location) < 0) { WhatToDoNext(11); warn("!! " @ Pawn.GetHumanReadableName() @ " STUCK IN AVOID MONSTER !!"); GoalString = "!! STUCK IN AVOID MONSTER !!"; } Sleep(0.2); GoTo('Begin'); } // Something is shooting along a line, get out of the way of that line function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin) { GetOutOfTheWayDirection = ShotDirection; GetOutOfTheWayOrigin = ShotOrigin; if( KFMonster(Pawn) != none && KFMonster(Pawn).CanGetOutOfWay() && !IsInState('GettingOutOfTheWayOfShot')) { GotoState('GettingOutOfTheWayOfShot'); } } // State for being scared of something, the bot attempts to move away from it state GettingOutOfTheWayOfShot { ignores EnemyNotVisible,SeePlayer,HearNoise; function HitTheDirt() { local vector dir, side; GoalString = "AVOID Shot! Jumping!!!"; dir = GetOutOfTheWayDirection; side = dir cross vect(0,0,1); Pawn.Velocity = Pawn.JumpZ * 0.5 * Normal(side); // jump the other way if its shorter if ( side dot (Pawn.Location - GetOutOfTheWayOrigin) < 0) Pawn.Velocity = -Pawn.Velocity; Pawn.Velocity.Z = Pawn.JumpZ * 0.5; bPlannedJump=True; Pawn.SetPhysics(PHYS_Falling); } Begin: WaitForLanding(); HitTheDirt(); Sleep(0.2); WhatToDoNext(11); } // Something has startled this actor and they want to stay away from it function Startle(Actor Feared) { if ( Monster(Pawn) != none && !Monster(Pawn).bShotAnim && Skill > 1 + 2.0 * FRand() ) { GoalString = "STARTLED!"; StartleActor = Feared; GotoState('Startled'); } } state Startled { ignores EnemyNotVisible,SeePlayer,HearNoise; function Startle(Actor Feared) { GoalString = "STARTLED!"; StartleActor = Feared; BeginState(); } function BeginState() { // FIXME - need FindPathAwayFrom() Pawn.Acceleration = Pawn.Location - StartleActor.Location; Pawn.Acceleration.Z = 0; Pawn.bIsWalking = false; Pawn.bWantsToCrouch = false; if ( Pawn.Acceleration == vect(0,0,0) ) Pawn.Acceleration = VRand(); Pawn.Acceleration = Pawn.AccelRate * Normal(Pawn.Acceleration); } Begin: Sleep(0.5); WhatToDoNext(11); Goto('Begin'); } // Randomly plays a different moan sound for the Zombie each time it is called. Gruesome! function ZombieMoan() { if( Pawn==None || Pawn.Health<=0 ) Destroy(); else if( !KFM.bDecapitated ) // Headless zombies can't moan. KFM.ZombieMoan(); } function PostBeginPlay() { Super.PostBeginPlay(); SetCombatTimer(); if ( UnrealMPGameInfo(Level.Game).bSoaking ) bSoaking = true; MoanTime = Level.TimeSeconds + 2 + (36*FRand()); EvaluateTime = Level.TimeSeconds; TimeToSet = Level.TimeSeconds; ItsSet=false; /* Kind of a hack, but it's the safest way to toggle Threat assessment behaviour right now .. */ if(KFGameType(Level.Game) != none && KFGameType(Level.Game).bUseZEDThreatAssessment) { bUseThreatAssessment = true; } } // Get rid of this Zed if he's stuck somewhere and noone has seen him function bool CanKillMeYet() { if( KFMonster(Pawn) != none && KFGameType(Level.Game).WaveNum >= KFGameType(Level.Game).FinalWave ) { return true; } if( KFMonster(Pawn) != none && (Level.TimeSeconds - KFMonster(Pawn).LastSeenOrRelevantTime) > 8 ) { return true; } return false; } function BreakUpDoor( KFDoorMover Other, bool bTryDistanceAttack ) // I have came up to a door, break it! { TargetDoor = Other; if( Pawn != none && KFMonster(Pawn) != none ) { if( KFMonster(Pawn).bCanDistanceAttackDoors && bTryDistanceAttack ) { KFMonster(Pawn).bDistanceAttackingDoor = true; } else { KFMonster(Pawn).bDistanceAttackingDoor = false; } } GoalString = "DOORBASHING"; GotoState('DoorBashing'); } // The Times between each call of the ZombieMoan function ... function tick(float DeltaTime) { if( Level.TimeSeconds >= MoanTime ) { ZombieMoan(); MoanTime = Level.TimeSeconds + 12 + (FRand()*8); } if( bAboutToGetDoor ) { bAboutToGetDoor = False; if( TargetDoor!=None ) { BreakUpDoor(TargetDoor, true); } } } // If we're not dead, and we can see our target, and we still have a head. lets go eat it. function bool FindFreshBody() { local KFGameType K; local int i; local PlayerDeathMark Best; local float Dist,BDist; K = KFGameType(Level.Game); if( K==None || KFM.bDecapitated || !KFM.bCannibal || (!Level.Game.bGameEnded && Pawn.Health>=(Pawn.Default.Health*1.5)) ) Return False; for( i=0; i 0 && !Human.bPendingDelete) { /* New Monster Threat logic ------------------------------------------ currently only used in story mode missions - Gives the pawn a chance to adjust the amount of interest ZEDs take in attacking him. */ if(bUseThreatAssessment) { ThreatLevel = Human.AssessThreatTo(self,true); if(ThreatLevel <= 0) { continue; } else if(ThreatLevel > HighestThreatLevel) { HighestThreatLevel = ThreatLevel; BestEnemy = Human; bSeeBest = CanSee(Human); } } else // Dont use threat assessment. Fall back on the old Distance based stuff. { NewDist = VSizeSquared(Human.Location - Pawn.Location); if( BestEnemy == none || (NewDist < BestDist) ) { BestEnemy = Human; BestDist = NewDist; } } } } if ( BestEnemy == Enemy ) return false; if ( BestEnemy != None ) { ChangeEnemy(BestEnemy,bSeeBest); return true; } return false; } // TODO - Is this the best way to deal with enemies we can't see? function EnemyNotVisible(); function Timer(); function DoCharge() { if(pawn != none) { if ( Enemy.PhysicsVolume.bWaterVolume ) { if ( !Pawn.bCanSwim ) { DoTacticalMove(); return; } } else { if (KFM.MeleeRange != KFM.default.MeleeRange) KFM.MeleeRange = KFM.default.MeleeRange; GotoState('ZombieCharge'); } } } function DoTacticalMove(); function bool FindBestPathToward(Actor A, bool bCheckedReach, bool bAllowDetour) { local vector Dummy; RouteCache[1] = None; if( A==None ) Return False; // Shouldn't get to this, but just in case. if ( !bCheckedReach && ActorReachable(A) ) MoveTarget = A; else { // Sometimes they may attempt to find another way around if this way leads to i.e. a welded door. if( ExtraCostWay!=None ) ExtraCostWay.ExtraCost+=200; if( BlockedWay!=None ) { BlockedWay.ExtraCost+=10000; MoveTarget = FindPathToward(A); BlockedWay.ExtraCost-=10000; } else MoveTarget = FindPathToward(A); if( ExtraCostWay!=None ) ExtraCostWay.ExtraCost-=200; if( MoveTarget!=None ) { if( LastResult==MoveTarget && NavigationPoint(MoveTarget)!=None && NumAttmpts>3 && FRand()<0.6 ) { BlockedWay = NavigationPoint(MoveTarget); LastResult = None; NumAttmpts = 0; return FindBestPathToward(A,True,bAllowDetour); } else if( LastResult==MoveTarget ) NumAttmpts++; else NumAttmpts = 0; LastResult = MoveTarget; if( NavigationPoint(MoveTarget)!=None && KFMonster(Trace(Dummy,Dummy,MoveTarget.Location,Pawn.Location,True))!=None ) { ExtraCostWay = NavigationPoint(MoveTarget); ExtraCostWay.ExtraCost+=200; MoveTarget = FindPathToward(A); // Might consider taking another path if zombie is blocking this one. ExtraCostWay.ExtraCost-=200; } } } if ( MoveTarget!=None ) { if( RouteCache[1]!=None && ActorReachable(RouteCache[1]) ) MoveTarget = RouteCache[1]; if( KFM.bCanDistanceAttackDoors ) { A = Trace(Dummy,Dummy,MoveTarget.Location,Pawn.Location,False); if( KFDoorMover(A)!=None && KFDoorMover(A).bSealed ) { TargetDoor = KFDoorMover(A); bAboutToGetDoor = True; } } return true; } else { if ( (A == Enemy) && (A != None) ) { FailedHuntTime = Level.TimeSeconds; FailedHuntEnemy = Enemy; } if ( bSoaking && (Physics != PHYS_Falling) ) SoakStop("COULDN'T FIND BEST PATH TO "$A); } return false; } /* Threat Assessment Logic - Returns true if there's a player nearby who has a higher threat level than the current enemy. Probably means this ZED should break off and attack it */ function bool EnemyThreatChanged() { local Controller PC; local KFHumanPawn C; local float NewThreat,CurrentThreat; if(!bUseThreatAssessment) { return false; } if(KFHumanPawn(Enemy) != none) { CurrentThreat = KFHumanPawn(Enemy).AssessThreatTo(self); } /* Current Enemy is of no threat suddenly */ if(CurrentThreat <= 0) { return true; } /* There's another guy nearby with a greater threat than me */ for ( PC=Level.ControllerList; PC!=None; PC=PC.NextController ) { C = KFHumanPawn(PC.Pawn); if(C == none || C == Enemy) { continue; } NewThreat = C.AssessThreatTo(self); if(NewThreat > CurrentThreat) { return true; } } return false; } 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 || EnemyThreatChanged()) 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() ) { GoalString = "Hunt"; GotoState('ZombieHunt'); return; } // see enemy - decide whether to charge it or strafe around/stand and fire Target = Enemy; GoalString = "Charge"; PathFindState = 2; DoCharge(); } state ZombieRoam extends Roaming { function Timer() { if(Pawn.Velocity == vect(0,0,0)) GotoState('ZombieRestFormation','Moving'); } } state ZombieHunt extends Hunting { function BeginState() { local float ZDif; if( Pawn.CollisionRadius>27 || Pawn.CollisionHeight>46 ) { ZDif = Pawn.CollisionHeight-44; Pawn.SetCollisionSize(24,44); Pawn.MoveSmooth(vect(0,0,-1)*ZDif); } } function EndState() { local float ZDif; if( Pawn != none && 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); } } function Timer() { if(Pawn.Velocity == vect(0,0,0)) GotoState('ZombieRestFormation','Moving'); SetCombatTimer(); StopFiring(); } function PickDestination() { local vector nextSpot, ViewSpot,Dir; local float posZ; local bool bCanSeeLastSeen; if( FindFreshBody() ) Return; if ( (Enemy != None) && !KFM.bCannibal && (Enemy.Health <= 0) ) { Enemy = None; WhatToDoNext(23); return; } if( PathFindState==0 ) { InitialPathGoal = FindRandomDest(); PathFindState = 1; } if( PathFindState==1 ) { if( InitialPathGoal==None ) PathFindState = 2; else if( ActorReachable(InitialPathGoal) ) { MoveTarget = InitialPathGoal; PathFindState = 2; Return; } else if( FindBestPathToward(InitialPathGoal, true,true) ) Return; else PathFindState = 2; } if ( Pawn.JumpZ > 0 ) Pawn.bCanJump = true; if( KFM.Intelligence==BRAINS_Retarded && FRand()<0.25 ) { Destination = Pawn.Location+VRand()*200; Return; } if ( ActorReachable(Enemy) ) { Destination = Enemy.Location; if( KFM.Intelligence==BRAINS_Retarded && FRand()<0.5 ) { Destination+=VRand()*50; Return; } MoveTarget = None; return; } ViewSpot = Pawn.Location + Pawn.BaseEyeHeight * vect(0,0,1); bCanSeeLastSeen = bEnemyInfoValid && FastTrace(LastSeenPos, ViewSpot); if ( FindBestPathToward(Enemy, true,true) ) return; if ( bSoaking && (Physics != PHYS_Falling) ) SoakStop("COULDN'T FIND PATH TO ENEMY "$Enemy); MoveTarget = None; if ( !bEnemyInfoValid ) { Enemy = None; GotoState('StakeOut'); return; } Destination = LastSeeingPos; bEnemyInfoValid = false; if ( FastTrace(Enemy.Location, ViewSpot) && VSize(Pawn.Location - Destination) > Pawn.CollisionRadius ) { SeePlayer(Enemy); return; } posZ = LastSeenPos.Z + Pawn.CollisionHeight - Enemy.CollisionHeight; nextSpot = LastSeenPos - Normal(Enemy.Velocity) * Pawn.CollisionRadius; nextSpot.Z = posZ; if ( FastTrace(nextSpot, ViewSpot) ) Destination = nextSpot; else if ( bCanSeeLastSeen ) { Dir = Pawn.Location - LastSeenPos; Dir.Z = 0; if ( VSize(Dir) < Pawn.CollisionRadius ) { Destination = Pawn.Location+VRand()*500; return; } Destination = LastSeenPos; } else { Destination = LastSeenPos; if ( !FastTrace(LastSeenPos, ViewSpot) ) { // check if could adjust and see it if ( PickWallAdjust(Normal(LastSeenPos - ViewSpot)) || FindViewSpot() ) { if ( Pawn.Physics == PHYS_Falling ) SetFall(); else GotoState('Hunting', 'AdjustFromWall'); } else { Destination = Pawn.Location+VRand()*500; return; } } } } } function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class damageType, vector Momentum) { local KFMonster Monster; // Get ticked and attack nearby bloats because you think they puked on you! if(class(DamageType)!=none && Damage > 0) { foreach VisibleCollidingActors( class 'KFMonster', Monster, 1000, Pawn.Location ) { if( Monster.IsA('ZombieBloatBase') && Monster != Pawn && KFHumanPawn(instigatedBy) != none ) { if( KFMonster(Pawn) != none ) { SetEnemy(Monster,true,KFMonster(Pawn).HumanBileAggroChance); } return; } } } Super.NotifyTakeHit(InstigatedBy,HitLocation,Damage,DamageType,Momentum); } state ZombieCharge extends Charging { function SeePlayer( Pawn Seen ) { if( KFM.Intelligence==BRAINS_Human ) SetEnemy(Seen); } function DamageAttitudeTo(Pawn Other, float Damage) { if( KFM.Intelligence>=BRAINS_Mammal && Other!=None && SetEnemy(Other) ) SetEnemy(Other); } function HearNoise(float Loudness, Actor NoiseMaker) { if( KFM.Intelligence==BRAINS_Human && NoiseMaker.Instigator!=None && FastTrace(NoiseMaker.Location,Pawn.Location) ) SetEnemy(NoiseMaker.Instigator); } 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 NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class damageType, vector Momentum) { local KFMonster Monster; // Get ticked and attack nearby bloats because you think they puked on you! if(class(DamageType)!=none && Damage > 0) { foreach VisibleCollidingActors( class 'KFMonster', Monster, 1000, Pawn.Location ) { if( Monster.IsA('ZombieBloatBase') && Monster != Pawn && KFHumanPawn(instigatedBy) != none ) { if( KFMonster(Pawn) != none ) { SetEnemy(Monster,true,KFMonster(Pawn).HumanBileAggroChance); } return; } } } Super.NotifyTakeHit(InstigatedBy,HitLocation, Damage,DamageType,Momentum); } Begin: if (Pawn.Physics == PHYS_Falling) { Focus = Enemy; Destination = Enemy.Location; WaitForLanding(); } if ( Enemy == None ) WhatToDoNext(16); WaitForAnim: While( KFM.bShotAnim ) Sleep(0.35); if ( !FindBestPathToward(Enemy, false,true) ) GotoState('TacticalMove'); Moving: if( KFM.Intelligence==BRAINS_Retarded ) { if( FRand()<0.3 ) MoveTo(Pawn.Location+VRand()*200,None); else if( MoveTarget==Enemy && FRand()<0.5 ) MoveTo(MoveTarget.Location+VRand()*50,None); else MoveToward(MoveTarget,FaceActor(1),,ShouldStrafeTo(MoveTarget)); } else MoveToward(MoveTarget,FaceActor(1),,ShouldStrafeTo(MoveTarget)); WhatToDoNext(17); if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); } function CheckIfShouldCrouch(vector StartPosition, vector TargetPosition, float probability); function InitializeSkill(float InSkill); function ResetSkill(); // Randomize their speeds a bit. function SetMaxDesiredSpeed() { Pawn.MaxDesiredSpeed = 0.9+FRand()*0.2; } function SetPeripheralVision(); // TODO: zombies commit suicide. Is this right that they do so? function bool FindRoamDest() { local actor BestPath; if ( Pawn.FindAnchorFailedTime == Level.TimeSeconds ) { // couldn't find an anchor. GoalString = "No anchor "$Level.TimeSeconds; if ( Pawn.LastValidAnchorTime > 5 ) { if ( bSoaking ) SoakStop("NO PATH AVAILABLE!!!"); else { if ( NumRandomJumps > 5 ) { Pawn.Health = 0; Pawn.Died( self, class'Suicided', Pawn.Location ); return true; } else { // jump NumRandomJumps++; if ( Physics != PHYS_Falling ) { Pawn.SetPhysics(PHYS_Falling); // Pawn.Velocity = 0.5 * Pawn.GroundSpeed * VRand(); Pawn.Velocity.Z = Pawn.JumpZ; } } } } //log(self$" Find Anchor failed!"); return false; } NumRandomJumps = 0; GoalString = "Find roam dest "$Level.TimeSeconds; // find random NavigationPoint to roam to if ( (RouteGoal == None) || (Pawn.Anchor == RouteGoal) || Pawn.ReachedDestination(RouteGoal) ) { RouteGoal = FindRandomDest(); BestPath = RouteCache[0]; if ( RouteGoal == None ) { if ( bSoaking && (Physics != PHYS_Falling) ) SoakStop("COULDN'T FIND ROAM DESTINATION"); return false; } } if ( BestPath == None ) BestPath = FindPathToward(RouteGoal,false); if ( BestPath != None ) { MoveTarget = BestPath; GotoState('ZombieRoam'); return true; } if ( bSoaking && (Physics != PHYS_Falling) ) SoakStop("COULDN'T FIND ROAM PATH TO "$RouteGoal); RouteGoal = None; return false; } function DirectedWander(vector WanderDir) { GoalString = "DIRECTED WANDER "$GoalString; if ( TestDirection(WanderDir,Destination) ) GotoState('ZombieRestFormation', 'Moving'); else GotoState('ZombieRestFormation', 'Begin'); } function WanderOrCamp(bool bMayCrouch) { if( KFM.bNoAutoHuntEnemies ) GoToState('WaitToStart'); else FindRoamDest(); } state ZombieRestFormation extends RestFormation { ignores EnemyNotVisible; function CancelCampFor(Controller C) { DirectedWander(Normal(Pawn.Location - C.Pawn.Location)); } function bool Formation() { return true; } function Timer() { if(Pawn.Velocity == vect(0,0,0)) Gotostate('ZombieRestFormation','Moving'); SetCombatTimer(); disable('NotifyBump'); } function PickDestination() { local vector nextSpot, ViewSpot,Dir; local float posZ; local bool bCanSeeLastSeen; if ( TestDirection(VRand(),Destination) ) { // If we're not a cannibal. dont munch if ( (Enemy != None) && !KFM.bCannibal && (Enemy.Health <= 0) ) { Enemy = None; WhatToDoNext(23); return; } if ( Pawn.JumpZ > 0 ) Pawn.bCanJump = true; if ( Enemy!=None && ActorReachable(Enemy) ) { Destination = Enemy.Location; MoveTarget = None; return; } ViewSpot = Pawn.Location + Pawn.BaseEyeHeight * vect(0,0,1); bCanSeeLastSeen = bEnemyInfoValid && FastTrace(LastSeenPos, ViewSpot); if ( Enemy!=None && FindBestPathToward(Enemy, true,true) ) return; if ( bSoaking && (Physics != PHYS_Falling) ) SoakStop("COULDN'T FIND PATH TO ENEMY "$Enemy); MoveTarget = None; if ( Enemy==None || !bEnemyInfoValid ) { Enemy = None; WhatToDoNext(26); return; } Destination = LastSeeingPos; bEnemyInfoValid = false; if ( FastTrace(Enemy.Location, ViewSpot) && VSize(Pawn.Location - Destination) > Pawn.CollisionRadius ) { SeePlayer(Enemy); return; } posZ = LastSeenPos.Z + Pawn.CollisionHeight - Enemy.CollisionHeight; nextSpot = LastSeenPos - Normal(Enemy.Velocity) * Pawn.CollisionRadius; nextSpot.Z = posZ; if ( FastTrace(nextSpot, ViewSpot) ) Destination = nextSpot; else if ( bCanSeeLastSeen ) { Dir = Pawn.Location - LastSeenPos; Dir.Z = 0; if ( VSize(Dir) < Pawn.CollisionRadius ) { GoalString = "Stakeout 3 from hunt"; GotoState('StakeOut'); return; } Destination = LastSeenPos; } else { Destination = LastSeenPos; if ( !FastTrace(LastSeenPos, ViewSpot) ) { // check if could adjust and see it if ( PickWallAdjust(Normal(LastSeenPos - ViewSpot)) || FindViewSpot() ) { if ( Pawn.Physics == PHYS_Falling ) SetFall(); else GotoState('Hunting', 'AdjustFromWall'); } else { GoalString = "Stakeout 2 from hunt"; GotoState('StakeOut'); return; } } } } else TestDirection(VRand(),Destination); } function BeginState() { // Enemy = None; //Pawn.bAvoidLedges = true; //Pawn.bStopAtLedges = true; //Pawn.SetWalking(true); MinHitWall += 0.15; } function EndState() { //MonitoredPawn = None; MinHitWall -= 0.15; if ( Pawn != None ) { if (Pawn.JumpZ > 0) Pawn.bCanJump = true; } } function bool FindViewSpot() { local vector X,Y,Z; GetAxes(Rotation,X,Y,Z); // try left and right if ( Enemy!=None && FastTrace(Enemy.Location, Pawn.Location + 2 * Y * Pawn.CollisionRadius) ) { Destination = Pawn.Location + 2.5 * Y * Pawn.CollisionRadius; return true; } if ( Enemy!=None && FastTrace(Enemy.Location, Pawn.Location - 2 * Y * Pawn.CollisionRadius) ) { Destination = Pawn.Location - 2.5 * Y * Pawn.CollisionRadius; return true; } if ( FRand() < 0.5 ) Destination = Pawn.Location - 2.5 * Y * Pawn.CollisionRadius; else Destination = Pawn.Location - 2.5 * Y * Pawn.CollisionRadius; return true; } event MonitoredPawnAlert() { WhatToDoNext(6); } Begin: WaitForLanding(); Camping: //Pawn.Acceleration = vect(0,0,0); Focus = None; FocalPoint = VRand(); NearWall(MINVIEWDIST); FinishRotation(); Sleep(3 + FRand()); Moving: // WaitForLanding(); PickDestination(); WaitForAnim: if ( KFM.bShotAnim ) { Sleep(0.5); Goto('WaitForAnim'); } MoveTo(Destination,,true); if ( Pawn.bCanFly && (Physics == PHYS_Walking) ) SetPhysics(PHYS_Flying); WhatToDoNext(8); Goto('Begin'); } state DoorBashing extends MoveToGoalNoEnemy { ignores EnemyNotVisible,SeeMonster; function Timer() { Disable('NotifyBump'); } function AttackDoor() { Target = TargetDoor; KFM.Acceleration = vect(0,0,0); KFM.DoorAttack(Target); } function SeePlayer( Pawn Seen ) { if( KFM.Intelligence==BRAINS_Human && ActorReachable(Seen) && SetEnemy(Seen) ) WhatToDoNext(23); } function DamageAttitudeTo(Pawn Other, float Damage) { if( KFM.Intelligence>=BRAINS_Mammal && Other!=None && ActorReachable(Other) && SetEnemy(Other) ) WhatToDoNext(32); } function HearNoise(float Loudness, Actor NoiseMaker) { if( KFM.Intelligence==BRAINS_Human && NoiseMaker!=None && NoiseMaker.Instigator!=None && ActorReachable(NoiseMaker.Instigator) && SetEnemy(NoiseMaker.Instigator) ) WhatToDoNext(32); } function Tick( float Delta ) { Global.Tick(Delta); // Don't move while we are bashing a door! MoveTarget = None; MoveTimer = -1; Pawn.Acceleration = vect(0,0,0); Pawn.GroundSpeed = 1; Pawn.AccelRate = 0; } function EndState() { if( Pawn!=None ) { Pawn.AccelRate = Pawn.Default.AccelRate; Pawn.GroundSpeed = Pawn.Default.GroundSpeed; } } Begin: WaitForLanding(); KeepMoving: //MoveTarget = TargetDoor; //if(MoveTarget!=none) // MoveToward(MoveTarget,FaceActor(1),,false ); //,GetDesiredOffset(),ShouldStrafeTo(MoveTarget)); While( KFM.bShotAnim ) Sleep(0.25); While( TargetDoor!=none && !TargetDoor.bHidden && TargetDoor.bSealed && !TargetDoor.bZombiesIgnore ) { AttackDoor(); While( KFM.bShotAnim ) Sleep(0.25); Sleep(0.1); if( KFM.Intelligence>=BRAINS_Mammal && Enemy!=None && ActorReachable(Enemy) ) WhatToDoNext(14); } WhatToDoNext(152); Moving: MoveToward(TargetDoor); WhatToDoNext(17); if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); } state CorpseFeeding { ignores EnemyNotVisible,SeePlayer,HearNoise,NotifyBump; // Don't do this in this state function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} function Timer() { Target = TargetCorpse; if(Target == none) WhatToDoNext(38); } function AttackCorpse() { Target = TargetCorpse; KFM.CorpseAttack(Target); } Begin: WaitForLanding(); While( TargetCorpse!=None && !ActorReachable(TargetCorpse) ) { if( !FindBestPathToward(TargetCorpse,True,False) ) WhatToDoNext(33); MoveToward(MoveTarget,MoveTarget); } if( TargetCorpse==None ) WhatToDoNext(32); MoveTo(TargetCorpse.Location+Normal(Pawn.Location-TargetCorpse.Location)*(20+FRand()*20),TargetCorpse); if( TargetCorpse==None ) WhatToDoNext(31); Focus = TargetCorpse; While( TargetCorpse!=None && (Pawn.Health<(Pawn.Default.Health*1.5) || Level.Game.bGameEnded) ) { AttackCorpse(); While( KFM.bShotAnim ) Sleep(0.1); if( Enemy!=None && VSize(Enemy.Location-Pawn.Location)<500 && CanSee(Enemy) ) WhatToDoNext(37); // Can't look, eating. } WhatToDoNext(30); } function WaitForMover(Mover M) { if ( (Enemy != None) && (Level.TimeSeconds - LastSeenTime < M.MoveTime) ) Focus = Enemy; PendingMover = M; bPreparingMove = true; Pawn.Acceleration = vect(0,0,0); StopStartTime = Level.TimeSeconds; } function HearNoise(float Loudness, Actor NoiseMaker) { if( NoiseMaker!=none && FastTrace(NoiseMaker.Location,Pawn.Location) ) { if ( ((ChooseAttackCounter < 2) || (ChooseAttackTime != Level.TimeSeconds)) && SetEnemy(NoiseMaker.instigator) ) WhatToDoNext(2); } } state Kicking { ignores EnemyNotVisible; Begin: WaitForAnim: if ( KFM.bShotAnim ) { if(KickTarget!=none) { //MoveToward(KickTarget,FaceActor(1),,false ); //,GetDesiredOffset(),ShouldStrafeTo(MoveTarget)); Sleep(0.1); Goto('WaitForAnim'); } } WhatToDoNext(152); if ( bSoaking ) SoakStop("STUCK IN KICKING!!!"); } state KnockDown { ignores EnemyNotVisible,Startle; // Don't do this in this state function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} Begin: Pawn.ShouldCrouch(True); WaitForAnim: if ( KFM.bShotAnim ) { Sleep(0.1); Goto('WaitForAnim'); } WhatToDoNext(152); if ( bSoaking ) SoakStop("STUCK IN STAGGERED!!!"); End: Pawn.ShouldCrouch(False); } function ExecuteWhatToDoNext() { bHasFired = false; GoalString = "WhatToDoNext at "$Level.TimeSeconds; if ( Pawn == None ) { warn(GetHumanReadableName()$" WhatToDoNext with no pawn"); return; } if( KFM.bStartUpDisabled ) { KFM.bStartUpDisabled = False; GoToState('WaitToStart'); Return; } if( KFM.bShotAnim ) { GoToState('WaitForAnim'); Return; } if( Level.Game.bGameEnded && FindFreshBody() ) Return; if (Pawn.Physics == PHYS_None) Pawn.SetMovementPhysics(); if ( (Pawn.Physics == PHYS_Falling) && DoWaitForLanding() ) return; if ( (Enemy != None) && ((Enemy.Health <= 0) || (Enemy.Controller == None)) ) Enemy = None; if ( (Enemy == None) || !EnemyVisible() ) FindNewEnemy(); if ( Enemy != None ) ChooseAttackMode(); else { GoalString = "WhatToDoNext Wander or Camp at "$Level.TimeSeconds; WanderOrCamp(true); } } state StakeOut { ignores EnemyNotVisible; /* DoStakeOut() called by ChooseAttackMode - if called in this state, means stake out twice in a row */ function DoStakeOut() { SetFocus(); if ( Enemy!=None && ((FRand() < 0.3) || !FastTrace(FocalPoint + vect(0,0,0.9) * Enemy.CollisionHeight, Pawn.Location + vect(0,0,0.8) * Pawn.CollisionHeight)) ) FindNewStakeOutDir(); GotoState('StakeOut','Begin'); } function rotator AdjustAim(FireProperties FiredAmmunition, vector projStart, int aimerror) { local vector FireSpot; local actor HitActor; local vector HitLocation, HitNormal; if( Enemy==None ) Return Pawn.Rotation; FireSpot = FocalPoint; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if( HitActor != None ) { FireSpot += 2 * Enemy.CollisionHeight * HitNormal; if ( !FastTrace(FireSpot, ProjStart) ) { FireSpot = FocalPoint; StopFiring(); } } SetRotation(Rotator(FireSpot - ProjStart)); return Rotation; } function BeginState() { StopStartTime = Level.TimeSeconds; Pawn.Acceleration = vect(0,0,0); Pawn.bCanJump = false; SetFocus(); if ( Enemy!=None && (!bEnemyInfoValid || !ClearShot(FocalPoint,false) || ((Level.TimeSeconds - LastSeenTime > 6) && (FRand() < 0.5))) ) FindNewStakeOutDir(); } function SetFocus() { if ( bEnemyInfoValid ) FocalPoint = LastSeenPos; else if( Enemy!=None ) FocalPoint = Enemy.Location; } Begin: Pawn.Acceleration = vect(0,0,0); Focus = None; CheckIfShouldCrouch(Pawn.Location,FocalPoint, 1); FinishRotation(); if ( Enemy!=None && KFM.HasRangedAttack() && (FRand() < 0.5) && (VSize(Enemy.Location - FocalPoint) < 150) && (Level.TimeSeconds - LastSeenTime < 4) && ClearShot(FocalPoint,true) ) FireWeaponAt(Enemy); else StopFiring(); Sleep(0.4 + FRand()*0.4); // check if uncrouching would help if ( Pawn.bIsCrouched && !FastTrace(FocalPoint, Pawn.Location + Pawn.EyeHeight * vect(0,0,1)) && FastTrace(FocalPoint, Pawn.Location + (Pawn.Default.EyeHeight + Pawn.Default.CollisionHeight - Pawn.CollisionHeight) * vect(0,0,1)) ) { Pawn.bWantsToCrouch = false; Sleep(0.4 + FRand()*0.4); } MoveTo(Pawn.Location+VRand()*80); // Try moving somewhere WhatToDoNext(31); if ( bSoaking ) SoakStop("STUCK IN STAKEOUT!"); } state WaitForAnim { Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump,Startle; // Don't do this in this state function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} event AnimEnd(int Channel) { /*local name Sequence; local float Frame, Rate; Pawn.GetAnimParams( KFMonster(Pawn).ExpectingChannel, Sequence, Frame, Rate ); log(GetStateName()$" AnimEnd for Exp Chan "$KFMonster(Pawn).ExpectingChannel$" = "$Sequence); Pawn.GetAnimParams( 0, Sequence, Frame, Rate ); log(GetStateName()$" AnimEnd for Chan 0 = "$Sequence); Pawn.GetAnimParams( 1, Sequence, Frame, Rate ); log(GetStateName()$" AnimEnd for Chan 1 = "$Sequence); log(GetStateName()$" AnimEnd bShotAnim = "$Monster(Pawn).bShotAnim);*/ Pawn.AnimEnd(Channel); if ( !Monster(Pawn).bShotAnim ) WhatToDoNext(99); } function BeginState() { bUseFreezeHack = False; } function Tick( float Delta ) { Global.Tick(Delta); if( bUseFreezeHack ) { MoveTarget = None; MoveTimer = -1; Pawn.Acceleration = vect(0,0,0); Pawn.GroundSpeed = 1; Pawn.AccelRate = 0; } } function EndState() { if( Pawn!=None ) { Pawn.AccelRate = Pawn.Default.AccelRate; Pawn.GroundSpeed = Pawn.Default.GroundSpeed; } bUseFreezeHack = False; } Begin: While( KFM.bShotAnim ) { Sleep(0.15); } WhatToDoNext(99); } function bool SetEnemy( Pawn NewEnemy, optional bool bHateMonster, optional float MonsterHateChanceOverride ) { /* This enemy is of absolutely no threat currently, ignore it */ if(bUseThreatAssessment && KFHumanpawn(NewEnemy) != none && KFHumanpawn(NewEnemy).AssessThreatTo(self) <= 0) { return false; } if( !bHateMonster && KFHumanPawnEnemy(NewEnemy)!=None && KFHumanPawnEnemy(NewEnemy).AttitudeToSpecimen<=ATTITUDE_Ignore ) Return False; // In other words, dont attack human pawns as long as they dont damage me or hates me. if( KFM.Intelligence>=BRAINS_Mammal && Enemy!=None && NewEnemy!=None && NewEnemy!=Enemy && NewEnemy.Controller!=None && NewEnemy.Controller.bIsPlayer ) { if( LineOfSightTo(Enemy) && VSize(Enemy.Location-Pawn.Location)0 && VSize(NewEnemy.Location-Pawn.Location)<1500 && LineOfSightTo(NewEnemy) ) // Get pissed at this fucker.. { ChangeEnemy(NewEnemy,CanSee(NewEnemy)); return true; } if( Super.SetEnemy(NewEnemy,bHateMonster,MonsterHateChanceOverride) ) { if( !bTriggeredFirstEvent ) { bTriggeredFirstEvent = True; if( KFM.FirstSeePlayerEvent!='' ) TriggerEvent(KFM.FirstSeePlayerEvent,Pawn,Pawn); } Return True; } Return False; } function Celebrate(); State WaitToStart { Ignores Tick,Timer,FindNewEnemy,NotifyLanded,DoWaitForLanding; function Trigger( actor Other, pawn EventInstigator ) { SetEnemy(EventInstigator,True); WhatToDoNext(56); } function bool SetEnemy( Pawn NewEnemy, optional bool bHateMonster, optional float MonsterHateChanceOverride ) { if( Level.TimeSeconds<1 ) Return False; Return Global.SetEnemy(NewEnemy,bHateMonster,MonsterHateChanceOverride); } function EndState() { if( Pawn.Health>0 ) { if( !bTriggeredFirstEvent ) { bTriggeredFirstEvent = True; if( KFM.FirstSeePlayerEvent!='' ) TriggerEvent(KFM.FirstSeePlayerEvent,Pawn,Pawn); } Pawn.AmbientSound = Pawn.Default.AmbientSound; } } Begin: Pawn.AmbientSound = None; Enemy = None; Focus = None; FocalPoint = Pawn.Location+vector(Pawn.Rotation)*5000; Pawn.Acceleration = vect(0,0,0); } function Trigger( actor Other, pawn EventInstigator ) { if( SetEnemy(EventInstigator,True) ) WhatToDoNext(54); } simulated event Destroyed() { local int i; StartleActor = none; for ( i = 0; i < KillAssistants.Length; i++ ) { KillAssistants[i].PC = none; } KillAssistants.Remove(0, KillAssistants.Length); Super.Destroyed(); } simulated function AddKillAssistant(Controller PC, float Damage) { local bool bIsalreadyAssistant; local int i; for ( i = 0; i < KillAssistants.Length; i++ ) { if ( PC == KillAssistants[i].PC ) { bIsalreadyAssistant = true; KillAssistants[i].Damage += Damage; break; } } if ( !bIsalreadyAssistant ) { KillAssistants.Insert(0, 1); KillAssistants[0].PC = PC; KillAssistants[0].Damage = Damage; } } defaultproperties { StrafingAbility=-1.000000 CombatStyle=1.000000 ReactionTime=1.000000 FarMult=3.0 NearMult=1.5 }