836 lines
23 KiB
C++
836 lines
23 KiB
C++
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
#include "AI_Manager.h"
|
|
#include "AI_Util.h"
|
|
#include "AI_Medic.h"
|
|
|
|
|
|
const idEventDef AI_DisableHeal ( "disableHeal" );
|
|
const idEventDef AI_EnableHeal ( "enableHeal" );
|
|
const idEventDef AI_TakePatient ( "takePatient", "e" );
|
|
|
|
CLASS_DECLARATION( rvAITactical, rvAIMedic )
|
|
EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal )
|
|
EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal )
|
|
EVENT( AI_TakePatient, rvAIMedic::TakePatient )
|
|
EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement )
|
|
EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::rvAIMedic
|
|
================
|
|
*/
|
|
rvAIMedic::rvAIMedic ( void ) {
|
|
patient = NULL;
|
|
isTech = false;
|
|
noAutoHeal = false;
|
|
stationary = false;
|
|
silent = false;
|
|
healObeyTether = false;
|
|
healing = false;
|
|
lastPatientCheckTime = 0;
|
|
emergencyOverride = false;
|
|
//healedAmount = 0;
|
|
healDisabled = false;
|
|
wasAware = false;
|
|
wasIgnoreEnemies = false;
|
|
healDebounceTime = 0;
|
|
}
|
|
|
|
void rvAIMedic::InitSpawnArgsVariables( void )
|
|
{
|
|
//NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded?
|
|
isTech = spawnArgs.GetBool( "tech" );
|
|
noAutoHeal = spawnArgs.GetBool( "noAutoHeal" );
|
|
healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" );
|
|
healObeyTether = spawnArgs.GetBool( "healObeyTether" );
|
|
patientRange = spawnArgs.GetFloat( "patientRange", "640" );
|
|
buddyRange = spawnArgs.GetFloat( "buddyRange", "640" );
|
|
enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" );
|
|
healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) );
|
|
|
|
/*
|
|
// JTD: check for player being strogg, since the limits will be different in that case..
|
|
idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" );
|
|
str.Strip( "player_" );
|
|
// ...we'll end up with something like minMarineHeal...or minStroggHeal
|
|
*/
|
|
minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" );
|
|
maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" );
|
|
}
|
|
/*
|
|
================
|
|
rvAIMedic::Spawn
|
|
================
|
|
*/
|
|
void rvAIMedic::Spawn ( void ) {
|
|
InitSpawnArgsVariables();
|
|
|
|
stationary = spawnArgs.GetBool( "stationary" );
|
|
silent = spawnArgs.GetBool( "silent", "0");
|
|
if ( spawnArgs.GetBool( "disableHeal" ) ) {
|
|
Event_DisableHeal();
|
|
}
|
|
if ( spawnArgs.GetBool( "enableHeal" ) ) {
|
|
Event_EnableHeal();
|
|
}
|
|
const char *func;
|
|
if ( spawnArgs.GetString( "script_postHeal", "", &func ) )
|
|
{
|
|
mPostHealScript.Init( func );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::Show
|
|
================
|
|
*/
|
|
void rvAIMedic::Show( void ) {
|
|
rvAITactical::Show();
|
|
HideAttachment( spawnArgs.GetString("def_attach") );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::Save
|
|
================
|
|
*/
|
|
void rvAIMedic::Save( idSaveGame *savefile ) const {
|
|
patient.Save( savefile );
|
|
savefile->WriteBool( healing );
|
|
savefile->WriteInt( lastPatientCheckTime );
|
|
savefile->WriteBool( emergencyOverride );
|
|
savefile->WriteBool( healDisabled );
|
|
savefile->WriteBool( wasAware );
|
|
savefile->WriteBool( wasIgnoreEnemies );
|
|
//savefile->WriteInt( healedAmount );
|
|
savefile->WriteInt( healDebounceTime );
|
|
savefile->WriteBool( stationary );
|
|
savefile->WriteBool( silent );
|
|
|
|
//NOTE: every time these are used, they are set first, no point in saving/loading them!
|
|
savefile->WriteInt( curHealValue );
|
|
savefile->WriteInt( maxPatientValue );
|
|
mPostHealScript.Save( savefile );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::Restore
|
|
================
|
|
*/
|
|
void rvAIMedic::Restore( idRestoreGame *savefile ) {
|
|
patient.Restore( savefile );
|
|
savefile->ReadBool( healing );
|
|
savefile->ReadInt( lastPatientCheckTime );
|
|
savefile->ReadBool( emergencyOverride );
|
|
savefile->ReadBool( healDisabled );
|
|
savefile->ReadBool( wasAware );
|
|
savefile->ReadBool( wasIgnoreEnemies );
|
|
//savefile->ReadInt( healedAmount );
|
|
savefile->ReadInt( healDebounceTime );
|
|
savefile->ReadBool( stationary );
|
|
savefile->ReadBool( silent );
|
|
|
|
//NOTE: every time these are used, they are set first, no point in saving/loading them!
|
|
savefile->ReadInt( curHealValue );
|
|
savefile->ReadInt( maxPatientValue );
|
|
mPostHealScript.Restore( savefile );
|
|
|
|
InitSpawnArgsVariables();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvAIMedic::Event_EnableHeal
|
|
=====================
|
|
*/
|
|
void rvAIMedic::Event_EnableHeal( void ) {
|
|
healDisabled = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvAIMedic::Event_DisableHeal
|
|
=====================
|
|
*/
|
|
void rvAIMedic::Event_DisableHeal( void ) {
|
|
healDisabled = true;
|
|
/*
|
|
if ( patient ) {
|
|
//drop them right now - NOTE: should be done before any scripting?
|
|
DropPatient();
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvAIMedic::Event_EnableMovement
|
|
==================
|
|
*/
|
|
void rvAIMedic::Event_EnableMovement( void ) {
|
|
stationary = false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvAIMedic::Event_DisableMovement
|
|
==================
|
|
*/
|
|
void rvAIMedic::Event_DisableMovement ( void ) {
|
|
stationary = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvAIMedic::TalkTo
|
|
=====================
|
|
*/
|
|
void rvAIMedic::TalkTo( idActor *actor ) {
|
|
if ( actor->IsType( idPlayer::GetClassType() ) )
|
|
{
|
|
idPlayer* player = dynamic_cast<idPlayer*>(actor);
|
|
if ( player )
|
|
{
|
|
emergencyOverride = true;
|
|
if ( AvailableToTakePatient() && CheckTakePatient( player ) )
|
|
{
|
|
return;
|
|
}
|
|
emergencyOverride = false;
|
|
}
|
|
}
|
|
rvAITactical::TalkTo( actor );
|
|
|
|
if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) {
|
|
if ( GetEnemy() || patient ) {
|
|
Speak( "lipsync_heal_busy_", true );
|
|
} else {
|
|
//Never got proper VO for this, disabling it... :/
|
|
//Speak( "lipsync_heal_noheal_", true );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::GetDebugInfo
|
|
================
|
|
*/
|
|
void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) {
|
|
// Base class first
|
|
rvAITactical::GetDebugInfo ( proc, userData );
|
|
|
|
proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData );
|
|
proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData );
|
|
proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData );
|
|
proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData );
|
|
proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData );
|
|
proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData );
|
|
proc ( "rvAIMedic", "silent", silent?"true":"false", userData );
|
|
proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData );
|
|
proc ( "rvAIMedic", "patient", patient==NULL?"<none>":patient->GetName(), userData );
|
|
proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData );
|
|
proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData );
|
|
proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData );
|
|
proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData );
|
|
proc ( "rvAIMedic", "healing", healing?"true":"false", userData );
|
|
proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData );
|
|
//proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData );
|
|
proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData );
|
|
proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData );
|
|
proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData );
|
|
proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData );
|
|
proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData );
|
|
proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData );
|
|
proc ( "rvAIMedic", "tech", isTech?"true":"false", userData );
|
|
}
|
|
|
|
/*
|
|
============
|
|
rvAIMedic::IsTethered
|
|
============
|
|
*/
|
|
bool rvAIMedic::IsTethered ( void ) const {
|
|
if ( rvAITactical::IsTethered() ) {
|
|
if ( !healObeyTether && patient ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void rvAIMedic::TakePatient( idPlayer* pPatient )
|
|
{
|
|
patient = pPatient;
|
|
if ( emergencyOverride )
|
|
{
|
|
ClearEnemy();
|
|
}
|
|
|
|
wasAware = combat.fl.aware;
|
|
wasIgnoreEnemies = combat.fl.ignoreEnemies;
|
|
ProcessEvent( &AI_BecomePassive, true );
|
|
combat.fl.ignoreEnemies = true;
|
|
ClearEnemy();
|
|
lookTarget = patient;
|
|
healing = false;
|
|
//healedAmount = 0;
|
|
|
|
if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent )
|
|
{
|
|
//have enough time to say this before we get there...?
|
|
//jshepard: tech/medic dependent speech
|
|
if( isTech ) {
|
|
Speak( "lipsync_call_player_tech_", true );
|
|
} else {
|
|
Speak( "lipsync_call_player_", true );
|
|
}
|
|
}
|
|
|
|
if ( !stationary && !move.fl.disabled ) {
|
|
MoveToEntity( patient, 42 );
|
|
}
|
|
SetState( "State_Medic" );
|
|
PostState( "State_Combat" );
|
|
|
|
//just in case
|
|
if ( healDebounceInterval ) {
|
|
healDebounceTime = gameLocal.GetTime() + healDebounceInterval;
|
|
}
|
|
}
|
|
|
|
void rvAIMedic::DropPatient( void )
|
|
{
|
|
/*
|
|
if ( !spawnArgs.GetBool( "ignoreEnemies" ) )
|
|
{
|
|
combat.fl.ignoreEnemies = false;
|
|
}
|
|
*/
|
|
/*
|
|
if ( !aifl.scripted )
|
|
{
|
|
ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT );
|
|
}
|
|
*/
|
|
healing = false;
|
|
if ( !aifl.scripted ) {
|
|
ProcessEvent( &AI_BecomeAggressive );
|
|
combat.fl.aware = wasAware;
|
|
combat.fl.ignoreEnemies = wasIgnoreEnemies;
|
|
lookTarget = NULL;
|
|
} else if ( lookTarget == patient ) {
|
|
//if scripted, clear the looktarget only if it's the patient? This could be wrong, though....
|
|
lookTarget = NULL;
|
|
}
|
|
|
|
patient = NULL;
|
|
|
|
if ( healDebounceInterval ) {
|
|
healDebounceTime = gameLocal.GetTime() + healDebounceInterval;
|
|
}
|
|
|
|
ForceTacticalUpdate();
|
|
if ( !aifl.scripted ) {
|
|
UpdateTactical ( 0 );
|
|
}
|
|
//FIXME: what if they stay in this state?
|
|
HideAttachment( spawnArgs.GetString("def_attach") );
|
|
}
|
|
|
|
void rvAIMedic::SetHealValues( idPlayer* player )
|
|
{
|
|
if ( !player )
|
|
{
|
|
return;
|
|
}
|
|
if ( isTech )
|
|
{
|
|
curHealValue = player->inventory.armor;
|
|
maxPatientValue = player->inventory.maxarmor;
|
|
}
|
|
else
|
|
{
|
|
curHealValue = player->health;
|
|
maxPatientValue = player->inventory.maxHealth;
|
|
}
|
|
}
|
|
|
|
bool rvAIMedic::CheckTakePatient( idPlayer* player )
|
|
{
|
|
if ( !player )
|
|
{
|
|
return false;
|
|
}
|
|
if ( player->IsHidden() )
|
|
{
|
|
return false;
|
|
}
|
|
if ( player->health <= 0 )
|
|
{
|
|
return false;
|
|
}
|
|
SetHealValues( player );
|
|
|
|
if ( curHealValue < maxPatientValue )
|
|
{//they are hurt
|
|
if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) )
|
|
{//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one)
|
|
if ( DistanceTo( player ) < patientRange )
|
|
{//close enough
|
|
if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange )
|
|
{//either I am allowed to move or player is close enough that I don't have to
|
|
//if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) )
|
|
{//not tethered or patient is in our tether
|
|
if ( emergencyOverride || CanSee( player, false ) )
|
|
{//can see the patient - OR: just check PVS?
|
|
TakePatient( player );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool rvAIMedic::SituationAllowsPatient( void )
|
|
{
|
|
if ( !aifl.scripted && !IsHidden() && !aifl.dead )
|
|
{//not scripted right now
|
|
if ( combat.fl.ignoreEnemies ) {
|
|
return true;
|
|
}
|
|
bool enemyInPVS = false;
|
|
bool enemyInRange = false;
|
|
if ( GetEnemy() )
|
|
{//sorry, we have to bail on you!
|
|
enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange);
|
|
if ( enemyInRange ) {
|
|
pvsHandle_t pvs;
|
|
// Setup our local variables used in the search
|
|
pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() );
|
|
// If this enemy isnt in the same pvps then use them as a backup
|
|
if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) {
|
|
enemyInPVS = true;
|
|
emergencyOverride = false;
|
|
}
|
|
gameLocal.pvs.FreeCurrentPVS( pvs );
|
|
}
|
|
}
|
|
if ( emergencyOverride )
|
|
{//don't care how crazy it is, go for it!
|
|
return true;
|
|
}
|
|
if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 )
|
|
{//haven't had an enemy for 5 seconds
|
|
if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) )
|
|
{//local buddies don't have enemies
|
|
//NOTE: which buddies and enemies are local can change as we head to our patient!
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool rvAIMedic::AvailableToTakePatient( void )
|
|
{
|
|
if ( !healDisabled )
|
|
{//not forced to disabled
|
|
if ( !aifl.action )
|
|
{//not in the middle of an action
|
|
if ( !patient )
|
|
{//don't already have a patient
|
|
if ( !IsSpeaking() )
|
|
{//not talking
|
|
return SituationAllowsPatient();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvAIMedic::Think
|
|
================
|
|
*/
|
|
void rvAIMedic::Think ( void ) {
|
|
rvAITactical::Think ( );
|
|
|
|
// while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1")
|
|
//???
|
|
if ( !noAutoHeal )
|
|
{
|
|
if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 )
|
|
{
|
|
lastPatientCheckTime = gameLocal.GetTime();
|
|
if ( !patient )
|
|
{
|
|
emergencyOverride = false;
|
|
}
|
|
if ( AvailableToTakePatient() )
|
|
{
|
|
idPlayer* player = gameLocal.GetLocalPlayer();
|
|
|
|
if ( CheckTakePatient( player ) )
|
|
{
|
|
return;
|
|
}
|
|
//otherwise, check team?
|
|
/*
|
|
idActor* actor;
|
|
for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() )
|
|
{
|
|
if ( CheckTakePatient( actor ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvAIMedic::OnStateThreadClear
|
|
=====================
|
|
*/
|
|
void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) {
|
|
if ( idStr::Icmp( statename, "State_Medic" ) ) {
|
|
if ( patient ) {
|
|
//BAH! Someone changed our state on us!
|
|
if ( lookTarget == patient ) {
|
|
lookTarget = NULL;
|
|
}
|
|
patient = NULL;
|
|
healing = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
rvAIMedic::OnStartMoving
|
|
============
|
|
*/
|
|
void rvAIMedic::OnStartMoving ( void ) {
|
|
idAI::OnStartMoving();
|
|
if ( patient )
|
|
{//we were trying to heal!
|
|
if ( move.moveCommand != MOVE_TO_ENTITY
|
|
|| move.goalEntity != patient )
|
|
{//being told to leave the patient
|
|
//abort the heal, for now
|
|
DropPatient();
|
|
if ( !aifl.scripted ) {
|
|
//only do this if you're not already scripted?
|
|
ExecScriptFunction( mPostHealScript );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvAIMedic::Pain
|
|
=====================
|
|
*/
|
|
bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location );
|
|
if ( retVal && patient ) {
|
|
//if get hit while trying to heal, protect ourselves
|
|
if ( !aifl.scripted ) {
|
|
//only do this if you're not already scripted?
|
|
StopMove ( MOVE_STATUS_DONE );
|
|
}
|
|
DropPatient();
|
|
if ( !aifl.scripted ) {
|
|
//only do this if you're not already scripted?
|
|
ExecScriptFunction( mPostHealScript );
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvAIMedic )
|
|
STATE ( "State_Medic", rvAIMedic::State_Medic )
|
|
END_CLASS_STATES
|
|
|
|
/*
|
|
================
|
|
idAI::State_Medic
|
|
|
|
Rush towards the patient to melee range and heal until they're okay
|
|
================
|
|
*/
|
|
stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_MOVE, // Move towards the patient, speak & start anim
|
|
STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim
|
|
STAGE_HEAL_START_WAIT,// Wait for start anim to finish
|
|
STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed
|
|
STAGE_WAIT_FINISH // Finish anim
|
|
};
|
|
if ( !patient || patient->health <= 0 || !SituationAllowsPatient() )
|
|
{//patient dead or situation is bad
|
|
//NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume?
|
|
StopMove ( MOVE_STATUS_DONE );
|
|
DropPatient();
|
|
ExecScriptFunction( mPostHealScript );
|
|
return SRESULT_DONE;
|
|
}
|
|
|
|
if ( !move.fl.done )
|
|
{
|
|
if ( move.moveCommand != MOVE_TO_ENTITY
|
|
|| move.goalEntity != patient )
|
|
{//something stomped our move, give it up, for now... :/
|
|
DropPatient();
|
|
ExecScriptFunction( mPostHealScript );
|
|
return SRESULT_DONE;
|
|
}
|
|
}
|
|
|
|
switch ( parms.stage ) {
|
|
case STAGE_MOVE:
|
|
// Attack when we have either stopped moving or are within melee range
|
|
if ( move.fl.done )
|
|
{
|
|
if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) )
|
|
{//wtf, we're not there yet, try again!
|
|
if ( !stationary && !move.fl.disabled ) {
|
|
MoveToEntity( patient, 42 );
|
|
}
|
|
}
|
|
else
|
|
{//we're there!
|
|
if ( !healing )
|
|
{
|
|
SetHealValues( patient );
|
|
if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent )
|
|
{//didn't speak in last couple seconds
|
|
//jshepard: tech/medic dependent speech
|
|
if( isTech ) {
|
|
Speak( "lipsync_heal_start_tech_", true );
|
|
} else {
|
|
Speak( "lipsync_heal_start_", true );
|
|
}
|
|
}
|
|
}
|
|
StopMove ( MOVE_STATUS_DONE );
|
|
healing = true;
|
|
// check for preHeal anim key and if it's present, play it first.
|
|
const char *preHealAnim;
|
|
if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ))
|
|
{
|
|
PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 );
|
|
return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT );
|
|
}
|
|
else // otherwise just use the regular anim to start healing
|
|
{
|
|
PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 );
|
|
return SRESULT_STAGE ( STAGE_HEAL_START_WAIT );
|
|
}
|
|
}
|
|
}
|
|
if ( !stationary && move.range != 42.0f )
|
|
{
|
|
//MCG: if you ever get this assert, please call me over so I can debug it!
|
|
assert(move.range==42.0f);
|
|
move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_(
|
|
}
|
|
/*
|
|
else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) )
|
|
{//we're here, just stop
|
|
Speak( "lipsync_medic_arrive", true );
|
|
StopMove ( MOVE_STATUS_DONE );
|
|
return SRESULT_STAGE ( STAGE_HEAL );
|
|
}
|
|
*/
|
|
|
|
// Perform actions on the way to the patient
|
|
if ( UpdateAction ( ) ) {
|
|
return SRESULT_WAIT;
|
|
}
|
|
|
|
// Update enemy, not tactical state
|
|
if ( !emergencyOverride )
|
|
{
|
|
// Keep the enemy status up to date
|
|
if ( !combat.fl.ignoreEnemies ) {
|
|
// If we dont have an enemy or havent seen our enemy for a while just find a new one entirely
|
|
if ( gameLocal.time - enemy.checkTime > 250 ) {
|
|
CheckForEnemy ( true, true );
|
|
} else if ( !IsEnemyRecentlyVisible ( ) ) {
|
|
CheckForEnemy ( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
return SRESULT_WAIT;
|
|
|
|
// intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity
|
|
case STAGE_PRE_HEAL_ANIM_WAIT:
|
|
const char *preHealAnim;
|
|
spawnArgs.GetString( "anim_preHeal", "", &preHealAnim );
|
|
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim ))
|
|
{//finished or interrupted
|
|
PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 );
|
|
return SRESULT_STAGE ( STAGE_HEAL_START_WAIT );
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
|
|
case STAGE_HEAL_START_WAIT:
|
|
/*
|
|
if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH )
|
|
{
|
|
ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH );
|
|
}
|
|
else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH )
|
|
{
|
|
ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND );
|
|
}
|
|
*/
|
|
TurnToward ( patient->GetPhysics()->GetOrigin ( ) );
|
|
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) )
|
|
{//finished or interrupted
|
|
// PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 );
|
|
PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 );
|
|
//show the tool, just in case the anim was interrupted
|
|
ShowAttachment( spawnArgs.GetString("def_attach") );
|
|
return SRESULT_STAGE ( STAGE_HEAL );
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_HEAL:
|
|
{
|
|
SetHealValues( patient );
|
|
/*
|
|
if ( curHealValue >= maxPatientValue
|
|
|| (curHealValue > minHealValue && healedAmount >= healAmt) )
|
|
{//patient fully healed (or we've used up our allotment), we're done here
|
|
Speak( "lipsync_heal_end_", true );
|
|
PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 );
|
|
return SRESULT_STAGE ( STAGE_WAIT_FINISH );
|
|
}
|
|
*/
|
|
|
|
// If we are out of melee range or lost sight of our patient then start moving again
|
|
/*
|
|
if ( !stationary && !move.fl.disabled ) {
|
|
if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) {
|
|
MoveToEntity( patient, 42 );
|
|
Speak( "lipsync_heal_move_", true );
|
|
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
|
|
return SRESULT_STAGE ( STAGE_MOVE );
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
combat.fl.ignoreEnemies = true;
|
|
*/
|
|
/*
|
|
if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH )
|
|
{
|
|
ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH );
|
|
}
|
|
else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH )
|
|
{
|
|
ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND );
|
|
}
|
|
*/
|
|
|
|
// Always face patient when in melee range
|
|
TurnToward ( patient->GetPhysics()->GetOrigin ( ) );
|
|
|
|
// Perform actions while standing still
|
|
/*
|
|
if ( UpdateAction ( ) ) {
|
|
return SRESULT_WAIT;
|
|
}
|
|
|
|
// Update enemy, not tactical state
|
|
if ( !emergencyOverride )
|
|
{
|
|
combat.tacticalUpdateTime = gameLocal.GetTime();
|
|
if ( UpdateTactical( 100000 ) ) {
|
|
DropPatient();
|
|
return SRESULT_DONE_WAIT;
|
|
}
|
|
}
|
|
*/
|
|
|
|
//jshepard: tech/medic dependent speech
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) {
|
|
if ( !isTech ) {
|
|
patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt;
|
|
if( !silent) {
|
|
Speak( "lipsync_heal_end_", true );
|
|
}
|
|
} else {
|
|
patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;;
|
|
if( !silent) {
|
|
Speak( "lipsync_heal_end_tech_", true );
|
|
}
|
|
}
|
|
PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 );
|
|
return SRESULT_STAGE ( STAGE_WAIT_FINISH );
|
|
}
|
|
if ( gameLocal.random.RandomFloat() > 0.5f )
|
|
{
|
|
if ( !isTech )
|
|
{
|
|
if ( patient->health < maxPatientValue ) {
|
|
patient->health++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( patient->inventory.armor < maxPatientValue ) {
|
|
patient->inventory.armor++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
case STAGE_WAIT_FINISH:
|
|
if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) )
|
|
{//finished or interrupted
|
|
//turn off the tool, just in case we were interrupted
|
|
DropPatient();
|
|
ExecScriptFunction( mPostHealScript );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|