902 lines
26 KiB
C++
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;
|
|
}
|