359 lines
9.2 KiB
Ucode
359 lines
9.2 KiB
Ucode
//=============================================================================
|
|
// AIController, the base class of AI.
|
|
//
|
|
// Controllers are non-physical actors that can be attached to a pawn to control
|
|
// its actions. AIControllers implement the artificial intelligence for the pawns they control.
|
|
//
|
|
//Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
|
//=============================================================================
|
|
class AIController extends Controller
|
|
native(AI);
|
|
|
|
`if(`notdefined(__TW_))
|
|
/** auto-adjust around corners, with no hitwall notification for controller or pawn
|
|
if wall is hit during a MoveTo() or MoveToward() latent execution. */
|
|
var bool bAdjustFromWalls;
|
|
`endif
|
|
|
|
/** skill, scaled by game difficulty (add difficulty to this value) */
|
|
var float Skill;
|
|
|
|
/** Move target from last scripted action */
|
|
var Actor ScriptedMoveTarget;
|
|
|
|
/** Route from last scripted action; if valid, sets ScriptedMoveTarget with the points along the route */
|
|
var Route ScriptedRoute;
|
|
|
|
/** if true, we're following the scripted route in reverse */
|
|
var bool bReverseScriptedRoute;
|
|
|
|
/** if ScriptedRoute is valid, the index of the current point we're moving to */
|
|
var int ScriptedRouteIndex;
|
|
|
|
/** view focus from last scripted action */
|
|
var Actor ScriptedFocus;
|
|
|
|
cpptext
|
|
{
|
|
INT AcceptNearbyPath(AActor *goal);
|
|
#if __TW_PATHFINDING_
|
|
virtual void AdjustFromWall(FVector HitNormal, AActor* HitActor);
|
|
#else
|
|
void AdjustFromWall(FVector HitNormal, AActor* HitActor);
|
|
#endif
|
|
virtual void SetAdjustLocation(FVector NewLoc,UBOOL bAdjust,UBOOL bOffsetFromBase=FALSE);
|
|
virtual FVector DesiredDirection();
|
|
|
|
/** Called when the AIController is destroyed via script */
|
|
virtual void PostScriptDestroyed();
|
|
}
|
|
|
|
event PreBeginPlay()
|
|
{
|
|
Super.PreBeginPlay();
|
|
if ( bDeleteMe )
|
|
return;
|
|
|
|
if ( WorldInfo.Game != None )
|
|
Skill += WorldInfo.Game.GetModifiedGameDifficulty();
|
|
Skill = FClamp(Skill, 0, 3);
|
|
}
|
|
|
|
`if(`__TW_PATHFINDING_)
|
|
// Adding declaration here for easier ability to call this event from native code in Engine
|
|
event AILog_Internal( coerce string LogText, optional Name LogCategory, optional bool bForce, optional bool BugIt, optional bool bSkipExtraInfo );
|
|
`endif
|
|
|
|
/* Reset()
|
|
reset actor to initial state - used when restarting level without reloading.
|
|
*/
|
|
function Reset()
|
|
{
|
|
Super.Reset();
|
|
}
|
|
|
|
/**
|
|
* list important AIController variables on canvas. HUD will call DisplayDebug() on the current ViewTarget when
|
|
* the ShowDebug exec is used
|
|
*
|
|
* @param HUD - HUD with canvas to draw on
|
|
* @input out_YL - Height of the current font
|
|
* @input out_YPos - Y position on Canvas. out_YPos += out_YL, gives position to draw text for next debug line.
|
|
*/
|
|
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
|
|
{
|
|
local int i;
|
|
local string T;
|
|
local Canvas Canvas;
|
|
|
|
Canvas = HUD.Canvas;
|
|
|
|
super.DisplayDebug(HUD, out_YL, out_YPos);
|
|
|
|
if (HUD.ShouldDisplayDebug('AI'))
|
|
{
|
|
Canvas.DrawColor.B = 255;
|
|
if ( (Pawn != None) && (MoveTarget != None) && Pawn.ReachedDestination(MoveTarget) )
|
|
Canvas.DrawText(" Skill "$Skill$" NAVIGATION MoveTarget "$GetItemName(String(MoveTarget))$"(REACHED) MoveTimer "$MoveTimer, false);
|
|
else
|
|
Canvas.DrawText(" Skill "$Skill$" NAVIGATION MoveTarget "$GetItemName(String(MoveTarget))$" MoveTimer "$MoveTimer, false);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
|
|
Canvas.DrawText(" Destination "$GetDestinationPosition()$" Focus "$GetItemName(string(Focus))$" Preparing Move "$bPreparingMove, false);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
|
|
Canvas.DrawText(" RouteGoal "$GetItemName(string(RouteGoal))$" RouteDist "$RouteDist, false);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
|
|
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]))$"-";
|
|
}
|
|
|
|
Canvas.DrawText(" RouteCache: "$T, false);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
}
|
|
}
|
|
|
|
event SetTeam(int inTeamIdx)
|
|
{
|
|
WorldInfo.Game.ChangeTeam(self,inTeamIdx,true);
|
|
}
|
|
|
|
simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
|
|
{
|
|
// AI does things from the Pawn
|
|
if (Pawn != None)
|
|
{
|
|
out_Location = Pawn.Location;
|
|
out_Rotation = Pawn.Rotation;
|
|
}
|
|
else
|
|
{
|
|
Super.GetPlayerViewPoint(out_Location, out_Rotation);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scripting hook to move this AI to a specific actor.
|
|
*/
|
|
function OnAIMoveToActor(SeqAct_AIMoveToActor Action)
|
|
{
|
|
local Actor DestActor;
|
|
local SeqVar_Object ObjVar;
|
|
|
|
// abort any previous latent moves
|
|
ClearLatentAction(class'SeqAct_AIMoveToActor',true,Action);
|
|
// pick a destination
|
|
DestActor = Action.PickDestination(Pawn);
|
|
// if we found a valid destination
|
|
if (DestActor != None)
|
|
{
|
|
// set the target and push our movement state
|
|
ScriptedRoute = Route(DestActor);
|
|
if (ScriptedRoute != None)
|
|
{
|
|
if (ScriptedRoute.RouteList.length == 0)
|
|
{
|
|
`warn("Invalid route with empty MoveList for scripted move");
|
|
}
|
|
else
|
|
{
|
|
ScriptedRouteIndex = 0;
|
|
if (!IsInState('ScriptedRouteMove'))
|
|
{
|
|
PushState('ScriptedRouteMove');
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScriptedMoveTarget = DestActor;
|
|
if (!IsInState('ScriptedMove'))
|
|
{
|
|
PushState('ScriptedMove');
|
|
}
|
|
}
|
|
// set AI focus, if one was specified
|
|
ScriptedFocus = None;
|
|
foreach Action.LinkedVariables(class'SeqVar_Object', ObjVar, "Look At")
|
|
{
|
|
ScriptedFocus = Actor(ObjVar.GetObjectValue());
|
|
if (ScriptedFocus != None)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`warn("Invalid destination for scripted move");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simple scripted movement state, attempts to pathfind to ScriptedMoveTarget and
|
|
* returns execution to previous state upon either success/failure.
|
|
*/
|
|
state ScriptedMove
|
|
{
|
|
event PoppedState()
|
|
{
|
|
if (ScriptedRoute == None)
|
|
{
|
|
// if we still have the move target, then finish the latent move
|
|
// otherwise consider it aborted
|
|
ClearLatentAction(class'SeqAct_AIMoveToActor', (ScriptedMoveTarget == None));
|
|
}
|
|
// and clear the scripted move target
|
|
ScriptedMoveTarget = None;
|
|
}
|
|
|
|
event PushedState()
|
|
{
|
|
if (Pawn != None)
|
|
{
|
|
// make sure the pawn physics are initialized
|
|
Pawn.SetMovementPhysics();
|
|
}
|
|
}
|
|
|
|
Begin:
|
|
// while we have a valid pawn and move target, and
|
|
// we haven't reached the target yet
|
|
while (Pawn != None &&
|
|
ScriptedMoveTarget != None &&
|
|
!Pawn.ReachedDestination(ScriptedMoveTarget))
|
|
{
|
|
// check to see if it is directly reachable
|
|
if (ActorReachable(ScriptedMoveTarget))
|
|
{
|
|
// then move directly to the actor
|
|
MoveToward(ScriptedMoveTarget, ScriptedFocus);
|
|
}
|
|
else
|
|
{
|
|
// attempt to find a path to the target
|
|
MoveTarget = FindPathToward(ScriptedMoveTarget);
|
|
if (MoveTarget != None)
|
|
{
|
|
// move to the first node on the path
|
|
MoveToward(MoveTarget, ScriptedFocus);
|
|
}
|
|
else
|
|
{
|
|
// abort the move
|
|
`warn("Failed to find path to"@ScriptedMoveTarget);
|
|
ScriptedMoveTarget = None;
|
|
}
|
|
}
|
|
}
|
|
// return to the previous state
|
|
PopState();
|
|
}
|
|
|
|
/** scripted route movement state, pushes ScriptedMove for each point along the route */
|
|
state ScriptedRouteMove
|
|
{
|
|
event PoppedState()
|
|
{
|
|
// if we still have the move target, then finish the latent move
|
|
// otherwise consider it aborted
|
|
ClearLatentAction(class'SeqAct_AIMoveToActor', (ScriptedRoute == None));
|
|
ScriptedRoute = None;
|
|
}
|
|
|
|
Begin:
|
|
while (Pawn != None && ScriptedRoute != None && ScriptedRouteIndex < ScriptedRoute.RouteList.length && ScriptedRouteIndex >= 0)
|
|
{
|
|
ScriptedMoveTarget = ScriptedRoute.RouteList[ScriptedRouteIndex].Actor;
|
|
if (ScriptedMoveTarget != None)
|
|
{
|
|
PushState('ScriptedMove');
|
|
}
|
|
if (Pawn != None && Pawn.ReachedDestination(ScriptedRoute.RouteList[ScriptedRouteIndex].Actor))
|
|
{
|
|
if (bReverseScriptedRoute)
|
|
{
|
|
ScriptedRouteIndex--;
|
|
}
|
|
else
|
|
{
|
|
ScriptedRouteIndex++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`warn("Aborting scripted route");
|
|
ScriptedRoute = None;
|
|
PopState();
|
|
}
|
|
}
|
|
|
|
if (Pawn != None && ScriptedRoute != None && ScriptedRoute.RouteList.length > 0)
|
|
{
|
|
switch (ScriptedRoute.RouteType)
|
|
{
|
|
case ERT_Linear:
|
|
PopState();
|
|
break;
|
|
case ERT_Loop:
|
|
bReverseScriptedRoute = !bReverseScriptedRoute;
|
|
// advance index by one to get back into valid range
|
|
if (bReverseScriptedRoute)
|
|
{
|
|
ScriptedRouteIndex--;
|
|
}
|
|
else
|
|
{
|
|
ScriptedRouteIndex++;
|
|
}
|
|
Goto('Begin');
|
|
break;
|
|
case ERT_Circle:
|
|
ScriptedRouteIndex = 0;
|
|
Goto('Begin');
|
|
break;
|
|
default:
|
|
`warn("Unknown route type");
|
|
ScriptedRoute = None;
|
|
PopState();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScriptedRoute = None;
|
|
PopState();
|
|
}
|
|
|
|
// should never get here
|
|
`warn("Reached end of state execution");
|
|
ScriptedRoute = None;
|
|
PopState();
|
|
}
|
|
|
|
function NotifyWeaponFired(Weapon W, byte FireMode);
|
|
function NotifyWeaponFinishedFiring(Weapon W, byte FireMode);
|
|
|
|
function bool CanFireWeapon( Weapon Wpn, byte FireModeNum ) { return TRUE; }
|
|
|
|
|
|
defaultproperties
|
|
{
|
|
bAdjustFromWalls=true
|
|
bCanDoSpecial=true
|
|
MinHitWall=-0.5f
|
|
}
|