593 lines
15 KiB
C++
593 lines
15 KiB
C++
|
|
||
|
#include "../../idlib/precompiled.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include "../Game_local.h"
|
||
|
|
||
|
/*
|
||
|
===============================================================================
|
||
|
|
||
|
rvAIActionTimer
|
||
|
|
||
|
===============================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::rvAIActionTimer
|
||
|
================
|
||
|
*/
|
||
|
rvAIActionTimer::rvAIActionTimer ( void ) {
|
||
|
time = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Init
|
||
|
================
|
||
|
*/
|
||
|
bool rvAIActionTimer::Init ( const idDict& args, const char* name ) {
|
||
|
rate = SEC2MS ( args.GetFloat ( va("%s_rate",name), "0" ) );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Save
|
||
|
================
|
||
|
*/
|
||
|
void rvAIActionTimer::Save ( idSaveGame *savefile ) const {
|
||
|
savefile->WriteInt ( rate );
|
||
|
savefile->WriteInt ( time );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Restore
|
||
|
================
|
||
|
*/
|
||
|
void rvAIActionTimer::Restore ( idRestoreGame *savefile ) {
|
||
|
savefile->ReadInt ( rate );
|
||
|
savefile->ReadInt ( time );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Reset
|
||
|
================
|
||
|
*/
|
||
|
void rvAIActionTimer::Reset ( int currentTime, float diversity, float scale ) {
|
||
|
float _rate = rate * scale;
|
||
|
time = currentTime + (-gameLocal.random.RandomInt( 2.0f * _rate * diversity ) + _rate * diversity + _rate);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Clear
|
||
|
================
|
||
|
*/
|
||
|
void rvAIActionTimer::Clear ( int currentTime ) {
|
||
|
time = currentTime;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIActionTimer::Add
|
||
|
================
|
||
|
*/
|
||
|
void rvAIActionTimer::Add ( int _time, float diversity ) {
|
||
|
time += (-gameLocal.random.RandomInt( 2.0f * _time * diversity ) + _time * diversity + _time);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============================================================================
|
||
|
|
||
|
rvAIAction
|
||
|
|
||
|
===============================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIAction::rvAIAction
|
||
|
================
|
||
|
*/
|
||
|
rvAIAction::rvAIAction ( void ) {
|
||
|
memset ( &fl, 0, sizeof(fl) );
|
||
|
status = STATUS_UNUSED;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIAction::Init
|
||
|
================
|
||
|
*/
|
||
|
bool rvAIAction::Init ( const idDict& args, const char* name, const char* defaultState, int _flags ) {
|
||
|
const idKeyValue* kv;
|
||
|
|
||
|
if ( _flags & AIACTIONF_ATTACK ) {
|
||
|
fl.isAttack = true;
|
||
|
}
|
||
|
if ( _flags & AIACTIONF_MELEE ) {
|
||
|
fl.isMelee = true;
|
||
|
}
|
||
|
|
||
|
// Initialize timer
|
||
|
timer.Init ( args, name );
|
||
|
|
||
|
// Is this action enabled?
|
||
|
fl.disabled = !args.GetBool ( va("%s",name), "0" );
|
||
|
fl.noPain = args.GetBool ( va("%s_nopain",name), "0" );
|
||
|
fl.noTurn = args.GetBool ( va("%s_noturn",name), "1" );
|
||
|
fl.overrideLegs = args.GetBool ( va("%s_overrideLegs",name), "1" );
|
||
|
fl.noSimpleThink = args.GetBool ( va("%s_nosimplethink",name), "0" );
|
||
|
|
||
|
blendFrames = args.GetInt ( va("%s_blendFrames",name), "4" );
|
||
|
failRate = SEC2MS ( args.GetInt ( va("%s_failRate",name), ".1" ) );
|
||
|
|
||
|
minRange = args.GetInt ( va("%s_minRange",name), "0" );
|
||
|
maxRange = args.GetInt ( va("%s_maxRange",name), (_flags & AIACTIONF_ATTACK ) ? "-1" : "0" );
|
||
|
minRange2d = args.GetInt ( va("%s_minRange2d",name), "0" );
|
||
|
maxRange2d = args.GetInt ( va("%s_maxRange2d",name), "0" );
|
||
|
|
||
|
chance = args.GetFloat ( va("%s_chance",name), "1" );
|
||
|
diversity = args.GetFloat ( va("%s_diversity", name ), ".5" );
|
||
|
|
||
|
// action state
|
||
|
state = args.GetString ( va("%s_state",name), (!defaultState||!*defaultState) ? "Torso_Action" : defaultState );
|
||
|
|
||
|
// allow for multiple animations
|
||
|
const char* prefix = va("%s_anim",name);
|
||
|
for ( kv = args.MatchPrefix ( prefix, NULL ); kv; kv = args.MatchPrefix ( prefix, kv ) ) {
|
||
|
if ( kv->GetValue ( ).Length ( ) ) {
|
||
|
anims.Append ( kv->GetValue ( ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIAction::Save
|
||
|
================
|
||
|
*/
|
||
|
void rvAIAction::Save ( idSaveGame *savefile ) const {
|
||
|
int i;
|
||
|
|
||
|
savefile->Write( &fl, sizeof( fl ) );
|
||
|
|
||
|
savefile->WriteInt ( anims.Num ( ) );
|
||
|
for ( i = 0; i < anims.Num(); i ++ ) {
|
||
|
savefile->WriteString ( anims[i] );
|
||
|
}
|
||
|
savefile->WriteString ( state );
|
||
|
|
||
|
timer.Save ( savefile );
|
||
|
|
||
|
savefile->WriteInt ( blendFrames );
|
||
|
savefile->WriteInt ( failRate );
|
||
|
|
||
|
savefile->WriteFloat ( minRange );
|
||
|
savefile->WriteFloat ( maxRange );
|
||
|
savefile->WriteFloat ( minRange2d );
|
||
|
savefile->WriteFloat ( maxRange2d );
|
||
|
|
||
|
savefile->WriteFloat ( chance );
|
||
|
savefile->WriteFloat ( diversity );
|
||
|
|
||
|
savefile->WriteInt ( (int)status );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
rvAIAction::Restore
|
||
|
================
|
||
|
*/
|
||
|
void rvAIAction::Restore ( idRestoreGame *savefile ) {
|
||
|
int num;
|
||
|
|
||
|
savefile->Read( &fl, sizeof( fl ) );
|
||
|
|
||
|
savefile->ReadInt ( num );
|
||
|
anims.Clear ( );
|
||
|
anims.SetNum ( num );
|
||
|
for ( num--; num >= 0; num -- ) {
|
||
|
savefile->ReadString ( anims[num] );
|
||
|
}
|
||
|
savefile->ReadString ( state );
|
||
|
|
||
|
timer.Restore ( savefile );
|
||
|
|
||
|
savefile->ReadInt ( blendFrames );
|
||
|
savefile->ReadInt ( failRate );
|
||
|
|
||
|
savefile->ReadFloat ( minRange );
|
||
|
savefile->ReadFloat ( maxRange );
|
||
|
savefile->ReadFloat ( minRange2d );
|
||
|
savefile->ReadFloat ( maxRange2d );
|
||
|
|
||
|
savefile->ReadFloat ( chance );
|
||
|
savefile->ReadFloat ( diversity );
|
||
|
|
||
|
savefile->ReadInt ( (int&)status );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============================================================================
|
||
|
|
||
|
Actions
|
||
|
|
||
|
===============================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_EvadeLeft
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) {
|
||
|
if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) {
|
||
|
return false;
|
||
|
}
|
||
|
// TODO: dont evade unless it was coming from directly in front of us
|
||
|
if ( animNum != -1 && !TestAnimMove ( animNum ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_EvadeRight
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) {
|
||
|
if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){
|
||
|
return false;
|
||
|
}
|
||
|
// TODO: Dont eveade unless it was coming from directly in front of us
|
||
|
if ( animNum != -1 && !TestAnimMove ( animNum ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_JumpBack
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_JumpBack ( rvAIAction* action, int animNum ) {
|
||
|
// Jump back after taking damage
|
||
|
if ( !aifl.damage ) {
|
||
|
return false;
|
||
|
}
|
||
|
// TODO: enemy must be in front to jump backwards
|
||
|
|
||
|
// Can we actually move backwards?
|
||
|
if ( !TestAnimMove ( animNum ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_RangedAttack
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) {
|
||
|
if ( !enemy.ent || !enemy.fl.inFov ) {
|
||
|
return false;
|
||
|
}
|
||
|
if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) {
|
||
|
return false;
|
||
|
}
|
||
|
if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_MeleeAttack
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) {
|
||
|
if ( !enemy.ent || !enemy.fl.inFov ) {
|
||
|
return false;
|
||
|
}
|
||
|
if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckAction_LeapAttack
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) {
|
||
|
if ( !enemy.ent || !enemy.fl.inFov ) {
|
||
|
return false;
|
||
|
}
|
||
|
if ( enemy.range > 64.0f && !TestAnimMove ( animNum, enemy.ent ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
// Must be looking right at the enemy to leap
|
||
|
if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 4 ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::UpdateAction
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::UpdateAction ( void ) {
|
||
|
// Update action MUST be called from the main state loop
|
||
|
assert ( stateThread.IsExecuting ( ) );
|
||
|
|
||
|
// If an action is already running then dont let another start
|
||
|
if ( aifl.action ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return CheckActions ( );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckPainActions
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckPainActions ( void ) {
|
||
|
if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
PerformAction ( "Torso_Pain", 2, true );
|
||
|
actionTimerPain.Reset ( actionTime );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::CheckActions
|
||
|
================
|
||
|
*/
|
||
|
bool idAI::CheckActions ( void ) {
|
||
|
|
||
|
// Pain?
|
||
|
if ( CheckPainActions ( ) ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Actions are limited at a cover position to shooting and leaning
|
||
|
if ( IsBehindCover ( ) ) {
|
||
|
// Test ranged attack first
|
||
|
if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
|
||
|
return true;
|
||
|
}
|
||
|
} else {
|
||
|
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 ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
|
||
|
PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idAI::PerformAction
|
||
|
================
|
||
|
*/
|
||
|
void idAI::PerformAction ( const char* stateName, int blendFrames, bool noPain ) {
|
||
|
// Allow movement in actions
|
||
|
move.fl.allowAnimMove = true;
|
||
|
|
||
|
// Start the action
|
||
|
if ( legsAnim.Disabled() ) {
|
||
|
//MCG: Hmmm... I hope this doesn't break anything, but if an action happens *right*
|
||
|
// at the end of a trigger_anim, then the legs will be enabled (by the SetAnimState
|
||
|
// on the torso) with no state! The actor will then be stuck in place until
|
||
|
// something actually sets the legsAnim state... so let's check for disabled and
|
||
|
// set a default state right here...?
|
||
|
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
|
||
|
}
|
||
|
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
|
||
|
PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames );
|
||
|
|
||
|
// Main state will wait until action is finished before continuing
|
||
|
if ( noPain ) {
|
||
|
InterruptState ( "Wait_ActionNoPain" );
|
||
|
} else {
|
||
|
InterruptState ( "Wait_Action" );
|
||
|
}
|
||
|
|
||
|
OnStartAction ( );
|
||
|
}
|
||
|
|
||
|
bool idAI::PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) {
|
||
|
// If we arent ignoring simple think then dont perform this action on a simple think frame
|
||
|
if ( !action->fl.noSimpleThink && aifl.simpleThink ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Is the action disabled?
|
||
|
if ( action->fl.disabled ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_DISABLED;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Action timers still running?
|
||
|
if ( !action->timer.IsDone ( actionTime ) ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_TIMER;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( timer && !timer->IsDone ( actionTime ) ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_EXTERNALTIMER;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Special code for attacks
|
||
|
if ( action->fl.isAttack ) {
|
||
|
// Attacks disabled?
|
||
|
if ( ai_disableAttacks.GetBool() ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_DISABLED;
|
||
|
return false;
|
||
|
}
|
||
|
// No attack actions if we have no enemy or our enemy cant be hurt
|
||
|
if ( !enemy.ent || enemy.ent->health <= 0 ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_NOENEMY;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Min Range check
|
||
|
if ( action->minRange ) {
|
||
|
if ( !enemy.ent || !enemy.range || enemy.range < action->minRange ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_MINRANGE;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if ( action->minRange2d ) {
|
||
|
if ( !enemy.ent || !enemy.range2d || enemy.range2d < action->minRange2d ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_MINRANGE;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Max Range check
|
||
|
if ( action->maxRange != 0 ) {
|
||
|
float maxrange = action->maxRange == -1 ? combat.attackRange[1] : action->maxRange;
|
||
|
if ( !enemy.ent || !enemy.range || enemy.range > maxrange ) {
|
||
|
if ( action->fl.isMelee && GetEnemy() ) {
|
||
|
//FIXME: make work with gravity vector
|
||
|
idVec3 org = physicsObj.GetOrigin();
|
||
|
const idBounds &myBounds = physicsObj.GetBounds();
|
||
|
idBounds bounds;
|
||
|
|
||
|
// expand the bounds out by our melee range
|
||
|
bounds[0][0] = -combat.meleeRange;
|
||
|
bounds[0][1] = -combat.meleeRange;
|
||
|
bounds[0][2] = myBounds[0][2] - 4.0f;
|
||
|
bounds[1][0] = combat.meleeRange;
|
||
|
bounds[1][1] = combat.meleeRange;
|
||
|
bounds[1][2] = myBounds[1][2] + 4.0f;
|
||
|
bounds.TranslateSelf( org );
|
||
|
|
||
|
idVec3 enemyOrg = GetEnemy()->GetPhysics()->GetOrigin();
|
||
|
idBounds enemyBounds = GetEnemy()->GetPhysics()->GetBounds();
|
||
|
enemyBounds.TranslateSelf( enemyOrg );
|
||
|
|
||
|
if ( !bounds.IntersectsBounds( enemyBounds ) ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_MAXRANGE;
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
action->status = rvAIAction::STATUS_FAIL_MAXRANGE;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ( action->maxRange2d ) {
|
||
|
if ( !enemy.ent || !enemy.range2d || enemy.range2d > action->maxRange2d ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_MAXRANGE;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int animNum;
|
||
|
if ( action->anims.Num ( ) ) {
|
||
|
// Pick a random animation from the list
|
||
|
animNum = GetAnim ( ANIMCHANNEL_TORSO, action->anims[gameLocal.random.RandomInt(action->anims.Num())] );
|
||
|
if ( !animNum ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_ANIM;
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
animNum = -1;
|
||
|
}
|
||
|
|
||
|
// Random chance?
|
||
|
if ( action->chance < 1.0f && gameLocal.random.RandomFloat ( ) > action->chance ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_CHANCE;
|
||
|
action->timer.Clear ( actionTime );
|
||
|
action->timer.Add ( 100 );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check the condition
|
||
|
if ( condition && !(this->*(condition)) ( action, animNum ) ) {
|
||
|
action->status = rvAIAction::STATUS_FAIL_CONDITION;
|
||
|
action->timer.Clear ( actionTime );
|
||
|
action->timer.Add ( action->failRate );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Disallow turning during action?
|
||
|
if ( action->fl.noTurn ) {
|
||
|
OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true );
|
||
|
}
|
||
|
|
||
|
// Perform the raw action
|
||
|
PerformAction ( action->state, action->blendFrames, action->fl.noPain );
|
||
|
|
||
|
// Override legs for this state?
|
||
|
if ( action->fl.overrideLegs ) {
|
||
|
DisableAnimState ( ANIMCHANNEL_LEGS );
|
||
|
}
|
||
|
|
||
|
// When attacking scale the time by the aggression scale
|
||
|
float scale;
|
||
|
if ( action->fl.isAttack ) {
|
||
|
scale = 2.0f - combat.aggressiveScale;
|
||
|
} else {
|
||
|
scale = 1.0f;
|
||
|
}
|
||
|
|
||
|
// Restart the action timer using the length of the animation being played
|
||
|
action->timer.Reset ( actionTime, action->diversity, scale );
|
||
|
|
||
|
// If the action gets interrupted it will be skipped, if it is then move the action timers forward
|
||
|
// by half of its normal delay to allow it to be performed again quicker than usual. This also allows
|
||
|
// other actions that may be still pending to be performed and thus cause less of a pause after taking pain.
|
||
|
actionSkipTime = (action->timer.GetTime ( ) + actionTime) / 2;
|
||
|
|
||
|
// Restart the global action timer using the length of the animation being played
|
||
|
if ( timer ) {
|
||
|
timer->Reset ( actionTime, action->diversity, scale );
|
||
|
}
|
||
|
|
||
|
action->status = rvAIAction::STATUS_OK;
|
||
|
|
||
|
actionAnimNum = animNum;
|
||
|
|
||
|
return true;
|
||
|
}
|