519 lines
18 KiB
Ucode
519 lines
18 KiB
Ucode
|
//=============================================================================
|
||
|
// KFAIController_Monster.uc
|
||
|
//=============================================================================
|
||
|
// Base AIController for KF2's Zeds
|
||
|
//=============================================================================
|
||
|
// Killing Floor 2
|
||
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
||
|
//=============================================================================
|
||
|
|
||
|
class KFAIController_Monster extends KFAIController
|
||
|
dependson(KFAIController)
|
||
|
abstract
|
||
|
native(AI);
|
||
|
|
||
|
`include(KFGame\KFGameAnalytics.uci);
|
||
|
|
||
|
/** Zeds who can grab prefer to use a grab as their initial attack - if true, they've already done this */
|
||
|
var bool bCompletedInitialGrabAttack;
|
||
|
/** Clot won't perform grab until closer than this distance. TODO: If we keep this, change it to a % scale of MaxGrabDistance in KFSM_Clot_Grab */
|
||
|
var float MinDistanceToPerformGrabAttack;
|
||
|
/** Time frequency for grab attacks */
|
||
|
var float MinTimeBetweenGrabAttacks;
|
||
|
/** Last time a grab attack was performed */
|
||
|
var float LastAttackTime_Grab;
|
||
|
var bool bPathAroundDestructiblesICantBreak;
|
||
|
/** Determines if a zed should try to force a repath if they cannot execute a valid strike */
|
||
|
var bool bRepathOnInvalidStrike;
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* RunOverWarning (warns Zeds nearby that my pawn's about to run into them)
|
||
|
********************************************************************************************* */
|
||
|
/** Zed will transmit ReceiveRunOverWarning events to other Zeds if its about to run them over */
|
||
|
var bool bUseRunOverWarning;
|
||
|
/** Speed must be greater than this to transmit run over warning (if bUseRunOverWarning=true) */
|
||
|
var float MinRunOverSpeed;
|
||
|
/** Last time checked for pawns to transmit RunOverWarning to (if bUseRunOverWarning=true) */
|
||
|
var float LastRunOverWarningTime;
|
||
|
/** Minimum angle to victim required to transmit RunOverWarning (if bUseRunOverWarning=true) */
|
||
|
var float MinRunOverWarningAim;
|
||
|
/** When TRUE, this Zed will attempt to evade when warned of being run over */
|
||
|
var bool bEvadeOnRunOverWarning;
|
||
|
/** Scales the delay from the initial warning notification. Increase for zeds that are fast evaders, decrease for slow evaders */
|
||
|
var float RunOverEvadeDelayScale;
|
||
|
|
||
|
cpptext
|
||
|
{
|
||
|
UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
|
||
|
|
||
|
// Called by native Tick() to evaluate if Zed in melee range of target - if so, will call InMeleeRange() event
|
||
|
virtual UBOOL TickMeleeCombatDecision( FLOAT DeltaTime );
|
||
|
// Supports "run over" warning notification to other NPCs - mainly for larger Zeds to use
|
||
|
// to give nearby Zeds a chance to get out of the way.
|
||
|
virtual void TickRunOverWarning( FLOAT DeltaSeconds );
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Initialization, Pawn Possession, and Destruction
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** Only spawning a PRI for gameplayevents! */
|
||
|
function InitPlayerReplicationInfo()
|
||
|
{
|
||
|
local KFGameInfo KFGI;
|
||
|
local string NPCName;
|
||
|
|
||
|
KFGI = KFGameInfo(WorldInfo.Game);
|
||
|
if( KFGI != none && KFGI.bEnableGameAnalytics )
|
||
|
{
|
||
|
PlayerReplicationInfo = Spawn(class'KFDummyReplicationInfo', self,, vect(0,0,0),rot(0,0,0));
|
||
|
if ( Pawn != none )
|
||
|
{
|
||
|
NPCName = string(Pawn.name);
|
||
|
NPCName = Repl(NPCName,"KFPawn_Zed","",false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NPCName = string(self.name);
|
||
|
NPCName = Repl(NPCName,"KFAIController_Zed","",false);
|
||
|
}
|
||
|
|
||
|
PlayerReplicationInfo.PlayerName = NPCName;
|
||
|
|
||
|
/* __TW_ANALYTICS_ */
|
||
|
`RecordZedSpawn(self);
|
||
|
// don't call SetPlayerName() as that will broadcast entry messages but the GameInfo hasn't had a chance
|
||
|
// to potentionally apply a player/bot name yet
|
||
|
//PlayerReplicationInfo.PlayerName = class'GameInfo'.default.DefaultPlayerName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Set MyKFPawn to avoid casting */
|
||
|
event Possess( Pawn inPawn, bool bVehicleTransition )
|
||
|
{
|
||
|
if( KFPawn_Monster(inPawn) != none )
|
||
|
{
|
||
|
MyKFPawn = KFPawn_Monster( inPawn );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`warn( GetFuncName()$"() attempting to possess "$inPawn$", but it's not a KFPawn_Monster class! MyKFPawn variable will not be valid." );
|
||
|
}
|
||
|
|
||
|
super.Possess( inPawn, bVehicleTransition );
|
||
|
|
||
|
SetPawnDefaults();
|
||
|
}
|
||
|
|
||
|
function SetPawnDefaults()
|
||
|
{
|
||
|
local float SprintChance;
|
||
|
local float SprintDamagedChance;
|
||
|
local float HiddenSpeedMod;
|
||
|
local float GameDifficulty;
|
||
|
local KFGameDifficultyInfo DifficultyInfo;
|
||
|
local KFGameInfo KFGI;
|
||
|
|
||
|
KFGI = KFGameInfo( WorldInfo.Game );
|
||
|
|
||
|
GameDifficulty = KFGI.GetModifiedGameDifficulty();
|
||
|
DifficultyInfo = KFGI.DifficultyInfo;
|
||
|
|
||
|
SprintChance = DifficultyInfo.GetCharSprintChanceByDifficulty( MyKFPawn, GameDifficulty );
|
||
|
SprintDamagedChance = DifficultyInfo.GetCharSprintWhenDamagedChanceByDifficulty( MyKFPawn, GameDifficulty );
|
||
|
HiddenSpeedMod = DifficultyInfo.GetAIHiddenSpeedModifier( KFGI.GetLivingPlayerCount() );
|
||
|
MyKFPawn.HiddenGroundSpeed = MyKFPawn.default.HiddenGroundSpeed * HiddenSpeedMod;
|
||
|
|
||
|
if ( MyKFPawn.PawnAnimInfo != none )
|
||
|
{
|
||
|
MyKFPawn.PawnAnimInfo.SetDifficultyValues( DifficultyInfo );
|
||
|
}
|
||
|
|
||
|
// Each zed has a chance he will sprint at a certain difficulty
|
||
|
// NOTE: Some zeds now bypass this check because they need to sprint under certain conditions regardless of
|
||
|
// difficulty! Search the code for bIsSprinting = true. Evil, yes, but necessary
|
||
|
SetCanSprint( FRand() <= SprintChance );
|
||
|
SetCanSprintWhenDamaged( FRand() <= SprintDamagedChance );
|
||
|
|
||
|
bDefaultCanSprint = bCanSprint;
|
||
|
|
||
|
if( KFGI.BaseMutator != None )
|
||
|
{
|
||
|
KFGI.BaseMutator.ModifyAI( Pawn );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Notifications & Events
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** Re-Enables notifications from TickMeleeCombatDecision() */
|
||
|
function Timer_EnableMeleeRangeEventProbing()
|
||
|
{
|
||
|
if( !MyKFPawn.IsDoingSpecialMove() )
|
||
|
{
|
||
|
EnableMeleeRangeEventProbing();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Re-Enable timer once at a time (added 7/2014)
|
||
|
SetTimer( 0.12f, false, nameof(Timer_EnableMeleeRangeEventProbing), self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Notification that we have passed all our basic melee checks and are ready to attempt a melee attack */
|
||
|
event ReadyToMelee()
|
||
|
{
|
||
|
// Check script to see if a strike is allowed
|
||
|
if( CanDoStrike() )
|
||
|
{
|
||
|
// Update our next pending strike
|
||
|
UpdatePendingStrike();
|
||
|
LastGetStrikeTime = WorldInfo.TimeSeconds;
|
||
|
|
||
|
// Perform strike if we have a valid animation
|
||
|
if( PendingAnimStrikeIndex != 255 )
|
||
|
{
|
||
|
DoStrike();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Attempt to find another path to enemy
|
||
|
if( bRepathOnInvalidStrike && (bFailedToMoveToEnemy || (!bMovingToGoal && !bMovingToEnemy)) )
|
||
|
{
|
||
|
SetEnemyMoveGoal(self, true,,, true);
|
||
|
}
|
||
|
// If we can't attack, and are close to the enemy, do a taunt so we don't just stand there
|
||
|
else if( !CheckOverallCooldownTimer() && Enemy != none && Pawn != none && Pawn.IsAliveAndWell() )
|
||
|
{
|
||
|
if( VSize(Enemy.Location - Pawn.Location) < MyKFPawn.CylinderComponent.CollisionRadius * 3.0 )
|
||
|
{
|
||
|
if( MyKFPawn.CanDoSpecialMove(SM_Taunt) && `TimeSince(LastTauntTime) > 2.f )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" starting taunt command", 'CantMelee' );
|
||
|
class'AICommand_TauntEnemy'.static.Taunt( self, KFPawn(Enemy), TAUNT_Standard );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Pathfinding
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** Set up path constraints and attempt to build a path to Goal actor. Distance is an optional offset. */
|
||
|
event Actor GeneratePathTo( Actor Goal, optional float Distance, optional bool bAllowPartialPath )
|
||
|
{
|
||
|
local actor PathResult;
|
||
|
local int i;
|
||
|
|
||
|
if( bDisablePartialPaths )
|
||
|
{
|
||
|
bAllowPartialPath = false;
|
||
|
}
|
||
|
|
||
|
AddBasePathConstraints();
|
||
|
|
||
|
class'Path_TowardGoal'.static.TowardGoal( Pawn, Goal );
|
||
|
|
||
|
if( bPathAroundDestructiblesICantBreak )
|
||
|
{
|
||
|
/** NPC will build path around destructible objects not configured to accept bump damage */
|
||
|
class'Path_AroundDestructibles'.static.AvoidDestructibles( Pawn, true, true );
|
||
|
class'Goal_Null'.static.GoUntilBust( Pawn, 2024 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
class'Goal_AtActor'.static.AtActor( Pawn, Goal, Distance, bAllowPartialPath );
|
||
|
}
|
||
|
// Attempt to build the path.
|
||
|
PathResult = FindPathToward( Goal );
|
||
|
Pawn.ClearConstraints();
|
||
|
|
||
|
if( PathResult == None )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() failed to build a path to "$Goal$", offset distance was "$Distance$", bAllowPartialPath was "$bAllowPartialPath, 'PathWarning' );
|
||
|
}
|
||
|
|
||
|
if( bShowMovePointsDebugInfo )
|
||
|
{
|
||
|
for( i = 0; i < RouteCache.Length; i++ )
|
||
|
{
|
||
|
DrawDebugStar( RouteCache[i].Location, PathNodeShowRouteCacheCrossSize, PathNodeShowRouteCacheColor.R, PathNodeShowRouteCacheColor.G, PathNodeShowRouteCacheColor.B, true);
|
||
|
DrawDebugString( RouteCache[i].Location + vect(0,0,5), string(i), , PathNodeShowRouteCacheColor, PathNodeShowRouteCacheNumberLabelDuration);
|
||
|
|
||
|
if( i > 0 )
|
||
|
{
|
||
|
DrawDebugLine( RouteCache[i].Location, RouteCache[i-1].Location, PathNodeShowRouteCacheColor.R, PathNodeShowRouteCacheColor.G, PathNodeShowRouteCacheColor.B, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return PathResult;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Combat
|
||
|
**********************************************************************************************/
|
||
|
|
||
|
/** Can this pawn perform a grab attack? */
|
||
|
event bool CanGrabAttack()
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local KFPerk EnemyPerk;
|
||
|
local KFPawn KFPawnEnemy;
|
||
|
local float DistSq;
|
||
|
local vector Extent, HitLocation, HitNormal;
|
||
|
local Actor HitActor;
|
||
|
|
||
|
// If I'm dead, incapable of grabbing, or have no enemy, or my enemy is a player, or I'm busy doing a melee attack, refuse.
|
||
|
if( (MyKFPawn == none || !MyKFPawn.bCanGrabAttack || MyKFPawn.Health <= 0) || (Enemy == none) || (Enemy != none && Pawn.IsSameTeam(Enemy)) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
KFPawnEnemy = KFPawn( Enemy );
|
||
|
if( KFPawnEnemy == none || !KFPawnEnemy.CanBeGrabbed(MyKFPawn) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If I'm crippled, falling, busy doing an attack, or incapacitated, refuse.
|
||
|
if( MyKFPawn.bIsHeadless || (MyKFPawn.Physics == PHYS_Falling) || IsDoingAttackSpecialMove() || !MyKFPawn.IsCombatCapable() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check for fakeout perk
|
||
|
KFPH = KFPawn_Human(Enemy);
|
||
|
if ( KFPH != none )
|
||
|
{
|
||
|
EnemyPerk = KFPH.GetPerk();
|
||
|
if ( EnemyPerk != none && EnemyPerk.CanNotBeGrabbed() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !bCompletedInitialGrabAttack || (LastAttackTime_Grab == 0.f || (`TimeSince(LastAttackTime_Grab) > MinTimeBetweenGrabAttacks)) )
|
||
|
{
|
||
|
// Make sure the enemy's center of mass (location) is within my collision cylinder
|
||
|
if( Abs(Enemy.Location.Z - Pawn.Location.Z) > class'KFSM_GrappleCombined'.default.MaxVictimZOffset )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
DistSq = VSizeSq(Enemy.Location - Pawn.Location);
|
||
|
if( DistSq > MinDistanceToPerformGrabAttack * MinDistanceToPerformGrabAttack || MyKFPawn.IsPawnMovingAwayFromMe(Enemy, 300.f) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Set our extent
|
||
|
Extent.X = Pawn.GetCollisionRadius() * 0.5f;
|
||
|
Extent.Y = Extent.X;
|
||
|
Extent.Z = Pawn.GetCollisionHeight() * 0.5f;
|
||
|
|
||
|
// Do the same kind of trace we do in KFSM_GrappleStart
|
||
|
HitActor = Trace(HitLocation, HitNormal, Enemy.Location, Pawn.Location, true, Extent);
|
||
|
if ( HitActor != None && HitActor != Enemy )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if( !CanTargetBeGrabbed(KFPawnEnemy) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
/** Makes Zed have high desire to grab as initial attack */
|
||
|
if( !MyKFPawn.IsDoingMeleeAttack() && (!bCompletedInitialGrabAttack || (FRand() < MyKFPawn.GrabAttackFrequency)) ) //&& !MyKFPawn.IsPawnMovingAwayFromMe(Enemy, 250.f) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
`AILog( GetFuncName()$"() returning FALSE", 'GrabAttack' );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool CanDoStrike()
|
||
|
{
|
||
|
local actor HitActor;
|
||
|
local vector TraceStepLocation;
|
||
|
|
||
|
// Used by KFPawnAnimInfo to determine if an attack can be performed if legs are blocked (lunges, etc)
|
||
|
bIsBodyBlocked = false;
|
||
|
|
||
|
// Check if a wall or another Zed is blocking my pawn from performing a melee attack, ignore zed collision if bCanStrikeThroughEnemies is true,
|
||
|
TraceStepLocation = Pawn.Location + (vect(0,0,-1) * (Pawn.CylinderComponent.CollisionHeight * 0.5f));
|
||
|
HitActor = ActorBlockTest( Pawn, Enemy.Location, TraceStepLocation,, !bCanStrikeThroughEnemies );
|
||
|
if( HitActor != none && HitActor != Enemy )
|
||
|
{
|
||
|
if( HitActor.bWorldGeometry )
|
||
|
{
|
||
|
// Set the body blocked flag so the anim info can check it
|
||
|
bIsBodyBlocked = true;
|
||
|
}
|
||
|
|
||
|
// Try again at eyeheight
|
||
|
HitActor = ActorBlockTest( Pawn, Enemy.Location + (vect(0,0,1) * Enemy.BaseEyeHeight), Pawn.Location + (vect(0,0,1) * Pawn.BaseEyeHeight),, !bCanStrikeThroughEnemies );
|
||
|
if( HitActor != None && HitActor != Enemy && (!bCanStrikeThroughEnemies || HitActor.bWorldGeometry) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function DoStrike()
|
||
|
{
|
||
|
local byte StrikeFlags;
|
||
|
|
||
|
if( MyKFPawn != none && MyKFPawn.PawnAnimInfo != none )
|
||
|
{
|
||
|
StrikeFlags = MyKFPawn.PawnAnimInfo.GetStrikeFlags(PendingAnimStrikeIndex);
|
||
|
if( StrikeFlags != 255 )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() "$VSize(MyKFPawn.Location - Enemy.Location)$" units from enemy and I DO HAVE AN available attack!", 'Command_Attack_Melee' );
|
||
|
class'AICommand_Attack_Melee'.static.Melee( self, Enemy, StrikeFlags );
|
||
|
|
||
|
MyKFPawn.PawnAnimInfo.UpdateAttackCooldown(self, PendingAnimStrikeIndex);
|
||
|
|
||
|
UpdatePendingStrike();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() "$VSize(MyKFPawn.Location - Enemy.Location)$" units from enemy and I have no available attack!", 'Command_Attack_Melee' );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Perform a melee attack AICommand.. InTarget is optional actor to attack (door, etc.) */
|
||
|
function DoMeleeAttack( optional Pawn NewEnemy, optional Actor InTarget, optional byte AttackFlags )
|
||
|
{
|
||
|
/*
|
||
|
local AICommand AIC;
|
||
|
|
||
|
if( MyKFPawn != none && (!MyKFPawn.bIsHeadless && !MyKFPawn.bEmpPanicked && !IsMeleeRangeEventProbingEnabled()) || (MyKFPawn.IsDoingSpecialMove() && !MyKFPawn.IsDoingSpecialMove(SM_ChargeRun)) )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() skipping melee attack because "$Pawn$" is already busy.", 'Command_Attack_Melee' );
|
||
|
return;
|
||
|
}
|
||
|
AIC = AICommand( GetActiveCommand() );
|
||
|
if( AIC != none )
|
||
|
{
|
||
|
if( !AIC.bAllowedToAttack )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() refusing to do melee attack because "$AIC$" bAllowedToAttack is FALSE", 'Command_Attack_Melee' );
|
||
|
return;
|
||
|
}
|
||
|
if( AICommand_Pause(AIC) != none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if( AICommand_TauntEnemy(AIC) != none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if( MyKFPawn != none && MyKFPawn.PawnAnimInfo != none )
|
||
|
{
|
||
|
// Only Pack flags if 255 was initially passed in
|
||
|
if( AttackFlags == 255 )
|
||
|
{
|
||
|
AttackFlags = ChooseStrikeAnimation();
|
||
|
}
|
||
|
|
||
|
if( AttackFlags != 255 )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Aborting movement commands and starting melee attack command", 'Command_Attack_Melee' );
|
||
|
class'AICommand_Attack_Melee'.static.Melee( self, InTarget, AttackFlags );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( !AICommand(CommandList).bAllowedToAttack )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() refusing to do melee attack because "$CommandList$" bAllowedToAttack is FALSE", 'Command_Attack_Melee' );
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
/** Called when in melee range but enemy is blocked from me, probably by another Zed */
|
||
|
function bool HandleZedBlockedPath()
|
||
|
{
|
||
|
local actor HitActor;
|
||
|
local KFPawn_Monster HitMonster;
|
||
|
|
||
|
HitActor = ActorBlockTest( Pawn, Enemy.Location + vect(0,0,1) * (Enemy.BaseEyeHeight * 0.5f), MyKFPawn.Location + vect(0,0,1) * (MyKFPawn.BaseEyeHeight * 0.5f), MyKFPawn.GetCollisionExtent() * vect(0.2f,0.2f,0.2f), true );
|
||
|
if( HitActor == none || HitActor == Enemy )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If we hit a monster check HandleEnemyBlocked, otherwise we're good to strike
|
||
|
HitMonster = KFPawn_Monster(HitActor);
|
||
|
if( HitMonster != none && HitMonster.Health > 0 )
|
||
|
{
|
||
|
if( MyKFPawn == none || MyKFPawn.Health <= 0 || MyKFPawn.IsDoingSpecialMove() )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
`AILog( GetFuncName()$" ENEMY IS BLOCKED", 'ReachedEnemy' );
|
||
|
DisableMeleeRangeEventProbing();
|
||
|
SetTimer( 1.5f + (2.f*FRand()), false, nameof(Timer_EnableMeleeRangeEventProbing), self );
|
||
|
if( FindNewEnemy() )
|
||
|
{
|
||
|
ForcePauseAndRepath();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( VSize(Enemy.Location - Pawn.Location) < AttackRange && bDirectMoveToGoal )
|
||
|
{
|
||
|
if( MyKFPawn.CanDoSpecialMove(SM_Taunt) && FRand() < 0.32 && `TimeSince(LastTauntTime) > 2.f )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" starting taunt command", 'ReachedEnemy' );
|
||
|
class'AICommand_TauntEnemy'.static.Taunt( self, KFPawn(Enemy), TAUNT_Standard );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( GetFuncName()$" starting pauseAI command", 'ReachedEnemy' );
|
||
|
DoPauseAI( 1.f + (3.f * FRand()), true );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Notification I'm about to be run into by a Zed which has bUseRunOverWarning set to true */
|
||
|
event RunOverWarning( KFPawn IncomingKFP, float IncomingSpeedSquared, vector RunOverPoint )
|
||
|
{
|
||
|
local float Delay;
|
||
|
|
||
|
if( bEvadeOnRunOverWarning && CanEvade(true) )
|
||
|
{
|
||
|
Delay = ( VSize(IncomingKFP.Location - MyKFPawn.Location) / Sqrt(IncomingSpeedSquared) ) * RunOverEvadeDelayScale;
|
||
|
DoEvade( GetBestEvadeDir(RunOverPoint,, false), IncomingKFP,, Delay, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DefaultProperties
|
||
|
{
|
||
|
// ---------------------------------------------
|
||
|
// Combat
|
||
|
MeleeCommandClass=class'AICommand_Base_Zed'
|
||
|
DoorMeleeDistance=200.f
|
||
|
MinTimeBetweenGrabAttacks=5.f
|
||
|
MinDistanceToPerformGrabAttack=188.f
|
||
|
|
||
|
// ---------------------------------------------
|
||
|
// AI / Navigation
|
||
|
DefaultCommandClass=class'AICommand_Base_Zed'
|
||
|
SightCounterInterval=0.35f
|
||
|
bEvadeOnRunOverWarning=false
|
||
|
RunOverEvadeDelayScale=0.25f
|
||
|
|
||
|
bIsPlayer=false
|
||
|
}
|