class KFFriendlyAI extends MonsterController; var KFHumanPawnEnemy KFHM; var Actor MoveGoal; var bool bStoppedFiring,bFireSuccess; function Restart() { Super.Restart(); KFHM = KFHumanPawnEnemy(Pawn); Accuracy = KFHM.Accuracy; ReactionTime = KFHM.ReactionTime; ReSetSkill(); } function Possess(Pawn aPawn) { Super(AIController).Possess(aPawn); InitializeSkill(DeathMatch(Level.Game).AdjustedDifficulty); GoToState('InitilizingMe'); Disable('Tick'); } State InitilizingMe { ignores SeePlayer, HearNoise, KilledBy, NotifyBump, HitWall, NotifyPhysicsVolumeChange, NotifyHeadVolumeChange, Falling, TakeDamage, ReceiveWarning; function SetupFlags() { Pawn.MaxFallSpeed = 1.1 * Pawn.default.MaxFallSpeed; // so bots will accept a little falling damage for shorter routes Pawn.SetMovementPhysics(); if( Pawn.Physics==PHYS_Walking ) Pawn.SetPhysics(PHYS_Falling); bIsPlayer = True; // Fake player to make zombies attack me, and make me see zombies. KFHM.GiveDefaultWeapon(); enable('NotifyBump'); Switch( KFHM.SoldierOrders ) { Case ORDER_Guarding: GoToState('Waiting'); Break; Case ORDER_Wander: GoToState('Wandering'); Break; Case ORDER_Hunt: GoToState('ActiveHunting'); Break; Default: GoToState('Patroling'); Break; } } Begin: Sleep(0.001); SetupFlags(); } // added CanAttack() check to calm trigger-happy bots function bool FireWeaponAt(Actor A) { if ( A == None ) A = Enemy; if ( (A == None) || (Focus != A) ) return false; Target = A; if ( Pawn.Weapon != None ) { Pawn.Weapon.FillToInitialAmmo(); if( !Pawn.Weapon.CanAttack(A) ) return false; if ( Pawn.Weapon.HasAmmo() ) return WeaponFireAgain(Pawn.Weapon.RefireRate(),false); } else return WeaponFireAgain(Pawn.RefireRate(),false); return false; } function TimedFireWeaponAtEnemy() { if ( (Enemy == None) || FireWeaponAt(Enemy) ) SetCombatTimer(); else SetTimer(0.1, True); } function StopFiring() { if ( (Pawn != None) && Pawn.StopWeaponFiring() ) bStoppedFiring = true; bCanFire = false; bFire = 0; bAltFire = 0; } function WanderOrCamp(bool bMayCrouch) { Switch( KFHM.SoldierOrders ) { Case ORDER_Guarding: GoToState('Waiting'); Break; Case ORDER_Wander: GoToState('Wandering'); Break; Case ORDER_Hunt: GoToState('ActiveHunting'); Break; Default: GoToState('Patroling'); Break; } } state Wandering { ignores EnemyNotVisible,Timer; function CancelCampFor(Controller C) { DirectedWander(Normal(Pawn.Location - C.Pawn.Location)); } function bool Formation() { return true; } function HearNoise(float Loudness, Actor NoiseMaker) { if ( NoiseMaker.Instigator!=None && FastTrace(NoiseMaker.Location,Pawn.Location) && SetEnemy(NoiseMaker.Instigator,False) ) WhatToDoNext(3); } function PickDestination() { if ( TestDirection(VRand(),Destination) ) return; TestDirection(VRand(),Destination); } function BeginState() { Enemy = None; Pawn.bCanJump = false; Pawn.bAvoidLedges = true; Pawn.bStopAtLedges = true; Pawn.SetWalking(true); MinHitWall += 0.15; } function EndState() { MonitoredPawn = None; MinHitWall -= 0.15; if ( Pawn != None ) { Pawn.bStopAtLedges = false; Pawn.bAvoidLedges = false; Pawn.SetWalking(false); if (Pawn.JumpZ > 0) Pawn.bCanJump = true; } } event MonitoredPawnAlert() { WhatToDoNext(6); } Begin: WaitForLanding(); Camping: Pawn.Acceleration = vect(0,0,0); Focus = None; FocalPoint = Pawn.Location+VRand()*500; NearWall(MINVIEWDIST); FinishRotation(); Sleep(3 + FRand()); Moving: WaitForLanding(); PickDestination(); MoveTo(Destination,,true); if ( Pawn.bCanFly && (Physics == PHYS_Walking) ) SetPhysics(PHYS_Flying); Goto('Begin'); } state Waiting extends Wandering { Begin: WaitForLanding(); Camping: Pawn.Acceleration = vect(0,0,0); Focus = None; FocalPoint = Pawn.Location+vector(Pawn.Rotation)*2500; NearWall(MINVIEWDIST); Stop; } state ActiveHunting { ignores EnemyNotVisible,Timer; function HearNoise(float Loudness, Actor NoiseMaker) { if ( NoiseMaker.Instigator!=None && FastTrace(NoiseMaker.Location,Pawn.Location) && SetEnemy(NoiseMaker.Instigator,False) ) WhatToDoNext(3); } function PickDestination() { if( MoveGoal==None ) MoveGoal = FindRandomDest(); if( MoveGoal!=None ) { if( ActorReachable(MoveGoal) ) { MoveTarget = MoveGoal; MoveGoal = None; Return; } if( FindBestPathToward(MoveGoal,True,True) ) Return; MoveGoal = None; } if ( TestDirection(VRand(),Destination) ) return; TestDirection(VRand(),Destination); } function BeginState() { Enemy = None; Pawn.bCanJump = True; Pawn.bAvoidLedges = False; Pawn.bStopAtLedges = False; Pawn.SetWalking(False); } Begin: WaitForLanding(); PickDestination(); if( MoveTarget!=None ) MoveToward(MoveTarget,,,ShouldStrafeTo(MoveTarget)); else MoveTo(Destination); if( FRand()<0.1 ) { Pawn.Acceleration = vect(0,0,0); Focus = None; FocalPoint = Pawn.Location+VRand()*200; Sleep(0.5+FRand()*1.5); } Goto'Begin'; } function EnemyChanged(bool bNewEnemyVisible) { bEnemyAcquired = false; SetEnemyInfo(bNewEnemyVisible); } function NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn) { if ( Enemy!=None && Enemy==KilledPawn ) { Enemy = None; WhatToDoNext(45); } } function bool ShouldStrafeTo(Actor WayPoint) { local NavigationPoint N; if ( Skill + StrafingAbility < 3 ) return false; if ( WayPoint == Enemy ) return ( Skill + StrafingAbility < 5 * FRand() - 1 ); else if ( Pickup(WayPoint) == None ) { N = NavigationPoint(WayPoint); if ( (N == None) || N.bNeverUseStrafing ) return false; if ( N.FearCost > 200 ) return true; if ( N.bAlwaysUseStrafing && (FRand() < 0.8) ) return true; } if ( Pawn(WayPoint) != None ) return ( Skill + StrafingAbility < 5 * FRand() - 1 ); if ( Skill + StrafingAbility < 7 * FRand() - 1 ) return false; if ( Enemy == None ) return ( FRand() < 0.4 ); if ( EnemyVisible() ) return ( FRand() < 0.85 ); return ( FRand() < 0.6 ); } /* CheckIfShouldCrouch() returns true if target position still can be shot from crouched position, or if couldn't hit it from standing position either */ function CheckIfShouldCrouch(vector StartPosition, vector TargetPosition, float probability) { local actor HitActor; local vector HitNormal,HitLocation, X,Y,Z, projstart; if ( !Pawn.bCanCrouch || (!Pawn.bIsCrouched && (FRand() > probability)) || (Skill < 3 * FRand()) || KFHumanPawnEnemy(Pawn).RecommendSplashDamage() ) { Pawn.bWantsToCrouch = false; return; } GetAxes(Rotation,X,Y,Z); projStart = KFHumanPawnEnemy(Pawn).GetFireStart(X,Y,Z); projStart = projStart + StartPosition - Pawn.Location; projStart.Z = projStart.Z - 1.8 * (Pawn.CollisionHeight - Pawn.CrouchHeight); HitActor = Trace(HitLocation, HitNormal, TargetPosition , projStart, false); if ( HitActor == None ) { Pawn.bWantsToCrouch = true; return; } projStart.Z = projStart.Z + 1.8 * (Pawn.Default.CollisionHeight - Pawn.CrouchHeight); HitActor = Trace(HitLocation, HitNormal, TargetPosition , projStart, false); if ( HitActor == None ) { Pawn.bWantsToCrouch = false; return; } Pawn.bWantsToCrouch = true; } //============================================================================= function WhatToDoNext(byte CallingByte) { if ( ChooseAttackTime == Level.TimeSeconds ) ChooseAttackCounter++; else { ChooseAttackTime = Level.TimeSeconds; ChooseAttackCounter = 0; } ChoosingAttackLevel++; ExecuteWhatToDoNext(); ChoosingAttackLevel--; } function bool WeaponFireAgain(float RefireRate, bool bFinishedFire) { if ( Target == None ) Target = Enemy; if ( Target != None ) { if ( !Pawn.IsFiring() ) { if ( (Pawn.Weapon != None && Pawn.Weapon.bMeleeWeapon) || (!NeedToTurn(Target.Location) && Pawn.CanAttack(Target)) ) { Focus = Target; bCanFire = true; bStoppedFiring = false; if (Pawn.Weapon != None) bFireSuccess = Pawn.Weapon.BotFire(bFinishedFire); else { Pawn.ChooseFireAt(Target); bFireSuccess = true; } return bFireSuccess; } else { bCanFire = false; } } else if ( bCanFire && ShouldFireAgain(RefireRate) ) { if ( (Target != None) && (Focus == Target) && !Target.bDeleteMe ) { bStoppedFiring = false; if (Pawn.Weapon != None) bFireSuccess = Pawn.Weapon.BotFire(bFinishedFire); else { Pawn.ChooseFireAt(Target); bFireSuccess = true; } return bFireSuccess; } } } StopFiring(); return false; } function bool ShouldFireAgain(float RefireRate) { if ( FRand() < RefireRate ) return true; if ( Pawn.FireOnRelease() && (Pawn.Weapon == None || !Pawn.Weapon.bMeleeWeapon) ) return false; if ( Pawn(Target) != None ) return ( (Pawn.bStationary || Pawn(Target).bStationary) && (Pawn(Target).Health > 0) ); return false; } function FightEnemy(bool bCanCharge) { local vector X,Y,Z; local float enemyDist; local float AdjustedCombatStyle, Aggression; local bool bFarAway, bOldForcedCharge; if ( (Enemy == None) || (Pawn == None) ) { WhatToDoNext(45); Return; } if ( (Enemy == FailedHuntEnemy) && (Level.TimeSeconds == FailedHuntTime) ) { if ( Enemy == FailedHuntEnemy ) { GoalString = "FAILED HUNT - HANG OUT"; if ( EnemyVisible() ) bCanCharge = false; } } if( !EnemyVisible() ) { if( KFHM.bStationaryCombat ) { Enemy = None; WhatToDoNext(44); } else GoToState('Hunting'); Return; } else if( KFHM.bStationaryCombat ) { GoalString = "Ranged Attack"; DoRangedAttackOn(Enemy); return; } bOldForcedCharge = bMustCharge; bMustCharge = false; enemyDist = VSize(Pawn.Location - Enemy.Location); AdjustedCombatStyle = CombatStyle; Aggression = 1.5 * FRand() - 0.8 + 2 * AdjustedCombatStyle + FRand() * (Normal(Enemy.Velocity - Pawn.Velocity) Dot Normal(Enemy.Location - Pawn.Location)); if ( Enemy.Weapon != None ) Aggression += 2 * Enemy.Weapon.SuggestDefenseStyle(); if ( enemyDist > MAXSTAKEOUTDIST ) Aggression += 0.5; // Bloodlust *__* if ( (Enemy.Health / Enemy.HealthMax) <= 0.5 ) Aggression += 0.5; if ( (Pawn.Physics == PHYS_Walking) || (Pawn.Physics == PHYS_Falling) ) { if (Pawn.Location.Z > Enemy.Location.Z + TACTICALHEIGHTADVANTAGE) Aggression = FMax(0.0, Aggression - 1.0 + AdjustedCombatStyle); else if ( (Skill < 4) && (enemyDist > 0.65 * MAXSTAKEOUTDIST) ) { bFarAway = true; Aggression += 0.5; } else if (Pawn.Location.Z < Enemy.Location.Z - Pawn.CollisionHeight) // below enemy Aggression += CombatStyle; } // see enemy - decide whether to charge it or strafe around/stand and fire Target = Enemy; if( KFHM.PreferMelee() || (bCanCharge && bOldForcedCharge) ) { GoalString = "Charge"; DoCharge(); return; } if ( bCanCharge && bFarAway && (Aggression > 1) && (FRand() < 0.5) ) { GoalString = "Charge closer"; DoCharge(); return; } if ( !KFHM.PreferMelee() && (FRand() > 0.17 * (skill - 1)) && !DefendMelee(enemyDist) ) { GoalString = "Ranged Attack"; DoRangedAttackOn(Enemy); return; } if ( bCanCharge ) { if ( Aggression > 1 ) { GoalString = "Charge 2"; DoCharge(); return; } } if ( !Pawn.bCanStrafe ) { GoalString = "Ranged Attack"; DoRangedAttackOn(Enemy); return; } GoalString = "Do tactical move"; if ( !KFHM.RecommendSplashDamage() && KFHM.bCanDodgeDoubleJump && (FRand() < 0.7) && (FRand()*Skill > 3) ) { GetAxes(Pawn.Rotation,X,Y,Z); GoalString = "Try to Duck "; if ( FRand() < 0.5 ) { Y *= -1; TryToDuck(Y, true); } else TryToDuck(Y, false); } DoTacticalMove(); } function bool FindNewEnemy() { return false; } function DoCharge() { if ( Enemy.PhysicsVolume.bWaterVolume ) { if ( !Pawn.bCanSwim ) { DoTacticalMove(); return; } } else if ( !Pawn.bCanFly && !Pawn.bCanWalk ) { DoTacticalMove(); return; } GotoState('Charging'); } function SeeMonster( Pawn Seen ) { SeePlayer(Seen); } state Charging { ignores SeePlayer, HearNoise; /* MayFall() called by engine physics if walking and bCanJump, and is about to go off a ledge. Pawn has opportunity (by setting bCanJump to false) to avoid fall */ function MayFall() { if ( MoveTarget != Enemy ) return; Pawn.bCanJump = ActorReachable(Enemy); if ( !Pawn.bCanJump ) MoveTimer = -1.0; } function bool TryToDuck(vector duckDir, bool bReversed) { if ( FRand() < 0.6 ) return Global.TryToDuck(duckDir, bReversed); if ( MoveTarget == Enemy ) return TryStrafe(duckDir); } function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) { local vector sideDir; sideDir = Normal( Normal(Enemy.Location - Pawn.Location) Cross vect(0,0,1) ); if ( (Pawn.Velocity Dot sidedir) > 0 ) sidedir *= -1; return TryStrafe(sideDir); } function bool TryStrafe(vector sideDir) { local vector extent, HitLocation, HitNormal; local actor HitActor; Extent = Pawn.GetCollisionExtent(); HitActor = Trace(HitLocation, HitNormal, Pawn.Location + MINSTRAFEDIST * sideDir, Pawn.Location, false, Extent); if (HitActor != None) { sideDir *= -1; HitActor = Trace(HitLocation, HitNormal, Pawn.Location + MINSTRAFEDIST * sideDir, Pawn.Location, false, Extent); } if (HitActor != None) return false; if ( Pawn.Physics == PHYS_Walking ) { HitActor = Trace(HitLocation, HitNormal, Pawn.Location + MINSTRAFEDIST * sideDir - MAXSTEPHEIGHT * vect(0,0,1), Pawn.Location + MINSTRAFEDIST * sideDir, false, Extent); if ( HitActor == None ) return false; } Destination = Pawn.Location + 2 * MINSTRAFEDIST * sideDir; GotoState('TacticalMove', 'DoStrafeMove'); return true; } function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class damageType, vector Momentum) { local float pick; local vector sideDir; local bool bWasOnGround; Super.NotifyTakeHit(InstigatedBy,HitLocation, Damage,DamageType,Momentum); bWasOnGround = (Pawn.Physics == PHYS_Walking); if ( Pawn.health <= 0 ) return; if ( StrafeFromDamage(damage, damageType, true) ) return; else if ( bWasOnGround && (MoveTarget == Enemy) && (Pawn.Physics == PHYS_Falling) ) //weave { pick = 1.0; if ( bStrafeDir ) pick = -1.0; sideDir = Normal( Normal(Enemy.Location - Pawn.Location) Cross vect(0,0,1) ); sideDir.Z = 0; Pawn.Velocity += pick * Pawn.GroundSpeed * 0.7 * sideDir; if ( FRand() < 0.2 ) bStrafeDir = !bStrafeDir; } } event bool NotifyBump(actor Other) { if ( Other == Enemy ) { DoRangedAttackOn(Enemy); return false; } return Global.NotifyBump(Other); } function Timer() { enable('NotifyBump'); Target = Enemy; if(ClearShot(Target.Location,true)) TimedFireWeaponAtEnemy(); } function EnemyNotVisible() { // WhatToDoNext(15); GotoState('Charging', 'Moving'); WhatToDoNext(15); } function EndState() { if ( (Pawn != None) && Pawn.JumpZ > 0 ) Pawn.bCanJump = true; } Begin: if (Pawn.Physics == PHYS_Falling) { Focus = Enemy; Destination = Enemy.Location; WaitForLanding(); } if ( Enemy == None ) WhatToDoNext(16); WaitForAnim: /* if ( Pawn.bShotAnim ) { Sleep(0.35); Goto('WaitForAnim'); } */ if ( !FindBestPathToward(Enemy, false,true) ) GotoState('TacticalMove'); Moving: MoveToward(MoveTarget,FaceActor(1),,ShouldStrafeTo(MoveTarget)); WhatToDoNext(17); if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); } State WaitForAnim { ignores SeePlayer, HearNoise, KilledBy, NotifyBump, HitWall, NotifyPhysicsVolumeChange, NotifyHeadVolumeChange, Falling, TakeDamage, ReceiveWarning; event AnimEnd(int Channel) { WhatToDoNext(99); } Begin: Sleep(0.1); WhatToDoNext(99); } function bool IsStrafing() { return false; } function bool SetEnemy( Pawn NewEnemy, optional bool bHateMonster , optional float MonsterHateChanceOverride ) { if ( (NewEnemy == None) || (NewEnemy.Health <= 0) || (NewEnemy.Controller == None) || (NewEnemy == Enemy) ) return false; if( Monster(NewEnemy)!=None ) { if( KFHM.AttitudeToSpecimen>=ATTITUDE_Friendly ) Return False; else if( KFHM.AttitudeToSpecimen==ATTITUDE_Ignore && !bHateMonster ) Return False; } else if( KFHumanPawnEnemy(NewEnemy)!=None ) { if( KFHM.AttitudeToPlayer>=ATTITUDE_Friendly && KFHumanPawnEnemy(NewEnemy).AttitudeToPlayer>=ATTITUDE_Friendly ) Return False; else if( KFHM.AttitudeToPlayer=ATTITUDE_Friendly ) Return False; else if( KFHM.AttitudeToPlayer==ATTITUDE_Ignore && !bHateMonster ) Return False; } ChangeEnemy(NewEnemy,CanSee(NewEnemy)); return true; } function DoRangedAttackOn(Actor A) { Target = A; GotoState('RangedAttack'); } function bool ClearShot(Vector TargetLoc, bool bImmediateFire) { local bool bSeeTarget; if ( VSize(Enemy.Location - TargetLoc) > MAXSTAKEOUTDIST ) return false; bSeeTarget = FastTrace(TargetLoc, Pawn.Location + Pawn.EyeHeight * vect(0,0,1)); // if pawn is crouched, check if standing would provide clear shot if ( !bImmediateFire && !bSeeTarget && Pawn.bIsCrouched ) bSeeTarget = FastTrace(TargetLoc, Pawn.Location + (Pawn.Default.EyeHeight + Pawn.Default.CollisionHeight - Pawn.CollisionHeight) * vect(0,0,1)); if ( !bSeeTarget || !FastTrace(TargetLoc , Enemy.Location + Enemy.BaseEyeHeight * vect(0,0,1)) ); return false; if ( (Monster(Pawn).SplashDamage() && (VSize(Pawn.Location - TargetLoc) < Monster(Pawn).GetDamageRadius())) || !FastTrace(TargetLoc + vect(0,0,0.9) * Enemy.CollisionHeight, Pawn.Location) ) { StopFiring(); return false; } return true; } state RangedAttack { ignores SeePlayer, HearNoise, Bump; function bool Stopped() { return true; } function CancelCampFor(Controller C) { DoTacticalMove(); } function StopFiring() { Global.StopFiring(); if ( bHasFired ) { bHasFired = false; WhatToDoNext(32); } } function EnemyNotVisible() { //let attack animation complete DoCharge(); // WhatToDoNext(33); } function Timer() { if ( KFHumanPawnEnemy(Pawn).PreferMelee() ) { SetCombatTimer(); StopFiring(); Pawn.bWantsToCrouch = false; WhatToDoNext(34); } else TimedFireWeaponAtEnemy(); } function DoRangedAttackOn(Actor A) { Target = A; GotoState('RangedAttack'); } function BeginState() { StopStartTime = Level.TimeSeconds; bHasFired = false; Pawn.Acceleration = vect(0,0,0); //stop if ( Target == None ) Target = Enemy; if ( Target == None ) log(GetHumanReadableName()$" no target in ranged attack"); } Begin: bHasFired = false; GoalString = "Ranged attack"; Focus = Target; Sleep(0.0); if ( Enemy != None ) CheckIfShouldCrouch(Pawn.Location,Enemy.Location, 1); if ( NeedToTurn(Target.Location) ) { Focus = Target; FinishRotation(); } bHasFired = true; if ( Target == Enemy ) TimedFireWeaponAtEnemy(); else FireWeaponAt(Target); Sleep(0.1); if ( KFHumanPawnEnemy(Pawn).PreferMelee() || (Target == None) || (Target != Enemy) ) WhatToDoNext(35); if ( Enemy != None ) CheckIfShouldCrouch(Pawn.Location,Enemy.Location, 1); Focus = Target; Sleep(FMax(Pawn.RangedAttackTime(),0.2 + (0.5 + 0.5 * FRand()) * 0.4 * (7 - Skill))); WhatToDoNext(36); if ( bSoaking ) SoakStop("STUCK IN RANGEDATTACK!"); } function SetMaxDesiredSpeed() { if ( Pawn != None ) Pawn.MaxDesiredSpeed = 1; } state Hunting { ignores EnemyNotVisible; /* MayFall() called by] engine physics if walking and bCanJump, and is about to go off a ledge. Pawn has opportunity (by setting bCanJump to false) to avoid fall */ function bool IsHunting() { return true; } function MayFall() { Pawn.bCanJump = ( (MoveTarget == None) || (MoveTarget.Physics != PHYS_Falling) || !MoveTarget.IsA('Pickup') ); } function SeePlayer(Pawn SeenPlayer) { if ( SeenPlayer == Enemy ) { if ( Level.timeseconds - ChallengeTime > 7 ) { ChallengeTime = Level.TimeSeconds; //Monster(Pawn).PlayChallengeSound(); } VisibleEnemy = Enemy; EnemyVisibilityTime = Level.TimeSeconds; bEnemyIsVisible = true; Focus = Enemy; WhatToDoNext(22); } else Global.SeePlayer(SeenPlayer); } function Timer() { SetCombatTimer(); StopFiring(); } function PickDestination() { local vector nextSpot, ViewSpot,Dir; local float posZ; local bool bCanSeeLastSeen; // If no enemy, or I should see him but don't, then give up if ( (Enemy == None) || (Enemy.Health <= 0) ) { Enemy = None; WhatToDoNext(23); return; } if ( Pawn.JumpZ > 0 ) Pawn.bCanJump = true; if ( ActorReachable(Enemy) ) { Destination = Enemy.Location; 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; 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; } } } } function bool FindViewSpot() { local vector X,Y,Z; GetAxes(Rotation,X,Y,Z); // try left and right if ( FastTrace(Enemy.Location, Pawn.Location + 2 * Y * Pawn.CollisionRadius) ) { Destination = Pawn.Location + 2.5 * Y * Pawn.CollisionRadius; return true; } if ( 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; } function EndState() { if ( (Pawn != None) && (Pawn.JumpZ > 0) ) Pawn.bCanJump = true; } AdjustFromWall: MoveTo(Destination, MoveTarget); Begin: WaitForLanding(); if ( CanSee(Enemy) ) SeePlayer(Enemy); WaitForAnim: PickDestination(); if ( Level.timeseconds - ChallengeTime > 10 ) { ChallengeTime = Level.TimeSeconds; // Monster(Pawn).PlayChallengeSound(); } SpecialNavig: if (MoveTarget == None) MoveTo(Destination); else MoveToward(MoveTarget,FaceActor(10),,(FRand() < 0.75) && ShouldStrafeTo(MoveTarget)); WhatToDoNext(27); if ( bSoaking ) SoakStop("STUCK IN HUNTING!"); } function bool Stopped() { return bPreparingMove; } function ExecuteWhatToDoNext() { bHasFired = false; GoalString = "WhatToDoNext at "$Level.TimeSeconds; if ( Pawn == None ) { warn(GetHumanReadableName()$" WhatToDoNext with no pawn"); return; } if ( bPreparingMove ) { Pawn.Acceleration = vect(0,0,0); // GotoState('WaitForAnim'); 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 ( Level.Game.bGameEnded && (Enemy != None) && Enemy.Controller.bIsPlayer ) Enemy = None; if ( (Enemy == None)) // || !EnemyVisible() FindNewEnemy(); if ( Enemy != None ) ChooseAttackMode(); else { GoalString = "WhatToDoNext Wander or Camp at "$Level.TimeSeconds; WanderOrCamp(true); } SwitchToBestWeapon(); } function bool CheckPathToGoalAround(Pawn P) { return false; } state Fallback extends MoveToGoalWithEnemy { function bool IsRetreating() { return ( (Pawn.Acceleration Dot (Pawn.Location - Enemy.Location)) > 0 ); } event bool NotifyBump(actor Other) { local Pawn P; Disable('NotifyBump'); if ( MoveTarget == Other ) { if ( MoveTarget == Enemy && Pawn.HasWeapon() ) { TimedFireWeaponAtEnemy(); DoRangedAttackOn(Enemy); } return false; } P = Pawn(Other); if ( (P == None) || (P.Controller == None) ) return false; if ( !SameTeamAs(P.Controller) && (MoveTarget == RouteCache[0]) && (RouteCache[1] != None) && P.ReachedDestination(MoveTarget) ) { MoveTimer = VSize(RouteCache[1].Location - Pawn.Location)/(Pawn.GroundSpeed * Pawn.DesiredSpeed) + 1; MoveTarget = RouteCache[1]; } SetEnemy(P); if ( Enemy == Other ) { Focus = Enemy; TimedFireWeaponAtEnemy(); } if ( CheckPathToGoalAround(P) ) return false; AdjustAround(P); return false; } function MayFall() { Pawn.bCanJump = ( MoveTarget != None && (MoveTarget.Physics != PHYS_Falling) ); } function EnemyNotVisible() { if ( !FindNewEnemy() || (Enemy == None) ) WhatToDoNext(13); else { enable('SeePlayer'); disable('EnemyNotVisible'); } } function EnemyChanged(bool bNewEnemyVisible) { bEnemyAcquired = false; SetEnemyInfo(bNewEnemyVisible); if ( bNewEnemyVisible ) { disable('SeePlayer'); enable('EnemyNotVisible'); } } Begin: WaitForLanding(); Moving: MoveTarget = RouteCache[1]; MoveToward(MoveTarget,FaceActor(1),,ShouldStrafeTo(MoveTarget)); WhatToDoNext(14); if ( bSoaking ) SoakStop("STUCK IN FALLBACK!"); goalstring = goalstring$" STUCK IN FALLBACK!"; } /* ChooseAttackMode() Handles tactical attacking state selection - choose which type of attack to do from here */ function ChooseAttackMode() { GoalString = " ChooseAttackMode last seen "$(Level.TimeSeconds - LastSeenTime); // should I run away? if ( (pawn.Health / pawn.HealthMax) <= 0.25 || VSize(location - enemy.Location) < 50 && pawn.Weapon != none && !pawn.Weapon.bMeleeWeapon) { GoalString = "Retreat"; GotoState('FallBack'); } GoalString = "ChooseAttackMode FightEnemy"; FightEnemy(true); } function float AdjustAimError(float aimerror, float TargetDist, bool bDefendMelee, bool bInstantProj, bool bLeadTargetNow ) { if ( (Pawn(Target) != None) && (Pawn(Target).Visibility < 2) ) aimerror *= 2.5; // figure out the relative motion of the target across the bots view, and adjust aim error // based on magnitude of relative motion aimerror = aimerror * FMin(5,(12 - 11 * (Normal(Target.Location - Pawn.Location) Dot Normal((Target.Location + 1.2 * Target.Velocity) - (Pawn.Location + Pawn.Velocity))))); // if enemy is charging straight at bot with a melee weapon, improve aim if ( bDefendMelee ) aimerror *= 0.5; if ( Target.Velocity == vect(0,0,0) ) aimerror *= 0.6; // aiming improves over time if stopped if ( Stopped() && (Level.TimeSeconds > StopStartTime) ) { if ( (Skill+Accuracy) > 4 ) aimerror *= 0.9; aimerror *= FClamp((2 - 0.08 * FMin(skill,7) - FRand())/(Level.TimeSeconds - StopStartTime + 0.4),0.7,1.0); } // adjust aim error based on skill if ( !bDefendMelee ) aimerror *= (3.3 - 0.37 * (FClamp(skill+Accuracy,0,8.5) + 0.5 * FRand())); // Bots don't aim as well if recently hit, or if they or their target is flying through the air if ( ((skill < 7) || (FRand()<0.5)) && (Level.TimeSeconds - Pawn.LastPainTime < 0.2) ) aimerror *= 1.3; if ( (Pawn.Physics == PHYS_Falling) || (Target.Physics == PHYS_Falling) ) aimerror *= 1.6; // Bots don't aim as well at recently acquired targets (because they haven't had a chance to lock in to the target) if ( AcquireTime > Level.TimeSeconds - 0.5 - 0.6 * (7 - skill) ) { aimerror *= 1.5; if ( bInstantProj ) aimerror *= 1.5; } return (Rand(2 * aimerror) - aimerror); } defaultproperties { }