2020-12-13 15:01:13 +00:00
//=============================================================================
// KFAIController
//=============================================================================
// Base AI Controller for KFMonsterPawns.
// A lot of this should really be in KFAIController_Monster, which hasn't been
// a priority because currently all KFAI pawns are monsters/player enemies.
// TODO: Move zed-specific code down to KFAIController_Monster.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFAIController extends BaseAIController
native ( AI ) ;
` include(KFGame \K FGameAnalytics.uci);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Misc . Defines
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** For convenience (LATENT_MOVETOWARD is already defined in Controller.uc) */
const LATENT _MOVETO = 501 ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Misc . Defines
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** If true, will call ScriptGetTeamNum() to determine team if there is no PRI for this AI */
var bool bAllowScriptTeamCheck ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialization
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Cached copy of gameinfo to avoid casting */
var transient KFGameInfo MyKFGameInfo ;
/** Cached copy of AI pawn to avoid casting */
var transient KFPawn _Monster MyKFPawn ;
/** GameInfo's current AIManager */
var transient KFAIDirector MyAIDirector ;
/** Steering used to actually move the controlled pawn */
var instanced KFAISteering Steering ;
/** Default Combat Command we should move into when spotting an enemy */
var class < AICommand > DefaultCommandClass ;
/** command we should use when we want to pursue and melee an enemy */
var class < AICommand > MeleeCommandClass ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Behavior Properties
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** (Default behavior) Will send NPC to move to nearest enemy after possession, from ActionIdle startup state */
var bool bIdleMoveToNearestEnemy ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* General Movement
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Intermediate move goal used when pathfinding to MoveGoal */
var Actor IntermediateMoveGoal ;
/** Used in tracking # of times AICommand_MoveToEnemy has failed */
var int FailedMoveToEnemyCount ;
/ * * A c c e p t a b l e d i s t a n c e o f f s e t f r o m t h e g o a l , u s e d b y A I C o m m a n d _ M o v e T o G o a l ( T O D O : M o v e t h i s i n t o A I C o m m a n d _ M o v e T o G o a l i f n o t
needed here ) * /
var float MoveOffset ;
/** Actor to move toward when destination is an actor rather than a vector, used in latent movement (MOVETOWARD)*/
var Actor MoveGoal ;
/** Location to move to when destination is a vector rather than an actor, used in latent movement (MOVETO) */
var BasedPosition MovePosition ;
/** Focus override while performing a move */
var Actor MoveFocus ;
/** Last time we researched for a destination after a delay, used by AICommand_MoveToGoal */
var float LastDetourCheckTime ;
/** Was current MoveGoal successfully reached? */
var bool bReachedMoveGoal ;
/** Forces movement command to build a new path when command resumes, regardless of current routecache */
var bool bReevaluatePath ;
/** TRUE if my pawn is currently moving to its goal (that is, if an AICommand_MoveToGoal is active) */
var bool bMovingToGoal ;
/** TRUE if my pawn is currently moving to its enemy (AICommand_MoveToEnemy is active) */
var bool bMovingToEnemy ;
/** Currently moving directly to a goal, rather than still on my path */
var bool bDirectMoveToGoal ;
/** Is the current movement goal interruptable? If you need to rely on this, re-confirm that it's working properly (haven't needed this) */
var bool bMoveGoalInterruptable ;
/** AI should not update route cache - flag to prevent cache from being changed when pathing is used to pick locations (flank, hide, etc.) */
var bool bSkipRouteCacheUpdates ;
/** Used by base Zed AICommand and AICommand_MoveToEnemy */
var bool bFailedToMoveToEnemy ;
/** Used with HandlePathObstruction() event (see AICommand_MoveToGoal version, which is a function called by the controller's HPO event) */
var float LastObstructionTime ;
/** Use to track when my pawn last transitioned from walk to sprint (or vice versa) to prevent changing too frequently */
var float LastSprintChangeTime ;
/** Lock the AI to a specific rotation, see KFAIController::UpdatePawnRotation() */
var Rotator LockedRotation ;
/** Cached version of CommandList that's an AICommand (instead of BaseAICommand) */
var AICommand CachedAICommandList ;
/** Currently not turned, going to be deprecated */
var transient bool bAvoidChokePoints ;
/** Used in determining how often (when moving along a path) to check if my goal is now directly reachable */
var transient float NextDirectPathCheckTime ;
var float DirectPathCheckFrequency _Min ;
var float DirectPathCheckFrequency _Max ;
/** Used by AKFAIController::DirectPathToGoal */
var float DirectPathExtentModifier ;
/** Should sprint if enemy distance is > X and < Y - older, might be able to get rid of this */
var Vector2D SprintWithinEnemyRange ;
/** Modifier to rotation rate whenever not already modified by a special move */
var float RotationRateMultiplier ;
/** Current closed door that I'm for (assuming bPreparingMove is true to delay any latent move) */
var KFDoorActor PendingDoor ;
/ * * I f t r u e , t h i s p a w n w i l l r e c e u v e N o t i f y O n A d d T o R o u t e C a c h e ( ) e v e n t s f o r a n y N a v i g a t i o n P o i n t s w i t h b N o t i f y O n A d d T o R o u t e C a c h e = t r u e w h e n t h e y
are added to a path I ' m building * /
var bool bProbeNotifyOnAddToRouteCache ;
/** Set to true by KFJumpSpot, used in MayFall() event but might be able to get rid of this, it's old */
var bool bPlannedJump ;
/** Used by direct reachability checking code while NPC is pathfinding, Used by AKFAIController::HasDirectPathToGoal */
var float LastLOSCheckTime ;
/** Used by direct reachability if MinTimeBetweenLOSChecks seconds have yet to pass */
var bool CachedLOSCheck ;
/** Minimum time that must pass between checking direct reachability to goal */
var float MinTimeBetweenLOSChecks ;
/** Used when failing a move in AICommand_MoveToGoal... testing */
var KFPathnode BlockedPath ; // 2.21.2014
var bool bIgnoreBlockedPathList ;
var float MaxBlockedPathDuration ;
/** Turns PathLane code on/off (see ShouldUsePathlanes()) */
var bool bShouldUsePathLanes ;
/** Turns corner cutting on off (see ShouldOffsetCorners()) */
var bool bShouldOffsetCorners ;
/** If this is on, NPC will always be willing to accept partial paths when using movement AICommands */
var bool bAlwaysAcceptPartialPaths ;
/** This monster has the capability to sprint in general */
var bool bCanSprint ;
/** This monster has the capability to sprint when damaged */
var bool bCanSprintWhenDamaged ;
/** This monster's ability to sprint is currently disabled */
var bool bSprintingDisabled ;
var config Color PathNodeShowRouteCacheColor ;
var config vector PathNodeShowRouteCacheCrossOffset ;
var config float PathNodeShowRouteCacheCrossSize ;
var config vector PathNodeShowRouteCacheNumberLabelOffset ;
var config float PathNodeShowRouteCacheNumberLabelDuration ;
// ----------------------------------------------------------------------- //
// DEPRECATED KF Cached version of movement Plugins
// ----------------------------------------------------------------------- //
var AIPluginMovement KfMovementPlugin ;
var AIPluginLeap KfLeapPlugin ;
var AIPluginStuckFix KfStuckFixPlugin ;
//var class<AIPluginMovement> WallWalkingPluginClass;
var AIPluginMovement KfWallWalkingPlugIn ;
//var bool bGoToEnemiesOutsideNavmesh;
//var Actor ActorFindingNavMeshPointCloseToo;
//var KFNavigationHandle MyKFNavigationHandle;
var MOVEMENT _PHASE _TYPE CurrentMovementPhase ;
// failure
var config Color Move _failure _type _none _color ;
var config Color Move _failure _type _no _nav _mesh _path _color ;
var config Color Move _failure _type _same _intermediate _point _too _many _times _color ;
var config Color MoveFailureTypeTargetOffNavMeshAndCanNotFindLocaitonNearThemICanMoveTo ;
var float DefaultMaxTimeAllowedToStayStuckBeforeSuicide ;
var float NoNavMeshPathMaxTimeAllowedToStayStuckBeforeSuicide ;
var float SameIntermediatePointToManyTimesMaxTimeAllowedToStayStuckBeforeSuicide ;
var float TargetOffNavMeshAndCanNotFindLocaitonNearThemICanMoveTooMaxTimeAllowedToStayStuckBeforeSuicide ;
var float SameIntermediatePointToManyTimesDurationAfterStartedMovingAgaintToStopStuckCheck ;
var float DefaultMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
var float NoNavMeshPathMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
var float SameIntermediatePointToManyTimesMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
var MOVE _FAILURE _TYPE TypeOfMovementStuckOn ;
// Pathnode icons
var Texture2D MovementPhaseTypePathNodeNormalMoveToIcon ;
var Texture2D MovementPhaseTypePathNodeMoveFailedIcon ;
var Texture2D MovementPhaseTypePathNodeMoveToPointIcon ;
var Texture2D MovementPhaseTypePathNodeRotateToFocusIcon ;
var Texture2D MovementPhaseTypePathNodeDelayMoveIcon ;
// end Pathnode icons
var Texture2D MovementPhaseTypeUnknownIcon ;
var Texture2D MovementPhaseTypeNavMeshNormalMoveToIcon ;
var Texture2D MovementPhaseTypeFinalDestMoveTowardsIcon ;
var Texture2D MovementPhaseTypeEMT _LOS _MoveTowardsIcon ;
var Texture2D MovementPhaseTypeMovingToNavMeshUsingPathNodesIcon ;
var Texture2D MovementPhaseTypeFALLBACK _REFUSED _TO _BE _EXPLOTIED _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon ;
var Texture2D MovementPhaseTypeFALLBACK _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon ;
var Texture2D TypeOfMovementStuckOnUnknownWhyIcon ;
var Texture2D TypeOfMovementStuckOnMOVE _FAILURE _TYPE _NO _NAV _MESH _PATHIcon ;
var Texture2D TypeOfMovementStuckOnMOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMESIcon ;
var Texture2D TypeOfMovementStuckOnMOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TOIcon ;
var Texture2D TypeOfMovementStuckOnLookingForBetterIntermediateLoc ;
var Texture2D TypeOfMovementStuckOnMoveToBetterIntermediate ;
var config bool bConfigShowMovePointsDebugInfo ;
var bool bShowMovePointsDebugInfo ;
var config bool bConfigShowHighDetailCombatMovementDebugInfo ;
var bool bShowHighDetailCombatMovementDebugInfo ;
var config bool bConfigShowVisualStuckZedDebugInfo ;
var bool bShowVisualStuckZedDebugInfo ;
var config color ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOff ;
var config color ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOn ;
var config bool bConfigShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState ;
var bool bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState ;
/** how far away to look for position on the nav mesh when I end up off the nav mesh, first try */
var float DistanceToCheckForClearPathOnNavMeshLocWhenOffNavMesh ;
/** how far away to look for position on the nav mesh when I end up off the nav mesh, 2nd try */
var float DistanceToCheckForNonExploitedOnNavMeshLocWhenOffNavMesh ;
/** how far away to look for position on the nav mesh when I end up on a dead end of the nav mesh */
var float DistanceToCheckForNonExploitedOnNavMeshLocWhenOnDeadEndOfNavMesh ;
var config color DefaultColorOfValidLocationWhenLookingForLocationsOnNavMesh ;
var config color DefaultColorOfSearchSphereWhenNoValidLocationsFoundWhenLookingForLocationsOnNavMesh ;
var config color ColorOfValidLocationFoundFor1stTryLookingForLocationsOnNavMesh ;
var config color ColorOfSearchSphereWhenNoValidLocationsFoundFor1stTryLookingForLocationsOnNavMesh ;
var config color ColorOfValidLocationFoundFor2ndTryLookingForLocationsOnNavMesh ;
var config color ColorOfSearchSphereWhenNoValidLocationsFoundFor2ndTryLookingForLocationsOnNavMesh ;
var config color ColorOfValidLocationFoundFor3rdTryLookingForLocationsOnNavMesh ;
var config color ColorOfSearchSphereWhenNoValidLocationsFoundFor3rdTryLookingForLocationsOnNavMesh ;
var config color ColorOfValidLocationFoundForLookingForLocationsOnNavMeshWhenOnDeadEndOfNavMesh ;
var config color ColorOfSearchSphereWhenNoValidLocationsFoundForTryLookingForLocationsOnNavMeshWhenOnDeadEndOfNavMesh ;
/** how far away to look for position on the nav mesh when my enemy ends up off the nav mesh, first try */
var float DistanceToCheckForClearPathOnNavMeshLocWhenEnemyIsOffNavMesh ;
/** how far away to look for position on the nav mesh when my enemy ends up off the nav mesh, 2nd try */
var float DistanceToCheckForNonExploitedOnNavMeshLocWhenEnemyIsOffNavMesh ;
/** How far away can the door be from the closest nav loc so that I can reach it */
var float GeneralGoalDistanceForMovingToDoor ;
/** If the enemy moves more than this distance away from where it was when we pathed, repath */
var float RecastEnemyRepathDistance ;
/** DropEdgeLeapVelocity */
var Vector DropEdgeLeapVelocity ;
/** MaxRangeToDropEdgeAllowedToLeadFrom */
var float MaxRangeToDropEdgeAllowedToLeadFrom ;
/** DistanceDownRangeToFocusForDropEdgeLeap */
var float DistanceDownRangeToFocusForDropEdgeLeap ;
var bool bShowLeapDownDebugArtifacts ;
var config bool bConfigShowLeapDownDebugArtifacts ;
var bool bShowDoorNavigationDebugArtifacts ;
var config bool bConfigShowDoorNavigationDebugArtifacts ;
var bool bShowDestructibleNavigationDebugArtifacts ;
var config bool bConfigShowDestructibleNavigationDebugArtifacts ;
var config float TimeToShowEdgeTypeForNavMeshPathting ;
/** determiner whether given AI actor respects numerous leader person/distance restrictions */
var bool bMindLeader ;
var vector LastLeaderPosOffset ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Tracking obstructions via NotifyHitWall
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var float LastNotifyHitWallTime ;
var actor LastHitWall ;
var vector LastWallHitNormal ;
var int HitWallCount ;
var bool LastPathFailTime ;
struct native sBlockedPathInfo
{
var ReachSpec BlockedReachSpec ;
var int BlockedCost ; // 0 = Completely blocked
var float BlockedTime ;
} ;
var array < sBlockedPathInfo > BlockedPathList ;
// Is the AI locked to a specific rotation
var bool IsRotationLocked ;
/ * * M y p a w n w a s s p a w n e d f r o m a n e m e r g e p o r t a l , s e t t o f a l s e b y A I C o m m a n d M o v e o r f i r s t t i m e U p d a t e R o t a t i o n
is called in PHYS _Spider * /
var bool bSpawnedByEmergeSpecialMove ;
/** Will call TargetedByPlayer event if true when calling AKFAIController::IsTargetedByPlayer() */
var bool bUseTargetedByPlayerEvent ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Bump testing - only in use if bSpecialBumpHandling is true ( see BumperSomewhereToGo , Tick )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var bool bBumpedThisFrame ;
var KFPawn LastBumper ;
var float CurBumpVal ;
var float BumpThreshold ;
var float BumpDecayRate ;
var float BumpGrowthRate ;
var config bool bDoNotBlockFriendlyAI ;
var config bool bSuperSpeedDoNotBlockFriendlyAI ;
var bool bSpecialBumpHandling ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Stuck checking
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** This last time this AI checked to see if it was stuck */
var float LastStuckCheckTime ;
/** How much time we've spent within close range to the player */
var float TotalStuckCheckCloseRangeTime ;
/** The last time we checked and found that we were within close range to the player */
var float LastStuckCheckCloseRangeTime ;
/** How often to check and see if this AI is stuck */
var ( StuckChecking ) float StuckCheckInterval ;
/** How slow a zed needs to be moving (squared) in the X/Y access to be considered possibly stuck */
var ( StuckChecking ) float StuckVelocityThreshholdSquared ;
/** How stuck this zed thinks it might be */
var float StuckPossiblity ;
/** The threshold to actually consider this zed stuck when it thinks it might be */
var ( StuckChecking ) float StuckPossiblityThreshhold ;
/** The zed thinks it is stuck and is trying to get unstuck */
var bool bTryingToGetUnstuck ;
/** The time at which this AI started thinking it was stuck */
var float LastStuckTime ;
/** Is this AI allowed to teleport to move closer? */
var bool bCanTeleportCloser ;
/** The minimum number of zeds remaining before teleportation is disabled for this zed */
var ( Teleport ) byte AIRemainingTeleportThreshold ;
/** The last time this AI checked to see if it can teleport closer to it's enemy */
var float LastTeleportCheckTime ;
/** The last time this AI teleported either because it was stuck or relocated */
var float LastTeleportTime ;
/** How often to check and see if this AI can relocate teleport*/
var ( Teleport ) float TeleportCheckInterval ;
/** How long to wait after teleporting to be able to teleport again*/
var ( Teleport ) float TeleportCooldown ;
/** How long this zed must be hidden before it can relocate teleport. Must be at least 5, because hidden checks can be delayed as much as 5 seconds*/
var ( Teleport ) float HiddenRelocateTeleportThreshold ;
/** How long this zed must ahve been alive before it can relocate teleport. */
var ( Teleport ) float PostSpawnRelocateTeleportCooldown ;
/** The last location this zed teleported from */
var vector LastTeleportLocation ;
/** This last time this AI finished a special move */
var float LastSpecialMoveEndTime ;
/** Where this AI was the last time we did a stuck check */
var vector LastStuckCheckLocation ;
/** How far this AI should have moved in 2d space since last check to not be considered stuck */
var float XYMoveStuckThresholdSquared ;
/** How far this AI should have moved in Z space since last check to not be considered stuck */
var float ZMoveStuckThresholdSquared ;
/** If I am closer than this distance to my enemy, then don't do any special stuck checking/handling */
var float StuckCheckEnemyDistThreshholdSquared ;
/** If I am closer than this distance to my enemy, then don't reduce collision size with other zeds */
var float NavigationBumpTeamCollisionThreshholdSquared ;
/** How long we've been falling with no z velocity */
var float FallingStuckNoZVelocityTime ;
/** Number of times a walk move has failed to apply velocity while Pawn->Acceleration is nonzero */
var int NumFailedLatentWalkMoves ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* StepAside
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Current step aside goal, for StepAside state */
var Pawn StepAsideGoal ;
/** Distance to attempt to step aside when bumping into other players */
var float MaxStepAsideDist ;
/** should we ignore stepasidefor calls? **/
var bool bIgnoreStepAside ;
var float LastFailedToFindStepAsideLocation ;
/** last pawn we bumped */
var Pawn LastBumpedPawn ;
/** last time we bumped someone */
var float LastBumpTime ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Combat
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** The range a zed needs to be from a player to begin considering his attack, also this is the MAX range I will ever be able to do a strike, any strike at, if I can not get this close I am SOL */
var float AttackRange ;
/** The next melee attack index we will attempt */
var byte PendingAnimStrikeIndex ;
/** The range a zed needs to be from a player to perform a melee attack */
var float StrikeRange ;
/** Used to get the StrikeRange as a value between the min and max PawnAnimInfoGroup's attacks */
var float StrikeRangePercentage ;
/** Used to get the StrikeRange as a value between the min and max PawnAnimInfoGroup's attacks */
var bool bCanStrikeThroughEnemies ;
/** Used by KFPawnAnimInfo to determine if an attack can be performed if legs are blocked (lunges, etc) */
var bool bIsBodyBlocked ;
/** Desired melee distance when attacking a door */
var float DoorMeleeDistance ;
/** Max height offset (normalized degrees) that zed is able to attack in melee from */
var float MaxMeleeHeightAngle ;
/** Last time a melee attack was performed */
var float LastAttackTime _Melee ;
/** Last time a we have taken damage */
var float LastDamageTime _Taken ;
/** Last time a melee attack decision was evaluated */
var float LastMeleeAttackDecisionTime ;
/** Last time an enemy was selected using SelectEnemy() */
var float LastSelectEnemyTime ;
/** Last time an enemy was set using SelectEnemy() */
var float LastSetEnemyTime ;
var float MinTimeBetweenStatusUpdates ;
var float MaxTimeBetweenStatusUPdates ;
/** Last time a latent move completed */
var float LastMoveFinishTime ;
/** Used to prevent changing my enemy too frequently (see SetEnemy()) */
var float MinTimeBetweenEnemyChanges ;
/** Last time we collided with a destructible */
var float LastDestructibleBumpTime ;
/** Enemies are pawns - use this for temporary actor enemies (like destructibles that need to be melee-attacked) */
var Actor ActorEnemy ;
/** Set when attacking a welded door */
var KFDoorActor DoorEnemy ;
/** Current melee target, if any - stored here for separation steering.. don't want a Zed to be pushed away from his melee target. */
var KFPawn MeleeTarget ;
/** Result of last enemy LineOfSightTo() check */
var bool bEnemyIsVisible ;
/** Result of last call to SeePlayer */
var transient bool bIsVisibleToEnemy ;
/** Result of previous call to SeePlayer */
var transient bool bWasVisibleToEnemy ;
/** When last enemy LineOfSightTo() check was done */
var float EnemyVisibilityTime ;
/** The last time an enemy switch occurred (only set if enemy was set to a new enemy) */
var float LastEnemySwitchTime ;
/** Who the enemy was for the last LineOfSightTo() check */
var Pawn CachedVisibleEnemy ;
/** Incoming projectile that I should evade away from (see ReceiveProjectileWarning()) */
var Projectile PendingEvadeProjectile ;
/** The chance that this zed has to dodge an incoming grenade */
var float EvadeGrenadeChance ;
/** If true, will continually rotate to enemy during non-rootmotion melee attacks */
var bool bUseDesiredRotationForMelee ;
/** The last time we tried to get a new move */
var float LastGetStrikeTime ;
/** How long we need to wait before picking another move */
var const float MaxGetStrikeTime ;
/** The movement system has done its best to get me to my enemy but can not get me any closer, the attack behavior needs to come up with something */
var bool bIamAsClosesToTheEnemyAsICanGet ;
/** Force frustration on, regardless of frustration threshold settings */
var ( Frustration ) bool bForceFrustration ;
/** The minimum number of zeds remaining before frustration mode (sprint) is activated */
var ( Frustration ) byte FrustrationThreshold ;
/** The amount of time to delay zeds from entering frustration mode after it's first detected */
var ( Frustration ) float FrustrationDelay ;
var float LastFrustrationCheckTime ;
/** Cached default to workaround an issue with frustration reset */
var transient bool bDefaultCanSprint ;
var float GoalDistanceWhenMovingToLocationForMeleeStrikeWhenEnemyIsOffNavMesh ;
struct native CooldownData
{
var ( ) name Tag ;
var ( ) float ActivationTime ;
var ( ) float Duration ;
} ;
/** List of active attack cooldown timers */
var array < CooldownData > CooldownTimers ;
/** Store info for an overall attack cooldown timer so we can throttle all attacks by this zed in certain circumstances */
var ( ) CooldownData OverallAttackCooldownTimer ;
/** Attack cooldown time to use when throttling all attacks by this zed in certain circumstances */
var ( ) float LowIntensityAttackCooldown ;
//var KFAiBallisticProjectileFireBehavior BallisticProjectileFireBehavior;
var KFAiDirectProjectileFireBehavior DirectProjectileFireBehavior ;
var KFAiLeapBehavior LeapBehavior ;
//var KFAiDestructibleInterActionBehavior DestructibleInterActionBehavior;
var const vector BaseShapeOfProjectileForCalc ;
/** The last time this AI did an AICommand_ShootBase attack. See AICommand_ShootBase for notes on why this was brought over here - Ramm */
var float LastShotTime ;
/** Set to true if we want to knockdown and possibly obliterate other zeds on bump */
var const bool bCanDoHeavyBump ;
/** The threshold of life to consider doing a special bump effect (ex. enraged or knockdown) */
var const int ZedBumpEffectThreshold ;
/** The chance of obliterating a zed on an enraged bump */
var const float ZedBumpObliterationEffectChance ;
2022-09-01 15:58:51 +00:00
// Only enabled while we didn't receive damage and we use Aggro for choosing Enemy
var bool CanForceEnemy ;
var Pawn ForcedEnemy ;
var Pawn LastForcedEnemy ;
var float ForcedEnemyLastTime ;
var float DamageRatioToChangeForcedEnemy ;
var float TimeCanRestartForcedEnemy ;
var float TimeCannotChangeFromForcedEnemy ;
2020-12-13 15:01:13 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Evasion / Blocking
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Struct containing info for forced evade directions and their chance of being selected */
struct native sForcedEvadeChanceInfo
{
/** Forward-Left */
var float FL ;
/** Forward-Right */
var float FR ;
} ;
/** Struct containing information for AI to evade danger */
struct native sDangerEvadeInfo
{
/** Projectile class to check against */
var name ClassName ;
/** Cooldown period, per-difficulty. Difficulty ranges are 0 to 3, Normal-Hard-Suicidal-HoE */
var array < float > Cooldowns ;
/** Maximum evade chance, per-difficulty */
var array < float > EvadeChances ;
/** Forced evade chances per difficulty */
var array < sForcedEvadeChanceInfo > ForcedEvadeChances ;
/** Minimum and maximum delay before AI reacts to a projectile of this type */
var array < vector2d > ReactionDelayRanges ;
/** Chances to block instead of evade */
var array < float > BlockChances ;
/** How much to multiply the chance by when in a solo game */
var float SoloChanceMultiplier ;
/** Last time a projectile of this type was evaded */
var transient float LastEvadeTime ;
} ;
/** Struct containing information for AI to evade on damage */
struct native sEvadeOnDamageInfo
{
var float Chance ;
var float DamagedHealthPctToTrigger ;
var sForcedEvadeChanceInfo ForcedEvadeChance ;
structdefaultproperties
{
Chance = 0. f
}
} ;
/** Settings used for danger evasion, set on a per-projectile or per-weapon basis */
var protected array < sDangerEvadeInfo > DangerEvadeSettings ;
/** What percentage of max health AccumulatedEvadeDamage needs to reach before an evade is triggered */
var transient sEvadeOnDamageInfo EvadeOnDamageSettings ;
/** How much damage we've accumulated towards our next evade trigger */
var transient protected int AccumulatedEvadeDamage ;
/** How much damage we've accumulated towards our next block trigger */
var transient protected int AccumulatedBlockDamage ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Aggro
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
struct native sFriendlyDamageInfo
{
var Controller DamagerController ;
var int Damage ;
} ;
/** Array containing all friendly AI that have damaged us */
var array < sFriendlyDamageInfo > FriendlyDamageHistory ;
var byte CurrentEnemysHistoryIndex ;
/** The percentage of damage that needs to be dealt to a zed to make this controller consider attacking the player */
var const float AggroPlayerHealthPercentage ;
/** The time it will take before we will reset our damage history from an attacking player */
var const float AggroPlayerResetTime ;
/** The time it will take before we will reset our damage history from another zed */
var float MinDistanceToAggroZed ;
/** The time it will take before we will reset our damage history from another zed */
var const float AggroZedResetTime ;
/** The percentage of damage that needs to be taken from another zed to make this controller consider attacking him */
var const float AggroZedHealthPercentage ;
/** How long to wait between aggro switches */
var const float AggroEnemySwitchWaitTime ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
AI Commands
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Allow/Disallow combat transitions (see CheckCombatTransition()) */
var bool bAllowCombatTransitions ;
/** If true, NPC is polling range to enemy, used in TickMeleeCombatDecision() */
var bool bIsProbingMeleeRangeEvents ;
/** Last time this NPC performed a taunt */
var float LastTauntTime ;
/** The time until we can perform another walking taunt */
var float RepeatWalkingTauntTime ;
var float NextTauntTime ;
var float NextRandomTauntTime ;
var config float TauntTeamFactor ;
var config Vector2D TauntTimeDelay ;
var config Vector2D RandomTauntTimeDelay ;
struct native InvalidAnchorItem
{
/** Anchor that was invalid */
var ( ) NavigationPoint InvalidNav ;
/** Time of path search that invalidated the anchor */
var ( ) float InvalidTime ;
} ;
var transient array < InvalidAnchorItem > InvalidAnchorList ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @ name GameConductor balance values
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Tracks the time that this controller first saw an enemy player */
var float TimeFirstSawPlayer ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Debugging Related
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var Vector ChargeLocation ;
/** String displayed with KFSceneCaptureDebugCam */
var string RecentActionInfo ;
/** Render AI debug info to screen */
var bool bDebug _DrawAIDebug ;
/** Draw line to Pawn's anchor when true */
var config bool bDebug _DrawAnchor ;
/** Draws sphere around Pawn when bPreparingMove is true */
var config bool bDebug _ShowPreparingMove ;
/** Draws the ranges for pending and current melee attacks */
var config bool bDebug _ShowStrikeRange ;
/** Draws the ranges for all melee attacks */
var config bool bDebug _ShowAllStrikeRange ;
/** Log AI_Log events to AIProfiler */
var config bool bDebug _LogToProfiler ;
/** Render line to intermediate goal and one to end-goal during MoveToward calls */
var config bool bDebug _DrawPath ;
/** Render overhead debug text */
var config bool bDebug _DrawOverheadInfo ;
/** Used for debugging StepAside command */
var Rotator Debug _StepRot ;
/** Used for debugging StepAside command */
var BasedPosition Debug _StepLoc ;
/** If true, AILog will use a unique log file for each NPC rather than dumping everything in Launch.log */
var config bool bUseUniqueAILogFile ;
/** Allows postrendering of AI debug info */
var config bool bDebug _PostRenderInfo ;
/** Renders Separation steering force between NPCs if enabled */
var config bool bDebug _DrawSeparationSteering ;
/** If true, NPC is in debug mode (AICommand_Debug is active command) */
var bool bHasDebugCommand ;
/** Used internally, set by KFAIDirector on possess if KFAIDirector.bDebugAllAI is true */
var bool bForceDebugCommand ;
/** Renders attack anim info if true */
var config bool bDebug _DrawAttackAnimInfo ;
/** Draw viewcone (used in seeplayer) */
var config bool bDebug _ShowViewCone ;
var KFPawn ChargeTarget ;
var string AIActionStatus ;
var config color Debug _TextColorOverride ;
var array < name > EnabledDebugTextCategories ;
/** Used for tracking down Zeds who might get stuck when spawning from certain volumes */
var KFSpawnVolume MySpawnVolume ;
var bool bDisablePartialPaths ;
/** The melee attack index we are currently doing ONLY USED FOR DEBUGGING */
var byte DebugCurrentStrikeIndex ;
/** If set to true, this AI is considered a player by KFAISpawnManager_Versus */
var bool bIsSimulatedPlayerController ;
var Vector LocationAtStartOfStuckCheck ;
var Vector LocationAtLastStuckCheck ;
var AICommand CmdTriggeringHardCoreStuckChecking ;
var AITickablePlugin PlugInTriggeringTriggeringHardCoreStuckChecking ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* AICommand History Debugging - useful in tracking down command related bugs . Enable through
* config file and each NPC will dump its history of AICommands and related info . If enabled ,
* history will be output when this controller is destroyed , the pawn dies , or optionally only
* when exiting the game .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Used when dumping all NPC AICommand history to log file */
var config bool bDebugCommandHistory ;
/** When true, each NPCs AICommand history is dumped to log file when exiting game */
var bool bDumpCommandHistoryOnExit ;
/** True if my AICommand history was already dumped to the log file */
var bool bDumpedCommandHistory ;
/** Used with DumpCommandHistory(), contains info about each AICommand instance executed (see bDebugCommandHistory) */
struct native KFAICmdHistoryItem
{
var class < AICommandBase > CmdClass ;
var string CmdName ;
var float TimeStamp ; // World time command was started
var float Duration ; // Total time spent in the command
var bool bAborted ; // Command was aborted (which is often fine and intentional depending on the context) (TODO:Change to bytes)
var bool bFailure ; // Command failed (this is also not necessarily bad, at times it's expected)
var bool bSuccess ; // Command succeeded
var string VerboseString ; // Optional extra info that can be added
} ;
var array < KFAICmdHistoryItem > KFAICommandHistory ;
cpptext
{
virtual void Initialize ( ) ;
virtual void JumpOverWall ( FVector WallNormal , AActor * Wall ) ;
virtual void StoreCommandHistory ( UGameAICommand * Cmd ) ;
virtual UReachSpec * PrepareForMove ( ANavigationPoint * NavGoal , UReachSpec * Path ) ;
virtual DWORD LineOfSightTo ( const AActor * Other , INT bUseLOSFlag = 0 , const FVector * chkLocation = NULL , UBOOL bTryAlternateTargetLoc = FALSE ) ;
virtual UBOOL AirControlFromWall ( float DeltaTime , FVector & RealAcceleration ) ;
virtual FVector DesiredDirection ( ) ;
virtual void BeginDestroy ( ) ;
virtual UBOOL Tick ( FLOAT DeltaTime , enum ELevelTick TickType ) ;
virtual UBOOL TickMeleeCombatDecision ( FLOAT DeltaTime ) { return FALSE ; }
virtual UBOOL ShouldOffsetCorners ( ) ;
virtual UBOOL ShouldUsePathLanes ( ) ;
virtual UBOOL ForceReached ( ANavigationPoint * Nav , const FVector & TestPosition ) ;
virtual void UpdatePawnRotation ( ) ;
virtual void UpdateLatentMoveDestination ( ) ;
virtual void AdjustFromWall ( FVector Hitnormal , AActor * HitActor ) ;
virtual void RouteCache _Empty ( ) ;
virtual void RouteCache _AddItem ( ANavigationPoint * Nav ) ;
virtual void RouteCache _InsertItem ( ANavigationPoint * Nav , INT Idx ) ;
virtual void RouteCache _RemoveItem ( ANavigationPoint * Nav ) ;
virtual void RouteCache _RemoveIndex ( INT Index , INT Count ) ;
virtual void StoreKFAICommandHistory ( UGameAICommand * Cmd ) ;
virtual UBOOL DirectPathToGoal ( FVector TargetLoc , FLOAT TargetGroundOffset , FCheckResult & HitList , FVector FromLocation = FVector ( 0 , 0 , 0 ) , UBOOL bCheckActors = true ) ;
virtual UBOOL ShouldIgnoreNavigationBlockingFor ( const AActor * Other ) ;
virtual FRotator SetRotationRate ( FLOAT deltaTime ) ;
virtual INT ControllerModifyCostForReachSpec ( UReachSpec * Spec , INT Cost ) ;
virtual void PostPhysWalking ( FLOAT DeltaTime ) ;
virtual void MoveToward ( AActor * goal , AActor * viewfocus , FLOAT DesiredOffset , UBOOL bStrafe , UBOOL bShouldWalk ) ;
// Debugging
# if _ _TW _AIDEBUGGING _
virtual void FailMove ( const FString & Reason ) ;
# endif
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* General native function declarations
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * E x p e r i m e n t a l - i f r e p e a t e d l y b u m p e d b y a m o v i n g Z e d i n a v e r y s h o r t t i m e s p a n , t h i s w i l l t e m p o r a r i l y
turn off the collision for the bumped - into Zed to let the moving Zed pass through . GOW has similar
code to let players pass through blocking friendly NPC teammates . * /
native function bool BumperSomewhereToGo ( ) ;
native function bool LineBlockedByActor ( vector Start , vector End , ReachSpec Spec , actor Actor ) ;
native function bool IsPawnBlockingLine ( vector Start , vector End , optional float MinCollisionHeight ) ;
//native function bool CanJumpTo( Actor JumpToActor );
native function vector ComputeTrajectoryByTime ( vector StartPosition , vector EndPosition , float EndTime ) ;
native function CleanUp ( optional bool bBeingDestroyed ) ;
/** Aborts a command and any children if it has any. */
native function bool AbortCommand ( GameAICommand AbortCmd , optional class < GameAICommand > AbortClass ) ;
/** Push a new command on to the stack */
native function PushCommand ( GameAICommand NewCommand ) ;
/** Pop a command from the stack */
native function PopCommand ( GameAICommand ToBePoppedCommand ) ;
/** AI should ignore notifications that would alter behavior - ie. seeing/hearing enemies, getting shot, etc */
native function bool IgnoreNotifies ( ) const ;
/** Returns true if CheckPawn is currently moving away from my pawn, optionally at MinSpeed */
native function bool IsPawnMovingAwayFromMe ( Pawn CheckPawn , optional float MinSpeed ) ;
/** Returns a KFPawn if there is one blocking the path to EnemyPawn */
native function Pawn GetPawnBlockingPathTo ( Pawn EnemyPawn , optional bool bTestTeam , optional bool bCheckVisible ) ;
/** Lock the AI pawn rotation to a specific rotation, to unlock the pawn pass in zero */
native function LockPawnRotationTo ( Rotator NewRotation ) ;
/** Unlock the AI pawn's rotation */
native function UnlockPawnRotation ( ) ;
/** Checks to see if our line of fire is blocked by friendlies. Return True if it is blocked */
final native function bool IsFriendlyBlockingFireLine ( vector FireStart ) ;
/** Used for leading Zed's target */
native function vector CalcAimLocToHit ( vector AimSpot , vector StartFireLoc , float inSpeed , float inMaxSpeed , optional Vector LeadTargetVelocity ) ;
/** Used for leading Zed's target */
static native function float EstimateProjectileTimeToTarget ( float Distance , float StartSpeed , float MaxSpeed ) ;
static native function bool FastActorTrace ( Actor OriginTestActor , vector TraceEnd , optional vector TraceStart , optional vector BoxExtent , optional bool bTraceComplex ) ;
static native function Actor ActorBlockTest ( Actor OriginTestActor , vector TraceEnd , optional vector TraceStart , optional vector BoxExtent , optional bool bTraceActors , optional bool bTraceComplex ) ;
native function bool TestTrace ( vector TraceEnd , optional vector TraceStart ) ;
native function bool IsPawnInFireLine ( Pawn CheckPawn , vector FireStart , vector FireLine ) ;
native final function bool SuggestNewWanderPoint ( out vector out _NewMovePt , vector TryThisDirFirst , optional float MoveDist = 1024. f ) ;
/** Queries the PRI and returns our current team index */
simulated native function byte GetTeamNum ( ) ;
protected native function bool IsLookingAtWall ( optional float CheckDist = 768. f ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initializtion , Pawn Possession , and Destruction
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Native Init / Destroy related function declarations
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Clean up all the subobjects for the controller */
native function CleanUpOnDestroy ( ) ;
/** TODO: Make this a native function */
event SetChargeLocation ( Actor Victim , Vector VictimLocation ) ;
/ * * T h i s m i g h t b e u s e f u l i f y o u w a n t a Z e d t o b e n o t i f i e d w h e n t h e p l a y e r i s a i m i n g a t i t . I f
bUseTargetedByPlayerEvent is true for this KFAIC , when you call IsTargetedByPlayer ( ) , this
event will be called . * /
event TargetedByPlayer ( KFPawn InPlayer ) ;
event PostBeginPlay ( )
{
Super . PostBeginPlay ( ) ;
//BallisticProjectileFireBehavior = new(self) class'KFAiBallisticProjectileFireBehavior';
//BallisticProjectileFireBehavior.MyAiCtrl = self;
DirectProjectileFireBehavior = new ( self ) class 'KFAiDirectProjectileFireBehavior' ;
DirectProjectileFireBehavior . MyAiCtrl = self ;
LeapBehavior = new ( self ) class 'KFAiLeapBehavior' ;
LeapBehavior . MyAiCtrl = self ;
//DestructibleInterActionBehavior = new(self) class'KFAiDestructibleInterActionBehavior';
//DestructibleInterActionBehavior.MyAiCtrl = self;
bIamAsClosesToTheEnemyAsICanGet = false ;
//BallisticProjectileFireBehavior.SetUp();
DirectProjectileFireBehavior . SetUp ( ) ;
LeapBehavior . SetUp ( ) ;
// Set our frustration delay
FrustrationDelay = RandRange ( default . FrustrationDelay , default . FrustrationDelay * 2 ) ; //+ Rand(3) + (2.0*fRand());
//Set initial value so things spawning dependent on this have a safe starting point
LastEnemySightedTime = WorldInfo . TimeSeconds ;
}
/** Called when this controller has possessed inPawn */
event Possess ( Pawn inPawn , bool bVehicleTransition )
{
local KFAIDirector Director ;
` AILog( GetFuncName() $ "() possessing pawn " $ inPawn, 'AIController' );
super . Possess ( inPawn , bVehicleTransition ) ;
// Currently only enabling SeePlayer during latent moves in AICommand_MoveToGoal
Disable ( 'SeePlayer' ) ;
if ( Steering != none )
{
InitSteering ( ) ;
}
Pawn . bCanJump = true ;
Pawn . bCanWalkOffLedges = true ;
MyKFGameInfo = KFGameInfo ( WorldInfo . Game ) ;
if ( MyKFGameInfo != none )
{
// Register new possession with AIDirector
Director = MyKFGameInfo . GetAIDirector ( ) ;
if ( Director != none )
{
MyAIDirector = Director ;
MyAIDirector . NotifyNewPossess ( self ) ;
}
}
// Transition to the idle state.
BeginCombatCommand ( None , "Possessed" ) ;
// Init AI Steering object in 1-3 seconds
SetTimer ( RandRange ( 1 , 3 ) , false , nameof ( StartSteering ) ) ;
SetTimer ( RandRange ( MinTimeBetweenStatusUpdates , MaxTimeBetweenStatusUpdates ) , false , nameof ( Timer _EvaluateStatus ) , self ) ;
// Set up any desired debug related options.
SetupDebug ( ) ;
// Give myself a PlayerReplicationInfo if bEnableGameAnalytics is true so I'll be viewable as if I'm a player when playing
// back recorded gamestats files.
if ( ! bIsPlayer && MyKFGameInfo . bEnableGameAnalytics && ! bDeleteMe && ( WorldInfo . NetMode != NM _Client ) )
{
InitPlayerReplicationInfo ( ) ;
}
bShowVisualStuckZedDebugInfo = MyAIDirector . bShowVisualStuckZedDebugInfo ;
bShowMovePointsDebugInfo = MyAIDirector . bShowMovePointsDebugInfo ;
bShowHighDetailCombatMovementDebugInfo = MyAiDirector . bShowHighDetailCombatMovementDebugInfo ;
bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState = MyAIDirector . bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState ;
bShowLeapDownDebugArtifacts = MyAIDirector . bShowLeapDownDebugArtifacts ;
bShowDoorNavigationDebugArtifacts = MyAIDirector . bShowDoorNavigationDebugArtifacts ;
bShowDestructibleNavigationDebugArtifacts = MyAiDirector . bShowDestructibleNavigationDebugArtifacts ;
if ( bUsePluginsForMovement )
{
//KfMovementPlugin = KFAIPluginMovement_Recast(MovementPlugin);
//KfLeapPlugin = KFAIPluginLeap(LeapPlugin);
//KfStuckFixPlugin = KFAIPluginStuckFix(StuckFixPlugin);
}
// Give our pawn a pending attack upon on possesion
if ( MyKFPawn != none && MyKFPawn . PawnAnimInfo != none )
{
AttackRange = MyKFPawn . PawnAnimInfo . GetAttackRange ( self ) ;
}
}
/** Used to initialize a pending attack, and choose our next attack after once we've started the pending attack */
function UpdatePendingStrike ( )
{
local KFPawnAnimInfo KFPAI ;
if ( MyKFPawn == none || MyKFPawn . PawnAnimInfo == none )
{
return ;
}
DebugCurrentStrikeIndex = PendingAnimStrikeIndex ;
KFPAI = MyKFPawn . PawnAnimInfo ;
// Make sure we always have an attack range
if ( AttackRange == 0 )
{
AttackRange = KFPAI . GetAttackRange ( self ) ;
}
PendingAnimStrikeIndex = KFPAI . ChooseNextStrike ( MyKFPawn , Enemy ) ;
// Make sure our attack name matches the PawnAnimInfo's tag
if ( PendingAnimStrikeIndex == 255 )
{
StrikeRange = 0 ;
return ;
}
// Update the range we want to get to perform our strike
UpdateStrikeRange ( ) ;
` AILog( GetFuncName() $ "() using AttackRange of " $ AttackRange $ " for attack polling.", 'Command_Attack_Melee' );
return ;
}
/ * * F i n d t h e m i n / m a x r a n g e o f e a c h T a g ( a t t a c k c a t e g o r y ) d e f i n e d i n m y K F P a w n _ A n i m G r o u p . T h i s i s p e r - T a g ,
* not per attack animation , and is used to pre - determine if any of the attacks for a Tag is within range . * /
function UpdateStrikeRange ( )
{
local float CollisionRadius ;
if ( PendingAnimStrikeIndex != 255 )
{
CollisionRadius = MyKFPawn . CylinderComponent . CollisionRadius ;
StrikeRange = MyKFPawn . PawnAnimInfo . GetMedianStrikeRange ( PendingAnimStrikeIndex , StrikeRangePercentage , CollisionRadius ) ;
` AILog( GetFuncName() $ "() using StrikeRange of " $ StrikeRange $ " for attack polling.", 'Command_Attack_Melee' );
}
}
/ * T e m p o r a r i l y b l o c k t h e s p e c i f i e d N a v i g a t i o n P o i n t i f i t ' s i n m y c u r r e n t p a t h . T h i s w i l l o n l y a f f e c t m y o w n n a v i g a t i o n ,
* it ' s not globally blocked for all NPCs . * /
event bool CreateTemporaryBlockedPath ( NavigationPoint Nav )
{
local int i , x , PointIdx ;
` AILog( GetFuncName() $ "() for " $ Nav, 'PathWarning' );
for ( i = 0 ; i < RouteCache . Length ; ++ i )
{
if ( RouteCache [ i ] == Nav )
{
for ( x = 0 ; x < Nav . PathList . Length ; ++ x )
{
//`AILog( GetFuncName()$" Nav to block: "$Nav$", Anchor: "$Pawn.Anchor$"... checking current end actor "$Nav.PathList[x].End.Actor, 'PathWarning' );
DebugLogRoute ( ) ;
PointIdx = i + 1 ;
if ( RouteCache . Length > PointIdx && Nav . PathList [ x ] . End . Actor == RouteCache [ PointIdx ] )
{
` AILog( GetFuncName() $ "() Found a match, calling AddBlockedReachSpec for spec " $ Nav.PathList[x] $ " on nav point " $ RouteCache[PointIdx] );
//`Log( GetFuncName()$"() Found a match, calling AddBlockedReachSpec for spec "$Nav.PathList[x]$" on nav point "$RouteCache[i+1] );
` RecordAIBlockedPath( self, Nav, RouteCache[PointIdx], "TestBlockedPath from " $ Nav $ " to " $ RouteCache[PointIdx] );
` RecordAIRedirectedPath( self, IntermediateMoveGoal, "[HPO]Path:" $ IntermediateMoveGoal $ " and " $ RouteCache[PointIdx] );
AddBlockedReachSpec ( Nav . PathList [ x ] , 10000000 ) ;
return true ;
}
}
}
}
return false ;
}
/ * T e m p o r a r i l y b l o c k t h e s p e c i f i e d N a v i g a t i o n P o i n t i f i t ' s i n m y c u r r e n t p a t h . T h i s w i l l o n l y a f f e c t m y o w n n a v i g a t i o n ,
* it ' s not globally blocked for all NPCs . * /
function bool CreateTemporaryBlockedReach ( NavigationPoint Nav , ReachSpec Reach )
{
` AILog( GetFuncName() $ "() for " $ Nav@Reach , 'PathWarning' );
if ( Reach != none && Reach . End . Actor == Nav )
{
//`AILog( GetFuncName()$" Nav to block: "$Nav$", Anchor: "$Pawn.Anchor$"... checking current end actor "$Nav.PathList[x].End.Actor, 'PathWarning' );
DebugLogRoute ( ) ;
` AILog( GetFuncName() $ "() Found a match, calling AddBlockedReachSpec for spec " $ Reach $ " to nav point " $ Nav );
//`Log( GetFuncName()$"() Found a match, calling AddBlockedReachSpec for spec "$Reach$" to nav point "$Nav );
` RecordAIBlockedPath( self, Nav, NavigationPoint(Reach.End.Actor), "TestBlockedPath from " $ Nav $ " to " $ Reach.End.Actor );
` RecordAIRedirectedPath( self, IntermediateMoveGoal, "[HPO]Path:" $ IntermediateMoveGoal $ " and " $ RouteCache[1] );
AddBlockedReachSpec ( Reach , 10000000 ) ;
return true ;
}
return false ;
}
function Timer _EvaluateStatus ( )
{
if ( ` TimeSince(LastNotifyHitWallTime) > 3.f )
{
LastHitWall = none ;
LastWallHitNormal = vect ( 0. f , 0. f , 0. f ) ;
HitWallCount = 0 ;
}
if ( bHasDebugCommand || MyKFPawn == none || MyKFPawn . Health <= 0 || MyKFPawn . IsDoingSpecialMove ( ) )
{
return ;
}
if ( Enemy == None || ! Enemy . IsAliveAndWell ( ) )
{
FindNewEnemy ( ) ;
}
SetTimer ( RandRange ( MinTimeBetweenStatusUpdates , MaxTimeBetweenStatusUpdates ) , false , nameof ( Timer _EvaluateStatus ) , self ) ;
}
/** Called after PostBeginPlay(), and sets the initial state for this controller, if not already in that state */
simulated event SetInitialState ( )
{
if ( GetStateName ( ) != InitialState )
{
GotoState ( InitialState ) ;
}
}
/** Returns the default AI Command class for this controller */
function class < AICommand > GetDefaultCommand ( )
{
return DefaultCommandClass ;
}
/** Returns whether we should exit combat and return to the idle state, generally because we don't have any more enemies */
function bool ShouldReturnToIdle ( )
{
local bool bShouldReturn ;
bShouldReturn = ( ! bMovingToGoal && Enemy == None ) ;
return bShouldReturn ;
}
function SetBestAnchor ( )
{
local NavigationPoint BestAnchor ;
local float Dist ;
BestAnchor = Pawn . GetBestAnchor ( Pawn , Pawn . Location , true , false , Dist ) ;
if ( BestAnchor != none )
{
Pawn . SetAnchor ( BestAnchor ) ;
}
}
/** So far, no KF2 NPCs really need to return to idle, but leaving this in for now. Can override in subclasses */
function ReturnToIdle ( ) { }
/** Used to add path constraints that we want on all the time */
function AddBasePathConstraints ( ) { }
/** Returns current health percentage */
simulated final function float GetHealthPercentage ( )
{
local Pawn P ;
P = Pawn ;
if ( P == none )
{
return 1. f ;
}
else
{
return ( P . Health / float ( P . HealthMax ) ) ;
}
}
function PawnDied ( Pawn InPawn )
{
if ( MyKFPawn != none )
{
` AILog( GetFuncName() $ "() InPawn: " $ InPawn $ " - Lifetime was " $ ` TimeSince ( MyKFPawn . CreationTime ) , 'Damage' ) ;
}
else
{
` AILog( GetFuncName() $ "() InPawn: " $ InPawn, 'Damage' );
}
if ( bDumpCommandHistoryOnExit )
{
if ( bDebugCommandHistory && ! bDumpedCommandHistory )
{
bDumpedCommandHistory = true ;
DumpCommandHistory ( ) ;
}
}
if ( MyAIDirector != none )
{
// Unregister NPC with the AIDirector
MyAIDirector . UnregisterAIMember ( self ) ;
}
if ( MyKFPawn != none )
{
MyKFPawn = None ;
}
if ( KFPawn _Monster ( Pawn ) != none && KFGameInfo ( WorldInfo . Game ) != none &&
TimeFirstSawPlayer >= 0 )
{
KFGameInfo ( WorldInfo . Game ) . GameConductor . HandleZedKill ( FMax ( ` TimeSince(TimeFirstSawPlayer),0.0));
// Set this so we know we already logged a kill for our pawn
TimeFirstSawPlayer = - 1 ;
}
Super . PawnDied ( InPawn ) ;
}
function bool AttemptToTeleport ( optional float CheckRadius = 512 )
{
local NavigationPoint ResNav ;
` AILog( GetFuncName() );
ResNav = class 'KFPathnode' . static . GetNearestValidFloorNavWithinRadiusToPawn ( Pawn , CheckRadius ) ;
if ( ResNav != none && TeleportToLocation ( Resnav . Location , Pawn . Rotation ) )
{
return true ;
}
return false ;
}
event bool TeleportToLocation ( vector NewLoc , rotator NewRot , optional bool bGiveNavZBuffer = true , optional bool bCancelCombat = true , optional bool bCancelMovement = true )
{
local vector OldLocation ;
//debug
` AILog( GetFuncName()@bCancelCombat@bCancelMovement@NewLoc, 'PathError' );
//DrawDebugLine(Pawn.Location,NewLoc,255,0,0,TRUE);
//DrawDebugCoordinateSystem(NewLoc,rot(0,0,0),100.f,TRUE);
if ( Pawn != None )
{
OldLocation = Pawn . Location ;
}
if ( bGiveNavZBuffer )
{
NewLoc . Z += 32. f ;
}
if ( Pawn != None &&
Pawn . SetLocation ( NewLoc ) )
{
LastTeleportLocation = OldLocation ;
LastTeleportTime = WorldInfo . TimeSeconds ;
if ( Pawn . Physics == PHYS _RigidBody )
{
Pawn . Mesh . SetRBPosition ( NewLoc ) ;
}
RouteCache _Empty ( ) ;
if ( bCancelMovement )
{
ClearMovementInfo ( false ) ;
}
if ( bCancelCombat && CommandList != None )
{
BeginCombatCommand ( None , "Teleported" ) ;
}
Pawn . SetRotation ( NewRot ) ;
Pawn . SetMovementPhysics ( ) ;
AIZeroMovementVariables ( ) ;
return true ;
}
//debug
` AILog( "TELEPORT FAILED?!", 'PathWarning' );
return false ;
}
/ * * C l e a n u p a l l i n t e r n a l o b j e c t s a n d r e f e r e n c e s w h e n t h e A I i s d e s t r o y e d
TBD : Look into using Unpossessed ( ) to do this ! * /
simulated event Destroyed ( )
{
local KFDebugLines KFDL ;
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
if ( KFDL != none )
{
KFDL . RemoveOwnedDebugLines ( name ) ;
KFDL . RemoveOwnedDebugSpheres ( name ) ;
KFDL . RemoveOwnedDebugText3D ( name ) ;
// SetPostRendering( false );
}
AbortCommand ( CommandList ) ;
MyKFPawn = None ;
if ( MyAIDirector != none )
{
// Unregister NPC with the AIDirector
MyAIDirector . UnregisterAIMember ( self ) ;
}
// Get rid of steering object.
if ( Steering != None )
{
Steering . OnOwnerDestroy ( ) ;
Steering = None ;
}
// Unregister NPC with the AIDirector
if ( MyAIDirector != none )
{
MyAIDirector . UnregisterAIMember ( self ) ;
}
super . Destroyed ( ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Combat
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Native combat function declarations
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Do I have an enemy within the specified distance range? */
final native function bool HasEnemyWithinDistance ( float Distance , optional out Pawn out _EnemyPawn , optional bool bExact ) ;
/** If enemy or my pawn is moving, will return the time (seconds) until and distance (UU) the two pawns will be closest along their trajectories */
native function GetClosestTimeAndDistToPawn ( KFPawn CheckPawn , out float out _ClosestTime , out float out _ClosestDist ) ;
/** Have I moved closely enough to my enemy? */
native function bool IsCloseEnoughToEnemy ( ) ;
/** Is test location within melee range? */
native function bool InMeleeRange ( const Vector TestLocation , optional name AttackTag ) ;
native function bool InAttackTagRange ( const name AttackTag , const Vector TestLocation ) ;
native function bool InAnyAttackTagRange ( const Vector TestLocation , out name outAttackTag ) ;
/** Is test location within charge attack range? */
native function bool InChargeRange ( const Vector TestLocation ) ;
/** Enable polling for medium/long range attack range checking */
final native simulated final function EnableMeleeRangeEventProbing ( ) ;
/** Disable polling for medium/long range attack range checking */
final native simulated final function DisableMeleeRangeEventProbing ( ) ;
/** Is medium/long range attack range checking currently enabled? */
final native simulated final function bool IsMeleeRangeEventProbingEnabled ( ) const ;
/** Calls StopLatentExecution() if currently performing a latent moveto or movetoward */
native function StopAllLatentMoveExecution ( ) ;
/** Am I being targeted by a player (optionally returns first found) */
native function bool IsTargetedByPlayer ( optional out KFPawn outThreateningPlayer ) ;
2022-09-01 15:58:51 +00:00
function Pawn FindForcedEnemy ( )
{
local KFGameInfo KFGI ;
local KFPlayerController _WeeklySurvival KFPC _WS ;
local class < KFPawn _Monster > MyMonster ;
KFGI = KFGameInfo ( WorldInfo . Game ) ;
if ( KFGI != none && KFGI . OutbreakEvent != none && KFGI . OutbreakEvent . ActiveEvent . bVIPGameMode )
{
MyMonster = class < KFPawn _Monster > ( Pawn . Class ) ;
// If this monster is included on the vip targetting, force VIP as enemy
if ( KFGI . OutbreakEvent . ActiveEvent . VIPTargetting . Find ( MyMonster ) != INDEX _NONE )
{
foreach WorldInfo . AllControllers ( class 'KFPlayerController_WeeklySurvival' , KFPC _WS )
{
if ( KFPC _WS . VIPGameData . IsVIP && KFPC _WS . Pawn . IsAliveAndWell ( ) && KFPC _WS . Pawn . CanAITargetThisPawn ( self ) )
{
return KFPC _WS . Pawn ;
}
}
}
}
return none ;
}
2020-12-13 15:01:13 +00:00
/ * *
* Originally from KF1 , KFMonsterController . uc , added check to take # of Zeds targeting
* the threat into account .
* /
event bool FindNewEnemy ( )
{
local Pawn PotentialEnemy , BestEnemy ;
local float BestDist , NewDist ;
local int BestEnemyZedCount ;
local int PotentialEnemyZedCount ;
local bool bUpdateBestEnemy ;
if ( Pawn == none )
{
return false ;
}
BestEnemy = none ;
2022-09-01 15:58:51 +00:00
if ( BestEnemy == none )
{
foreach WorldInfo . AllPawns ( class 'Pawn' , PotentialEnemy )
2020-12-13 15:01:13 +00:00
{
2022-09-01 15:58:51 +00:00
if ( ! PotentialEnemy . IsAliveAndWell ( ) || Pawn . IsSameTeam ( PotentialEnemy ) ||
! PotentialEnemy . CanAITargetThisPawn ( self ) )
2020-12-13 15:01:13 +00:00
{
2022-09-01 15:58:51 +00:00
continue ;
2020-12-13 15:01:13 +00:00
}
2022-09-01 15:58:51 +00:00
NewDist = VSizeSq ( PotentialEnemy . Location - Pawn . Location ) ;
if ( BestEnemy == none || BestDist > NewDist )
2020-12-13 15:01:13 +00:00
{
2022-09-01 15:58:51 +00:00
// New best enemies do not care about the number of zeds around us yet
BestEnemyZedCount = INDEX _None ;
2020-12-13 15:01:13 +00:00
bUpdateBestEnemy = true ;
}
2022-09-01 15:58:51 +00:00
else
{
// Only update NumZedsTargetingBestEnemy if it's a new best enemy and the best enemy is further
if ( BestEnemyZedCount == INDEX _None )
{
// Cache BestEnemyZedCount so we don't need to calculate it again
BestEnemyZedCount = NumberOfZedsTargetingPawn ( BestEnemy ) ;
}
2020-12-13 15:01:13 +00:00
2022-09-01 15:58:51 +00:00
PotentialEnemyZedCount = NumberOfZedsTargetingPawn ( PotentialEnemy ) ;
if ( PotentialEnemyZedCount < BestEnemyZedCount )
{
BestEnemyZedCount = PotentialEnemyZedCount ;
bUpdateBestEnemy = true ;
}
}
if ( bUpdateBestEnemy )
{
BestEnemy = PotentialEnemy ;
BestDist = NewDist ;
bUpdateBestEnemy = false ;
}
2020-12-13 15:01:13 +00:00
}
}
2022-09-01 15:58:51 +00:00
2020-12-13 15:01:13 +00:00
if ( Enemy != none && BestEnemy != none && BestEnemy == Enemy )
{
return false ;
}
if ( BestEnemy != none )
{
ChangeEnemy ( BestEnemy ) ;
return HasValidEnemy ( ) ;
}
return false ;
}
/** Returns true if our new enemy has enough zeds already attacking him, for him to be our target */
function bool GetHasAcceptableEnemyCount ( Pawn NewEnemy )
{
local Float NumZedsTargetingCurrentEnemy , NumZedsTargetingNewEnemy ;
local float RatioOfZedsOldEnemyToNewEnemy ;
// Added some rudimentary checks to help zeds prioritize when to switch targets - Ramm
if ( Enemy != none )
{
// Check for zeds targeting the enemies out to 10 meters
NumZedsTargetingCurrentEnemy = NumberOfZedsTargetingPawn ( Enemy , false , 1000 ) ;
NumZedsTargetingNewEnemy = NumberOfZedsTargetingPawn ( NewEnemy , false , 1000 ) ;
// New enemy already has a full dance card, stick with current enemy
if ( NumZedsTargetingNewEnemy >= 3 )
{
` AILog(GetFuncName() $ " New enemy already has a full dance card, stick with current enemy", 'SetEnemy');
return false ;
}
// Nobody nearby targeting the new enemy, go for it
if ( NumZedsTargetingNewEnemy < 1 )
{
` AILog(GetFuncName() $ " accepting " $ NewEnemy $ " because it's closer than our current, and nobody nearby targeting the new enemy", 'SetEnemy');
return true ;
}
else
{
RatioOfZedsOldEnemyToNewEnemy = NumZedsTargetingCurrentEnemy / NumZedsTargetingNewEnemy ;
// Don't switch to the new enemy if there are too few zeds targeting the current one
if ( RatioOfZedsOldEnemyToNewEnemy <= 0.5 )
{
` AILog(GetFuncName() $ " not accepting " $ NewEnemy $ " because there are too few zeds targeting the current one", 'SetEnemy');
return false ;
}
}
}
return true ;
}
/** Set a new enemy, returns false potential enemy is a player and AI is set to ignore players */
event bool SetEnemyToZed ( Pawn NewEnemy )
{
local float NewEnemyDist , TimeSinceLastEnemyChange ;
` AILog( GetFuncName() $ "(), desired new enemy: " $ NewEnemy, 'SetEnemy' );
// Reject potential enemy if it's invalid or not on our team, or if it's already my current enemy, or if my pawn is dead or invalid
if ( NewEnemy == none || ! NewEnemy . IsAliveAndWell ( ) ||
Pawn == none || ! Pawn . IsAliveAndWell ( ) ||
Pawn == NewEnemy || ! NewEnemy . CanAITargetThisPawn ( self ) )
{
return false ;
}
// Evaluate whether enough time has passed since I last changed my enemy (TODO: Need to be able to override this in some situations)
TimeSinceLastEnemyChange = ` TimeSince(LastSetEnemyTime);
if ( TimeSinceLastEnemyChange < MinTimeBetweenEnemyChanges )
{
` AILog( GetFuncName() $ "() not evaluating because not enough time has passed (" $ TimeSinceLastEnemyChange $ ") since last SetEnemy() call.", 'SetEnemy' );
return false ;
}
// Accept new enemy if its distance (plus some padding) is closer than enemy distance
NewEnemyDist = VSize ( NewEnemy . Location - Pawn . Location ) ;
if ( NewEnemyDist < MinDistanceToAggroZed )
{
if ( ! GetHasAcceptableEnemyCount ( NewEnemy ) )
{
return false ;
}
` AILog( GetFuncName() $ "() accepting " $ NewEnemy $ " because Enemy (" $ Enemy $ ") dist is > 1.2 * NewEnemy dist", 'SetEnemy' );
ChangeEnemy ( NewEnemy ) ;
return true ;
}
return false ;
}
/** Set a new enemy, returns false potential enemy is a player and AI is set to ignore players */
event bool SetEnemy ( Pawn NewEnemy )
{
local float EnemyDistSq , NewEnemyDistSq , TimeSinceLastEnemyChange ;
` AILog( GetFuncName() $ "(), desired new enemy: " $ NewEnemy, 'SetEnemy' );
// Reject potential enemy if it's invalid or not on our team, or if it's already my current enemy, or if my pawn is dead or invalid
if ( NewEnemy == none || ! NewEnemy . IsAliveAndWell ( ) || Pawn . IsSameTeam ( NewEnemy ) ||
Pawn == none || ! Pawn . IsAliveAndWell ( ) || ! NewEnemy . CanAITargetThisPawn ( self ) )
{
return false ;
}
if ( Enemy != none && NewEnemy == Enemy )
{
// Was attacking a door to get to enemy, but enemy has now become reachable
if ( DoorEnemy != none && LineOfSightTo ( Enemy ) )
{
ChangeEnemy ( NewEnemy ) ;
return true ;
}
return false ;
}
// Ignore cooldown time and accept the new enemy if I absolutely need one
if ( Enemy == none || ! Enemy . IsAliveAndWell ( ) )
{
` AILog( GetFuncName() $ "(), accepting new enemy " $ NewEnemy $ " because current enemy is none or dead", 'SetEnemy' );
ChangeEnemy ( NewEnemy ) ;
return true ;
}
// Evaluate whether enough time has passed since I last changed my enemy (TODO: Need to be able to override this in some situations)
TimeSinceLastEnemyChange = ` TimeSince(LastSetEnemyTime);
if ( TimeSinceLastEnemyChange < MinTimeBetweenEnemyChanges )
{
` AILog( GetFuncName() $ "() not evaluating because not enough time has passed (" $ TimeSinceLastEnemyChange $ ") since last SetEnemy() call.", 'SetEnemy' );
return false ;
}
// Give any AICommands on the stack an opportunity to reject changing my enemy.
if ( CachedAICommandList != none && ! CachedAICommandList . CanChangeEnemy ( NewEnemy ) )
{
` AILog( GetFuncName() $ "() rejecting " $ NewEnemy $ " because an AICommand rejected it", 'SetEnemy' );
return false ;
}
// From KF1 - if I already have an enemy, and I have LOS to my enemy and my enemy is closer than NewEnemy, then keep my current enemy.
if ( LineOfSightTo ( Enemy ) && VSize ( Enemy . Location - Pawn . Location ) < VSize ( NewEnemy . Location - Pawn . Location ) )
{
` AILog( GetFuncName() $ "() rejecting " $ NewEnemy $ " because I have LOS to current enemy (" $ Enemy $ ") and current enemy is also closer to me", 'SetEnemy' );
return false ;
}
// Accept new enemy if I can't see my current enemy (CanSee takes PeripheralVision into account)
if ( ! CanSee ( Enemy ) )
{
` AILog( GetFuncName() $ "() accepting " $ NewEnemy $ " because I cannot see my current enemy " $ Enemy, 'SetEnemy' );
ChangeEnemy ( NewEnemy ) ;
return true ;
}
// Reject new enemy if I can't see it (CanSee takes PeripheralVision into account)
if ( ! CanSee ( NewEnemy ) )
{
` AILog( GetFuncName() $ "() rejecting " $ NewEnemy $ " because I cannot see it, but I can see my current enemy " $ Enemy, 'SetEnemy' );
return false ;
}
// Reject new enemy if my current enemy is within my StrikeRange
EnemyDistSq = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( MyKFPawn != none && EnemyDistSq < Square ( StrikeRange ) )
{
` AILog( GetFuncName() $ "() rejecting " $ NewEnemy $ " because current enemy (" $ Enemy $ ") is within my desired melee range", 'SetEnemy' );
return false ;
}
// Accept new enemy if its distance (plus some padding) is closer than enemy distance
NewEnemyDistSq = Square ( 1.2 f * VSize ( NewEnemy . Location - Pawn . Location ) ) ;
if ( EnemyDistSq > NewEnemyDistSq )
{
if ( ! GetHasAcceptableEnemyCount ( NewEnemy ) )
{
return false ;
}
` AILog( GetFuncName() $ "() accepting " $ NewEnemy $ " because Enemy (" $ Enemy $ ") dist is > 1.2 * NewEnemy dist", 'SetEnemy' );
ChangeEnemy ( NewEnemy ) ;
return true ;
}
return false ;
if ( MyKFPawn != none && Enemy != none && Enemy == NewEnemy )
{
MyKFPawn . LookAtPawn ( NewEnemy ) ;
}
return true ;
}
function ChangeEnemy ( Pawn NewEnemy , optional bool bCanTaunt = true )
{
2022-09-01 15:58:51 +00:00
local Pawn OldEnemy , NewForcedEnemy ;
2020-12-13 15:01:13 +00:00
local KFGameInfo KFGI ;
2022-09-01 15:58:51 +00:00
if ( CanForceEnemy )
{
NewForcedEnemy = FindForcedEnemy ( ) ;
}
else if ( NewEnemy == LastForcedEnemy )
{
return ; // Don't allow to change to the ForcedEnemy while we can't (we reenable that again from outside)
}
if ( NewForcedEnemy != none )
{
ForcedEnemy = NewForcedEnemy ;
if ( Enemy != ForcedEnemy )
{
LastForcedEnemy = ForcedEnemy ;
ForcedEnemyLastTime = WorldInfo . TimeSeconds ;
}
NewEnemy = NewForcedEnemy ;
}
else
{
ForcedEnemy = none ;
}
2020-12-13 15:01:13 +00:00
// gameinfo hook that calls mutator hook
KFGI = KFGameInfo ( WorldInfo . Game ) ;
if ( KFGI != none )
{
KFGI . OnAIChangeEnemy ( self , NewEnemy ) ;
}
if ( Enemy != none && Enemy != NewEnemy )
{
if ( bCanTaunt && MyKFPawn != none && MyKFPawn . CanDoSpecialMove ( SM _WalkingTaunt ) )
{
MyKFPawn . DoSpecialMove ( SM _WalkingTaunt ) ;
}
OldEnemy = Enemy ;
if ( OldEnemy != none )
{
` RecordAIChangedEnemy( self, NewEnemy, OldEnemy, "ChangeEnemy() " );
}
// Set our last enemy switch time for the aggro system
LastEnemySwitchTime = WorldInfo . TimeSeconds ;
}
` AILog( GetFuncName() $ "() set Enemy to " $ NewEnemy, 'SetEnemy' );
Enemy = NewEnemy ;
BroadcastEnemyKnowledge ( NewEnemy , WSPM _Belief ) ;
// Notify AI commands about the enemy change.
if ( CachedAICommandList != none )
{
if ( OldEnemy != none )
{
CachedAICommandList . NotifyEnemyChanged ( OldEnemy ) ;
}
else
{
CachedAICommandList . NotifyEnemyChanged ( ) ;
}
}
// Update cooldown time.
LastSetEnemyTime = WorldInfo . TimeSeconds ;
}
/** Search for enemies within a set FOV range */
function KFPawn CheckForEnemiesInFOV ( float MaxRange ,
float MinFOV ,
float MaxFOV ,
optional bool bForceRetarget ,
optional bool bTauntNewEnemy = true )
{
local vector PawnDir , Projection ;
local float FOVDot , TempDistSQ , BestDistSQ ;
local KFPawn KFP , BestTarget ;
if ( Pawn == none )
{
return none ;
}
PawnDir = vector ( Pawn . Rotation ) ;
foreach Pawn . OverlappingActors ( class 'KFPawn' , KFP , MaxRange )
{
if ( bForceRetarget && Enemy == KFP )
{
continue ;
}
if ( ! KFP . IsAliveAndWell ( ) || KFP . GetTeamNum ( ) == GetTeamNum ( ) || ! KFP . CanAITargetThisPawn ( Pawn . Controller ) )
{
continue ;
}
Projection = KFP . Location - Pawn . Location ;
// Only care about players within the FOV range
FOVDot = PawnDir dot Normal ( Projection ) ;
if ( FOVDot < MinFOV || FOVDot > MaxFOV )
{
continue ;
}
// Make sure enemy isn't obstructed
if ( ! ` FastTracePhysX(KFP.Location, Pawn.Location) )
{
continue ;
}
// Make sure enemy isn't too far away
TempDistSQ = VSizeSQ ( Projection ) ;
// Scales distance by aggro rating (0.0 - 1.0)
TempDistSQ *= 1. f - GetAggroRating ( KFP ) ;
if ( BestTarget == none || TempDistSQ < BestDistSQ )
{
BestDistSQ = TempDistSQ ;
BestTarget = KFP ;
}
}
if ( BestTarget != none && BestTarget != Enemy )
{
ChangeEnemy ( BestTarget , bTauntNewEnemy ) ;
return BestTarget ;
}
return none ;
}
/** Allows subclasses to assess aggro ratings on a pawn-by-pawn basis */
function float GetAggroRating ( KFPawn KFP )
{
return 0. f ;
}
/** Enable notifications sent from native code which signial when NPC is in range to perform certain attacks */
function EnableProbingMeleeRangeEvents ( optional bool bForce )
{
if ( bForce || ! MyKFPawn . IsDoingSpecialMove ( ) )
{
` AILog( GetFuncName() $ "() setting bIsProbingMeleeRangeEvents to true, restoring ability to melee attack", 'AIController' );
EnableMeleeRangeEventProbing ( ) ;
ClearTimer ( nameof ( EnableProbingMeleeRangeEvents ) ) ;
}
else
{
` AILog( GetFuncName() $ "() waiting to restore ability to melee attack, will check again in 0.1 seconds", 'AIController' );
SetTimer ( 0.25 f , true , nameof ( EnableProbingMeleeRangeEvents ) ) ;
}
}
/** Disable notifications sent from native code which signial when NPC is in range to perform certain attacks */
function ResetProbingMeleeRangeEvents ( optional float DelayOverride )
{
local float Delay ;
//DelayOverride = 0.5;
Delay = ( DelayOverride > 0. f ? DelayOverride : 0.07 f ) ;
` AILog( GetFuncName() $ "() will be resetting bIsProbingMeleeRangeEvents to true in " $ Delay $ " seconds", 'Command_Attack_Melee' );
SetTimer ( Delay , false , nameof ( EnableProbingMeleeRangeEvents ) , self ) ;
//EnableProbingMeleeRangeEvents();
}
/** Event called by TickMeleeCombatDecision() if MeleeRangeEventProbing is enabled and all basic melee checks have passed */
event ReadyToMelee ( ) ;
/** Override in child classes for NPCs using the suicide AICommand (i.e, Husks) */
function bool IsSuicidal ( )
{
return false ;
}
function bool IsDoingGrabSpecialMove ( )
{
return ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( SM _GrappleAttack ) ) ;
}
function bool CanTargetBeGrabbed ( KFPawn TargetKFP )
{
local KFAIController OtherKFAIC ;
// Disallow if target is invalid, already being grabbed, or currently falling
if ( TargetKFP == none || ( TargetKFP . Health <= 0 ) || ( TargetKFP . IsDoingSpecialMove ( SM _GrappleVictim ) ) || TargetKFP . Physics == PHYS _Falling )
{
return false ;
}
// Need to check that no other clots are performing initial grab move on player since SM_GRAB is run without the player in a special move
foreach WorldInfo . AllControllers ( class 'KFAIController' , OtherKFAIC )
{
if ( OtherKFAIC == self )
{
continue ;
}
// If the the other Zed is doing a grab start or grab attack special move, and TargetKFP is also his enemy, disallow
// this grab. Already checked if player is doing a grapplevictim special move above, but the player isn't executing
// the victim special move until the other Zed's initial grab anim is complete (see KFSM_Clot_Grab - this is the move that determines
// whether the clot's grab should miss or succeed based on when the initial sequence is interrupted)... so instead check
// whether the starting-to-grab Zed has the same enemy.
// The grab special move assumes that the grab victim is also the grabber's enemy.
if ( OtherKFAIC . MyKFPawn != none && OtherKFAIC . Enemy == TargetKFP && OtherKFAIC . IsDoingGrabSpecialMove ( ) )
{
return false ;
}
}
return true ;
}
/ * *
* BeginCombatCommand ( ) - Initiates general base combat command for NPC .
*
* @ param CmdClass Command to start .
* @ param Reason Debug string .
* @ param bForced Init command regardless of combat transition status .
* /
function BeginCombatCommand ( class < AICommand > CmdClass , optional coerce string Reason , optional bool bForced )
{
local class < AICommand _Base _Combat > CurClass ;
local AICommand _Base _Combat CurCommand ;
` AILog( GetFuncName()@CmdClass@"(CommandList:" $ CommandList $ ") Reason: " $ Reason $ " bForced: " $ bForced, 'InitAICommand' );
if ( CommandList != None )
{
CurCommand = AICommand _Base _Combat ( CommandList ) ;
if ( CurCommand != None )
{
CurClass = AICommand _Base _Combat ( CommandList ) . Class ;
}
}
if ( ! bForced )
{
if ( CmdClass != None && ! bAllowCombatTransitions )
{
` AILog( self $ " Not allowing combat transition due to scripting", 'InitAICommand' );
return ;
}
if ( CommandList != None && ! CommandList . AllowTransitionTo ( CmdClass ) )
{
` AILog( self $ " Current command stack rejected transiton", 'InitAICommand' );
return ;
}
}
CleanOutCommandListOnCombatTransition ( ) ;
if ( CmdClass != None )
{
InitalizeBaseCommand ( CmdClass ) ;
}
else
{
// If moving from combat back to Idle
if ( CurClass != None )
{
ReturnToIdle ( ) ;
}
GotoState ( 'Action_Idle' , 'Begin' ) ;
}
}
function InitalizeBaseCommand ( class < AICommand > CmdClass )
{
CmdClass . static . InitCommand ( self ) ;
}
function CleanOutCommandListOnCombatTransition ( )
{
if ( CommandList != None )
{
AbortCommand ( CommandList ) ; //[NEWCODE] was commented out, isn't in gow3
}
}
/** Find the closest potential enemy to my pawn */
final function Pawn GetClosestEnemy ( optional Pawn ExcludePawn )
{
local Pawn P , Best ;
local float Dist , BestDist ;
BestDist = 1000000.0 ;
foreach WorldInfo . AllPawns ( class 'Pawn' , P )
{
if ( ! P . IsAliveAndWell ( ) || ( ExcludePawn != none && ExcludePawn == P ) || ! P . CanAITargetThisPawn ( self ) )
{
continue ;
}
if ( ( P . Controller != None && P . Controller != self && P . IsAliveAndWell ( ) ) && ! Pawn . IsSameTeam ( P ) )
{
Dist = VSize ( P . Location - Pawn . Location ) ;
if ( Dist < BestDist )
{
Best = P ;
BestDist = Dist ;
}
}
}
` AILog( GetFuncName() $ "() returning closest enemy " $ Best, 'SetEnemy' );
if ( Best == none && ExcludePawn != none )
{
Best = ExcludePawn ;
}
return Best ;
}
/** Returns true if NPC is doing an attack-related special move. TODO: Needs refactor (remove IsA, etc.) */
final event bool IsDoingAttackSpecialMove ( )
{
local ESpecialMove KFSM ;
if ( MyKFPawn . IsDoingSpecialMove ( ) )
{
KFSM = MyKFPawn . SpecialMove ;
if ( KFSM == SM _MeleeAttack || KFSM == SM _GrappleAttack || KFSM == SM _SonicAttack || KFSM == SM _MeleeAttackDoor
|| KFSM == SM _Suicide )
{
return true ;
}
}
return false ;
}
/** Check for a combat transition, called once the current behavior has completed */
function CheckCombatTransition ( )
{
local class < AICommand > NewCommand ;
local string Reason ;
local bool bTransition ;
local AICommand _Base _Combat CurCommand ;
` AILog( GetFuncName() $ "() bAllowCombatTransitions: " $ bAllowCombatTransitions, 'CombatTransitions' );
if ( Pawn == None || ! Pawn . IsAliveAndWell ( ) || ! bAllowCombatTransitions || IsDead ( ) )
{
return ;
}
` AILog( GetFuncName()@CommandList $ " calling CheckInterruptCombatTransitions now", 'CombatTransitions' );
// First check for interrupt transitions
bTransition = ! CheckInterruptCombatTransitions ( ) ;
// If no interrupt occurred
if ( bTransition )
{
` AILog( "bTransition was true from CheckInterruptCombatTransitions()", 'CombatTransitions' );
CurCommand = AICommand _Base _Combat ( CommandList ) ;
if ( CurCommand != None &&
CurCommand . CheckTransition ( NewCommand , Reason ) )
{
` AILog( GetFuncName() $ "() Calling BeginCombatCommand for " $ NewCommand, 'CombatTransitions' );
BeginCombatCommand ( NewCommand , Reason ) ;
}
else
if ( CurCommand == None && ( CommandList == none || CommandList . Class != DefaultCommandClass ) )
{
NewCommand = GetDefaultCommand ( ) ;
if ( NewCommand == none )
{
` AILog("WARNING! " $ self $ " has no default command specified!", 'CombatTransitions');
return ;
}
` AILog( GetFuncName() $ "() Calling BeginCombatCommand for 2 " $ NewCommand, 'CombatTransitions' );
BeginCombatCommand ( NewCommand , "No Command Specified" ) ;
}
}
}
//function NotifyKilled(Controller Killer, Controller Killed, Pawn KilledPawn)
function NotifyKilled ( Controller Killer , Controller Killed , pawn KilledPawn , class < DamageType > damageTyp )
{
// TODO: add a radius check so that zeds won't taunt over really far away guys
// or if there is another enemy really close!
if ( Killer != none && Killer == self && KilledPawn != none && Pawn != none
&& ! Pawn . IsSameTeam ( KilledPawn ) && KilledPawn == Enemy )
{
class 'AICommand_TauntEnemy' . static . Taunt ( self , KilledPawn , TAUNT _EnemyKilled ) ;
}
else if ( KilledPawn != none && KilledPawn == Enemy )
{
// Find a new enemy if our current enemy dies!
FindNewEnemy ( ) ;
}
super . NotifyKilled ( Killer , Killed , KilledPawn , damageTyp ) ;
}
/** Returns true if we have a valid enemy targeted */
final function bool HasValidEnemy ( optional Pawn TestEnemy )
{
if ( TestEnemy == None )
{
TestEnemy = Enemy ;
}
if ( TestEnemy == None ||
! TestEnemy . IsValidEnemyTargetFor ( PlayerReplicationInfo , true ) )
{
return false ;
}
return true ;
}
/** TODO: Refactor to always just use FindNewEnemy */
function bool SelectEnemy ( )
{
return FindNewEnemy ( ) ;
}
/** TODO: Refactor to always just use FindNewEnemy */
function bool SelectTarget ( )
{
return FindNewEnemy ( ) ;
}
/** Effects and damage from a zed sprinting and bumping other monsters */
function bool DoHeavyZedBump ( Actor Other , vector HitNormal )
{
local int BumpEffectDamage ;
local KFPawn _Monster BumpedMonster ;
/** If we bumped into a glass window, break it */
if ( Other . bCanBeDamaged && KFFracturedMeshGlass ( Other ) != none )
{
KFFracturedMeshGlass ( Other ) . BreakOffAllFragments ( ) ;
return true ;
}
BumpedMonster = KFPawn _Monster ( Other ) ;
if ( BumpedMonster == none || ! BumpedMonster . IsAliveAndWell ( ) || BumpedMonster . ZedBumpDamageScale <= 0 )
{
return false ;
}
if ( MyKFPawn == none || ! MyKFPawn . IsAliveAndWell ( ) )
{
return false ;
}
if ( ( MyKFPawn . bIsSprinting || MyKFPawn . IsEnraged ( ) ) )
{
BumpEffectDamage = ZedBumpEffectThreshold * BumpedMonster . ZedBumpDamageScale * ( MyKFPawn . bIsSprinting ? 2 : 1 ) ;
// If the Bumped Zed is near death, play either a knockdown or an immediate obliteration
if ( BumpedMonster . Health - BumpEffectDamage <= 0 )
{
BumpedMonster . TakeDamage ( BumpEffectDamage , self , BumpedMonster . Location , vect ( 0 , 0 , 0 ) , MyKFPawn . GetBumpAttackDamageType ( ) ) ;
BumpedMonster . Knockdown ( , vect ( 1 , 1 , 1 ) , Pawn . Location , 1000 , 100 ) ;
return true ;
}
else
{
// otherwise deal damage and stumble the zed
BumpedMonster . TakeDamage ( BumpEffectDamage , self , BumpedMonster . Location , vect ( 0 , 0 , 0 ) , MyKFPawn . GetBumpAttackDamageType ( ) ) ;
BumpedMonster . DoSpecialMove ( SM _Stumble , , , class 'KFSM_Stumble' . static . PackBodyHitSMFlags ( BumpedMonster , HitNormal ) ) ;
return true ;
}
}
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* MELEE
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Notification from AICommand_Attack_Melee that I've just completed an attack */
function NotifyMeleeAttackFinished ( ) ;
/** Starts base melee AI command */
function BeginMeleeCommand ( Pawn TargetPawn , optional coerce String Reason )
{
` AILog( GetFuncName() $ "() TargetPawn: " $ TargetPawn, 'Command_Attack_Melee' );
if ( AICommand _Base _Zed ( CommandList ) == none )
{
// Optionally set my enemy to TargetPawn
if ( TargetPawn != None )
{
SetEnemy ( TargetPawn ) ;
}
if ( MeleeCommandClass != GetDefaultCommand ( ) )
{
` AILog( GetFuncName() $ "() calling BeginCombatCommand for " $ MeleeCommandClass, 'Command_Attack_Melee' );
BeginCombatCommand ( MeleeCommandClass , Reason , true ) ;
}
}
}
/** Is current potential target valid? TODO: Conditions need refactoring */
function bool IsValidAttackTarget ( Actor CheckActor )
{
if ( CheckActor == none || CheckActor . bTearOff )
{
return false ;
}
return true ;
}
/ * *
* Used by movement commands to determine if NPC is close enough consider a strike
* /
function bool IsWithinAttackRange ( )
{
local float DistSqToEnemy ;
// If my current enemy isn't valid, set my enemy to the closest one available
if ( MyKFPawn == none || Enemy == none || ! MyKFPawn . IsAliveAndWell ( ) || ! Enemy . IsAliveAndWell ( ) )
{
if ( Enemy == none || Enemy . Health <= 0 )
{
Enemy = none ;
SetEnemy ( GetClosestEnemy ( ) ) ;
return false ;
}
return true ;
}
if ( MyKFGameInfo . NumPlayers > 1 )
{
// If at least 4 other KFPawns are within 350 units of my enemy, and I'm close enough to see my enemy...
if ( KFPawn ( Enemy ) . IsSurrounded ( false , 4 , 350. f ) && CanSee ( Enemy ) )
{
// ... Try to find a new enemy and return false if I decided to change targets
if ( FindNewEnemy ( ) )
{
` AILog( GetFuncName() $ " found that enemy is surrounded so changed enemy to " $ Enemy, 'ReachedEnemy' );
return false ;
}
}
}
if ( MyKFPawn . PawnAnimInfo == none )
{
` warn( "No PawnAnimInfo defined for " $ MyKFPawn );
return IsCloseEnoughToEnemy ( ) ;
}
// Check distance from enemy versus my PawnAnimInfo's AttackRange value.
DistSqToEnemy = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( DistSqToEnemy <= AttackRange * AttackRange )
{
return true ;
}
return false ;
}
/** Check to see if this KFPawn is within allowed height difference for melee */
function bool WithinMeleeHeightDifference ( KFPawn EnemyPawn )
{
local vector EnemyPawnGroundPos , MyPawnGroundPos ;
local vector EnemyPawnCylinder , MyPawnCylinder ;
EnemyPawnCylinder . Z = EnemyPawn . CylinderComponent . CollisionHeight ;
MyPawnCylinder . Z = Pawn . CylinderComponent . CollisionHeight ;
EnemyPawnGroundPos = EnemyPawn . Location - EnemyPawnCylinder ;
MyPawnGroundPos = Pawn . Location - MyPawnCylinder ;
return ( Abs ( Normal ( EnemyPawnGroundPos - MyPawnGroundPos ) . Z ) < MaxMeleeHeightAngle ) ;
}
/** Notification that a melee attack has ended or been aborted */
function EndOfMeleeAttackNotification ( ) ;
/** Notification from melee attack command that it was aborted due to damage taken */
function NotifyCommandMeleeAttackAborted ( )
{
local AICommand _Attack _Melee MeleeCommand ;
` AILog( GetFuncName() $ "()", 'Command_Attack_Melee' );
// Push AICommand if it is defined. (jessnew)
if ( CommandList != None && GetActiveCommand ( ) . IsA ( 'AICommand_Attack_Melee' ) )
{
MeleeCommand = AICommand _Attack _Melee ( GetActiveCommand ( ) ) ;
if ( MeleeCommand != none )
{
` AILog( GetFuncName() $ "() calling AbortCommand for " $ MeleeCommand, 'Command_Attack_Melee' );
AbortCommand ( MeleeCommand ) ;
}
}
}
/** Updates time of last melee attack performed by this NPC (TODO: Need to update this) */
function UpdateLastMeleeTime ( EAnimSlotStance BodyStance )
{
LastAttackTime _Melee = WorldInfo . TimeSeconds ;
}
/** Chooses a melee attack animation by name */
function ChooseStrikeByName ( name AttackName , optional bool bForce )
{
local int DesiredStrikeIndex ;
local KFPawnAnimInfo KFPAI ;
if ( AttackName != '' && MyKFPawn != none && MyKFPawn . PawnAnimInfo != none )
{
KFPAI = MyKFPawn . PawnAnimInfo ;
DesiredStrikeIndex = KFPAI . GetAttackIndexByTag ( AttackName ) ;
// Make sure our attack name matches the PawnAnimInfo's tag
if ( DesiredStrikeIndex < 0 )
{
` warn("MeleeAttackRandList Tag " $ AttackName $ " Could not be found in the PawnAnimInfo");
return ;
}
// if we aren't forcing the attack, check to see if this is a valid attack
if ( bForce || KFPAI . CanDoAttackAnim ( DesiredStrikeIndex , MyKFPawn , Enemy ) )
{
// Updates the next time this attack can be used
KFPAI . UpdateAttackCooldown ( self , DesiredStrikeIndex ) ;
return ;
}
}
` log(self@GetFuncName()@"failed to find a valid attack");
}
/** Override in child classes - return true if permitted to grab enemy */
event bool CanGrabAttack ( )
{
return false ;
}
/** Override in child classes if pawn can grab */
event DoGrabAttack ( optional Pawn NewEnemy , optional float InPostSpecialMoveSleepTime = 0. f ) { }
/** Count how many Zeds are currently targeting P, optionally excluding myself from the count */
function int NumberOfZedsTargetingPawn ( Pawn P , optional bool bExcludeMe = true , optional float MinDistanceToInclude )
{
local Controller C ;
local int ZedCount ;
if ( P == none || ! P . IsAliveAndWell ( ) )
{
return 0 ;
}
foreach WorldInfo . AllControllers ( class 'Controller' , C )
{
if ( C . Enemy == P && C . Pawn != none && ( C . Pawn != Pawn || ! bExcludeMe ) && C . Pawn . IsAliveAndWell ( ) )
{
if ( MinDistanceToInclude == 0. f || ( MinDistanceToInclude > 0. f && ( VSizeSq ( C . Pawn . Location - P . Location ) <= MinDistanceToInclude * MinDistanceToInclude ) ) )
{
ZedCount ++ ;
}
}
}
return ZedCount ;
}
/ * *
* How many other Zeds are currently targeting my enemy ? Use to determine ahead of time if it ' d be worthwhile
* to target players with fewer Zeds targeting them .
* /
function int OtherZedsTargetingMyEnemy ( )
{
local KFAIController KFAIC ;
local int ZedCount ;
if ( Enemy == none || ! Enemy . IsAliveAndWell ( ) )
{
return 0 ;
}
foreach WorldInfo . AllControllers ( class 'KFAIController' , KFAIC )
{
if ( KFAIC != self && KFAIC . Pawn != none && KFAIC . Pawn . IsAliveAndWell ( ) && KFAIC . Enemy != none && KFAIC . Enemy == Enemy )
{
ZedCount ++ ;
}
}
return ZedCount ;
}
/** Returns how many Zeds are out there, optionally how many are within the specified # of units to my pawn */
function int GetZedCount ( optional float MinDistanceFromMyPawn = 0. f )
{
local KFAIController KFAIC ;
local int ZedCount ;
foreach WorldInfo . AllControllers ( class 'KFAIController' , KFAIC )
{
if ( KFAIC . Pawn != none && KFAIC . Pawn . IsAliveAndWell ( ) )
{
if ( MinDistanceFromMyPawn > 0. f )
{
if ( VSizeSq ( KFAIC . Pawn . Location - Pawn . Location ) <= ( MinDistanceFromMyPawn * MinDistanceFromMyPawn ) )
{
ZedCount ++ ;
}
}
else
{
ZedCount ++ ;
}
}
}
return ZedCount ;
}
native final function bool CanSeeByPointsEx ( Vector ViewLocation , Vector TestLocation , Rotator ViewRotation , out Vector outHitLoc , out Actor outHitActor ) ;
/ * *
* True if MinimalCount Zeds are within CheckRadius units of aLocation , optionally requiring a Zed can currently see
* that location before counting him .
* /
function bool AreZedsNear ( vector aLocation , optional bool bRequireCanSee = false , optional float CheckRadius = 650. f , optional int MinimalCount = 1 )
{
local int i , ZedCount ;
local rotator ViewRot ;
local vector ViewLoc ;
if ( MyAIDirector == none )
{
return false ;
}
// Can use KFAIDirector's AIList rather than AllControllers, etc.
for ( i = 0 ; i < MyAIDirector . AIList . Length ; i ++ )
{
if ( ZedCount >= MinimalCount )
{
return true ;
}
if ( MyAIDirector . AIList [ i ] == self || MyAIDirector . AIList [ i ] . MyKFPawn == none || MyAIDirector . AIList [ i ] . MyKFPawn . IsUsingSuperSpeed ( ) )
{
continue ;
}
if ( CheckRadius > 0. f && VSizeSq ( MyAIDirector . AIList [ i ] . Pawn . Location - aLocation ) < ( CheckRadius * CheckRadius ) )
{
if ( bRequireCanSee )
{
MyAIDirector . AIList [ i ] . MyKFPawn . GetActorEyesViewPoint ( ViewLoc , ViewRot ) ;
if ( MyAIDirector . AIList [ i ] . CanSeeByPoints ( ViewLoc , aLocation , ViewRot ) )
{
ZedCount ++ ;
}
else
{
continue ;
}
}
else
{
ZedCount ++ ;
}
}
}
return false ;
}
/** Returns false if this attack is still on cooldown */
function bool CheckCooldownTimer ( name CooldownTag )
{
local int i ;
i = CooldownTimers . Find ( 'Tag' , CooldownTag ) ;
if ( i != INDEX _None )
{
if ( ` TimeSince(CooldownTimers[i].ActivationTime) < CooldownTimers[i].Duration )
{
return false ;
}
}
return true ;
}
/** Add (or update existing) cooldown timer */
function AddCooldownTimer ( name CooldownTag , float CooldownTime )
{
local int i ;
local CooldownData NewCooldown ;
i = CooldownTimers . Find ( 'Tag' , CooldownTag ) ;
if ( i == INDEX _None )
{
NewCooldown . Tag = CooldownTag ;
i = CooldownTimers . AddItem ( NewCooldown ) ;
}
CooldownTimers [ i ] . Duration = CooldownTime ;
CooldownTimers [ i ] . ActivationTime = WorldInfo . TimeSeconds ;
}
/** Set overall attack cooldown timer */
function SetOverallCooldownTimer ( float CooldownTime )
{
OverallAttackCooldownTimer . Duration = CooldownTime ;
OverallAttackCooldownTimer . ActivationTime = WorldInfo . TimeSeconds ;
}
/** Returns false if the overall attacks are still on cooldown */
function bool CheckOverallCooldownTimer ( )
{
if ( OverallAttackCooldownTimer . Duration > 0 &&
` TimeSince(OverallAttackCooldownTimer.ActivationTime) < OverallAttackCooldownTimer.Duration )
{
return false ;
}
return true ;
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Combat transitions
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Check for an interrupt transition, can override any normal transition */
function bool CheckInterruptCombatTransitions ( )
{
local AICommand _Base _Combat CurCommand ;
` AILog( "---- " $ GetFuncName() $ "() IgnoreNotifies? : " $ IgnoreNotifies(), 'CombatTransitions' );
// If dead, gone, or ignoring notifies don't bother
if ( Pawn == None || ! Pawn . IsAliveAndWell ( ) || IsDead ( ) || IgnoreNotifies ( ) )
{
` AILog( "---- " $ GetFuncName() $ "() aborting early", 'CombatTransitions' );
return false ;
}
// check that our enemy is still valid
if ( Enemy != None && ( ! Enemy . IsAliveAndWell ( ) || Enemy . Controller == none ) )
{
` AILog( GetFuncName() $ "() setting enemy to NONE because they have no controller or they are dead!", 'CombatTransitions' );
SetEnemy ( None ) ;
}
// If not already in combat then don't bother
CurCommand = AICommand _Base _Combat ( CommandList ) ;
if ( CurCommand == None )
{
` AILog( "---- " $ GetFuncName() $ "() setting enemy aborting because current command (" $ CurCommand $ ") isn't a combat based command", 'CombatTransitions' );
return false ;
}
if ( AICommand _Base _Combat ( CommandList ) != None && ShouldReturnToIdle ( ) )
{
//`AILog( "---- "$GetFuncName()$"() setting enemy aborting because current command ("$CurCommand$") isn't a combat based command", 'CombatTransitions' );
` AILog( GetFuncName() $ "() returning true because I have a combat command and ShouldReturnToIdle returned true", 'CombatTransitions' );
//BeginCombatCommand( None, "No Enemies" );
return true ;
}
` AILog( GetFuncName() $ "() returning false", 'CombatTransitions' );
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Movement Methods
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Native definitions
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Same as actor reachable, only allows the "no anchor check" option to be used from script */
native final function bool ActorReachableNoAnchorCheck ( actor anActor ) ;
// Returns true if next intermediate move target is a KFJumpPoint node
native function bool IsMovingToJumpPoint ( ) ;
// Initializes NPC steering
native function InitSteering ( ) ;
// Returns NPC's steering object
native function KFAISteering GetSteering ( ) ;
// Calculates distance along current path, using NPC's routecache if valid
final native function float GetRouteCacheDistance ( ) ;
// < -1 = Already passed LocB, nearly zero = nearly parallel
native function float CalcClosestPointTime ( Vector LocA , Vector VelocityA , Vector LocB , Vector VelocityB ) ;
// Used in checking direct reachability to goal while pawn is moving along a path
native function SetDirectPathCheckTime ( ) ;
/** Is my pawn currently performing a latent move? */
function bool IsDoingLatentMove ( )
{
return ( InLatentExecution ( LATENT _MOVETOWARD ) || InLatentExecution ( LATENT _MOVETO ) ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* MOVEMENT AND PATHFINDING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Forcibly stops any active latent movement and execution and stops accel/velocity */
event StopAllLatentMovement ( optional bool bForced )
{
StopAllLatentMoveExecution ( ) ;
AIZeroMovementVariables ( ) ;
}
/** Abort any movement commands and reset MoveTarget */
function ClearMovementInfo ( optional bool bSafeAbort , optional string DebugMsg )
{
` AILog( GetFuncName() $ "() Aborting movement commands and setting MoveTarget to none", 'Command_MoveToGoal' );
AbortMovementCommands ( ) ;
AbortMovementPlugIns ( ) ;
MoveTarget = None ;
}
/** Abort the main movement commands, might change to abort others like StepAside */
event AbortMovementCommands ( optional bool bSafeAbort , optional string DebugMsg )
{
` AILog( GetFuncName() $ "() called, aborting MoveToEnemy and MoveToGoal commands if they are active", 'Command_MoveToGoal' );
if ( DebugMsg != "" )
{
FindCommandOfClass ( class 'AICommand_MoveToEnemy' ) . UpdateHistoryString ( DebugMsg ) ;
FindCommandOfClass ( class 'AICommand_MoveToGoal' ) . UpdateHistoryString ( DebugMsg ) ;
}
AbortCommand ( None , class 'AICommand_MoveToEnemy' ) ;
AbortCommand ( None , class 'AICommand_MoveToGoal' ) ;
}
/** Abort the main movement commands, might change to abort others like StepAside */
event AbortMovementPlugIns ( optional bool bSafeAbort , optional string DebugMsg )
{
` AILog( GetFuncName() $ "() called, aborting MovementPlugin and LeapPlugin Plug Ins if they are active", 'Movement_Plugins' );
if ( DebugMsg != "" )
{
if ( KfMovementPlugin != none )
{
KfMovementPlugin . UpdateHistoryString ( DebugMsg ) ;
}
if ( KfLeapPlugin != none )
{
KfLeapPlugin . UpdateHistoryString ( DebugMsg ) ;
}
if ( StuckFixPlugin != none )
{
StuckFixPlugin . UpdateHistoryString ( DebugMsg ) ;
}
}
if ( KfMovementPlugin != none )
{
KfMovementPlugin . AbortMove ( false ) ;
}
if ( KfLeapPlugin != none )
{
KfLeapPlugin . AbortMove ( false ) ;
}
if ( StuckFixPlugin != none )
{
StuckFixPlugin . AbortMove ( false ) ;
}
}
/** Stops latent move, and zeroes pawn Acceleration and Velocity if the pawn's not falling (or if bForce is true) */
function AIZeroMovementVariables ( optional bool bForce )
{
` AILog( WorldInfo.TimeSeconds $ " " $ GetFuncName() $ " setting movetimer to -1", 'PathWarning' );
MoveTimer = - 1. f ; // Keep an eye on this
if ( Pawn != none )
{
if ( ! bForce && Pawn . Physics == PHYS _Falling )
{
return ;
}
Pawn . ZeroMovementVariables ( ) ;
}
}
function StopMovement ( optional EActionPriority ActionPriority = AP _Logic )
{
Super . StopMovement ( ActionPriority ) ;
if ( KfWallWalkingPlugIn != none )
{
KfWallWalkingPlugIn . AbortMove ( false ) ;
}
}
/** Called by AICommands to notify that a move is about to start */
event StartingMovement ( )
{
` AILog( self $ " StartingMovement", 'AIController' );
}
/** Called by AICommands to notify that a move has ended */
event StoppingMovement ( )
{
` AILog( self $ " StoppingMovement", 'AIController' );
}
/** Called by AICommands to determine if NPC is allowed to move */
event bool AllowedToMove ( )
{
// Disallow for SM attacks since movement will be handled by root motion
return ( ! IsDoingAttackSpecialMove ( ) ) ;
}
/** Called by movetogoal when we arrive at our destination */
event ReachedMoveGoal ( )
{
` AILog( self $ " Reached MoveGoal!", 'AIController' );
}
/** Called by movetogoal when we arrive at an intermediate point in our path (pathnode location, etc.) */
function ReachedIntermediateMoveGoal ( )
{
` AILog( self $ " ReachedIntermediateMoveGoal!", 'AIController' );
}
/** TODO: Needs to be deprecated or revised */
function bool MoveIsInterruptable ( optional bool bForce )
{
return true ;
}
/** Check if given goal is valid */
function bool MoveGoalIsValid ( optional Actor Goal )
{
if ( Goal == None )
{
Goal = MoveGoal ;
}
return ( Goal != None ) ;
}
/** Check if given point is valid */
function bool MovePointIsValid ( optional Vector Point )
{
if ( Point == vect ( 0 , 0 , 0 ) )
{
Point = GetBasedPosition ( MovePosition ) ;
}
return ( Point != vect ( 0 , 0 , 0 ) ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* PATH BUILDING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Build a path to goal actor */
event Actor GeneratePathTo ( Actor Goal , optional float Distance , optional bool bAllowPartialPath )
{
local actor PathResult ;
AddBasePathConstraints ( ) ;
` AILog( GetFuncName() $ "() Goal: " $ Goal $ " optional distance: " $ Distance $ " bAllowPartialPath: " $ bAllowPartialPath, 'AIController' );
class 'Path_TowardGoal' . static . TowardGoal ( Pawn , Goal ) ;
if ( bDisablePartialPaths )
{
bAllowPartialPath = false ;
}
class 'Goal_AtActor' . static . AtActor ( Pawn , Goal , Distance , bAllowPartialPath ) ;
PathResult = FindPathToward ( Goal ) ;
Pawn . ClearConstraints ( ) ;
return PathResult ;
}
event bool GenerateNavMeshPathTo ( Actor Goal , optional float WithinDistance , optional bool bAllowPartialPath )
{
if ( NavigationHandle == None )
{
return false ;
}
NavigationHandle . ClearConstraints ( ) ;
class 'NavMeshPath_Toward' . static . TowardGoal ( NavigationHandle , Goal ) ;
class 'NavMeshGoal_At' . static . AtActor ( NavigationHandle , Goal , WithinDistance , bAllowPartialPath ) ;
return NavigationHandle . FindPath ( ) ;
}
event bool GenerateNavMeshPathToLocation ( Vector Goal , optional float WithinDistance , optional bool bAllowPartialPath )
{
if ( NavigationHandle == None )
{
return FALSE ;
}
NavigationHandle . ClearConstraints ( ) ;
// NavMeshPathConstraint
class 'NavMeshPath_Toward' . static . TowardPoint ( NavigationHandle , Goal ) ;
// NavMeshPathGoalEvaluator
class 'NavMeshGoal_At' . static . AtLocation ( NavigationHandle , Goal , WithinDistance , bAllowPartialPath ) ;
return NavigationHandle . FindPath ( ) ;
}
/** Generates a path to goal which avoids passing through closed and locked doors */
event Actor GenerateDoorlessPathTo ( Actor Goal , optional float Distance , optional bool bAllowPartialPath )
{
local actor PathResult ;
local KFPawn CheckPawn ;
CheckPawn = KFPawn ( Goal ) ;
AddBasePathConstraints ( ) ;
class 'Path_TowardGoal' . static . TowardGoal ( Pawn , CheckPawn ) ;
class 'Path_AvoidClosedDoors' . static . AvoidClosedDoors ( Pawn , true ) ;
class 'Goal_Null' . static . GoUntilBust ( Pawn , 2024 ) ;
//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' );
}
return PathResult ;
}
/** Manually block a troublesome path connection, or specify a cost which won't explicitly block it */
native function bool AddBlockedReachSpec ( ReachSpec BlockedSpec , INT BlockedCost ) ;
static function ShowAdvancedRouteEdgeDebugInfo ( NavigationHandle KFNavigationHandle2Use , optional bool bShowPathCachePolys = false )
{
//local array<KFNavMeshMovementStepData> debugPathData;
//local array<string> listOfEdgeTrips;
//local int i;
//local Vector textOffset;
//if( bShowPathCachePolys )
//{
// //FlushPersistentDebugLines();
// KFNavigationHandle2Use.DrawPathCache(,true);
//}
//KFNavigationHandle2Use.FillArrayWithAllCurrentNavMeshMovementStepData(debugPathData);
//KFNavigationHandle2Use.FillArrayWithAllCurrentPathEdgeTrippyDebugInfo( listOfEdgeTrips );
//for( i = 0; i < KFNavigationHandle2Use.PathCache.EdgeList.Length; i++ )
//{
// //typeOfEdge = listOfEdgeTypes[i];
// textOffset = Vect(0,0,1);
// textOffset *= 10.0f + 10*i;
// DrawDebugString( debugPathData[i].EdgeCenter + textOffset, string(i) @ listOfEdgeTrips[i] /*string(typeOfEdge)*/, , debugPathData[i].EdgeColor, default.TimeToShowEdgeTypeForNavMeshPathting);
// DrawDebugLine( debugPathData[i].EdgeCenter , debugPathData[i].EdgeCenter + textOffset , debugPathData[i].EdgeColor.R, debugPathData[i].EdgeColor.G, debugPathData[i].EdgeColor.B, true );
// DrawDebugLine( debugPathData[i].EdgeCenter + textOffset , debugPathData[i].Poly0Center + textOffset , debugPathData[i].EdgeColor.R, debugPathData[i].EdgeColor.G, debugPathData[i].EdgeColor.B, true );
// if( i != 0 )
// {
// DrawDebugLine( debugPathData[i].EdgeCenter + textOffset, debugPathData[i-1].EdgeCenter + textOffset , debugPathData[i].EdgeColor.R, debugPathData[i].EdgeColor.G, debugPathData[i].EdgeColor.B, true );
// }
//}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* AICOMMANDS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * P a u s e s t h e A I f o r s p e c i f i e d d u r a t i o n ( s e c o n d s ) , o p t i o n a l l y s t o p s m o v e m e n t , a b o r t s A I c o m m a n d s , a n d d e l a y s p a u s e u n t i l
pawn is on the ground * /
function DoPauseAI ( float InDuration , optional bool bStopMovement , optional bool bAbortCommands , optional bool bWaitForLanding )
{
local vector OldVelocity ;
local bool bWasFalling ;
if ( bStopMovement )
{
if ( Pawn != none && Pawn . Physics == PHYS _Falling )
{
OldVelocity = Pawn . Velocity ;
bWasFalling = true ;
}
AbortMovementCommands ( ) ;
AbortMovementPlugIns ( ) ;
// Set our old velocity if we zeroed our movement while falling (probably from being pushed by weapon physics)
if ( Pawn != none && bWasFalling )
{
Pawn . Velocity = OldVelocity ;
}
}
` AILog( GetFuncName() $ "() Init AICommand_Pause Duration: " $ InDuration $ " Active command: " $ GetActiveCommand(), 'Command_Pause' );
class 'AICommand_Pause' . static . Pause ( self , InDuration , bStopMovement ) ;
}
/** Starts wander AICommand, with optional goal to wander toward or away from, wander duration (-1 = none), and maximum distance to wander */
function DoWander ( optional actor WanderGoal , optional float WanderDuration = - 1. f , optional bool bWanderAwayFromGoal , optional float MaxWanderDist = 10000. f )
{
class 'AICommand_Wander' . static . BeginWander ( self , WanderDuration , WanderGoal , bWanderAwayFromGoal , MaxWanderDist ) ;
}
/** Begins Hide AICommand, hiding from HideFrom, with an optional duration to control when the command expires */
/ * f u n c t i o n D o H i d e F r o m ( a c t o r H i d e F r o m , o p t i o n a l f l o a t H i d e D u r a t i o n , o p t i o n a l f l o a t H i d e D i s t a n c e )
{
class 'AICommand_Hide' . static . HideFrom ( self , HideFrom , HideDuration , HideDistance ) ;
` DialogManager.PlaySpotRunAwayDialog( MyKFPawn );
} * /
/* Starts Flee AICommand, with optional duration and distance */
function DoFleeFrom ( actor FleeFrom ,
optional float FleeDuration ,
optional float FleeDistance ,
optional bool bShouldStopAtGoal = false ,
optional bool bFromFear = false )
{
class 'AICommand_Flee' . static . FleeFrom ( self , FleeFrom , FleeDuration , FleeDistance , bShouldStopAtGoal ) ;
}
/ *
* SetMoveGoal ( ) - main function used to initiate NPC movement toward a goal actor
* @ param NewMoveGoal
* @ param NewMoveFocus Optional focus override while moving
* @ param bInterruptable Whether or not this move can be interrupted
* @ param OffsetDist Optional offset distance from move point
* @ param bIsValidCache Whether routecache is currently valid
* @ param bInCanPathfind When false , NPC will only move if goal is directly reachable
* @ param bForce Forces the move regardless of interruptability
* @ param bAllowedToAttack Whether or not NPC is permitted to attack during this move
* @ param bAllowPartialPath Whether or not to allow a partial path
* /
event SetMoveGoal ( Actor NewMoveGoal , optional Actor NewMoveFocus ,
optional bool bInterruptable , optional float OffsetDist ,
optional bool bIsValidCache , optional bool bInCanPathfind = true ,
optional bool bForce , optional bool bAllowedToAttack = true ,
optional bool bAllowPartialPath )
{
// Sandbox
if ( bAlwaysAcceptPartialPaths && MyKFPawn . Physics != PHYS _Spider )
{
bAllowPartialPath = true ;
}
bMoveGoalInterruptable = bInterruptable ;
MoveGoal = NewMoveGoal ;
SetBasedPosition ( MovePosition , vect ( 0 , 0 , 0 ) ) ;
MoveFocus = NewMoveFocus ;
MoveOffset = OffsetDist ;
` AILog( GetFuncName() $ "() initializing AICommand_MoveToGoal"@NewMoveGoal@NewMoveFocus@bInterruptable@bAllowedToAttack, 'InitAICommand' );
if ( NewMoveGoal != None && ( MoveIsInterruptable ( bForce ) || ! bInterruptable ) )
{
class 'AICommand_MoveToGoal' . static . MoveToGoal ( self , NewMoveGoal , NewMoveFocus , OffsetDist , bIsValidCache , bInCanPathfind , bAllowedToAttack , bAllowPartialPath ) ;
}
else if ( NewMoveGoal != none )
{
` AILog( GetFuncName() @"!! -- ignoring movegoal because I already have a moveaction, which is non-interruptable, and the new movegoal IS interruptable.. trumped", 'InitAICommand' );
}
}
/ * *
* SetMovePoint ( ) - main function used to initiate NPC movement to a location
* @ param NewMovePoint Move to this location
* @ param NewMoveFocus Optional focus override while moving
* @ param bInterruptable Whether or not this move can be interrupted
* @ param OffsetDist Optional offset distance from move point
* @ param bIsValidCache Whether routecache is currently valid
* @ param bAllowedToAttack Whether or not NPC is permitted to attack during this move
* @ param bAllowPartialPath Whether or not to allow a partial path
* /
final event SetMovePoint ( Vector NewMovePoint , optional Actor NewMoveFocus ,
optional bool bInterruptable , optional float OffsetDist ,
optional bool bIsValidCache , optional bool bAllowedToAttack = true ,
optional bool bAllowPartialPath , optional bool bCanPathfind = true )
{
` AILog( GetFuncName() $ "() initializing AICommand_MoveToGoal" $ NewMovePoint@NewMoveFocus@bInterruptable@bAllowedToAttack, 'InitAICommand' );
bReachedMoveGoal = false ;
bMoveGoalInterruptable = bInterruptable ;
MoveGoal = None ;
SetBasedPosition ( MovePosition , NewMovePoint ) ;
MoveFocus = NewMoveFocus ;
MoveOffset = OffsetDist ;
if ( NewMovePoint != vect ( 0 , 0 , 0 ) )
{
class 'AICommand_MoveToGoal' . static . MoveToPoint ( self , NewMovePoint , NewMoveFocus , OffsetDist , bIsValidCache , bCanPathfind , bAllowedToAttack , bAllowPartialPath ) ;
}
}
/ * *
* SetEnemyMoveGoal ( ) - initiates movement toward NPC ' s current enemy .
* @ param bCompleteMove When false , a partial move is acceptable
* @ param GoalDistance Optional offset distance ( UU ) from enemy
* @ param AbandonDistance Distance ( UU ) from enemy that will forcibly abort the command
* @ param bAllowedToAttack Whether or not NPC is permitted to attack during this move
* /
final event SetEnemyMoveGoal ( Object Observer ,
optional bool bCompleteMove ,
optional float GoalDistance = 0. f ,
optional float AbandonDistance = 0. f ,
optional bool bAllowedToAttack = true )
{
` AILog( GetFuncName() $ "() initializing AICommand_MoveToEnemy - setting enemy goal Dist:"@GoalDistance@"bCompleteMove?"@bCompleteMove@"bAllowedToAttack?"@bAllowedToAttack, 'InitAICommand' );
PreMoveToEnemy ( ) ;
if ( bUseNavMesh && bUsePluginsForMovement )
{
//MoveToEnemyRequest(Observer, AP_Logic, bAllowedToAttack, GoalDistance);
}
else
{
class 'AICommand_MoveToEnemy' . static . MoveToEnemy ( self , bCompleteMove , GoalDistance , AbandonDistance , bAllowedToAttack , Enemy ) ;
}
}
// ----------------------------------------------------------------------- //
// UBTTaskGoto methods
// ----------------------------------------------------------------------- //
/** Begin a strike attack */
function DoStrike ( ) ;
function bool CanDoStrike ( ) ;
/** 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 ) ;
/** Triggers an evade from the PendingEvadeProjectile, if possible and it's still on target */
final function DoProjectileEvade ( )
{
local byte BestDir ;
//local KFDebugLines KFDL;
//KFDL = class'KFDebugLines'.static.GetDebugLines();
MyKFPawn . SetHeadTrackTarget ( none ) ;
if ( MyKFPawn != none && ( MyKFPawn . CanDoSpecialMove ( SM _Evade ) || MyKFPawn . CanDoSpecialMove ( SM _Evade _Fear ) ) && PendingEvadeProjectile != none && ! PendingEvadeProjectile . bDeleteMe &&
! IsZero ( PendingEvadeProjectile . Velocity ) && CanEvade ( ) )
{
BestDir = GetBestEvadeDir ( PendingEvadeProjectile . Location , PendingEvadeProjectile . Instigator ) ;
if ( BestDir != DIR _None )
{
DoEvade ( BestDir , PendingEvadeProjectile , , 0.1 f + FRand ( ) * 0.2 f , true ) ;
}
}
PendingEvadeProjectile = None ;
}
/** Called just prior to setting MoveToEnemy */
function PreMoveToEnemy ( ) ;
function DoEvade ( byte EvadeDir , optional actor EvadeActor , optional vector DangerInstigatorLocation = vect ( 0 , 0 , 0 ) , optional float Delay , optional bool bFrightened , optional bool bTurnToThreat )
{
` AILog( GetFuncName()@EvadeDir@Pawn.Physics@Pawn.Anchor, 'Command_Evade' );
class 'AICommand_Evade' . static . Evade ( self , EvadeDir , Delay , bFrightened , , DangerInstigatorLocation ) ;
}
function DoStumble ( vector Momentum , EHitZoneBodyPart HitZoneLimb )
{
local AICommand _Attack _Melee MeleeCommand ;
// Don't stumble if we're in a panic wander state
if ( GetActiveCommand ( ) != none && AICommand _PanicWander ( GetActiveCommand ( ) ) != none )
{
return ;
}
if ( CommandList != None && GetActiveCommand ( ) . IsA ( 'AICommand_Attack_Melee' ) )
{
MeleeCommand = AICommand _Attack _Melee ( GetActiveCommand ( ) ) ;
if ( MeleeCommand != none )
{
` AILog( GetFuncName() $ "() Aborting Melee Command", 'InitAICommand' );
AbortCommand ( MeleeCommand ) ;
}
}
` AILog( GetFuncName() $ "() Aborting movement commands", 'Command_MoveToGoal' );
AbortMovementCommands ( ) ;
AbortMovementPlugIns ( ) ;
if ( CommandList == None || ! GetActiveCommand ( ) . IsA ( 'AICommand_Stumble' ) )
{
` AILog( GetFuncName() $ "() Init AICommand_Stumble", 'InitAICommand' );
class 'AICommand_Stumble' . static . Stumble ( self , Momentum , HitZoneLimb ) ;
}
}
function DoHeadlessWander ( )
{
class 'AICommand_HeadlessWander' . Static . HeadlessWander ( self ) ;
}
/** Launch the AICommand to send the KFAIController into the EMP Wander state */
function DoPanicWander ( )
{
class 'AICommand_PanicWander' . Static . PanicWander ( self ) ;
}
/** Stop doing the AICommand with the AI in the EMP Wander state */
function EndPanicWander ( )
{
local AICommand _PanicWander CurCommand ;
if ( GetActiveCommand ( ) != None )
{
CurCommand = AICommand _PanicWander ( GetActiveCommand ( ) ) ;
if ( CurCommand != None )
{
CurCommand . EndWander ( self ) ;
}
}
}
event AttitudeStateChanged ( name NewState ) { }
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* StepAside
* StepAside is a potentially useful AICommand that hasn ' t gotten much use , but should work .
* It 's similar to evading, only it' s not animation - based and instead performs a latent move
* to move the NPC a few steps away in a safe direction ( usually left / right ) .
*
* Call StepAsideFor ( ) to start the command . ChkPawn is the pawn to step aside from . If ChkPawn
* is my own pawn , a random stepaside direction will be chosen . See GetStepAsideLocation ( )
* in AICommand _StepAside .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** StepAsideFor() - starts the StepAside AI Command */
event bool StepAsideFor ( Pawn ChkPawn , Vector HitNormal )
{
local bool bResult , bStepAside , bDelayStep ;
local KFAIController AI ;
local bool bNoFocus ;
` AILog(GetFuncName()@ChkPawn);
if ( ! bIgnoreStepAside && /*(MyKFPawn.Physics != PHYS_Spider &&*/ ! MyKFPawn . IsDoingSpecialMove ( ) )
{
// step aside for players
bResult = true ;
bStepAside = true ;
// if we're dealing with another AI
AI = KFAIController ( ChkPawn . Controller ) ;
if ( AI != None && ( AI . StepAsideGoal == Pawn ) )
{
` AILog("- other AI is stepping aside for us already");
bResult = true ;
bStepAside = false ;
}
// If we are moving (11/7 changed Velocity is zero check to InLatentExecution check)
if ( Pawn . IsSameTeam ( ChkPawn ) && InLatentExecution ( LATENT _MOVETOWARD ) && ( Pawn . Velocity DOT ChkPawn . Velocity ) > 0. f )
{
bDelayStep = ShouldDelayStepAside ( ChkPawn ) ;
if ( ! bDelayStep )
{
` AILog( self $ " - moving in the same direction as pawn we bumped" );
bResult = true ;
bStepAside = false ;
}
}
if ( bStepAside && StepAsideGoal != ChkPawn )
{
// note: don't need to abort existing instances because stepaside is set bReplaceActiveSameClassInstance=true
if ( GetActiveCommand ( ) . IsA ( 'AICommand_Debug' ) )
{
bNoFocus = AICommand _Debug ( GetActiveCommand ( ) ) . bNoFocus ;
}
bResult = class 'AICommand_StepAside' . static . StepAside ( self , ChkPawn , bDelayStep , bNoFocus ) ;
}
}
return bResult ;
}
/ * * C h e c k s t o s e e i f a s t e p - a s i d e s h o u l d b e d e l a y e d b e c a u s e m y p a w n ' s m o v i n g i n t h e s a m e g e n e r a l
direction as ( or already in front of ) the pawn set to be stepped aside from * /
function bool ShouldDelayStepAside ( Pawn GoalPawn )
{
local float VelDotVel , GoalDotVel ;
local Vector VectToGoal , VelDir ;
if ( ! IsZero ( Pawn . Velocity ) && ! IsZero ( GoalPawn . Velocity ) )
{
// If moving in the same direction as pawn we bumped
VelDir = Normal ( Pawn . Velocity ) ;
VelDotVel = Normal ( GoalPawn . Velocity ) DOT VelDir ;
if ( VelDotVel > 0.3 )
{
// If pawn we bumped is in front of us already
VectToGoal = Normal ( GoalPawn . Location - Pawn . Location ) ;
GoalDotVel = VectToGoal DOT VelDir ;
if ( GoalDotVel > 0 )
{
// Just delay movement
return true ;
}
}
}
return false ;
}
/** Alerts NPC that current move has failed */
event FailedMove ( optional string Reason )
{
` AILog( "FailedMove! Reason: " $ Reason, 'PathWarning' );
` RecordAIMoveFailure(self,Pawn.Location,Pawn.Rotation,MoveGoal,"4 " $ Reason);
}
/** Optionally overrides movefocus for upcoming move toward enemy */
function Actor MoveToEnemy _GetMoveFocus ( )
{
if ( Enemy != none )
{
return Enemy ;
}
return None ;
}
function float GetEstimatedGroundSpeedForMoveTimout ( bool bDoingLeadinOutWalk )
{
local float GroundSpeed ;
GroundSpeed = Pawn . GroundSpeed ;
if ( MyKFPawn != none )
{
GroundSpeed = MyKFPawn . NormalGroundSpeed ;
}
return GroundSpeed ;
}
function float GetMoveTimeOutDuration ( vector dest , bool bDoingLeadInOutWalk )
{
local float dist ;
local float GroundSpeed ;
local float Duration ;
if ( Pawn == none )
{
return 5. f ;
}
else
{
GroundSpeed = Pawn . GroundSpeed ;
}
if ( MyKFPawn != none )
{
GroundSpeed = MyKFPawn . NormalGroundSpeed ;
}
Dist = VSize ( Pawn . Location - Dest ) ;
if ( Pawn . bIsWalking || bDoingLeadInOutWalk )
{
GroundSpeed *= Pawn . WalkingPct ;
}
Duration = FMax ( 0.5 f , 2.0 f * ( Dist / GroundSpeed ) ) ;
` AILog( GetFuncName() $ "() returning " $ Duration $ " for dist of " $ Dist, 'Command_MoveToGoal' );
return Duration ;
}
/** Determines if we can even consider sprinting */
function SetCanSprint ( bool bNewSprintStatus )
{
bCanSprint = bNewSprintStatus ;
if ( ! bCanSprint || bSprintingDisabled )
{
MyKFPawn . bIsSprinting = false ;
}
}
/** Determines if we can't at the moment */
function SetSprintingDisabled ( bool bNewSprintStatus )
{
bSprintingDisabled = bNewSprintStatus ;
if ( MyKFPawn != none && ( ! bCanSprint || bSprintingDisabled ) )
{
MyKFPawn . bIsSprinting = false ;
}
}
/** Determines if we can sprint when damaged */
function SetCanSprintWhenDamaged ( bool bNewSprintDamagedStatus )
{
bCanSprintWhenDamaged = bNewSprintDamagedStatus ;
if ( ( ! bCanSprint && ! bCanSprintWhenDamaged ) || bSprintingDisabled )
{
MyKFPawn . bIsSprinting = false ;
}
}
function bool CanSetSprinting ( bool bNewSprintStatus )
{
if ( bNewSprintStatus && ( ( ! bCanSprint && ! bCanSprintWhenDamaged ) || bSprintingDisabled ) )
{
return false ;
}
return true ;
}
/ * * T i m e r f u n c t i o n c a l l e d d u r i n g l a t e n t m o v e s t h a t d e t e r m i n e s w h e t h e r N P C s h o u l d s p r i n t o r s t o p s p r i n t i n g . T h i s i s o f t e n
overridden in child classes * /
function bool ShouldSprint ( )
{
local float DistToEnemy ;
local name AttackTag ;
if ( MyKFPawn != none && MyKFPawn . IsAliveAndWell ( ) && Enemy != none && Enemy . IsAliveAndWell ( ) )
{
// Don't allow sprinting when blocking attacks
if ( MyKFPawn . IsDoingSpecialMove ( SM _Block ) )
{
return false ;
}
// Sprint if we've reached the frustration threshold
if ( IsFrustrated ( ) )
{
return true ;
}
if ( SprintWithinEnemyRange . X > 0. f && SprintWithinEnemyRange . Y > 0. f )
{
DistToEnemy = VSize ( Enemy . Location - Pawn . Location ) ;
if ( DistToEnemy > SprintWithinEnemyRange . X && DistToEnemy < SprintWithinEnemyRange . Y )
{
return true ;
}
else
{
return false ;
}
}
else
{
return false ; // Old bug undiscovered until now (6.17.14), keep an eye on npcs no longer sprinting when they should
}
}
if ( MyKFPawn == none || Enemy == none /*|| !bDirectMoveToGoal*/ )
{
return false ;
}
if ( Enemy . Velocity dot ( Pawn . Location - Enemy . Location ) < 0. f )
{
//if( !InMeleeRange( Enemy.Location ) )
if ( ! MyKFPawn . InAnyAttackTagRange ( Enemy . Location , AttackTag ) )
{
return true ;
}
}
else if ( ! MyKFPawn . InAnyAttackTagRange ( Enemy . Location , AttackTag ) && VSize ( Pawn . Location - Enemy . Location ) > 300. f )
{
return true ;
}
if ( VSize ( Enemy . Location - Pawn . Location ) <= 512. f )
{
return false ;
}
return false ;
}
/ * *
* Update sprint settings based on Frustration
* /
function UpdateSprintFrustration ( optional byte bForceFrustrationState = 255 )
{
if ( FrustrationThreshold > 0 )
{
if ( bForceFrustrationState == 1 || ( IsFrustrated ( ) && bForceFrustrationState != 0 ) )
{
bCanSprint = true ;
}
else
{
// can't use true default as it's modified by difficulty
bCanSprint = bDefaultCanSprint ;
}
}
}
function bool IsFrustrated ( )
{
// Forced frustration, controlled by AI director
if ( MyAIDirector . bForceFrustration )
{
return true ;
}
// Forced frustration, controlled by individual AI
if ( bForceFrustration )
{
return true ;
}
if ( FrustrationThreshold > 0 && MyKFGameInfo . MyKFGRI != None && MyKFGameInfo . MyKFGRI . AIRemaining <= FrustrationThreshold )
{
if ( LastFrustrationCheckTime == 0 )
{
LastFrustrationCheckTime = WorldInfo . TimeSeconds ;
}
if ( ` TimeSince(LastFrustrationCheckTime) >= FrustrationDelay )
{
return true ;
}
}
return false ;
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pathfinding events and related notifications
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Notification from AICommand::Popped that it has completed */
function NotifyCommandFinished ( AICommand FinishedCommand ) ;
/** Notification from the flee command that the flee completed successfully (was not interrupted) */
function NotifyFleeFinished ( optional bool bAcquireNewEnemy = true ) ;
/** Notification from the move command that we've arrived at an intermediate destination */
function NotifyReachedLatentMoveGoal ( ) ;
event NotifyLatentPostPhysWalking ( )
{
if ( CachedAICommandList != none )
{
/** Let the current AICommand, if any, handle this. */
CachedAICommandList . NotifyLatentPostPhysWalking ( ) ;
}
}
` if( ` _ _TW _PATHFINDING _ )
event NotifyFailMove ( string Reason )
{
//`RecordAIMoveFailure(self,Pawn.Location,Pawn.Rotation,MoveGoal,"5 "$Reason);
// if( GetActiveKFCommand() != none )
// {
// //GetActiveKFCommand().UpdateHistoryString( "[NotifyFailMove: "$Reason$"]" );
// }
` AILog( "NotifyFailMove, Reason: " $ Reason, 'PathWarning' );
}
` endif //__TW_PATHFINDING_
/** Notification from gameinfo that paths have changed */
event NotifyPathChanged ( )
{
` AILog( GetFuncName() $ "() Command: " $ GetActiveCommand(), 'PathWarning' );
}
/** Returns true if the actor is currently directly reachable */
function bool IsValidDirectMoveGoal ( Actor A )
{
// if( Pawn(A) != none && !FastTrace(A.Location, Pawn.Location, Pawn.GetCollisionExtent()) )
// {
// return false;
// }
if ( ActorReachable ( A ) )
{
return true ;
}
return false ;
}
final function SetFailedPathToEnemy ( Pawn TestEnemy , optional float Offset )
{
` RecordAIMoveFailure(self,Pawn.Location,Pawn.Rotation,MoveGoal,"7 SetFailedpathToEnemy");
` AILog( GetFuncName() $ "() TestEnemy: " $ TestEnemy $ " Offset: " $ Offset, 'PathWarning' );
// Below might come in handy, but haven't tested it enough.
// if( Pawn.Anchor != none )
// {
// `AILog( GetFuncName()$"() calling InvalidateAnchor (Anchor: "$Pawn.Anchor$")", 'PathWarning' );
// //InvalidateAnchor( Pawn.Anchor );
// }
}
/** Used by pathfinding code to allow NPC to detour to special navigation points (pickups, etc.) */
event bool AllowDetourTo ( NavigationPoint N )
{
return true ;
}
/ * * F o r c e s N P C t o r e - e v a l u a t e i n - p r o g r e s s m o v e , t o s e e i f t h e g o a l i s n o w d i r e c t l y r e a c h a b l e , o t h e r w i s e
the pawn will need pathfind to the goal . * /
event FindDirectPath ( )
{
if ( CachedAICommandList != none )
{
/** Let the current AICommand, if any, handle this. */
CachedAICommandList . FindDirectPath ( ) ;
}
SetDirectPathCheckTime ( ) ;
}
/ * * A l e r t f r o m m o v e m e n t c o d e t h a t p a t h i s b l o c k e d b y a n a c t o r w h i c h h a s i t s b B l o c k s N a v i g a t i o n f l a g s e t t o t r u e .
Note that if ShouldIgnoreNavigationBlockingFor ( ) returns true for BlockedBy , this event will not be called .
See UReachSpec : : PrepareForMove ( ) * /
event bool HandlePathObstruction ( Actor BlockedBy )
{
// local Pawn BlockPawn;
local bool bResult ;
if ( LastObstructionTime == 0. f || ` TimeSince(LastObstructionTime) > 0.7f )
{
LastObstructionTime = WorldInfo . TimeSeconds ;
` AILog( GetFuncName()@BlockedBy@ ` TimeSince ( LastObstructionTime ) , 'HandlePathObstruction' ) ;
// Let the current AICommand deal with the obstruction.
if ( CachedAICommandList != None )
{
bResult = CommandList . HandlePathObstruction ( BlockedBy ) ;
}
}
return bResult ;
}
function bool HandleZedBlockedPath ( ) ;
event FailMove ( string Reason )
{
//`RecordAIMoveFailure(self,Pawn.Location,Pawn.Rotation,MoveGoal,Reason);
` AILog( GetFuncName() $ "() REASON: " $ Reason, 'PathWarning' );
}
/** Event from latent movement code notifying that current move goal is not reachable */
event MoveUnreachable ( Vector AttemptedDest , Actor AttemptedTarget )
{
` AILog( GetFuncName() $ "()" $ AttemptedDest@AttemptedTarget, 'PathWarning' );
if ( CachedAICommandList != None )
{
CachedAICommandList . MoveUnreachable ( AttemptedDest , AttemptedTarget ) ;
}
if ( KfMovementPlugin != none )
{
KfMovementPlugin . MoveUnreachable ( AttemptedDest , AttemptedTarget ) ;
}
}
/** Will cause AI to briefly pause and sets ReevaluatePath to true so if the AI is moving it will re-path */
event ForcePauseAndRepath ( optional Actor InInstigator )
{
//`Log( GetFuncName()@InInstigator );
bReevaluatePath = true ;
NotifyNeedRepath ( ) ;
class 'AICommand_Pause' . static . Pause ( self , 0.25 f + FRand ( ) ) ;
}
/** Notification that any existing pathing should be checked and rebuilt */
function NotifyNeedRepath ( )
{
` AILog( GetFuncName() $ "()", 'PathWarning' );
if ( CachedAICommandList != None )
{
CachedAICommandList . NotifyNeedRepath ( ) ;
}
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Anchor handling
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/ * * I n v a l i d a t e s p a w n ' s c u r r e n t a n c h o r , m a k i n g i t u n u s a b l e b y p a w n f o r a b r i e f d u r a t i o n a n d s e t s c u r r e n t
anchor to none , forcing the pawn to find a new valid anchor on next pathfind attempt ( with the
expectation that the newly invalidated anchor will be skipped since it ' s in the invalid anchor list * /
final function InvalidateAnchor ( NavigationPoint Nav )
{
local int Idx ;
Idx = InvalidAnchorList . Find ( 'InvalidNav' , Nav ) ;
` AILog( GetFuncName()@Nav@Idx, 'PathWarning' );
if ( Idx < 0 )
{
Idx = InvalidAnchorList . Length ;
InvalidAnchorList . Length = Idx + 1 ;
InvalidAnchorList [ Idx ] . InvalidNav = Nav ;
InvalidAnchorList [ Idx ] . InvalidTime = WorldInfo . TimeSeconds ;
` AILog( GetFuncName() $ " Invalidating anchor (" $ Nav $ ") - setting anchor to NONE", 'PathWarning' );
// Clear the old anchor so we need to get a new one
Pawn . SetAnchor ( none ) ;
}
}
final function NavigationPoint GetFallbackAnchor ( )
{
local NavigationPoint LastAnchor , ResultAnchor ;
local float AnchorDist ;
` AILog( GetFuncName() $ "() trying to get fallback anchor", 'PathWarning' );
// block the current anchor if possible to prevent it from being reacquired as an anchor
if ( Pawn . Anchor != None && ! Pawn . Anchor . bBlocked )
{
` AILog( GetFuncName() $ "() setting Pawn anchor (" $ Pawn.Anchor $ ") to bBlocked to true!", 'PathWarning' );
LastAnchor = Pawn . Anchor ;
LastAnchor . bBlocked = true ;
}
// look for the nearest anchor
ResultAnchor = Pawn . GetBestAnchor ( Pawn , Pawn . Location , true , true , AnchorDist ) ;
// unblock the last anchor
if ( LastAnchor != None )
{
LastAnchor . bBlocked = false ;
if ( LastAnchor == ResultAnchor )
{
` AILog( "ERROR! LastAnchor == ResultAnchor", 'PathWarning' );
}
}
if ( ResultAnchor == None )
{
FailedToFindFallbackAnchor ( ) ;
}
return ResultAnchor ;
}
final function FailedToFindFallbackAnchor ( )
{
` AILog( self $ " FailedToFindFallbackAnchor!", 'PathWarning' );
// Haven't actually gotten a pawn yet
if ( Pawn == None )
return ;
}
/** Returns true if Nav is found in the RouteCache (i.e, is it part of NPC's current path) */
function bool IsNavInRouteCache ( NavigationPoint Nav )
{
local int i ;
for ( i = 0 ; i < RouteCache . Length ; i ++ )
{
if ( RouteCache [ i ] == Nav )
{
return true ;
}
}
return false ;
}
/ * *
* Event called when specific navigation points ( with bProbeNotifyOnAddToRouteCache true ) are
* added to this controller ' s route cache .
* /
event NotifyOnAddToRouteCache ( NavigationPoint Nav ) { }
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* FALLING , PHYS _FALLING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Called when NPC reaches a ledge, setting bCanJump to true here will allow NPC to continue move despite pending fall
event MayFall ( bool bFloor , vector FloorNormal )
{
` AILog( "MayFall Event! bFloor: " $ bFloor $ " Phys: " $ Pawn.Physics $ " MoveTarget: " $ MoveTarget $ " Reachable: " $ ActorReachable(Enemy), 'Falling' );
if ( ! bPlannedJump )
{
//msg( "MayFall Event! bFloor: "$bFloor$" Phys: "$Pawn.Physics$" MoveTarget: "$MoveTarget$" Reachable: "$ActorReachable(Enemy) );
Pawn . JumpZ = 100. f ;
}
else
{
Pawn . JumpZ = Pawn . Default . JumpZ ;
}
Pawn . bCanJump = true ;
}
/** NPC has attempted to jump over an obstruction after bumping into it */
event JumpedOverWall ( vector WallHitNormal , optional actor Wall )
{
` AiLog(GetFuncName() @ " WallHitNormal: " @ WallHitNormal @ " Wall: " @Wall );
/* Below untested - enable to lower jump over wall frequency */
//MyKFPawn.bCanJumpOverWalls = false;
//SetTimer( 2.f * FRand() + 0.25f, false, nameof(Timer_EnableJumpOverWalls), self );
` RecordAIWall( ` StatID ( AI _JUMPOVERWALL ) , self , Pawn . Location , Pawn . Rotation , Wall , "HN: " $string ( WallHitNormal ) ) ;
}
/** Re-Enables pawn's ability to jump over obstructions */
function Timer _EnableJumpOverWalls ( )
{
MyKFPawn . bCanJumpOverWalls = true ;
}
function Vector GetDropEdgeLeapVelocity ( )
{
return DropEdgeLeapVelocity ; // Vect( 200.0f,0.0f, 20.0f );
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Sight / Hearing / Bump
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function RecordLeapToWall ( )
{
//`RecordAILeapToWall( self, Pawn.Location, Pawn.Rotation, "Base: "$Pawn.Base );
}
function RecordHitWall ( actor Wall )
{
//`RecordAIHitWall( self, Pawn.Location, Pawn.Rotation, Wall, "SS: "$MyKFPawn.IsUsingSuperSpeed()$" WallGeo:"$Wall.bWorldGeometry$" Crawlable:"$Wall.bCrawlable );
}
function bool NotifyBaseChange ( actor NewBase , vector NewFloor )
{
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
if ( CachedAICommandList . NotifyBaseChange ( NewBase , NewFloor ) )
{
return true ;
}
}
return false ;
}
/** Notification from ProcessLanded() that pawn has stopped falling */
event bool NotifyLanded ( vector HitNormal , Actor FloorActor )
{
` AILog( GetFuncName() $ " - HitNormal: " $ HitNormal $ " - FloorActor: " $ FloorActor, 'LandedEvent' );
if ( MyKFPawn != none )
{
MyKFPawn . BaseEyeHeight = MyKFPawn . default . BaseEyeHeight ;
// Reset JumpZ (might not be necessary anymore)
Pawn . JumpZ = Pawn . Default . JumpZ ;
// If movement AI is not active, reset acceleration to zero to prevent sliding
if ( bPreparingMove )
{
Pawn . Acceleration = vect ( 0 , 0 , 0 ) ;
}
}
bPlannedJump = false ;
// Reset NotifyJumpApex event notification, turned off by PHYS_Falling
bNotifyApex = Default . bNotifyApex ;
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
//`AILog( "NotifyLanded: "$FloorActor$" - while I'm moving to goal, notifying "$CommandList$" and letting it handle this event", 'NotifyLanded' );
return CachedAICommandList . NotifyLanded ( HitNormal , FloorActor ) ;
}
return super . NotifyLanded ( HitNormal , FloorActor ) ;
}
/** Notification that my current enemy is surrounded by other Zeds */
event bool EnemyIsSurrounded ( )
{
` AILog( "EnemyIsSurrounded! (" $ Enemy $ ")", 'EnemyStatus' );
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
//`AILog( "EnemyIsSurrounded", 'Command_MoveToEnemy' );
return CachedAICommandList . EnemyIsSurrounded ( ) ;
}
return false ;
}
/** Notification that a Husk is going to commit suicide, providing chance to move away */
function bool NotifyHuskSuicide ( KFPawn _Monster Husk )
{
return false ; // Temp disabled
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
if ( CachedAICommandList . NotifyHuskSuicide ( Husk ) )
{
return true ;
}
}
if ( ! IgnoreNotifies ( ) )
{
// class'AICommand_Hide'.static.HideFrom( self, Husk,, false );
// class'AICommand_Scatter'.static.ScatterFrom( self, Husk,, true );
}
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* HitWall Handling
* NotifyHitWall is enabled by default . Note that if the pawn ' s bDirectHitWall flag is true ,
* this will be bypassed and the pawn ' s HitWall event will be called directly .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Timer which re-enables SeePlayer event polling */
final function EnableNotifyHitWall ( )
{
` AILog( GetFuncName() $ "() Enabling NotifyHitWall event", 'HitWall' );
Enable ( 'NotifyHitWall' ) ;
}
/** Disable this NPC's SeePlayer polling for DisabledTime seconds */
final function DisableNotifyHitWall ( optional float DisabledTime = 0.2 f )
{
//`AILog( GetFuncName()$"() disabling NotifyHitWall for "$DisabledTime$" seconds", 'HitWall' );
Disable ( 'NotifyHitWall' ) ;
if ( DisabledTime > 0. f )
{
SetTimer ( DisabledTime , false , nameof ( EnableNotifyHitWall ) ) ;
}
}
/ * * N o t i f i c a t i o n t h a t I ' v e r u n i n t o a w a l l . I f t h e p a w n ' s b D i r e c t H i t W a l l f l a g i s t r u e , t h i s w i l l b e b y p a s s e d a n d
the pawn ' s HitWall ( ) event will be called directly . * /
event bool NotifyHitWall ( vector HitNormal , actor Wall )
{
local KFDestructibleActor KFDA ;
local bool bEmerging ;
LastWallHitNormal = HitNormal ;
LastHitWall = Wall ;
LastNotifyHitWallTime = WorldInfo . TimeSeconds ;
` AiLog( GetFuncName() @ " Wall: " @ Wall @ " HitNormal: " @ HitNormal );
if ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( SM _Emerge ) )
{
bEmerging = true ;
}
if ( ! Wall . bStatic && Wall . bCollideActors && Wall . bCanBeDamaged )
{
KFDA = KFDestructibleActor ( Wall ) ;
if ( KFDA != none )
{
MyKFPawn . HandleDestructibleBump ( KFDA , HitNormal ) ;
if ( bEmerging )
{
return true ;
}
if ( ActorEnemy != KFDA
&& KFDA . bBlockActors
&& ! MyKFPawn . IsDoingSpecialMove ( )
&& ( LastDestructibleBumpTime == 0. f || ` TimeSince(LastDestructibleBumpTime) > 1.f)
&& CanAttackDestructibles ( )
&& KFDA . HasAnyHealth ( )
&& ! GetActiveCommand ( ) . IsA ( 'AICommand_Melee' ) )
{
// FOV test
if ( vector ( MyKFPawn . Rotation ) dot Normal ( KFDA . Location - MyKFPawn . Location ) >= 0.67 f )
{
AIZeroMovementVariables ( ) ;
NotifyAttackActor ( Wall ) ;
}
}
LastDestructibleBumpTime = WorldInfo . TimeSeconds ;
}
}
/** I shouldn't be colliding if I'm emerging, so ignore notification but return true to prevent move adjusting in native code */
if ( bEmerging )
{
return true ;
}
/** Return whatever the current AICommand wants me to */
if ( CachedAICommandList != none )
{
return CachedAICommandList . NotifyHitWall ( HitNormal , Wall ) ;
}
else
{
DisableNotifyHitWall ( 0.2 f ) ;
}
return Super . NotifyHitWall ( HitNormal , Wall ) ;
}
/** Pawn has bumped into a wall while falling - unusual given KF2 NPCs have very little air control */
event NotifyFallingHitWall ( vector HitNormal , actor Wall )
{
if ( CachedAICommandList != none )
{
` AILog( "NotifyFallingHitWall: " $ Wall $ " - while I'm moving to goal, notifying " $ CommandList $ " and letting it handle this event", 'HitWall' );
CachedAICommandList . NotifyFallingHitWall ( HitNormal , Wall ) ;
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* SEEPLAYER
* KF2 NPCs do not heavily rely on SeePlayer ( ) and EnemyNotVisible ( ) , since the NPCs are
* omniscient when it comes to choosing an enemy . If the NPCs were to begin in a state where
* they randomly wander about , they would then need to rely more on these events .
* Currently they are used for some miscellaneous things , like using SeePlayer ( ) when
* attacking a door and using that notification to stop attacking the door and target the
* newly spotted enemy instead .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Timer which re-enables SeePlayer event polling */
final event EnableSeePlayer ( )
{
` AILog( GetFuncName() $ "() Enabling SeePlayer event", 'SeePlayer' );
if ( ! bHasDebugCommand )
{
Enable ( 'SeePlayer' ) ;
}
}
/** Disable this NPC's SeePlayer polling for DisabledTime seconds */
final event DisableSeePlayer ( optional float DisabledTime )
{
` AILog( GetFuncName() $ "() disabling SeePlayer polling for " $ DisabledTime $ " seconds", 'SeePlayer' );
Disable ( 'SeePlayer' ) ;
if ( DisabledTime > 0. f )
{
SetTimer ( DisabledTime , false , nameof ( EnableSeePlayer ) ) ;
}
}
/** Timer which re-enables SeePlayer event polling */
final event EnableEnemyNotVisible ( )
{
` AILog( GetFuncName() $ "() Enabling EnemyNotVisible event", 'EnemyNotVisible' );
Enable ( 'EnemyNotVisible' ) ;
}
/** Disable this NPC's SeePlayer polling for DisabledTime seconds */
final event DisableEnemyNotVisible ( optional float DisabledTime = 2. f )
{
` AILog( GetFuncName() $ "() disabling EnemyNotVisible polling for " $ DisabledTime $ " seconds", 'EnemyNotVisible' );
Disable ( 'EnemyNotVisible' ) ;
if ( DisabledTime > 0. f )
{
SetTimer ( DisabledTime , false , nameof ( EnableEnemyNotVisible ) ) ;
}
}
function bool IsPawnVisibleViaTrace ( Pawn PawnToCheck , optional bool bUsePawnRotation )
{
local Vector TestLocation ;
local Rotator Rot ;
Rot = Pawn . Rotation ;
TestLocation = PawnToCheck . GetPawnViewLocation ( ) ;
if ( ! bUsePawnRotation )
{
Rot = Rotator ( PawnToCheck . Location - Pawn . Location ) ;
}
return CanSeeByPoints ( Pawn . Location , TestLocation , Rot ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* HEARING
* Use MakeNoise ( ) to simulate noise for NPCs to "hear."
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Timer which re-enables HearNoise event polling. */
final event EnableHearNoise ( )
{
Enable ( 'HearNoise' ) ;
` AILog( GetFuncName() $ "() Enabled HearNoise event", 'HearNoise' );
}
/** Disable this NPC's hearing for DisabledTime seconds */
final event DisableHearNoise ( optional float DisabledTime = 1. f )
{
if ( DisabledTime <= 0. f )
{
DisabledTime = 1. f + FRand ( ) ;
}
` AILog( GetFuncName() $ "() disabling noise hearing for " $ DisabledTime $ " seconds", 'HearNoise' );
Disable ( 'HearNoise' ) ;
SetTimer ( DisabledTime , false , nameof ( EnableHearNoise ) ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* BUMP HANDLING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Records bump related gameplay event */
function RecordBump ( actor Other )
{
` RecordAIBump( ` StatID ( AI _BUMP ) , self , Pawn . Location , Pawn . Rotation , Other , "SSpeed: " $MyKFPawn . IsUsingSuperSpeed ( ) ) ;
}
/** Timer which re-enables Bump event polling */
final event EnableBump ( )
{
` AILog( GetFuncName() $ " Enabling Bump Events" , 'BumpEvent' );
Enable ( 'NotifyBump' ) ;
}
/** Disable this NPC's Bump event polling for DisabledTime seconds */
final event DisableBump ( optional float DisabledTime = 0.5 f )
{
DisabledTime = FMax ( 0.1 f , DisabledTime ) ;
` AILog( GetFuncName() $ "() disabling Bump polling for " $ DisabledTime $ " seconds", 'BumpEvent' );
Disable ( 'NotifyBump' ) ;
if ( DisabledTime > 0. f )
{
SetTimer ( DisabledTime , false , nameof ( EnableBump ) ) ;
}
}
simulated event Touch ( Actor Other , PrimitiveComponent OtherComp , vector HitLocation , vector HitNormal )
{
` AILog( GetFuncName() $ " - Other: " $ Other $ " OtherComp" $ OtherComp $ " - HitLocation: " $ HitLocation $ " - HitNormal: " $ HitNormal, 'TouchEvent' );
if ( CachedAICommandList != none )
{
CachedAICommandList . NotifyTouch ( Other , OtherComp , HitLocation , HitNormal ) ;
}
if ( KfMovementPlugin != none )
{
//KfMovementPlugin.NotifyTouch( Other, OtherComp, HitLocation, HitNormal);
}
Super . Touch ( Other , OtherComp , HitLocation , HitNormal ) ;
}
/** Override is currently only used if bSpecialBumpHandling is true in .ini file */
simulated function Tick ( FLOAT DeltaTime )
{
Super . Tick ( DeltaTime ) ;
if ( GetIsInZedVictoryState ( ) )
{
return ;
}
if ( bSpecialBumpHandling && Role == ROLE _Authority && MyKFPawn != None && MyKFPawn . Health >= 0 && ! MyKFPawn . IsDoingSpecialMove ( ) )
{
SpecialBumpHandling ( DeltaTime ) ;
}
// Regularly check to see if this zed is stuck
if ( Role == ROLE _Authority && MyKFPawn != none && MyKFPawn . Health > 0 && ( ( ` TimeSince(LastStuckCheckTime) > StuckCheckInterval
&& ! MyKFPawn . IsDoingSpecialMove ( ) ) || ( MyKFPawn . Physics == PHYS _Falling && MyKFPawn . Velocity . Z == 0 ) ) )
{
EvaluateStuckPossibility ( DeltaTime ) ;
}
// Regularly check to see if this AI can teleport closer to the enemy
if ( bCanTeleportCloser && PendingDoor == none && Role == ROLE _Authority && MyKFPawn != none && MyKFGameInfo . MyKFGRI != None
&& MyKFPawn . Health > 0 && ` TimeSince(LastTeleportCheckTime) > TeleportCheckInterval && !MyKFPawn.IsDoingSpecialMove()
&& MyKFGameInfo . MyKFGRI . AIRemaining >= AIRemainingTeleportThreshold )
{
EvaluateTeleportPossibility ( DeltaTime ) ;
}
}
function EvaluateTeleportPossibility ( float DeltaTime )
{
LastTeleportCheckTime = WorldInfo . TimeSeconds ;
// Teleport if we have an enemy and we're superspeeding and we haven't teleported in a while
if ( Enemy != none && MyKFPawn != none && MyKFPawn . IsUsingSuperSpeed ( ) )
{
if ( ` TimeSince(MyKFPawn.LastLOSOrRelevantTime) > HiddenRelocateTeleportThreshold
&& ` TimeSince(LastTeleportTime) > TeleportCooldown
&& ` TimeSince(MyKFPawn.SpawnTime) > PostSpawnRelocateTeleportCooldown)
{
RelocateTeleport ( ) ;
}
}
}
/** Evaluate if this zed might be getting stuck, and then call functions to handle it */
function EvaluateStuckPossibility ( float DeltaTime )
{
local float DistToEnemySquared ;
local vector OldStuckCheckLocation ;
local bool bSeenRecently ;
local int i ;
LastStuckCheckTime = WorldInfo . TimeSeconds ;
OldStuckCheckLocation = LastStuckCheckLocation ;
LastStuckCheckLocation = Pawn . Location ;
// Special big hack here. This fixes the bug where zeds would get stuck
// floating in air unable to move. Someday perhaps we'll track down the
// source of that issue and won't need this code - Ramm
if ( MyKFPawn . Physics == PHYS _Falling && MyKFPawn . Velocity . Z == 0 )
{
//`log(self@GetFuncName()$" falling with no Z velocity!!!");
FallingStuckNoZVelocityTime += DeltaTime ;
MyKFPawn . Velocity = MyKFPawn . GroundSpeed * Normal ( GetDestinationPosition ( ) - MyKFPawn . Location ) ;
MyKFPawn . Acceleration = MyKFPawn . AccelRate * Normal ( GetDestinationPosition ( ) - MyKFPawn . Location ) ;
// Continue on and try and get unstuck if this time is greather than the
// StuckCheckInterval, otherwise just return out and see if the hack will
// get us unstuck!
if ( FallingStuckNoZVelocityTime < StuckCheckInterval )
{
return ;
}
}
else
{
FallingStuckNoZVelocityTime = 0 ;
}
if ( ` TimeSince(LastDamageTime_Taken) < 5.0 )
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
// Don't check stuck if we recently melee'd
if ( ` TimeSince(LastAttackTime_Melee) < 5.0 )
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
// Don't check stuck if we just teleported
if ( ` TimeSince(LastTeleportTime) < 5.0 )
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
if ( ` TimeSince(LastSpecialMoveEndTime) < 5.0 )
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
bSeenRecently = ! MyKFPawn . IsUsingSuperSpeed ( ) ;
// Don't do any stuck checking if we're close to an enemy
if ( Enemy != none )
{
DistToEnemySquared = VSizeSq ( Enemy . Location - Pawn . Location ) ;
// Modified to only allow it to bypass the rest of the stuck checks for 5 seconds
// if we are down to only a few zeds. Would never run HandleStuck() on stuck zeds
// if any players were too close to them.
if ( DistToEnemySquared < StuckCheckEnemyDistThreshholdSquared )
{
if ( MyKFGameInfo . MyKFGRI . AIRemaining > 5 || TotalStuckCheckCloseRangeTime < 5.0 f )
{
if ( LastStuckCheckCloseRangeTime > 0. f )
{
TotalStuckCheckCloseRangeTime += ` TimeSince(LastStuckCheckCloseRangeTime);
}
LastStuckCheckCloseRangeTime = WorldInfo . TimeSeconds ;
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
}
else
{
TotalStuckCheckCloseRangeTime = 0. f ;
LastStuckCheckCloseRangeTime = 0. f ;
}
}
// Don't check stuck if we're line of site pathing to the enemy
if ( Enemy != none && MoveTarget == Enemy && RouteCache . Length <= 0 )
{
//`log(self@GetFuncName()$"I'm on final approach to enemy, just keep going");
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
// Don't check stuck if we're waiting outside a welded door while it's being beaten down
for ( i = 0 ; i < RouteCache . Length ; i ++ )
{
if ( RouteCache [ i ] != None )
{
if ( KFDoorMarker ( RouteCache [ i ] ) != none && KFDoorMarker ( RouteCache [ i ] ) . MyKFDoor . WeldedShut ( )
&& VSizeSq2d ( RouteCache [ i ] . Location - Pawn . Location ) < 1000000 ) // 10 Meters
{
//`log(self@GetFuncName()$"I'm trying to pass through a welded door. Just chill here");
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
}
}
// See if the zed isn't moving around in the XY plane while walking
if ( MyKFPawn . Physics != PHYS _Falling && VSizeSq2d ( MyKFPawn . Velocity ) < StuckVelocityThreshholdSquared )
{
if ( ! IsZero ( OldStuckCheckLocation ) )
{
//`log(self$" XY Move since last check: "$VSizeSq2d(Pawn.Location - OldStuckCheckLocation)$" XYMoveStuckThresholdSquared "$XYMoveStuckThresholdSquared);
if ( VSizeSq2d ( Pawn . Location - OldStuckCheckLocation ) > XYMoveStuckThresholdSquared )
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
}
else
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
StuckPossiblity += 0.5 ;
if ( StuckPossiblity > StuckPossiblityThreshhold )
{
//`log(self$" thinks he's stuck!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" 2d Velocity "$VSize2d(MyKFPawn.Velocity)$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
StuckPossiblity = 0 ;
HandleStuck ( ) ;
DumpCommandStack ( ) ;
DumpCommandHistory ( ) ;
}
else
{
//`log(self$" thinks he's getting stuck!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" 2d Velocity "$VSize2d(MyKFPawn.Velocity)$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
}
}
// See if the zed is stuck while falling
else if ( MyKFPawn . Physics == PHYS _Falling )
{
// Check if jumping up and down in the same place
if ( MyKFPawn . Velocity . Z != 0 )
{
if ( ! IsZero ( OldStuckCheckLocation ) )
{
//`log(self$" XY Move since last check: "$VSizeSq2d(Pawn.Location - OldStuckCheckLocation)$" XYMoveStuckThresholdSquared "$XYMoveStuckThresholdSquared);
if ( VSizeSq2d ( Pawn . Location - OldStuckCheckLocation ) < XYMoveStuckThresholdSquared )
{
if ( bSeenRecently )
{
StuckPossiblity += 1.0 ;
}
else
{
StuckPossiblity += 0.5 ;
}
if ( StuckPossiblity > StuckPossiblityThreshhold )
{
//`log(self$" is falling stuck because of XY!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" Falling Velocity "$MyKFPawn.Velocity.Z$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
StuckPossiblity = 0 ;
HandleStuck ( ) ;
}
else
{
//`log(self$" thinks he's falling stuck because of XY!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" Falling Velocity "$MyKFPawn.Velocity.Z$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
}
}
}
else
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
return ;
}
}
// Falling, but no velocity so possibly stuck in place
else
{
if ( bSeenRecently )
{
StuckPossiblity += 2.0 ;
}
else
{
StuckPossiblity += 1.0 ;
}
if ( StuckPossiblity > StuckPossiblityThreshhold )
{
//`log(self$" is falling stuck!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" Falling Velocity "$MyKFPawn.Velocity.Z$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
StuckPossiblity = 0 ;
HandleStuck ( ) ;
}
else
{
//`log(self$" thinks he's falling stuck!!!!!!!!!!!!!!!!! StuckPossiblity: "$StuckPossiblity$" Falling Velocity "$MyKFPawn.Velocity.Z$" Physics: "$Pawn.GetPhysicsName());
//`log(self$" MoveTarget: "$GetItemName(String(MoveTarget))$" MoveTimer: "$MoveTimer$" RouteGoal: "$GetItemName(string(RouteGoal))$" RouteDist: "$RouteDist$" bPreparingMove: "$bPreparingMove);
}
}
}
else
{
StuckPossiblity = 0 ;
bTryingToGetUnstuck = false ;
}
}
/** Handle a zed that thinks it's stuck. Teleport it to a spawn volume, or handle it some other way */
function HandleStuck ( )
{
local bool bSeenRecently ;
if ( ! bTryingToGetUnstuck )
{
LastStuckTime = WorldInfo . TimeSeconds ;
bTryingToGetUnstuck = true ;
}
DumpCommandStack ( ) ;
DumpCommandHistory ( ) ;
bSeenRecently = ! MyKFPawn . IsUsingSuperSpeed ( ) ;
//`log(self@GetFuncName()$" bSeenRecently = "$bSeenRecently$" `TimeSince(LastLOSOrRelevantTime): "$`TimeSince(MyKFPawn.LastLOSOrRelevantTime)$" TimeSeconds: "$WorldInfo.TimeSeconds$" LastLOSOrRelevantTime: "$MyKFPawn.LastLOSOrRelevantTime);
if ( bSeenRecently )
{
if ( AmIAllowedToSuicideWhenStuck ( ) )
{
//`log(self$" killed itself because it was stuck and being seen!!!");
//ScreenMessagePlayer(self$" killed itself because it was stuck and being seen!!!");
StuckSuicide ( ) ;
}
else if ( StuckTeleportToPathNode ( ) )
{
//`log(self$" teleported because it was stuck, and seen, but couldn't suicide!!!");
//ScreenMessagePlayer(self$" teleported because it was stuck, and seen, but couldn't suicide!!!");
}
else if ( StuckTeleportToSpawnVolume ( ) )
{
//`log(self$" teleported to spawn volume because it was stuck, and seen, but couldn't suicide and couldn't teleport to pathnode!!!");
//ScreenMessagePlayer(self$" teleported to spawn volume because it was stuck, and seen, but couldn't suicide and couldn't teleport to pathnode!!!");
}
else
{
//`log(self$" stuck but can't teleport to spawn or nav or suicide and I've been seen!!!");
//ScreenMessagePlayer(self$" stuck but can't teleport to spawn or nav or suicide and I've been seen!!!");
}
}
else
{
if ( StuckTeleportToSpawnVolume ( ) )
{
//`log(self$" teleported to spawn volume because it was stuck!!!");
//ScreenMessagePlayer(self$" teleported to spawn volume because it was stuck!!!");
}
else if ( StuckTeleportToPathNode ( ) )
{
//`log(self$" teleported because it was stuck, but couldn't teleport to spawn volume!!!");
//ScreenMessagePlayer(self$" teleported because it was stuck, but couldn't teleport to spawn volume!!!");
}
else if ( AmIAllowedToSuicideWhenStuck ( ) )
{
//`log(self$" killed itself because it was stuck and couldn't teleport anywhere!!!");
//ScreenMessagePlayer(self$" killed itself because it was stuck and couldn't teleport anywhere!!!");
StuckSuicide ( ) ;
}
else
{
//`log(self$" stuck but can't teleport to spawn or nav or suicide!!!");
//ScreenMessagePlayer(self$" stuck but can't teleport to spawn or nav or suicide!!!");
}
}
}
/** Kill this zed because it is stuck */
function StuckSuicide ( )
{
// Add back to the spawn queue!
if ( MyKFGameInfo != None && MyKFGameInfo . SpawnManager != none )
{
MyKFGameInfo . NumAISpawnsQueued -= 1 ;
MyKFGameInfo . MyKFGRI . AIRemaining ++ ;
MyKFGameInfo . SpawnManager . LeftoverSpawnSquad [ MyKFGameInfo . SpawnManager . LeftoverSpawnSquad . Length ] = class < KFPawn _Monster > ( Pawn . Class ) ;
}
Pawn . Died ( self , //Controller Killer,
class 'DmgType_Suicided' , // class<DamageType> damageType,
Pawn . Location ) ; // vector HitLocation);
}
/** Teleport this zed to a spawn volume because it is stuck */
function bool StuckTeleportToSpawnVolume ( )
{
local array < class < KFPawn _Monster > > AIToTeleport ;
local KFSpawnVolume KFSV ;
local KFAISpawnManager SpawnManager ;
local vector TeleportLocation ;
AIToTeleport [ AIToTeleport . Length ] = MyKFPawn . Class ;
SpawnManager = MyKFGameInfo . SpawnManager ;
if ( SpawnManager != none )
{
SpawnManager . DesiredSquadType = MyKFPawn . default . MinSpawnSquadSizeType ;
if ( Enemy != none && Enemy . Controller != none )
{
KFSV = SpawnManager . GetBestSpawnVolume ( AIToTeleport , Enemy . Controller , , True ) ;
}
else
{
KFSV = SpawnManager . GetBestSpawnVolume ( AIToTeleport , , , True ) ;
}
if ( KFSV != None )
{
TeleportLocation = KFSV . FindTeleportLocation ( MyKFPawn . Class ) ;
if ( ! IsZero ( TeleportLocation ) )
{
if ( TeleportToLocation ( TeleportLocation , Pawn . Rotation , false ) )
{
KFSV . HandleTeleportedTo ( ) ;
//`log(self$" teleported to spawn volume "$KFSV$" because it was stuck!!!");
return true ;
}
}
}
}
return false ;
}
/** Teleport this zed to a path node because it is stuck */
function bool StuckTeleportToPathNode ( optional float CheckRadius = 512 )
{
local NavigationPoint ResNav ;
` AILog( GetFuncName() );
ResNav = class 'KFPathnode' . static . GetNearestValidFloorNavWithinRadiusToPawn ( Pawn , CheckRadius ) ;
if ( ResNav != none && TeleportToLocation ( Resnav . Location , Pawn . Rotation ) )
{
return true ;
}
return false ;
}
/** Teleport to a spawn volume closer to the enemy */
function RelocateTeleport ( )
{
local array < class < KFPawn _Monster > > AIToTeleport ;
local KFSpawnVolume KFSV ;
local KFAISpawnManager SpawnManager ;
local vector TeleportLocation ;
local float DistToEnemySquared ;
AIToTeleport [ AIToTeleport . Length ] = MyKFPawn . Class ;
SpawnManager = MyKFGameInfo . SpawnManager ;
if ( SpawnManager != none )
{
SpawnManager . DesiredSquadType = MyKFPawn . default . MinSpawnSquadSizeType ;
if ( Enemy != none && Enemy . Controller != none )
{
DistToEnemySquared = VSizeSq ( Pawn . Location - Enemy . Location ) ;
KFSV = SpawnManager . GetBestSpawnVolume ( AIToTeleport , Enemy . Controller , , True , DistToEnemySquared ) ;
}
// else
// {
// KFSV = SpawnManager.GetBestSpawnVolume(AIToTeleport,, True );
// }
if ( KFSV != None )
{
TeleportLocation = KFSV . FindTeleportLocation ( MyKFPawn . Class ) ;
if ( ! IsZero ( TeleportLocation ) )
{
if ( TeleportToLocation ( TeleportLocation , Pawn . Rotation , false ) )
{
KFSV . HandleTeleportedTo ( ) ;
LastTeleportTime = WorldInfo . TimeSeconds ;
//`log(self$" teleported to spawn volume "$KFSV$" because it hadn't seen an enemy for a while!!!");
return ;
}
}
}
}
}
/** Return true if we should reduce zed on zed collision on bump when navigating */
function bool ShouldReduceZedOnZedCollisionOnBumpForNavigating ( )
{
local float DistToEnemySquared ;
// Don't reduce zed on zed collision if we're close to an enemy
if ( Enemy != none )
{
DistToEnemySquared = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( bEnemyIsVisible )
{
if ( DistToEnemySquared < NavigationBumpTeamCollisionThreshholdSquared )
{
return false ;
}
}
}
if ( MyKFPawn != none )
{
// If noone can see us, reduce collision
if ( MyKFPawn . IsUsingSuperSpeed ( ) )
{
return true ;
}
// If we think we may be stuck, reduce collision
if ( StuckPossiblity > ( StuckPossiblityThreshhold * 0.5 ) )
{
return true ;
}
}
return false ;
}
simulated function SpecialBumpHandling ( float DeltaTime )
{
local float delta ;
local Vector upvect ;
if ( MyKFPawn . CurrentChokePointTrigger == none && ! ShouldReduceZedOnZedCollisionOnBumpForNavigating ( ) )
{
if ( MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive )
{
` AiLog( GetFuncName() @ " does not have a CurrentChokePointTrigger so turning turn collision on", 'SpecialBumpHandling' );
RestoreCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( ) ;
}
}
else
{
//decay bump val
if ( bBumpedThisFrame )
{
delta = fMax ( 0.7 , abs ( BumpThreshold - CurBumpVal ) ) ;
CurBumpVal = FInterpTo ( CurBumpVal , BumpThreshold * 5.01 f , DeltaTime , BumpGrowthRate / delta ) ;
if ( CurBumpVal > 0.3 f )
{
//MessagePlayer(GetFuncname()@"HIT THRESHOLD! TURNING OFF COLLISION ZOMG");
` AiLog( GetFuncName() @ " CurBumpVal: " @ CurBumpVal @ " Has Gotten High enough to turn collision off - lastbumper: " @ lastbumper, 'SpecialBumpHandling' );
if ( MyKFPawn . CurrentChokePointTrigger != none && MyKFPawn . CurrentChokePointTrigger . PartialReduceTeammateCollision ( ) )
{
ReduceCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( true ) ;
}
else
{
ReduceCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( ) ;
}
}
bBumpedThisFrame = false ;
}
else
{
if ( CurBumpVal > 0.01 )
{
CurBumpVal = FInterpTo ( CurBumpVal , 0. f , DeltaTime , BumpDecayRate / fMax ( CurBumpVal , 0.1 ) ) ;
}
else if ( MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive )
{
` AiLog( GetFuncName() @ " CurBumpVal: " @ CurBumpVal @ " Has Gotten low enough to turn collision on - LastBumper: " @ LastBumper, 'SpecialBumpHandling' );
RestoreCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( ) ;
}
}
}
if ( bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState && MyKFPawn != none )
{
upvect . X = 0.0 ;
upvect . Y = 0.0 ;
upvect . Z = MyKFPawn . CylinderComponent . CollisionHeight ;
if ( MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive )
{
DrawDebugCylinder ( MyKFPawn . Location + upvect ,
MyKFPawn . Location - upvect ,
MyKFPawn . CylinderComponent . CollisionRadius * MyKFPawn . TeammateCollisionRadiusPercent ,
10 ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOn . R ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOn . G ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOn . B ,
false ) ;
}
else
{
DrawDebugCylinder ( MyKFPawn . Location + upvect ,
MyKFPawn . Location - upvect ,
MyKFPawn . CylinderComponent . CollisionRadius * MyKFPawn . TeammateCollisionRadiusPercent ,
10 ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOff . R ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOff . G ,
ColorForCollisionRadiusForReducedZedOnZedPinchPointCollisionStateOff . B ,
false ) ;
}
}
}
/** Only used if bSpecialBumpHandling is true in .ini file */
function ReduceCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( optional bool bPartialReduction )
{
if ( MyKFPawn != None && MyKFPawn . Health > 0 )
{
//MyKFPawn.SetCollision(,false);
if ( ! MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive )
{
// Only make it a little smaller
if ( bPartialReduction )
{
MyKFPawn . TeammateCollisionRadiusPercent = 0.4 ;
}
else
{
MyKFPawn . TeammateCollisionRadiusPercent = MyKFPawn . TeammateCollisionRadiusPercent / MyKFPawn . CylinderComponent . CollisionRadius ;
}
` AiLog( GetFuncName() @ " - CollisionRadiusBeforeReducedZedOnZedPinchPointCollisionState: " @ MyKFPawn.default.TeammateCollisionRadiusPercent @ " - CollisionCylinderReducedPercentForSameTeamCollision: " @ MyKFPawn.TeammateCollisionRadiusPercent, 'CollisionToggle' );
MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive = true ;
}
}
}
/** Only used if bSpecialBumpHandling is true in .ini file */
function RestoreCollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy ( )
{
if ( MyKFPawn != None && MyKFPawn . Health > 0 )
{
//MyKFPawn.SetCollision(,true);
MyKFPawn . TeammateCollisionRadiusPercent = MyKFPawn . default . TeammateCollisionRadiusPercent ;
MyKFPawn . bReducedZedOnZedPinchPointCollisionStateActive = false ;
` AiLog( GetFuncName() @ " - CollisionRadiusBeforeReducedZedOnZedPinchPointCollisionState: " @ MyKFPawn.default.TeammateCollisionRadiusPercent @ " - CollisionCylinderReducedPercentForSameTeamIgnoreBlockingBy: " @ MyKFPawn.TeammateCollisionRadiusPercent, 'CollisionToggle' );
}
else
{
` AiLog( GetFuncName() @ " MyKFPawn == None || MyKFPawn.Health <= 0 ", 'CollisionToggle' );
}
}
/** NPC has seen a player - use SeeMonster for similar notifications about seeing any pawns (currently not in use) */
event SeePlayer ( Pawn Seen )
{
local float DistToEnemy , DistToSeen ;
// if( bHasDebugCommand && AICommand_Debug(GetActiveCommand()).GetStateName() == 'DebugVision' )
// {
// AICommand(CommandList).NotifyEnemyBecameVisible( Seen );
// Enemy = Seen;
// Enable( 'EnemyNotVisible' );
// return;
// }
// Store the time this AI first saw a player
if ( TimeFirstSawPlayer == 0 )
{
TimeFirstSawPlayer = Worldinfo . TimeSeconds ;
}
if ( MyKFPawn != none )
{
if ( ! MyKFPawn . IsAliveAndWell ( ) )
{
// Disable SeePlayer indefinitely
DisableSeePlayer ( 0. f ) ;
}
else if ( MyKFPawn . IsDoingSpecialMove ( SM _Emerge ) )
{
DisableSeePlayer ( 0. f ) ;
return ;
}
}
if ( Enemy == Seen )
{
CachedVisibleEnemy = Enemy ;
EnemyVisibilityTime = WorldInfo . TimeSeconds ;
bEnemyIsVisible = true ;
EnableEnemyNotVisible ( ) ;
if ( MyKFPawn != none && MyKFPawn . IsAliveAndWell ( ) )
{
` DialogManager.CheckSpotMonsterDialog( Enemy, MyKFPawn );
// Store the last time we saw the enemy
if ( Enemy == Seen )
{
if ( LastEnemySightedTime == 0 || ` TimeSince(LastEnemySightedTime) > RepeatWalkingTauntTime )
{
if ( MyKFPawn . CanDoSpecialMove ( SM _WalkingTaunt ) )
{
MyKFPawn . DoSpecialMove ( SM _WalkingTaunt ) ;
}
}
LastEnemySightedTime = WorldInfo . TimeSeconds ;
}
}
return ;
}
if ( Enemy != none && Enemy != Seen )
{
//`log( self$" I already have an enemy: "$Enemy$" Visible? "$bEnemyIsVisible$" LOS to Seen? "$LineOfSightTo(Seen)$" CanSee Seen? "$CanSee(Seen) );
//`log( self$" Dist from Enemy: "$VSize(Enemy.Location - Pawn.Location)$" vs "$VSize( Seen.Location - Pawn.Location ) );
if ( KFPawn ( Seen ) != none && KFPawn ( Seen ) . IsDoingSpecialMove ( SM _GrappleVictim ) && NumberOfZedsTargetingPawn ( Seen ) <= 3 && ! bEnemyIsVisible )
{
SetEnemy ( Seen ) ;
}
else if ( LineOfSightTo ( Seen ) && ! bEnemyIsVisible )
{
SetEnemy ( Seen ) ;
}
else if ( CanSee ( Seen ) )
{
DistToEnemy = VSize ( Enemy . Location - Pawn . Location ) ;
DistToSeen = VSize ( Seen . Location - Pawn . Location ) ;
if ( DistToEnemy > StrikeRange * 1.5 && DistToEnemy > 1.7 * DistToSeen )
{
SetEnemy ( Seen ) ;
}
}
}
else
{
SetEnemy ( Seen ) ;
}
// Store the last time we saw the enemy if we just switched to that enemy
if ( Enemy == Seen )
{
LastEnemySightedTime = WorldInfo . TimeSeconds ;
}
// Notify the AI command that we saw an enemy
if ( CachedAICommandList != none )
{
` AILog( "SeePlayer: " $ Seen $ " - while I'm moving to goal, notifying " $ CommandList $ " and letting it handle this event", 'SeePlayer' );
CachedAICommandList . NotifyPlayerBecameVisible ( Seen ) ;
}
}
/** NPC has lost sight of current enemy */
event EnemyNotVisible ( )
{
// if( bHasDebugCommand && AICommand_Debug(GetActiveCommand()).GetStateName() == 'DebugVision' )
// {
// msg( "I CANNOT SEE YOU!" );
// AICommand(CommandList).NotifyEnemyNotVisible();
// return;
// }
// Let the command, if any, handle this
if ( CommandList != none )
{
` AILog( GetFuncName() $ "() - notifying " $ CommandList $ " and letting it handle this event", 'EnemyNotVisible' );
AICommand ( CommandList ) . NotifyEnemyNotVisible ( ) ;
}
if ( Enemy != none && MyKFPawn != none )
{
MyKFPawn . ClearHeadTrackTarget ( Enemy ) ;
}
if ( bEnemyIsVisible )
{
// Evalulate if we should switch enemies if we lose site of our current enemy
FindNewEnemy ( ) ;
}
bEnemyIsVisible = false ;
DisableEnemyNotVisible ( ) ;
EnableSeePlayer ( ) ;
bWasVisibleToEnemy = bIsVisibleToEnemy ;
bIsVisibleToEnemy = false ;
}
/** NPC has "heard" a noise (instigated by MakeNoise()) */
event HearNoise ( float Loudness , Actor NoiseMaker , optional Name NoiseType )
{
return ;
DisableHearNoise ( ) ;
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
//`AILog( "HearNoise: "$Other$" - notifying "$CommandList$" and letting it handle this event", 'BumpEvent' );
if ( CachedAICommandList . NotifyHearNoise ( Loudness , NoiseMaker , NoiseType ) )
{
return ;
}
}
// Respond to MakeNoise from nearby player reloading
// if( NoiseType == 'PlayerReload' && FRand() < 0.75f && VSize(KFWeapon(NoiseMaker).Instigator.Location - Pawn.Location) < 1024.f )
// {
// `AILog( "HearNoise aborting movement commands", 'Command_MoveToGoal' );
// AbortMovementCommands();
//
// SetEnemy( KFWeapon(NoiseMaker).Instigator );
// }
// else
// {
// super.HearNoise( Loudness, NoiseMaker, NoiseType );
// }
` AILog( self $ " HearNoise event, Loudness: " $ Loudness $ " NoiseMaker: " $ NoiseMaker $ " Type: " $ NoiseType $ " Dist: " $ VSize( NoiseMaker.Location - Pawn.Location ) $ "... Disabling HearNoise", 'HearNoise' );
// Disable noise hearing, it will be re-enabled later.
DisableHearNoise ( ) ;
}
event bool NotifyBump ( Actor Other , vector HitNormal )
{
local AICommand _HeadlessWander HeadlessWander ;
local KFPawn _Monster KFPM ;
local bool bInPartialCollisionReductionTrigger ;
local actor HitActor ;
if ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( SM _Emerge ) )
{
DisableBump ( 0.25 f ) ;
return true ;
}
KFPM = KFPawn _Monster ( Other ) ;
if ( bSpecialBumpHandling )
{
if ( MyKFPawn != none && KFPM != none && KFPM . Health > 0 )
{
MyKFPawn . HandleMonsterBump ( KFPM , HitNormal ) ;
if ( ! KFPM . IsDoingSpecialMove ( SM _MeleeAttack ) && KFPM . MyKFAIC != none && KFPM . MyKFAIC . DoorEnemy == none && ! IsZero ( KFPM . Acceleration ) )
{
bInPartialCollisionReductionTrigger = MyKFPawn . CurrentChokePointTrigger != none && MyKFPawn . CurrentChokePointTrigger . PartialReduceTeammateCollision ( ) ;
// Reduce the collision with other zeds if noone is watching, or we are in a choke point that allows it
if ( MyKFPawn . CurrentChokePointTrigger != none
&& ( MyKFPawn . CurrentChokePointTrigger . CanReduceTeammateCollision ( )
|| bInPartialCollisionReductionTrigger
|| ShouldReduceZedOnZedCollisionOnBumpForNavigating ( ) ) )
{
if ( bInPartialCollisionReductionTrigger && Enemy != none )
{
// If this line doesn't hit our enemy, go ahead and let this bump count to reduce collision if we're standing in the trigger of a welded door!
HitActor = ActorBlockTest ( Pawn , Enemy . Location + vect ( 0 , 0 , 1 ) * Enemy . BaseEyeHeight , MyKFPawn . Location + vect ( 0 , 0 , 1 ) * MyKFPawn . BaseEyeHeight , , true ) ;
}
if ( ! IsWithinAttackRange ( ) || ( bInPartialCollisionReductionTrigger && ( HitActor == none || HitActor != Enemy ) ) )
{
bBumpedThisFrame = true ;
lastbumper = KFPM ;
//msg( "BUMP! "$CurBumpVal );
return true ;
//MessagePlayer(GetFuncname()@"BUMP! -"@CurBumpVal);
}
else
{
` AiLog( GetFuncName() @ " Bumped: " @ KFPM @ " but IsWithinAttackRange so not going to care about SpecialBumpHandling right now!!!", 'SpecialBumpHandling' );
}
}
else
{
` AiLog( GetFuncName() @ " Bumped: " @ KFPM @ " but not in a door trigger so not going to care about SpecialBumpHandling right now!!!", 'SpecialBumpHandling' );
}
}
}
}
DisableBump ( 0.25 f ) ;
if ( MyKFPawn != none && MyKFPawn . Physics == PHYS _Falling )
{
return false ;
}
` RecordAIBump( ` StatID ( AI _BUMP ) , self , Pawn . Location , Pawn . Rotation , Other , "SSpeed: " $MyKFPawn != none ? MyKFPawn . IsUsingSuperSpeed ( ) : False ) ;
` AILog( "NotifyBump() into " $ Other $ " HitNormal: " $ HitNormal $ " MoveTarget: " $ MoveTarget!=none?string(MoveTarget):"none", 'BumpEvent' );
DisableBump ( 0.25 f ) ;
LastBumpedPawn = Pawn ( Other ) ;
if ( MyKFPawn != none && MyKFPawn . bPlayShambling && LastBumpedPawn != none && LastBumpedPawn . IsAliveAndWell ( ) )
{
HeadlessWander = AICommand _HeadlessWander ( GetActiveCommand ( ) ) ;
DisableBump ( 0.3 f ) ;
if ( HeadlessWander != none && ! MyKFPawn . IsDoingSpecialMove ( ) && ` TimeSince( HeadlessWander.LastHeadlessAttackTime ) > 2.5f )
{
UpdatePendingStrike ( ) ;
DoStrike ( ) ;
}
}
if ( Role == ROLE _Authority && bCanDoHeavyBump )
{
DoHeavyZedBump ( Other , HitNormal ) ;
return true ;
}
// if( IgnoreNotifies() )
// {
// `AILog( "Bump ignoring notifies for "$GetActiveCommand(), 'BumpEvent' );
// return false;
// }
// Let any interested AICommands deal with this
if ( CachedAICommandList != none ) // && Enemy == Seen )
{
` AILog( "Bump: " $ Other $ " - notifying " $ CommandList $ " and letting it handle this event", 'BumpEvent' );
if ( CachedAICommandList . NotifyBump ( Other , HitNormal ) )
{
return true ;
}
}
if ( KfMovementPlugin != none )
{
//if( KfMovementPlugin.NotifyBump(Other, HitNormal) )
//{
// return true;
//}
}
if ( LastBumpedPawn == none || Pawn . IsSameTeam ( LastBumpedPawn ) || ! LastBumpedPawn . IsAliveAndWell ( ) || ( Enemy != none && Enemy == Other ) )
{
return false ;
}
// From KF1
if ( SetEnemy ( LastBumpedPawn ) )
{
return false ;
}
return false ;
}
function DrawFireAttackDebug ( )
{
//BallisticProjectileFireBehavior.DrawFireAttackDebug();
DirectProjectileFireBehavior . DrawFireAttackDebug ( ) ;
LeapBehavior . DrawFireAttackDebug ( ) ;
}
/** Called when we have dealt melee damage */
function NotifyMeleeDamageDealt ( ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* SpecialMoves
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Stub function
function SpecialMoveTimeout ( ) ;
function NotifySpecialMoveEnded ( KFSpecialMove SM )
{
local LatentActionObserver ObserverInterface ;
` AILog(SM@"finished", 'SpecialMove');
// notify observers
ObserverInterface = LatentActionObserver ( SM . AISpecialOwner ) ;
if ( ObserverInterface != none )
{
ObserverInterface . OnLatentFinished ( self , SM , BTR _Success ) ;
}
LastSpecialMoveEndTime = WorldInfo . TimeSeconds ;
}
function NotifySpecialMoveStarted ( KFSpecialMove SM ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Steering related
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Timer called after possession
simulated function StartSteering ( )
{
if ( Steering != none )
{
` AILog( GetFuncName() $ "() turning on separation steering, separating from class KFPawn_Monster", 'AISteering' );
Steering . SeparationOn ( class 'KFPawn_Monster' ) ;
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Debugging related functions
* For NPC Overhead debug text , see :
* To display a message to players , see : event msg ( )
* To set PlayersOnly and display a message to players , see : event PauseAndShowMsg ( )
* To edit AILog output , see : event AILog _Internal ( ) and Globals . uci
* To set up a debug mode when NPC spawns , see : SetUpDebug ( )
*
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Native function declarations
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Outputs the AICommand history array to the log file */
native function DumpCommandHistory ( ) ;
native static function DrawDebugText ( HUD HUD , String Text , optional color TextColor ) ;
function DrawDebugTextToHud ( HUD HUD , String Text , optional color TextColor )
{
DrawDebugText ( HUD , Text , TextColor ) ;
}
/** Assorted debug options - uncomment to enable */
function SetupDebug ( )
{
local KFAIDirector Director ;
if ( MyKFGameInfo != none )
{
Director = MyKFGameInfo . GetAIDirector ( ) ;
if ( Director != none )
{
bDebug _PostRenderInfo = ( bDebug _DrawOverheadInfo || Director . bShowAINames ) || bDebug _DrawAttackAnimInfo || bDebug _DrawAIDebug || ( MyKFPawn != none && MyKFPawn . bDebug _DrawSprintingOverheadInfo ) ;
if ( Director . bShowAINames )
{
bDebug _DrawOverheadInfo = true ;
MyKFPawn . SetDebugTextRendering ( true ) ;
//bDebug_DrawAIDebug = true;
//SetPostRendering( bDebug_PostRenderInfo );
}
}
}
//`if(`__TW_AIDEBUGGING_)
if ( ! IsTimerActive ( nameof ( Debug _CheckRecentMoveTime ) ) )
{
// Tracking stuck NPCs
SetTimer ( 20. f + FRand ( ) , true , nameof ( Debug _CheckRecentMoveTime ) ) ;
}
//`endif
}
/** Is the AI currently in debug mode? (i.e, using AICommand_Debug) */
function bool IsInDebugMode ( )
{
local GameAICommand Cmd ;
local AICommand _Debug DebugCommand ;
for ( Cmd = CommandList ; Cmd != None ; Cmd = Cmd . ChildCommand )
{
DebugCommand = AICommand _Debug ( Cmd ) ;
if ( DebugCommand != none )
{
return true ;
}
}
return false ;
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Logging / Screen Messages
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
/** Used to update RecentActionInfo string, which is contoniually displayed on-screen in some debug modes */
event SetRecentActionInfo ( string Info )
{
RecentActionInfo = "[" $GetStateName ( ) $ "][" $Info$ "]" ;
}
simulated function OnDestroy ( SeqAct _Destroy Action )
{
if ( Pawn != None )
{
Pawn . OnDestroy ( Action ) ;
}
Super . OnDestroy ( Action ) ;
}
/** Sends message to local players using ClientMessage and log the text */
event Msg ( string MsgTxt , optional bool bOutputToLog = true , optional name MessageType , optional float MessageDuration )
{
if ( MyKFPawn != none )
{
MyKFPawn . KFMessagePlayer ( MsgTxt , MessageType , MessageDuration ) ;
if ( bOutputToLog )
{
` AILog( MsgTxt, 'Msg' );
}
}
}
/** Main AI related logging function usually called from macros `AILog & `AILog_Ext or from native code */
event AILog _Internal ( coerce string LogText , optional Name LogCategory , optional bool bForce , optional bool BugIt , optional bool bSkipExtraInfo )
{
` if( ` notdefined ( ShippingPC ) )
local AICommand ActiveCommand ;
local int Idx , FileNameLength ;
local String ActionStr , BorderStr , FinalStr , StatusStr , FileName , GoString , LocString ;
local Engine Eng ;
local KFGameReplicationInfo KFGRI ;
if ( MyKFPawn == none || MyKFPawn . Health <= 0 )
{
return ;
}
Eng = class 'Engine' . static . GetEngine ( ) ;
if ( ( ! bForce && ! bAILogging ) || Eng . bDisableAILogging )
{
return ;
}
if ( LogCategory == 'AICommand' || LogCategory == 'InitAICommand' || LogCategory == 'Command_Base' )
{
bSkipExtraInfo = true ;
}
ActiveCommand = AICommand ( GetActiveCommand ( ) ) ;
if ( ! bForce )
{
if ( ActiveCommand != none && ActiveCommand . OverrideLogCategory != "" )
{
LogCategory = name ( ActiveCommand . OverrideLogCategory ) ;
}
for ( Idx = 0 ; Idx < AILogFilter . Length ; Idx ++ )
{
if ( AILogFilter [ Idx ] == LogCategory || ( LogCategory == '' && AILogFilter [ Idx ] == 'Default' ) )
{
return ;
}
}
}
BorderStr = "--------------------------------------------------------------------------------------------------" ;
if ( MyKFPawn != none )
{
if ( bUseUniqueAILogFile && AILogFile == None )
{
AILogFile = Spawn ( class 'FileLog' ) ;
FileName = string ( MyKFPawn ) ;
FileName = Repl ( FileName , "KFPawn_" , "" , false ) ;
FileName = Repl ( FileName , "Zed" , "" , false ) ;
if ( WorldInfo . GRI != none )
{
KFGRI = KFGameReplicationInfo ( WorldInfo . GRI ) ;
FileName = "w" $KFGRI . WaveNum$ "_" $FileName ;
}
// Include the file extension in determining the length
FileNameLength = Len ( Filename ) + 6 ;
// make sure file name isn't too long for consoles
if ( FileNameLength > 40 )
{
// Make sure there is room for the .ailog part too
FileName = Right ( Filename , 34 ) ;
}
` log( "Created log file " $ AILogFile $ " name: " $ FileName );
AILogFile . bKillDuringLevelTransition = true ;
AILogFile . bFlushEachWrite = bFlushAILogEachLine ;
// Use async unless flushing was requested
AILogFile . bWantsAsyncWrites = ! bFlushAILogEachLine ;
AILogFile . OpenLog ( FileName , ".ailog" ) ;
AILogFile . Logf ( "========================================================================================================" ) ;
AILogFile . Logf ( "Log file for " $MyKFPawn$ " created: [" $MyKFPawn . CreationTime$ "] " $self$ " Controlling " $MyKFPawn ) ;
AILogFile . Logf ( " " ) ;
AILogFile . Logf ( "Spawn Location Info: " ) ;
AIBugItStringCreator ( MyKFPawn . Location , MyKFPawn . Rotation , GoString , LocString ) ;
AILogFile . Logf ( GoString ) ;
AILogFile . Logf ( LocString ) ;
AILogFile . Logf ( "Difficulty:" $MyKFGameInfo . GetModifiedGameDifficulty ( ) ) ;
AILogFile . Logf ( "Original Health:" $MyKFPawn . HealthMax$ " Adjusted Health:" $MyKFPawn . Health ) ;
AILogFile . Logf ( "Original GroundSpeed:" $MyKFPawn . default . GroundSpeed$ " Adjusted GroundSpeed:" $MyKFPawn . GroundSpeed ) ;
AILogFile . Logf ( "Original SprintSpeed:" $MyKFPawn . default . SprintSpeed$ " Adjusted SprintSpeed:" $MyKFPawn . SprintSpeed ) ;
AILogFile . Logf ( "Original HiddenSpeed:" $MyKFPawn . default . HiddenGroundSpeed$ " Adjusted HiddenSpeed:" $MyKFPawn . HiddenGroundSpeed ) ;
if ( MyKFPawn . PawnAnimInfo != none )
{
AILogFile . Logf ( "WeakAttackChance:" $MyKFPawn . PawnAnimInfo . WeakAttackChance ) ;
AILogFile . Logf ( "MediumAttackChance:" $MyKFPawn . PawnAnimInfo . MediumAttackChance ) ;
AILogFile . Logf ( "HardAttackChance:" $MyKFPawn . PawnAnimInfo . HardAttackChance ) ;
}
AILogFile . Logf ( "========================================================================================================" ) ;
AILogFile . Logf ( " " ) ;
}
}
//ActionStr = String(GetStateName());
if ( ! bSkipExtraInfo )
{
if ( ActiveCommand != none )
{
ActionStr = string ( ActiveCommand . Name ) ; //$":"$String( ActiveCommand.GetStateName() );
}
if ( MyKFPawn != none )
{
StatusStr = " [Phys:" $MyKFPawn . GetPhysicsName ( ) $ "][Velocity:" $VSize ( MyKFPawn . Velocity ) $ "]" ;
if ( bPreparingMove )
{
StatusStr = StatusStr$ "[bPreparingMove:TRUE]" ;
}
if ( MyKFPawn . Anchor != none )
{
StatusStr = StatusStr$ "[Anchor:" $MyKFPawn . Anchor$ "]" ;
}
if ( MyKFPawn . IsUsingSuperSpeed ( ) )
{
StatusStr = StatusStr$ "[SUPERSPEED]" ;
}
if ( Enemy != none )
{
StatusStr = StatusStr$ "[EnemyDist:" $VSize ( Enemy . Location - MyKFPawn . Location ) $ "]" ;
}
if ( MyKFPawn . IsDoingSpecialMove ( ) )
{
StatusStr = StatusStr$ "[SM:" $MyKFPawn . SpecialMoves [ MyKFPawn . SpecialMove ] $ "]" ;
}
}
else
{
FinalStr = "[NO PAWN]" ;
}
if ( MyKFPawn . bCrawler && MyKFPawn . Base != none )
{
StatusStr = StatusStr$ "[BASE:" $MyKFPawn . Base$ "]" ;
}
if ( PendingDoor != none )
{
StatusStr = StatusStr$ "[PENDING DOOR:" $PendingDoor$ "]" ;
}
if ( DoorEnemy != none )
{
StatusStr = StatusStr$ "[DOORENEMY:" $DoorEnemy$ "]" ;
}
FinalStr = "[" $WorldInfo . TimeSeconds$ "][" $ActionStr$ "]" $FinalStr ;
}
else
{
FinalStr = "[" $WorldInfo . TimeSeconds$ "]" ; //["$self$"]";
}
if ( bDebug _LogToProfiler )
{
LogToProfiler ( LogText , LogCategory ) ;
}
if ( AILogFile != none && bUseUniqueAILogFile )
{
if ( ! bSkipExtraInfo )
{
AILogFile . Logf ( BorderStr ) ;
AILogFile . Logf ( FinalStr ) ;
AILogFile . Logf ( StatusStr ) ;
AILogFile . Logf ( LogText ) ;
AILogFile . Logf ( BorderStr ) ;
}
else
{
AILogFile . Logf ( FinalStr @ LogText ) ;
}
if ( BugIt )
{
AIBugItStringCreator ( MyKFPawn . Location , MyKFPawn . Rotation , GoString , LocString ) ;
AILogFile . Logf ( "BUGIT: " $GoString ) ;
}
AILogFile . Logf ( " " ) ;
}
else
{
if ( ! bSkipExtraInfo )
{
` log( BorderStr );
}
` log( FinalStr );
` log( StatusStr );
` log( LogText );
if ( ! bSkipExtraInfo )
{
` log( BorderStr );
}
if ( BugIt && AILogFile != none )
{
AIBugItStringCreator ( MyKFPawn . Location , MyKFPawn . Rotation , GoString , LocString ) ;
AILogFile . Logf ( "BUGIT: " $GoString ) ;
}
` log( " " );
}
//else
//{
// `AILog( Pawn@"["$WorldInfo.TimeSeconds$"]"@ActionStr$":"@LogText );
//}
` Log( Pawn@"[" $ WorldInfo.TimeSeconds $ "]"@FinalStr $ ":"@LogText, bAILogToWindow );
` endif // ` notdefined ( ShippingPC )
}
function AIBugItStringCreator ( const out Vector ViewLocation , const out Rotator ViewRotation , out String GoString , out String LocString )
{
GoString = "BugItGo " $ ViewLocation . X $ " " $ ViewLocation . Y $ " " $ ViewLocation . Z $ " " $ ViewRotation . Pitch $ " " $ ViewRotation . Yaw $ " " $ ViewRotation . Roll ;
//`AILog( GoString, 'BugIt' );
LocString = "?BugLoc=" $ string ( ViewLocation ) $ "?BugRot=" $ string ( ViewRotation ) ;
//`AILog( LocString, 'BugIt' );
}
/** Debug event which will initiate PlayersOnly pause mode and display MsgTxt to player(s) */
event PauseAndShowMsg ( optional string MsgTxt , optional vector TeleportToLocation )
{
local PlayerController PC ;
local DebugCameraController DCC ;
foreach WorldInfo . AllControllers ( class 'PlayerController' , PC )
{
PC . ClientMessage ( MsgTxt ) ;
if ( ! WorldInfo . bPlayersOnly && ! WorldInfo . bPlayersOnlyPending )
{
PC . CheatManager . PlayersOnly ( ) ;
break ;
}
}
if ( TeleportToLocation != vect ( 0 , 0 , 0 ) )
{
foreach WorldInfo . AllControllers ( class 'DebugCameraController' , DCC )
{
DCC . SetLocation ( TeleportToLocation ) ;
}
}
}
function ScreenMessagePlayer ( coerce String Msg )
{
` if( ` notdefined ( FINAL _RELEASE ) )
local PlayerController PC ;
foreach LocalPlayerControllers ( class 'PlayerController' , PC )
{
PC . ClientMessage ( Msg ) ;
}
` endif
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* KFDebugLines & Path rendering in - game
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function KFDebugLines GetDebugLines ( )
{
return class 'KFDebugLines' . static . GetDebugLines ( ) ;
}
function KFDebug _ClearLines ( optional int ID )
{
class 'KFDebugLines' . static . KFDebug _ClearLines ( name , ID ) ;
}
function KFDebug _ClearSpheres ( optional int ID )
{
class 'KFDebugLines' . static . KFDebug _ClearSpheres ( name , ID ) ;
}
function KFDebug _ClearDebugText ( optional int ID )
{
class 'KFDebugLines' . static . KFDebug _ClearText ( name , ID ) ;
}
function KFDebug _DrawPath ( optional int ID )
{
local KFDebugLines KFDL ;
return ;
KFDL = GetDebugLines ( ) ;
if ( KFDL != none )
{
KFDL . AddDebugLineFromOwner ( name , MoveTarget . Location , 0 , 255 , 0 , false , 20 , ID ) ;
KFDL . AddDebugSphere ( MoveTarget . Location , 16 , 8 , 0 , 100 , 100 , false , 20 , name , ID ) ;
}
}
function KFDebug _DrawMyPath ( )
{
local KFDebugLines KFDL ;
local int i ;
KFDL = GetDebugLines ( ) ;
if ( KFDL != none )
{
KFDL . RemoveOwnedDebugLines ( name ) ;
KFDL . RemoveOwnedDebugSpheres ( name ) ;
KFDL . RemoveOwnedDebugText3D ( name ) ;
for ( i = 0 ; i < RouteCache . Length ; i ++ )
{
if ( i < ( RouteCache . Length - 1 ) )
{
KFDL . AddDebugLine ( RouteCache [ i ] . Location , RouteCache [ i + 1 ] . Location , 113 , 167 , 207 , false , 20 , name , i ) ;
}
KFDL . AddDebugSphere ( RouteCache [ i ] . Location , 24 , 8 , 113 , 167 , 207 , true , , name , i ) ;
KFDL . AddDebugText3D ( RouteCache [ i ] . Location + vect ( 0 , 0 , 32 ) , string ( RouteCache [ i ] ) , true , 113 , 167 , 207 , true , , , , name , i ) ;
}
}
}
function KFDebug _DrawPathTo ( Actor A , optional int ID , optional int SecondaryID , optional int TextID )
{
local KFDebugLines KFDL ;
if ( bDebug _DrawPath )
{
KFDL = GetDebugLines ( ) ;
if ( KFDL != none )
{
KFDL . AddDebugLineFromOwner ( name , A . Location , 113 , 167 , 207 , false , 45 , ID ) ;
//KFDL.AddDebugLine( Pawn.Location, MoveTarget.Location, 0, 255, 0, false, 45, name, ID );
KFDL . AddDebugSphere ( A . Location , 16 , 8 , 113 , 167 , 207 , false , 45 , name , ID ) ;
KFDL . AddDebugLineFromOwner ( name , MoveGoal . Location , 126 , 250 , 85 , false , 45 , SecondaryID ) ;
KFDL . AddDebugSphere ( MoveGoal . Location , 16 , 8 , 125 , 250 , 85 , false , 45 , name , SecondaryID ) ;
KFDL . AddDebugText3D ( A . Location + vect ( 0 , 0 , 32 ) , string ( A ) , true , 113 , 167 , 207 , false , 45 , A , , name , ID ) ;
}
}
}
function KFDebug _DrawPathGoal ( optional int ID )
{
local KFDebugLines KFDL ;
return ;
KFDL = GetDebugLines ( ) ;
if ( KFDL != none )
{
KFDL . AddDebugLineFromOwner ( name , MoveGoal . Location , 0 , 255 , 0 , false , 45 , ID ) ;
//KFDL.AddDebugLine( Pawn.Location, MoveTarget.Location, 0, 255, 0, false, 45, name, ID );
KFDL . AddDebugSphere ( MoveGoal . Location , 16 , 8 , 0 , 100 , 100 , false , 45 , name , ID ) ;
}
}
/** Logs text/category to file readable by AIProfiler */
native function LogToProfiler ( string Text , name Category ) ;
/ * *
*
*
* /
function ShowStuckNpcsToggle ( )
{
// start timer to check update
if ( IsTimerActive ( nameof ( Debug _CheckStuckTimer ) ) )
{
ClearTimer ( nameof ( Debug _CheckStuckTimer ) ) ;
}
else
{
// record location at debug start
LocationAtStartOfStuckCheck = MyKFPawn . Location ;
LocationAtLastStuckCheck = MyKFPawn . Location ;
// Tracking stuck NPCs
SetTimer ( 1.0 f , true , nameof ( Debug _CheckStuckTimer ) ) ;
}
}
function Debug _CheckStuckTimer ( )
{
local Vector displacementFromStartOfCheck ;
local float distanceFromStartOfCheck ;
local Vector displacementFromLastStuckUpdate ;
local float distanceFromLastStuckUpdate ;
displacementFromStartOfCheck = MyKFPawn . Location - LocationAtStartOfStuckCheck ;
distanceFromStartOfCheck = VSize ( displacementFromStartOfCheck ) ;
displacementFromLastStuckUpdate = MyKFPawn . Location - LocationAtLastStuckCheck ;
distanceFromLastStuckUpdate = VSize ( displacementFromLastStuckUpdate ) ;
LocationAtLastStuckCheck = MyKFPawn . Location ;
if ( distanceFromStartOfCheck < 1.0 f )
{
DrawDebugLine ( MyKFPawn . Location , Enemy . Location , 255 , 0 , 0 , true ) ;
}
else if ( distanceFromLastStuckUpdate < 1.0 f )
{
DrawDebugLine ( MyKFPawn . Location , Enemy . Location , 255 , 255 , 0 , true ) ;
}
}
function float GetMaxTimeAllowedToStayStuckBasedOnStuckType ( MOVE _FAILURE _TYPE TypeOfFailure )
{
local float maxTime ;
if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _NO _NAV _MESH _PATH )
{
maxTime = NoNavMeshPathMaxTimeAllowedToStayStuckBeforeSuicide ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMES )
{
maxTime = SameIntermediatePointToManyTimesMaxTimeAllowedToStayStuckBeforeSuicide ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TO )
{
maxTime = TargetOffNavMeshAndCanNotFindLocaitonNearThemICanMoveTooMaxTimeAllowedToStayStuckBeforeSuicide ;
}
else
{
maxTime = DefaultMaxTimeAllowedToStayStuckBeforeSuicide ;
}
return maxTime ;
}
/ * *
*
*
* /
function ToggleHardCoreStuckNpcs ( AICommand CmdTriggering , AITickablePlugin PlugInTriggering )
{
// start timer to check update
local float timeToTrip ;
if ( IsTimerActive ( nameof ( HardCoreCheckStuckTimer ) ) )
{
ClearTimer ( nameof ( HardCoreCheckStuckTimer ) ) ;
CmdTriggeringHardCoreStuckChecking = none ;
PlugInTriggeringTriggeringHardCoreStuckChecking = none ;
}
else if ( MyKFPawn != none )
{
// record location at debug start
LocationAtStartOfStuckCheck = MyKFPawn . Location ;
LocationAtLastStuckCheck = MyKFPawn . Location ;
CmdTriggeringHardCoreStuckChecking = CmdTriggering ;
PlugInTriggeringTriggeringHardCoreStuckChecking = PlugInTriggering ;
timeToTrip = GetMaxTimeAllowedToStayStuckBasedOnStuckType ( TypeOfMovementStuckOn ) ;
// Tracking stuck NPCs
SetTimer ( timeToTrip , true , nameof ( HardCoreCheckStuckTimer ) ) ;
}
}
function float GetMinDistaceToHaveToMoveToBeConcideredStuckByStuckType ( MOVE _FAILURE _TYPE TypeOfFailure )
{
local float minDistance ;
if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _NO _NAV _MESH _PATH )
{
minDistance = NoNavMeshPathMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMES )
{
minDistance = SameIntermediatePointToManyTimesMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
}
else
{
minDistance = DefaultMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide ;
}
return minDistance ;
}
function HardCoreCheckStuckTimer ( )
{
local Vector displacementFromStartOfCheck ;
local float distanceFromStartOfCheck ;
local Vector displacementFromLastStuckUpdate ;
local float distanceFromLastStuckUpdate ;
local color stuckLineColor ;
local float minDistanceToMove ;
displacementFromStartOfCheck = MyKFPawn . Location - LocationAtStartOfStuckCheck ;
distanceFromStartOfCheck = VSize ( displacementFromStartOfCheck ) ;
displacementFromLastStuckUpdate = MyKFPawn . Location - LocationAtLastStuckCheck ;
distanceFromLastStuckUpdate = VSize ( displacementFromLastStuckUpdate ) ;
LocationAtLastStuckCheck = MyKFPawn . Location ;
minDistanceToMove = GetMinDistaceToHaveToMoveToBeConcideredStuckByStuckType ( TypeOfMovementStuckOn ) ;
if ( distanceFromStartOfCheck <= minDistanceToMove )
{
if ( bShowVisualStuckZedDebugInfo )
{
if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _NO _NAV _MESH _PATH )
{
stuckLineColor = Move _failure _type _no _nav _mesh _path _color ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMES )
{
stuckLineColor = Move _failure _type _same _intermediate _point _too _many _times _color ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TO )
{
stuckLineColor = MoveFailureTypeTargetOffNavMeshAndCanNotFindLocaitonNearThemICanMoveTo ;
}
else
{
stuckLineColor = Move _failure _type _none _color ;
}
DrawDebugLine ( MyKFPawn . Location , Enemy . Location , stuckLineColor . R , stuckLineColor . G , stuckLineColor . B , true ) ;
}
if ( CmdTriggeringHardCoreStuckChecking != none )
{
CmdTriggeringHardCoreStuckChecking . NotifyNpcTerminallyStuck ( ) ;
}
else if ( PlugInTriggeringTriggeringHardCoreStuckChecking != none )
{
PlugInTriggeringTriggeringHardCoreStuckChecking . NotifyNpcTerminallyStuck ( ) ;
}
}
else if ( distanceFromLastStuckUpdate <= minDistanceToMove )
{
if ( bShowVisualStuckZedDebugInfo )
{
DrawDebugLine ( MyKFPawn . Location , Enemy . Location , 255 , 255 , 0 , true ) ;
}
if ( CmdTriggeringHardCoreStuckChecking != none )
{
CmdTriggeringHardCoreStuckChecking . NotifyNpcInGrannyMode ( ) ;
}
else if ( PlugInTriggeringTriggeringHardCoreStuckChecking != none )
{
PlugInTriggeringTriggeringHardCoreStuckChecking . NotifyNpcInGrannyMode ( ) ;
}
}
}
function bool AmIAllowedToSuicideWhenStuck ( )
{
return true ;
}
function bool AmIAllowedToStillUsePathNodes ( )
{
return false ;
}
//`if(`__TW_AIDEBUGGING_)
/** Timer that's tracking stuck NPCs */
function Debug _CheckRecentMoveTime ( )
{
// local float TimeSinceLastMove;
// local float TimerRate;
// TimeSinceLastMove = `TimeSince(LastMoveFinishTime);
// TimerRate = GetTimerRate(nameof(Debug_CheckRecentMoveTime));
//
// if( TimeSinceLastMove > TimerRate )
// {
// `AILog( "** WARNING ** "$self$" hasn't moved in over "$TimeSinceLastMove$" seconds! Dumping CommandStack and CommandHistory:", 'Critical' );
// DumpCommandStack();
// DumpCommandHistory();
// }
}
//`endif
/** Called from native code when AI is paused */
event Debug _AIPaused ( bool bNewPause )
{
if ( bNewPause )
{
AbortMovementCommands ( ) ;
AbortMovementPlugIns ( ) ;
Pawn . ZeroMovementVariables ( ) ;
StopAllLatentMovement ( ) ;
}
}
/** Dump AI command stack to the log file */
function DumpStack ( )
{
DumpCommandStack ( ) ;
}
/** Adds self to Player HUD PostRenderedActors list. Actors in the list will have PostRenderFor() called. */
function SetPostRendering ( bool bOn )
{
local PlayerController KFPC ;
local KFHUDBase KFHUD ;
foreach LocalPlayerControllers ( class 'PlayerController' , KFPC )
{
KFHUD = KFHUDBase ( KFPC . myHUD ) ;
if ( KFHUD != none )
{
if ( bOn )
{
bPostRenderIfNotVisible = true ;
KFHUD . SetShowOverlays ( true ) ;
KFHUD . AddPostRenderedActor ( self ) ;
}
else
{
bPostRenderIfNotVisible = default . bPostRenderIfNotVisible ;
//KFHUD.SetShowOverlays( false );
KFHUD . RemovePostRenderedActor ( self ) ;
}
}
}
}
/ * *
Script function called by NativePostRenderFor ( ) .
* /
simulated event PostRenderFor ( PlayerController PC , Canvas Canvas , vector CameraPosition , vector CameraDir )
{
if ( MyKFPawn != none )
{
MyKFPawn . PostRenderFor ( PC , Canvas , CameraPosition , CameraDir ) ;
}
}
function DrawAttackInfo ( KFHUDBase HUD )
{
local Canvas C ;
local int i , x ;
local string DisplayString ;
local KFPawnAnimInfo KFPAG ;
local name CurrentSeq , InRangeTag ;
if ( ! bDebug _DrawAttackAnimInfo || MyKFPawn == none || MyKFPawn . PawnAnimInfo == none || Enemy == none )
{
return ;
}
if ( MyKFPawn . PawnAnimInfo == none )
{
return ;
}
C = HUD . Canvas ;
C . Font = class 'Engine' . Static . GetTinyFont ( ) ;
C . SetDrawColor ( 255 , 255 , 255 ) ;
C . SetPos ( C . SizeX * 0.05 f , C . SizeY * 0.22 f ) ;
DrawDebugText ( HUD , "ATTACK ANIMATION INFO FOR " $Pawn . Name$ " (Controller: " $self$ ")" ) ;
C . SetDrawColor ( 0 , 0 , 255 , 255 ) ;
DrawDebugText ( HUD , "--------------------------------------------------------------------------" ) ;
DrawDebugText ( HUD , " " ) ;
KFPAG = MyKFPawn . PawnAnimInfo ;
if ( MyKFPawn . InAnyAttackTagRange ( Enemy . Location , InRangeTag ) )
{
C . SetDrawColor ( 255 , 25 , 0 , 255 ) ;
DrawDebugText ( HUD , "Distance From Enemy: " $VSize ( Pawn . Location - Enemy . Location ) $ "uu [WITHIN ATTACK RANGE]" ) ;
C . SetDrawColor ( 0 , 255 , 50 , 255 ) ;
}
else
{
C . SetDrawColor ( 0 , 255 , 50 , 255 ) ;
DrawDebugText ( HUD , "Distance From Enemy: " $VSize ( Pawn . Location - Enemy . Location ) $ "uu" ) ;
}
if ( MyKFPawn . IsDoingSpecialMove ( ) )
{
C . SetDrawColor ( 255 , 25 , 50 , 255 ) ;
DrawDebugText ( HUD , "[Doing Special Move: " $MyKFPawn . SpecialMove$ "]" ) ;
}
C . SetDrawColor ( 0 , 255 , 50 , 255 ) ;
if ( GetActiveCommand ( ) != none )
{
DisplayString = "[Active Command: " $GetActiveCommand ( ) $ "]" ;
}
if ( CommandList != none )
{
DisplayString = DisplayString$ " [CommandList: " $CommandList$ "]" ;
if ( CommandList . ChildCommand != none )
{
DisplayString = DisplayString$ " [ChildCommand: " $CommandList . ChildCommand$ "]" ;
if ( CommandList . ChildStatus != '' )
{
DisplayString = DisplayString$ " [ChildStatus: " $CommandList . ChildStatus$ "]" ;
}
}
}
if ( DisplayString != "" )
{
DrawDebugText ( HUD , DisplayString ) ;
DisplayString = "" ;
}
CurrentSeq = MyKFPawn . BodyStanceNodes [ EAS _FullBody ] . GetPlayedAnimation ( ) ;
if ( CurrentSeq != '' )
{
for ( i = 0 ; i < KFPAG . Attacks . Length ; i ++ )
{
for ( x = 0 ; x < KFPAG . Attacks [ i ] . Anims . Length ; x ++ )
{
if ( CurrentSeq == KFPAg . Attacks [ i ] . Anims [ x ] )
{
DisplayString = "[FULLBODY] Playing " $string ( CurrentSeq ) $ " [Tag " $KFPAG . Attacks [ i ] . Tag$ "] [Min: " $KFPAG . Attacks [ i ] . MinDistance$ "uu Max: " $KFPAG . Attacks [ i ] . MaxDistance$ "uu]" ;
break ;
}
if ( DisplayString != "" )
{
break ;
}
}
}
if ( DisplayString != "" )
{
DrawDebugText ( HUD , DisplayString ) ;
DisplayString = "" ;
}
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Overhead Debug Text
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function bool IsDebugTextCategoryEnabled ( name CategoryName )
{
return ( EnabledDebugTextCategories . Find ( 'All' ) != - 1 || EnabledDebugTextCategories . Find ( CategoryName ) != - 1 ) ;
}
simulated function DrawDebug ( KFHUDBase HUD , name Category )
{
local int CmdCnt ;
local Canvas C ;
local GameAICommand Cmd ;
//local String Text;
local AICmdHistoryItem Item ;
local bool bDrawDebugCommandStack , bDrawDebugCommandHistory ;
local bool bDrawDebugAllPlugins , bDrawDebugPlugInHistory ;
local string AddTxt , NullTxt ;
local AICommand AC ;
//local float Aggression;
bDrawDebugCommandStack = false ;
bDrawDebugCommandHistory = false ;
bDrawDebugAllPlugins = false ;
if ( Category == 'Default' || Category == 'All' || Category == 'PlugIns' )
{
bDrawDebugAllPlugins = true ;
bDrawDebugPlugInHistory = true ;
}
// if( Category == 'Default' || Category == 'All' || Category == 'OverheadNames' )
// {
// bDrawDebugCommandStack = false;
// bDrawDebugCommandHistory = false;
// Icon = Texture2D'ENG_EditorResources_TEX.AI.S_AI';
// DrawIconOverhead(HUD, Icon);
// return;
// }
// if( bDebug_ShowViewCone )
// {
// if( MyKFPawn != None )
// {
// tmp = MyKFPawn.GetPawnViewLocation();
// rot = MyKFPawn.GetBaseAimRotation();
// }
// DrawDebugCone(tmp ,vector( rot),Pawn .SightRadius, Acos(Pawn .PeripheralVision), Acos(Pawn .PeripheralVision),16,MakeColor(255,0,0,255));
// }
//return;
NullTxt = "None" ;
// Draw list of commands down the side of the screen
if ( Pawn != None && Category == 'All' )
{
C = HUD . Canvas ;
C . SetOrigin ( 0 , 0 ) ;
C . Font = class 'Engine' . Static . GetSmallFont ( ) ;
C . SetPos ( C . SizeX * 0.05 f , C . SizeY * 0.25 f ) ;
if ( bEnemyIsVisible )
{
DrawDebugText ( HUD , "bEnemyIsVisible: " $bEnemyIsVisible , MakeColor ( 0 , 255 , 0 , 255 ) ) ;
}
else
{
DrawDebugText ( HUD , "bEnemyIsVisible: " $bEnemyIsVisible ) ;
}
// C.SetDrawColor(0, 255, 0, 255);
// if( InLatentExecution( LATENT_MOVETOWARD ) )
// {
// C.SetDrawColor(225, 255, 0, 255);
// DrawDebugText( HUD, String(self)$" IN LATENT MOVETOWARD" );
// C.SetDrawColor(225, 0, 0, 255);
// }
if ( bDrawDebugCommandStack )
{
// WRITE OUT COMMAND STACK
C . SetDrawColor ( 255 , 255 , 255 , 255 ) ;
DrawDebugText ( HUD , "************************************************************" ) ;
C . SetDrawColor ( 0 , 0 , 255 , 255 ) ;
DrawDebugText ( HUD , "COMMAND STACK" ) ;
foreach AllCommands ( class 'GameAICommand' , Cmd )
{
if ( Cmd . ChildCommand == None )
{
C . SetDrawColor ( 225 , 0 , 0 , 255 ) ;
}
else
{
C . SetDrawColor ( 0 , 255 , 0 , 255 ) ;
}
DrawDebugText ( HUD , String ( Cmd . Name ) @ ":" @ Cmd . GetStateName ( ) ) ;
// Text = Cmd.GetDebugVerboseText();
// if( Len(Text) > 0 )
// {
// DrawDebugText( HUD, ".........."@Text );
// }
CmdCnt ++ ;
}
if ( CmdCnt == 0 )
{
DrawDebugText ( HUD , "NO COMMANDS ACTIVE" ) ;
}
}
if ( bDrawDebugCommandHistory )
{
// WRITE OUT COMMAND HISTORY
// C.SetDrawColor(255, 255, 255, 255);
DrawDebugText ( HUD , "************************************************************" ) ;
//C.SetDrawColor(0, 0, 255, 255);
DrawDebugText ( HUD , "COMMAND HISTORY (Count:" @ CommandHistoryNum$ ")" ) ;
CmdCnt = 0 ;
foreach CommandHistory ( Item )
{
CmdCnt ++ ;
// C.SetDrawColor(255, 0, 0, 255);
DrawDebugText ( HUD , "Cmd" @ CmdCnt$ ":" @ String ( Item . CmdClass ) @ "Time:" @ Item . TimeStamp ) ;
if ( Len ( Item . VerboseString ) > 0 )
{
// C.SetDrawColor(255, 64, 64, 255);
DrawDebugText ( HUD , ".............." @ Item . VerboseString ) ;
}
}
}
C . Font = class 'Engine' . Static . GetSmallFont ( ) ;
if ( MyKFPawn != none )
{
//C.SetDrawColor(0, 255, 0, 255);
DrawDebugText ( HUD , string ( Pawn ) ) ; //, MakeColor(0,255,0,255) );
AddTxt = "ActiveCommand: " ;
if ( CachedAICommandList != None )
{
AC = CachedAICommandList ;
if ( AC != none )
{
AddTxt = AddTxt @ AC ;
if ( AC . CachedChildCommand != none )
{
AddTxt = AddTxt @ "Child1: " $AC . CachedChildCommand ;
if ( AC . CachedChildCommand . CachedChildCommand != none )
{
AddTxt = AddTxt @ "Child2: " $AC . CachedChildCommand . CachedChildCommand ;
}
}
}
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
DrawDebugText ( HUD , AddTxt ) ;
if ( IsDoingLatentMove ( ) )
{
DrawDebugText ( HUD , "Latent Action: MOVE" , MakeColor ( 200 , 0 , 0 ) ) ;
}
else
{
DrawDebugText ( HUD , "Latent Action: None" ) ;
}
DrawDebugText ( HUD , AddTxt ) ;
DrawDebugText ( HUD , "Floor: " $Pawn . Floor$ " DesiredRot: " $Pawn . DesiredRotation$ " PawnRot: " $Pawn . Rotation$ " KFAIC Rot: " $Rotation ) ;
DrawDebugText ( HUD , "Velocity: " $VSize ( Pawn . Velocity ) $ " Accel: " $VSize ( Pawn . Acceleration ) $ " Physics: " $Pawn . GetPhysicsName ( ) ) ;
DrawDebugText ( HUD , "Direct Path: " $bDirectMoveToGoal$ " bPreparingMove: " $bPreparingMove$ " GroundSpeed: " $Pawn . GroundSpeed ) ;
DrawDebugText ( HUD , "bPreciseDest: " $bPreciseDestination$ " bForceMaxAccel: " $MyKFPawn . bForceMaxAccel$ " MoveOffset: " $MoveOffset ) ;
AddTxt = "Anchor:" ;
if ( Pawn . Anchor != none )
{
AddTxt = AddTxt @ MyKFPawn . Anchor ;
}
else
{
AddTxt = AddTxt @ "None" ;
}
if ( RouteCache . Length > 0 )
{
AddTxt = AddTxt @ " RouteCache[0]:" $RouteCache [ 0 ] ;
}
AddTxt = "SpecialMove:" ;
if ( MyKFPawn . IsDoingSpecialmove ( ) )
{
AddTxt = AddTxt @ MyKFPawn . SpecialMoves [ MyKFPawn . SpecialMove ] ;
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
AddTxt = AddTxt @ "MoveTarget:" ;
if ( MoveTarget != none )
{
AddTxt = AddTxt @ MoveTarget ;
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
AddTxt = AddTxt @ "Enemy:" ;
if ( Enemy != none )
{
AddTxt = AddTxt @ Enemy ;
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
DrawDebugText ( HUD , AddTxt ) ;
AddTxt = "Sprinting: " $MyKFPawn . bIsSprinting$ " FocalPoint: " $GetFocalPoint ( ) $ " Focus:" ;
if ( Focus != none )
{
AddTxt = AddTxt @ Focus ;
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
AddTxt = AddTxt @ ":" ;
if ( MyKFPawn . bIsHeadTrackingActive && MyKFPawn . MyLookAtInfo . LookAtTarget != none )
{
AddTxt = AddTxt @ MyKFPawn . MyLookAtInfo . LookAtTarget ;
}
else
{
AddTxt = AddTxt @ NullTxt ;
}
DrawDebugText ( HUD , AddTxt ) ;
if ( Enemy != none )
{
DrawDebugText ( HUD , "EnemyDist: " $VSize ( Enemy . Location - Pawn . Location ) $ " RouteCache: " $RouteCache . Length$ " DestinationOffset: " $Pawn . DestinationOffset ) ;
DrawDebugText ( HUD , "MeleeEvents Enabled: " $bIsProbingMeleeRangeEvents$ " Within Melee Range [" $StrikeRange$ "]" ) ;
DrawDebugText ( HUD , "DotToEnemy: " $Normal ( MyKFPawn . Location - Enemy . Location ) dot vector ( Enemy . Rotation ) ) ;
}
if ( Pawn . IsDesiredRotationInUse ( ) )
{
DrawDebugText ( HUD , "DesiredRotation In Use: TRUE" , MakeColor ( 255 , 0 , 0 , 255 ) ) ;
}
if ( Pawn . IsDesiredRotationLocked ( ) )
{
DrawDebugText ( HUD , "DesiredRotation is locked" , MakeColor ( 255 , 0 , 0 , 255 ) ) ;
}
if ( bHasDebugCommand )
{
FindCommandOfClass ( class 'AICommand_Debug' ) . DrawDebug ( HUD , Category ) ;
}
}
}
if ( bDrawDebugAllPlugins )
{
if ( KfMovementPlugin != none )
{
KfMovementPlugin . DrawDebugToHud ( Hud , Category ) ;
}
if ( KfLeapPlugin != none )
{
KfLeapPlugin . DrawDebugToHud ( Hud , Category ) ;
}
if ( KfMovementPlugin != none )
{
KfStuckFixPlugin . DrawDebugToHud ( Hud , Category ) ;
}
}
if ( bDrawDebugPlugInHistory )
{
MyAiPlugInHistory . DrawDebugToHud ( Hud , Category ) ;
}
}
function BeginDebugCommand ( )
{
AbortCommand ( None , class 'AICommand' ) ;
class 'AICommand_Debug' . static . Debug ( self ) ;
}
function DoDebugTurnInPlace ( KFPlayerController KFPC , optional bool bAllowMelee = false )
{
AbortCommand ( None , class 'AICommand' ) ;
class 'AICommand_DebugTurn' . static . DebugTurnInPlace ( self , bAllowMelee , KFPC ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* TakeDamage
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** To override in subclasses */
function bool IsAggroEnemySwitchAllowed ( )
{
if ( LastEnemySwitchTime > 0. f && ` TimeSince(LastEnemySwitchTime) < AggroEnemySwitchWaitTime )
{
return false ;
}
return true ;
}
/ * *
*
* @ param HUD Player HUD
* /
function DrawDebugOverheadMovementPhaseData ( KFHUDBase HUD , Out Vector2d ScreenPos )
{
local Texture2D Icon ;
local PlayerController PC ;
local Canvas Canvas ;
local Vector CameraLoc , ScreenLoc ;
local Rotator CameraRot ;
local float X , Y ;
local float DOT ;
if ( MyKFPawn == none || MyKFPawn . Health <= 0 )
{
return ;
}
if ( HUD . ShouldDisplayDebug ( 'AIPathing' ) || HUD . ShouldDisplayDebug ( 'AllVerbose' ) )
{
HUD . DrawRoute ( MyKFPawn ) ;
}
Canvas = HUD . Canvas ;
ScreenLoc = Canvas . Project ( Pawn . Location + vect ( 0 , 0 , 1 ) * Pawn . GetCollisionHeight ( ) * 1.5 f ) ;
if ( ScreenLoc . X < 0 || ScreenLoc . X >= HUD . Canvas . ClipX || ScreenLoc . Y < 0 && ScreenLoc . Y >= HUD . Canvas . ClipY )
{
return ;
}
PC = HUD . PlayerOwner ;
Canvas . SetDrawColor ( 255 , 255 , 255 ) ;
PC . GetPlayerViewPoint ( CameraLoc , CameraRot ) ;
Dot = vector ( CameraRot ) dot ( Pawn . Location - CameraLoc ) ;
if ( Dot < 0.5 f )
{
return ;
}
if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _PATHNODE _NORMAL _MOVE _TO )
{
Icon = MovementPhaseTypePathNodeNormalMoveToIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _PATHNODE _DELAY _MOVE )
{
Icon = MovementPhaseTypePathNodeDelayMoveIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _PATHNODE _POINT _MOVE _TO )
{
Icon = MovementPhaseTypePathNodeMoveToPointIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _PATHNODE _ROTATE _TO _FOCUS )
{
Icon = MovementPhaseTypePathNodeRotateToFocusIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _PATHNODE _FAILED _MOVE )
{
Icon = MovementPhaseTypePathNodeMoveFailedIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _NAV _MESH _NORMAL _MOVE _TO )
{
Icon = MovementPhaseTypeNavMeshNormalMoveToIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _FINAL _DEST _MOVE _TOWARDS )
{
Icon = MovementPhaseTypeFinalDestMoveTowardsIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _EMT _LOS _MOVE _TOWARDS )
{
Icon = MovementPhaseTypeEMT _LOS _MoveTowardsIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _MOVE _TOO _MESH _USING _PATH _NODES )
{
Icon = MovementPhaseTypeMovingToNavMeshUsingPathNodesIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _FALLBACK _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POS )
{
Icon = MovementPhaseTypeFALLBACK _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _FALLBACK _REFUSED _TO _BE _EXPLOTIED _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POS )
{
Icon = MovementPhaseTypeFALLBACK _REFUSED _TO _BE _EXPLOTIED _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon ;
}
else if ( CurrentMovementPhase == MOVEMENT _PHASE _TYPE _STUCK )
{
if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _NO _NAV _MESH _PATH )
{
Icon = TypeOfMovementStuckOnMOVE _FAILURE _TYPE _NO _NAV _MESH _PATHIcon ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMES )
{
Icon = TypeOfMovementStuckOnMOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMESIcon ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TO )
{
Icon = TypeOfMovementStuckOnMOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TOIcon ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _LOOKING _FOR _BETTER _INTERMEDIATE _LOCATION )
{
Icon = TypeOfMovementStuckOnLookingForBetterIntermediateLoc ;
}
else if ( TypeOfMovementStuckOn == MOVE _FAILURE _TYPE _MOVING _TO _BETTER _INTERMEDIATE _LOCATION )
{
Icon = TypeOfMovementStuckOnMoveToBetterIntermediate ;
}
else
{
Icon = TypeOfMovementStuckOnUnknownWhyIcon ;
}
}
else
{
Icon = MovementPhaseTypeUnknownIcon ;
}
if ( Icon != None )
{
Canvas . SetPos ( ScreenLoc . X - Icon . SizeX / 2 , ScreenLoc . Y - Icon . SizeY / 2 , ScreenLoc . Z ) ;
Canvas . DrawTexture ( Icon , 1. f ) ;
X = ScreenLoc . X + Icon . SizeX / 2 + 5 ;
Y = ScreenLoc . Y - Icon . SizeY / 2 ;
}
else
{
X = ScreenLoc . X ;
Y = ScreenLoc . Y ;
}
Canvas . SetPos ( X , Y ) ;
ScreenPos . X = Canvas . CurX ;
ScreenPos . Y = Canvas . CurY ;
//Str = "#"$GetRightMost(self);
Canvas . Font = class 'Engine' . Static . GetSmallFont ( ) ;
if ( KfMovementPlugin != none )
{
KfMovementPlugin . DrawDebugToHud ( HUD , 'Recast' ) ;
}
if ( KfStuckFixPlugin != none )
{
KfStuckFixPlugin . DrawDebugToHud ( HUD , 'Recast' ) ;
}
if ( KfLeapPlugin != none )
{
KfLeapPlugin . DrawDebugToHud ( HUD , 'Recast' ) ;
}
}
simulated function DrawBehaviorTreeIconOverhead ( KFHUDBase HUD )
{
//@deprecated with __TW_BASEAI_LEAN_
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Evade / Evasion / Blocking
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Returns a reaction delay only if the projectile/weapon can be evaded */
function bool GetDangerEvadeDelay ( Name InstigatorClassName , out float ReactionDelay , out byte ForcedEvadeDir , out byte bShouldBlock )
{
local int Index , D ;
local sBlockInfo BlockSettings ;
Index = DangerEvadeSettings . Find ( 'ClassName' , InstigatorClassName ) ;
if ( Index != INDEX _NONE )
{
bShouldBlock = 0 ;
// Cache difficulty
D = WorldInfo . Game . GetModifiedGameDifficulty ( ) ;
// Check cooldown
if ( DangerEvadeSettings [ Index ] . LastEvadeTime > 0. f
&& ` TimeSince(DangerEvadeSettings[Index].LastEvadeTime) < DangerEvadeSettings[Index].Cooldowns[D] )
{
return false ;
}
// Check evade chance
if ( D >= DangerEvadeSettings [ Index ] . EvadeChances . Length
|| fRand ( ) > DangerEvadeSettings [ Index ] . EvadeChances [ D ] * ( WorldInfo . Game . NumPlayers == 1 ? DangerEvadeSettings [ Index ] . SoloChanceMultiplier : 1. f ) )
{
// Check block chance
if ( D < DangerEvadeSettings [ Index ] . BlockChances . Length )
{
BlockSettings = MyKFPawn . GetBlockSettings ( ) ;
if ( ` TimeSince(MyKFPawn.LastBlockTime) >= BlockSettings.Cooldown
&& fRand ( ) < DangerEvadeSettings [ Index ] . BlockChances [ D ] * ( WorldInfo . Game . NumPlayers == 1 ? BlockSettings . SoloChanceMultiplier : 1. f ) )
{
// We are blocking
bShouldBlock = 1 ;
}
else
{
return false ;
}
}
else
{
return false ;
}
}
// Check forced evade chances
if ( bShouldBlock == 0 )
{
ForcedEvadeDir = DIR _None ;
if ( DangerEvadeSettings [ Index ] . ForcedEvadeChances . Length > D )
{
if ( fRand ( ) < DangerEvadeSettings [ Index ] . ForcedEvadeChances [ D ] . FL )
{
ForcedEvadeDir = DIR _ForwardLeft ;
}
else if ( fRand ( ) < DangerEvadeSettings [ Index ] . ForcedEvadeChances [ D ] . FR )
{
ForcedEvadeDir = DIR _ForwardRight ;
}
}
// Set our delay
ReactionDelay = RandRange ( DangerEvadeSettings [ Index ] . ReactionDelayRanges [ D ] . X , DangerEvadeSettings [ Index ] . ReactionDelayRanges [ D ] . Y ) ;
}
// Set our evade time for cooldowns
DangerEvadeSettings [ Index ] . LastEvadeTime = WorldInfo . TimeSeconds ;
return true ;
}
return false ;
}
/** Receives a melee warning including direction of attack and direction from pawn to attacker */
function ReceiveMeleeWarning ( EPawnOctant MeleeDir , vector ProjectionToAttacker , Pawn Attacker )
{
local vector NormalDir ;
local EPawnOctant AttackerDir ;
local byte BestDir ;
// Early out if blocking isn't possible
if ( ! MyKFPawn . CanBlock ( ) )
{
return ;
}
NormalDir = Normal ( ProjectionToAttacker ) ;
// No blocking attacks from behind
if ( vector ( MyKFPawn . Rotation ) dot NormalDir < MyKFPawn . GetMinBlockFOV ( ) )
{
return ;
}
BestDir = DIR _None ;
// Start with which direction attack is coming from
AttackerDir = class 'KFPawn' . static . CalcQuadRegion ( MyKFPawn . Rotation , NormalDir ) ;
if ( AttackerDir == DIR _Left || AttackerDir == DIR _Right )
{
BestDir = AttackerDir ;
}
else if ( AttackerDir == DIR _Forward )
{
// If we're being attacked from the front, use melee direction to determine block
BestDir = MeleeDir == DIR _Left ? DIR _Right : DIR _Left ;
}
if ( BestDir != DIR _None )
{
MyKFPawn . DoSpecialMove ( SM _Block , MyKFPawn . bIsBlocking , , class 'KFSM_Block' . static . PackBlockSMFlags ( BestDir ) ) ;
}
}
/** Deprecated, doesn't work well for our purposes */
function ReceiveProjectileWarning ( Projectile Proj ) ;
/** Warns the AI that it needs to evade from a particular location */
function ReceiveLocationalWarning ( vector DangerPoint , vector DangerInstigatorLocation , optional Object EvadeCauser )
{
local byte BestDir ;
local float ReactionDelay ;
local byte bWantsBlock ;
if ( MyKFPawn == none )
{
return ;
}
if ( CanEvade ( ) || MyKFPawn . CanDoSpecialMove ( SM _Block ) )
{
// Make sure we're not evading something we can't even see
if ( vector ( MyKFPawn . Rotation ) dot Normal ( DangerInstigatorLocation - MyKFPawn . Location ) < 0.1 f )
{
return ;
}
// Attempt to look up our danger evade settings
if ( EvadeCauser != none )
{
// Returns false if it has no entry, is on cooldown, or it failed the evade chance check
if ( ! GetDangerEvadeDelay ( EvadeCauser . class . name , ReactionDelay , BestDir , bWantsBlock ) )
{
return ;
}
}
else
{
// Make sure we always have a valid reaction delay
ReactionDelay = RandRange ( 0. f , 0.2 f ) ;
}
// Do blocks first
if ( bWantsBlock == 1 )
{
BestDir = class 'KFPawn' . static . CalcQuadRegion ( MyKFPawn . Rotation , DangerPoint - MyKFPawn . Location ) ;
if ( BestDir != DIR _None )
{
MyKFPawn . DoSpecialMove ( SM _Block , MyKFPawn . bIsBlocking , , class 'KFSM_Block' . static . PackBlockSMFlags ( BestDir ) ) ;
}
return ;
}
// Calculate evade direction if there was no forced direction
if ( BestDir == DIR _None )
{
BestDir = GetBestEvadeDir ( DangerPoint , , true ) ;
}
if ( BestDir != DIR _None )
{
DoEvade ( BestDir , , DangerInstigatorLocation , ReactionDelay , true ) ;
}
}
}
function NotifyTakeHit ( Controller InstigatedBy , vector HitLocation , int Damage , class < DamageType > damageType , vector Momentum )
{
local sBlockInfo BlockSettings ;
local byte BestDir ;
super . NotifyTakeHit ( InstigatedBy , HitLocation , Damage , damageType , Momentum ) ;
if ( MyKFPawn == none )
{
return ;
}
UpdateLasDamageTime ( ) ;
// See if we should trigger our block
if ( ! MyKFPawn . bIsBlocking )
{
BlockSettings = MyKFPawn . GetBlockSettings ( ) ;
// If there's no blocking chance, early out
if ( BlockSettings . Chance > 0. f )
{
AccumulatedBlockDamage += Damage ;
// If accumulated damage has reached or exceeded the threshold, attempt to trigger a block
if ( AccumulatedBlockDamage >= MyKFPawn . HealthMax * BlockSettings . DamagedHealthPctToTrigger
&& MyKFPawn . Physics == PHYS _Walking
&& MyKFPawn . IsCombatCapable ( )
&& MyKFPawn . CanBlock ( ) )
{
AccumulatedBlockDamage = 0 ;
// Get direction we should block
BestDir = class 'KFPawn' . static . CalcQuadRegion ( MyKFPawn . Rotation , Momentum ) ;
MyKFPawn . DoSpecialMove ( SM _Block , MyKFPawn . bIsBlocking , , class 'KFSM_Block' . static . PackBlockSMFlags ( BestDir ) ) ;
return ;
}
}
}
// See if we should trigger our evade
if ( EvadeOnDamageSettings . Chance > 0. f )
{
AccumulatedEvadeDamage += Damage ;
// If accumulated damage has reached or exceeded the threshold, attempt to trigger an evade
if ( AccumulatedEvadeDamage >= MyKFPawn . HealthMax * EvadeOnDamageSettings . DamagedHealthPctToTrigger
&& fRand ( ) < EvadeOnDamageSettings . Chance
&& CanEvade ( ) )
{
AccumulatedEvadeDamage = 0 ;
// Get direction we should evade
BestDir = DIR _None ;
if ( EvadeOnDamageSettings . ForcedEvadeChance . FL > 0. f && fRand ( ) < EvadeOnDamageSettings . ForcedEvadeChance . FL )
{
BestDir = DIR _ForwardLeft ;
}
else if ( EvadeOnDamageSettings . ForcedEvadeChance . FR > 0. f && fRand ( ) < EvadeOnDamageSettings . ForcedEvadeChance . FR )
{
BestDir = DIR _ForwardRight ;
}
// If we don't have a forced evade direction, pick one based on the hit location
if ( BestDir == DIR _None )
{
BestDir = GetBestEvadeDir ( HitLocation , , true ) ;
}
if ( BestDir != DIR _None )
{
DoEvade ( BestDir , , ( InstigatedBy != none && InstigatedBy . Pawn != none ) ? InstigatedBy . Pawn . Location : vect ( 0 , 0 , 0 ) , RandRange ( 0. f , 0.2 f ) , true ) ;
}
}
}
}
function UpdateLasDamageTime ( )
{
LastDamageTime _Taken = WorldInfo . TimeSeconds ;
}
/** Notification that we've been damaged by a friendly AI */
function NotifyFriendlyAIDamageTaken ( Controller DamagerController , int Damage , Actor DamageCauser , class < KFDamageType > DamageType )
{
local int Idx ;
local Pawn BlockerPawn ;
if ( DamageType == none || DamageType . default . bIgnoreAggroOnDamage )
{
return ;
}
// Retrieves the index and, if necessary, creates a new entry
Idx = UpdateFriendlyDamageHistory ( DamagerController , Damage ) ;
if ( Idx == INDEX _NONE )
{
return ;
}
if ( IsAggroEnemySwitchAllowed ( )
&& DoorEnemy == none
&& PendingDoor == none
&& DamagerController . Pawn != Enemy
&& FriendlyDamageHistory [ Idx ] . Damage >= float ( Pawn . HealthMax ) * AggroZedHealthPercentage )
{
BlockerPawn = GetPawnBlockingPathTo ( DamagerController . Pawn ) ;
if ( BlockerPawn == none )
{
SetEnemyToZed ( DamagerController . Pawn ) ;
}
}
}
/** Updates our friendly damage history, and returns the index in the damage history array */
native function int UpdateFriendlyDamageHistory ( Controller DamagerController , int Damage ) ;
/** Called from KFProjectile after certain impact criteria is met, to warn AI of a pending explosion */
function DoProjectileWarning ( KFProjectile KFProj )
{
if ( MyKFPawn == none || MyKFPawn . Health <= 0 || ( ! MyKFPawn . CanDoSpecialMove ( SM _Evade ) && ! MyKFPawn . CanDoSpecialMove ( SM _Evade _Fear ) ) )
{
return ;
}
HandleProjectileWarning ( KFProj ) ;
}
function HandleProjectileWarning ( Projectile Proj )
{
if ( MyKFPawn == none || MyKFPawn . Health <= 0 || ( ! MyKFPawn . CanDoSpecialMove ( SM _Evade ) && ! MyKFPawn . CanDoSpecialMove ( SM _Evade _Fear ) ) )
{
return ;
}
if ( CanEvadeGrenade ( ) && Proj . IsA ( 'KFProj_Grenade' ) )
{
// if we're looking in that direction and skilled enough, try an evasive maneuver. TODO: Add KFProjectile flag to check instead of IsA.
if ( ! IsTimerActive ( nameof ( Timer _DoProjectileEvade ) ) &&
( vector ( Pawn . Rotation ) dot Normal ( Proj . Instigator . Location - Pawn . Location ) > 0.7 ) )
{
//TimeToImpact = Proj.GetTimeToLocation(Pawn.Location);
PendingEvadeProjectile = Proj ;
MyKFPawn . SetHeadTrackTarget ( PendingEvadeProjectile , , 1. f ) ;
SetTimer ( ( 0.265 + FRand ( ) * 0.12 f ) , false , nameof ( Timer _DoProjectileEvade ) ) ;
}
}
}
function bool CanEvadeGrenade ( )
{
return ( FRand ( ) < EvadeGrenadeChance ) ;
}
/** triggers an evade from the PendingEvadeProjectile, if possible and it's still on target */
final function Timer _DoProjectileEvade ( )
{
local byte BestDir ;
/ * * W h e n e v a d i n g p r o j e c t i l e s , h e a d t r a c k i n g i s v e r y b r i e f l y e n a b l e d w h e n t h i s t i m e r i s s t a r t e d , a n d e n d e d h e r e w i t h i n
the timer . This helps to make the NPC reaction look a little more realistic , turning its head toward the threat right
before responding to it . * /
MyKFPawn . SetHeadTrackTarget ( none ) ;
if ( MyKFPawn != none && ( MyKFPawn . CanDoSpecialMove ( SM _Evade ) || MyKFPawn . CanDoSpecialMove ( SM _Evade _Fear ) ) && PendingEvadeProjectile != none && ! PendingEvadeProjectile . bDeleteMe
&& ! IsZero ( PendingEvadeProjectile . Velocity ) && CanEvade ( ) )
{
/** Find the safest direction to evade in - away from the threat and hopefully not into a larger threat or obstruction. */
BestDir = GetBestEvadeDir ( PendingEvadeProjectile . Location , PendingEvadeProjectile . Instigator ) ;
if ( BestDir != DIR _None )
{
DoEvade ( BestDir , PendingEvadeProjectile , , 0.1 f + FRand ( ) * 0.2 f , true ) ;
}
}
PendingEvadeProjectile = None ;
}
/** Returns true if my pawn is permitted to evade away from something. */
function bool CanEvade ( optional bool bOverrideSpecialMove )
{
return MyKFPawn . Physics == PHYS _Walking
&& ( MyKFPawn . CanDoSpecialMove ( SM _Evade ) || MyKFPawn . CanDoSpecialMove ( SM _Evade _Fear ) )
&& ( bOverrideSpecialMove || ! MyKFPawn . IsDoingSpecialMove ( ) )
&& MyKFPawn . IsCombatCapable ( ) ;
}
/** Pick best direction to evade */
final function byte GetBestEvadeDir ( Vector DangerPoint , optional Pawn ThreatPawn , optional bool bUseFastTrace = true , optional bool bCross )
{
local vector X , Y , Z , VectToDanger , Offset , EvadeLocation , Extent ;
local bool bLeftOpen , bRightOpen , bFrontOpen , bBackOpen ;
local float DotX , CheckHeight ;
local EPawnOctant EvadeDir ;
if ( bCross )
{
EvadeDir = class 'KFPawn' . static . CalcOctagonRegion ( MyKFPawn . GetViewRotation ( ) , ( Normal ( ThreatPawn . Location - Pawn . Location ) Cross vect ( 0 , 0 , 1 ) ) ) ;
return EvadeDir ;
}
// Figure out which directions we can evade(Normal(IncomingKFP.Location - Pawn.Location) Cross vect(0,0,1))
EvadeDir = class 'KFPawn' . static . CalcOctagonRegion ( MyKFPawn . GetViewRotation ( ) , normal ( MyKFPawn . Location - DangerPoint ) ) ;
GetAxes ( Pawn . Rotation , X , Y , Z ) ;
switch ( EvadeDir )
{
case DIR _ForwardLeft :
return DIR _ForwardLeft ;
case DIR _ForwardRight :
return DIR _ForwardRight ;
case DIR _BackwardLeft :
return DIR _BackwardLeft ;
case DIR _BackwardRight :
return DIR _BackwardRight ;
case DIR _Forward :
if ( ! bUseFastTrace || FastActorTrace ( Pawn , Pawn . Location + 256. f * X , Pawn . Location , Pawn . GetCollisionExtent ( ) * 0.5 f ) )
{
return DIR _Forward ;
}
break ;
case DIR _Backward :
if ( ! bUseFastTrace || FastActorTrace ( Pawn , Pawn . Location - 256. f * X , Pawn . Location , Pawn . GetCollisionExtent ( ) * 0.5 f ) )
{
return DIR _Backward ;
}
break ;
case DIR _Left :
if ( ! bUseFastTrace || FastActorTrace ( Pawn , Pawn . Location - 256. f * Y , Pawn . Location , Pawn . GetCollisionExtent ( ) * 0.5 f ) )
{
return DIR _Left ;
}
break ;
case DIR _Right :
if ( ! bUseFastTrace || FastActorTrace ( Pawn , Pawn . Location + 256. f * Y , Pawn . Location , Pawn . GetCollisionExtent ( ) * 0.5 f ) )
{
return DIR _Right ;
}
break ;
}
if ( EvadeDir == DIR _None && ThreatPawn != none )
{
CheckHeight = ( MyKFPawn . GetCollisionHeight ( ) * 0.5 ) + MyKFPawn . MaxStepHeight ;
GetAxes ( MyKFPawn . Rotation , X , Y , Z ) ;
VectToDanger = Normal ( DangerPoint - Pawn . Location ) ;
DotX = X DOT VectToDanger ;
//DotY = Y DOT VectToDanger;
Extent = Pawn . GetCollisionExtent ( ) * 0.5 f ;
// If dot is mostly forward or backward
if ( DotX >= 0.7071 f || DotX <= - 0.7071 f )
{
Offset = X * 300. f ;
EvadeLocation = Pawn . Location - Offset ;
bBackOpen = CanReachEvadeLocation ( EvadeLocation , CheckHeight , Extent ) ;
if ( bBackOpen && DotX >= 0.7071 )
{
return DIR _Backward ;
}
EvadeLocation = Pawn . Location + Offset ;
bFrontOpen = CanReachEvadeLocation ( EvadeLocation , CheckHeight , Extent ) ;
if ( bFrontOpen && DotX <= - 0.7071 )
{
return DIR _Forward ;
}
}
else
{
Offset = Y * 300.0 f ;
EvadeLocation = Pawn . Location + Offset ;
bRightOpen = CanReachEvadeLocation ( EvadeLocation , CheckHeight , Extent ) ;
if ( bRightOpen )
{
return DIR _Right ;
}
EvadeLocation = Pawn . Location - Offset ;
bLeftOpen = CanReachEvadeLocation ( EvadeLocation , CheckHeight , Extent ) ;
if ( bLeftOpen )
{
return DIR _Left ;
}
}
}
return DIR _None ;
}
/** Checks to see if a potential location to evade to is obstructed */
function bool CanReachEvadeLocation ( vector EvadeLocation , float CheckHeight , vector Extent )
{
if ( ` FastTracePhysX(EvadeLocation, Pawn.Location)
&& ! FastTrace ( EvadeLocation + ( vect ( 0 , 0 , - 1 ) * CheckHeight ) , EvadeLocation , Extent ) )
{
if ( ! IsPawnBlockingLine ( Pawn . Location , EvadeLocation ) )
{
return true ;
}
}
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Door handling ( attacking , passing through )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Notification that NPC needs to stop moving and wait for door */
event WaitForDoor ( KFDoorActor Door )
{
` RecordAIDoor( ` StatID ( AI _WAITFORDOOR ) , self , Pawn . Location , Door , "Waiting at " $WorldInfo . TimeSeconds ) ;
` AILog( GetFuncName() $ "() Waiting for door " $ Door $ " to open or be destroyed", 'Doors' );
SetTimer ( 5. f , true , nameof ( Timer _WaitingForDoor ) ) ;
DoorEnemy = none ;
PendingDoor = Door ;
Door . bMonitorDoor = true ;
bPreparingMove = true ;
//MoveTimer = -1.0f; // To be enabled
StopAllLatentMovement ( ) ; // To be enabled
Pawn . Acceleration = vect ( 0 , 0 , 0 ) ;
// if( GetActiveCommand().IsA('AICommand_MoveToGoal') )
// {
// ForcePauseAndRepath();
// }
}
/ * * D e b u g t i m e r s e t w h e n N P C b e g i n s w a i t i n g f o r a d o o r . T i m e r i s c l e a r e d b y D o o r F i n i s h e d ( ) . C u r r e n t l y o n l y
in use to find any NPCs who might become stuck indefinitely while waiting for a door to finish opening ,
which hasn ' t been an issue lately . * /
function Timer _WaitingForDoor ( )
{
` AILog( "** WARNING ** [" $ GetFuncName() $ "] I've been waiting for at least 10 seconds for door " $ PendingDoor $ " bPreparingMove: " $ bPreparingMove $ " (Command: " $ CachedAICommandList $ ")", 'Doors' );
DoorFinished ( ) ;
}
/ * * D o o r h a s f i n i s h e d o p e n i n g , l e t t h e N P C m o v e t h r o u g h - n o t i f i c a t i o n c o m e s f r o m t h e d o o r i t s e l f i f m y
bMonitorDoor flag is true . * /
function bool DoorFinished ( )
{
` AILog( GetFuncName() $ "() door has finished opening or is destroyed!", 'Doors' );
if ( Pawn == None || PendingDoor . MyMarker == None || PendingDoor . MyMarker . ProceedWithMove ( Pawn ) )
{
` RecordAIDoor( ` StatID ( AI _FINISHEDWAITFORDOOR ) , self , Pawn . Location , PendingDoor , "Waiting at " $WorldInfo . TimeSeconds ) ;
` AILog( GetFuncName() $ "() setting pending door to none, bPreparingMove to false. Proceeding with move.", 'Doors' );
PendingDoor = None ;
// Notify command list that door is open
if ( CachedAICommandList != None )
{
CachedAICommandList . NotifyDoorOpened ( ) ;
}
ClearTimer ( nameof ( Timer _WaitingForDoor ) ) ;
return true ;
}
` AILog( GetFuncName() $ "() *** WARNING *** I received DoorFinished event but returned false! " $ CachedAICommandList, 'Doors' );
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* ATTACKING DOORS / ACTORS INSTEAD OF PAWNS
* "Enemy" is a Pawn , so we also have "DoorEnemy" and "ActorEnemy" .
*
* TODO : Get rid of DoorEnemy , and and replace it with ActorEnemy for attacking doors
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Notification that NPC needs to attack this door */
function NotifyAttackDoor ( KFDoorActor Door )
{
local KFDoorMarker DoorMarker ;
local int AttackerCount , QueuedCount ;
local byte SMFlags ;
DoorMarker = KFDoorMarker ( Door . MyMarker ) ;
if ( DoorMarker != none )
{
Door . GetQueuedDoorAICounts ( AttackerCount , QueuedCount ) ;
// Force a repath if there are too many zeds queued at this door
if ( AttackerCount + QueuedCount > 8 && DoorEnemy != Door )
{
DoorEnemy = none ;
PendingDoor = none ;
if ( Enemy != none && Focus != Enemy )
{
Focus = none ;
}
if ( Enemy == none )
{
ChangeEnemy ( GetClosestEnemy ( ) , false ) ;
}
// Update the cost of this door marker based on the number queued
DoorMarker . UpdatePathingCost ( AttackerCount , QueuedCount ) ;
// Tell our move commands that we're leaving
NotifyNeedRepath ( ) ;
return ;
}
else
{
// Update the cost of this door marker based on the number queued
DoorMarker . UpdatePathingCost ( AttackerCount + ( DoorEnemy != Door ? 1 : 0 ) , QueuedCount ) ;
}
}
` AILog( GetFuncName() $ "() initializing AICommand_Attack_Melee, MoveTarget: " $ MoveTarget $ " Dist: " $ VSize( Door.MyMarker.Location - Pawn.Location ), 'Doors' );
/** Sets the pending door for reference */
PendingDoor = Door ;
/** DoorEnemy is used by attack code */
DoorEnemy = Door ;
/** When opened (or destroyed), a KFDoorActor will notify any AIControllers (with "bMonitorDoor" true) that it's opened/closed status has changed */
Door . bMonitorDoor = true ;
/** Prevents any latent movement while true */
bPreparingMove = true ;
/** Stop moving and face the door */
StopAllLatentMovement ( ) ;
AIZeroMovementVariables ( ) ;
Focus = Door ;
SMFlags = 255 ;
/** Try to get a valid door attack from my pawn's AnimInfo */
SMFlags = class 'KFSM_DoorMeleeAttack' . static . PackSMFlags ( MyKFPawn ) ;
if ( SMFlags != 255 )
{
class 'AICommand_Attack_Melee' . static . Melee ( self , Door , SMFlags ) ;
}
else
{
class 'AICommand_Attack_Melee' . static . Melee ( self , Door ) ;
}
}
/ * * C a n b e c a l l e d t o i n i t i a t e a m e l e e a t t a c k o n a n o n - p a w n a t t a c k t a r g e t , l i k e a d e s t r u c t i b l e a c t o r .
Currently called from FleshPound ' s Bump ( ) if the bumped - into actor is destructible . * /
function NotifyAttackActor ( Actor A )
{
if ( ! MyKFPawn . IsDoingSpecialMove ( ) )
{
ActorEnemy = A ;
StopAllLatentMovement ( ) ;
AIZeroMovementVariables ( ) ;
bPreparingMove = true ;
Focus = A ;
class 'AICommand_Attack_Melee' . static . Melee ( self , A ) ;
}
}
function bool CanAttackDestructibles ( )
{
return bCanDoHeavyBump ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Dead State
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
State Dead
{
ignores SeePlayer , HearNoise , KilledBy ;
function BeginState ( name PreviousStateName )
{
` AILog( "DEAD", 'Damage' );
super . BeginState ( PreviousStateName ) ;
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DebugState - Add "`DEBUGSTATE" to state declaration to make that state descend from
* this , which will then log any related state transitions for it , assuming you ' re
* calling the super version if you override any of these functions / events in your own
* state .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
state DebugState
{
function BeginState ( Name PreviousStateName )
{
` AILog( "BEGINSTATE"@PreviousStateName, 'State' );
}
function EndState ( Name NextStateName )
{
` AILog( "ENDSTATE"@NextStateName, 'State' );
}
function PushedState ( )
{
` AILog( "PUSHED", 'State' );
}
function PoppedState ( )
{
` AILog( "POPPED", 'State' );
}
function ContinuedState ( )
{
` AILog( "CONTINUED", 'State' );
}
function PausedState ( )
{
` AILog( "PAUSED", 'State' );
}
}
/** Log the NPC's routecache (list of navigation points to traverse for current path, if any) */
final function DebugLogRoute ( )
{
` if( ` notdefined ( ShippingPC ) )
local String RouteString ;
local int i ;
for ( i = 0 ; i < RouteCache . Length ; i ++ )
{
RouteString = "DebugLogRoute()" ;
if ( Pawn . Anchor != none )
{
RouteString = RouteString$ " [Anchor:" $Pawn . Anchor$ "][Dist:" $VSize ( Pawn . Anchor . Location - Pawn . Location ) $ "]" ;
}
RouteString = RouteString$ "[RC " $i$ ":" $RouteCache [ i ] $ "]" ;
if ( i + 1 < RouteCache . Length )
{
RouteString = RouteString$ "," ;
}
}
if ( RouteCache . Length == 0 )
{
RouteString = "Empty Route" ;
}
else
{
` AILog( "DebugLogRoute(): " $ RouteString, 'PathWarning',,, true );
}
` endif
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Action _Idle State - default state this controller uses while pawn is active
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
state Action _Idle ` DEBUGSTATE
{
event BeginState ( Name PreviousStateName )
{
Super . BeginState ( PreviousStateName ) ;
Enable ( 'SeePlayer' ) ;
` AILog( "Entering Action_Idle() state, previous state was " $ PreviousStateName, 'Action_Idle' );
if ( Pawn != none )
{
` AILog( "Action_Idle BeginState() calling Pawn.ZeroMovementVariables()", 'Action_Idle' );
AIZeroMovementVariables ( ) ;
}
}
function ContinuedState ( )
{
Super . ContinuedState ( ) ;
if ( Pawn != none )
{
` AILog( "Action_Idle ContinuedState() calling Pawn.ZeroMovementVariables()", 'Action_Idle' );
AIZeroMovementVariables ( ) ;
}
}
event EndState ( name NextStateName )
{
Super . EndState ( NextStateName ) ;
` AILog( "Ending Action_Idle state and going to " $ NextStateName, 'Action_Idle' );
}
Begin :
Sleep ( 0. f ) ;
// Wait until Emerge SpecialMove is complete before beginning state code
// 8/5/14: Wait for all special moves! (fixes knockdown bug)
// @todo: Needs AI review. Setting PHYS_Walking below is unsafe and causing
// special move errors. Doing this wait before WaitForLanding is also unsafe
if ( MyKFPawn == none || MyKFPawn . Health <= 0 || MyKFPawn . IsDoingSpecialMove ( ) )
{
Sleep ( 0.1 f ) ;
Goto ( 'Begin' ) ;
}
` AILog( self $ " -- Begin Label", 'Action_Idle' );
if ( Pawn != none && Pawn . Physics == PHYS _Falling )
{
AIActionStatus = "Falling" ;
WaitForLanding ( ) ;
}
if ( ! MyKFPawn . bCrawler || ! bSpawnedByEmergeSpecialMove )
{
Pawn . SetPhysics ( PHYS _Walking ) ;
}
// Begins the debug command (can be customized) if flag is set.
if ( bForceDebugCommand )
{
BeginDebugCommand ( ) ;
// Stop state code execution here so that no other commands will be loaded.
Stop ;
}
// Auto-acquire an enemy even if it's not visible to my pawn. This also will
// start up the MoveToEnemy command if a valid enemy is found.
Goto ( 'IdleMoveToNearestEnemy' ) ;
IdleMoveToNearestEnemy :
if ( bIdleMoveToNearestEnemy )
{
if ( Physics == PHYS _Falling )
{
WaitForLanding ( ) ;
}
//SetEnemy( GetClosestEnemy() );
if ( ! FindNewEnemy ( ) )
{
// Fallback
SetEnemy ( GetClosestEnemy ( ) ) ;
}
if ( Enemy != none )
{
` AILog( self $ " -- GetClosestEnemy() returned " $ Enemy, 'Action_Idle' );
AIActionStatus = "Starting to hunt" ;
BeginCombatCommand ( GetDefaultCommand ( ) , "Initial aggressive move" ) ;
Stop ;
}
else
{
` AILog( self $ " Pausing and going back to begin label - haven't found an enemy yet.", 'Action_Idle' );
AIActionStatus = "Can't find an enemy!" ;
// if( bSpawnedByEmergeSpecialMove || MySpawnVolume != none )
// {
// // Only sleep for a frame if spawned using Emerge special move
// Sleep( 0.f );
// }
// else
// {
Sleep ( 0.5 f + FRand ( ) * 1.5 f ) ;
// }
}
}
else
{
` AILog( self $ " Not moving yet because bIdleMoveToNearestEnemy is false", 'Action_Idle' );
Sleep ( 0.25 f + FRand ( ) * 0.5 f ) ;
}
Goto ( 'Begin' ) ;
}
function float GetMinDistanceToAnyPlayer ( )
{
local PlayerController PC ;
local float MinRange ;
local float CurRange ;
MinRange = - 1. f ;
if ( Pawn == none )
{
return - 1. f ;
}
ForEach WorldInfo . AllControllers ( class 'PlayerController' , PC )
{
if ( PC != none && PC . ViewTarget != None )
{
CurRange = VSizeSq ( PC . ViewTarget . Location - Pawn . Location ) ;
if ( CurRange < MinRange || MinRange < 0. f )
{
MinRange = CurRange ;
}
}
}
return sqrt ( MinRange ) ;
}
function NotifyEnRaged ( bool bEnraged ) ;
//If this function returns true, use the AI controller's spawn enrage.
// If this is false, recommended to use the direct call into the pawn.
function bool SpawnEnraged ( )
{
return false ;
}
function ResetKFAIC ( )
{
if ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( ) )
{
MyKFPawn . EndSPecialMove ( ) ;
SetTimer ( 0.1 , false , nameof ( ResetKFAIC ) , self ) ;
return ;
}
AIZeroMovementVariables ( ) ;
StopLatentExecution ( ) ;
StopAllLatentMovement ( ) ;
Enemy = none ;
DoorEnemy = none ;
CachedVisibleEnemy = none ;
MeleeTarget = none ;
MoveTarget = none ;
MoveGoal = none ;
LastHitWall = none ;
PendingDoor = none ;
RouteGoal = none ;
MoveFailureCount = 0 ;
FailedMoveToEnemyCount = 0 ;
HitWallCount = 0 ;
RouteCache . Length = 0 ;
LastTauntTime = 0. f ;
LastNotifyHitWallTime = 0. f ;
LastBumpTime = 0. f ;
LastObstructionTime = 0. f ;
MoveTimer = - 1. f ;
bHasDebugCommand = false ;
bPreparingMove = false ;
bAdjusting = false ;
bReevaluatePath = false ;
IsRotationLocked = false ;
bEnemyIsVisible = false ;
bReachedLatentMoveGoal = false ;
LockedRotation = rot ( 0 , 0 , 0 ) ;
// Init AI Steering object in 1-3 seconds
SetTimer ( RandRange ( 1 , 3 ) , false , nameof ( StartSteering ) ) ;
BeginCombatCommand ( None , "Reset" ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Victory
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Enter this state when the match is over and the zeds have won */
state ZedVictory
{
function bool GetIsInZedVictoryState ( )
{
return true ;
}
function bool CanDoStrike ( )
{
return false ;
}
event bool CanGrabAttack ( )
{
return false ;
}
Begin :
DisableMeleeRangeEventProbing ( ) ;
if ( FRand ( ) <= 0.6 f )
{
if ( FRand ( ) <= 0.5 f )
{
class 'AICommand_TauntEnemy' . static . Taunt ( self , none , TAUNT _EnemyKilled ) ;
}
else
{
class 'AICommand_TauntEnemy' . static . Taunt ( self , none , TAUNT _Standard ) ;
}
}
else
{
DoWander ( Pawn , , true ) ;
}
Sleep ( 0. f ) ;
if ( MyKFPawn == none || MyKFPawn . Health <= 0 || MyKFPawn . IsDoingSpecialMove ( ) )
{
Sleep ( 0.1 f ) ;
Goto ( 'Begin' ) ;
}
if ( Pawn != none && Pawn . Physics == PHYS _Falling )
{
AIActionStatus = "Falling" ;
WaitForLanding ( ) ;
}
Goto ( 'Begin' ) ;
}
function bool GetIsInZedVictoryState ( )
{
return false ;
}
function EnterZedVictoryState ( )
{
ClearMovementInfo ( ) ;
if ( CommandList != None )
{
AbortCommand ( CommandList ) ;
}
DisableMeleeRangeEventProbing ( ) ;
ChangeEnemy ( none ) ;
MyKFPawn . SetSprinting ( false ) ;
GotoState ( 'ZedVictory' , 'Begin' ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
View and Rotation related functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Use where the bot is actually looking from
simulated event GetPlayerViewPoint ( out vector out _Location , out Rotator out _Rotation )
{
GetActorEyesViewPoint ( out _Location , out _Rotation ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Enemy Threat Evaluations [ EXPERIMENTAL , has not been in use , but if we want more intelligent
* NPC decisions regarding threat evaluation , the below two functions are worth trying out .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * E x p e r i m e n t a l - s c o r e s p o t e n t i a l n e w e n e m y b a s e d o n m a n y c o n d i t i o n s :
Distance
Is CheckPawn moving toward or away from me ?
Am I currently visible to CheckPawn ?
Have I recently taken damage from CheckPawn ? * /
event float EvaluateThreatFrom ( Pawn CheckPawn , optional float EarlyOutScore )
{
local KFPawn Threat ;
local float DistToThreat , ThreatScore ;
local int RecentDamageFromThreat ; //ZedsTargetingThreat;
local string outmsg ;
local float modscore , Dot ;
local vector Loc ;
local rotator Rot ;
local float DistScore ;
local float ClosestDist ;
local float Time ;
Threat = KFPawn ( CheckPawn ) ;
RecentDamageFromThreat = MyKFPawn . RecentDamageFrom ( Threat ) ;
DistToThreat = VSize ( Threat . Location - Pawn . Location ) ;
// Count # of Zeds who are currently targeting CheckPawn and closer to CheckPawn than I am
//ZedsTargetingThreat = NumberOfZedsTargetingPawn( Threat, true, DistToThreat * 1.75f );
if ( CheckPawn . IsHumanControlled ( ) && CheckPawn . PlayerReplicationInfo != none )
{
outmsg = "[" $CheckPawn . PlayerReplicationInfo . PlayerName$ "] THREATSCORE" ;
}
else
{
outmsg = "[" $CheckPawn$ "] THREATSCORE" ;
}
if ( DistToThreat < 2500. f )
{
ThreatScore += ( 2500. f - DistToThreat ) / 2500. f ;
DistScore = ThreatScore ;
}
if ( MyKFPawn . IsPawnMovingTowardMe ( Threat , 300. f ) )
{
modscore = 0.12 f * DistScore ;
outmsg = outmsg$ " [MOVING TOWARD ME SCORE: " $modscore$ "]" ;
ThreatScore += 0.12 f * DistScore ;
GetClosestTimeAndDistToPawn ( Threat , Time , ClosestDist ) ;
if ( Time > 0. f && Time < 2.5 f && ClosestDist < 250. f )
{
modscore = ( 2.5 f - Time ) / 2.5 f ;
ThreatScore += modscore ;
outmsg = outmsg$ " [TIME TO ENEMY: " $modscore$ " TIME: " $Time$ "]" ;
}
}
else if ( MyKFPawn . IsPawnMovingAwayFromMe ( Threat , 300. f ) )
{
modscore = - 0.15 f * ( 1 / DistScore ) ;
outmsg = outmsg$ " [MOVING AWAY SCORE: " $modscore$ "]" ;
ThreatScore -= 0.15 f * ( 1 / DistScore ) ;
}
if ( Threat . Controller . CanSee ( Pawn ) )
{
Threat . Controller . GetPlayerViewPoint ( Loc , Rot ) ;
Dot = Normal ( Pawn . Location - Loc ) dot vector ( Rot ) ;
if ( Dot > 0.7 f )
{
//modscore = 1.f - Dot;
modscore += Dot * DistScore ;
ThreatScore += Dot * DistScore ;
}
else
{
modscore = 0.1 f ;
ThreatScore -= 0.1 f ;
}
//msg( "[Dot: "$Dot$"] TEST: "$modscore$" versus further scaled "$modscore * (DistScore * 0.88f)$"... dist score was "$DistScore$" Dist: "$DistToThreat );
outmsg = outmsg$ " [DOT: " $DOT$ " AIMING AT SCORE: " $modscore$ "]" ;
}
else
{
ThreatScore -= 0.12 f * ( 1 / DistScore ) ;
modscore = 0.12 f * ( 1 / DistScore ) ;
outmsg = outmsg$ " [CANT SEE YOU PENALTY:" $modscore$ " DIST SCORE WAS " $DistScore$ "]" ;
}
if ( RecentDamageFromThreat > 0 )
{
modscore = float ( RecentDamageFromThreat ) / float ( MyKFPawn . HealthMax ) ;
outmsg = outmsg$ " [RECENT DAMAGE:" $RecentDamageFromThreat$ " SCORE: " $modscore$ "]" ;
ThreatScore += float ( RecentDamageFromThreat ) / float ( MyKFPawn . HealthMax ) ;
}
//VSize(Pawn.Velocity) > 0.f &&
//FLOAT DistanceRating = 1 - ActorToLookAt->LastKnownDistance/LookAtActorRadiusSq;
outmsg = "FINAL SCORE: " $ThreatScore$ "..." $outmsg ;
msg ( Threat . PlayerReplicationInfo . PlayerName$ ": " $outmsg ) ;
return ThreatScore ;
}
/** if bDebug_ShowStrikeRange is true, draw the range of every zed around its pawn */
event DrawStrikeRanges ( )
{
local KFPawnAnimInfo KFPAI ;
local byte i ;
if ( MyKFPawn != none && MyKFPawn . PawnAnimInfo != none )
{
KFPAI = MyKFPawn . PawnAnimInfo ;
// Draw inactive ranges first
for ( i = 0 ; i < MyKFPawn . PawnAnimInfo . Attacks . length ; i ++ )
{
if ( i != PendingAnimStrikeIndex &&
( bDebug _ShowAllStrikeRange || KFPAI . AllowAttackByDifficulty ( i ) && KFPAI . AllowAttackByMovement ( i , MyKFPawn ) ) )
{
// Give different colors for the different unused attacks
DrawStrikeRange ( i , KFPAI , MakeColor ( 255 - i * 20 , 0 , i * 20 , 255 ) ) ;
}
}
// Draw pending attack range
if ( PendingAnimStrikeIndex != 255 )
{
DrawStrikeRange ( PendingAnimStrikeIndex , KFPAI , MakeColor ( 255 , 255 , 0 , 255 ) ) ;
}
// Draw the StrikeRange in black. This is the range between min and max multiplied by the StrikeRangePercentage
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 1.57 , 0 , StrikeRange , MakeColor ( 0 , 0 , 0 , 255 ) ) ;
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 0 , 1.57 , StrikeRange , MakeColor ( 0 , 0 , 0 , 255 ) ) ;
// Draw Current Attack
if ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( SM _MeleeAttack ) )
{
DrawStrikeRange ( DebugCurrentStrikeIndex , KFPAI , MakeColor ( 0 , 255 , 0 , 255 ) ) ;
}
// Draw a line for the max hit range
DrawMaxHitRange ( MakeColor ( 0 , 255 , 0 , 255 ) ) ;
}
}
function DrawStrikeRange ( byte Index , KFPawnAnimInfo KFPAI , Color ZoneColor )
{
local float MinDistance , MaxDistance , AverageDistance , CollisionRadius ;
local string TagString ;
CollisionRadius = MyKFPawn . CylinderComponent . CollisionRadius ;
MinDistance = ( KFPAI . Attacks [ Index ] . MinDistance < CollisionRadius ) ? CollisionRadius : KFPAI . Attacks [ Index ] . MinDistance ;
MaxDistance = KFPAI . Attacks [ Index ] . MaxDistance ;
AverageDistance = KFPAI . GetMedianStrikeRange ( Index , 0.5 f , CollisionRadius ) ;
TagString = String ( KFPAI . Attacks [ Index ] . Tag ) ;
// Draws backwards
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 1.57 , 0 , MaxDistance , ZoneColor ) ;
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 1.57 , 0 , MinDistance , ZoneColor ) ;
// Draws forwards
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 0 , 1.57 , MaxDistance , ZoneColor ) ;
DrawStrikeRangeLines ( MyKFPawn . Location , MyKFPawn . Rotation , 0 , 1.57 , MinDistance , ZoneColor ) ;
DrawAttackRangeText ( Index , 40960 , 2048 , AverageDistance , TagString , ZoneColor ) ;
}
event DrawStrikeRangeLines ( Vector OrginOfStrikeZone ,
Rotator RotOrginOfZone ,
float StartAngleInRads ,
float EndAngleInRads ,
float RangeOfZone ,
Color ZoneColor )
{
local Vector arcRightInnerLineEnd ;
local Vector arcMidRightLineEnd ;
local Vector arcFarRightLineEnd ;
local Vector arcLeftInnerLineEnd ;
local Vector arcMidLeftLineEnd ;
local Vector arcFarLeftLineEnd ;
local Rotator arcRightInnerEdge ;
local Rotator arcMidRightEdge ;
local Rotator arcFarRightEdge ;
local Rotator arcLeftInnerEdge ;
local Rotator arcMidLeftEdge ;
local Rotator arcFarLeftEdge ;
local int innerHorArcInUU ;
local int farHorArcInUU ;
local int midHorArcInUU ;
local float verticalShiftForZoneLines ;
verticalShiftForZoneLines = MyKFPawn . CylinderComponent . CollisionHeight ;
OrginOfStrikeZone . Z = OrginOfStrikeZone . Z - verticalShiftForZoneLines ;
innerHorArcInUU = StartAngleInRads * 10430.2192 ;
farHorArcInUU = EndAngleInRads * 10430.2192 ;
midHorArcInUU = ( ( farHorArcInUU - innerHorArcInUU ) * 0.5 ) + innerHorArcInUU ;
//
// Inner Right Edge
//
arcRightInnerEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw + innerHorArcInUU , 0 ) ;
arcRightInnerLineEnd = OrginOfStrikeZone + Vector ( arcRightInnerEdge ) * RangeOfZone ;
//
// far right edge
//
if ( farHorArcInUU > 0 )
{
arcFarRightEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw + farHorArcInUU , 0 ) ;
arcFarRightLineEnd = OrginOfStrikeZone + Vector ( arcFarRightEdge ) * RangeOfZone ;
//MyKFPawn.DrawDebugLine(OrginOfStrikeZone , arcFarRightLineEnd, ZoneColor.R, ZoneColor.G, ZoneColor.B );
}
else
{
midHorArcInUU = ( ( 32767 - innerHorArcInUU ) * 0.5 ) + innerHorArcInUU ;
arcFarRightEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw + 32767 , 0 ) ;
arcFarRightLineEnd = OrginOfStrikeZone + Vector ( arcFarRightEdge ) * RangeOfZone ;
}
//
// Mid right edge
//
arcMidRightEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw + midHorArcInUU , 0 ) ;
arcMidRightLineEnd = OrginOfStrikeZone + Vector ( arcMidRightEdge ) * RangeOfZone ;
//
// Inner left Edge
//
arcLeftInnerEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw - innerHorArcInUU , 0 ) ;
arcLeftInnerLineEnd = OrginOfStrikeZone + Vector ( arcLeftInnerEdge ) * RangeOfZone ;
//
// Far left edge line
//
if ( farHorArcInUU > 0 )
{
arcFarLeftEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw - farHorArcInUU , 0 ) ;
arcFarLeftLineEnd = OrginOfStrikeZone + Vector ( arcFarLeftEdge ) * RangeOfZone ;
//MyKFPawn.DrawDebugLine( OrginOfStrikeZone , arcFarLeftLineEnd, ZoneColor.R, ZoneColor.G, ZoneColor.B );
}
else
{
arcFarLeftEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw - 32767 , 0 ) ;
arcFarLeftLineEnd = OrginOfStrikeZone + Vector ( arcFarLeftEdge ) * RangeOfZone ;
}
//
// Mid left edge line
//
arcMidLeftEdge = MakeRotator ( 0 , RotOrginOfZone . Yaw - MidHorArcInUU , 0 ) ;
arcMidLeftLineEnd = OrginOfStrikeZone + Vector ( arcMidLeftEdge ) * RangeOfZone ;
//
// connect far right to middle right to inner right
//
MyKFPawn . DrawDebugLine ( arcFarRightLineEnd , arcMidRightLineEnd , ZoneColor . R , ZoneColor . G , ZoneColor . B ) ;
MyKFPawn . DrawDebugLine ( arcRightInnerLineEnd , arcMidRightLineEnd , ZoneColor . R , ZoneColor . G , ZoneColor . B ) ;
//
// connect far left to middle left to inner left
//
MyKFPawn . DrawDebugLine ( arcFarLeftLineEnd , arcMidLeftLineEnd , ZoneColor . R , ZoneColor . G , ZoneColor . B ) ;
MyKFPawn . DrawDebugLine ( arcLeftInnerLineEnd , arcMidLeftLineEnd , ZoneColor . R , ZoneColor . G , ZoneColor . B ) ;
}
function DrawAttackRangeText ( byte Index , float StartingAngle , float RotationInterval , float MedianMeleeRange , string Text , Color TextColor )
{
local Vector TextLocation ;
local Rotator DrawDirection ;
local int RotationOffset ;
RotationOffset = RotationInterval * Index ;
StartingAngle = MyKFPawn . Rotation . Yaw - StartingAngle + RotationOffset ;
DrawDirection = MakeRotator ( 0 , StartingAngle , 0 ) ;
TextLocation = MyKFPawn . Location + Vector ( DrawDirection ) * MedianMeleeRange ;
TextLocation . Z -= MyKFPawn . CylinderComponent . CollisionHeight ;
// Draw the textfield in world
GetALocalPlayerController ( ) . AddDebugText ( Text , WorldInfo , 0.02 * WorldInfo . TimeDilation , TextLocation , TextLocation , TextColor , true , true ) ;
}
function DrawMaxHitRange ( Color LineColor )
{
local Rotator ZedRotation ;
local Vector LineStart , LineEnd ;
// Draw the MeleeHitRange also
ZedRotation = MakeRotator ( 0 , MyKFPawn . Rotation . Yaw , 0 ) ;
LineStart = MyKFPawn . Location ;
LineStart . Z -= MyKFPawn . CylinderComponent . CollisionHeight ;
LineEnd = LineStart + Vector ( ZedRotation ) * MyKFPawn . MeleeAttackHelper . GetMeleeRange ( ) ;
MyKFPawn . DrawDebugLine ( LineStart , LineEnd , LineColor . R , LineColor . G , LineColor . B ) ;
}
/** Shows this KFAIController's AICommand stack on screen (see ShowAIInfo cheat) */
function GetCommandStack ( HUD Hud , out array < string > OverheadTexts , out array < Color > OverheadColors )
{
local int CmdCnt ;
local GameAICommand Cmd ;
local String CommandText ;
local bool bShowCommands , bShowMovement ;
local int i ;
local string T ;
local AICommand _Wander WanderCommand ;
if ( HUD . ShouldDisplayDebug ( 'AllVerbose' ) )
{
bShowCommands = true ;
bShowMovement = true ;
}
if ( HUD . ShouldDisplayDebug ( 'All' ) )
{
bShowCommands = true ;
bShowMovement = true ;
}
if ( HUD . ShouldDisplayDebug ( 'AICommands' ) )
{
bShowCommands = true ;
}
if ( HUD . ShouldDisplayDebug ( 'AIMovement' ) || HUD . ShouldDisplayDebug ( 'AIPathing' ) )
{
bShowMovement = true ;
}
if ( MyKFPawn == none )
{
return ;
}
if ( bShowMovement || HUD . ShouldDisplayDebug ( 'AI' ) || HUD . ShouldDisplayDebug ( 'AIMovementVerbose' ) )
{
CommandText = "---------- AI General ----------\n" ;
if ( Enemy != None )
{
CommandText = CommandText @ "STATE: " $GetStateName ( ) $ " Enemy " $Enemy . GetHumanReadableName ( ) $ "\n" ;
}
else
{
CommandText = CommandText @ "STATE: " $GetStateName ( ) $ " NO Enemy " $ "\n" ;
}
if ( HUD . ShouldDisplayDebug ( 'AI' ) || HUD . ShouldDisplayDebug ( 'AIMovementVerbose' ) )
{
CommandText = CommandText @ "Anchor: " $Pawn . Anchor$ " Serpentine Dist: " $Pawn . SerpentineDist$ " Time: " $Pawn . SerpentineTime$ "\n" ;
if ( ( Pawn != None ) && ( MoveTarget != None ) && Pawn . ReachedDestination ( MoveTarget ) )
{
CommandText = CommandText @ "Skill: " $Skill$ " NAVIGATION MoveTarget: " $GetItemName ( String ( MoveTarget ) ) $ "(REACHED) MoveTimer: " $MoveTimer$ "\n" ;
}
else
{
CommandText = CommandText @ "Skill: " $Skill$ " NAVIGATION MoveTarget: " $GetItemName ( String ( MoveTarget ) ) $ " MoveTimer: " $MoveTimer$ "\n" ;
}
CommandText = CommandText @ "Destination: " $GetDestinationPosition ( ) $ " Focus: " $GetItemName ( string ( Focus ) ) $ " Preparing Move: " $bPreparingMove$ "\n" ;
CommandText = CommandText @ "RouteGoal: " $GetItemName ( string ( RouteGoal ) ) $ " RouteDist: " $RouteDist$ "\n" ;
for ( i = 0 ; i < RouteCache . Length ; i ++ )
{
if ( RouteCache [ i ] == None )
{
if ( i > 5 )
T = T$ "--" $GetItemName ( string ( RouteCache [ i - 1 ] ) ) ;
break ;
}
else if ( i < 5 )
T = T$GetItemName ( string ( RouteCache [ i ] ) ) $ "-" ;
}
CommandText = CommandText @ "RouteCache: " $T$ "\n" ;
}
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 0 , 200 , 50 ) ;
CommandText = "" ;
}
if ( bShowMovement )
{
CommandText = "---------- AI MOVEMENT ----------\n" ;
if ( IntermediateMoveGoal != none )
{
CommandText = CommandText @ "IntermediateMoveGoal:" $IntermediateMoveGoal$ "\n" ;
}
if ( MoveTarget != none )
{
CommandText = CommandText @ "MoveTarget:" $MoveTarget$ "\n" ;
}
WanderCommand = FindCommandOfClass ( class 'AICommand_Wander' ) ;
if ( WanderCommand != none )
{
CommandText = CommandText @ "AIC Wander Timer:" $GetRemainingTimeForTimer ( 'Timer_WanderDurationExpired' , WanderCommand ) $ "\n" ;
}
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
}
if ( bShowCommands )
{
CommandText = "---------- AI Commands ----------\n" ;
if ( AIActionStatus != "" )
{
CommandText = CommandText @ "Status:" $AIActionStatus$ "\n" ;
}
}
if ( bShowMovement || bShowCommands )
{
if ( MyKFPawn . SpecialMove != SM _None )
{
CommandText = CommandText @ "SpecialMove:" $MyKFPawn . SpecialMove$ "\n" ;
}
else
{
CommandText = CommandText @ "SpecialMove: NONE \n" ;
}
}
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 0 , 200 , 50 ) ;
if ( bShowCommands )
{
CommandText = "-- AICommand History --" ;
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 0 , 255 , 0 ) ;
foreach AllCommands ( class 'GameAICommand' , Cmd )
{
if ( Cmd . ChildCommand == None )
{
CommandText = String ( Cmd . Name ) @ ":" @ Cmd . GetStateName ( ) ;
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 0 , 255 , 0 ) ;
}
else
{
CommandText = String ( Cmd . Name ) @ ":" @ Cmd . GetStateName ( ) ;
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 255 , 128 , 0 ) ;
}
CmdCnt ++ ;
}
if ( CmdCnt == 0 )
{
CommandText = "No Active Command" ;
OverheadTexts [ OverheadTexts . Length ] = CommandText ;
OverheadColors [ OverheadTexts . Length - 1 ] = MakeColor ( 0 , 200 , 50 ) ;
}
}
}
DefaultProperties
{
// ---------------------------------------------
// Combat
MeleeCommandClass = class 'KFGame.AICommand_Base_Zed'
bAllowCombatTransitions = true
bIdleMoveToNearestEnemy = true
MinTimeBetweenEnemyChanges = 5 f
DoorMeleeDistance = 200. f
MaxMeleeHeightAngle = 0.64 f
MaxGetStrikeTime = 0.25 f
MinDistanceToAggroZed = 1500
PendingAnimStrikeIndex = 255
bCanDoHeavyBump = false
ZedBumpEffectThreshold = 270
ZedBumpObliterationEffectChance = 0.4
AIRemainingTeleportThreshold = 12.0
FrustrationThreshold = 5. f
FrustrationDelay = 2.5 f
LastFrustrationCheckTime = 0. f
LowIntensityAttackCooldown = 2.0
//bUseOldAttackDecisions=true
2022-09-01 15:58:51 +00:00
CanForceEnemy = true
ForcedEnemy = none
LastForcedEnemy = none
ForcedEnemyLastTime = 0. f
DamageRatioToChangeForcedEnemy = 0.5 f
TimeCanRestartForcedEnemy = 10. f
TimeCannotChangeFromForcedEnemy = 10. f
2020-12-13 15:01:13 +00:00
// ---------------------------------------------
// AI / Navigation
MaxBlockedPathDuration = 30. f
bCanDoSpecial = true
InUseNodeCostMultiplier = 3. f
bProbeNotifyOnAddToRouteCache = false // 7/1/14
DirectPathExtentModifier = 1. f
DirectPathCheckFrequency _Min = 0.1 f
DirectPathCheckFrequency _Max = 0.3 f
bDisablePartialPaths = false
RepeatWalkingTauntTime = 15
EvadeGrenadeChance = 1.0 f
// ---------------------------------------------
// Movement
RotationRate = ( Pitch = 50000 , Yaw = 50000 , Roll = 50000 )
MaxStepAsideDist = 128.0
// Begin Object Class=KFAISteering Name=Steering0
// End Object
// Steering=Steering0
RotationRateMultiplier = 1. f
bIgnoreStepAside = true // 3.1.2014
MinTimeBetweenLOSChecks = 4. f
//bNotifyApex=true
// optimization - simplify LineOfSight/SeePlayer line traces
bSkipExtraLOSChecks = true
bDumpCommandHistoryOnExit = false
bUseDesiredRotationForMelee = true
BumpThreshold = 10 f
BumpDecayRate = 0.5 f
BumpGrowthRate = 10.0 f
bNotifyFallingHitWall = true
// No navigation handle right now since we are doing path nodes
NavigationHandleClass = none //class'KFNavigationHandle'
CurrentMovementPhase = MOVEMENT _PHASE _TYPE _NONE ;
//bGoToEnemiesOutsideNavmesh=true
//MovementPluginClass="KFAIPluginMovement_Recast"
//LeapPluginClass="KFAIPluginLeap"
//StuckFixPluginClass="KFAIPluginStuckFix"
KfWallWalkingPlugIn = None
//============================================
// Movement/Path Paramaters
//============================================
bAlwaysAcceptPartialPaths = true
bShouldUsePathLanes = true
bShouldOffsetCorners = false
//bDebugDisableSprinting=false
DistanceToCheckForClearPathOnNavMeshLocWhenOffNavMesh = 512
DistanceToCheckForNonExploitedOnNavMeshLocWhenOffNavMesh = 2056
DistanceToCheckForNonExploitedOnNavMeshLocWhenOnDeadEndOfNavMesh = 512
DistanceToCheckForClearPathOnNavMeshLocWhenEnemyIsOffNavMesh = 200
DistanceToCheckForNonExploitedOnNavMeshLocWhenEnemyIsOffNavMesh = 500
GeneralGoalDistanceForMovingToDoor = 256
RecastEnemyRepathDistance = 512
DropEdgeLeapVelocity = ( X = 400 , Y = 0 , Z = 400 )
MaxRangeToDropEdgeAllowedToLeadFrom = 100
DistanceDownRangeToFocusForDropEdgeLeap = 1000
//============================================
// General AI System Paramaters
//============================================
DefaultMaxTimeAllowedToStayStuckBeforeSuicide = 5.0
NoNavMeshPathMaxTimeAllowedToStayStuckBeforeSuicide = 1.0
SameIntermediatePointToManyTimesMaxTimeAllowedToStayStuckBeforeSuicide = 5.0
TargetOffNavMeshAndCanNotFindLocaitonNearThemICanMoveTooMaxTimeAllowedToStayStuckBeforeSuicide = 5.0
SameIntermediatePointToManyTimesDurationAfterStartedMovingAgaintToStopStuckCheck = 10.0
DefaultMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide = 5.0
NoNavMeshPathMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide = 1.0
SameIntermediatePointToManyTimesMinDistaceToHaveToMoveToBeConcideredStuckBeforeSuicide = 200.0
GoalDistanceWhenMovingToLocationForMeleeStrikeWhenEnemyIsOffNavMesh = 0
bSpecialBumpHandling = true
bUseNavMesh = false
bUsePluginsForMovement = false
//
/////
//
MovementPhaseTypeUnknownIcon = Texture2D 'AI_Debug_Helpers.UnknownMovementPhase'
MovementPhaseTypePathNodeNormalMoveToIcon = Texture2D 'AI_Debug_Helpers.MapIcon_PathNodeMoveTo'
MovementPhaseTypePathNodeMoveFailedIcon = Texture2D 'AI_Debug_Helpers.MapIcon_FailedMove'
MovementPhaseTypePathNodeMoveToPointIcon = Texture2D 'AI_Debug_Helpers.MapIcon_MoveToAPoint'
MovementPhaseTypePathNodeRotateToFocusIcon = Texture2D 'AI_Debug_Helpers.MapIcon_RotateToFocus'
MovementPhaseTypePathNodeDelayMoveIcon = Texture2D 'AI_Debug_Helpers.MapIcon_AIDelay'
MovementPhaseTypeNavMeshNormalMoveToIcon = Texture2D 'AI_Debug_Helpers.NavMeshMoveTo'
MovementPhaseTypeFinalDestMoveTowardsIcon = Texture2D 'AI_Debug_Helpers.FinalDestMoveTowards'
MovementPhaseTypeEMT _LOS _MoveTowardsIcon = Texture2D 'AI_Debug_Helpers.LOS_MoveTowards'
MovementPhaseTypeMovingToNavMeshUsingPathNodesIcon = Texture2D 'AI_Debug_Helpers.WallToNavMesh'
MovementPhaseTypeFALLBACK _REFUSED _TO _BE _EXPLOTIED _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon = Texture2D 'AI_Debug_Helpers.FindNearbyMeshPointRefusedToBeExplotied'
MovementPhaseTypeFALLBACK _FIND _NEARBY _MESH _POINT _MOVE _TO _DIRECT _NON _PATH _POSIcon = Texture2D 'AI_Debug_Helpers.FindNearbyMeshPoint'
TypeOfMovementStuckOnUnknownWhyIcon = Texture2D 'AI_Debug_Helpers.StuckUnknown'
TypeOfMovementStuckOnMOVE _FAILURE _TYPE _NO _NAV _MESH _PATHIcon = Texture2D 'AI_Debug_Helpers.NoNavStuck'
TypeOfMovementStuckOnMOVE _FAILURE _TYPE _SAME _INTERMEDIATE _POINT _TOO _MANY _TIMESIcon = Texture2D 'AI_Debug_Helpers.TriskelionStuck'
TypeOfMovementStuckOnMOVE _FAILURE _TYPE _TARGET _OFF _NAV _MESH _AND _CAN _NOT _FIND _LOCAITON _NEAR _THEM _I _CAN _MOVE _TOIcon = Texture2D 'AI_Debug_Helpers.EnemyOffNavMeshAndCanDoNothing'
TypeOfMovementStuckOnLookingForBetterIntermediateLoc = Texture2D 'AI_Debug_Helpers.LookingForBetterInter'
TypeOfMovementStuckOnMoveToBetterIntermediate = Texture2D 'AI_Debug_Helpers.MoveToBetterIntermediate'
Role = ROLE _Authority
// Settings this to true so the base KFAIController gets a PRI, etc
bIsPlayer = true
// ---------------------------------------------
// Behaviors
BaseShapeOfProjectileForCalc = ( X = 1 , Y = 1 , Z = 1 )
AggroPlayerHealthPercentage = 0.1
AggroPlayerResetTime = 10
AggroZedResetTime = 30
AggroZedHealthPercentage = 0.15
AggroEnemySwitchWaitTime = 5. f
// Don't always assign an enemy in the BaseAIController native code. Let the script code handle it!!!
bAlwaysAssignEnemy = false
StuckCheckInterval = 0.5
StuckVelocityThreshholdSquared = 625 //(25 UUS)
StuckPossiblityThreshhold = 5.0
XYMoveStuckThresholdSquared = 625 //(25 UUS)
StuckCheckEnemyDistThreshholdSquared = 1000000 // 10 meters
NavigationBumpTeamCollisionThreshholdSquared = 1000000 // 10 meters
bCanTeleportCloser = true
TeleportCheckInterval = 1.0
TeleportCooldown = 7.0
HiddenRelocateTeleportThreshold = 5.0
PostSpawnRelocateTeleportCooldown = 10
StrikeRangePercentage = 0.85
}