1
0
This commit is contained in:
2020-12-13 18:01:13 +03:00
commit dd42f84140
3764 changed files with 596895 additions and 0 deletions

View File

@ -0,0 +1,244 @@
//=============================================================================
// AIDebugTool
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AIDebugTool extends Object
dependson(BaseAIController)
config(AI)
native(Debug);
var transient float GameFrameTime;
var int BTPersonalLogSize;
var array<string> BTPersonalLog;
var BaseAIController DebugTarget;
var int EntryToDisplay;
var transient init array<name> Filters;
var array<name> ColorCategories;
var array<Actor> UnreachableActors;
enum ELogDrawingMode
{
AIDH_DrawAll,
AIDH_EntryDetails,
AIDH_BlackBoard,
AIDH_Preconditions,
AIDH_SquadEnemy,
AIDH_Animation,
AIDH_Weapon
};
var int DrawingFlags;
struct String_Mirror
{
var string Value;
};
struct native AIDebugLogLine
{
var native String_Mirror Line{FString};
var native Name LineCategory;
//@todo Add additional data, like position, hit info, etc
structcpptext
{
FAIDebugLogLine(const TCHAR* PtrLine, FName inCategory = NAME_None)
{
Line = PtrLine;
LineCategory = inCategory;
}
FAIDebugLogLine(const FString& LineIn, FName inCategory = NAME_None)
{
Line = LineIn;
LineCategory = inCategory;
}
}
};
struct native AIDebugLogEntry
{
var float TimeStamp;
var vector WorldPosition;
var vector EnemyPosition;
var vector Facing;
var native String_Mirror BTStatus{FString};
var native array<String_Mirror> BlackBoardEntries{FString};
var native array<String_Mirror> BehaviorTrace{FString};
var native array<String_Mirror> Preconditions{FString};
var native array<String_Mirror> CommandStack{FString};
var native array<String_Mirror> SquadEnemyList{FString};
var native array<String_Mirror> LocalEnemyList{FString};
var native array<String_Mirror> InnerState{FString};
var native array<String_Mirror> AnimationProxy{FString};
var native array<vector> Route;
var native init array<SpaceLineInfo> DebugLines;
var native array<String_Mirror> WeaponInfo{FString};
var array<AIDebugLogLine> Lines;
structcpptext
{
FAIDebugLogEntry()
{
appMemzero(this, sizeof(FAIDebugLogEntry));
}
}
};
struct native AIDebugActorsLog
{
var int Tail;
var array<AIDebugLogEntry> Entries;
var int NotEmptyElements;
/** this variable is used for pooling. DO NOT CHANGE! */
var private const int nIndex;
structcpptext
{
enum { LogSize = 500 };
FAIDebugActorsLog(UBOOL bInit = TRUE)
{
appMemzero(this, sizeof(FAIDebugLogEntry));
if(bInit)
{
Init();
}
}
FORCEINLINE void Init()
{
Entries.Reserve(LogSize);
Entries.AddZeroed(LogSize);
}
FORCEINLINE void Reset()
{
Entries.Empty(LogSize);
Entries.AddZeroed(LogSize);
NotEmptyElements = 0;
}
/** Pooling interface Get*/
FORCEINLINE INT GetIndex() const
{
return nIndex;
}
/** Pooling interface Set*/
FORCEINLINE void SetIndex(INT index)
{
nIndex = index;
}
void SaveToFile(FArchive* File);
void LoadFile(FArchive* File, UBOOL bLoadOldLogs = FALSE);
}
};
struct native ETQDebugEntry
{
var float TimeStamp;
var native init array<String_Mirror> ExecutedQueries{FString};
};
var transient ETQDebugEntry CurrentETQLog;
var init array<ETQDebugEntry> ETQLogHistory;
var bool bLoadOldLogs;
var transient Actor LoggingContext;
var native const Map_Mirror Logs{TMap<const AActor*, FAIDebugActorsLog*>};
/**
* Draws stored log for given Actor. If Actor == None than contents of loaded
* ailogex file are displayed
*/
final native function DrawLog(Canvas Canvas, Actor Actor);
final native function bool LoadLogFile(string FileName, optional bool bAppendMapName = true);
final native function FlushLogs(optional string DirName, optional BaseAIController AI);
final native function SetDebugTarget(BaseAIController NewDebugTarget);
final native noexport function Log(Actor Actor, string LogText, optional Name LogCategory);
final native noexport function LogSpaceLine(Actor Actor, vector Start, vector End, EDebugLineType Type, optional string Comment, optional Name Category);
final native function SetContext(Actor Actor);
/**
* To get previous entries send a negative number as a parameter
*/
final native function DisplayNextEntry(optional int Count = 1, optional int Direction = 1);
final native function ToggleLogDrawingMode(ELogDrawingMode Mode);
final native function SetHistoryFilter(Name Filter, bool bVal);
final native function ClearHistoryFilter();
final native function ColorHistory(Name Filter);
final native function ClearHistoryColor();
final native function LogUnreachableActor(Actor inActor);
final native function FlushUnreachableActors();
final native function AddETQLog(string QueryName, string TestName, Actor Querier);
final native function FlushETQHistory();
final native function AddBTLogEntry(string Entry);
final native function Vector GetActorEntryLocation(optional Actor inActor, optional int Index=-1);
final native function DumpNativeCallStack(Actor Actor, optional Name LogCategory = 'CallStack');
cpptext
{
void Init();
void Tick(FLOAT DeltaTime);
void CleanUp(UBOOL bShutDown);
void FlushAILog(class AActor* Actor, UBOOL bRemoveLog = FALSE);
enum EAILogElement
{
AILE_PrevChannel,
AILE_Command,
AILE_Timestamp,
AILE_BlackBoard,
AILE_BehaviorStack,
AILE_Preconditions,
AILE_CommandStack,
AILE_SquadEnemies,
AILE_LocalEnemies,
AILE_Route,
AILE_InnerState,
AILE_WeaponInfo
};
void Log(const class AActor* Actor,const FString& LogText,FName LogCategory=NAME_None);
void LogSpaceLine(const class AActor* Actor,const FVector& Start,const FVector& End,BYTE Type,const FString& Comment=TEXT(""),FName Category=NAME_None);
void ContextLog(const FString& LogText,FName LogCategory=NAME_None);
void ContextLogSpaceLine(const FVector& Start,const FVector& End,BYTE Type,const FString& Comment=TEXT(""),FName Category=NAME_None);
protected:
void ChannelEntryHeader(const class AActor* Actor, FAIDebugLogEntry* ptrEntry);
UBOOL HasAnyLines(struct FAIDebugActorsLog* Log, INT EntryIdx, TArray<FName>& inFilters) const;
FAIDebugLogEntry* GetLogEntry(const class AActor* Actor);
private:
static TDataBank<FAIDebugActorsLog> sm_Logs;
};
defaultproperties
{
BTPersonalLogSize=23
bLoadOldLogs=false
}

View File

@ -0,0 +1,90 @@
//=============================================================================
// AIPlugin
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AIPlugin extends PluginBase within BaseAIController
native(Plugin)
abstract;
var const protected{protected} BaseAIController MyBaseAIController;
var const protected{private} bool bCustomSetupController;
cpptext
{
virtual UBOOL SetupController(class ABaseAIController* Owner);
// Called if bCustomSetupController == true
virtual UBOOL SetupControllerImpl(class ABaseAIController* Owner) PURE_VIRTUAL(UPluginBase::SetupConrollerImpl, return TRUE;);
UBOOL HasCustomControllerSetup() const
{
return bCustomSetupController;
}
FORCEINLINE const ABaseAIController* GetController() const
{
return MyBaseAIController;
}
FORCEINLINE ABaseAIController* GetController()
{
return MyBaseAIController;
}
};
event ScriptSetUp()
{
}
/**
* ===========
* DEBUG STATES
* ===========
*/
state DEBUGSTATE
{
function BeginState( Name PreviousStateName )
{
//debug
`AILog( "BEGINSTATE"@PreviousStateName, 'State' );
}
function EndState( Name NextStateName )
{
//debug
`AILog( "ENDSTATE"@NextStateName, 'State' );
}
function PushedState()
{
//debug
`AILog( "PUSHED", 'State' );
}
function PoppedState()
{
//debug
`AILog( "POPPED", 'State' );
}
function ContinuedState()
{
//debug
`AILog( "CONTINUED", 'State' );
}
function PausedState()
{
//debug
`AILog( "PAUSED", 'State' );
}
}
defaultproperties
{
bCustomSetupController=false
}

View File

@ -0,0 +1,413 @@
//=============================================================================
// AIPluginLeap
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AIPluginLeap extends AITickablePlugin within BaseAIController
native(Plugin)
dependson(NavigationPath)
abstract
config(AI);
//var KFJumpDownNavPoint TheJumpDownNavPointPathObject;
//var Actor BaseForLeaping;
//var KFJumpDownNavPoint TheJumpDownNavPointPathObject;
//var int LandSpotIndex;
var Actor BaseForLanding;
//var KFJumpDownLandNavPoint LandNavPoint;
/** Start and End for the leap */
var Vector LeapDirection;
var transient Vector LocationAtStartOfLeap;
var transient Vector ClosestLocOnEdge;
var transient Vector ExpectedLandingLoc;
var const public{protected} EActionPriority ActionPriority;
var const transient bool bObserverNotified;
var const transient bool bDontNotifyObserver;
var transient bool bDontRestartByKismet;
var float ConfigFactorToIncreaseCalcedLeapVolBy;
var float ConfigFactorToIncreasePlottedLeapVolBy;
var float ConfigFactorToReduceLeapProjectionSizeBy;
var float ConfigDistanceOutFromDropEdgeToCalcDropLoc;
//var transient float DropHeight;
var float PercentageOfLeapImmuneFromCollision;
var float ConfigPercentageOfLeapImmuneFromCollision;
var bool bCollisionOffPhase;
var float ConfigDesiredLeapSpeed;
var float ConfigMinSpeedAllowed;
var float ConfigMaxSpeedAllowed;
var float ConfigBaseLeapZ;
var float ConfigDesiredZPct;
var bool bConfigOnlyTraceUp;
struct native LeapRequest
{
//var native MoveParameters MoveParams;
var Actor TheJumpDownNavPointPathObject;
var int LandSpotIndex;
var float EdgesDropHeight;
var EActionPriority Priority;
var const object Observer;
var int QueryID;
structcpptext
{
FLeapRequest()
{
appMemzero(this, sizeof(FLeapRequest));
Reset();
}
FLeapRequest(FLeapRequest* pOther)
{
if(pOther != NULL)
{
appMemCopy(*this, *pOther);
}
else
{
appMemzero(this, sizeof(FLeapRequest));
Reset();
}
}
FString GetDescription() const;
void Reset()
{
Priority = AP_Invalid;
TheJumpDownNavPointPathObject = NULL;
Observer = NULL;
LandSpotIndex = -1;
}
}
};
var LeapRequest MyLeapRequest;
var bool bWantTotalMovementControl;
cpptext
{
void ClearMoveInfo();
}
/** Simple constructor that pushes a new instance of the command for the AI */
function bool Leap( const out LeapRequest InLeapRequest ) // AIController AI, Actor InTheJumpDownNavPointPathObject, int InLandSpotIndex, float InDropHeight )
{
//DropHeight = InDropHeight;
//LandSpotIndex = InLandSpotIndex;
//BaseForLeaping = InTheJumpDownNavPointPathObject;
MyLeapRequest = InLeapRequest;
ScriptCommonMovePreparation( AP_Logic );
GotoState( 'Command_SpecialMove' );
return true;
}
/** Build debug string */
event String GetDumpString()
{
return "LeapDirection:"@LeapDirection;
}
//function Pushed()
event bool ScriptCommonMovePreparation(EActionPriority Priority)
{
bCollisionOffPhase = false;
//Super.Pushed();
//GotoState( 'Command_SpecialMove' );
return true;
}
//function Popped()
event ScriptCleanUp()
{
//Super.Popped();
Outer.MyBaseAIPawn.bLeaping = false;
//bReevaluatePath = TRUE;
`AILog( GetFuncName() @ " Turning me on because ScriptCleanUp: " );
TurnMeOn();
}
native function protected Success();
native function protected Failure();
native function bool AbortMove(bool bNewRequest);
state Succeeding `DEBUGSTATE
{
Begin:
`AILog("Leaping - END:"@GetStateName(), 'Leap');
Success();
}
state Failing `DEBUGSTATE
{
Begin:
`AILog("Leaping - END:"@GetStateName(), 'Leap');
Failure();
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Aborting `DEBUGSTATE
{
Begin:
`AILog("Leaping - Aborted", 'Leap');
//AbortMove(false);
StopMovement();
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Idling `DEBUGSTATE
{
Begin:
`AILog("Leaping - Idling so doing nothing", 'Leap');
//StopMovement();
}
function TestForTimeToTurnCollisionBackOn()
{
local float timeDelta;
if( bCollisionOffPhase )
{
if( HasPercentOfZDiffBeenCovered() )
{
`AILog( GetFuncName() @ " Turning me on because HasPercentOfZDiffBeenCovered: " );
TurnMeOn();
}
else
{
timeDelta = WorldInfo.TimeSeconds - Outer.MyBaseAIPawn.TimeStartedLeap;
if( timeDelta > Outer.MyBaseAIPawn.TimeImmuneWhileLeaping )
{
`AILog( GetFuncName() @ " Turning me on because of time in leap of: " @ timeDelta );
TurnMeOn();
}
}
}
}
function bool HasPercentOfZDiffBeenCovered();
function TurnMeOn()
{
Outer.MyBaseAIPawn.SetCollision( true, true );
Outer.MyBaseAIPawn.bCollideWorld = true;
Outer.MyBaseAIPawn.SetPushesRigidBodies(true);
bCollisionOffPhase = false;
}
function vector GetJumpVelForDropEdge( out float TimeToReachLandLoc );
function vector GetJumpVelForUsingJumpDownNavPointPathObject( out float TimeToReachLandLoc );
state Command_SpecialMove
{
function BeginState( Name PreviousStateName )
{
Super.BeginState( PreviousStateName );
if( MyLeapRequest.TheJumpDownNavPointPathObject != none )
{
SetDesiredDirectionForJumpDoenNavPointEdge();
}
else
{
SetDesiredDirectionForDropEdge();
}
bWantTotalMovementControl = true;
}
function Bool IfShowLeapDownDebugArtifacts()
{
return false;
}
function vector GetCurrentJumpEdgeDirectionOfLeap()
{
return Vector(MyLeapRequest.TheJumpDownNavPointPathObject.Rotation);
}
function float GetDistanceDownRangeToFocusForDropEdgeLeap()
{
return 500.0;
}
//function SetDesiredDirectionForDropEdge()
//{
// local Vector newFocalPoint;
// local float edgesDropHeight;
// LeapDirection = MyKFNavigationHandle.GetCurrentJumpEdgeDirectionOfLeap( Outer, edgesDropHeight, ClosestLocOnEdge );
// Focus = none;
// newFocalPoint = Outer.Pawn.Location + LeapDirection * Outer.DistanceDownRangeToFocusForDropEdgeLeap;
// SetFocalPoint( newFocalPoint );
// if( Outer.bShowLeapDownDebugArtifacts )
// {
// DrawDebugStar( newFocalPoint, 30, 255, 165, 0, true);
// }
// SetDesiredRotation(rotator(LeapDirection));
//}
function SetDesiredDirectionForDropEdge()
{
local Vector newFocalPoint;
//local float edgesDropHeight;
LeapDirection = GetCurrentJumpEdgeDirectionOfLeap();
Focus = none;
newFocalPoint = Outer.Pawn.Location + LeapDirection * GetDistanceDownRangeToFocusForDropEdgeLeap();
SetFocalPoint( newFocalPoint );
if( IfShowLeapDownDebugArtifacts() ) // Outer.bShowLeapDownDebugArtifacts )
{
DrawDebugStar( newFocalPoint, 300, 255, 165, 0, true);
}
SetDesiredRotation(rotator(LeapDirection));
}
//function SetDesiredDirectionForJumpDoenNavPointEdge()
//{
// local Vector newFocalPoint;
// LeapDirection = BaseForLanding.Location - Outer.Pawn.Location;
// Focus = none;
// newFocalPoint = BaseForLanding.Location;// Outer.Pawn.Location + LeapDirection * Outer.DistanceDownRangeToFocusForDropEdgeLeap;
// SetFocalPoint( newFocalPoint );
// if( Outer.bShowLeapDownDebugArtifacts )
// {
// DrawDebugStar( newFocalPoint, 30, 255, 165, 0, true);
// }
// SetDesiredRotation(rotator(LeapDirection));
//}
function SetDesiredDirectionForJumpDoenNavPointEdge()
{
local Vector newFocalPoint;
if( BaseForLanding == none )
{
return;
}
LeapDirection = BaseForLanding.Location - Outer.Pawn.Location;
Focus = none;
newFocalPoint = BaseForLanding.Location;// Outer.Pawn.Location + LeapDirection * Outer.DistanceDownRangeToFocusForDropEdgeLeap;
SetFocalPoint( newFocalPoint );
if( IfShowLeapDownDebugArtifacts() )
{
DrawDebugStar( newFocalPoint, 300, 255, 165, 0, true);
}
SetDesiredRotation(rotator(LeapDirection));
}
function bool DoLeap()
{
return false;
}
function LeapLoopPreSleepDataUpdate()
{
//Outer.LeapBehavior.ProjMeshLocations.AddItem(MyKfPawn.Location);
}
Begin:
while( !Pawn.ReachedDesiredRotation() )
{
Sleep(0.03);
}
if( DoLeap() )
{
do
{
LeapLoopPreSleepDataUpdate();
Sleep( 0.0f );
TestForTimeToTurnCollisionBackOn();
} until( Pawn.Physics != PHYS_Falling );
`AILog( GetFuncName() @ " Pawn.Physics != PHYS_Falling, so landed" );
// CGH this is where i am an trying to get the pawn told about the leap.
Outer.MyBaseAIPawn.bLeaping = false;
Outer.MyBaseAIPawn.SetCollision( true, true, true );
// Status = 'Success';
Focus = Enemy;
//PopCommand( self );
//Stop;
NotifyLanded();
}
bWantTotalMovementControl = false;
GotoState( 'Succeeding' );
}
function NotifyLanded();
defaultproperties
{
}

View File

@ -0,0 +1,367 @@
//=============================================================================
// AIPluginMovement
//=============================================================================
// A proxy/parent class for all specific movement implementations
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AIPluginMovement extends AITickablePlugin within BaseAIController
native(Plugin)
dependson(NavigationPath)
abstract;
struct native MoveParameters
{
var EBaseMoveMood MoveMood;
var EBaseMoveType MoveType;
// var EStridePose EndStridePose;
var Actor RotateAtEndToMatch;
/** When path is actually for following actor. */
var Actor FollowingActor;
/** Movement can be inaccurate. Useful when character ends up around < 2m away and it is not needed for it cover distance. */
var bool bMovementCanBeInaccurate;
/** Don't stop at the end of path. Useful for charge attacks etc. when character is not expected to stop at the end of path. Doesn't apply for entering cover. */
var bool bMoveThroughLastPoint;
/** Indicates whether movement needs to start/end with shooting animation. */
var bool bStartPathFollowingShooting;
var bool bEndPathFollowingShooting;
/** makes sure AI will do any move even if destination is close enough */
var bool bForceAnyMove;
var bool bAllowedToFire;
structcpptext
{
FMoveParameters()
{
Reset();
}
void Reset()
{
appMemzero(this, sizeof(FMoveParameters));
// copied from structdefaultproperties
MoveMood = BMM_Fast;
MoveType = BMT_Normal;
bStartPathFollowingShooting = TRUE;
bEndPathFollowingShooting = FALSE;
}
}
structdefaultproperties
{
// BodyStance=NBS_Stand
MoveMood=BMM_Fast
MoveType=BMT_Normal
bStartPathFollowingShooting=true
}
};
enum EMoveRequestDestinationType
{
MRDT_Invalid,
MRDT_Vector,
MRDT_Actor,
};
struct native MoveRequestDestination
{
var vector VectorDest;
var Actor ActorDest;
var EMoveRequestDestinationType Type;
structcpptext
{
FMoveRequestDestination()
{
appMemzero(this, sizeof(FMoveRequestDestination));
}
FMoveRequestDestination(const class FVector& vDestination)
{
VectorDest = vDestination;
Type = MRDT_Vector;
}
FMoveRequestDestination(class AActor* Actor) : Type(MRDT_Invalid)
{
if(Actor != NULL)
{
ActorDest = Actor;
Type = MRDT_Actor;
}
}
void Reset()
{
Type = MRDT_Invalid;
}
void Set(const class FVector& vDestination)
{
VectorDest = vDestination;
Type = MRDT_Vector;
}
void Set(class AActor* Actor)
{
if(Actor != NULL)
{
ActorDest = Actor;
Type = MRDT_Actor;
}
else
{
Type = MRDT_Invalid;
}
}
//void Set(struct FCoverInfo& inCoverInfo)
//{
// if(inCoverInfo.IsSet() == TRUE)
// {
// CoverInfoDest = inCoverInfo;
// Type = MRDT_Cover;
// }
// else
// {
// Type = MRDT_Invalid;
// }
//}
UBOOL IsActor() const
{
return Type == MRDT_Actor;
}
UBOOL IsVector() const
{
return Type == MRDT_Vector;
}
//UBOOL IsCover() const
//{
// return Type == MRDT_Cover;
//}
class AActor* GetAsActor()
{
return Type == MRDT_Actor ? ActorDest : NULL;
}
const class AActor* GetAsActor() const
{
return Type == MRDT_Actor ? ActorDest : NULL;
}
//struct FCoverInfo GetAsCoverInfo() const
//{
// return Type == MRDT_Cover ? CoverInfoDest : FCoverInfo();
//}
FVector GetPosition() const
{
switch(Type)
{
case MRDT_Vector:
return VectorDest;
break;
//case MRDT_Cover:
// return CoverInfoDest.Link ? CoverInfoDest.Link->GetSlotLocation(CoverInfoDest.SlotIdx) : FVector(0.f);
// break;
case MRDT_Actor:
return ActorDest ? ActorDest->Location : FVector(0.f);
break;
default: // MRDT_Invalid
break;
}
return FVector(0.0f);
}
}
};
struct native MovementRequest
{
var native MoveParameters MoveParams;
var float AcceptableDistance;
var vector DestOffset;
var vector MidPointOffset;
/** a struct type with union inside representing move destination. It seems
* ok to have a native only access to it - script will only be able to modify means
* of execution, but not destination itself.
*/
var native MoveRequestDestination Destination;
var native NavigationPath PreComputedPath;
var EActionPriority Priority;
var const object Observer;
var bool bStickToNavmesh;
var bool bStickToActionArea;
var bool bDynamicDestOffset; // not used at the moment. Added for consistence
var bool bDynamicMidPoint;
var bool bPostProcessPath;
var int QueryID;
structcpptext
{
FMovementRequest()
{
appMemzero(this, sizeof(FMovementRequest));
Reset();
}
FMovementRequest(FMovementRequest* pOther)
{
if(pOther != NULL)
{
appMemCopy(*this, *pOther);
}
else
{
appMemzero(this, sizeof(FMovementRequest));
Reset();
}
}
FString GetDescription() const;
void Reset()
{
MoveParams.Reset();
AcceptableDistance=0.0f;
Destination.Reset();
PreComputedPath = NULL;
Priority = AP_Invalid;
bStickToNavmesh = TRUE;
bStickToActionArea = FALSE;
bPostProcessPath = FALSE;
Observer = NULL;
}
}
};
var MovementRequest MoveRequest;
var transient float GoalDistanceSq;
var const public{protected} EActionPriority ActionPriority;
var const public{private} EActionPriority MovementLock;
var const transient bool bObserverNotified;
var const transient bool bDontNotifyObserver;
var transient bool bDontRestartByKismet;
var float MinimumSuccessDistance;
cpptext
{
UBOOL IsMovementLocked(const EActionPriority Priority = AP_Logic) const // AP_Locic, i.e. AP_Invalid + 1.
{
return MovementLock >= Priority;
}
EActionPriority GetMovementLock() const
{
return (EActionPriority)MovementLock;
}
UBOOL CanAcceptNewMoveRequest(EActionPriority NewRequestPriority) const
{
return NewRequestPriority >= ActionPriority;
}
FORCEINLINE EActionPriority GetActionPriority() const
{
return (EActionPriority)ActionPriority;
}
FVector GetDestination(struct FMovementRequest& Request) const
{
return Request.Destination.GetPosition();
}
}
/** overrideable only in native code (no-exported as virtual) */
native function bool MoveToRequest(out MovementRequest Request);
native function bool MoveToPointRequest(vector InDestLocation, EActionPriority CommandPriority, optional object ActionObserver, optional bool bStopAtEnd=true, optional bool bStickToNavmesh = true, optional float AcceptableDistance, optional Actor RotateAtEndToMatch);
native function bool MoveToActorRequest(Actor inPawnGoal, EActionPriority CommandPriority, optional object ActionObserver, optional bool bInAllowedToFire=true, optional float AcceptableDistance, optional vector DestOffset, optional vector MidPointOffset, optional bool bDynamicMidPoint, optional bool bStopAtEnd=true, optional bool bStickToNavmesh=true);
native function bool FollowPlugInsPath(NavigationPath InPath, EActionPriority CommandPriority, optional object ActionObserver, optional bool bStopAtEnd=true, optional Actor RotateAtEndToMatch, optional Float AcceptableDistance);
native function bool AbortMove(bool bNewRequest);
/**
* @param bNewRequest if set to false (default) then if there was a move
* task paused while lock was being set, then this task will be resumed.
* If true, it will not. This param makes sense only for bLock == true
*/
native function SetMovementLock(bool bLock, optional EActionPriority Priority = AP_Logic, optional bool bNewRequest);
native function protected Success();
native function protected Failure();
native function bool RePath();
/** Script interface to get location of destination point from given MovementRequest.
* no-exported to make inline. */
final native noexport function vector GetDestination(out MovementRequest Request) const;
final function EMoveRequestDestinationType GetDestinationType()
{
return MoveRequest.Destination.Type;
}
function StopMovement()
{
//@todo fill it!
}
/** Called from native code during latent movement when current move is considered unreachable */
function bool MoveUnreachable( Vector AttemptedDest, Actor AttemptedTarget )
{
if( AttemptedTarget != none )
{
`AILog( GetFuncName()$" AttemptedTarget: "$AttemptedTarget, 'PathWarning' );
}
return false;
}
state Succeeding `DEBUGSTATE
{
Begin:
`AILog("Moving - END:"@GetStateName(), 'Move');
Success();
}
state Failing `DEBUGSTATE
{
Begin:
`AILog("Moving - END:"@GetStateName(), 'Move');
Failure();
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Aborting `DEBUGSTATE
{
Begin:
`AILog("Moving - Aborted", 'Move');
//AbortMove(false);
StopMovement();
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Idling `DEBUGSTATE
{
Begin:
`AILog("Idling", 'Move');
StopMovement();
}
defaultproperties
{
// mz> this change is a little bit hacky and will need further changes. AnimSys accepts path success if destination to cover < 150.0f;
//MinimumSuccessDistance=8100.0f //90 units
MinimumSuccessDistance=160.0f //90 units
}

View File

@ -0,0 +1,190 @@
//=============================================================================
// AIPluginStuckFix
//=============================================================================
// A proxy/parent class for all specific Stuck implementations
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AIPluginStuckFix extends AITickablePlugin within BaseAIController
native(Plugin)
dependson(NavigationPath, AIPluginMovement)
abstract;
struct native FixStuckRequest
{
var native MoveParameters MoveParams;
var float AcceptableDistance;
var vector DestOffset;
//var vector MidPointOffset;
/** a struct type with union inside representing move destination. It seems
* ok to have a native only access to it - script will only be able to modify means
* of execution, but not destination itself.
*/
var native MoveRequestDestination Destination;
//var native NavigationPath PreComputedPath;
var EActionPriority Priority;
var const object Observer;
var bool bFinalApproach;
var int QueryID;
structcpptext
{
FFixStuckRequest()
{
appMemzero(this, sizeof(FFixStuckRequest));
Reset();
}
FFixStuckRequest(FFixStuckRequest* pOther)
{
if(pOther != NULL)
{
appMemCopy(*this, *pOther);
}
else
{
appMemzero(this, sizeof(FFixStuckRequest));
Reset();
}
}
FString GetDescription() const;
void Reset()
{
MoveParams.Reset();
AcceptableDistance=0.0f;
Destination.Reset();
Priority = AP_Invalid;
bFinalApproach = FALSE;
Observer = NULL;
}
}
};
var FixStuckRequest MoveRequest;
var const public{protected} EActionPriority ActionPriority;
var const public{private} EActionPriority MovementLock;
var const transient bool bObserverNotified;
var const transient bool bDontNotifyObserver;
var transient bool bDontRestartByKismet;
cpptext
{
UBOOL IsMovementLocked(const EActionPriority Priority = AP_Logic) const // AP_Locic, i.e. AP_Invalid + 1.
{
return MovementLock >= Priority;
}
EActionPriority GetMovementLock() const
{
return (EActionPriority)MovementLock;
}
UBOOL CanAcceptNewMoveRequest(EActionPriority NewRequestPriority) const
{
return NewRequestPriority >= ActionPriority;
}
FORCEINLINE EActionPriority GetActionPriority() const
{
return (EActionPriority)ActionPriority;
}
FVector GetDestination(struct FFixStuckRequest& Request) const
{
return Request.Destination.GetPosition();
}
}
/** overrideable only in native code (no-exported as virtual) */
function bool StuckFixToPointRequest(vector InDestLocation, EActionPriority CommandPriority, optional object ActionObserver, optional bool bStopAtEnd=true, optional bool bStickToNavmesh = true, optional float AcceptableDistance, optional Actor RotateAtEndToMatch);
function bool StuckFixToActorRequest(Actor inPawnGoal, EActionPriority CommandPriority, optional object ActionObserver, optional bool bInAllowedToFire=true, optional float AcceptableDistance, optional vector DestOffset, optional vector MidPointOffset, optional bool bDynamicMidPoint, optional bool bStopAtEnd=true, optional bool bStickToNavmesh=true);
event bool AbortMove(bool bNewRequest);
/**
* @param bNewRequest if set to false (default) then if there was a move
* task paused while lock was being set, then this task will be resumed.
* If true, it will not. This param makes sense only for bLock == true
*/
function SetMovementLock(bool bLock, optional EActionPriority Priority = AP_Logic, optional bool bNewRequest);
function protected Success();
function protected Failure();
/** Script interface to get location of destination point from given MovementRequest.
* no-exported to make inline. */
final native noexport function vector GetDestination(out MovementRequest Request) const;
final function EMoveRequestDestinationType GetDestinationType()
{
return MoveRequest.Destination.Type;
}
function StopMovement()
{
//@todo fill it!
}
/** Called from native code during latent movement when current move is considered unreachable */
function bool MoveUnreachable( Vector AttemptedDest, Actor AttemptedTarget )
{
if( AttemptedTarget != none )
{
`AILog( GetFuncName()$" AttemptedTarget: "$AttemptedTarget, 'PathWarning' );
}
return false;
}
state Succeeding `DEBUGSTATE
{
Begin:
`AILog("Moving - END:"@GetStateName(), 'Move');
Success();
GotoState( 'Idling' );
}
state Failing `DEBUGSTATE
{
Begin:
`AILog("Moving - END:"@GetStateName(), 'Move');
Failure();
GotoState( 'Idling' );
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Aborting `DEBUGSTATE
{
Begin:
`AILog("Moving - Aborted", 'Move');
//AbortMove(false);
StopMovement();
GotoState( 'Idling' );
}
/* this state does nothing. It's here to leave other states when move is aborted,
* and to indicate that it happened */
state Idling `DEBUGSTATE
{
Begin:
`AILog("Idling", 'Move');
StopMovement();
}
defaultproperties
{
// mz> this change is a little bit hacky and will need further changes. AnimSys accepts path success if destination to cover < 150.0f;
//MinimumSuccessDistance=8100.0f //90 units
//MinimumSuccessDistance=160.0f //90 units
}

View File

@ -0,0 +1,57 @@
//=============================================================================
// AITickablePlugin
//=============================================================================
// this plugin type introduces tickable states functionality to deriving plugins
//
// NOTE: currently every plugin used is ticked directly by owner (only movement plugins
// implemented so far). If more plugins are created and need to be ticked a simple //
// registration mechanism should be implemented to store all plugins to be ticked in
// some array in owning BaseAIController (much like BaseAISubsystem.SmartObjects for example)
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AITickablePlugin extends AIPlugin within BaseAIController
native(Plugin)
abstract;
var const float LatentTime; // Internal latent function use.
var const private{private} bool bTickingEnabled;
cpptext
{
void TickPlugin(FLOAT DeltaTime);
virtual void ProcessState(FLOAT DeltaSeconds);
virtual EGotoState GotoState( FName State, UBOOL bForceEvents = 0, UBOOL bKeepStack = 0 );
void SetEnableTicking(UBOOL bEnable)
{
bTickingEnabled = bEnable;
}
UBOOL IsTickingEnabled() const
{
return bTickingEnabled;
}
}
final native latent function Sleep(float Seconds);
event ScriptTick( float DeltaTime )
{
}
function bool NotifyNpcTerminallyStuck()
{
return false;
}
function bool NotifyNpcInGrannyMode()
{
return false;
}
defaultproperties
{
bTickingEnabled=true
}

View File

@ -0,0 +1,318 @@
//=============================================================================
// BaseAIController
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAIController extends GameAIController
dependson(BaseAIPawn, PluginBase, AIPluginMovement)
implements(PlugInOwnerInterface)
native
config(AI);
var transient BaseAIPawn MyBaseAIPawn;
var transient BaseAISquad BaseSquad;
// ----------------------------------------------------------------------- //
// animation related vars
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Plugins
// ----------------------------------------------------------------------- //
var const public{protected} array<AITickablePlugin> TickablePlugins;
var instanced PluginSquad SquadPlugin;
var instanced AIPluginMovement MovementPlugin;
var class<AIPluginMovement> MovementPluginClass;
var instanced AIPluginLeap LeapPlugin;
var class<AIPluginLeap> LeapPluginClass;
var instanced AIPluginStuckFix StuckFixPlugin;
var class<AIPluginStuckFix> StuckFixPluginClass;
// base off KFAICommandHistory
var transient BaseAiPlugInHistory MyAiPlugInHistory;
// ----------------------------------------------------------------------- //
// Movement
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// perception
// ----------------------------------------------------------------------- //
var bool bUsePerceptionHearing;
var bool bAlwaysAssignEnemy;
/** When last time our enemy was visible */
var float LastEnemySightedTime;
// ----------------------------------------------------------------------- //
// firing
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Target selection/limitation
// ----------------------------------------------------------------------- //
/** List of targets to shoot at */
var array<Actor> TargetList;
/** List of prohibited targets */
var array<Actor> ProhibitedTargetList;
var transient BaseAIPawn SquadAssignedTarget;
struct native LocalEnemyInfo
{
var array<float> PerceptionTimestamp;
var EWSPerceptionMode LatestPerception;
var EWSPerceptionMode DominantPerception;
var int VisibleCoverActions;
var float CurrentThreat;
var vector LatestLocation; //latest location with Seen perception
var Pawn Pawn;
var bool bSeenBefore;
var bool bIsPlayer;
structcpptext
{
/** Constructors */
FLocalEnemyInfo() {}
FLocalEnemyInfo(EEventParm)
{
}
FLocalEnemyInfo(APawn* InPawn)
{
appMemzero(this, sizeof(FLocalEnemyInfo));
Pawn = InPawn;
}
FORCEINLINE UBOOL CanBeSeenWithAction(BYTE Action) const
{
return (VisibleCoverActions & (1 << Action)) != 0;
}
UBOOL operator==(const FLocalEnemyInfo& InEnemyInfo) const
{
return Pawn == InEnemyInfo.Pawn;
}
UBOOL operator==(const APawn* EnemyPawn) const
{
return Pawn == EnemyPawn;
}
}
};
var array<LocalEnemyInfo> LocalEnemyList;
// ----------------------------------------------------------------------- //
// Attachments and SpecialActionSttachments
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Enable/Disable Navmesh and plugins
// ----------------------------------------------------------------------- //
/** If this is on, NPC will always try to use Nav Mesh Path Finding Before Path Node */
var bool bUseNavMesh;
/** If this is on, NPC will always try to use plugins for movement. Only works with navmesh */
var bool bUsePluginsForMovement;
// ----------------------------------------------------------------------- //
// debug variables
// ----------------------------------------------------------------------- //
var(Debug) config int PlugInHistoryNum;
cpptext
{
virtual void Initialize();
void OnLevelStreamedOut(ULevel* Level);
virtual UBOOL Tick(FLOAT DeltaTime, enum ELevelTick TickType);
virtual void PreBeginPlay();
virtual void BeginDestroy();
virtual void GetSquadEnemies(TArray<class APawn*>& Items, UBOOL bExcludeProhibitedTargets = FALSE, UBOOL bExcludeBeliefInfo = FALSE);
virtual UBOOL IsFriendly(const class AController* const TestPlayer) const;
// ----------------------------------------------------------------------- //
// Movement
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// perception
// ----------------------------------------------------------------------- //
void UpdateEnemyKnowledge(INT EnemyIdx, BYTE Perception);
#if !DO_BASEAI_LOGGING
# if COMPILER_SUPPORTS_NOOP
# define BaseAILog __noop
# else
# define BaseAILog GNull->Logf
# endif
static class FOutputDeviceRedirectorBase* GLog;
#else
VARARG_DECL(void,void,{},BaseAILog,VARARG_NONE,const TCHAR*,VARARG_NONE,VARARG_NONE);
VARARG_DECL(void,void,{},BaseAILog,VARARG_NONE,const TCHAR*,VARARG_EXTRA(enum EName E),VARARG_EXTRA(E));
#endif
// AI debug stuff:
virtual void GetGameSpecificDebug(BYTE element, TArrayNoInit<FString>& outLogLines, TArray<struct FSpaceLineInfo>* Lines = NULL) const;
static TArray<UClass*> InitializedAIClasses;
}
/** Called ONCe per class for class specific initialization (e.g. ETQ queries)
*/
event InitializeAIClass()
{
}
event Possess(Pawn inPawn, bool bVehicleTransition)
{
super.Possess(inPawn, bVehicleTransition);
if (inPawn != none)
{
MyBaseAIPawn = BaseAIPawn(inPawn);
`if(`notdefined(__TW_BASEAI_LEAN_))
InitializeDefaultBehavior();
// setup movement properties
SetupPathfinding();
`endif
}
}
function PawnDied(Pawn InPawn)
{
CleanUp();
super.PawnDied(InPawn);
}
/** This function needs to stay final */
native final function float UpdateEnemyRange();
native final function EWSSymbolicAngle UpdateEnemyAngle();
native function CleanUp(optional bool bBeingDestroyed);
// ----------------------------------------------------------------------- //
// PlugInOwner interface plimentation
// ----------------------------------------------------------------------- //
function BaseAiPlugInHistory GetAiPlugInHistory()
{
return MyAiPlugInHistory;
}
function float GetTimeSince( float Time2Test )
{
return `TimeSince(Time2Test);
}
// ----------------------------------------------------------------------- //
// Squad mechanics
// ----------------------------------------------------------------------- //
native function int BroadcastEnemyKnowledge(Pawn EnemyPawn, EWSPerceptionMode Perception);
native function RemoveEnemy(Pawn EnemyPawn);
native function RemoveAllEnemies();
native final function bool IsFriendlyPawn(Pawn TestPlayer) const;
native noexport function bool IsFriendly(Controller TestPlayer) const;
// ----------------------------------------------------------------------- //
// Animation related
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// perception
// ----------------------------------------------------------------------- //
native final function UpdateEnemyPerception(optional bool bSkipResponseDelay);
/** Selects a target from the TargetList or selects an enemy normally */
native function bool SelectTargetInternal(bool bOnlyFromTargetList);
native event bool SetEnemy(Pawn NewEnemy);
native function bool SelectEnemy();
native function bool SelectTarget();
// @todo this will be handled by squads
//function NotifyKilled(Controller Killer, Controller Killed, Pawn KilledPawn)
function NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn, class<DamageType> damageTyp)
{
// remove focus from killed enemy
if (KilledPawn == Focus)
{
Focus = None;
}
// Controller's implementation mess with Enemy, so work on this before calling parent
RemoveEnemy(KilledPawn);
super.NotifyKilled(Killer, Killed, KilledPawn, damageTyp);
}
// ----------------------------------------------------------------------- //
// logic flow control
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Navigation
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Movement
// ----------------------------------------------------------------------- //
function StopMovement(optional EActionPriority ActionPriority = AP_Logic)
{
if(MovementPlugin != None)
{
MovementPlugin.AbortMove(FALSE);
}
if( LeapPlugin != none )
{
LeapPlugin.AbortMove(FALSE);
}
if( StuckFixPlugin != none )
{
StuckFixPlugin.AbortMove(false);
}
}
// ----------------------------------------------------------------------- //
// Attachments and SpecialActionSttachments
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Level progress
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Debug functions
// ----------------------------------------------------------------------- //
native function DrawEnemyPerception(Canvas DrawCanvas);
function DrawDebugTextToHud( HUD HUD, String Text, optional color TextColor );
defaultproperties
{
bAlwaysAssignEnemy=true
bUsePerceptionHearing=false
// this one is a legacy thing - this needs to be true even for AI actors
// because only then PlayerReplicationInfo will be created - teams for example need that
// @todo - really needs to be refactored, but can be soooo tricky
//bIsPlayer=TRUE
MovementPlugin=None
//MovementPluginClass="AIPluginMovement_Recast"
}

View File

@ -0,0 +1,229 @@
//=============================================================================
// BaseAIPawn
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAIPawn extends GamePawn
dependson(BaseAITypes)
config(Game)
native
abstract
notplaceable
nativereplication;
var transient BaseAIController MyBaseAI;
/** remembers the team we were on pre-death as our PlayerReplicationInfo will be disconnected
* mz> reused as simple team number cache, moved from KFPawn.uc
*/
var byte LastTeamNum;
// ----------------------------------------------------------------------- //
// Breadcrumbs
// ----------------------------------------------------------------------- //
// mz> chaning this remember to change 'kBreadCrumbsMax' in BreadCrumbs struct (below)
const kBreadCrumbsMax = 10;
struct native Breadcrumbs
{
var transient vector Crumbs[kBreadCrumbsMax];
var transient byte CurrentCrumb;
var float CrumbDistanceSq;
structcpptext
{
// added this way since script consts were unsuable in this case
enum
{
kBreadCrumbsMax = 10
};
FBreadcrumbs()
{
appMemzero(this, sizeof(FBreadcrumbs));
}
FBreadcrumbs(EEventParm)
{
appMemzero(this, sizeof(FBreadcrumbs));
}
void Init(const FVector& Location, FLOAT inCrumbDistanceSq=10000.f)
{
for(INT i = 0; i < kBreadCrumbsMax; ++i)
{
Crumbs[i] = Location;
}
CrumbDistanceSq = inCrumbDistanceSq;
}
void UpdateCrumbs(const FVector& Location)
{
if(Crumbs[CurrentCrumb].DistanceSquared(Location) >= CrumbDistanceSq)
{
const BYTE newCrumb = (CurrentCrumb + 1) % kBreadCrumbsMax;
Crumbs[newCrumb] = Location;
CurrentCrumb = newCrumb;
}
}
FORCEINLINE FVector GetOldestCrumb() const
{
return Crumbs[(CurrentCrumb + 1) % kBreadCrumbsMax];
}
FVector GetNthCrumb(INT N) const
{
return N >= kBreadCrumbsMax ? GetOldestCrumb() : Crumbs[(CurrentCrumb + kBreadCrumbsMax - N) % kBreadCrumbsMax];
}
}
structdefaultproperties
{
CrumbDistanceSq=10000.f
}
};
var transient BreadCrumbs MyBreadCrumbs;
// ----------------------------------------------------------------------- //
// Target selection/limitation
// ----------------------------------------------------------------------- //
/** Cached value for damage config: AI type */
var transient /*const*/ byte MyAIType;
/** Array of all pawns attacking this controller.*/
var const array<BaseAIPawn> Attackers;
var const array<INT> AttackersPerTypeCount;
// ----------------------------------------------------------------------- //
// Animation related
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// members moved from GearPawn
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Plugins
// ----------------------------------------------------------------------- //
var transient bool bLeaping;
var transient float TimeStartedLeap;
var transient float TimeImmuneWhileLeaping;
cpptext
{
virtual void PostBeginPlay();
virtual void PostScriptDestroyed();
virtual UBOOL Tick(FLOAT DeltaTime, enum ELevelTick TickType);
void AddAttacker(class ABaseAIPawn* Attacker);
};
// ----------------------------------------------------------------------- //
// Properties interface
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Weapon handling
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Pawn overrides
// ----------------------------------------------------------------------- //
function bool Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)
{
`if(`notdefined(__TW_BASEAI_LEAN_))
AbortSOUsage();
`endif
class'BaseAISubsystem'.static.DecreaseTeamSize(GetTeamNum());
if(MyBaseAI != None && MyBaseAI.Enemy != None)
{
BaseAIPawn(MyBaseAI.Enemy).RemoveAttacker(self);
}
return Super.Died(Killer,damageType,HitLocation);
}
function PossessedBy(Controller C, bool bVehicleTransition)
{
Super.PossessedBy(C, bVehicleTransition);
MyBaseAI = BaseAIController(C);
// BaseAIPawn subclasses actually do get possessed by players!
if ( MyBaseAI != None )
{
LastTeamNum = MyBaseAI.GetTeamNum();
}
}
function UnPossessed()
{
MyBaseAI = None;
super.UnPossessed();
`if(`notdefined(__TW_BASEAI_LEAN_))
if (AnimationProxy != None)
{
AnimationProxy.UnPossessed();
}
`endif
}
simulated function NotifyTeamChanged()
{
Super.NotifyTeamChanged();
LastTeamNum = GetTeamNum();
}
// ----------------------------------------------------------------------- //
// Readabilities (Chatter too)
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// Target selection/limitation
// ----------------------------------------------------------------------- //
/**
* @param AIType specifies what type of attackers is of interest, -1 means all
* @return number of Pawns of given type (or all if AIType == -1) attacking this controller
*/
native final function int GetAttackerCount(optional int AIType = -1) const;
native final function RemoveAttacker(BaseAIPawn Attacker);
// ----------------------------------------------------------------------- //
// level progress stuff
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// movement
// ----------------------------------------------------------------------- //
// ----------------------------------------------------------------------- //
// debug
// ----------------------------------------------------------------------- //
final native function DrawCrumbs(HUD HUD) const;
defaultproperties
{
`if(`notdefined(__TW_BASEAI_LEAN_))
AISelectionModifier=1.0
AimAccuracy=Accuracy_Normal
bUseAnimationProxy=true
MovementProps=(bStartMovementShooting=true, bStartMovementShootingOnRepath=false, bEndMovementShooting=true, bCanCombatWalk=true)
bHasAISelectionModifierPerTeam=false
`endif
bCanBeAdheredTo=TRUE
bCanBeFrictionedTo=TRUE
}

View File

@ -0,0 +1,79 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAISquad extends Info
native;
/** Controller for leader of the squad */
var() editconst Controller Leader;
var() editconst Actor FormationCenter;
/** whether this squad has a human controlled player in it */
var bool bPlayerSquad;
var bool bIsMP;
var transient bool bSquadInCombat;
var transient float NextEnemySelectionTimestamp;
/** Call UpdateEnemySelection every X seconds*/
var float EnemySelectionInterval;
cpptext
{
virtual void TickSpecial(FLOAT DeltaSeconds);
}
/** Called immediately after gameplay begins. */
event PostBeginPlay()
{
super.PostBeginPlay();
class'BaseAISubsystem'.static.RegisterSquad(self);
bIsMP = WorldInfo.GRI.IsMultiplayerGame();
}
event Destroyed()
{
class'BaseAISubsystem'.static.UnRegisterSquad(self);
super.Destroyed();
}
/**
* Called by BaseAISubsystem to update enemy info
* @return true if given pawn was on squad's enemy list, false otherwise
*/
native function bool NotifyKilled(Controller Killer, Controller Killed, Pawn KilledPawn, class<DamageType> damageType);
/**
* Called by PluginSquad
*/
final native function EnemyPerceivedBy(Controller Member, EWSPerceptionMode PerceptionType, Pawn Enemy);
native function UpdateLeader(Controller inLeader);
native function int GetSquadMemberCount() const;
native function bool GetSquadMembers(out array<BaseAIPawn> Members);
/** Returns FormationCenter when not in combat, and Leader pawn if in combat.
* @note could add a flag toggling this behavior. No need for it at the moment
*/
native final function BaseAIPawn GetSquadCenterPawn();
/** picks target/enemy for all squad members with BaseAIController.bSquadBasedEnemySelection == true
* @return TRUE if any work has been done, and there is a point in re-calling this in some time
*/
native function bool UpdateEnemySelection();
defaultproperties
{
EnemySelectionInterval=0.4f
bIsMP=false
}

View File

@ -0,0 +1,114 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAISubsystem extends AISubsystem
config(Engine)
native
inherits(FCallbackEventDevice,FTickableObject);
var AIDebugTool AIDebug;
var const array<BaseAISquad> Squads;
var const array<INT> TeamSizes;
// ----------------------------------------------------------------------- //
// @deprecated with __TW_BASEAI_LEAN_
// ----------------------------------------------------------------------- //
`if(`notdefined(__TW_BASEAI_LEAN_))
var ETQSystem ETQSys;
var BTManager BTMgr;
var MessageSystem MsgSys;
var NavigationSystem NavSys;
var DigitalActingManager DAM;
var SightSystem SightSys;
var const SmartObjectReplicationActor SmartObjectReplication;
var const array<SOHubComponent> SmartObjects;
var const array<SOHubComponent> InitPendingSmartObjects;
var const array<CoverLink> SpawnedCovers;
var const array<AIAvoidanceComponent> AvoidanceComponents;
native static final noexport function ETQSystem GetETQSystem();
native static final noexport function BTManager GetBTManager();
native static final noexport function MessageSystem GetMessageSystem();
native static final noexport function NavigationSystem GetNavigationSystem();
native static final noexport function DigitalActingManager GetDAM();
native static final noexport function SightSystem GetSightSystem();
native static final noexport function RegisterSmartObject(SOHubComponent SO);
native static final noexport function UnRegisterSmartObject(SOHubComponent SO);
native static final noexport function RegisterSpawnedCover(CoverLink Cover);
native static final noexport function UnRegisterSpawnedCover(CoverLink Cover);
native static final noexport function RegisterAvoidanceComponent(AIAvoidanceComponent AvoidanceComponent);
native static final noexport function UnRegisterAvoidanceComponent(AIAvoidanceComponent AvoidanceComponent);
`endif
// ----------------------------------------------------------------------- //
// END __TW_BASEAI_LEAN_
// ----------------------------------------------------------------------- //
native static final noexport function BaseAISubsystem GetInstance();
native static final noexport function AIDebugTool GetAIDebugTool();
native static final noexport function RegisterSquad(BaseAISquad Squad);
native static final noexport function UnRegisterSquad(BaseAISquad Squad);
native static final noexport function IncreaseTeamSize(BYTE TeamId);
native static final noexport function DecreaseTeamSize(BYTE TeamId);
native static final noexport function int GetTeamSize(byte TeamId);
native static final noexport function int GetEnemyTeamsSize(byte TeamId);
//NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn, class<DamageType> damageType)
native static final function NotifyKilled(Controller Killer, Controller KilledController, Pawn KilledPawn, class<DamageType> damageType);
cpptext
{
virtual void Init(UBOOL bEngineStart = FALSE);
virtual void Tick(FLOAT DeltaSeconds);
virtual void CleanUp(UBOOL bShutDown = FALSE);
virtual void PrepareMapChange();
virtual void OnLevelStreamedOut(ULevel* Level);
static UAIDebugTool* GetAIDebugTool(void);
static void RegisterSquad(class ABaseAISquad* Squad);
static void UnRegisterSquad(class ABaseAISquad* Squad);
static TArray<ABaseAISquad*>* GetSquads();
static void ObjectDestroyed(UObject* Object);
static void IncreaseTeamSize(BYTE TeamId);
static void DecreaseTeamSize(BYTE TeamId);
static void OnPawnDestroyed(class ABaseAIPawn* Pawn);
void OnCheckPointLoad();
private:
void DecreaseTeamSizeInternal(BYTE TeamId);
public:
static int GetTeamSize(BYTE TeamId);
static int GetEnemyTeamsSize(BYTE TeamId, BYTE TeamIdLimit = 255);
//-- editor related methods --//
void OnPIEEnd();
void OnPIEMapSwitch();
virtual void Send(ECallbackEventType InType);
virtual void Send(ECallbackEventType InType, DWORD Flags);
virtual UBOOL IsTickable() const { return TRUE; }
};
defaultproperties
{
bImplementsNavMeshGeneration=true
}

View File

@ -0,0 +1,105 @@
//=============================================================================
// BaseAITypes
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAITypes extends Object
native(Types)
abstract
config(AI);
enum EActionPriority
{
AP_Invalid,
AP_Logic,
AP_Kismet, // default GoW priority, not only for kismet
AP_Reaction,
};
enum EBaseMoveMood
{
BMM_Invalid,
BMM_Static,
BMM_Slow,
BMM_Normal,
BMM_Fast,
};
enum EBaseMoveType
{
BMT_Invalid,
BMT_Normal,
BMT_Combat,
BMT_Careful,
BMT_Pain,
};
enum EWSPerceptionMode
{
WSPM_None,
WSPM_Memory,
WSPM_Belief,
WSPM_Hearing,
WSPM_Sight,
};
enum EWSSymbolicAngle
{
WSSA_Front,
WSSA_Side,
WSSA_Back,
WSSA_Unknown,
};
enum EBTResult
{
BTR_Error,
BTR_Abort,
BTR_Fail,
BTR_Success,
BTR_NotFinished,
};
// ----------------------------------------------------------------------- //
// Debug types
// ----------------------------------------------------------------------- //
enum EDebugLineType
{
DLT_Generic,
DLT_InstantFire,
DLT_NoPath,
DLT_LeaderFollowing,
DLT_Failed,
DLT_Red,
DLT_Green,
DLT_Blue,
};
struct native SpaceLineInfo
{
var vector Start;
var vector End;
var EDebugLineType Type;
var init string Comment;
var native Name Category;
structcpptext
{
/** Constructors */
FSpaceLineInfo() {}
FSpaceLineInfo(EEventParm)
{
appMemzero(this, sizeof(FSpaceLineInfo));
}
FSpaceLineInfo(FVector inStart,FVector inEnd,BYTE inType, const FString& inComment,FName inCategory=NAME_None)
: Start(inStart), End(inEnd), Type(inType), Comment(inComment), Category(inCategory)
{}
}
};
defaultproperties
{
}

View File

@ -0,0 +1,111 @@
//=============================================================================
// BaseAiPlugInHistory
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class BaseAiPlugInHistory extends Object
native(Plugin)
dependson(PluginBase);
struct native BaseAIPlugInHistoryItem
{
var class<AICommandBase> PlugInClass;
var string PlugInName;
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 transient array<BaseAIPlugInHistoryItem> PlugInHistory;
var int PlugInHistoryNum;
var BaseAIController CtrlOwner;
cpptext
{
virtual void StorePlugInHistory( UPluginBase* ThePlugIn );
}
event Setup( int InPlugInHistoryNum, BaseAIController InCtrlOwner )
{
PlugInHistoryNum = InPlugInHistoryNum;
CtrlOwner = InCtrlOwner;
}
simulated function DrawDebugToHud( HUD HUD, name Category )
{
//local KFHUDBase kfHUD;
local int plugInCnt;
local Canvas canToUse;
//local BaseAICommand Cmd;
local BaseAIPlugInHistoryItem plugInItem;
//local bool bDrawDebugCommandStack, bDrawDebugCommandHistory;
//local bool bDrawDebugAllPlugins, bDrawDebugPlugInHistory;
//local string AddTxt, NullTxt;
//local AICommand AC;
//kfHUD = KFHUDBase(kfHud);
////local float Aggression;
//bDrawDebugCommandStack = false;
//bDrawDebugCommandHistory = false;
// 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'*/ true )
{
canToUse = HUD.Canvas;
canToUse.SetOrigin(0,0);
canToUse.Font = class'Engine'.Static.GetSmallFont();
canToUse.SetPos(canToUse.SizeX * 0.05f, canToUse.SizeY * 0.25f);
// WRITE OUT COMMAND HISTORY
// C.SetDrawColor(255, 255, 255, 255);
CtrlOwner.DrawDebugTextToHud( HUD, "************************************************************" );
//C.SetDrawColor(0, 0, 255, 255);
CtrlOwner.DrawDebugTextToHud( HUD, "PLUG IN HISTORY (Count:"@PlugInHistoryNum$")" );
plugInCnt = 0;
foreach PlugInHistory( plugInItem )
{
plugInCnt++;
// C.SetDrawColor(255, 0, 0, 255);
CtrlOwner.DrawDebugTextToHud( HUD, "PlugIn"@plugInCnt$":"@String(plugInItem.PlugInClass)@"Time:"@plugInItem.TimeStamp);
if( Len(plugInItem.VerboseString) > 0 )
{
// C.SetDrawColor(255, 64, 64, 255);
CtrlOwner.DrawDebugTextToHud( HUD, ".............."@plugInItem.VerboseString );
}
}
}
}
defaultproperties
{
PlugInHistoryNum=25
}

View File

@ -0,0 +1,25 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class CodeSpeedTestCommandlet extends Commandlet
native;
var transient object CurrentPackage;
cpptext
{
/**
* Commandlet entry point
*
* @param Params the command line parameters that were passed in.
*
* @return 0 if the commandlet succeeded; otherwise, an error code defined by the commandlet.
*/
virtual INT Main(const FString& Params);
};

View File

@ -0,0 +1,13 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
interface LatentActionObserver
native;
native function OnLatentFinished(BaseAIController Observer, Object Action, byte FinishResult);

View File

@ -0,0 +1,64 @@
//=============================================================================
// NavigationPath
//=============================================================================
// deprecated stub class on 6/29/2015 (script only)
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class NavigationPath extends Object;
struct BaseTransform
{
var const private{private} Actor Base;
var const private{private} Vector InitialLocation;
var const private{private} Rotator InitialRotation;
// used in lazy update
var const private{private} Vector UpdatedForLocation;
var const private{private} Rotator UpdatedForRotation;
// to take from initial pos into base pos
var const private{private} Matrix InitialReversedTransform;
// whole transform from initial pos (as in array) into world pos
var const private{private} Matrix IBS2WSTransform;
// reverse
var const private{private} Matrix WS2IBSTransform;
};
struct PathPoint
{
var Vector Location;
var byte Flags;
};
// -- not to be changed from script! -- //
var init const array<PathPoint> PathPoints;
struct PolyArray_Mirror
{
var init array<int> Dummy;
};
var private const PolyArray_Mirror PolysUsed;
/** Base on which this path is generated. Valid if bDynamic == true. */
var const BaseTransform Base;
/** Destination expressed in initial navmesh position. */
var const vector LocalDestination;
var private{private} const bool bDynamic;
/** variable auto-incremented every new path is stored in this path object */
var public{private} const int Version;
/**
* Returns indexed element's position offset and rotated if necessary
*/
final function vector GetElementPos(int index);
final function SetRoute(array<PathPoint> Route);
/** function used to translate given location to path's generation base's space */
final function vector WorldToLocal(vector InLocation);
// ----------------------------------------------------------------------- //
// debug stuff
// ----------------------------------------------------------------------- //
final function DrawPath(Canvas Canvas, optional byte R=0, optional byte G=255, optional byte B=128, optional bool bPersistent);

View File

@ -0,0 +1,52 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class PlayerInputRecorder extends Object
native(Tools);
static native noexport final function StartRecording(string Filename);
static native noexport final function StopRecording();
static native noexport final function Replay(string Filename);
static native noexport final function StopReplay();
cpptext
{
#if HASEDITOR
// to be specialized in Platforms' drivers
template<typename TJoystickInfo, typename TXInputState>
static void Replay(TJoystickInfo&, TXInputState&);
// to be specialized in Platforms' drivers
template<typename TJoystickInfo, typename TXInputState>
static void Record(TJoystickInfo&, TXInputState&);
// to be defined in Platforms' drivers
static void FlushInputRecBuffer(UBOOL bFinalize);
static UBOOL IsRecording();
static UBOOL IsReplaying();
static void StopReplay();
static void OnReplayingStop();
static UBOOL ReadInRecordedPlayerInput(UBOOL bFinishing = FALSE);
enum
{
kInputRecordBufferSize = 128
};
protected:
static FString CurrentFileName;
static UBOOL bIsInputRecording;
static UBOOL bIsInputReplaying;
static INT Version;
static INT ReplayIndex;
#endif // HASEDITOR
};

View File

@ -0,0 +1,14 @@
//=============================================================================
// PlugInOwnerInterface
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
interface PlugInOwnerInterface;
function BaseAiPlugInHistory GetAiPlugInHistory();
//function float GetCurrentTime();
function float GetTimeSince( float Time2Test );

View File

@ -0,0 +1,122 @@
//=============================================================================
// PluginBase
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class PluginBase extends Object
abstract
native(Plugin);
var PlugInOwnerInterface PlugInOwner;
var const public{private} bool bIsPluginEnabled;
var bool bAborted;
var bool bFailure;
var bool bSuccess;
var BaseAiPlugInHistory OwnersAiPlugInHistory;
var string HistoryString;
/** Exiting status of this plugin */
var() transient string StatusStr;
cpptext
{
FORCEINLINE UBOOL IsPluginEnabled() const
{
return bIsPluginEnabled;
}
virtual void CleanUp(UBOOL bBeingDestroyed) {}
}
native function DisablePlugin();
native function EnablePlugin();
simulated function DrawDebugToHud( HUD HUD, name Category );
event ScriptInitialize()
{
bAborted = false;
bFailure = false;
bSuccess = false;
}
event DrawDebug(HUD H, Name Category);
/*********************************************************************************************
* Debugging
********************************************************************************************* */
/** Update command history (debugging) */
function UpdateCommandHistory()
{
local int i;
if( PlugInOwner != none )
{
if( OwnersAiPlugInHistory == none )
{
OwnersAiPlugInHistory = PlugInOwner.GetAiPlugInHistory();
}
for( i = 0; i < OwnersAiPlugInHistory.PlugInHistory.Length; i++ )
{
if( OwnersAiPlugInHistory.PlugInHistory[i].PlugInName != "" && OwnersAiPlugInHistory.PlugInHistory[i].PlugInName == string(name) )
{
if( bAborted)
{
OwnersAiPlugInHistory.PlugInHistory[i].bAborted = true;
}
if( bFailure)
{
OwnersAiPlugInHistory.PlugInHistory[i].bFailure = true;
}
if( bSuccess )
{
OwnersAiPlugInHistory.PlugInHistory[i].bSuccess = true;
}
UpdateHistoryString( "Status: " $ StatusStr );
//HistoryString = "Status: "$Status;
OwnersAiPlugInHistory.PlugInHistory[i].Duration = PlugInOwner.GetTimeSince(OwnersAiPlugInHistory.PlugInHistory[i].TimeStamp);
OwnersAiPlugInHistory.PlugInHistory[i].VerboseString = HistoryString;
}
}
}
}
/** Update the command's HistoryString, which is output when DumpCommandHistory() is called */
function UpdateHistoryString( string AddString )
{
if( PlugInOwner != none )
{
if( OwnersAiPlugInHistory == none )
{
OwnersAiPlugInHistory = PlugInOwner.GetAiPlugInHistory();
}
if( OwnersAiPlugInHistory.PlugInHistoryNum > 0 )
{
HistoryString = HistoryString$" "$AddString;
}
}
}
/** Used when dumping command history to log file */
event string GetDebugVerboseText()
{
return HistoryString;
}
defaultproperties
{
bIsPluginEnabled=true
HistoryString="[I]"
}

View File

@ -0,0 +1,26 @@
//=============================================================================
// AnimationProxy
//=============================================================================
//
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
/**
*
*
* This plugin introduces squad mechanics to owning BaseAIController. This class is not the same as a squad itself,
* it introduces actions that can be performed on a squad. Advantage of this approach is that any given BaseAIController
* can easily change squads just by changing 'Squad' variable of its 'CoveringPlugin'.
*/
class PluginSquad extends AIPlugin within BaseAIController
native(Plugin);
var const BaseAISquad Squad;
final native function EnemyPerceived(EWSPerceptionMode PerceptionType, Pawn PerceivedEnemy);
defaultproperties
{
}

66
BaseAI/Globals.uci Normal file
View File

@ -0,0 +1,66 @@
//=============================================================================
// Globals
//=============================================================================
// /* MessageSystem macros */
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
`define MsgSystem class'MessageSystem'.static.GetInstance()
/* Behavior tree macros */
`if(`isdefined(FINAL_RELEASE))
`define BTLog(msg)
`define BTLog_Ext(msg, object)
`define AILog(text)
`define AILog(text, category)
`define AILog(text, category, bForce)
`define AILog_Ext(text, category, object)
`define AILogNativeCallStack()
`define AILogScriptCallStack()
`define AILogNativeCallStack_Ext(actor)
`define AILogScriptCallStack_Ext(actor)
`define AILogSpaceLine(Owner, Start, End)
`define AILogSpaceLine(Owner, Start, End, Type)
`define AILogSpaceLine(Owner, Start, End, Type, Comment)
`define AILogSpaceLine(Owner, Start, End, Type, Comment, Category)
`define AILogSetContext(Owner)
`define DEBUGSTATE
`else
`define BTLog(msg) AILog_Internal(`msg, 'BehaviorTree')
`define BTLog_Ext(msg, object) if (`object != none) { `object.AILog_Internal(`msg, 'BehaviorTree' ); }
`define StaticEngineContext class'Engine'.static.GetEngine()
`define AILog(text) if( ! `StaticEngineContext.bDisableAILogging) {AILog_Internal(`text);}
`define AILog(text, category) if( ! `StaticEngineContext.bDisableAILogging) {AILog_Internal(`text,`category);}
`define AILog(text, category, bForce) if( ! `StaticEngineContext.bDisableAILogging) {AILog_Internal(`text,`category,`bForce);}
`define AILog_Ext(text, category, object) if( !`StaticEngineContext.bDIsableAILogging && `object != None ) { `object.AILog_Internal(`text,`category); }
`define AILogNativeCallStack() class'BaseAISubsystem'.static.GetAIDebugTool().DumpNativeCallStack(self)
`define AILogScriptCallStack() AILog_Internal(GetScriptTrace(),'CallStack')
`define AILogNativeCallStack_Ext(actor) class'BaseAISubsystem'.static.GetAIDebugTool().DumpNativeCallStack(`actor)
`define AILogScriptCallStack_Ext(actor) `actor.AILog_Internal(GetScriptTrace(),'CallStack')
`define AILogSpaceLine(Owner, Start, End) class'BaseAISubsystem'.static.GetAIDebugTool().LogSpaceLine(`Owner, `Start, `End, DLT_Generic)
`define AILogSpaceLine(Owner, Start, End, Type) class'BaseAISubsystem'.static.GetAIDebugTool().LogSpaceLine(`Owner, `Start, `End, `Type)
`define AILogSpaceLine(Owner, Start, End, Type, Comment) class'BaseAISubsystem'.static.GetAIDebugTool().LogSpaceLine(`Owner, `Start, `End, `Type, `Comment)
`define AILogSpaceLine(Owner, Start, End, Type, Comment, Category) class'BaseAISubsystem'.static.GetAIDebugTool().LogSpaceLine(`Owner, `Start, `End, `Type, `Comment, `Category)
`define AILogSetContext(Owner) class'BaseAISubsystem'.static.GetAIDebugTool().SetContext(`Owner)
`define DEBUGSTATE extends DEBUGSTATE
`endif