662 lines
20 KiB
C++
662 lines
20 KiB
C++
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
//------------------------------------------------------------
|
|
class rvMonsterBossBuddy : public idAI
|
|
//------------------------------------------------------------
|
|
{
|
|
public:
|
|
|
|
CLASS_PROTOTYPE( rvMonsterBossBuddy );
|
|
|
|
rvMonsterBossBuddy( void );
|
|
|
|
void Spawn ( void );
|
|
void InitSpawnArgsVariables ( void );
|
|
void Save ( idSaveGame *savefile ) const;
|
|
void Restore ( idRestoreGame *savefile );
|
|
|
|
bool CanTurn ( void ) const;
|
|
|
|
void Think ( void );
|
|
bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
|
|
void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
|
|
void OnWakeUp ( void );
|
|
|
|
// Add some dynamic externals for debugging
|
|
void GetDebugInfo ( debugInfoProc_t proc, void* userData );
|
|
|
|
protected:
|
|
|
|
bool CheckActions ( void );
|
|
// void PerformAction ( const char* stateName, int blendFrames, bool noPain );
|
|
// bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer );
|
|
|
|
void AdjustShieldState ( bool becomeShielded );
|
|
void ReduceShields ( int amount );
|
|
|
|
int mShots;
|
|
int mShields;
|
|
int mMaxShields;
|
|
int mLastDamageTime;
|
|
int mShieldsLastFor; // read from def file, shouldn't need to be saved.
|
|
|
|
bool mIsShielded;
|
|
bool mRequestedZoneMove;
|
|
bool mRequestedRecharge;
|
|
|
|
//bool mCanIdle;
|
|
//bool mChaseMode;
|
|
|
|
private:
|
|
|
|
rvAIAction mActionRocketAttack;
|
|
rvAIAction mActionSlashMoveAttack;
|
|
rvAIAction mActionLightningAttack;
|
|
rvAIAction mActionDarkMatterAttack;
|
|
rvAIAction mActionMeleeMoveAttack;
|
|
rvAIAction mActionMeleeAttack;
|
|
|
|
rvScriptFuncUtility mRequestRecharge; // script to run when a projectile is launched
|
|
rvScriptFuncUtility mRequestZoneMove; // script to run when he should move to the next zone
|
|
|
|
stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_SlashAttack ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms );
|
|
|
|
void Event_RechargeShields( float amount );
|
|
|
|
CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy );
|
|
};
|
|
|
|
#define BOSS_BUDDY_MAX_SHIELDS 8500
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::rvMonsterBossBuddy
|
|
//------------------------------------------------------------
|
|
rvMonsterBossBuddy::rvMonsterBossBuddy( void )
|
|
{
|
|
mMaxShields = BOSS_BUDDY_MAX_SHIELDS;
|
|
mShields = mMaxShields;
|
|
mLastDamageTime = 0;
|
|
mIsShielded = false;
|
|
mRequestedZoneMove = false;
|
|
mRequestedRecharge = false;
|
|
// mCanIdle = false;
|
|
// mChaseMode = true;
|
|
}
|
|
|
|
void rvMonsterBossBuddy::InitSpawnArgsVariables ( void )
|
|
{
|
|
mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f);
|
|
mMaxShields = BOSS_BUDDY_MAX_SHIELDS;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Spawn
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Spawn( void )
|
|
{
|
|
mActionRocketAttack.Init( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK );
|
|
mActionLightningAttack.Init( spawnArgs,"action_lightningAttack", NULL, AIACTIONF_ATTACK );
|
|
mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack", NULL, AIACTIONF_ATTACK );
|
|
mActionMeleeMoveAttack.Init( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK );
|
|
mActionSlashMoveAttack.Init( spawnArgs, "action_slashMoveAttack", NULL, AIACTIONF_ATTACK );
|
|
mActionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK );
|
|
|
|
InitSpawnArgsVariables();
|
|
mShields = mMaxShields;
|
|
|
|
const char *func;
|
|
if ( spawnArgs.GetString( "requestRecharge", "", &func ) )
|
|
{
|
|
mRequestRecharge.Init( func );
|
|
}
|
|
if ( spawnArgs.GetString( "requestZoneMove", "", &func ) )
|
|
{
|
|
mRequestZoneMove.Init( func );
|
|
}
|
|
|
|
HideSurface( "models/monsters/bossbuddy/forcefield" );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Save
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const
|
|
{
|
|
savefile->WriteInt( mShots );
|
|
savefile->WriteInt( mShields );
|
|
savefile->WriteInt( mMaxShields );
|
|
savefile->WriteInt( mLastDamageTime );
|
|
savefile->WriteBool( mIsShielded );
|
|
savefile->WriteBool( mRequestedZoneMove );
|
|
savefile->WriteBool( mRequestedRecharge );
|
|
|
|
mActionRocketAttack.Save( savefile );
|
|
mActionLightningAttack.Save( savefile );
|
|
mActionDarkMatterAttack.Save( savefile );
|
|
mActionMeleeMoveAttack.Save( savefile );
|
|
mActionMeleeAttack.Save( savefile );
|
|
mActionSlashMoveAttack.Save( savefile );
|
|
|
|
mRequestRecharge.Save( savefile );
|
|
mRequestZoneMove.Save( savefile );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Restore
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Restore( idRestoreGame *savefile )
|
|
{
|
|
savefile->ReadInt( mShots );
|
|
savefile->ReadInt( mShields );
|
|
savefile->ReadInt( mMaxShields );
|
|
savefile->ReadInt( mLastDamageTime );
|
|
savefile->ReadBool( mIsShielded );
|
|
savefile->ReadBool( mRequestedZoneMove );
|
|
savefile->ReadBool( mRequestedRecharge );
|
|
|
|
mActionRocketAttack.Restore( savefile );
|
|
mActionLightningAttack.Restore( savefile );
|
|
mActionDarkMatterAttack.Restore( savefile );
|
|
mActionMeleeMoveAttack.Restore( savefile );
|
|
mActionMeleeAttack.Restore( savefile );
|
|
mActionSlashMoveAttack.Restore( savefile );
|
|
|
|
mRequestRecharge.Restore( savefile );
|
|
mRequestZoneMove.Restore( savefile );
|
|
|
|
InitSpawnArgsVariables();
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBerserker::GetDebugInfo
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData )
|
|
{
|
|
// Base class first
|
|
idAI::GetDebugInfo( proc, userData );
|
|
|
|
proc ( "idAI", "action_darkMatterAttack", aiActionStatusString[mActionDarkMatterAttack.status], userData );
|
|
proc ( "idAI", "action_rocketAttack", aiActionStatusString[mActionRocketAttack.status], userData );
|
|
proc ( "idAI", "action_meleeMoveAttack", aiActionStatusString[mActionMeleeMoveAttack.status], userData );
|
|
proc ( "idAI", "action_lightningAttack", aiActionStatusString[mActionLightningAttack.status], userData );
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
// Custom Script Events
|
|
//--------------------------------------------------------------
|
|
const idEventDef EV_RechargeShields( "rechargeShields", "f", 'f' );
|
|
|
|
CLASS_DECLARATION( idAI, rvMonsterBossBuddy )
|
|
EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields )
|
|
END_CLASS
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Event_RechargeShields
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Event_RechargeShields( float amount )
|
|
{
|
|
mShields += (int)amount;
|
|
|
|
if ( mShields >= mMaxShields )
|
|
{
|
|
// charge is done
|
|
mShields = mMaxShields;
|
|
idThread::ReturnInt(0);
|
|
|
|
// reset request states
|
|
mRequestedRecharge = false;
|
|
mRequestedZoneMove = false;
|
|
|
|
// shield warning no longer neede for now
|
|
gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn");
|
|
}
|
|
else
|
|
{
|
|
// still charging
|
|
idThread::ReturnInt(1);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::ReduceShields
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::ReduceShields( int amount )
|
|
{
|
|
mShields -= amount;
|
|
|
|
// if no mShields left... or the last time we took damage was more than 8 seconds ago
|
|
if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time )
|
|
{
|
|
//....remove the shielding
|
|
AdjustShieldState( false );
|
|
}
|
|
|
|
if (mShields < 1000)
|
|
{
|
|
if (!mRequestedRecharge )
|
|
{
|
|
// entering a dangerous state! Get to the recharge station, fast!
|
|
gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn");
|
|
ExecScriptFunction( mRequestRecharge );
|
|
mRequestedRecharge = true;
|
|
}
|
|
}
|
|
else if (mShields < 4000)
|
|
{
|
|
if ( !mRequestedZoneMove )
|
|
{
|
|
// Getting low, so move him close to the next zone so he can be ready to recharge
|
|
ExecScriptFunction( mRequestZoneMove );
|
|
mRequestedZoneMove = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::AdjustShieldState
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded )
|
|
{
|
|
// only do the work for adjusting the state when it doesn't match our current state
|
|
if ( !mIsShielded && becomeShielded )
|
|
{
|
|
// Activate Shields!
|
|
ShowSurface( "models/monsters/bossbuddy/forcefield" );
|
|
StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL );
|
|
gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" );
|
|
}
|
|
else if ( mIsShielded && !becomeShielded )
|
|
{
|
|
// Deactivate Shields!
|
|
HideSurface( "models/monsters/bossbuddy/forcefield" );
|
|
StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL );
|
|
// gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" );
|
|
}
|
|
mIsShielded = becomeShielded;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Damage
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Think()
|
|
{
|
|
if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead )
|
|
{
|
|
// run simple shielding logic when we have them active
|
|
if ( mIsShielded )
|
|
{
|
|
ReduceShields( 1 );
|
|
|
|
// if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields
|
|
if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time )
|
|
{
|
|
AdjustShieldState( false );
|
|
}
|
|
}
|
|
|
|
// update shield bar
|
|
idUserInterface *hud = gameLocal.GetLocalPlayer()->hud;
|
|
if ( hud )
|
|
{
|
|
float percent = ((float)mShields/mMaxShields);
|
|
|
|
hud->SetStateFloat( "boss_shield_percent", percent );
|
|
hud->HandleNamedEvent( "updateBossShield" );
|
|
}
|
|
}
|
|
|
|
if ( move.obstacle.GetEntity() )
|
|
{
|
|
PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack );
|
|
}
|
|
|
|
idAI::Think();
|
|
}
|
|
/*
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::PerformAction
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain )
|
|
{
|
|
// Allow movement in actions
|
|
move.fl.allowAnimMove = true;
|
|
|
|
if ( mChaseMode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Start the action
|
|
SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames );
|
|
|
|
// Always call finish action when the action is done, it will clear the action flag
|
|
aifl.action = true;
|
|
PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR );
|
|
|
|
// Go back to idle when done-- sometimes.
|
|
if ( mCanIdle )
|
|
{
|
|
PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames );
|
|
}
|
|
|
|
// Main state will wait until action is finished before continuing
|
|
InterruptState( "Wait_ActionNoPain" );
|
|
OnStartAction( );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::PerformAction
|
|
//------------------------------------------------------------
|
|
bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer )
|
|
{
|
|
if ( mChaseMode )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return idAI::PerformAction( action, condition ,timer );
|
|
}
|
|
*/
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Damage
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
|
|
const char *damageDefName, const float damageScale, const int location )
|
|
{
|
|
// get damage amount so we can decay the shields and check for ignoreShields
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false );
|
|
if ( !damageDef )
|
|
{
|
|
gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
|
|
}
|
|
|
|
// NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def,
|
|
// we don't run shielding logic
|
|
bool directDamage = damageDef->GetBool( "ignoreShields" );
|
|
|
|
int loc = location;
|
|
if ( directDamage )
|
|
{
|
|
// Lame, I know, but hack the location
|
|
loc = INVALID_JOINT;
|
|
}
|
|
else if ( attacker == this )
|
|
{
|
|
// can't damage self
|
|
return;
|
|
}
|
|
|
|
float scale = 1;
|
|
|
|
// Shields will activate for a set amount of time when damage is being taken
|
|
mLastDamageTime = gameLocal.time;
|
|
|
|
// if shields are active, we should try to 'eat' them before directing damage to the BB
|
|
if ( mIsShielded && !directDamage )
|
|
{
|
|
// BB is resistant to any kind of splash damage when the shields are up
|
|
if ( loc <= INVALID_JOINT )
|
|
{
|
|
// damage must have been done by splash damage
|
|
return;
|
|
}
|
|
|
|
int damage = damageDef->GetInt( "damage" );
|
|
ReduceShields( damage * 8 );
|
|
|
|
// Shielding dramatically reduces actual damage done to BB
|
|
scale = 0.1f;
|
|
}
|
|
else if ( mShields > 0 ) // not currently shielded...does he have shields to use?
|
|
{
|
|
// Yep, so turn them on
|
|
AdjustShieldState( true );
|
|
}
|
|
|
|
idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::Pain
|
|
//------------------------------------------------------------
|
|
bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location )
|
|
{
|
|
// immune to small damage. Is this safe to do?
|
|
if ( damage > 5 )
|
|
{
|
|
rvClientCrawlEffect* effect;
|
|
effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) );
|
|
effect->Play( gameLocal.time, false );
|
|
|
|
return idAI::Pain( inflictor, attacker, damage, dir, location );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::CheckActions
|
|
//------------------------------------------------------------
|
|
bool rvMonsterBossBuddy::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( &mActionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ||
|
|
PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack ))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( PerformAction( &mActionDarkMatterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
|
|
PerformAction( &mActionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
|
|
PerformAction( &mActionLightningAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return idAI::CheckActions( );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::CanTurn
|
|
//------------------------------------------------------------
|
|
bool rvMonsterBossBuddy::CanTurn( void ) const
|
|
{
|
|
/* if ( !idAI::CanTurn ( ) ) {
|
|
return false;
|
|
}
|
|
return move.anim_turn_angles != 0.0f || move.fl.moving;
|
|
*/
|
|
return idAI::CanTurn ( );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::OnWakeUp
|
|
//------------------------------------------------------------
|
|
void rvMonsterBossBuddy::OnWakeUp( void )
|
|
{
|
|
mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity );
|
|
mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity );
|
|
idAI::OnWakeUp( );
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// States
|
|
//------------------------------------------------------------
|
|
|
|
CLASS_STATES_DECLARATION( rvMonsterBossBuddy )
|
|
STATE( "Torso_RocketAttack", rvMonsterBossBuddy::State_Torso_RocketAttack )
|
|
STATE( "Torso_SlashAttack", rvMonsterBossBuddy::State_Torso_SlashAttack )
|
|
STATE( "Torso_TurnRight90", rvMonsterBossBuddy::State_Torso_TurnRight90 )
|
|
STATE( "Torso_TurnLeft90", rvMonsterBossBuddy::State_Torso_TurnLeft90 )
|
|
END_CLASS_STATES
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::State_Torso_RocketAttack
|
|
//------------------------------------------------------------
|
|
stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( 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, "attack_rocket2start", parms.blendFrames );
|
|
mShots = (gameLocal.random.RandomInt( 3 ) + 2) * 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, "attack_rocket2loop2", 0 );
|
|
return SRESULT_STAGE( STAGE_WAITLOOP );
|
|
|
|
case STAGE_WAITLOOP:
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 0 ))
|
|
{
|
|
if ( --mShots <= 0 || // exhausted mShots? .. or
|
|
(!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) || // ... player is no longer visible .. or
|
|
( enemy.ent && DistanceTo(enemy.ent)<256 ) ) // ... player is so close, we prolly want to do a melee attack
|
|
{
|
|
PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 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;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::State_Torso_SlashAttack
|
|
//------------------------------------------------------------
|
|
stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms )
|
|
{
|
|
enum
|
|
{
|
|
STAGE_INIT,
|
|
STAGE_WAIT_FIRST_SWIPE,
|
|
STAGE_WAIT_FINISH
|
|
};
|
|
switch ( parms.stage )
|
|
{
|
|
case STAGE_INIT:
|
|
DisableAnimState( ANIMCHANNEL_LEGS );
|
|
PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames );
|
|
return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE );
|
|
|
|
case STAGE_WAIT_FIRST_SWIPE:
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 0 ))
|
|
{
|
|
PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames );
|
|
return SRESULT_STAGE( STAGE_WAIT_FINISH );
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_WAIT_FINISH:
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 0 ))
|
|
{
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::State_Torso_TurnRight90
|
|
//------------------------------------------------------------
|
|
stateResult_t rvMonsterBossBuddy::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 ))
|
|
{
|
|
AnimTurn( 0, true );
|
|
combat.investigateTime = gameLocal.time + 250;
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// rvMonsterBossBuddy::State_Torso_TurnLeft90
|
|
//------------------------------------------------------------
|
|
stateResult_t rvMonsterBossBuddy::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 ))
|
|
{
|
|
AnimTurn( 0, true );
|
|
combat.investigateTime = gameLocal.time + 250;
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|