quake4-sdk/source/game/ai/AI_States.cpp
2007-06-15 00:00:00 +00:00

2213 lines
61 KiB
C++

#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../vehicle/Vehicle.h"
#include "AI_Manager.h"
#include "AI_Util.h"
#include "AAS_Find.h"
static const int TACTICALUPDATE_MELEEDELAY = 500; // Rate to update tactical state when rushing
static const int TACTICALUPDATE_RANGEDDELAY = 2000; // Rate to update tactical state when in ranged combat
static const int TACTICALUPDATE_COVERDELAY = 5000; // Rate to update tactical state when in cover
static const int TACTICALUPDATE_HIDEDELAY = 5000; // Rate to update tactical state when hiding
static const int TACTICALUPDATE_FOLLOWDELAY = 500; // Rate to update tactical state when following
static const int TACTICALUPDATE_MOVETETHERDELAY = 1000; // Rate to update tactical state when moving into tether range
static const int TACTICALUPDATE_PASSIVEDELAY = 500; // Rate to update tactical state when in passive mode
static const int TACTICALUPDATE_TURRETDELAY = 250; // Rate to update tactical state when in turret mode
static const int RANGED_ENEMYDELAY = 2000; // Time to wait to move after losing sight of an enemy
static const int COVER_ENEMYDELAY = 5000; // Stay behind cover for 5 seconds after loosing sight of an enemy
static const float COVER_TRIGGERRADIUS = 64.0f;
CLASS_STATES_DECLARATION ( idAI )
// Wait States
STATE ( "Wait_Activated", idAI::State_Wait_Activated )
STATE ( "Wait_ScriptedDone", idAI::State_Wait_ScriptedDone )
STATE ( "Wait_Action", idAI::State_Wait_Action )
STATE ( "Wait_ActionNoPain", idAI::State_Wait_ActionNoPain )
// Global States
STATE ( "State_WakeUp", idAI::State_WakeUp )
STATE ( "State_TriggerAnim", idAI::State_TriggerAnim )
// Passive states
STATE ( "State_Passive", idAI::State_Passive )
// Combat states
STATE ( "State_Combat", idAI::State_Combat )
STATE ( "State_CombatCover", idAI::State_CombatCover )
STATE ( "State_CombatMelee", idAI::State_CombatMelee )
STATE ( "State_CombatRanged", idAI::State_CombatRanged )
STATE ( "State_CombatTurret", idAI::State_CombatTurret )
STATE ( "State_CombatHide", idAI::State_CombatHide )
STATE ( "State_Wander", idAI::State_Wander )
STATE ( "State_MoveTether", idAI::State_MoveTether )
STATE ( "State_MoveFollow", idAI::State_MoveFollow )
STATE ( "State_MovePlayerPush", idAI::State_MovePlayerPush )
STATE ( "State_Killed", idAI::State_Killed )
STATE ( "State_Dead", idAI::State_Dead )
STATE ( "State_LightningDeath", idAI::State_LightningDeath )
STATE ( "State_Burn", idAI::State_Burn )
STATE ( "State_Remove", idAI::State_Remove )
STATE ( "State_ScriptedMove", idAI::State_ScriptedMove )
STATE ( "State_ScriptedFace", idAI::State_ScriptedFace )
STATE ( "State_ScriptedStop", idAI::State_ScriptedStop )
STATE ( "State_ScriptedPlaybackMove", idAI::State_ScriptedPlaybackMove )
STATE ( "State_ScriptedPlaybackAim", idAI::State_ScriptedPlaybackAim )
STATE ( "State_ScriptedJumpDown", idAI::State_ScriptedJumpDown )
// Torso States
STATE ( "Torso_Idle", idAI::State_Torso_Idle )
STATE ( "Torso_Sight", idAI::State_Torso_Sight )
STATE ( "Torso_CustomCycle", idAI::State_Torso_CustomCycle )
STATE ( "Torso_Action", idAI::State_Torso_Action )
STATE ( "Torso_FinishAction", idAI::State_Torso_FinishAction )
STATE ( "Torso_Pain", idAI::State_Torso_Pain )
STATE ( "Torso_ScriptedAnim", idAI::State_Torso_ScriptedAnim )
STATE ( "Torso_PassiveIdle", idAI::State_Torso_PassiveIdle )
STATE ( "Torso_PassiveFidget", idAI::State_Torso_PassiveFidget )
// Leg States
STATE ( "Legs_Idle", idAI::State_Legs_Idle )
STATE ( "Legs_Move", idAI::State_Legs_Move )
STATE ( "Legs_MoveThink", idAI::State_Legs_MoveThink )
STATE ( "Legs_TurnLeft", idAI::State_Legs_TurnLeft )
STATE ( "Legs_TurnRight", idAI::State_Legs_TurnRight )
STATE ( "Legs_ChangeDirection", idAI::State_Legs_ChangeDirection )
// Head States
STATE ( "Head_Idle", idAI::State_Head_Idle )
END_CLASS_STATES
/*
===============================================================================
States
===============================================================================
*/
/*
================
idAI::State_TriggerAnim
================
*/
stateResult_t idAI::State_TriggerAnim ( const stateParms_t& parms ) {
const char* triggerAnim;
// If we dont have the trigger anim, just skip it
triggerAnim = spawnArgs.GetString ( "trigger_anim" );
if ( !*triggerAnim || !HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) {
gameLocal.Warning ( "Missing or invalid 'trigger_anim' ('%s') specified for entity '%s'",
triggerAnim, GetName() );
return SRESULT_DONE;
}
Show();
ScriptedAnim ( triggerAnim, 4, false, true );
// FIXME: should do this a better way
// Alwasy let trigger anims play out
fl.neverDormant = true;
return SRESULT_DONE;
}
/*
================
idAI::State_WakeUp
================
*/
stateResult_t idAI::State_WakeUp ( const stateParms_t& parms ) {
const char* triggerAnim;
WakeUp ( );
// Start immeidately into a playback?
if( spawnArgs.FindKey( "playback_intro" ) ){
ScriptedPlaybackMove ( "playback_intro", PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, 0 );
// Start immeidately into a scripted anim?
} else if ( spawnArgs.GetString ( "trigger_anim", "", &triggerAnim) && *triggerAnim && HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) {
PostState ( "State_TriggerAnim" );
return SRESULT_DONE;
} else {
// Either wait to be triggered or wait until they have an enemy
if ( spawnArgs.GetBool ( "trigger" ) ) {
// Start the legs and head in the idle position
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
if ( head ) {
SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 );
}
PostState ( "Wait_Activated" );
}
}
PostState ( "State_Combat" );
return SRESULT_DONE;
}
/*
================
idAI::State_Passive
================
*/
stateResult_t idAI::State_Passive ( const stateParms_t& parms ) {
if ( leader && !aifl.scripted ) {
if ( !GetEnemy() ) {
if ( combat.fl.aware && !combat.fl.ignoreEnemies ) {
//aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive
TurnTowardLeader( (focusType==AIFOCUS_LEADER) );
} else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) {
//passive
TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() );
}
}
}
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
if ( UpdateTactical ( TACTICALUPDATE_PASSIVEDELAY ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_Combat
The base combat state is basically the clearing house for other combat states. By calling
UpdateTactical with a timer of zero it ensures a better tactical move can be found.
================
*/
stateResult_t idAI::State_Combat ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
combat.tacticalCurrent = AITACTICAL_NONE;
// Start the legs and head in the idle position
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
if ( head ) {
SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 );
}
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
// Make sure we keep facing our enemy
if ( enemy.ent ) {
TurnToward ( enemy.lastKnownPosition );
}
// Update the tactical state using all available tactical abilities and reset
// the current tactical state since this is the generic state.
combat.tacticalCurrent = AITACTICAL_NONE;
if ( UpdateTactical ( 0 ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
// If we are here then there isnt a single combat state available, thats not good
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_CombatCover
Seek out a cover point that has a vantage point on the enemy. Stay there firing at the
enemy until the cover point is invalidated.
================
*/
stateResult_t idAI::State_CombatCover ( const stateParms_t& parms ) {
enum {
STAGE_MOVE,
STAGE_ATTACK,
};
switch ( parms.stage ) {
case STAGE_MOVE:
// Attack when we have either stopped moving or are within melee range
if ( move.fl.done && aasSensor->Reserved ( ) ) {
StopMove ( MOVE_STATUS_DONE );
TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f );
return SRESULT_STAGE ( STAGE_ATTACK );
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions on the way to the enemy
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
case STAGE_ATTACK:
// If we dont have a cover point anymore then just bail out
if ( !aasSensor->Reserved ( ) ) {
ForceTacticalUpdate ( );
UpdateTactical ( 0 );
return SRESULT_DONE_WAIT;
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// If we have moved off our cover point, move back
if ( DistanceTo ( aasSensor->ReservedOrigin ( ) ) > 8.0f ) {
if ( UpdateTactical ( 0 ) ) {
return SRESULT_DONE_WAIT;
}
}
// Dont do any cover checks until they are facing towards the wall
if ( !FacingIdeal ( ) ) {
return SRESULT_WAIT;
}
// Perform cover point actions
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_CombatMelee
Head directly towards our enemy but allow ranged actions along the way
================
*/
stateResult_t idAI::State_CombatMelee ( const stateParms_t& parms ) {
enum {
STAGE_MOVE, // Move towards the enemy
STAGE_ATTACK, // Keep attacking until no longer in melee range
};
switch ( parms.stage ) {
case STAGE_MOVE:
// If we can no longer get to our enemy, give up on this and do something else!
if ( move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) {
ForceTacticalUpdate ( );
UpdateTactical ( 0 );
return SRESULT_DONE_WAIT;
}
// Attack when we have either stopped moving or are within melee range
if ( move.fl.done ) {
StopMove ( MOVE_STATUS_DONE );
return SRESULT_STAGE ( STAGE_ATTACK );
}
// Perform actions on the way to the enemy
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
case STAGE_ATTACK:
// If we are out of melee range or lost sight of our enemy then start moving again
if ( !IsEnemyVisible ( ) ) {
ForceTacticalUpdate ( );
UpdateTactical ( 0 );
return SRESULT_DONE_WAIT;
}
// Always face enemy when in melee range
TurnToward ( enemy.lastKnownPosition );
// Perform actions while standing still
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_CombatRanged
Move towards enemy until within firing range then continue to shoot at the enemy
================
*/
stateResult_t idAI::State_CombatRanged ( const stateParms_t& parms ) {
enum {
STAGE_MOVE, // Move to an attack position
STAGE_ATTACK, // Perform actions until the enemy is out of range or not visible
};
switch ( parms.stage ) {
case STAGE_MOVE:
// if we lost our enemy we are done here
if ( !enemy.ent ) {
ForceTacticalUpdate ( );
if ( UpdateTactical ( 0 ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
// If done moving or within range of a visible enemy we can stop
if ( move.fl.done ) {
StopMove ( MOVE_STATUS_DONE );
return SRESULT_STAGE ( STAGE_ATTACK );
}
// Stop early because we have a shot on our enemy?
if ( !move.fl.noRangedInterrupt &&
!aifl.simpleThink && enemy.fl.visible
&& gameLocal.time - enemy.lastVisibleChangeTime > 500
&& enemy.range >= combat.attackRange[0]
&& enemy.range <= combat.attackRange[1]
&& ( DistanceTo ( move.moveDest ) < DistanceTo ( move.lastMoveOrigin ) )
&& (!tether || tether->ValidateDestination ( this, physicsObj.GetOrigin( ) ) )
&& aiManager.ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) {
StopMove ( MOVE_STATUS_DONE );
return SRESULT_STAGE ( STAGE_ATTACK );
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions along the way
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
case STAGE_ATTACK:
// If the enemy is visible but not in fov then rotate
if ( !enemy.fl.inFov && enemy.ent ) {
TurnToward ( enemy.lastKnownPosition );
}
// Update tactical state occasionally
if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_CombatTurret
Stay put and shoot at the enemy when nearby
================
*/
stateResult_t idAI::State_CombatTurret ( const stateParms_t& parms ) {
// Turn toward the enemy if visible but not in fov
if ( IsEnemyVisible ( ) && !enemy.fl.inFov ) {
TurnToward ( enemy.lastKnownPosition );
// Turn towards tether direction if we have no enemy
} else if ( !enemy.ent && tether ) {
TurnToward ( physicsObj.GetOrigin() + tether->GetPhysics()->GetAxis()[0] * 64.0f );
} else if ( leader && !aifl.scripted ) {
if ( !GetEnemy() ) {
if ( combat.fl.aware && !combat.fl.ignoreEnemies ) {
//aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive
TurnTowardLeader( (focusType==AIFOCUS_LEADER) );
} else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) {
//passive
TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() );
}
}
}
// Perform actions
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
// If there are other available tactical states besides turret then try to
// go to one of them
if ( combat.tacticalMaskAvailable & ~(1<<AITACTICAL_TURRET) ) {
if ( UpdateTactical ( TACTICALUPDATE_TURRETDELAY ) ) {
return SRESULT_DONE_WAIT;
}
}
return SRESULT_WAIT;
}
/*
================
idAI::State_CombatHide
================
*/
stateResult_t idAI::State_CombatHide ( const stateParms_t& parms ) {
// Turn toward the enemy if visible but not in fov
if ( IsEnemyVisible ( ) ) {
if ( !move.fl.moving ) {
TurnToward ( enemy.lastKnownPosition );
}
}
if ( UpdateTactical ( TACTICALUPDATE_HIDEDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_Wander
================
*/
stateResult_t idAI::State_Wander ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
if ( !WanderAround ( ) ) {
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( enemy.ent ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_MoveTether
Move into tether range
================
*/
stateResult_t idAI::State_MoveTether ( const stateParms_t& parms ) {
enum {
STAGE_MOVE,
STAGE_DONE,
};
switch ( parms.stage ) {
case STAGE_MOVE:
// If we lost our tether then let UpdateTactical figure out what to do next
if ( !tether ) {
if ( UpdateTactical ( 0 ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
// Stopped moving?
if ( move.fl.done ) {
StopMove ( MOVE_STATUS_DONE );
if ( tether ) {
idVec3 toPos = physicsObj.GetOrigin( ) + 64.0f * tether->GetPhysics()->GetAxis()[0];
TurnToward ( toPos );
if ( !IsBehindCover() ) {
//Do a trace *once* to see if I can crouch-look in the direction of the tether at this point
trace_t tr;
idVec3 crouchEye = physicsObj.GetOrigin( );
crouchEye.z += 32.0f;
gameLocal.TracePoint( this, tr, crouchEye, toPos, MASK_SHOT_BOUNDINGBOX, this );
if ( tr.fraction >= 1.0f ) {
combat.fl.crouchViewClear = true;
}
}
}
ForceTacticalUpdate ( );
return SRESULT_STAGE ( STAGE_DONE );
}
// Update tactical state occasionally to see if there is something better to do
if ( UpdateTactical ( TACTICALUPDATE_MOVETETHERDELAY ) ) {
return SRESULT_DONE_WAIT;
}
// Perform actions on the way to the enemy
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
case STAGE_DONE:
if ( UpdateTactical ( ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_MoveFollow
================
*/
stateResult_t idAI::State_MoveFollow ( const stateParms_t& parms ) {
enum {
STAGE_MOVE,
STAGE_DONE,
};
switch ( parms.stage ) {
case STAGE_MOVE:
// If we lost our leader we are done
if ( !leader ) {
move.fl.done = true;
// Can we just stop here?
} else if ( DistanceTo ( leader ) < (move.followRange[0]+move.followRange[1])*0.5f && aiManager.ValidateDestination ( this, physicsObj.GetOrigin( ), false, leader ) ) {
move.fl.done = true;
/*
idEntity* leaderGroundElevator = leader->GetGroundElevator();
if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) {
move.fl.done = false;
}
*/
}
// Are we done moving?
if ( move.fl.done ) {
StopMove ( MOVE_STATUS_DONE );
if ( leader ) {
TurnToward ( leader->GetPhysics()->GetOrigin() );
}
ForceTacticalUpdate ( );
return SRESULT_STAGE ( STAGE_DONE );
} else {
if ( UpdateTactical ( TACTICALUPDATE_FOLLOWDELAY ) ) {
return SRESULT_DONE_WAIT;
}
}
// Perform actions on the way to the enemy
UpdateAction ( );
return SRESULT_WAIT;
case STAGE_DONE:
if ( UpdateTactical ( ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_MovePlayerPush
Move out of the way when the player is pushing us
================
*/
stateResult_t idAI::State_MovePlayerPush ( const stateParms_t& parms ) {
enum {
STAGE_MOVE,
STAGE_DONE,
};
switch ( parms.stage ) {
case STAGE_MOVE:
if ( move.fl.done ) {
StopMove(MOVE_STATUS_DONE);
ForceTacticalUpdate ( );
return SRESULT_STAGE ( STAGE_DONE );
} else if ( gameLocal.GetTime() - move.startTime > 2000 ) {
if ( UpdateTactical ( ) ) {
return SRESULT_DONE_WAIT;
}
}
return SRESULT_WAIT;
case STAGE_DONE:
if ( UpdateTactical ( ) ) {
return SRESULT_DONE_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_Killed
================
*/
stateResult_t idAI::State_Killed ( const stateParms_t& parms ) {
disablePain = true;
//quickburning subjects skip all this jazz
if( fl.quickBurn ) {
PostState ( "State_Dead" );
return SRESULT_DONE;
}
// Make sure all animation stops
StopAnimState ( ANIMCHANNEL_TORSO );
StopAnimState ( ANIMCHANNEL_LEGS );
if ( head ) {
StopAnimState ( ANIMCHANNEL_HEAD );
}
// Make sure all animations stop
animator.ClearAllAnims ( gameLocal.time, 0 );
if( spawnArgs.GetBool ( "remove_on_death" ) ){
PostState ( "State_Remove" );
} else {
PostState ( "State_Dead" );
}
return SRESULT_DONE;
}
/*
================
idAI::State_Dead
================
*/
stateResult_t idAI::State_Dead ( const stateParms_t& parms ) {
if ( !fl.hidden ) {
float burnDelay = spawnArgs.GetFloat ( "burnaway" );
if ( burnDelay > 0.0f ) {
if( fl.quickBurn ) {
StopRagdoll();
PostState ( "State_Burn", SEC2MS(0.05f) );
} else if ( spawnArgs.GetString( "fx_burn_lightning", NULL ) ) {
lightningNextTime = 0;
lightningEffects = 0;
PostState ( "State_LightningDeath", SEC2MS(burnDelay) );
} else {
PostState ( "State_Burn", SEC2MS(burnDelay) );
}
}
float removeDelay = SEC2MS ( spawnArgs.GetFloat ( "removeDelay" ) );
if ( removeDelay >= 0.0f ) {
PostState ( "State_Remove", removeDelay );
}
} else {
PostState ( "State_Remove" );
}
return SRESULT_DONE;
}
/*
================
idAI::State_LightningDeath
================
*/
stateResult_t idAI::State_LightningDeath ( const stateParms_t& parms ) {
if ( gameLocal.time > lightningNextTime ) {
if ( !lightningEffects )
{
StartSound ( "snd_burn_lightning", SND_CHANNEL_BODY, 0, false, NULL );
}
rvClientCrawlEffect* effect;
effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( spawnArgs, "fx_burn_lightning" ), this, 100 );
effect->Play ( gameLocal.time, false );
lightningNextTime = gameLocal.time + 100;
lightningEffects++;
}
if ( lightningEffects < 10 )
{
return SRESULT_WAIT;
}
/*
if ( spawnArgs.GetString( "fx_burn_lightning" ) )
{
for ( int i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) {
if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) {
if ( !gameLocal.random.RandomInt(1) ) {
PlayEffect( "fx_burn_lightning", (jointHandle_t)i );
}
}
}
}
*/
float burnDelay = spawnArgs.GetFloat ( "burnaway" );
if ( burnDelay > 0.0f ) {
PostState ( "State_Burn", 0 );
}
return SRESULT_DONE;
}
/*
================
idAI::State_Burn
================
*/
stateResult_t idAI::State_Burn ( const stateParms_t& parms ) {
if( fl.hidden ){
return SRESULT_DONE;
}
renderEntity.noShadow = true;
// Dont let the articulated figure be shot once they start burning away
SetCombatContents ( false );
fl.takedamage = false;
renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
idEntity *head = GetHead();
if ( head ) {
head->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
}
UpdateVisuals();
if ( spawnArgs.GetString( "fx_burn_particles", NULL ) )
{
int i = GetAnimator()->GetJointHandle( spawnArgs.GetString("joint_chestOffset","chest") );
if ( i != INVALID_JOINT )
{
PlayEffect( "fx_burn_particles_chest", (jointHandle_t)i );
}
for ( i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) {
if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) {
PlayEffect( "fx_burn_particles", (jointHandle_t)i );
}
}
//FIXME: head, too?
//FIXME: from joint to parent joint?
//FIXME: not small joints...
}
StartSound ( "snd_burn", SND_CHANNEL_BODY, 0, false, NULL );
return SRESULT_DONE;
}
/*
================
idAI::State_Remove
================
*/
stateResult_t idAI::State_Remove ( const stateParms_t& parms ) {
PostEventMS( &EV_Remove, 0 );
return SRESULT_DONE;
}
/*
================
idAI::State_Torso_ScriptedAnim
================
*/
stateResult_t idAI::State_Torso_ScriptedAnim ( const stateParms_t& parms ) {
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
aifl.scripted = false;
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_ScriptedMove
================
*/
stateResult_t idAI::State_ScriptedMove ( const stateParms_t& parms ) {
if ( !aifl.scripted || move.fl.done ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_ScriptedFace
================
*/
stateResult_t idAI::State_ScriptedFace ( const stateParms_t& parms ) {
if ( !aifl.scripted || FacingIdeal() ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_ScriptedPlaybackMove
================
*/
stateResult_t idAI::State_ScriptedPlaybackMove ( const stateParms_t& parms ) {
if ( mPlayback.IsActive() ) {
return SRESULT_WAIT;
}
return SRESULT_DONE;
}
/*
================
idAI::State_ScriptedPlaybackAim
================
*/
stateResult_t idAI::State_ScriptedPlaybackAim ( const stateParms_t& parms ) {
if ( mLookPlayback.IsActive() ) {
return SRESULT_WAIT;
}
return SRESULT_DONE;
}
/*
================
idAI::State_ScriptedStop
================
*/
stateResult_t idAI::State_ScriptedStop ( const stateParms_t& parms ) {
ScriptedEnd ( );
// If ending in an idle animation move the legs back to idle and
// revert back to normal combat
if ( aifl.scriptedEndWithIdle ) {
ForceTacticalUpdate ( );
UpdateTactical ( 0 );
}
return SRESULT_DONE;
}
/*
===============================================================================
AI Torso States
===============================================================================
*/
/*
================
idAI::State_Torso_Idle
================
*/
stateResult_t idAI::State_Torso_Idle ( const stateParms_t& parms ) {
// Custom passive idle?
if ( combat.tacticalCurrent == AITACTICAL_PASSIVE && passive.animIdlePrefix.Length() ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", parms.blendFrames );
return SRESULT_DONE;
}
// If the legs were disabled then get them back into idle again
if ( legsAnim.Disabled ( ) ) {
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames );
}
// Start idle animation
const char* idleAnim = GetIdleAnimName ();
if ( !torsoAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) ) ) {
IdleAnim ( ANIMCHANNEL_TORSO, idleAnim, parms.blendFrames );
}
return SRESULT_DONE;
}
/*
================
idAI::State_Torso_PassiveIdle
================
*/
stateResult_t idAI::State_Torso_PassiveIdle ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_START_WAIT,
STAGE_LOOP,
STAGE_LOOP_WAIT,
STAGE_END,
STAGE_END_WAIT,
STAGE_TALK_WAIT,
};
idStr animName;
switch ( parms.stage ) {
case STAGE_START:
// Talk animation?
if ( passive.talkTime > gameLocal.time ) {
idStr postfix;
if ( talkMessage >= TALKMSG_LOOP ) {
postfix = aiTalkMessageString[TALKMSG_LOOP];
postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) );
} else {
postfix = aiTalkMessageString[talkMessage];
}
// Find their talk animation
animName = spawnArgs.GetString ( va("%s_%s", passive.animTalkPrefix.c_str(), postfix.c_str() ) );
if ( !animName.Length ( ) ) {
animName = spawnArgs.GetString ( passive.animTalkPrefix );
}
if ( animName.Length ( ) ) {
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayCycle ( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
return SRESULT_STAGE ( STAGE_TALK_WAIT );
}
}
// If we have a fidget animation then see if its time to play it
if ( passive.animFidgetPrefix.Length() ) {
if ( passive.fidgetTime == 0 ) {
passive.fidgetTime = gameLocal.time + SEC2MS ( spawnArgs.GetInt ( "fidget_rate", "20" ) );
} else if ( gameLocal.time > passive.fidgetTime ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveFidget", 4 );
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", 4 );
return SRESULT_DONE;
}
}
// Do we have a custom idle animation?
passive.idleAnim = spawnArgs.RandomPrefix ( passive.animIdlePrefix, gameLocal.random );
passive.idleAnimChangeTime = passive.fl.multipleIdles ? SEC2MS ( spawnArgs.GetFloat ( "idle_change_rate", "10" ) ) : 0;
// Is there a start animation for the idle?
animName = va("%s_start", passive.idleAnim.c_str() );
if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
return SRESULT_STAGE ( STAGE_START_WAIT );
}
PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, parms.blendFrames );
return SRESULT_STAGE ( STAGE_LOOP_WAIT );
case STAGE_START_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_LOOP:
PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, 0 );
return SRESULT_STAGE ( STAGE_LOOP_WAIT );
case STAGE_LOOP_WAIT:
// Talk animation interrupting?
if ( passive.animTalkPrefix.Length() && passive.talkTime > gameLocal.time ) {
return SRESULT_STAGE ( STAGE_END );
}
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
// If its time to play a new idle animation then do so
if ( passive.idleAnimChangeTime && gameLocal.time > passive.idleAnimChangeTime ) {
return SRESULT_STAGE ( STAGE_END );
}
// If its time to fidget then do so
if ( passive.fidgetTime && gameLocal.time > passive.fidgetTime ) {
return SRESULT_STAGE ( STAGE_END );
}
// Loop the idle again
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_END:
animName = va("%s_end", passive.idleAnim.c_str() );
if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
return SRESULT_STAGE ( STAGE_END_WAIT );
}
return SRESULT_STAGE ( STAGE_START );
case STAGE_END_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_STAGE ( STAGE_START );
}
return SRESULT_WAIT;
case STAGE_TALK_WAIT:
if ( gameLocal.time > passive.talkTime ) {
return SRESULT_STAGE ( STAGE_START );
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_Torso_PassiveFidget
================
*/
stateResult_t idAI::State_Torso_PassiveFidget ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
idStr animName;
switch ( parms.stage ) {
case STAGE_INIT:
passive.fidgetTime = 0;
animName = spawnArgs.RandomPrefix ( va("anim_%sfidget", passive.prefix.c_str() ), gameLocal.random );
if ( !animName.Length ( ) ) {
animName = spawnArgs.RandomPrefix ( "anim_fidget", gameLocal.random );
}
if ( !animName.Length ( ) ) {
return SRESULT_DONE;
}
if ( !PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_ScriptedJumpDown
Face edge, walk off
================
*/
stateResult_t idAI::State_ScriptedJumpDown( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_FACE_WAIT,
STAGE_ANIM,
STAGE_JUMP_WAIT,
STAGE_INAIR,
STAGE_WAIT_LAND,
STAGE_ANIM_WAIT,
STAGE_FINISH
};
switch ( parms.stage ) {
case STAGE_INIT:
{
aifl.scripted = true;
Event_SaveMove();
StopMove(MOVE_STATUS_DONE);
//move.current_yaw = move.ideal_yaw;
move.moveCommand = MOVE_NONE;
move.fl.allowDirectional = false;
}
if ( !move.fl.onGround ) {
return SRESULT_STAGE ( STAGE_INAIR );
}
return SRESULT_STAGE ( STAGE_FACE_WAIT );
break;
case STAGE_FACE_WAIT:
if ( FacingIdeal() )
{
return SRESULT_STAGE ( STAGE_ANIM );
}
return SRESULT_WAIT;
break;
case STAGE_ANIM:
{
DisableAnimState ( ANIMCHANNEL_LEGS );
torsoAnim.StopAnim ( 0 );
legsAnim.StopAnim ( 0 );
if ( HasAnim( ANIMCHANNEL_TORSO, "jumpdown_start" ) ) {
PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_start", 4 );
move.fl.allowAnimMove = true;
move.fl.noGravity = true;
return SRESULT_STAGE ( STAGE_JUMP_WAIT );
}
return SRESULT_STAGE ( STAGE_INAIR );
}
break;
case STAGE_JUMP_WAIT:
if ( AnimDone( ANIMCHANNEL_TORSO, 2 ) ) {
return SRESULT_STAGE ( STAGE_INAIR );
}
return SRESULT_WAIT;
break;
case STAGE_INAIR:
{
idVec3 vel = viewAxis[0] * 200.0f;
vel.z = -50.0f;
physicsObj.SetLinearVelocity( vel );
PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_loop", 4 );
move.fl.allowAnimMove = false;
move.fl.noGravity = false;
return SRESULT_STAGE ( STAGE_WAIT_LAND );
}
break;
case STAGE_WAIT_LAND:
if ( physicsObj.OnGround() )//GetPhysics()->HasGroundContacts() )
{
PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_end", 0 );
move.fl.allowAnimMove = true;
return SRESULT_STAGE ( STAGE_ANIM_WAIT );
}
return SRESULT_WAIT;
break;
case STAGE_ANIM_WAIT:
if ( AnimDone( ANIMCHANNEL_TORSO, 0 ) )
{
return SRESULT_STAGE ( STAGE_FINISH );
}
return SRESULT_WAIT;
break;
case STAGE_FINISH:
Event_RestoreMove();
SetState( "State_Combat" );
aifl.scripted = false;
return SRESULT_DONE;
break;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_Torso_CustomCycle
================
*/
stateResult_t idAI::State_Torso_CustomCycle ( const stateParms_t& parms ) {
return SRESULT_DONE;
}
/*
================
idAI::State_Torso_Sight
================
*/
stateResult_t idAI::State_Torso_Sight ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT: {
// Dont let everyone on the team play their sight sounds at the same time
if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT ) ) {
Speak ( "lipsync_sight", true );
aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT, 1000 );
}
// Execute sighted scripts
if( !enemy.fl.sighted ) {
enemy.fl.sighted = true;
ExecScriptFunction ( funcs.first_sight, this );
}
ExecScriptFunction ( funcs.sight );
idStr animName = spawnArgs.GetString ( "anim_sight", spawnArgs.GetString ( "sight_anim" ) );
if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) {
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
}
return SRESULT_DONE;
}
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_Torso_Action
================
*/
stateResult_t idAI::State_Torso_Action ( const stateParms_t& parms ) {
// Invalid animation so dont bother with running the action
if ( actionAnimNum == -1 ) {
return SRESULT_DONE_WAIT;
}
// Play the action animation
PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames );
// Wait till animation is finished
PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", parms.blendFrames );
return SRESULT_DONE_WAIT;
}
/*
================
idAI::State_Torso_FinishAction
================
*/
stateResult_t idAI::State_Torso_FinishAction ( const stateParms_t& parms ) {
RestoreFlag ( AIFLAGOVERRIDE_DISABLEPAIN );
RestoreFlag ( AIFLAGOVERRIDE_DAMAGE );
RestoreFlag ( AIFLAGOVERRIDE_NOTURN );
RestoreFlag ( AIFLAGOVERRIDE_NOGRAVITY );
enemy.fl.lockOrigin = false;
aifl.action = false;
OnStopAction ( );
return SRESULT_DONE;
}
/*
================
idAI::State_Torso_Pain
================
*/
stateResult_t idAI::State_Torso_Pain ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_START_WAIT,
STAGE_LOOP,
STAGE_LOOP_WAIT,
STAGE_END,
STAGE_END_WAIT
};
idStr animName;
switch ( parms.stage ) {
case STAGE_START:
DisableAnimState ( ANIMCHANNEL_LEGS );
pain.loopEndTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "pain_maxLoopTime", "1" ) );
// Just in case the pain anim wasnt set before we got here.
if ( !painAnim.Length ( ) ) {
painAnim = "pain";
}
animName = va( "%s_start", painAnim.c_str() );
if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
return SRESULT_STAGE ( STAGE_START_WAIT );
}
PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames );
return SRESULT_STAGE ( STAGE_END_WAIT );
case STAGE_START_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 1 ) ) {
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_LOOP:
PlayCycle ( ANIMCHANNEL_TORSO, painAnim, 1 );
return SRESULT_STAGE ( STAGE_LOOP_WAIT );
case STAGE_LOOP_WAIT:
if ( gameLocal.time - pain.lastTakenTime > AI_PAIN_LOOP_DELAY ) {
return SRESULT_STAGE ( STAGE_END );
}
if ( !pain.loopEndTime || gameLocal.time > pain.loopEndTime ) {
return SRESULT_STAGE ( STAGE_END );
}
return SRESULT_WAIT;
case STAGE_END:
animName = va( "%s_end", painAnim.c_str() );
PlayAnim ( ANIMCHANNEL_TORSO, animName, 1 );
return SRESULT_STAGE ( STAGE_END_WAIT );
case STAGE_END_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
===============================================================================
Head States
===============================================================================
*/
/*
================
idAI::State_Head_Idle
================
*/
stateResult_t idAI::State_Head_Idle ( const stateParms_t& parms ) {
return SRESULT_DONE;
}
/*
===============================================================================
AI Leg States
===============================================================================
*/
/*
================
idAI::State_Legs_Idle
================
*/
stateResult_t idAI::State_Legs_Idle ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT: {
const char* idleAnim;
move.fl.allowAnimMove = false;
idleAnim = GetIdleAnimName ( );
if ( !legsAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) ) ) {
IdleAnim ( ANIMCHANNEL_LEGS, idleAnim, parms.blendFrames );
}
return SRESULT_STAGE ( STAGE_WAIT );
}
case STAGE_WAIT:
// Dont let the legs do anything from idle until they are done blending
// the animations they have (this is to prevent pops when an action finishes
// and blends to idle then immediately the legs move to walk)
if ( animator.IsBlending ( ANIMCHANNEL_LEGS, gameLocal.time ) ) {
return SRESULT_WAIT;
}
if ( move.fl.moving && CanMove() ) {
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 2 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
idAI::State_Legs_TurnLeft
================
*/
stateResult_t idAI::State_Legs_TurnLeft ( const stateParms_t& parms ) {
// Go back to idle if we are done here
if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) {
return SRESULT_WAIT;
}
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames );
return SRESULT_DONE;
}
/*
================
idAI::State_Legs_TurnRight
================
*/
stateResult_t idAI::State_Legs_TurnRight ( const stateParms_t& parms ) {
// Go back to idle if we are done here
if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) {
return SRESULT_WAIT;
}
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames );
return SRESULT_DONE;
}
/*
================
idAI::State_Legs_Move
================
*/
stateResult_t idAI::State_Legs_Move ( const stateParms_t& parms ) {
idStr animName;
move.fl.allowAnimMove = true;
move.fl.allowPrevAnimMove = false;
// If not moving forward just go back to idle
if ( !move.fl.moving || !CanMove() ) {
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames );
return SRESULT_DONE;
}
// Movement direction changed?
if ( move.idealDirection != move.currentDirection ) {
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_ChangeDirection", parms.blendFrames );
}
// Make sure run status is up to date when legs start moving
UpdateRunStatus ( );
// Run or walk?
animName = "run";
if ( !move.fl.idealRunning && HasAnim ( ANIMCHANNEL_TORSO, "walk" ) ) {
animName = "walk";
}
// Append the run direction to the animation name
if ( move.idealDirection == MOVEDIR_LEFT ) {
animName = animName + "_left";
} else if ( move.idealDirection == MOVEDIR_RIGHT ) {
animName = animName + "_right";
} else if ( move.idealDirection == MOVEDIR_BACKWARD ) {
animName = animName + "_backwards";
} else {
/*
// When moving to cover the walk animation is special
if ( move.moveCommand == MOVE_TO_COVER && !move.fl.idealRunning && move.fl.running ) {
animName = "run_slowdown";
}
*/
}
// Update running flag with ideal state
move.fl.running = move.fl.idealRunning;
move.currentDirection = move.idealDirection;
PlayCycle ( ANIMCHANNEL_LEGS, animName, parms.blendFrames );
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_MoveThink", parms.blendFrames );
return SRESULT_DONE;
}
/*
================
idAI::State_Legs_MoveThink
================
*/
stateResult_t idAI::State_Legs_MoveThink ( const stateParms_t& parms ) {
move.fl.allowAnimMove = true;
move.fl.allowPrevAnimMove = false;
// If not moving anymore then go back to idle
if ( !move.fl.moving || !CanMove() ) {
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames );
return SRESULT_DONE;
}
// If the run state has changed restart the leg movement
if ( UpdateRunStatus ( ) || (move.idealDirection != move.currentDirection) ) {
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 4 );
return SRESULT_DONE;
}
// Just wait a frame before doing anything else
return SRESULT_WAIT;
}
/*
================
idAI::State_Legs_ChangeDirection
================
*/
stateResult_t idAI::State_Legs_ChangeDirection ( const stateParms_t& parms ) {
if ( FacingIdeal ( ) || (move.idealDirection != move.currentDirection) ) {
return SRESULT_DONE;
}
move.fl.allowAnimMove = false;
move.fl.allowPrevAnimMove = true;
return SRESULT_WAIT;
}
/*
===============================================================================
State helpers
===============================================================================
*/
/*
================
idAI::UpdateRunStatus
================
*/
bool idAI::UpdateRunStatus ( void ) {
// Get new run status
if ( !move.fl.moving || !CanMove() ) {
move.fl.idealRunning = false;
} else if ( move.walkRange && !ai_useRVMasterMove.GetBool() && move.moveCommand != MOVE_TO_ENEMY && (DistanceTo ( move.moveDest ) - move.range) < move.walkRange ) {
move.fl.idealRunning = false;
} else if ( IsTethered() && tether->IsRunForced ( ) ) {
move.fl.idealRunning = true;
} else if ( IsTethered() && tether->IsWalkForced ( ) ) {
move.fl.idealRunning = false;
} else if ( move.fl.noWalk ) {
move.fl.idealRunning = true;
} else if ( move.fl.noRun ) {
move.fl.idealRunning = false;
} else if ( move.walkRange ) {
if ( move.fl.obstacleInPath ) {
move.fl.idealRunning = false;
} else if ( InLookAtCoverMode() ) {
move.fl.idealRunning = false;
} else if ( !ai_useRVMasterMove.GetBool() && move.walkTurn && !move.fl.allowDirectional && fabs(GetTurnDelta ( )) > move.walkTurn ) {
move.fl.idealRunning = false;
} else {
move.fl.idealRunning = true;
}
} else {
move.fl.idealRunning = true;
}
return move.fl.running != move.fl.idealRunning;
}
/*
================
idAI::UpdateTactical
This method is a recursive method that will determine the best tactical state to be in
from the given available states.
================
*/
bool idAI::UpdateTactical ( int delay ) {
// Update tactical cannot be called while performing an action and it must be called from the main state loop
assert ( !aifl.action );
// No movement updating on simple think (if the update is forced do it anyway)
if ( combat.tacticalUpdateTime && aifl.simpleThink && !combat.tacticalMaskUpdate ) {
return false;
}
// Don't let tactical updates pre-empt pain actions
if ( pain.takenThisFrame ) {
return false;
}
// AI Speeds
if ( ai_speeds.GetBool ( ) ) {
aiManager.timerFindEnemy.Start ( );
}
// Keep the enemy status up to date
if ( !combat.fl.ignoreEnemies ) {
// If we dont have an enemy or havent seen our enemy for a while just find a new one entirely
if ( gameLocal.time - enemy.checkTime > 250 ) {
CheckForEnemy ( true, true );
} else if ( !IsEnemyRecentlyVisible ( ) ) {
CheckForEnemy ( true );
}
}
// AI Speeds
if ( ai_speeds.GetBool ( ) ) {
aiManager.timerFindEnemy.Stop ( );
}
// We have sighted an enemy so execute the sight state
if ( IsEnemyVisible() && !enemy.fl.sighted ) {
PerformAction ( "Torso_Sight", 4, true );
return true;
}
// continue with the last updatetactical if we still have bits left to check in our update mask
if ( !combat.tacticalMaskUpdate ) {
// Ignore the tactical delay if we are taking too much damage here.
// TODO: use skipcurrentdestination instead
if ( !combat.tacticalPainThreshold || combat.tacticalPainTaken < combat.tacticalPainThreshold ) {
// Delay tactical updating?
if ( combat.tacticalUpdateTime && move.moveStatus != MOVE_STATUS_BLOCKED_BY_ENEMY && delay && (gameLocal.time - combat.tacticalUpdateTime) < delay ) {
return false;
}
}
// handle Auto break tethers
if ( IsTethered ( ) && tether->IsAutoBreak ( ) && IsWithinTether ( ) ) {
tether = NULL;
}
// Filter the tactical
combat.tacticalMaskUpdate = FilterTactical ( combat.tacticalMaskAvailable );
} else {
// Make sure all cached tactical updates are still valid
combat.tacticalMaskUpdate &= FilterTactical ( combat.tacticalMaskAvailable );
if ( !combat.tacticalMaskUpdate ) {
return false;
}
}
// AI Speeds
if ( ai_speeds.GetBool ( ) ) {
aiManager.timerTactical.Start ( );
}
// Recursively look for a better tactical state
bool result = UpdateTactical_r ( );
// AI Speeds
if ( ai_speeds.GetBool ( ) ) {
aiManager.timerTactical.Stop ( );
}
return result;
}
bool idAI::UpdateTactical_r ( void ) {
// Mapping of tactical types to combat states
static const char* tacticalState [ ] = {
"State_Combat", // AITACTICAL_NONE
"State_CombatMelee", // AITACTICAL_MELEE
"State_MoveFollow", // AITACTICAL_MOVE_FOLLOW
"State_MoveTether", // AITACTICAL_MOVE_TETHER
"State_MovePlayerPush", // AITACTICAL_MOVE_PLAYERPUSH
"State_CombatCover", // AITACTICAL_COVER
"State_CombatCover", // AITACTICAL_COVER_FLANK
"State_CombatCover", // AITACTICAL_COVER_ADVANCE
"State_CombatCover", // AITACTICAL_COVER_RETREAT
"State_CombatCover", // AITACTICAL_COVER_AMBUSH
"State_CombatRanged", // AITACTICAL_RANGED
"State_CombatTurret", // AITACTICAL_TURRET
"State_CombatHide", // AITACTICAL_HIDE
"State_Passive", // AITACTICAL_PASSIVE
};
if ( g_perfTest_aiStationary.GetBool() ) {
return false;
}
// Determine the new tactical state
aiTactical_t newTactical = AITACTICAL_TURRET;
if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_PLAYERPUSH_BIT ) {
newTactical = AITACTICAL_MOVE_PLAYERPUSH;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_FOLLOW_BIT ) {
newTactical = AITACTICAL_MOVE_FOLLOW;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_ADVANCE_BIT ) {
newTactical = AITACTICAL_COVER_ADVANCE;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_FLANK_BIT ) {
newTactical = AITACTICAL_COVER_FLANK;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_RETREAT_BIT ) {
newTactical = AITACTICAL_COVER_RETREAT;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_AMBUSH_BIT ) {
newTactical = AITACTICAL_COVER_AMBUSH;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_BIT ) {
newTactical = AITACTICAL_COVER;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_RANGED_BIT ) {
newTactical = AITACTICAL_RANGED;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_MELEE_BIT ) {
newTactical = AITACTICAL_MELEE;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_TETHER_BIT ) {
newTactical = AITACTICAL_MOVE_TETHER;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_HIDE_BIT ) {
newTactical = AITACTICAL_HIDE;
} else if ( combat.tacticalMaskUpdate & AITACTICAL_PASSIVE_BIT ) {
newTactical = AITACTICAL_PASSIVE;
}
// Remove the new tactical from the available in case we have to recursively call ourself
combat.tacticalMaskUpdate &= ~(1<<newTactical);
// Check the tactical state and see if we need to move or not
aiCTResult_t checkResult = CheckTactical ( newTactical );
// Skip the tactial state if necessary
if ( checkResult == AICTRESULT_SKIP ) {
return UpdateTactical_r ( );
}
// Check to see if we need to move for the new tactical tate
if ( checkResult == AICTRESULT_OK ) {
bool result = true;
switch ( newTactical ) {
case AITACTICAL_MOVE_TETHER:
result = MoveToTether ( tether );
break;
case AITACTICAL_MOVE_FOLLOW:
//FIXME: if leader is on an elevator, we need to find a position on the elevator...
result = MoveToEntity ( leader );
break;
case AITACTICAL_MOVE_PLAYERPUSH:
result = MoveOutOfRange ( pusher, (move.followRange[0] + move.followRange[1]) / 2.0f );
break;
case AITACTICAL_COVER:
case AITACTICAL_COVER_FLANK:
case AITACTICAL_COVER_ADVANCE:
case AITACTICAL_COVER_RETREAT:
case AITACTICAL_COVER_AMBUSH:
result = MoveToCover ( combat.attackRange[0], combat.attackRange[1], newTactical );
break;
case AITACTICAL_RANGED:
result = MoveToAttack ( enemy.ent, animator.GetAnim ( "ranged_attack" ) );
// Found nothing but still have a find going
if ( !result && aasFind ) {
// turn ranged back on for next update on next frame
combat.tacticalMaskUpdate |= AITACTICAL_RANGED_BIT;
return false;
}
break;
case AITACTICAL_MELEE:
result = MoveToEnemy ( );
break;
case AITACTICAL_PASSIVE:
case AITACTICAL_TURRET:
StopMove ( MOVE_STATUS_DONE );
break;
case AITACTICAL_HIDE:
if ( combat.tacticalMaskAvailable & (1<<AITACTICAL_COVER) ) {
result = MoveToCover ( combat.attackRange[1], 5000.0f, AITACTICAL_COVER );
} else {
result = false;
}
if ( !result ) {
result = MoveToHide ( );
}
if ( !result && enemy.ent ) {
result = MoveOutOfRange ( enemy.ent, combat.hideRange[1], combat.hideRange[0] );
}
break;
}
// Move impossible?
if ( !result ) {
return UpdateTactical_r ( );
}
// Look to see if we are already there
if ( ReachedPos ( move.moveDest, move.moveCommand, move.range ) ) {
StopMove ( MOVE_STATUS_DONE );
}
} else if ( newTactical == combat.tacticalCurrent ) {
// We are staying here, so update the time so the AI doesnt constantly keep checking
combat.tacticalUpdateTime = gameLocal.time;
combat.tacticalMaskUpdate = 0;
return false;
} else {
// Stop moving since we are changing state and dont need to move
StopMove ( MOVE_STATUS_DONE );
}
aiTactical_t oldTactical = combat.tacticalCurrent;
// If we had any saved aas find we dont need it anymore
delete aasFind;
aasFind = NULL;
// Set tactical update time to current time. This time is used to delay tactical updates
// in a generic fashion.
combat.tacticalUpdateTime = gameLocal.time;
combat.tacticalCurrent = newTactical;
// Allow handling of tactical changes
if ( newTactical != oldTactical ) {
OnTacticalChange ( newTactical );
}
// Reset tactical counters since we are probably going somewhere else
combat.tacticalPainTaken = 0;
combat.tacticalFlinches = 0;
combat.tacticalMaskUpdate = 0;
// Start the legs and head in the idle position
if ( legsAnim.Disabled ( ) ) {
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
}
SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
if ( head ) {
SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 );
}
// Go to the new state
PostState ( tacticalState [ newTactical ] );
return true;
}
/*
================
idAI::FilterTactical
Filters out tactical types from the given available list.
================
*/
int idAI::FilterTactical ( int availableTactical ) {
// No passive if we are agressive
if ( enemy.ent || combat.fl.aware ) {
availableTactical &= ~(AITACTICAL_PASSIVE_BIT);
}
// If we cant move then there are not many options
if ( move.moveType == MOVETYPE_STATIC || move.fl.disabled ) {
availableTactical &= AITACTICAL_NONMOVING_BITS;
return availableTactical;
}
// Only need push player
if ( aifl.scripted ) {
availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT);
} else {
if ( !pusher || !pusher->IsType ( idPlayer::GetClassType ( ) ) ) {
//no current pusher
if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) {
//in the middle of a push move, just continue it
availableTactical = AITACTICAL_NONE_BIT;
return availableTactical;
}
//stop pushing away
availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT);
} else {
//have a pusher
if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) {
//in the middle of a push move, only allow it to continue playerpush, but can re-calc, if needbe
availableTactical = AITACTICAL_MOVE_PLAYERPUSH_BIT;
return availableTactical;
}
if ( ai_playerPushAlways.GetBool() ) {
switch ( combat.tacticalCurrent ) {
case AITACTICAL_MELEE:
case AITACTICAL_MOVE_FOLLOW:
case AITACTICAL_MOVE_TETHER:
case AITACTICAL_MOVE_PLAYERPUSH:
case AITACTICAL_COVER:
case AITACTICAL_COVER_FLANK:
case AITACTICAL_COVER_ADVANCE:
case AITACTICAL_COVER_RETREAT:
case AITACTICAL_COVER_AMBUSH:
case AITACTICAL_RANGED:
case AITACTICAL_HIDE:
//okay to be pushed by players
break;
default:
if ( !leader ) {
//no leader and not in a moving tactical state, don't be pushed by players
availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT);
}
break;
}
} else if ( !leader || (move.fl.moving&&move.goalEntity!=leader) ) {
availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT);
}
}
}
// No tether move if not tethered
if ( !IsTethered ( ) ) {
availableTactical &= ~(AITACTICAL_MOVE_TETHER_BIT);
// Filter out any tactical states that require a leader
if ( !leader || (enemy.ent && IsEnemyVisible() && enemy.range < move.followRange[1] * 2.0f ) ) {
availableTactical &= ~(AITACTICAL_MOVE_FOLLOW_BIT);
} else if ( leader && !enemy.ent ) {
availableTactical &= ~(AITACTICAL_COVER_BITS);
}
// When tethered disable actions that dont adhear to the tether.
} else {
availableTactical &= ~(AITACTICAL_MELEE_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_HIDE_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT);
}
// If we dont have an enemy we can filter out pure combat states
if ( !enemy.ent ) {
availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_MELEE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT);
// If we arent aware either then we cant take cover
if ( !combat.fl.aware ) {
availableTactical &= ~(AITACTICAL_COVER_BITS);
}
} else {
// Dont advance or flank our enemy if we have seen them recently
if ( IsEnemyRecentlyVisible ( 0.5f ) ) {
availableTactical &= ~(AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT);
}
// FIXME: need conditions for these cover states
availableTactical &= ~(AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_COVER_RETREAT_BIT);
}
// Filter out all cover states if cover is disabled
if ( ai_disableCover.GetBool ( ) ) {
availableTactical &= ~(AITACTICAL_COVER_BITS);
}
//if we need to fight in melee then fight in melee!
if( IsMeleeNeeded( ) ) {
availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT);
}
return availableTactical;
}
/*
================
idAI::CheckTactical
Returns 'AICHECKTACTICAL_MOVE' if we should use the given tactical state and execute a movement to do so
Returns 'AICHECKTACTICAL_SKIP' if the given tactical state should be skipped
Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but no movement is required
================
*/
aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) {
// Handle non movement tactical states first
if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) {
// If we are moving
if ( move.fl.moving ) {
return AICTRESULT_OK;
}
return AICTRESULT_NOMOVE;
}
// If we are tethered always check that first
if ( IsTethered ( ) ) {
if ( !move.fl.moving || tactical != combat.tacticalCurrent ) {
// We stopped out of tether range so try a new move to get back in
if ( !tether->ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) {
return AICTRESULT_OK;
}
} else {
// Move is out of tether range, so look for another one
if ( !tether->ValidateDestination ( this, move.moveDest ) ) {
return AICTRESULT_OK;
}
}
}
// Anything wrong with our current destination, if so pick a new move
if ( SkipCurrentDestination ( ) ) {
return AICTRESULT_OK;
}
switch ( tactical ) {
case AITACTICAL_MOVE_FOLLOW:
// If already moving to our leader dont move again
if ( move.fl.moving && move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == leader ) {
return AICTRESULT_NOMOVE;
}
// If not moving and within follow range we can skip the follow state
if ( !move.fl.moving ) {
if ( DistanceTo ( leader ) < move.followRange[1] ) {
//unless the leader is on an elevator we should be standing on...
/*
idEntity* leaderGroundElevator = leader->GetGroundElevator();
if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) {
return AICTRESULT_OK;
}
*/
return AICTRESULT_SKIP;
}
}
return AICTRESULT_OK;
case AITACTICAL_MOVE_TETHER:
// If not moving we dont want to idle in the move state so skip it
if ( !move.fl.moving ) {
return AICTRESULT_SKIP;
}
// We are still heading towards our tether so dont reissue a move
return AICTRESULT_NOMOVE;
case AITACTICAL_RANGED:
// Currently issuing a find
if ( dynamic_cast<rvAASFindGoalForAttack*>(aasFind) ) {
return AICTRESULT_OK;
}
// If set to zero a tactical update was forced so lets force a move too
if ( !combat.tacticalUpdateTime ) {
return AICTRESULT_OK;
}
// If the enemy is visible then no movement is required for ranged combat
if ( !IsEnemyRecentlyVisible ( ) ) {
return AICTRESULT_OK;
}
if ( !move.fl.moving ) {
// If not within the attack range we need to move
if ( enemy.range > combat.attackRange[1] || enemy.range < combat.attackRange[0] ) {
return AICTRESULT_OK;
}
// If we havent seen our enemy for a while and arent moving, then get moving!
if ( enemy.lastVisibleTime && gameLocal.time - enemy.lastVisibleTime > RANGED_ENEMYDELAY ) {
return AICTRESULT_OK;
}
} else {
// Is our destination out of range?
if ( (move.moveDest - enemy.lastKnownPosition).LengthSqr ( ) > Square ( combat.attackRange[1] ) ) {
return AICTRESULT_OK;
}
}
// Our position is fine so just stay in ranged combat
return AICTRESULT_NOMOVE;
case AITACTICAL_MELEE:
// IF we are already moving towards our enemy then we dont need a state change
if ( move.fl.moving && move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) {
return AICTRESULT_NOMOVE;
}
return AICTRESULT_OK;
case AITACTICAL_COVER:
case AITACTICAL_COVER_FLANK:
case AITACTICAL_COVER_ADVANCE:
case AITACTICAL_COVER_RETREAT:
case AITACTICAL_COVER_AMBUSH:
// If set to zero a tactical update was forced so lets force a move too
if ( !combat.tacticalUpdateTime ) {
return AICTRESULT_OK;
}
if ( enemy.ent && !IsEnemyRecentlyVisible ( ) ) {
return AICTRESULT_OK;
}
// No longer behind cover?
if ( !IsBehindCover ( ) ) {
return AICTRESULT_OK;
}
// Move if there have been too many close calls
if ( combat.tacticalFlinches && combat.tacticalFlinches > gameLocal.random.RandomInt(12) + 3 ) {
return AICTRESULT_OK;
}
// Move if cover destination isnt valid anymore
if ( !IsCoverValid() ) {
return AICTRESULT_OK;
}
return AICTRESULT_NOMOVE;
}
return AICTRESULT_OK;
}
/*
================
idAI::WakeUpTargets
================
*/
void idAI::WakeUpTargets ( void ) {
const idKeyValue* kv;
for ( kv = spawnArgs.MatchPrefix ( "wakeup_target" ); kv; kv = spawnArgs.MatchPrefix ( "wakeup_target", kv ) ) {
idEntity* ent;
ent = gameLocal.FindEntity ( kv->GetValue ( ) );
if ( !ent ) {
gameLocal.Warning ( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), GetName() );
} else {
ent->Signal( SIG_TRIGGER );
ent->PostEventMS( &EV_Activate, 0, this );
ent->TriggerGuis ( );
}
}
// Find all the tether entities we target
const char* target;
if ( spawnArgs.GetString ( "tether_target", "", &target ) && *target ) {
idEntity* ent;
ent = gameLocal.FindEntity ( target );
if ( ent && ent->IsType ( rvAITether::GetClassType ( ) ) ) {
ProcessEvent ( &EV_Activate, ent );
}
}
}
/*
===============================================================================
Wait States
===============================================================================
*/
/*
================
idAI::State_Wait_Activated
Stop the state thread until the ai is activated
================
*/
stateResult_t idAI::State_Wait_Activated ( const stateParms_t& parms ) {
if ( (aifl.activated || aifl.pain ) && CanBecomeSolid ( ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_Wait_Action
Stop the state thread as long as a torso action is running and allow a pain action to interrupt
================
*/
stateResult_t idAI::State_Wait_Action ( const stateParms_t& parms ) {
if ( CheckPainActions ( ) ) {
// Our current action is being interrupted by pain so make sure the action can be performed
// immediately after coming out of the pain.
actionTime = actionSkipTime;
return SRESULT_DONE;
}
if ( !aifl.action ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_Wait_ActionNoPain
Stop the state thread as long as a torso action is running
================
*/
stateResult_t idAI::State_Wait_ActionNoPain ( const stateParms_t& parms ) {
if ( !aifl.action ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
idAI::State_Wait_ScriptedDone
Stop the state thread as a scripted sequence is active
================
*/
stateResult_t idAI::State_Wait_ScriptedDone ( const stateParms_t& parms ) {
if ( aifl.scripted ) {
return SRESULT_WAIT;
}
return SRESULT_DONE;
}