531 lines
12 KiB
C++
531 lines
12 KiB
C++
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
class rvMonsterIronMaiden : public idAI {
|
|
public:
|
|
|
|
CLASS_PROTOTYPE( rvMonsterIronMaiden );
|
|
|
|
rvMonsterIronMaiden ( void );
|
|
|
|
void InitSpawnArgsVariables( void );
|
|
void Spawn ( void );
|
|
void Save ( idSaveGame *savefile ) const;
|
|
void Restore ( idRestoreGame *savefile );
|
|
|
|
// Add some dynamic externals for debugging
|
|
virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData );
|
|
|
|
virtual int FilterTactical ( int availableTactical );
|
|
|
|
protected:
|
|
|
|
int phaseTime;
|
|
jointHandle_t jointBansheeAttack;
|
|
int enemyStunTime;
|
|
|
|
rvAIAction actionBansheeAttack;
|
|
|
|
virtual bool CheckActions ( void );
|
|
|
|
void PhaseOut ( void );
|
|
void PhaseIn ( void );
|
|
|
|
virtual void OnDeath ( void );
|
|
|
|
private:
|
|
|
|
// Custom actions
|
|
bool CheckAction_BansheeAttack ( rvAIAction* action, int animNum );
|
|
bool PerformAction_PhaseOut ( void );
|
|
bool PerformAction_PhaseIn ( void );
|
|
|
|
// Global States
|
|
stateResult_t State_Phased ( const stateParms_t& parms );
|
|
|
|
// Torso States
|
|
stateResult_t State_Torso_PhaseIn ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_PhaseOut ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_BansheeAttack ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_BansheeAttackDone ( const stateParms_t& parms );
|
|
|
|
// Frame commands
|
|
stateResult_t Frame_PhaseIn ( const stateParms_t& parms );
|
|
stateResult_t Frame_PhaseOut ( const stateParms_t& parms );
|
|
stateResult_t Frame_BansheeAttack ( const stateParms_t& parms );
|
|
stateResult_t Frame_EndBansheeAttack ( const stateParms_t& parms );
|
|
|
|
CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden );
|
|
};
|
|
|
|
CLASS_DECLARATION( idAI, rvMonsterIronMaiden )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::rvMonsterIronMaiden
|
|
================
|
|
*/
|
|
rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) {
|
|
enemyStunTime = 0;
|
|
phaseTime = 0;
|
|
}
|
|
|
|
void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) {
|
|
// Cache the mouth joint
|
|
jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) );
|
|
}
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Spawn
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::Spawn ( void ) {
|
|
// Custom actions
|
|
actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK );
|
|
|
|
InitSpawnArgsVariables();
|
|
|
|
PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Save
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const {
|
|
savefile->WriteInt ( phaseTime );
|
|
savefile->WriteInt ( enemyStunTime );
|
|
|
|
actionBansheeAttack.Save ( savefile );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Restore
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) {
|
|
savefile->ReadInt ( phaseTime );
|
|
savefile->ReadInt ( enemyStunTime );
|
|
|
|
actionBansheeAttack.Restore ( savefile );
|
|
|
|
InitSpawnArgsVariables();
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::FilterTactical
|
|
================
|
|
*/
|
|
int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) {
|
|
// When hidden the iron maiden only uses ranged tactical
|
|
if ( fl.hidden ) {
|
|
availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT);
|
|
}
|
|
return idAI::FilterTactical( availableTactical );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::CheckAction_BansheeAttack
|
|
================
|
|
*/
|
|
bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) {
|
|
return CheckAction_RangedAttack ( action, animNum );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::PerformAction_PhaseIn
|
|
================
|
|
*/
|
|
bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) {
|
|
if ( !phaseTime ) {
|
|
return false;
|
|
}
|
|
|
|
// Must be out for at least 3 seconds
|
|
if ( gameLocal.time - phaseTime < 3000 ) {
|
|
return false;
|
|
}
|
|
|
|
// Phase in after 10 seconds or our movement is done
|
|
if ( gameLocal.time - phaseTime > 10000 || move.fl.done ) {
|
|
// Make sure we arent in something
|
|
if ( CanBecomeSolid ( ) ) {
|
|
PerformAction ( "Torso_PhaseIn", 4, true );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::PerformAction_PhaseOut
|
|
================
|
|
*/
|
|
bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) {
|
|
// Little randomization
|
|
if ( gameLocal.random.RandomFloat ( ) > 0.5f ) {
|
|
return false;
|
|
}
|
|
if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) {
|
|
return false;
|
|
}
|
|
|
|
PerformAction ( "Torso_PhaseOut", 4, true );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::CheckActions
|
|
================
|
|
*/
|
|
bool rvMonsterIronMaiden::CheckActions ( void ) {
|
|
// When phased the only available action is phase in
|
|
if ( phaseTime ) {
|
|
if ( PerformAction_PhaseIn ( ) ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( PerformAction_PhaseOut ( ) ) {
|
|
return true;
|
|
}
|
|
|
|
return idAI::CheckActions ( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::PhaseOut
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::PhaseOut ( void ) {
|
|
if ( phaseTime ) {
|
|
return;
|
|
}
|
|
|
|
rvClientEffect* effect;
|
|
effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") );
|
|
if ( effect ) {
|
|
effect->Unbind ( );
|
|
}
|
|
|
|
Hide ( );
|
|
|
|
move.fl.allowHiddenMove = true;
|
|
|
|
ProcessEvent ( &EV_BecomeNonSolid );
|
|
|
|
StopMove ( MOVE_STATUS_DONE );
|
|
SetState ( "State_Phased" );
|
|
|
|
// Move away from here, to anywhere
|
|
MoveOutOfRange ( this, 500.0f, 150.0f );
|
|
|
|
SetShaderParm ( 5, MS2SEC ( gameLocal.time ) );
|
|
|
|
phaseTime = gameLocal.time;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::PhaseIn
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::PhaseIn ( void ) {
|
|
if ( !phaseTime ) {
|
|
return;
|
|
}
|
|
|
|
rvClientEffect* effect;
|
|
effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") );
|
|
if ( effect ) {
|
|
effect->Unbind ( );
|
|
}
|
|
|
|
if ( enemy.ent ) {
|
|
TurnToward ( enemy.lastKnownPosition );
|
|
}
|
|
|
|
ProcessEvent ( &AI_BecomeSolid );
|
|
|
|
Show ( );
|
|
|
|
// PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true );
|
|
|
|
phaseTime = 0;
|
|
// Wait for the action that started the phase in to finish, then go back to combat
|
|
SetState ( "Wait_Action" );
|
|
PostState ( "State_Combat" );
|
|
|
|
SetShaderParm ( 5, MS2SEC ( gameLocal.time ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::OnDeath
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::OnDeath ( void ) {
|
|
StopSound ( SND_CHANNEL_ITEM, false );
|
|
|
|
// Stop looping effects
|
|
StopEffect ( "fx_banshee" );
|
|
StopEffect ( "fx_dress" );
|
|
|
|
idAI::OnDeath( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::GetDebugInfo
|
|
================
|
|
*/
|
|
void rvMonsterIronMaiden::GetDebugInfo ( debugInfoProc_t proc, void* userData ) {
|
|
// Base class first
|
|
idAI::GetDebugInfo( proc, userData );
|
|
|
|
proc ( "idAI", "action_BansheeAttack", aiActionStatusString[actionBansheeAttack.status], userData );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvMonsterIronMaiden )
|
|
STATE ( "State_Phased", rvMonsterIronMaiden::State_Phased )
|
|
|
|
STATE ( "Torso_PhaseOut", rvMonsterIronMaiden::State_Torso_PhaseOut )
|
|
STATE ( "Torso_PhaseIn", rvMonsterIronMaiden::State_Torso_PhaseIn )
|
|
STATE ( "Torso_BansheeAttack", rvMonsterIronMaiden::State_Torso_BansheeAttack )
|
|
STATE ( "Torso_BansheeAttackDone", rvMonsterIronMaiden::State_Torso_BansheeAttackDone )
|
|
|
|
STATE ( "Frame_PhaseIn", rvMonsterIronMaiden::Frame_PhaseIn )
|
|
STATE ( "Frame_PhaseOut", rvMonsterIronMaiden::Frame_PhaseOut )
|
|
STATE ( "Frame_BansheeAttack", rvMonsterIronMaiden::Frame_BansheeAttack )
|
|
STATE ( "Frame_EndBansheeAttack", rvMonsterIronMaiden::Frame_EndBansheeAttack )
|
|
END_CLASS_STATES
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::State_Phased
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) {
|
|
// If done moving and cant become solid here, move again
|
|
if ( move.fl.done ) {
|
|
if ( !CanBecomeSolid ( ) ) {
|
|
MoveOutOfRange ( this, 300.0f, 150.0f );
|
|
}
|
|
}
|
|
|
|
// Keep the enemy status up to date
|
|
if ( !enemy.ent ) {
|
|
CheckForEnemy ( true );
|
|
}
|
|
|
|
// Make sure we keep facing our enemy
|
|
if ( enemy.ent ) {
|
|
TurnToward ( enemy.lastKnownPosition );
|
|
}
|
|
|
|
// Make sure we are checking actions if we have no tactical move
|
|
UpdateAction ( );
|
|
|
|
return SRESULT_WAIT;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::State_Torso_PhaseIn
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_ANIM,
|
|
STAGE_ANIM_WAIT,
|
|
STAGE_PHASE,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_ANIM:
|
|
if ( enemy.ent ) {
|
|
TurnToward ( enemy.lastKnownPosition );
|
|
}
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_ANIM_WAIT );
|
|
|
|
case STAGE_ANIM_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::State_Torso_PhaseOut
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_ANIM,
|
|
STAGE_ANIM_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_ANIM:
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_ANIM_WAIT );
|
|
|
|
case STAGE_ANIM_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::State_Torso_BansheeAttack
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_ATTACK,
|
|
STAGE_ATTACK_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_ATTACK:
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames );
|
|
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR );
|
|
return SRESULT_STAGE ( STAGE_ATTACK_WAIT );
|
|
|
|
case STAGE_ATTACK_WAIT:
|
|
if ( enemy.ent ) {
|
|
TurnToward ( enemy.lastKnownPosition );
|
|
}
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::State_Torso_BansheeAttackDone
|
|
|
|
To ensure the movement is enabled after the banshee attack and that the effect is stopped this state
|
|
will be posted to be run after the banshe attack finishes.
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) {
|
|
Frame_EndBansheeAttack ( parms );
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Frame_PhaseIn
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) {
|
|
PhaseIn ( );
|
|
return SRESULT_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Frame_PhaseOut
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) {
|
|
PhaseOut ( );
|
|
return SRESULT_OK;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Frame_BansheeAttack
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
idEntity* entities[ 1024 ];
|
|
int count;
|
|
int i;
|
|
|
|
// Get mouth origin
|
|
GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis );
|
|
|
|
// Find all entities within the banshee attacks radius
|
|
count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 );
|
|
for ( i = 0; i < count; i ++ ) {
|
|
idEntity* ent = entities[i];
|
|
if ( !ent || ent == this ) {
|
|
continue;
|
|
}
|
|
|
|
// Must be an actor that takes damage to be affected
|
|
if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Has to be within fov
|
|
if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Do some damage
|
|
idVec3 dir;
|
|
dir = origin = ent->GetEyePosition ( );
|
|
dir.NormalizeFast ( );
|
|
ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 );
|
|
|
|
// Cache the last time we stunned our own enemy for the phase out
|
|
if ( ent == enemy.ent ) {
|
|
enemyStunTime = gameLocal.time;
|
|
}
|
|
}
|
|
|
|
return SRESULT_OK;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterIronMaiden::Frame_EndBansheeAttack
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) {
|
|
StopEffect ( "fx_banshee" );
|
|
return SRESULT_OK;
|
|
}
|