#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" class rvMonsterStreamProtector : public idAI { public: CLASS_PROTOTYPE( rvMonsterStreamProtector ); rvMonsterStreamProtector ( void ); void InitSpawnArgsVariables ( void ); void Spawn ( void ); void Save ( idSaveGame *savefile ) const; void Restore ( idRestoreGame *savefile ); bool CanTurn ( void ) const; virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); protected: virtual bool CheckPainActions ( void ); virtual bool CheckActions ( void ); virtual bool UpdateAnimationControllers ( void ); jointHandle_t jointPlasmaMuzzle; int attackEndTime; int attackNextTime; int plasmaAttackRate; int shots; private: int painConsecutive; rvAIAction actionPlasmaAttack; rvAIAction actionRocketAttack; rvAIAction actionBlasterAttack; rvAIAction actionHeavyBlasterAttack; rvAIAction actionLightningActtack; rvAIAction actionChaingunAttack; // Torso States stateResult_t State_Killed ( const stateParms_t& parms ); stateResult_t State_Dead ( const stateParms_t& parms ); stateResult_t State_Torso_PlasmaAttack ( const stateParms_t& parms ); stateResult_t State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ); stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); stateResult_t State_Torso_LightningAttack ( const stateParms_t& parms ); stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); CLASS_STATES_PROTOTYPE ( rvMonsterStreamProtector ); }; CLASS_DECLARATION( idAI, rvMonsterStreamProtector ) END_CLASS /* ================ rvMonsterStreamProtector::rvMonsterStreamProtector ================ */ rvMonsterStreamProtector::rvMonsterStreamProtector ( void ) { painConsecutive = 0; } void rvMonsterStreamProtector::InitSpawnArgsVariables ( void ) { jointPlasmaMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_plasmaMuzzle", "NM_muzzle" ) ); plasmaAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_plasma_rate", ".15" ) ); } /* ================ rvMonsterStreamProtector::Spawn ================ */ void rvMonsterStreamProtector::Spawn ( void ) { actionPlasmaAttack.Init ( spawnArgs, "action_plasmaAttack", "Torso_PlasmaAttack", AIACTIONF_ATTACK ); actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", NULL, AIACTIONF_ATTACK ); actionHeavyBlasterAttack.Init ( spawnArgs, "action_heavyBlasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); actionLightningActtack.Init ( spawnArgs, "action_lightningAttack", "Torso_LightningAttack", AIACTIONF_ATTACK ); actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", "Torso_ChaingunAttack", AIACTIONF_ATTACK ); InitSpawnArgsVariables(); } /* ================ rvMonsterStreamProtector::Save ================ */ void rvMonsterStreamProtector::Save ( idSaveGame *savefile ) const { savefile->WriteInt( attackEndTime ); savefile->WriteInt( attackNextTime ); savefile->WriteInt( shots ); savefile->WriteInt( painConsecutive ); actionPlasmaAttack.Save( savefile ); actionRocketAttack.Save( savefile ); actionBlasterAttack.Save( savefile ); actionHeavyBlasterAttack.Save ( savefile ); actionLightningActtack.Save( savefile ); actionChaingunAttack.Save( savefile ); } /* ================ rvMonsterStreamProtector::Restore ================ */ void rvMonsterStreamProtector::Restore ( idRestoreGame *savefile ) { savefile->ReadInt( attackEndTime ); savefile->ReadInt( attackNextTime ); savefile->ReadInt( shots ); savefile->ReadInt( painConsecutive ); actionPlasmaAttack.Restore( savefile ); actionRocketAttack.Restore( savefile ); actionBlasterAttack.Restore( savefile ); actionHeavyBlasterAttack.Restore ( savefile ); actionLightningActtack.Restore( savefile ); actionChaingunAttack.Restore( savefile ); InitSpawnArgsVariables(); } /* ================ rvMonsterStreamProtector::Spawn ================ */ bool rvMonsterStreamProtector::UpdateAnimationControllers ( void ) { // TODO: Target enemies behind us? (doesnt need to be the same enemy that we are targetting) return idAI::UpdateAnimationControllers ( ); } /* ================ rvMonsterStreamProtector::CheckPainActions ================ */ bool rvMonsterStreamProtector::CheckPainActions ( void ) { if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { return false; } if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { if ( painConsecutive < 10 ) { return false; } else { painConsecutive = 0; } } PerformAction ( "Torso_Pain", 2, true ); actionTimerPain.Reset ( actionTime ); return true; } /* ================ rvMonsterStreamProtector::CheckActions ================ */ bool rvMonsterStreamProtector::CheckActions ( void ) { // If not moving, try turning in place if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) { float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; if ( turnYaw > lookMax[YAW] * 0.75f ) { PerformAction ( "Torso_TurnRight90", 4, true ); return true; } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { PerformAction ( "Torso_TurnLeft90", 4, true ); return true; } } if ( PerformAction ( &actionPlasmaAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionLightningActtack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionHeavyBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } return idAI::CheckActions ( ); } /* ================ rvMonsterStreamProtector::CanTurn ================ */ bool rvMonsterStreamProtector::CanTurn ( void ) const { if ( !idAI::CanTurn ( ) ) { return false; } return move.anim_turn_angles != 0.0f || move.fl.moving; } /* ================ rvMonsterStreamProtector::Pain ================ */ bool rvMonsterStreamProtector::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { painConsecutive++; } else { painConsecutive = 1; } return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); } /* ================ rvMonsterStreamProtector::Damage ================ */ void rvMonsterStreamProtector::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { if ( attacker && attacker->IsType( rvMonsterStreamProtector::GetClassType() ) ) { //don't take damage from ourselves or other stream protectors return; } idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvMonsterStreamProtector ) STATE ( "State_Killed", rvMonsterStreamProtector::State_Killed ) STATE ( "State_Dead", rvMonsterStreamProtector::State_Dead ) STATE ( "Torso_PlasmaAttack", rvMonsterStreamProtector::State_Torso_PlasmaAttack ) STATE ( "Torso_FinishPlasmaAttack", rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ) STATE ( "Torso_BlasterAttack", rvMonsterStreamProtector::State_Torso_BlasterAttack ) STATE ( "Torso_LightningAttack", rvMonsterStreamProtector::State_Torso_LightningAttack ) STATE ( "Torso_TurnRight90", rvMonsterStreamProtector::State_Torso_TurnRight90 ) STATE ( "Torso_TurnLeft90", rvMonsterStreamProtector::State_Torso_TurnLeft90 ) END_CLASS_STATES /* ================ rvMonsterStreamProtector::State_Killed ================ */ stateResult_t rvMonsterStreamProtector::State_Killed ( const stateParms_t& parms ) { // Make sure all animation stops StopAnimState ( ANIMCHANNEL_TORSO ); StopAnimState ( ANIMCHANNEL_LEGS ); if ( head ) { StopAnimState ( ANIMCHANNEL_HEAD ); } DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "death", parms.blendFrames ); PostState ( "State_Dead" ); return SRESULT_DONE; } /* ================ rvMonsterStreamProtector::State_Dead ================ */ stateResult_t rvMonsterStreamProtector::State_Dead ( const stateParms_t& parms ) { if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_WAIT; } return idAI::State_Dead ( parms ); } /* ================ rvMonsterStreamProtector::State_Torso_BlasterAttack ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames ); shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_LOOP: PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 ); return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 ); return SRESULT_STAGE ( STAGE_WAITEND ); } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStreamProtector::State_Torso_PlasmaAttack ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) { enum { STAGE_START, STAGE_START_WAIT, STAGE_FIRE, STAGE_INITIALFIRE_WAIT, STAGE_FIRE_WAIT, STAGE_END, STAGE_END_WAIT, }; switch ( parms.stage ) { case STAGE_START: DisableAnimState ( ANIMCHANNEL_LEGS ); // Loop the flame animation PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames ); // Make sure we clean up some things when this state is finished (effects for one) PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR ); return SRESULT_STAGE ( STAGE_START_WAIT ); case STAGE_START_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_FIRE ); } return SRESULT_WAIT; case STAGE_FIRE: attackEndTime = gameLocal.time + 500; attackNextTime = gameLocal.time; // Flame effect PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true ); PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 ); return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT ); case STAGE_INITIALFIRE_WAIT: if ( gameLocal.time > attackEndTime ) { attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f ); return SRESULT_STAGE ( STAGE_FIRE_WAIT ); } // Launch another attack? if ( gameLocal.time >= attackNextTime ) { Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); attackNextTime = gameLocal.time + plasmaAttackRate; } return SRESULT_WAIT; case STAGE_FIRE_WAIT: // If we have been using plasma too long or havent seen our enemy for at least half a second then // stop now. if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) { StopEffect ( "fx_plasma_muzzle" ); return SRESULT_STAGE ( STAGE_END ); } // Launch another attack? if ( gameLocal.time >= attackNextTime ) { Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); attackNextTime = gameLocal.time + plasmaAttackRate; } return SRESULT_WAIT; case STAGE_END: // End animations PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames ); return SRESULT_STAGE ( STAGE_END_WAIT ); case STAGE_END_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ) { StopEffect ( "fx_plasma_muzzle" ); return SRESULT_DONE; } /* ================ rvMonsterStreamProtector::State_Torso_LightningAttack ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) { enum { STAGE_START, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; switch ( parms.stage ) { case STAGE_START: DisableAnimState ( ANIMCHANNEL_LEGS ); attackEndTime = gameLocal.time + 5000; PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_LOOP: PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov) ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 ); return SRESULT_STAGE ( STAGE_WAITEND ); } PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStreamProtector::State_Torso_TurnRight90 ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_TurnRight90 ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); AnimTurn ( 90.0f, true ); return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { AnimTurn ( 0, true ); combat.investigateTime = gameLocal.time + 250; return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStreamProtector::State_Torso_TurnLeft90 ================ */ stateResult_t rvMonsterStreamProtector::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); AnimTurn ( 90.0f, true ); return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { AnimTurn ( 0, true ); combat.investigateTime = gameLocal.time + 250; return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; }