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

1054 lines
No EOL
28 KiB
C++

//
// TODO:
// - unarmed posture
// - relaxed posture
// - turning in place too much, rely more on look angles?
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "AI_Manager.h"
#include "AI_Util.h"
#include "AI_Tactical.h"
const idEventDef AI_ForcePosture ( "forcePosture", "d" );
CLASS_DECLARATION( idAI, rvAITactical )
EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture )
EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn )
END_CLASS
static const char* aiPostureString[AIPOSTURE_MAX] = {
"stand", // AIPOSTURE_STAND,
"crouch", // AIPOSTURE_CROUCH,
"cover_left", // AIPOSTURE_STAND_COVER_LEFT,
"cover_right", // AIPOSTURE_STAND_COVER_RIGHT,
"crouch_cover", // AIPOSTURE_CROUCH_COVER
"crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT,
"crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT,
"relaxed", // AIPOSTURE_RELAXED
"unarmed", // AIPOSTURE_UNARMED
"at_attention", // AIPOSTURE_AT_ATTENTION
};
/*
================
rvAITactical::rvAITactical
================
*/
rvAITactical::rvAITactical ( void ) {
shots = 0;
nextWallTraceTime = 0;
}
void rvAITactical::InitSpawnArgsVariables ( void )
{
// Initialize the posture info
InitPostureInfo ( );
maxShots = spawnArgs.GetInt ( "maxShots", "1" );
minShots = spawnArgs.GetInt ( "minShots", "1" );
fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) );
healthRegen = spawnArgs.GetInt( "healthRegen", "2" );
healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" );
}
/*
================
rvAITactical::Spawn
================
*/
void rvAITactical::Spawn ( void ) {
InitSpawnArgsVariables();
// Force a posture?
const char* temp;
postureForce = AIPOSTURE_DEFAULT;
if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) {
for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) {
if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) {
break;
}
}
if ( postureForce >= AIPOSTURE_MAX ) {
postureForce = AIPOSTURE_DEFAULT;
}
}
UpdatePosture ( );
postureCurrent = postureIdeal;
OnPostureChange ( );
ammo = spawnArgs.GetInt ( "ammo", "-1" );
// Initialize custom actions
actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK );
actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK );
actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" );
// playerFocusTime = 0;
// playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" ));
healthRegenNextTime = 0;
maxHealth = health;
}
/*
================
rvAITactical::Think
================
*/
void rvAITactical::Think ( void ) {
idAI::Think ( );
// If not simple thinking and not in an action, update the posture
if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) {
if ( UpdatePosture ( ) ) {
PerformAction ( "Torso_SetPosture", 4, true );
}
}
// FIXME: disabled for now, its annoying people
/*
idPlayer* localPlayer;
localPlayer = gameLocal.GetLocalPlayer();
// If the player has been standing in front of the marine and looking at him for too long he should say something
if ( !aifl.dead && playerFocusTime && playerAnnoyTime
&& !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking()
&& !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now
&& DistanceTo( localPlayer ) < 64.0f ) {
idVec3 diff;
diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin();
diff.NormalizeFast();
// Is the player looking at the marine?
if ( diff * localPlayer->viewAxis[0] > 0.7f ) {
// Say something every 5 seconds
if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) {
// Debounce it against other marines
if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) {
Speak ( "lipsync_canihelpyou", true );
aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 );
}
}
} else {
playerFocusTime = gameLocal.time;
}
} else {
playerFocusTime = gameLocal.time;
}
*/
if ( health > 0 ) {
//alive
if ( healthRegenEnabled && healthRegen ) {
if ( gameLocal.GetTime() >= healthRegenNextTime ) {
health = idMath::ClampInt( 0, maxHealth, health+healthRegen );
healthRegenNextTime = gameLocal.GetTime() + 1000;
}
}
}
//crappy place to do this, just testing
bool clearPrefix = true;
bool facingWall = false;
if ( move.fl.moving && InCoverMode() && combat.fl.aware ) {
clearPrefix = false;
if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) {
facingWall = true;
} else if ( nextWallTraceTime < gameLocal.GetTime() ) {
//do an occasional check for solid architecture directly in front of us
nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750;
trace_t wallTrace;
idVec3 start, end;
idMat3 axis;
if ( neckJoint != INVALID_JOINT ) {
GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis );
end = start + axis[0] * 32.0f;
} else {
start = GetEyePosition();
start += viewAxis[0] * 8.0f;//still inside bbox
end = start + viewAxis[0] * 32.0f;
}
//trace against solid arcitecture only, don't care about other entities
gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this );
if ( wallTrace.fraction < 1.0f ) {
facingWall = true;
} else {
clearPrefix = true;
}
}
}
if ( facingWall ) {
if ( !animPrefix.Length() ) {
animPrefix = "nearcover";
}
} else if ( clearPrefix && animPrefix == "nearcover" ) {
animPrefix = "";
}
}
/*
================
rvAITactical::Save
================
*/
void rvAITactical::Save( idSaveGame *savefile ) const {
savefile->WriteSyncId();
savefile->WriteInt ( ammo );
savefile->WriteInt ( shots );
// savefile->WriteInt ( playerFocusTime );
// savefile->WriteInt ( playerAnnoyTime );
savefile->WriteInt ( (int&)postureIdeal );
savefile->WriteInt ( (int&)postureCurrent );
savefile->WriteInt ( (int&)postureForce );
// TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson:
savefile->WriteInt ( healthRegenNextTime );
savefile->WriteInt ( maxHealth );
savefile->WriteInt ( nextWallTraceTime );
actionElbowAttack.Save ( savefile );
actionKillswitchAttack.Save ( savefile );
actionTimerPeek.Save ( savefile );
}
/*
================
rvAITactical::Restore
================
*/
void rvAITactical::Restore( idRestoreGame *savefile ) {
InitSpawnArgsVariables ( );
savefile->ReadSyncId( "rvAITactical" );
savefile->ReadInt ( ammo );
savefile->ReadInt ( shots );
// savefile->ReadInt ( playerFocusTime );
// savefile->ReadInt ( playerAnnoyTime );
savefile->ReadInt ( (int&)postureIdeal );
savefile->ReadInt ( (int&)postureCurrent );
savefile->ReadInt ( (int&)postureForce );
// TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX];
savefile->ReadInt ( healthRegenNextTime );
savefile->ReadInt ( maxHealth );
savefile->ReadInt ( nextWallTraceTime );
actionElbowAttack.Restore ( savefile );
actionKillswitchAttack.Restore ( savefile );
actionTimerPeek.Restore ( savefile );
UpdateAnimPrefix ( );
}
/*
================
rvAITactical::CanTurn
================
*/
bool rvAITactical::CanTurn ( void ) const {
if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) {
return false;
}
return idAI::CanTurn ( );
}
/*
================
rvAITactical::CanMove
================
*/
bool rvAITactical::CanMove ( void ) const {
if ( !postureInfo[postureCurrent].fl.canMove ) {
return false;
}
return idAI::CanMove ( );
}
/*
================
rvAITactical::CheckAction_Reload
================
*/
bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) {
if ( ammo == 0 ) {
return true;
}
return false;
}
/*
================
rvAITactical::CheckActions
================
*/
bool rvAITactical::CheckActions ( void ) {
// Pain?
if ( CheckPainActions ( ) ) {
return true;
}
// If we are pressed, fight-- do not break melee combat until you or the enemy is dead.
if ( IsMeleeNeeded ( )) {
if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ||
PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) {
return true;
}
//take no actions other than fighting
return false;
}
// Handle any posture changes
if ( postureIdeal != postureCurrent ) {
PerformAction ( "Torso_SetPosture", 4, true );
return true;
}
// Reload takes precedence
if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) {
PerformAction ( "Torso_Reload", 4, false );
return true;
}
if ( IsBehindCover ( ) ) {
// If we have no enemy try peeking
if ( !IsEnemyRecentlyVisible ( ) ) {
if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) {
if ( actionTimerPeek.IsDone ( actionTime ) ) {
actionTimerPeek.Reset ( actionTime, 0.5f );
aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 );
PerformAction ( "Torso_Cover_Peek", 4, false );
return true;
}
}
}
// Attacks from cover
if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) {
// Kill switch attack from cover?
if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) {
if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) {
return true;
}
}
if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
}
return false;
}
// Standard attacks
if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ||
PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) {
return true;
}
// Ranged attack only if there is ammo
if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) {
if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
}
return false;
}
/*
================
rvAITactical::CheckRelaxed
Returns true if the marine should currently be in a relaxed state
================
*/
bool rvAITactical::CheckRelaxed ( void ) const {
// if ( forceRelaxed ) {
// return true;
// }
/*
// If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed
if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) {
return true;
}
*/
// Alwasy relaxed when ignoring enemies
if ( !combat.fl.aware ) {
return true;
}
if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) {
return false;
}
if ( gameLocal.time >= focusTime ) {
return false;
}
return true;
}
/*
================
rvAITactical::GetIdleAnimName
================
*/
const char* rvAITactical::GetIdleAnimName ( void ) {
return "idle";
}
/*
================
rvAITactical::UpdateAnimPrefix
================
*/
void rvAITactical::UpdateAnimPrefix ( void ) {
if ( postureCurrent == AIPOSTURE_STAND ) {
animPrefix = "";
} else {
animPrefix = aiPostureString[postureCurrent];
}
}
/*
================
rvAITactical::InitPostureInfo
================
*/
void rvAITactical::InitPostureInfo ( void ) {
int posture;
for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) {
aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture];
postureCurrent = (aiPosture_t)posture;
UpdateAnimPrefix ( );
info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true );
info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true );
info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true );
info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true );
info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true );
info.fl.canTurn = false;
}
// FIXME: this should be based on the availablity of turn anims
postureInfo[AIPOSTURE_STAND].fl.canTurn = true;
postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true;
postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true;
}
/*
================
rvAITactical::UpdatePosture
================
*/
bool rvAITactical::UpdatePosture ( void ) {
// If the posture is being forced then use that until its no longer forced
if ( postureForce != AIPOSTURE_DEFAULT ) {
postureIdeal = postureForce;
// Not forcing posture, determine it from our current state
} else {
postureIdeal = AIPOSTURE_STAND;
// Behind cover?
if ( IsBehindCover ( ) ) {
bool left;
if ( enemy.ent ) {
left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f;
} else if ( tether ) {
left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f;
} else {
left = false;
}
// Should be crouching behind cover?
if ( InCrouchCoverMode ( ) ) {
if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) {
postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT;
} else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) {
postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT;
} else {
postureIdeal = AIPOSTURE_CROUCH_COVER;
}
} else {
if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) {
postureIdeal = AIPOSTURE_STAND_COVER_LEFT;
} else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) {
postureIdeal = AIPOSTURE_STAND_COVER_RIGHT;
} else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) {
postureIdeal = AIPOSTURE_STAND_COVER_LEFT;
} else {
postureIdeal = AIPOSTURE_STAND_COVER_RIGHT;
}
}
} else if ( combat.fl.aware //aggressive
&& (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction
&& ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here
//we crouch only if leader is
postureIdeal = AIPOSTURE_CROUCH;
} else if ( CheckRelaxed ( ) ) {
postureIdeal = AIPOSTURE_RELAXED;
}
//never crouch in melee!
if( IsMeleeNeeded() ) {
postureIdeal = AIPOSTURE_STAND;
}
}
// Default the posture if trying to move with one that doesnt support it
if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) {
postureIdeal = AIPOSTURE_STAND;
// Default the posture if trying to turn and we cant in the posture we chose
} else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) {
postureIdeal = AIPOSTURE_STAND;
}
return (postureIdeal != postureCurrent);
}
/*
================
rvAITactical::OnPostureChange
================
*/
void rvAITactical::OnPostureChange ( void ) {
UpdateAnimPrefix ( );
}
/*
============
rvAITactical::OnSetKey
============
*/
void rvAITactical::OnSetKey ( const char* key, const char* value ) {
idAI::OnSetKey ( key, value );
/*
if ( !idStr::Icmp ( key, "annoyed" ) ) {
playerAnnoyTime = SEC2MS( atof ( value ) );
}
*/
}
/*
================
rvAITactical::OnStopMoving
================
*/
void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) {
// Ensure the peek doesnt happen immedately every time we stop at a cover
if ( IsBehindCover ( ) ){
actionTimerPeek.Clear ( actionTime );
actionTimerPeek.Add ( 2000, 0.5f );
actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity );
// We should be looking fairly close to the right direction, so just snap it
TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f );
move.current_yaw = move.ideal_yaw;
}
idAI::OnStopMoving ( oldMoveCommand );
}
/*
================
rvAITactical::CalculateShots
================
*/
void rvAITactical::CalculateShots ( const char* fireAnim ) {
// Random number of shots ( scale by aggression range)
shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale;
if ( shots > ammo ) {
shots = ammo;
}
// Update the firing animation playback rate
int animNum;
animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim );
if ( animNum != 0 ) {
const idAnim* anim = GetAnimator()->GetAnim ( animNum );
if ( anim ) {
GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate );
}
}
}
/*
================
rvAITactical::UseAmmo
================
*/
void rvAITactical::UseAmmo ( int amount ) {
if ( ammo <= 0 ) {
return;
}
shots--;
ammo-=amount;
if ( ammo < 0 ) {
ammo = 0;
shots = 0;
}
}
/*
================
rvAITactical::GetDebugInfo
================
*/
void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) {
// Base class first
idAI::GetDebugInfo ( proc, userData );
proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData );
proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData );
proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData );
proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData );
proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData );
proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData );
proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData );
proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData );
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvAITactical )
STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack )
STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack )
STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack )
STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack )
STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack )
STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek )
STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload )
STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture )
STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek )
END_CLASS_STATES
/*
================
rvAITactical::State_Torso_SetPosture
================
*/
stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT_RELAXED,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT: {
idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] );
if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) {
postureCurrent = postureIdeal;
OnPostureChange ( );
return SRESULT_DONE;
}
if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT
|| postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT
|| (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) )
{
// FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION
move.fl.allowAnimMove = false;
} else {
//no need to play cover-to-stand/relaxed transition if:
//scripted...
//or we're moving already...
//or turning away from our old cover direction...
if ( aifl.scripted
/*|| (move.fl.moving&&!move.fl.blocked)
|| (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) {
postureCurrent = postureIdeal;
OnPostureChange ( );
return SRESULT_DONE;
}
}
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames );
if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT
&& postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT
&& postureIdeal == AIPOSTURE_RELAXED ) {
//we need to also play stand_to_relaxed at the end...
return SRESULT_STAGE ( STAGE_WAIT_RELAXED );
}
return SRESULT_STAGE ( STAGE_WAIT );
}
case STAGE_WAIT_RELAXED:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames );
}
return SRESULT_STAGE ( STAGE_WAIT );
}
return SRESULT_WAIT;
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
postureCurrent = postureIdeal;
OnPostureChange ( );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvAITactical::State_Torso_RangedAttack
================
*/
stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_START_WAIT,
STAGE_SHOOT,
STAGE_SHOOT_WAIT,
STAGE_END,
STAGE_END_WAIT,
};
switch ( parms.stage ) {
case STAGE_START:
// If moving switch to the moving ranged attack (torso only)
if ( move.fl.moving && FacingIdeal() ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames );
return SRESULT_DONE;
}
// Full body animations
DisableAnimState ( ANIMCHANNEL_LEGS );
CalculateShots ( "range_attack" );
// Attack lead in animation?
if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames );
return SRESULT_STAGE ( STAGE_START_WAIT );
}
return SRESULT_STAGE ( STAGE_SHOOT );
case STAGE_START_WAIT:
// When the pre shooting animation is done head over to shooting
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_STAGE ( STAGE_SHOOT );
}
return SRESULT_WAIT;
case STAGE_SHOOT:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 );
UseAmmo ( 1 );
return SRESULT_STAGE ( STAGE_SHOOT_WAIT );
case STAGE_SHOOT_WAIT:
// When the shoot animation is done either play another shot animation
// or finish up with post_shooting
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
// If our enemy is no longer in our fov we can stop shooting
if ( !enemy.fl.inFov ) {
return SRESULT_STAGE ( STAGE_END );
} else if ( enemy.fl.dead ) {
//if enemy is dead, stop shooting soon
if ( shots > 5 ) {
shots = gameLocal.random.RandomInt(6);
}
}
if ( shots <= 0 ) {
return SRESULT_STAGE ( STAGE_END );
}
return SRESULT_STAGE ( STAGE_SHOOT);
}
return SRESULT_WAIT;
case STAGE_END:
// Attack lead in animation?
if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames );
return SRESULT_STAGE ( STAGE_END_WAIT );
}
return SRESULT_DONE;
case STAGE_END_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvAITactical::State_Torso_MovingRangedAttack
================
*/
stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_SHOOT,
STAGE_SHOOT_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
CalculateShots ( "range_attack_torso" );
return SRESULT_STAGE ( STAGE_SHOOT );
case STAGE_SHOOT:
UseAmmo ( 1 );
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 );
return SRESULT_STAGE ( STAGE_SHOOT_WAIT );
case STAGE_SHOOT_WAIT:
// When the shoot animation is done either play another shot animation
// or finish up with post_shooting
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
if ( enemy.fl.dead ) {
//if enemy is dead, stop shooting soon
if ( shots > 5 ) {
shots = gameLocal.random.RandomInt(6);
}
}
if ( shots <= 0 || !enemy.fl.inFov ) {
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_SHOOT);
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvAITactical::State_Torso_Reload
================
*/
stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) {
ammo = spawnArgs.GetInt ( "ammo" );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvAITactical::State_Torso_Cover_LeanLeftAttack
================
*/
stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames );
return SRESULT_DONE;
}
/*
================
rvAITactical::State_Torso_Cover_LeanRightAttack
================
*/
stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames );
return SRESULT_DONE;
}
/*
================
rvAITactical::State_Torso_Cover_LeanAttack
================
*/
stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) {
enum {
STAGE_OUT,
STAGE_OUTWAIT,
STAGE_FIRE,
STAGE_FIREWAIT,
STAGE_IN,
STAGE_INWAIT,
};
switch ( parms.stage ) {
case STAGE_OUT:
DisableAnimState ( ANIMCHANNEL_LEGS );
// The lean out animation cannot blend with any other animations since
// it is essential that the movement delta out match the one back in. Therefore
// we force the legs and torso to be stoped before playing any animations
torsoAnim.StopAnim ( 0 );
legsAnim.StopAnim ( 0 );
PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 );
return SRESULT_STAGE ( STAGE_OUTWAIT );
case STAGE_OUTWAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
// Random number of shots
CalculateShots ( "lean_attack" );
return SRESULT_STAGE ( STAGE_FIRE );
}
return SRESULT_WAIT;
case STAGE_FIRE:
UseAmmo ( 1 );
PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 );
return SRESULT_STAGE ( STAGE_FIREWAIT );
case STAGE_FIREWAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
if ( enemy.fl.dead ) {
//if enemy is dead, stop shooting soon
if ( shots > 5 ) {
shots = gameLocal.random.RandomInt(6);
}
}
if ( shots > 0 ) {
return SRESULT_STAGE ( STAGE_FIRE );
}
return SRESULT_STAGE ( STAGE_IN );
}
return SRESULT_WAIT;
case STAGE_IN:
PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 );
return SRESULT_STAGE ( STAGE_INWAIT );
case STAGE_INWAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvAITactical::State_Torso_Cover_Peek
================
*/
stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", 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;
}
/*
================
rvAITactical::State_Frame_Peek
================
*/
stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) {
CheckForEnemy ( true, true );
return SRESULT_OK;
}
/*
================
rvAITactical::Event_ForcePosture
================
*/
void rvAITactical::Event_ForcePosture ( int posture ) {
postureForce = (aiPosture_t)posture;
}
/*
===================
rvAITactical::IsCrouching
===================
*/
bool rvAITactical::IsCrouching( void ) const {
if ( postureCurrent == AIPOSTURE_CROUCH
|| postureCurrent == AIPOSTURE_CROUCH_COVER
|| postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT
|| postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) {
return true;
}
return idAI::IsCrouching();
}
/*
================
rvAITactical::Event_PostSpawn
================
*/
void rvAITactical::Event_PostSpawn( void ) {
idAI::Event_PostSpawn();
if ( team == AITEAM_MARINE && healthRegenEnabled )
{//regen-enabled buddy marine
if ( CheckDeathCausesMissionFailure() )
{//who is important to a mission
if ( g_skill.GetInteger() > 2 )
{//on impossible
health *= 1.5f;
healthRegen *= 1.5f;
}
else if ( g_skill.GetInteger() > 1 )
{//on hard
health *= 1.2f;
healthRegen *= 1.25f;
}
}
}
}