#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; }