1
0
KF2-Dev-Scripts/KFGame/Classes/KFAIDirector.uc
2020-12-13 18:01:13 +03:00

425 lines
12 KiB
Ucode

//=============================================================================
// KFAIDirector
//=============================================================================
// Oversees AI responses to game events
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFAIDirector extends Object within KFGameInfo
config(Game)
native(AI);
/** List of current living Zeds, can be used by Zeds to access other Zeds without iterating through
all pawns, etc. */
var array <KFAIController> AIList;
/** Forces clots to initially sprint to perform their first grab - code in KFAIController_ZedClot must
be uncommented out (ShouldSprint()). */
var config bool bAISprintToGrabVictim;
/** Global status of NPC debug text rendering - can be used to make sure all newly spawned NPCs begin showing their
debug text as soon as they possess their pawns */
var config bool bShowAINames;
/** Forces newly possessing KFAIControllers into debug mode when true */
var config bool bDebugAllAI;
/** When true, will receive notification when a player is grabbed, allowing nearby Zeds to then target the grabbed player */
var bool bUseGrabAlerts;
/** Max distance from grabbed player at which to alert Zeds */
var float GrabAlertMaxZedDistance;
/** Turns on forced frustation mode */
var bool bForceFrustration;
/** List of all active bosses. */
var array<KFPawn_Monster> ActiveBossList;
/** A bi-direction graph representation of the connections between doors for long range "Macro" pathing decisions*/
//var KFDoorGraph DoorGraph;
var vector CurrentLocationSortingDoorsFor;
////
///
// Ai Debug Helpers
///
////
var private Font AiDebugScreenLargeFont;
var config string AiDebugScreenLargeFontName;
var private Texture2D DebugIsSprintingIcon;
var config string DebugIsSprintingIconName;
var private Texture2D DebugIsWalkingIcon;
var config string DebugIsWalkingIconName;
var config bool bLoadDebugPackageResourcesAtStart;
var bool bShowVisualStuckZedDebugInfo;
var bool bShowMovePointsDebugInfo;
var bool bShowHighDetailCombatMovementDebugInfo;
var bool bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState;
var bool bShowLeapDownDebugArtifacts;
var bool bShowDoorNavigationDebugArtifacts;
var bool bShowDestructibleNavigationDebugArtifacts;
/** Struct that info about each squad member */
struct native ActiveAIInfo
{
var Controller Member;
};
/** Array of all squad members */
var array<ActiveAIInfo> ActiveZeds;
native function RebuildAIList();
native function LoadAiDebugResources();
/**
* Initialize
*
*/
function Initialize()
{
if( bLoadDebugPackageResourcesAtStart )
{
LoadAiDebugResources();
}
//SetupDoorGraph();
bShowVisualStuckZedDebugInfo = class'KFAiController'.default.bConfigShowVisualStuckZedDebugInfo;
bShowMovePointsDebugInfo = class'KFAiController'.default.bConfigShowMovePointsDebugInfo;
bShowHighDetailCombatMovementDebugInfo = class'KFAiController'.default.bConfigShowHighDetailCombatMovementDebugInfo;
bShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState = class'KFAiController'.default.bConfigShowCollisionRadiusForReducedZedOnZedPinchPointCollisionState;
bShowLeapDownDebugArtifacts = class'KFAiController'.default.bConfigShowLeapDownDebugArtifacts;
bShowDoorNavigationDebugArtifacts = class'KFAiController'.default.bConfigShowDoorNavigationDebugArtifacts;
bShowDestructibleNavigationDebugArtifacts = class'KFAiController'.default.bConfigShowDestructibleNavigationDebugArtifacts;
}
/** Toggles debug-mode on/off for all existing Zeds */
function SetAIDebug( bool bOn )
{
local int i;
bDebugAllAI = bOn;
for( i = 0; i < AIList.Length; i++ )
{
if( AIList[i].bForceDebugCommand )
{
AIList[i].bForceDebugCommand = bOn;
}
AIList[i].Pawn.ZeroMovementVariables();
AIList[i].AbortMovementCommands();
AIList[i].AbortMovementPlugIns();
if( !bOn && AIList[i].bHasDebugCommand )
{
AIList[i].AbortCommand( None, class'AICommand_Debug' );
AIList[i].GotoState( 'Action_Idle', 'Begin' );
}
if( bOn )
{
AIList[i].BeginDebugCommand();
}
}
}
/**
* Returns the engine's default large font
*/
final function Font GetAiDebugScreenLargeFont()
{
if( AiDebugScreenLargeFont == none )
{
LoadAiDebugResources();
}
return AiDebugScreenLargeFont;
}
/**
* GetDebugIsSprintingIcon
*/
final function Texture2D GetDebugIsSprintingIcon()
{
if( DebugIsSprintingIcon == none )
{
LoadAiDebugResources();
}
return DebugIsSprintingIcon;
}
/**
* GetDebugIsWalkingIcon
*/
final function Texture2D GetDebugIsWalkingIcon()
{
if( DebugIsWalkingIcon == none )
{
LoadAiDebugResources();
}
return DebugIsWalkingIcon;
}
/** Notification that the calling KFAIController has just possessed a new pawn */
function NotifyNewPossess( KFAIController KFAIC )
{
`AILog_Ext( GetFuncName()$" registering new Possession by "$KFAIC, 'AIDirector', KFAIC );
RegisterAIMember( KFAIC );
KFAIC.bForceDebugCommand = bDebugAllAI;
}
final function RegisterAIMember( Controller NewMember )
{
local int Idx;
local KFAIController KFAIC;
if (NewMember != None)
{
if (ActiveZeds.Find('Member', NewMember) != INDEX_NONE)
{
}
else
{
// make sure they're not already a member
UnRegisterAIMember( NewMember );
Idx = ActiveZeds.Length;
ActiveZeds.Length = Idx + 1;
ActiveZeds[Idx].Member = NewMember;
KFAIC = KFAIController(NewMember);
// if it's an AI
if( KFAIC != None )
{
`AILog_Ext( GetFuncName()$" Adding "$KFAIC$" to AIList", 'AIDirector', KFAIC );
AIList.AddItem(KFAIC);
}
if (NewMember.Pawn != none && KFPawn_Monster(NewMember.Pawn) != none && KFPawn_Monster(NewMember.Pawn).IsABoss())
{
ActiveBossList.AddItem(KFPawn_Monster(NewMember.Pawn));
}
}
}
}
/** Remove squad member from this squad and clean up */
final function UnRegisterAIMember( Controller OldMember )
{
local KFAIController KFAIC;
//local GearPRI PRI;
local int Idx;
if (OldMember != None)
{
Idx = ActiveZeds.Find( 'Member', OldMember );
if( Idx >= 0 )
{
KFAIC = KFAIController(OldMember);
ActiveZeds.Remove( Idx, 1 );
// if it's an AI
if (KFAIC != None)
{
`AILog_Ext( GetFuncName()$" Removing "$KFAIC$" from AIList", 'AIDirector', KFAIC );
AIList.RemoveItem(KFAIC);
}
if (OldMember.Pawn != none && KFPawn_Monster(OldMember.Pawn) != none && KFPawn_Monster(OldMember.Pawn).IsABoss())
{
ActiveBossList.RemoveItem(KFPawn_Monster(OldMember.Pawn));
}
}
}
}
/** Can be used to let the KFAIDirector find a valid enemy for a KFAIController, optionally not taking
the KFAIC's current enemy into consideration */
function Actor FindEnemyFor( KFAIController RequestingAI, optional bool bSkipCurrentEnemy=true )
{
local Pawn P;
local float DistToPlayer, DistToEnemy;
if( RequestingAI.Pawn == none || !RequestingAI.Pawn.IsAliveAndWell() || RequestingAI.DoorEnemy != none )
{
return none;
}
if( RequestingAI.MyKFPawn.IsDoingSpecialMove(SM_GrappleAttack) )
{
return RequestingAI.Enemy;
}
if( RequestingAI.Enemy == none || !RequestingAI.Enemy.IsAliveAndWell() )
{
return RequestingAI.GetClosestEnemy();
}
for( P = WorldInfo.PawnList; P != none; P = P.NextPawn )
{
if( !RequestingAI.Pawn.IsSameTeam(P) && P.IsAliveAndWell() && P != RequestingAI.Enemy )
{
DistToPlayer = VSize(P.Location - RequestingAI.Pawn.Location);
DistToEnemy = VSize(RequestingAI.Enemy.Location - RequestingAI.Pawn.Location);
if( bSkipCurrentEnemy )
{
if( RequestingAI.ActorReachable(P) )
{
return P;
}
}
else if( (DistToPlayer < DistToEnemy && FRand() < 0.9f) || (KFPawn(P).IsDoingSpecialMove(SM_GrappleVictim) && (DistToPlayer < 1200.f) && RequestingAI.CanSee(P) && FRand() < 0.85f)
|| (RequestingAI.Pawn.Health < (0.33f * RequestingAI.Pawn.Default.Health) && FRand() < 0.65f) )
{
return P;
}
}
}
return none;
}
// Make nearby zeds more aggressive to the newly grabbed player
// TODO: Maybe only alert zeds with LOS to the victim if they seem too omniscient - for now it's just checking distance
function NotifyPawnGrabbed( KFPawn Victim, KFPawn Attacker )
{
local KFPawn KFP;
`AILog_Ext( GetFuncName()$" NotifyPawnGrabbed - victim, "$Victim$" attacker "$Attacker, 'AIDirector', Attacker.MyKFAIC );
foreach WorldInfo.AllPawns( class'KFPawn', KFP )
{
if( (Attacker != none && KFP == Attacker) || KFP.IsSameTeam(Victim) || KFP.MyKFAIC == none || !KFP.IsAliveAndWell()
|| Victim == none || !Victim.IsAliveAndWell() )
{
continue;
}
if( VSizeSq( KFP.Location - Victim.Location ) < GrabAlertMaxZedDistance * GrabAlertMaxZedDistance )
{
if( KFP.MyKFAIC.Enemy == none || (KFP.MyKFAIC.Enemy != none && KFP.MyKFAIC.Enemy != Victim) )
{
//TODO: Make this configurable
if( KFP.MyKFAIC.Enemy == none || FRand() < 0.85f )
{
KFP.MyKFAIC.SetEnemy( Victim );
}
}
}
if( bAISprintToGrabVictim && KFP.MyKFAIC.Enemy == Victim && FRand() < 0.5f )
{
KFP.SetSprinting(true);
}
}
}
/**
* FindDoorsWithInRange
*
*/
function array<KFDoorActor> FindDoorsWithInRange( float RangeToBeLessThan, vector Loc2TestFrom )
{
local KFDoorActor curDoorActor;
local KFGameReplicationInfo kfGRI;
local array<KFDoorActor> doorsInRange;
local int doorIndex;
local vector displacement2Door;
local float distance2Door;
kfGRI = KFGameReplicationInfo(WorldInfo.GRI);
for( doorIndex = 0; doorIndex < kfGRI.DoorList.Length; doorIndex++ )
{
curDoorActor = kfGRI.DoorList[doorIndex];
displacement2Door = curDoorActor.Location - Loc2TestFrom;
distance2Door = VSize( displacement2Door );
if( distance2Door <= RangeToBeLessThan )
{
doorsInRange.AddItem(curDoorActor);
}
}
return doorsInRange;
}
delegate int ClosestDoorSort( KFDoorActor A, KFDoorActor B)
{
local float distSqToDoorA;
local float distSqToDoorB;
distSqToDoorA = VSizeSq( CurrentLocationSortingDoorsFor - A.Location );
distSqToDoorB = VSizeSq( CurrentLocationSortingDoorsFor - B.Location );
return distSqToDoorA > distSqToDoorB ? -1 : 0;
}
/**
* FindDoorsWithInRange
*
*/
function array<KFDoorActor> FindClosedDoorsWithInRange( float RangeToBeLessThan, vector Loc2TestFrom )
{
local KFDoorActor curDoorActor;
//local KFGameReplicationInfo kfGRI;
local array<KFDoorActor> doorsInRange;
local array<KFDoorActor> closedDoorsInRange;
local int doorIndex;
//local vector displacement2Door;
//local float distance2Door;
// kfGRI = KFGameReplicationInfo(WorldInfo.GRI);
doorsInRange = FindDoorsWithInRange( RangeToBeLessThan, Loc2TestFrom );
for( doorIndex = 0; doorIndex < doorsInRange.Length; doorIndex++ )
{
curDoorActor = doorsInRange[doorIndex];
if( !curDoorActor.IsCompletelyOpen() )
{
closedDoorsInRange.AddItem( curDoorActor );
}
}
CurrentLocationSortingDoorsFor = Loc2TestFrom;
closedDoorsInRange.Sort( ClosestDoorSort );
return closedDoorsInRange;
}
function KFPawn_Monster GetActiveBoss()
{
if (ActiveBossList.length > 0)
{
return ActiveBossList[0];
}
return none;
}
DefaultProperties
{
GrabAlertMaxZedDistance=2700.f
bForceFrustration = false;
}