quake4-sdk/source/game/ai/Monster_LightTank.cpp

611 lines
17 KiB
C++
Raw Normal View History

2007-06-15 00:00:00 +00:00
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
class rvMonsterLightTank : public idAI {
public:
CLASS_PROTOTYPE( rvMonsterLightTank );
rvMonsterLightTank ( void );
void Spawn ( void );
void Save ( idSaveGame *savefile ) const;
void Restore ( idRestoreGame *savefile );
virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual int GetDamageForLocation ( int damage, int location );
virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage );
protected:
virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand );
virtual bool CheckActions ( void );
virtual void OnTacticalChange ( aiTactical_t oldTactical );
virtual bool UpdateRunStatus ( void );
virtual int FilterTactical ( int availableTactical );
int flamethrowerHealth;
int chargeDebounce;
void DestroyFlamethrower ( void );
private:
int standingMeleeNoAttackTime;
bool damaged;
// bool damagedMove;
int powerUpStartTime;
rvAIAction actionFlameThrower;
rvAIAction actionPowerUp;
rvAIAction actionChargeAttack;
bool CheckAction_PowerUp ( rvAIAction* action, int animNum );
virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum );
virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum );
bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum );
// Global States
stateResult_t State_Killed ( const stateParms_t& parms );
// Torso States
stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms );
stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms );
stateResult_t State_Torso_Pain ( const stateParms_t& parms );
stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms );
stateResult_t State_Torso_PowerUp ( const stateParms_t& parms );
rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed
CLASS_STATES_PROTOTYPE ( rvMonsterLightTank );
};
CLASS_DECLARATION( idAI, rvMonsterLightTank )
END_CLASS
/*
================
rvMonsterLightTank::rvMonsterLightTank
================
*/
rvMonsterLightTank::rvMonsterLightTank ( void ) {
damaged = false;
standingMeleeNoAttackTime = 0;
}
/*
================
rvMonsterLightTank::Spawn
================
*/
void rvMonsterLightTank::Spawn ( void ) {
// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" );
flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" );
chargeDebounce = 0;
actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK );
actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 );
actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK );
const char *func;
if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) )
{
mPostWeaponDestroyed.Init( func );
}
}
/*
================
rvMonsterLightTank::Save
================
*/
void rvMonsterLightTank::Save ( idSaveGame *savefile ) const {
savefile->WriteInt( flamethrowerHealth );
savefile->WriteInt( chargeDebounce );
savefile->WriteBool( damaged );
// savefile->WriteBool( damagedMove );
savefile->WriteInt( powerUpStartTime );
actionFlameThrower.Save( savefile );
actionPowerUp.Save( savefile );
actionChargeAttack.Save( savefile );
mPostWeaponDestroyed.Save( savefile );
savefile->WriteInt( standingMeleeNoAttackTime );
}
/*
================
rvMonsterLightTank::Restore
================
*/
void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) {
savefile->ReadInt( flamethrowerHealth );
savefile->ReadInt( chargeDebounce );
savefile->ReadBool( damaged );
// savefile->ReadBool( damagedMove );
savefile->ReadInt( powerUpStartTime );
actionFlameThrower.Restore( savefile );
actionPowerUp.Restore( savefile );
actionChargeAttack.Restore( savefile );
mPostWeaponDestroyed.Restore( savefile );
savefile->ReadInt( standingMeleeNoAttackTime );
}
/*
================
rvMonsterLightTank::FilterTactical
================
*/
int rvMonsterLightTank::FilterTactical ( int availableTactical ) {
if ( flamethrowerHealth > 0 ) {
// Only let the light tank use ranged tactical when he is really far from his enemy
if ( !enemy.range || enemy.range < combat.attackRange[1] ) {
availableTactical &= ~(AITACTICAL_RANGED_BITS);
}
}
if ( chargeDebounce > gameLocal.GetTime() )
{//don't charge again any time soon
availableTactical &= ~(AITACTICAL_MELEE_BIT);
}
return idAI::FilterTactical( availableTactical );
}
/*
================
rvMonsterLightTank::OnTacticalChange
Enable/Disable the ranged attack based on whether the grunt needs it
================
*/
void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) {
switch ( combat.tacticalCurrent ) {
case AITACTICAL_MELEE:
actionFlameThrower.fl.disabled = true;
actionRangedAttack.fl.disabled = true;
break;
default:
actionFlameThrower.fl.disabled = false;
actionRangedAttack.fl.disabled = false;
break;
}
}
/*
================
rvMonsterLightTank::UpdateRunStatus
================
*/
bool rvMonsterLightTank::UpdateRunStatus ( void ) {
// If rushing, run
if ( combat.tacticalCurrent == AITACTICAL_MELEE )
{
move.fl.idealRunning = true;
}
else
{
move.fl.idealRunning = false;
}
return move.fl.running != move.fl.idealRunning;
}
/*
================
rvMonsterLightTank::DestroyFlamethrower
================
*/
void rvMonsterLightTank::DestroyFlamethrower ( void ) {
StopEffect ( "fx_flame_muzzle" );
//HideSurface ( "models/monsters/light_tank/flamethrower" );
//GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 );
animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) );
animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) );
animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) );
flamethrowerHealth = -1;
pain.takenThisFrame = pain.threshold;
pain.lastTakenTime = gameLocal.time;
// flamethrowerDestroyedTime = gameLocal.GetTime();
// Tweak-out the AI to be more aggressive and more likely to charge?
PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") );
PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true );
DisableAnimState( ANIMCHANNEL_LEGS );
painAnim = "damaged";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" );
PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" );
chargeDebounce = 0;
damaged = true;
animPrefix = "damage";
actionFlameThrower.fl.disabled = true;
actionRangedAttack.fl.disabled = true;
combat.attackRange[1] = 200;
combat.aggressiveRange = 400;
spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f );
actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK );
actionMeleeAttack.failRate = 200;
actionMeleeAttack.chance = 1.0f;
combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS);
actionMeleeAttack.timer.Reset( actionTime );
actionChargeAttack.timer.Reset( actionTime );
ExecScriptFunction( mPostWeaponDestroyed );
}
/*
=====================
rvMonsterLightTank::Pain
=====================
*/
bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location );
if ( move.fl.moving && move.fl.running )
{
painAnim = "pain_charge";
}
return didPain;
}
/*
=====================
rvMonsterLightTank::GetDamageForLocation
=====================
*/
int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) {
// If the flamethrower was hit only do damage to it
if( !damaged && !aifl.dead )
{
if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) {
// pain.takenThisFrame = damage;
if ( flamethrowerHealth > 0 ){
flamethrowerHealth -= damage;
if ( flamethrowerHealth <= 0 ) {
DestroyFlamethrower();
}
}
return 0;
}
}
return idAI::GetDamageForLocation ( damage, location );
}
/*
================
rvMonsterLightTank::DamageFeedback
callback function for when another entity recieved damage from this entity
================
*/
void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
if ( !damaged )
{
if ( victim == GetEnemy() && inflictor == this )
{
if ( combat.tacticalCurrent == AITACTICAL_MELEE )
{//okay, get out of melee state for now
chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000;
}
}
}
idAI::DamageFeedback( victim, inflictor, damage );
}
/*
============
rvMonsterLightTank::OnStopMoving
============
*/
void rvMonsterLightTank::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_RANGED )
{
actionRangedAttack.timer.Clear( actionTime );
actionTimerRangedAttack.Clear( actionTime );
actionFlameThrower.timer.Clear( actionTime );
}
else if ( combat.tacticalCurrent == AITACTICAL_MELEE )
{//so we don't stand there and look stupid
actionMeleeAttack.timer.Clear( actionTime );
actionChargeAttack.timer.Clear( actionTime );
}
}
}
/*
================
rvMonsterLightTank::CheckAction_PowerUp
================
*/
bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum )
{
if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE )
{
return false;
}
if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) {
return false;
}
return true;
}
/*
================
rvMonsterLightTank::CheckAction_EvadeLeft
================
*/
bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) {
if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE )
{
return false;
}
return idAI::CheckAction_EvadeLeft( action, animNum );
}
/*
================
rvMonsterLightTank::CheckAction_EvadeRight
================
*/
bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) {
if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE )
{
return false;
}
return idAI::CheckAction_EvadeRight( action, animNum );
}
/*
================
rvMonsterLightTank::CheckAction_ChargeAttack
================
*/
bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) {
if ( !enemy.ent || !enemy.fl.inFov ) {
return false;
}
if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) {
return false;
}
if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) {
return false;
}
return true;
}
/*
================
rvMonsterLightTank::CheckActions
================
*/
bool rvMonsterLightTank::CheckActions ( void ) {
if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) {
return true;
}
if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) {
return true;
}
if ( CheckPainActions ( ) ) {
return true;
}
if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) ||
PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) ||
PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) ||
PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) {
return true;
} else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) {
standingMeleeNoAttackTime = 0;
return true;
} else {
if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER
&& actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER
&& actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE )
{//melee attack fail for any reason other than timer?
if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving )
{//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range!
//allow ranged attack
if ( !standingMeleeNoAttackTime )
{
standingMeleeNoAttackTime = gameLocal.GetTime();
}
else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() )
{//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack
actionFlameThrower.fl.disabled = false;
actionRangedAttack.fl.disabled = false;
}
}
}
if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
}
if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) {
return true;
}
return false;
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvMonsterLightTank )
STATE ( "State_Killed", rvMonsterLightTank::State_Killed )
STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower )
STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink )
STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain )
STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack )
STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp )
END_CLASS_STATES
/*
================
rvMonsterLightTank::State_Killed
================
*/
stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) {
StopEffect ( "fx_destroy_arm_trail" );
StopEffect ( "fx_flame_muzzle" );
return idAI::State_Killed ( parms );
}
/*
================
rvMonsterLightTank::State_Torso_FlameThrower
================
*/
stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) {
DisableAnimState ( ANIMCHANNEL_LEGS );
// Flame effect
PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true );
// Loop the flame animation
PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames );
// Delay start the flame thrower think to ensure he flames for a minimum time
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 );
return SRESULT_DONE;
}
/*
================
rvMonsterLightTank::State_Torso_FlameThrowerThink
================
*/
stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) {
if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
StopEffect ( "fx_flame_muzzle" );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
/*
================
rvMonsterLightTank::State_Torso_Pain
================
*/
stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) {
StopEffect ( "fx_flame_muzzle" );
// Default pain animation
return idAI::State_Torso_Pain ( parms );
}
/*
================
rvMonsterLightTank::State_Torso_RangedAttack
================
*/
stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_FINISH,
};
switch ( parms.stage ) {
case STAGE_START:
// If moving switch to the moving ranged attack (torso only)
if ( !move.fl.moving || !FacingIdeal() ) {
// Full body animations
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames );
}
else
{
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames );
}
return SRESULT_STAGE ( STAGE_FINISH );
case STAGE_FINISH:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterLightTank::State_Torso_PowerUp
================
*/
stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_START_WAIT,
STAGE_LOOP,
STAGE_FINISH,
};
switch ( parms.stage ) {
case STAGE_START:
// If moving switch to the moving ranged attack (torso only)
//fl.takedamage = false;
powerUpStartTime = gameLocal.GetTime();
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames );
return SRESULT_STAGE ( STAGE_START_WAIT );
case STAGE_START_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames );
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_LOOP:
health++;
if ( health >= spawnArgs.GetInt( "health" )
|| gameLocal.GetTime() - powerUpStartTime > 3875 )
{//full health or been charging up for 3 full anim loops
PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames );
return SRESULT_STAGE ( STAGE_FINISH );
}
return SRESULT_WAIT;
case STAGE_FINISH:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}