610 lines
17 KiB
C++
610 lines
17 KiB
C++
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
class rvMonsterLightTank : public idAI {
|
|
public:
|
|
|
|
CLASS_PROTOTYPE( rvMonsterLightTank );
|
|
|
|
rvMonsterLightTank ( void );
|
|
|
|
void Spawn ( void );
|
|
void Save ( idSaveGame *savefile ) const;
|
|
void Restore ( idRestoreGame *savefile );
|
|
|
|
virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
|
|
virtual int GetDamageForLocation ( int damage, int location );
|
|
virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage );
|
|
|
|
protected:
|
|
|
|
virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand );
|
|
|
|
virtual bool CheckActions ( void );
|
|
virtual void OnTacticalChange ( aiTactical_t oldTactical );
|
|
|
|
virtual bool UpdateRunStatus ( void );
|
|
|
|
virtual int FilterTactical ( int availableTactical );
|
|
|
|
int flamethrowerHealth;
|
|
int chargeDebounce;
|
|
void DestroyFlamethrower ( void );
|
|
|
|
private:
|
|
|
|
int standingMeleeNoAttackTime;
|
|
bool damaged;
|
|
// bool damagedMove;
|
|
int powerUpStartTime;
|
|
|
|
rvAIAction actionFlameThrower;
|
|
rvAIAction actionPowerUp;
|
|
rvAIAction actionChargeAttack;
|
|
|
|
bool CheckAction_PowerUp ( rvAIAction* action, int animNum );
|
|
virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum );
|
|
virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum );
|
|
bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum );
|
|
|
|
// Global States
|
|
stateResult_t State_Killed ( const stateParms_t& parms );
|
|
|
|
// Torso States
|
|
stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_Pain ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms );
|
|
stateResult_t State_Torso_PowerUp ( const stateParms_t& parms );
|
|
|
|
rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed
|
|
|
|
CLASS_STATES_PROTOTYPE ( rvMonsterLightTank );
|
|
};
|
|
|
|
CLASS_DECLARATION( idAI, rvMonsterLightTank )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::rvMonsterLightTank
|
|
================
|
|
*/
|
|
rvMonsterLightTank::rvMonsterLightTank ( void ) {
|
|
damaged = false;
|
|
standingMeleeNoAttackTime = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::Spawn
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::Spawn ( void ) {
|
|
// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" );
|
|
flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" );
|
|
chargeDebounce = 0;
|
|
|
|
actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK );
|
|
actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 );
|
|
actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK );
|
|
|
|
const char *func;
|
|
if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) )
|
|
{
|
|
mPostWeaponDestroyed.Init( func );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::Save
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::Save ( idSaveGame *savefile ) const {
|
|
savefile->WriteInt( flamethrowerHealth );
|
|
savefile->WriteInt( chargeDebounce );
|
|
|
|
savefile->WriteBool( damaged );
|
|
// savefile->WriteBool( damagedMove );
|
|
savefile->WriteInt( powerUpStartTime );
|
|
|
|
actionFlameThrower.Save( savefile );
|
|
actionPowerUp.Save( savefile );
|
|
actionChargeAttack.Save( savefile );
|
|
mPostWeaponDestroyed.Save( savefile );
|
|
savefile->WriteInt( standingMeleeNoAttackTime );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::Restore
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) {
|
|
savefile->ReadInt( flamethrowerHealth );
|
|
savefile->ReadInt( chargeDebounce );
|
|
|
|
savefile->ReadBool( damaged );
|
|
// savefile->ReadBool( damagedMove );
|
|
savefile->ReadInt( powerUpStartTime );
|
|
|
|
actionFlameThrower.Restore( savefile );
|
|
actionPowerUp.Restore( savefile );
|
|
actionChargeAttack.Restore( savefile );
|
|
mPostWeaponDestroyed.Restore( savefile );
|
|
savefile->ReadInt( standingMeleeNoAttackTime );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::FilterTactical
|
|
================
|
|
*/
|
|
int rvMonsterLightTank::FilterTactical ( int availableTactical ) {
|
|
if ( flamethrowerHealth > 0 ) {
|
|
// Only let the light tank use ranged tactical when he is really far from his enemy
|
|
if ( !enemy.range || enemy.range < combat.attackRange[1] ) {
|
|
availableTactical &= ~(AITACTICAL_RANGED_BITS);
|
|
}
|
|
}
|
|
if ( chargeDebounce > gameLocal.GetTime() )
|
|
{//don't charge again any time soon
|
|
availableTactical &= ~(AITACTICAL_MELEE_BIT);
|
|
}
|
|
|
|
return idAI::FilterTactical( availableTactical );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::OnTacticalChange
|
|
|
|
Enable/Disable the ranged attack based on whether the grunt needs it
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) {
|
|
switch ( combat.tacticalCurrent ) {
|
|
case AITACTICAL_MELEE:
|
|
actionFlameThrower.fl.disabled = true;
|
|
actionRangedAttack.fl.disabled = true;
|
|
break;
|
|
|
|
default:
|
|
actionFlameThrower.fl.disabled = false;
|
|
actionRangedAttack.fl.disabled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::UpdateRunStatus
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::UpdateRunStatus ( void ) {
|
|
// If rushing, run
|
|
if ( combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{
|
|
move.fl.idealRunning = true;
|
|
}
|
|
else
|
|
{
|
|
move.fl.idealRunning = false;
|
|
}
|
|
|
|
return move.fl.running != move.fl.idealRunning;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::DestroyFlamethrower
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::DestroyFlamethrower ( void ) {
|
|
|
|
StopEffect ( "fx_flame_muzzle" );
|
|
//HideSurface ( "models/monsters/light_tank/flamethrower" );
|
|
//GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 );
|
|
animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) );
|
|
animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) );
|
|
animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) );
|
|
flamethrowerHealth = -1;
|
|
|
|
|
|
pain.takenThisFrame = pain.threshold;
|
|
pain.lastTakenTime = gameLocal.time;
|
|
// flamethrowerDestroyedTime = gameLocal.GetTime();
|
|
|
|
// Tweak-out the AI to be more aggressive and more likely to charge?
|
|
|
|
PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") );
|
|
PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true );
|
|
|
|
DisableAnimState( ANIMCHANNEL_LEGS );
|
|
painAnim = "damaged";
|
|
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" );
|
|
PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" );
|
|
|
|
chargeDebounce = 0;
|
|
damaged = true;
|
|
animPrefix = "damage";
|
|
|
|
actionFlameThrower.fl.disabled = true;
|
|
actionRangedAttack.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;
|
|
|
|
combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS);
|
|
|
|
actionMeleeAttack.timer.Reset( actionTime );
|
|
actionChargeAttack.timer.Reset( actionTime );
|
|
|
|
ExecScriptFunction( mPostWeaponDestroyed );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvMonsterLightTank::Pain
|
|
=====================
|
|
*/
|
|
bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location );
|
|
if ( move.fl.moving && move.fl.running )
|
|
{
|
|
painAnim = "pain_charge";
|
|
}
|
|
return didPain;
|
|
}
|
|
/*
|
|
=====================
|
|
rvMonsterLightTank::GetDamageForLocation
|
|
=====================
|
|
*/
|
|
int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) {
|
|
// If the flamethrower was hit only do damage to it
|
|
if( !damaged && !aifl.dead )
|
|
{
|
|
if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) {
|
|
// pain.takenThisFrame = damage;
|
|
if ( flamethrowerHealth > 0 ){
|
|
flamethrowerHealth -= damage;
|
|
if ( flamethrowerHealth <= 0 ) {
|
|
DestroyFlamethrower();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return idAI::GetDamageForLocation ( damage, location );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::DamageFeedback
|
|
|
|
callback function for when another entity recieved damage from this entity
|
|
================
|
|
*/
|
|
void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
|
|
if ( !damaged )
|
|
{
|
|
if ( victim == GetEnemy() && inflictor == this )
|
|
{
|
|
if ( combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{//okay, get out of melee state for now
|
|
chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000;
|
|
}
|
|
}
|
|
}
|
|
|
|
idAI::DamageFeedback( victim, inflictor, damage );
|
|
}
|
|
|
|
/*
|
|
============
|
|
rvMonsterLightTank::OnStopMoving
|
|
============
|
|
*/
|
|
void rvMonsterLightTank::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) {
|
|
//MCG - once you get to your position, attack immediately (no pause)
|
|
//FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states?
|
|
if ( GetEnemy() )
|
|
{
|
|
if ( combat.tacticalCurrent == AITACTICAL_RANGED )
|
|
{
|
|
actionRangedAttack.timer.Clear( actionTime );
|
|
actionTimerRangedAttack.Clear( actionTime );
|
|
actionFlameThrower.timer.Clear( actionTime );
|
|
}
|
|
else if ( combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{//so we don't stand there and look stupid
|
|
actionMeleeAttack.timer.Clear( actionTime );
|
|
actionChargeAttack.timer.Clear( actionTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::CheckAction_PowerUp
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum )
|
|
{
|
|
if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{
|
|
return false;
|
|
}
|
|
if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::CheckAction_EvadeLeft
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) {
|
|
if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{
|
|
return false;
|
|
}
|
|
return idAI::CheckAction_EvadeLeft( action, animNum );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::CheckAction_EvadeRight
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) {
|
|
if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE )
|
|
{
|
|
return false;
|
|
}
|
|
return idAI::CheckAction_EvadeRight( action, animNum );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::CheckAction_ChargeAttack
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) {
|
|
if ( !enemy.ent || !enemy.fl.inFov ) {
|
|
return false;
|
|
}
|
|
if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) {
|
|
return false;
|
|
}
|
|
if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::CheckActions
|
|
================
|
|
*/
|
|
bool rvMonsterLightTank::CheckActions ( void ) {
|
|
if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) {
|
|
return true;
|
|
}
|
|
if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( CheckPainActions ( ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) ||
|
|
PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) ||
|
|
PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) ||
|
|
PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) {
|
|
return true;
|
|
} else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) {
|
|
standingMeleeNoAttackTime = 0;
|
|
return true;
|
|
} else {
|
|
if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER
|
|
&& actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER
|
|
&& actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE )
|
|
{//melee attack fail for any reason other than timer?
|
|
if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving )
|
|
{//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range!
|
|
//allow ranged attack
|
|
if ( !standingMeleeNoAttackTime )
|
|
{
|
|
standingMeleeNoAttackTime = gameLocal.GetTime();
|
|
}
|
|
else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() )
|
|
{//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack
|
|
actionFlameThrower.fl.disabled = false;
|
|
actionRangedAttack.fl.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvMonsterLightTank )
|
|
STATE ( "State_Killed", rvMonsterLightTank::State_Killed )
|
|
|
|
STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower )
|
|
STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink )
|
|
STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain )
|
|
STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack )
|
|
STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp )
|
|
|
|
END_CLASS_STATES
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Killed
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) {
|
|
StopEffect ( "fx_destroy_arm_trail" );
|
|
StopEffect ( "fx_flame_muzzle" );
|
|
return idAI::State_Killed ( parms );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Torso_FlameThrower
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) {
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
|
|
// Flame effect
|
|
PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true );
|
|
|
|
// Loop the flame animation
|
|
PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames );
|
|
|
|
// Delay start the flame thrower think to ensure he flames for a minimum time
|
|
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 );
|
|
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Torso_FlameThrowerThink
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) {
|
|
if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
|
|
StopEffect ( "fx_flame_muzzle" );
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
return SRESULT_WAIT;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Torso_Pain
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) {
|
|
|
|
StopEffect ( "fx_flame_muzzle" );
|
|
|
|
// Default pain animation
|
|
return idAI::State_Torso_Pain ( parms );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Torso_RangedAttack
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_START,
|
|
STAGE_FINISH,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_START:
|
|
// If moving switch to the moving ranged attack (torso only)
|
|
if ( !move.fl.moving || !FacingIdeal() ) {
|
|
// Full body animations
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames );
|
|
}
|
|
else
|
|
{
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames );
|
|
}
|
|
|
|
return SRESULT_STAGE ( STAGE_FINISH );
|
|
|
|
case STAGE_FINISH:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvMonsterLightTank::State_Torso_PowerUp
|
|
================
|
|
*/
|
|
stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_START,
|
|
STAGE_START_WAIT,
|
|
STAGE_LOOP,
|
|
STAGE_FINISH,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_START:
|
|
// If moving switch to the moving ranged attack (torso only)
|
|
//fl.takedamage = false;
|
|
powerUpStartTime = gameLocal.GetTime();
|
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames );
|
|
|
|
return SRESULT_STAGE ( STAGE_START_WAIT );
|
|
|
|
case STAGE_START_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_LOOP );
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_LOOP:
|
|
health++;
|
|
if ( health >= spawnArgs.GetInt( "health" )
|
|
|| gameLocal.GetTime() - powerUpStartTime > 3875 )
|
|
{//full health or been charging up for 3 full anim loops
|
|
PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_FINISH );
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_FINISH:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|