quake4-sdk/source/mpgame/ai/Monster_Gladiator.cpp
2007-06-15 00:00:00 +00:00

902 lines
26 KiB
C++

#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../client/ClientModel.h"
class rvMonsterGladiator : public idAI {
public:
CLASS_PROTOTYPE( rvMonsterGladiator );
rvMonsterGladiator ( void );
void InitSpawnArgsVariables( void );
void Spawn ( void );
void Save ( idSaveGame *savefile ) const;
void Restore ( idRestoreGame *savefile );
bool CanTurn ( void ) const;
virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData );
virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor );
virtual bool UpdateRunStatus ( void );
virtual int FilterTactical ( int availableTactical );
virtual int GetDamageForLocation( int damage, int location );
// virtual void SetTether ( rvAITether* newTether );
protected:
// Actions
rvAIAction actionRailgunAttack;
// Blaster attack
int maxShots;
int minShots;
int shots;
int lastShotTime;
// Shield
bool usingShield;
idEntityPtr<idEntity> shield;
int shieldStartTime;
int shieldWaitTime;
int shieldHitDelay;
//int shieldInDelay;
//int shieldFov;
int shieldHealth;
int shieldConsecutiveHits;
int shieldLastHitTime;
int railgunHealth;
int railgunDestroyedTime;
int nextTurnTime;
virtual bool CheckActions ( void );
void ShowShield ( void );
void HideShield ( int hideTime=0 );
void DestroyRailgun ( void );
private:
// Global States
stateResult_t State_Killed ( const stateParms_t& parms );
// Torso states
stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms );
stateResult_t State_Torso_RailgunAttack ( const stateParms_t& parms );
stateResult_t State_Torso_ShieldStart ( const stateParms_t& parms );
stateResult_t State_Torso_ShieldEnd ( const stateParms_t& parms );
stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms );
stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms );
stateResult_t State_Torso_ShieldFire ( const stateParms_t& parms );
rvScriptFuncUtility mPostWeaponDestroyed; // script to run after railgun is destroyed
CLASS_STATES_PROTOTYPE ( rvMonsterGladiator );
};
CLASS_DECLARATION( idAI, rvMonsterGladiator )
END_CLASS
/*
================
rvMonsterGladiator::rvMonsterGladiator
================
*/
rvMonsterGladiator::rvMonsterGladiator ( ) {
usingShield = false;
}
void rvMonsterGladiator::InitSpawnArgsVariables ( void )
{
maxShots = spawnArgs.GetInt ( "maxShots", "1" );
minShots = spawnArgs.GetInt ( "minShots", "1" );
shieldHitDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldHitDelay", "1" ) );
// shieldInDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldInDelay", "3" ) );
// shieldFov = spawnArgs.GetInt ( "shieldfov", "90" );
}
/*
================
rvMonsterGladiator::Spawn
================
*/
void rvMonsterGladiator::Spawn ( void ) {
shieldWaitTime = 0;
shieldStartTime = 0;
shieldHealth = 250;
shieldConsecutiveHits = 0;
shieldLastHitTime = 0;
InitSpawnArgsVariables();
shots = 0;
lastShotTime = 0;
railgunHealth = spawnArgs.GetInt ( "railgunHealth", "100" );
railgunDestroyedTime = 0;
actionRailgunAttack.Init ( spawnArgs, "action_railgunAttack", "Torso_RailgunAttack", AIACTIONF_ATTACK );
// Disable range attack until using shield
//actionRangedAttack.fl.disabled = true;
const char *func;
if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) )
{
mPostWeaponDestroyed.Init( func );
}
}
/*
================
rvMonsterGladiator::CheckActions
Overriden to handle taking the shield out and putting it away. Will also ensure the gladiator
stays hidden behind his shield if getting shot at.
================
*/
bool rvMonsterGladiator::CheckActions ( void ) {
// If not moving, try turning in place
if ( !move.fl.moving && gameLocal.time > nextTurnTime ) {
float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ;
if ( turnYaw > lookMax[YAW] * 0.75f || (turnYaw > 0 && !enemy.fl.inFov) ) {
PerformAction ( "Torso_TurnRight90", 4, true );
return true;
} else if ( turnYaw < -lookMax[YAW] * 0.75f || (turnYaw < 0 && !enemy.fl.inFov) ) {
PerformAction ( "Torso_TurnLeft90", 4, true );
return true;
}
}
if ( CheckPainActions ( ) ) {
return true;
}
// Limited actions with shield out
if ( usingShield ) {
if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) {
return true;
}
if ( move.moveCommand == MOVE_TO_ENEMY
&& move.fl.moving )
{//advancing on enemy with shield up
if ( gameLocal.GetTime() - lastShotTime > 1500 )
{//been at least a second since the last time we fired while moving
if ( !gameLocal.random.RandomInt(2) )
{//fire!
PerformAction ( "Torso_ShieldFire", 0, true );
return true;
}
}
}
// Only ranged attack and melee attack are available when using shield
if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ||
PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
( railgunHealth > 0
&& gameLocal.GetTime() - shieldStartTime > 2000
&& gameLocal.time - pain.lastTakenTime > 500
&& gameLocal.time - combat.shotAtTime > 300
&& gameLocal.GetTime() - shieldLastHitTime > 500
&& PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) ) {
shieldWaitTime = 0;
return true;
}
// see if it's safe to lower it?
if ( gameLocal.GetTime() - shieldStartTime > 2000 )
{//shield's been up for at least 2 seconds
if ( !enemy.fl.visible || (gameLocal.time - combat.shotAtTime > 1000 && gameLocal.GetTime() - shieldLastHitTime > 1500) )
{
if ( gameLocal.time - pain.lastTakenTime > 1500 )
{
PerformAction ( "Torso_ShieldEnd", 4, true );
return true;
}
}
}
return false;
}
else
{// Bring the shield out?
if ( combat.tacticalCurrent != AITACTICAL_MELEE || move.fl.done )
{//not while rushing (NOTE: unless railgun was just destroyed?)
if ( enemy.fl.visible && enemy.fl.inFov )
{
if ( combat.fl.aware && shieldWaitTime < gameLocal.GetTime() )
{
if ( gameLocal.time - pain.lastTakenTime <= 1500
|| ( combat.shotAtAngle < 0 && gameLocal.time - combat.shotAtTime < 100 )
|| !gameLocal.random.RandomInt( 20 ) )
{
if ( !gameLocal.random.RandomInt( 5 ) )
{
PerformAction ( "Torso_ShieldStart", 4, true );
return true;
}
}
}
}
}
if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) {
return true;
}
}
return idAI::CheckActions ( );
}
/*
================
rvMonsterGladiator::ShowShield
================
*/
void rvMonsterGladiator::ShowShield ( void ) {
// First time?
if ( !shield ) {
idEntity* ent;
idDict args;
const idDict *shieldDef = gameLocal.FindEntityDefDict( spawnArgs.GetString ( "def_shield" ), false );
args.Set ( "classname", spawnArgs.GetString ( "def_shield" ) );
if ( gameLocal.SpawnEntityDef( args, &ent ) ) {
shield = ent;
ent->GetPhysics()->SetClipMask ( 0 );
ent->GetPhysics()->SetContents ( CONTENTS_RENDERMODEL );
ent->GetPhysics()->GetClipModel ( )->SetOwner ( this );
Attach ( ent );
}
if ( !shield ) {
return;
}
if ( shieldDef && shield->IsType( idAFAttachment::GetClassType() ) )
{
idAFAttachment* afShield = static_cast<idAFAttachment*>(shield.GetEntity());
if ( afShield )
{
jointHandle_t joint = animator.GetJointHandle( shieldDef->GetString( "joint" ) );
afShield->SetBody ( this, shieldDef->GetString( "model" ), joint );
}
}
} else if ( !shield || !shield->IsHidden() ) {
return;
}
usingShield = true;
shieldWaitTime = 0;
animPrefix = "shield";
shieldStartTime = gameLocal.time;
// actionRangedAttack.fl.disabled = false;
shieldHealth = 250;
shieldConsecutiveHits = 0;
shieldLastHitTime = 0;
shield->SetShaderParm( SHADERPARM_MODE, 0 );
// Looping shield sound
StartSound ( "snd_shield_loop", SND_CHANNEL_ITEM, 0, false, NULL );
shield->Show ( );
SetShaderParm ( 6, gameLocal.time + 2000 );
}
/*
================
rvMonsterGladiator::HideShield
================
*/
void rvMonsterGladiator::HideShield ( int hideTime ) {
if ( !shield || shield->IsHidden() ) {
return;
}
usingShield = false;
animPrefix = "";
shieldWaitTime = gameLocal.GetTime()+hideTime;
// actionRangedAttack.fl.disabled = true;
shieldHealth = 0;
shieldConsecutiveHits = 0;
shieldLastHitTime = 0;
shield->SetShaderParm( SHADERPARM_MODE, 0 );
// Looping shield sound
StopSound ( SND_CHANNEL_ITEM, false );
shield->Hide ( );
}
/*
================
rvMonsterGladiator::DestroyRailgun
================
*/
void rvMonsterGladiator::DestroyRailgun ( void ) {
HideSurface ( "models/monsters/gladiator/glad_railgun" );
railgunHealth = -1;
idVec3 origin;
idMat3 axis;
jointHandle_t joint;
joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_railgun_explode", "gun_main_jt" ) );
GetJointWorldTransform ( joint, gameLocal.time, origin, axis );
gameLocal.PlayEffect ( spawnArgs, "fx_railgun_explode", origin, axis );
PlayEffect ( "fx_railgun_burn", joint, true );
GetAFPhysics()->GetBody ( "b_railgun" )->SetClipMask ( 0 );
pain.takenThisFrame = pain.threshold;
pain.lastTakenTime = gameLocal.time;
DisableAnimState( ANIMCHANNEL_LEGS );
painAnim = "pain_big";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" );
PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" );
railgunDestroyedTime = gameLocal.GetTime();
// Tweak-out the AI to be more aggressive and more likely to charge?
actionRailgunAttack.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;
actionRangedAttack.chance = 0.25f;
actionRangedAttack.maxRange = 400;
minShots = 5;
maxShots = 15;
combat.tacticalMaskAvailable &= ~AITACTICAL_HIDE_BIT;
//temporarily disable this so we can charge and get mad
//FIXME: force MELEE
actionRangedAttack.timer.Add( 6000 );
actionTimerRangedAttack.Add( 6000 );
actionMeleeAttack.timer.Reset( actionTime );
//drop any tether since we need to advance
//SetTether(NULL);
//nevermind: let scripters handle it
ExecScriptFunction( mPostWeaponDestroyed );
}
/*
================
rvMonsterGladiator::UpdateRunStatus
================
*/
bool rvMonsterGladiator::UpdateRunStatus ( void ) {
// If rushing and moving forward, run
if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.currentDirection == MOVEDIR_FORWARD ) {
move.fl.idealRunning = true;
return move.fl.running != move.fl.idealRunning;
}
// Alwasy walk with shield out
if ( usingShield ) {
move.fl.idealRunning = false;
return move.fl.running != move.fl.idealRunning;
}
return idAI::UpdateRunStatus ( );
}
/*
============
rvMonsterGladiator::SetTether
============
*/
/*
void rvMonsterGladiator::SetTether ( rvAITether* newTether ) {
if ( railgunHealth <= 0 ) {
//don't allow any tethers!
idAI::SetTether(NULL);
} else {
idAI::SetTether(newTether);
}
}
*/
/*
================
rvMonsterGladiator::FilterTactical
================
*/
int rvMonsterGladiator::FilterTactical ( int availableTactical ) {
if ( railgunHealth > 0 ) { // Only let the gladiator rush when he is really close to his enemy
if ( !enemy.range || enemy.range > combat.awareRange ) {
availableTactical &= ~AITACTICAL_MELEE_BIT;
} else {
availableTactical &= ~(AITACTICAL_RANGED_BITS);
}
} else if ( gameLocal.GetTime() - railgunDestroyedTime < 6000 ) {
availableTactical = AITACTICAL_MELEE_BIT;
}
return idAI::FilterTactical ( availableTactical );
}
/*
================
rvMonsterGladiator::AddDamageEffect
================
*/
void rvMonsterGladiator::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) {
if ( collision.c.material != NULL && (collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ) ) {
// Delay putting shield away and shooting until the shield hasnt been hit for a while
actionRangedAttack.timer.Reset ( actionTime );
actionRangedAttack.timer.Add ( shieldHitDelay );
shieldStartTime = gameLocal.time;
return;
}
return idAI::AddDamageEffect ( collision, velocity, damageDefName, inflictor );
}
/*
=====================
rvMonsterGladiator::GetDamageForLocation
=====================
*/
int rvMonsterGladiator::GetDamageForLocation( int damage, int location ) {
// If the gun was hit only do damage to it
if ( idStr::Icmp ( GetDamageGroup ( location ), "gun" ) == 0 ) {
// pain.takenThisFrame = damage;
if ( railgunHealth > 0 ){
railgunHealth -= damage;
if ( railgunHealth <= 0 ) {
DestroyRailgun ( );
}
}
return 0;
}
return idAI::GetDamageForLocation ( damage, location );
}
/*
================
rvMonsterGladiator::CanTurn
================
*/
bool rvMonsterGladiator::CanTurn ( void ) const {
if ( !idAI::CanTurn ( ) ) {
return false;
}
return move.anim_turn_angles != 0.0f || move.fl.moving;
}
/*
================
rvMonsterGladiator::Damage
================
*/
void rvMonsterGladiator::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
const char *damageDefName, const float damageScale, const int location )
{
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false );
if ( damageDef )
{
if ( usingShield )
{//shield up
if ( !damageDef->GetString( "filter_electricity", NULL ) )
{//not by electricity
//If we get hit enough times with shield up, charge forward
if ( gameLocal.GetTime() - shieldLastHitTime > 1500 )
{
shieldConsecutiveHits = 0;
}
shieldConsecutiveHits++;
shieldLastHitTime = gameLocal.GetTime();
if ( shieldConsecutiveHits > 20 && combat.tacticalCurrent != AITACTICAL_MELEE && move.fl.done )
{//really laying into us, move up
combat.tacticalUpdateTime = gameLocal.GetTime();
MoveToEnemy();
//reset counter
shieldConsecutiveHits = 0;
}
}
if ( idStr::Icmp ( GetDamageGroup ( location ), "shield" ) == 0 )
{//Hit in shield
if ( damageDef->GetString( "filter_electricity", NULL ) )
{//by electricity
shieldHealth -= damageDef->GetInt( "damage" ) * damageScale;
if ( shield )
{
shield->SetShaderParm( SHADERPARM_MODE, gameLocal.GetTime() + gameLocal.random.RandomInt(1000) + 1000 );
}
StartSound( "snd_shield_flicker", SND_CHANNEL_ANY, 0, false, NULL );
if ( shieldHealth <= 0 )
{//drop it
HideShield( gameLocal.random.RandomInt(3000)+2000 );//FIXME: when it does come back on, flicker back on?
painAnim = "pain_con";
AnimTurn( 0, true );
PerformAction ( "Torso_Pain", 2, true );
}
combat.shotAtTime = gameLocal.GetTime();
}
return;
}
}
}
idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
if ( aifl.pain )
{//hurt
if ( usingShield )
{//shield up
//move in!
combat.tacticalUpdateTime = gameLocal.GetTime();
MoveToEnemy();
//reset counter
shieldConsecutiveHits = 0;
}
}
}
/*
================
rvMonsterGladiator::Save
================
*/
void rvMonsterGladiator::Save( idSaveGame *savefile ) const {
actionRailgunAttack.Save ( savefile ) ;
savefile->WriteInt ( shots );
savefile->WriteInt ( lastShotTime );
savefile->WriteBool ( usingShield );
shield.Save( savefile );
savefile->WriteInt ( shieldStartTime );
savefile->WriteInt ( shieldWaitTime );
savefile->WriteInt ( shieldHealth );
savefile->WriteInt ( shieldConsecutiveHits );
savefile->WriteInt ( shieldLastHitTime );
savefile->WriteInt ( railgunHealth );
savefile->WriteInt ( railgunDestroyedTime );
savefile->WriteInt ( nextTurnTime ); // cnicholson: added unsaved var
mPostWeaponDestroyed.Save( savefile );
}
/*
================
rvMonsterGladiator::Restore
================
*/
void rvMonsterGladiator::Restore( idRestoreGame *savefile ) {
actionRailgunAttack.Restore ( savefile ) ;
savefile->ReadInt ( shots );
savefile->ReadInt ( lastShotTime );
savefile->ReadBool ( usingShield );
shield.Restore( savefile );
savefile->ReadInt ( shieldStartTime );
savefile->ReadInt ( shieldWaitTime );
savefile->ReadInt ( shieldHealth );
savefile->ReadInt ( shieldConsecutiveHits );
savefile->ReadInt ( shieldLastHitTime );
savefile->ReadInt ( railgunHealth );
savefile->ReadInt ( railgunDestroyedTime );
savefile->ReadInt ( nextTurnTime ); // cnicholson: added unsaved var
mPostWeaponDestroyed.Restore( savefile );
InitSpawnArgsVariables();
}
/*
================
rvMonsterGladiator::GetDebugInfo
================
*/
void rvMonsterGladiator::GetDebugInfo( debugInfoProc_t proc, void* userData ) {
// Base class first
idAI::GetDebugInfo ( proc, userData );
proc ( "idAI", "action_RailgunAttack", aiActionStatusString[actionRailgunAttack.status], userData );
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvMonsterGladiator )
STATE ( "State_Killed", rvMonsterGladiator::State_Killed )
STATE ( "Torso_BlasterAttack", rvMonsterGladiator::State_Torso_BlasterAttack )
STATE ( "Torso_RailgunAttack", rvMonsterGladiator::State_Torso_RailgunAttack )
STATE ( "Torso_ShieldStart", rvMonsterGladiator::State_Torso_ShieldStart )
STATE ( "Torso_ShieldEnd", rvMonsterGladiator::State_Torso_ShieldEnd )
STATE ( "Torso_TurnRight90", rvMonsterGladiator::State_Torso_TurnRight90 )
STATE ( "Torso_TurnLeft90", rvMonsterGladiator::State_Torso_TurnLeft90 )
STATE ( "Torso_ShieldFire", rvMonsterGladiator::State_Torso_ShieldFire )
END_CLASS_STATES
/*
================
rvMonsterGladiator::State_Killed
================
*/
stateResult_t rvMonsterGladiator::State_Killed ( const stateParms_t& parms ) {
HideShield ( );
return idAI::State_Killed ( parms );
}
/*
================
rvMonsterGladiator::State_Torso_ShieldStart
================
*/
stateResult_t rvMonsterGladiator::State_Torso_ShieldStart ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
ShowShield ( );
PlayAnim ( ANIMCHANNEL_TORSO, "start", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed
SetShaderParm ( 6, 0 );
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_ShieldEnd
================
*/
stateResult_t rvMonsterGladiator::State_Torso_ShieldEnd ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "end", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) //anim done
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed
HideShield ( 2000 );
actionRailgunAttack.timer.Reset( actionTime );
actionMeleeAttack.timer.Reset( actionTime );
actionRangedAttack.timer.Reset( actionTime );
actionTimerRangedAttack.Reset( actionTime );
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterGladiator::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, "blaster_start", parms.blendFrames );
//shots = 4;
shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale;
return SRESULT_STAGE ( STAGE_WAITSTART );
case STAGE_WAITSTART:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_start" ) ) {//anim changed
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_LOOP:
PlayAnim ( ANIMCHANNEL_TORSO, "blaster_loop", 0 );
return SRESULT_STAGE ( STAGE_WAITLOOP );
case STAGE_WAITLOOP:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed
if ( --shots <= 0 || !enemy.fl.inFov || aifl.damage ) {
PlayAnim ( ANIMCHANNEL_TORSO, "blaster_end", 0 );
return SRESULT_STAGE ( STAGE_WAITEND );
}
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_WAITEND:
if ( AnimDone ( ANIMCHANNEL_TORSO, 4 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_RailgunAttack
================
*/
stateResult_t rvMonsterGladiator::State_Torso_RailgunAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
if ( usingShield ) {
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ShieldEnd", parms.blendFrames );
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RailgunAttack", parms.blendFrames );
return SRESULT_DONE;
}
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "railgun_attack", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "railgun_attack" ) ) {//anim changed
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_TurnRight90
================
*/
stateResult_t rvMonsterGladiator::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", parms.blendFrames );
AnimTurn ( 90.0f, true );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( move.fl.moving
|| AnimDone ( ANIMCHANNEL_TORSO, 0 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_right" ) ) {//anim changed
AnimTurn ( 0, true );
nextTurnTime = gameLocal.time + 250;
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_TurnLeft90
================
*/
stateResult_t rvMonsterGladiator::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", parms.blendFrames );
AnimTurn ( 90.0f, true );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( move.fl.moving
|| AnimDone ( ANIMCHANNEL_TORSO, 0 )
|| animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!!
|| idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_left" ) ) {//anim changed
AnimTurn ( 0, true );
nextTurnTime = gameLocal.time + 250;
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterGladiator::State_Torso_ShieldFire
================
*/
stateResult_t rvMonsterGladiator::State_Torso_ShieldFire ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_ATTACK,
STAGE_ATTACK_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
if ( !enemy.ent ) {
return SRESULT_DONE;
}
shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale;
PlayCycle( ANIMCHANNEL_TORSO, "walk_aim", 1 );
return SRESULT_STAGE ( STAGE_ATTACK );
case STAGE_ATTACK:
Attack( "blaster", animator.GetJointHandle( "lft_wrist_jt"), GetEnemy() );
PlayEffect( "fx_blaster_flash", animator.GetJointHandle("lft_wrist_jt") );
lastShotTime = gameLocal.GetTime();
return SRESULT_STAGE ( STAGE_ATTACK_WAIT );
case STAGE_ATTACK_WAIT:
if ( move.fl.done )
{
return SRESULT_DONE;
}
if ( (gameLocal.GetTime()-lastShotTime) >= 250 ) {
shots--;
if ( GetEnemy() && shots > 0 )
{
return SRESULT_STAGE ( STAGE_ATTACK );
}
PlayCycle( ANIMCHANNEL_TORSO, "walk", 1 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}