1054 lines
No EOL
28 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|
|
} |