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

753 lines
20 KiB
C++

#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
//NOTE: actually a bit of a misnomer, as all Strogg Marine types use this class now...
class rvMonsterStroggMarine : public idAI {
public:
CLASS_PROTOTYPE( rvMonsterStroggMarine );
rvMonsterStroggMarine ( void );
void InitSpawnArgsVariables ( void );
void Spawn ( void );
void Save ( idSaveGame *savefile ) const;
void Restore ( idRestoreGame *savefile );
protected:
virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand );
virtual bool CheckActions ( void );
int maxShots;
int minShots;
int shots;
int shotsFired;
int fireAnimNum;
bool spraySideRight;
int sweepCount;
bool EnemyMovingToRight ( void );
private:
void CalculateShots ( void );
int nextShootTime;
int attackRate;
jointHandle_t attackJoint;
// Actions
rvAIAction actionStrafe;
rvAIAction actionCrouchRangedAttack;
rvAIAction actionRollAttack;
rvAIAction actionSprayAttack;
rvAIAction actionAngry;
rvAIAction actionReload;
virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum );
virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum );
virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum );
bool CheckAction_Strafe ( rvAIAction* action, int animNum );
virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum );
bool CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum );
bool CheckAction_RollAttack ( rvAIAction* action, int animNum );
bool CheckAction_SprayAttack ( rvAIAction* action, int animNum );
bool CheckAction_Angry ( rvAIAction* action, int animNum );
bool CheckAction_Reload ( rvAIAction* action, int animNum );
stateResult_t State_Torso_RollAttack ( const stateParms_t& parms );
stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms );
stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms );
stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms );
CLASS_STATES_PROTOTYPE ( rvMonsterStroggMarine );
};
CLASS_DECLARATION( idAI, rvMonsterStroggMarine )
END_CLASS
/*
================
rvMonsterStroggMarine::rvMonsterStroggMarine
================
*/
rvMonsterStroggMarine::rvMonsterStroggMarine ( ) {
nextShootTime = 0;
}
void rvMonsterStroggMarine::InitSpawnArgsVariables( void )
{
maxShots = spawnArgs.GetInt ( "maxShots", "1" );
minShots = spawnArgs.GetInt ( "minShots", "1" );
attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.2" ) );
attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) );
}
/*
================
rvMonsterStroggMarine::Spawn
================
*/
void rvMonsterStroggMarine::Spawn ( void ) {
actionStrafe.Init ( spawnArgs, "action_strafe", NULL, 0 );
actionCrouchRangedAttack.Init ( spawnArgs, "action_crouchRangedAttack", NULL, AIACTIONF_ATTACK );
actionRollAttack.Init ( spawnArgs, "action_rollAttack", NULL, AIACTIONF_ATTACK );
actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK );
actionAngry.Init ( spawnArgs, "action_angry", NULL, 0 );
actionReload.Init ( spawnArgs, "action_reload", NULL, 0 );
InitSpawnArgsVariables();
shots = 0;
shotsFired = 0;
}
/*
================
rvMonsterStroggMarine::Save
================
*/
void rvMonsterStroggMarine::Save ( idSaveGame *savefile ) const {
actionStrafe.Save ( savefile );
actionCrouchRangedAttack.Save( savefile );
actionRollAttack.Save( savefile );
actionSprayAttack.Save( savefile );
actionAngry.Save( savefile );
actionReload.Save( savefile );
savefile->WriteInt ( shots );
savefile->WriteInt ( shotsFired );
savefile->WriteInt ( fireAnimNum );
savefile->WriteBool ( spraySideRight );
savefile->WriteInt ( sweepCount );
savefile->WriteInt ( nextShootTime );
}
/*
================
rvMonsterStroggMarine::Restore
================
*/
void rvMonsterStroggMarine::Restore ( idRestoreGame *savefile ) {
actionStrafe.Restore ( savefile );
actionCrouchRangedAttack.Restore( savefile );
actionRollAttack.Restore( savefile );
actionSprayAttack.Restore( savefile );
actionAngry.Restore( savefile );
actionReload.Restore( savefile );
savefile->ReadInt ( shots );
savefile->ReadInt ( shotsFired );
savefile->ReadInt ( fireAnimNum );
savefile->ReadBool ( spraySideRight );
savefile->ReadInt ( sweepCount );
savefile->ReadInt ( nextShootTime );
InitSpawnArgsVariables();
}
/*
============
rvMonsterStroggMarine::OnStopMoving
============
*/
void rvMonsterStroggMarine::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) {
//MCG - once you get to your position, attack immediately (no pause)
//FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states?
if ( GetEnemy() )
{
if ( combat.tacticalCurrent == AITACTICAL_HIDE )
{
}
else if ( combat.tacticalCurrent == AITACTICAL_MELEE )
{
actionMeleeAttack.timer.Clear( actionTime );
}
else
{
actionRangedAttack.timer.Clear( actionTime );
actionTimerRangedAttack.Clear( actionTime );
actionCrouchRangedAttack.timer.Clear( actionTime );
actionRollAttack.timer.Clear( actionTime );
actionSprayAttack.timer.Clear( actionTime );
}
}
}
/*
================
rvMonsterStroggMarine::CheckAction_JumpBack
================
*/
bool rvMonsterStroggMarine::CheckAction_JumpBack ( rvAIAction* action, int animNum ) {
// Jump back after taking damage
if ( !aifl.damage && gameLocal.time - pain.lastTakenTime > 1500 ) {
return false;
}
// enemy must be in front to jump backwards
if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) {
return false;
}
// Can we actually move backwards?
if ( !TestAnimMove ( animNum ) ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_EvadeLeft
================
*/
bool rvMonsterStroggMarine::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) {
if ( gameLocal.time - pain.lastTakenTime > 1500 ) {
if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) {
return false;
}
}
// TODO: dont evade unless it was coming from directly in front of us
if ( !TestAnimMove ( animNum ) ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_EvadeRight
================
*/
bool rvMonsterStroggMarine::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) {
if ( gameLocal.time - pain.lastTakenTime > 1500 ) {
if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){
return false;
}
}
// TODO: Dont eveade unless it was coming from directly in front of us
if ( !TestAnimMove ( animNum ) ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_Strafe
================
*/
bool rvMonsterStroggMarine::CheckAction_Strafe ( rvAIAction* action, int animNum ) {
if ( !enemy.fl.visible ) {
return false;
}
if ( !enemy.fl.inFov ) {
return false;
}
if ( !move.fl.done ) {
return false;
}
if ( !TestAnimMove ( animNum ) ) {
//well, at least try a new attack position
if ( combat.tacticalCurrent == AITACTICAL_RANGED ) {
combat.tacticalUpdateTime = 0;
}
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_RangedAttack
================
*/
bool rvMonsterStroggMarine::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) {
if ( !enemy.ent || !enemy.fl.inFov ) {
return false;
}
if ( spawnArgs.GetBool( "rangeAttackChanceInverse" )
&& enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) {
//the father away you are, the more likely you are to not attack
return false;
}
if ( spawnArgs.GetBool( "rangeAttackChance" )
&& enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) {
//the closer you are, the more likely you are to not attack
return false;
}
return idAI::CheckAction_RangedAttack( action, animNum );
}
/*
================
rvMonsterStroggMarine::CheckAction_CrouchRangedAttack
================
*/
bool rvMonsterStroggMarine::CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum )
{
if ( !enemy.ent || !enemy.fl.inFov ) {
return false;
}
if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) {
return false;
}
if ( spawnArgs.GetBool( "rangeAttackChanceInverse" )
&& enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) {
//the father away you are, the more likely you are to not attack
return false;
}
if ( spawnArgs.GetBool( "rangeAttackChance" )
&& enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) {
//the closer you are, the more likely you are to not attack
return false;
}
if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_RollAttack
================
*/
bool rvMonsterStroggMarine::CheckAction_RollAttack ( rvAIAction* action, int animNum )
{
if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) {
return false;
}
if ( !TestAnimMove ( animNum ) ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_SprayAttack
================
*/
bool rvMonsterStroggMarine::CheckAction_SprayAttack ( rvAIAction* action, int animNum )
{
if ( !enemy.ent || !enemy.fl.inFov ) {
return false;
}
if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) {
return false;
}
if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) )
{//not moving
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_Angry
================
*/
bool rvMonsterStroggMarine::CheckAction_Angry ( rvAIAction* action, int animNum )
{
if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckAction_Reload
================
*/
bool rvMonsterStroggMarine::CheckAction_Reload ( rvAIAction* action, int animNum ) {
if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggMarine::CheckActions
================
*/
bool rvMonsterStroggMarine::CheckActions ( void ) {
if ( idAI::CheckActions ( ) )
{
return true;
}
if ( PerformAction ( &actionCrouchRangedAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_CrouchRangedAttack, &actionTimerRangedAttack ) ||
PerformAction ( &actionRollAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_RollAttack, &actionTimerRangedAttack ) ||
PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_SprayAttack, &actionTimerRangedAttack ) ||
PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Strafe ) ||
PerformAction ( &actionAngry, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Angry ) ||
PerformAction ( &actionReload, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Reload ) ) {
return true;
}
return false;
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvMonsterStroggMarine )
STATE ( "Torso_RollAttack", rvMonsterStroggMarine::State_Torso_RollAttack )
STATE ( "Torso_RangedAttack", rvMonsterStroggMarine::State_Torso_RangedAttack )
STATE ( "Torso_MovingRangedAttack", rvMonsterStroggMarine::State_Torso_MovingRangedAttack )
STATE ( "Torso_SprayAttack", rvMonsterStroggMarine::State_Torso_SprayAttack )
END_CLASS_STATES
/*
================
rvMonsterStroggMarine::State_Torso_RollAttack
================
*/
stateResult_t rvMonsterStroggMarine::State_Torso_RollAttack ( const stateParms_t& parms ) {
enum {
TORSO_ROLLATTACK_ROLL,
TORSO_ROLLATTACK_FACE,
TORSO_ROLLATTACK_FIRE,
TORSO_ROLLATTACK_FINISH
};
TurnToward(enemy.lastKnownPosition);
switch ( parms.stage ) {
// Start the roll attack animation
case TORSO_ROLLATTACK_ROLL:
// Full body animations
DisableAnimState ( ANIMCHANNEL_LEGS );
// Play the roll
PlayAnim ( ANIMCHANNEL_TORSO, "dive_turn", parms.blendFrames );
move.fl.noTurn = false;
//FaceEnemy();
return SRESULT_STAGE ( TORSO_ROLLATTACK_FACE );
// Wait for roll animation to finish
case TORSO_ROLLATTACK_FACE:
if ( AnimDone ( ANIMCHANNEL_LEGS, 6 ) ) {
return SRESULT_STAGE ( TORSO_ROLLATTACK_FIRE );
}
return SRESULT_WAIT;
// Play fire animation
case TORSO_ROLLATTACK_FIRE:
if ( !enemy.ent || !enemy.fl.visible )
{//whoops! rolled out of LOS
return SRESULT_DONE;
}
if ( enemy.fl.inFov )
{
PlayAnim ( ANIMCHANNEL_TORSO, "shotgun_range_attack", parms.blendFrames );
return SRESULT_STAGE ( TORSO_ROLLATTACK_FINISH );
}
return SRESULT_WAIT;
// Wait for fire animation to finish
case TORSO_ROLLATTACK_FINISH:
if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterStroggMarine::CalculateShots
================
*/
void rvMonsterStroggMarine::CalculateShots ( void ) {
// Random number of shots ( scale by aggression range)
shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale;
// 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 );
}
}
*/
}
/*
================
rvMonsterStroggMarine::State_Torso_RangedAttack
================
*/
stateResult_t rvMonsterStroggMarine::State_Torso_RangedAttack ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_START_WAIT,
STAGE_SHOOT,
STAGE_SHOOT_WAIT,
STAGE_END,
STAGE_END_WAIT,
};
//TurnToward(enemy.lastKnownPosition);
switch ( parms.stage ) {
case STAGE_START:
// If moving switch to the moving ranged attack (torso only)
if ( move.fl.moving && move.fl.running && !actionRangedAttack.fl.overrideLegs && FacingIdeal() ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames );
return SRESULT_DONE;
}
// Full body animations
DisableAnimState ( ANIMCHANNEL_LEGS );
fireAnimNum = gameLocal.random.RandomInt(2)+1;
CalculateShots();
shotsFired = 0;
// Attack lead in animation?
if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), true ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), 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, va("range_attack%d_loop", fireAnimNum), 0 );
shots--;
shotsFired++;
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 ( shots <= 0 ) {
return SRESULT_STAGE ( STAGE_END );
}
// If our enemy is no longer in our fov we can stop shooting
if ( !enemy.fl.inFov && shotsFired >= minShots ) {
return SRESULT_STAGE ( STAGE_END );
}
return SRESULT_STAGE ( STAGE_SHOOT);
}
return SRESULT_WAIT;
case STAGE_END:
// Attack lead in animation?
if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), true ) ) {
PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), 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;
}
/*
================
rvMonsterStroggMarine::State_Torso_MovingRangedAttack
================
*/
stateResult_t rvMonsterStroggMarine::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_SHOOT,
STAGE_SHOOT_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
CalculateShots();
shotsFired = 0;
return SRESULT_STAGE ( STAGE_SHOOT );
case STAGE_SHOOT:
shots--;
shotsFired++;
nextShootTime = gameLocal.GetTime() + attackRate;
if ( attackJoint != INVALID_JOINT ) {
Attack( "base", attackJoint, GetEnemy() );
PlayEffect( "fx_blaster_muzzleflash", attackJoint );
}
StartSound( "snd_weapon_fire", SND_CHANNEL_WEAPON, 0, false, 0 );
/*
switch ( move.currentDirection )
{
case MOVEDIR_RIGHT:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 );
break;
case MOVEDIR_LEFT:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 );
break;
case MOVEDIR_BACKWARD:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 );
break;
default:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 );
break;
}
*/
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 ( gameLocal.GetTime() >= nextShootTime ) {
if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= minShots) || !move.fl.running || !move.fl.moving ) {
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_SHOOT);
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterStroggMarine::EnemyMovingToRight
================
*/
bool rvMonsterStroggMarine::EnemyMovingToRight( void )
{
if ( !GetEnemy() )
{
return false;
}
//use their movement direction
idVec3 dir = GetEnemy()->GetPhysics()->GetLinearVelocity();
//flatten
dir.z = 0;
dir.Normalize();
idVec3 fwd = viewAxis[0];
idVec3 lt = viewAxis[1];
float dot = 0.0f;
dot = DotProduct(dir, lt);
if ( dot > 0 )
{
return false;
}
else
{
return true;
}
}
/*
================
rvMonsterStroggMarine::State_Torso_SprayAttack
================
*/
stateResult_t rvMonsterStroggMarine::State_Torso_SprayAttack ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_SWEEP,
STAGE_END,
STAGE_FINISH
};
switch ( parms.stage ) {
case STAGE_START:
DisableAnimState ( ANIMCHANNEL_LEGS );
spraySideRight = EnemyMovingToRight();
sweepCount = 0;
if ( spraySideRight )
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_start", 0 );
}
else
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_start", 0 );
}
return SRESULT_STAGE ( STAGE_SWEEP );
case STAGE_SWEEP:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
sweepCount++;
if ( spraySideRight )
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_sweep", 0 );
}
else
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_sweep", 0 );
}
return SRESULT_STAGE ( STAGE_END );
}
return SRESULT_WAIT;
case STAGE_END:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
bool curEnemyMovingRight = EnemyMovingToRight();
if ( sweepCount < 3
&& (!gameLocal.random.RandomInt(2)
|| (spraySideRight && !curEnemyMovingRight)
|| (!spraySideRight && curEnemyMovingRight)) )
{
spraySideRight = !spraySideRight;
return SRESULT_STAGE ( STAGE_SWEEP );
}
else
{
if ( spraySideRight )
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_end", 0 );
}
else
{
PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_end", 0 );
}
return SRESULT_STAGE ( STAGE_FINISH );
}
}
return SRESULT_WAIT;
case STAGE_FINISH:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}