1743 lines
43 KiB
Ucode
1743 lines
43 KiB
Ucode
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<KillAssistant> 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<K.DeathMarkers.Length; i++ )
|
|
{
|
|
if( K.DeathMarkers[i]==None )
|
|
Continue;
|
|
Dist = VSize(K.DeathMarkers[i].Location-Pawn.Location);
|
|
if( Dist<800 && ActorReachable(K.DeathMarkers[i]) && (Best==None || Dist<BDist) )
|
|
{
|
|
Best = K.DeathMarkers[i];
|
|
BDist = Dist;
|
|
}
|
|
}
|
|
if( Best==None )
|
|
Return False;
|
|
TargetCorpse = Best;
|
|
GoToState('CorpseFeeding');
|
|
Return True;
|
|
}
|
|
|
|
|
|
function bool FindNewEnemy()
|
|
{
|
|
local Pawn BestEnemy;
|
|
local bool bSeeBest;
|
|
local float BestDist, NewDist;
|
|
local Controller PC;
|
|
local KFHumanPawn Human;
|
|
local float HighestThreatLevel,ThreatLevel;
|
|
|
|
if( KFM.bNoAutoHuntEnemies )
|
|
Return False;
|
|
|
|
for ( PC=Level.ControllerList; PC!=None; PC=PC.NextController )
|
|
{
|
|
Human = KFHumanPawn(PC.Pawn);
|
|
|
|
if(Human != none &&
|
|
Human.Health > 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> damageType, vector Momentum)
|
|
{
|
|
local KFMonster Monster;
|
|
|
|
// Get ticked and attack nearby bloats because you think they puked on you!
|
|
if(class<DamTypeBlowerThrower>(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> 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> damageType, vector Momentum)
|
|
{
|
|
local KFMonster Monster;
|
|
|
|
// Get ticked and attack nearby bloats because you think they puked on you!
|
|
if(class<DamTypeBlowerThrower>(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)<VSize(NewEnemy.Location-Pawn.Location) )
|
|
Return False;
|
|
Enemy = None;
|
|
}
|
|
|
|
if( MonsterHateChanceOverride == 0 )
|
|
{
|
|
MonsterHateChanceOverride = 0.15;
|
|
}
|
|
|
|
if( bHateMonster && KFMonster(NewEnemy)!=None && NewEnemy.Controller!=None && (NewEnemy.Controller.Target==Self || FRand()<MonsterHateChanceOverride)
|
|
&& NewEnemy.Health>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
|
|
}
|