318 lines
7.9 KiB
C++
318 lines
7.9 KiB
C++
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
class rvMonsterGrunt : public idAI {
|
|
public:
|
|
|
|
CLASS_PROTOTYPE( rvMonsterGrunt );
|
|
|
|
rvMonsterGrunt ( void );
|
|
|
|
void Spawn ( void );
|
|
void Save ( idSaveGame *savefile ) const;
|
|
void Restore ( idRestoreGame *savefile );
|
|
|
|
virtual void AdjustHealthByDamage ( int damage );
|
|
|
|
protected:
|
|
|
|
rvAIAction actionMeleeMoveAttack;
|
|
rvAIAction actionChaingunAttack;
|
|
|
|
virtual bool CheckActions ( void );
|
|
|
|
virtual void OnTacticalChange ( aiTactical_t oldTactical );
|
|
virtual void OnDeath ( void );
|
|
|
|
private:
|
|
|
|
int standingMeleeNoAttackTime;
|
|
int rageThreshold;
|
|
|
|
void RageStart ( void );
|
|
void RageStop ( void );
|
|
|
|
// Torso States
|
|
stateResult_t State_Torso_Enrage ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_Pain ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_LeapAttack ( const stateParms_t& parms );
|
|
|
|
CLASS_STATES_PROTOTYPE ( rvMonsterGrunt );
|
|
};
|
|
|
|
CLASS_DECLARATION( idAI, rvMonsterGrunt )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::rvMonsterGrunt
|
|
================
|
|
*/
|
|
rvMonsterGrunt::rvMonsterGrunt ( void ) {
|
|
standingMeleeNoAttackTime = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::Spawn
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::Spawn ( void ) {
|
|
rageThreshold = spawnArgs.GetInt ( "health_rageThreshold" );
|
|
|
|
// Custom actions
|
|
actionMeleeMoveAttack.Init ( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK );
|
|
actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", NULL, AIACTIONF_ATTACK );
|
|
actionLeapAttack.Init ( spawnArgs, "action_leapAttack", "Torso_LeapAttack", AIACTIONF_ATTACK );
|
|
|
|
// Enraged to start?
|
|
if ( spawnArgs.GetBool ( "preinject" ) ) {
|
|
RageStart ( );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::Save
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::Save ( idSaveGame *savefile ) const {
|
|
actionMeleeMoveAttack.Save( savefile );
|
|
actionChaingunAttack.Save( savefile );
|
|
|
|
savefile->WriteInt( rageThreshold );
|
|
savefile->WriteInt( standingMeleeNoAttackTime );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::Restore
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::Restore ( idRestoreGame *savefile ) {
|
|
actionMeleeMoveAttack.Restore( savefile );
|
|
actionChaingunAttack.Restore( savefile );
|
|
|
|
savefile->ReadInt( rageThreshold );
|
|
savefile->ReadInt( standingMeleeNoAttackTime );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::RageStart
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::RageStart ( void ) {
|
|
SetShaderParm ( 6, 1 );
|
|
|
|
// Disable non-rage actions
|
|
actionEvadeLeft.fl.disabled = true;
|
|
actionEvadeRight.fl.disabled = true;
|
|
|
|
// Speed up animations
|
|
animator.SetPlaybackRate ( 1.25f );
|
|
|
|
// Disable pain
|
|
pain.threshold = 0;
|
|
|
|
// Start over with health when enraged
|
|
health = spawnArgs.GetInt ( "health" );
|
|
|
|
// No more going to rage
|
|
rageThreshold = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::RageStop
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::RageStop ( void ) {
|
|
SetShaderParm ( 6, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::CheckActions
|
|
================
|
|
*/
|
|
bool rvMonsterGrunt::CheckActions ( void ) {
|
|
// If our health is below the rage threshold then enrage
|
|
if ( health < rageThreshold ) {
|
|
PerformAction ( "Torso_Enrage", 4, true );
|
|
return true;
|
|
}
|
|
|
|
// Moving melee attack?
|
|
if ( PerformAction ( &actionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) {
|
|
return true;
|
|
}
|
|
|
|
// Default actions
|
|
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!
|
|
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
|
|
//allow ranged attack
|
|
actionRangedAttack.fl.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::OnDeath
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::OnDeath ( void ) {
|
|
RageStop ( );
|
|
return idAI::OnDeath ( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::OnTacticalChange
|
|
|
|
Enable/Disable the ranged attack based on whether the grunt needs it
|
|
================
|
|
*/
|
|
void rvMonsterGrunt::OnTacticalChange ( aiTactical_t oldTactical ) {
|
|
switch ( combat.tacticalCurrent ) {
|
|
case AITACTICAL_MELEE:
|
|
actionRangedAttack.fl.disabled = true;
|
|
break;
|
|
|
|
default:
|
|
actionRangedAttack.fl.disabled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvMonsterGrunt::AdjustHealthByDamage
|
|
=====================
|
|
*/
|
|
void rvMonsterGrunt::AdjustHealthByDamage ( int damage ) {
|
|
// Take less damage during enrage process
|
|
if ( rageThreshold && health < rageThreshold ) {
|
|
health -= (damage * 0.25f);
|
|
return;
|
|
}
|
|
return idAI::AdjustHealthByDamage ( damage );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvMonsterGrunt )
|
|
STATE ( "Torso_Enrage", rvMonsterGrunt::State_Torso_Enrage )
|
|
STATE ( "Torso_Pain", rvMonsterGrunt::State_Torso_Pain )
|
|
STATE ( "Torso_LeapAttack", rvMonsterGrunt::State_Torso_LeapAttack )
|
|
END_CLASS_STATES
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::State_Torso_Pain
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterGrunt::State_Torso_Pain ( const stateParms_t& parms ) {
|
|
// Stop streaming pain if its time to get angry
|
|
if ( pain.loopEndTime && health < rageThreshold ) {
|
|
pain.loopEndTime = 0;
|
|
}
|
|
return idAI::State_Torso_Pain ( parms );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::State_Torso_Enrage
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterGrunt::State_Torso_Enrage ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_ANIM,
|
|
STAGE_ANIM_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_ANIM:
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "anger", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_ANIM_WAIT );
|
|
|
|
case STAGE_ANIM_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
|
|
RageStart ( );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
rvMonsterGrunt::State_Torso_LeapAttack
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterGrunt::State_Torso_LeapAttack ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_ANIM,
|
|
STAGE_ANIM_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_ANIM:
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
lastAttackTime = 0;
|
|
// Play the action animation
|
|
PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_ANIM_WAIT );
|
|
|
|
case STAGE_ANIM_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
// If we missed our leap attack get angry
|
|
if ( !lastAttackTime && rageThreshold ) {
|
|
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Enrage", parms.blendFrames );
|
|
}
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|