Files
DoomRTX/neo/doom3/game/ai/AI_character_sentry.cpp
T
2026-05-23 20:50:12 -07:00

1042 lines
33 KiB
C++

// AI_character_sentry.cpp
// Native state-machine replacement for ai_character_sentry.script.
#include "precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
namespace {
const float SENTRY_WAIT_GETCLOSER = 3.0f;
const float SENTRY_LEAD_TIMEOUT = 1.0f;
const float SENTRY_FIRERATE = 0.1f;
const int SENTRY_MIN_SHOTS = 8;
const float SENTRY_MIN_LOOK_DELAY = 4.0f;
const float SENTRY_MAX_LOOK_DELAY = 8.0f;
const float SENTRY_LOOK_TIME = 1.5f;
const float SENTRY_LEAD_DIST = 140.0f;
const float SENTRY_LOST_DIST = 280.0f;
const float SENTRY_WALKTURN = 65.0f;
const float SENTRY_WALKTURN2 = 30.0f;
const float SENTRY_ATTACK_MAX_LENGTH = 4.0f;
const float SENTRY_ATTACK_MIN_LENGTH = 1.2f;
const float SENTRY_WAIT_MAX_LENGTH = 1.0f;
const float SENTRY_WAIT_MIN_LENGTH = 0.3f;
const float SENTRY_MIN_TURN = 10.0f;
}
rvmCharSentry::rvmCharSentry() {
lead_player = false;
hanging = false;
awake = false;
fire = false;
playerMoveState = NOT_MOVING;
light_on = false;
nextPositionSearch = 0.0f;
ignore_enemy_time = 0.0f;
lastValidPlayerPosition = vec3_zero;
playerPositionThread = false;
nextLookTime = 0.0f;
leadTimeout = 0.0f;
shotsFired = 0;
nextFireTime = 0.0f;
}
void rvmCharSentry::Init( idAI* aiOwner ) {
rvmCharacter::Init( aiOwner );
lead_player = GetIntKey( "lead_player" ) != 0;
canTalk = false;
noCowerSaved = true;
noCower = true;
ignore_enemy = NULL;
ignore_enemy_time = 0.0f;
lastValidPlayerPosition = Player() ? Player()->GetPhysics()->GetOrigin() : GetOrigin();
SetState( "state_Begin" );
}
void rvmCharSentry::Destroy() {
if ( light.GetEntity() ) {
light.GetEntity()->PostEventMS( &EV_Remove, 0 );
light = NULL;
}
playerPositionThread = false;
rvmCharacter::Destroy();
}
stateResult_t rvmCharSentry::RunStateByName( const char* stateName, stateParms_t* parms ) {
AI_DISPATCH_STATE( "Torso_Death", Torso_Death );
AI_DISPATCH_STATE( "Torso_Idle", Torso_Idle );
AI_DISPATCH_STATE( "Torso_Pain", Torso_Pain );
AI_DISPATCH_STATE( "Torso_RangeAttack", Torso_RangeAttack );
AI_DISPATCH_STATE( "Legs_Death", Legs_Death );
AI_DISPATCH_STATE( "Legs_Idle", Legs_Idle );
AI_DISPATCH_STATE( "Legs_Walk", Legs_Walk );
AI_DISPATCH_STATE( "Legs_Fold", Legs_Fold );
AI_DISPATCH_STATE( "Legs_Folded", Legs_Folded );
AI_DISPATCH_STATE( "Legs_Unfold", Legs_Unfold );
AI_DISPATCH_STATE( "Legs_Hanging", Legs_Hanging );
AI_DISPATCH_STATE( "Legs_TurnLeft", Legs_TurnLeft );
AI_DISPATCH_STATE( "Legs_TurnRight", Legs_TurnRight );
AI_DISPATCH_STATE( "state_Begin", state_Begin );
AI_DISPATCH_STATE( "updatePlayerPositionThread", updatePlayerPositionThread );
AI_DISPATCH_STATE( "path_sentry_light_on", path_sentry_light_on );
AI_DISPATCH_STATE( "path_sentry_light_off", path_sentry_light_off );
AI_DISPATCH_STATE( "path_sentry_shutdown", path_sentry_shutdown );
AI_DISPATCH_STATE( "path_sentry_unlock_door", path_sentry_unlock_door );
AI_DISPATCH_STATE( "path_corner", path_corner );
AI_DISPATCH_STATE( "path_sentry_lead_player", path_sentry_lead_player );
AI_DISPATCH_STATE( "path_sentry_ignore_player", path_sentry_ignore_player );
AI_DISPATCH_STATE( "state_Killed", state_Killed );
AI_DISPATCH_STATE( "state_WaitForTrigger", state_WaitForTrigger );
AI_DISPATCH_STATE( "state_Idle", state_Idle );
AI_DISPATCH_STATE( "state_FollowPath", state_FollowPath );
AI_DISPATCH_STATE( "state_Wait", state_Wait );
AI_DISPATCH_STATE( "state_GetCloser", state_GetCloser );
AI_DISPATCH_STATE( "state_CantReachPlayer", state_CantReachPlayer );
AI_DISPATCH_STATE( "state_Lead", state_Lead );
AI_DISPATCH_STATE( "state_FindPlayer", state_FindPlayer );
AI_DISPATCH_STATE( "state_Combat", state_Combat );
AI_DISPATCH_STATE( "combat_chase", combat_chase );
AI_DISPATCH_STATE( "combat_range", combat_range );
return rvmCharacter::RunStateByName( stateName, parms );
}
stateResult_t rvmCharSentry::Torso_Death( stateParms_t* parms ) {
FinishAction( "dead" );
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::Torso_Idle( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
IdleAnim( ANIMCHANNEL_TORSO, "stand" );
parms->stage = 1;
}
if ( Pain() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 );
return SRESULT_DONE;
}
if ( fire ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_RangeAttack", 4 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::Torso_Pain( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
SetBlendFrames( ANIMCHANNEL_TORSO, 2 );
PlayAnim( ANIMCHANNEL_TORSO, GetPainAnim() );
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( !AnimDone( ANIMCHANNEL_TORSO, 4 ) ) {
return SRESULT_WAIT;
}
PreventPain( 2.0f );
FinishAction( "pain" );
SetBlendFrames( ANIMCHANNEL_TORSO, 4 );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::Torso_RangeAttack( stateParms_t* parms ) {
enum { STAGE_START, STAGE_START_ANIM, STAGE_LOOP_BEGIN, STAGE_LOOP, STAGE_AIM, STAGE_END };
switch ( parms->stage ) {
case STAGE_START:
SetAnimPrefix( "" );
PlayAnim( ANIMCHANNEL_TORSO, "range_attack" );
shotsFired = 0;
parms->stage = STAGE_START_ANIM;
return SRESULT_WAIT;
case STAGE_START_ANIM:
if ( Pain() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_WAIT;
}
parms->stage = STAGE_LOOP_BEGIN;
return SRESULT_WAIT;
case STAGE_LOOP_BEGIN:
if ( !fire && shotsFired >= SENTRY_MIN_SHOTS ) {
PlayAnim( ANIMCHANNEL_TORSO, "range_attack_end" );
parms->stage = STAGE_END;
return SRESULT_WAIT;
}
scratchEndTime = RandomDelay( SENTRY_ATTACK_MIN_LENGTH, SENTRY_ATTACK_MAX_LENGTH );
nextFireTime = Time();
SetBlendFrames( ANIMCHANNEL_TORSO, 2 );
PlayCycle( ANIMCHANNEL_TORSO, "range_attack_loop" );
parms->stage = STAGE_LOOP;
return SRESULT_WAIT;
case STAGE_LOOP:
if ( Pain() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 );
return SRESULT_DONE;
}
if ( ( fire || shotsFired < SENTRY_MIN_SHOTS ) && Time() < scratchEndTime ) {
if ( Time() >= nextFireTime ) {
StartSoundKey( "snd_fire", SND_CHANNEL_WEAPON, false );
AttackMissile( "Barrel" );
shotsFired++;
nextFireTime = Time() + SENTRY_FIRERATE;
}
return SRESULT_WAIT;
}
if ( !fire && shotsFired >= SENTRY_MIN_SHOTS ) {
PlayAnim( ANIMCHANNEL_TORSO, "range_attack_end" );
parms->stage = STAGE_END;
return SRESULT_WAIT;
}
SetBlendFrames( ANIMCHANNEL_TORSO, 2 );
PlayCycle( ANIMCHANNEL_TORSO, "range_attack_aim" );
scratchEndTime = RandomDelay( SENTRY_WAIT_MIN_LENGTH, SENTRY_WAIT_MAX_LENGTH );
parms->stage = STAGE_AIM;
return SRESULT_WAIT;
case STAGE_AIM:
if ( Pain() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 );
return SRESULT_DONE;
}
if ( ( fire || shotsFired < SENTRY_MIN_SHOTS ) && Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
parms->stage = STAGE_LOOP_BEGIN;
return SRESULT_WAIT;
case STAGE_END:
if ( Pain() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_TORSO, 4 ) ) {
return SRESULT_WAIT;
}
FinishAction( "range_attack" );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::Legs_Death( stateParms_t* parms ) {
if ( owner && owner->AI_DEAD ) {
return SRESULT_WAIT;
}
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::Legs_Idle( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
IdleAnim( ANIMCHANNEL_LEGS, "stand" );
parms->stage = 1;
}
if ( !awake ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Fold", 4 );
return SRESULT_DONE;
}
if ( MovingForward() ) {
const float delta = GetTurnDelta();
if ( delta <= SENTRY_WALKTURN && delta >= -SENTRY_WALKTURN ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
return SRESULT_DONE;
}
}
if ( !FacingIdeal() ) {
if ( GetTurnDelta() > SENTRY_MIN_TURN ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
return SRESULT_DONE;
}
if ( GetTurnDelta() < -SENTRY_MIN_TURN ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
return SRESULT_DONE;
}
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::Legs_Walk( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
PlayCycle( ANIMCHANNEL_LEGS, "walk" );
parms->stage = 1;
}
const float delta = GetTurnDelta();
if ( !MovingForward() || delta > SENTRY_WALKTURN || delta < -SENTRY_WALKTURN ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::Legs_Fold( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
StartSoundKey( "snd_shutdown", SND_CHANNEL_VOICE, false );
PlayAnim( ANIMCHANNEL_LEGS, "fold" );
parms->stage = 1;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 0 ) ) {
return SRESULT_WAIT;
}
flashlight_off();
FinishAction( "fold" );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::Legs_Folded( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
PlayCycle( ANIMCHANNEL_LEGS, "folded" );
parms->stage = 1;
}
if ( awake ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Unfold", 4 );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::Legs_Unfold( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
PlayAnim( ANIMCHANNEL_LEGS, "unfold" );
parms->stage = 1;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 8 ) ) {
return SRESULT_WAIT;
}
FinishAction( "unfold" );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::Legs_Hanging( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
PlayCycle( ANIMCHANNEL_LEGS, "releaseidle" );
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( hanging ) {
return SRESULT_WAIT;
}
PlayAnim( ANIMCHANNEL_LEGS, "load" );
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( !AnimDone( ANIMCHANNEL_LEGS, 8 ) ) {
return SRESULT_WAIT;
}
FinishAction( "unload" );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::Legs_TurnLeft( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
PlayAnim( ANIMCHANNEL_LEGS, "turn_left" );
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( MovingForward() && GetTurnDelta() <= SENTRY_WALKTURN2 && GetTurnDelta() >= -SENTRY_WALKTURN2 ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 0 ) ) {
return SRESULT_WAIT;
}
PlayAnim( ANIMCHANNEL_LEGS, "turn_right" );
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( MovingForward() && GetTurnDelta() <= SENTRY_WALKTURN2 && GetTurnDelta() >= -SENTRY_WALKTURN2 ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 8 ) ) {
return SRESULT_WAIT;
}
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::Legs_TurnRight( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
PlayAnim( ANIMCHANNEL_LEGS, "turn_right" );
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( MovingForward() && GetTurnDelta() <= SENTRY_WALKTURN2 && GetTurnDelta() >= -SENTRY_WALKTURN2 ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 0 ) ) {
return SRESULT_WAIT;
}
PlayAnim( ANIMCHANNEL_LEGS, "turn_left" );
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( MovingForward() && GetTurnDelta() <= SENTRY_WALKTURN2 && GetTurnDelta() >= -SENTRY_WALKTURN2 ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
return SRESULT_DONE;
}
if ( !AnimDone( ANIMCHANNEL_LEGS, 8 ) ) {
return SRESULT_WAIT;
}
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::state_Begin( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
fire = false;
if ( owner ) {
owner->SetJointMod( true );
}
light_on = false;
if ( GetIntKey( "flashlight" ) ) {
spawn_flashlight();
}
hanging = GetIntKey( "hanging" ) != 0;
awake = GetIntKey( "unfolded" ) != 0;
parms->stage = 1;
return SRESULT_WAIT;
}
if ( hanging ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Hanging", 0 );
} else if ( !awake ) {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
} else {
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
}
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
SetState( "state_WaitForTrigger" );
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::updatePlayerPositionThread( stateParms_t* parms ) {
if ( Player() && CanReachEntity( Player() ) ) {
lastValidPlayerPosition = GetReachableEntityPosition( Player() );
}
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::path_sentry_light_on( stateParms_t* parms ) {
flashlight_on();
finishPathCommand();
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::path_sentry_light_off( stateParms_t* parms ) {
flashlight_off();
finishPathCommand();
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::path_sentry_shutdown( stateParms_t* parms ) {
idEntity* node = current_path.GetEntity();
switch ( parms->stage ) {
case 0:
StopMove();
if ( node ) {
TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() );
}
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( !FacingIdeal() ) {
return SRESULT_WAIT;
}
awake = false;
ClearActivated();
playerPositionThread = false;
waitActionName = "fold";
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( !Activated() ) {
return SRESULT_WAIT;
}
if ( GetIntKey( "flashlight_on" ) ) {
flashlight_on();
}
awake = true;
waitActionName = "unfold";
parms->stage = 3;
return SRESULT_WAIT;
case 3:
if ( !ActionDone( "unfold" ) ) {
return SRESULT_WAIT;
}
finishPathCommand();
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::path_sentry_unlock_door( stateParms_t* parms ) {
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
switch ( parms->stage ) {
case 0:
if ( !node->spawnArgs.GetString( "trigger", "" )[0] ) {
finishPathCommand();
return SRESULT_DONE;
}
TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() );
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( !FacingIdeal() ) {
return SRESULT_WAIT;
}
StartSoundKey( "snd_open_door", SND_CHANNEL_VOICE, false );
playCustomAnim( "unlock_door", 4.0f, 4.0f );
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( inCustomAnim() ) {
return SRESULT_WAIT;
}
if ( idEntity* trigger = gameLocal.FindEntity( node->spawnArgs.GetString( "trigger", "" ) ) ) {
trigger->ActivateTargets( owner );
}
node->spawnArgs.Set( "trigger", "" );
finishPathCommand();
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::path_corner( stateParms_t* parms ) {
if ( !lead_player ) {
SetState( "state_FollowPath" );
return SRESULT_DONE;
}
SetNeverDormant( true );
if ( Player() && !CanSeeEntity( Player() ) ) {
SetState( "state_FindPlayer" );
} else if ( checkDestinationDistance() == PLAYER_BEHIND_ME ) {
SetState( "state_Wait" );
} else {
SetState( "state_Lead" );
}
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::path_sentry_lead_player( stateParms_t* parms ) {
lead_player = true;
playerPositionThread = true;
updatePlayerPositionThread( parms );
finishPathCommand();
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::path_sentry_ignore_player( stateParms_t* parms ) {
lead_player = false;
playerPositionThread = false;
finishPathCommand();
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::state_Killed( stateParms_t* parms ) {
StopMove();
playerPositionThread = false;
flashlight_off();
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
if ( parms->stage == 0 ) {
waitActionName = "dead";
scratchEndTime = GetFloatKey( "burnaway" );
parms->stage = 1;
}
if ( scratchEndTime > 0.0f ) {
PreBurn();
scratchEndTime = 0.0f;
}
StopThinking();
return SRESULT_DONE;
}
stateResult_t rvmCharSentry::state_WaitForTrigger( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
if ( !GetIntKey( "hanging" ) || GetIntKey( "hanging" ) != 2 ) {
ClearActivated();
parms->stage = 1;
return SRESULT_WAIT;
}
parms->stage = 2;
return SRESULT_WAIT;
case 1:
if ( !Activated() ) {
return SRESULT_WAIT;
}
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( GetIntKey( "flashlight_on" ) ) {
flashlight_on();
}
if ( hanging ) {
hanging = false;
awake = true;
waitActionName = "unload";
parms->stage = 3;
return SRESULT_WAIT;
}
if ( !awake ) {
awake = true;
waitActionName = "unfold";
parms->stage = 3;
return SRESULT_WAIT;
}
parms->stage = 3;
return SRESULT_WAIT;
case 3:
if ( waitActionName.Length() && !ActionDone( waitActionName.c_str() ) ) {
return SRESULT_WAIT;
}
AllowDamage();
LookAtEntity( Player(), 3.0f );
current_path = owner ? owner->RandomPath() : NULL;
SetState( "state_Idle" );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmCharSentry::state_Idle( stateParms_t* parms ) {
StopMove();
if ( lead_player ) {
playerPositionThread = true;
updatePlayerPositionThread( parms );
if ( Player() && !CanSeeEntity( Player() ) ) {
SetState( "state_FindPlayer" );
return SRESULT_DONE;
}
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
if ( owner && owner->AI_PUSHED ) {
getOutOfWay();
owner->AI_PUSHED = false;
}
if ( current_path.GetEntity() ) {
executePathCommand( current_path.GetEntity() );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_FollowPath( stateParms_t* parms ) {
if ( !current_path.GetEntity() ) {
SetState( "state_Idle" );
return SRESULT_DONE;
}
if ( parms->stage == 0 ) {
MoveToEntity( current_path.GetEntity() );
parms->stage = 1;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
checkBlocked( parms );
if ( MoveDone() ) {
if ( DestinationUnreachable() ) {
SetState( "state_FindPlayer" );
return SRESULT_DONE;
}
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
finishPathCommand();
SetState( "state_Idle" );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_Wait( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
StopMove();
StartSoundKey( "snd_waiting_for_player", SND_CHANNEL_VOICE, false );
scratchEndTime = DelayTime( SENTRY_WAIT_GETCLOSER );
parms->stage = 1;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
LookAtEntity( Player(), 0.1f );
if ( owner && owner->AI_PUSHED ) {
getOutOfWay();
owner->AI_PUSHED = false;
}
const float result = checkDestinationDistance();
if ( result == PLAYER_LOST ) {
SetState( "state_FindPlayer" );
} else if ( result == PLAYER_AHEAD_OF_ME || ( result == PLAYER_OK && playerMoveState == APPROACHING ) ) {
SetState( "state_Lead" );
} else if ( Time() > scratchEndTime && result == PLAYER_BEHIND_ME ) {
SetState( "state_GetCloser" );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_GetCloser( stateParms_t* parms ) {
MoveToPosition( lastValidPlayerPosition );
if ( MoveDone() && Player() && !CanReachEntity( Player() ) ) {
SetState( "state_CantReachPlayer" );
return SRESULT_DONE;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
checkBlocked( parms );
const float result = checkDestinationDistance();
if ( result == PLAYER_LOST ) {
SetState( "state_FindPlayer" );
} else if ( result == PLAYER_OK ) {
SetState( "state_Wait" );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_CantReachPlayer( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
StartSoundKey( "snd_cant_reach_player", SND_CHANNEL_VOICE, false );
StopMove();
parms->stage = 1;
}
if ( Player() && CanReachEntity( Player() ) ) {
SetState( "state_Wait" );
return SRESULT_DONE;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_Lead( stateParms_t* parms ) {
if ( !current_path.GetEntity() ) {
SetState( "state_Idle" );
return SRESULT_DONE;
}
if ( parms->stage == 0 ) {
nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
leadTimeout = DelayTime( SENTRY_LEAD_TIMEOUT );
MoveToEntity( current_path.GetEntity() );
parms->stage = 1;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
if ( !light_on && Time() > nextLookTime ) {
LookAtEntity( Player(), SENTRY_LOOK_TIME );
nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
}
checkBlocked( parms );
const float result = checkDestinationDistance();
if ( result != PLAYER_BEHIND_ME && result != PLAYER_LOST ) {
leadTimeout = DelayTime( SENTRY_LEAD_TIMEOUT );
} else if ( Time() > leadTimeout ) {
SetState( "state_Wait" );
return SRESULT_DONE;
} else if ( !light_on ) {
LookAtEntity( Player(), 0.1f );
nextLookTime += 0.1f;
}
if ( MoveDone() ) {
if ( DestinationUnreachable() ) {
SetState( "state_FindPlayer" );
return SRESULT_DONE;
}
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
finishPathCommand();
SetState( "state_Idle" );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_FindPlayer( stateParms_t* parms ) {
MoveToPosition( lastValidPlayerPosition );
if ( ( MoveDone() || DestinationUnreachable() ) && Player() && !CanReachEntity( Player() ) ) {
SetState( "state_CantReachPlayer" );
return SRESULT_DONE;
}
if ( checkForEnemy( true ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
checkBlocked( parms );
const float result = checkDestinationDistance();
if ( Player() && CanSeeEntity( Player() ) && ( result == PLAYER_BEHIND_ME || result == PLAYER_OK ) ) {
SetState( "state_Wait" );
} else if ( result == PLAYER_AHEAD_OF_ME ) {
SetState( "state_Lead" );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::state_Combat( stateParms_t* parms ) {
FaceEnemy();
LookAtEnemy( 1.0f );
if ( EnemyDead() || !GetEnemy() ) {
StartSoundKey( "snd_target_lost", SND_CHANNEL_VOICE, false );
if ( owner ) {
owner->AI_ENEMY_DEAD = false;
}
ClearEnemy();
SetState( "state_Idle" );
return SRESULT_DONE;
}
const int attackFlags = check_attacks();
if ( attackFlags ) {
do_attack( attackFlags );
return SRESULT_WAIT;
}
if ( CanReachEnemy() ) {
StartAction( "combat_chase" );
} else if ( !find_attack_position() ) {
ignore( GetEnemy() );
checkForEnemy( false );
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::combat_chase( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
MoveToEnemy();
parms->stage = 1;
}
if ( DestinationUnreachable() ) {
StopMove();
return SRESULT_DONE;
}
if ( EnemyDead() ) {
StartSoundKey( "snd_target_lost", SND_CHANNEL_VOICE, false );
if ( owner ) {
owner->AI_ENEMY_DEAD = false;
}
ClearEnemy();
SetState( "state_Idle" );
return SRESULT_DONE;
}
if ( MoveDone() ) {
MoveToEnemy();
}
LookAtEnemy( 1.0f );
const int attackFlags = check_attacks();
if ( attackFlags ) {
do_attack( attackFlags );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmCharSentry::combat_range( stateParms_t* parms ) {
switch ( parms->stage ) {
case 0:
FaceEnemy();
fire = true;
parms->stage = 1;
return SRESULT_WAIT;
case 1:
if ( CanHitEnemyFromAnim( "range_attack_loop" ) ) {
return SRESULT_WAIT;
}
fire = false;
parms->stage = 2;
return SRESULT_WAIT;
case 2:
if ( InAnimState( ANIMCHANNEL_TORSO, "Torso_RangeAttack" ) ) {
return SRESULT_WAIT;
}
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
void rvmCharSentry::spawn_flashlight() {
if ( light.GetEntity() || !owner ) {
return;
}
PlayAnim( ANIMCHANNEL_TORSO, "ik_pose" );
idDict args;
args.Set( "classname", "light" );
args.Set( "name", va( "%s_light", owner->GetName() ) );
if ( GetIntKey( "flashlight" ) == 2 ) {
const float distance = GetFloatKey( "flashlight_distance", 640.0f );
args.Set( "light_target", "1 0 0" );
args.Set( "light_up", "0 0 .5" );
args.Set( "light_right", "0 -.5 0" );
args.Set( "light_end", va( "%g 0 0", distance ) );
args.Set( "texture", GetKey( "mtr_flashlight" ) );
}
idEntity* ent = NULL;
gameLocal.SpawnEntityDef( args, &ent );
light = ent;
if ( ent ) {
const idVec3 ownerAngles = owner->GetAngles();
ent->SetAngles( idAngles( ownerAngles.x, ownerAngles.y, ownerAngles.z ) );
ent->BindToJoint( owner, "light", true );
ent->SetOrigin( vec3_zero );
if ( GetIntKey( "flashlight" ) != 2 && ent->IsType( idLight::Type ) ) {
static_cast<idLight*>( ent )->SetRadius( 256.0f );
static_cast<idLight*>( ent )->SetShader( GetKey( "mtr_flashlight" ) );
}
}
StopAnim( ANIMCHANNEL_TORSO, 0 );
flashlight_off();
}
void rvmCharSentry::flashlight_off() {
if ( !light.GetEntity() ) {
return;
}
if ( light.GetEntity()->IsType( idLight::Type ) ) {
static_cast<idLight*>( light.GetEntity() )->Off();
}
if ( owner ) {
owner->SetSkin( declManager->FindSkin( GetKey( "skin_flashlight_off" ) ) );
}
light_on = false;
}
void rvmCharSentry::flashlight_on() {
if ( !light.GetEntity() ) {
return;
}
if ( light.GetEntity()->IsType( idLight::Type ) ) {
static_cast<idLight*>( light.GetEntity() )->On();
}
if ( owner ) {
owner->SetSkin( declManager->FindSkin( GetKey( "skin_flashlight_on" ) ) );
}
light_on = true;
}
void rvmCharSentry::ignore( idEntity* ignore_ent ) {
ignore_enemy = ignore_ent;
ignore_enemy_time = Time() + 2.0f;
}
bool rvmCharSentry::checkForEnemy( float use_fov ) {
idEntity* enemy = NULL;
idPlayer* player = Player();
if ( lead_player && player && player->HasEnemies() ) {
enemy = ClosestReachableEnemyOfEntity( player );
if ( ignore_enemy_time > Time() && ignore_enemy.GetEntity() == enemy ) {
enemy = NULL;
}
if ( !enemy ) {
enemy = player->ClosestEnemyToPoint( GetOrigin() );
if ( ignore_enemy_time > Time() && ignore_enemy.GetEntity() == enemy ) {
enemy = NULL;
}
}
}
if ( !enemy ) {
enemy = FindEnemyAI( use_fov != 0.0f );
if ( ignore_enemy_time > Time() && ignore_enemy.GetEntity() == enemy ) {
enemy = NULL;
}
}
if ( enemy ) {
StartSoundKey( "snd_sight_enemy", SND_CHANNEL_VOICE, false );
SetEnemy( enemy );
return true;
}
ClearEnemy();
return false;
}
float rvmCharSentry::checkDestinationDistance() {
idPlayer* player = Player();
if ( !player ) {
return PLAYER_OK;
}
if ( CanReachEntity( player ) ) {
lastValidPlayerPosition = GetReachableEntityPosition( player );
}
idVec3 delta = GetOrigin() - lastValidPlayerPosition;
const float relativeMoveDir = delta * player->GetPhysics()->GetLinearVelocity();
if ( relativeMoveDir > 1.0f ) {
playerMoveState = APPROACHING;
} else if ( relativeMoveDir < -1.0f ) {
playerMoveState = RETREATING;
} else {
playerMoveState = NOT_MOVING;
}
if ( !current_path.GetEntity() ) {
return PLAYER_OK;
}
const float playerDist = ( GetOrigin() - lastValidPlayerPosition ).Length();
if ( playerDist < SENTRY_LEAD_DIST ) {
return PLAYER_OK;
}
if ( !CanSeeEntity( player ) || playerDist > SENTRY_LOST_DIST ) {
const float currentDist = TravelDistanceToEntity( current_path.GetEntity() );
const float leadDistance = TravelDistanceBetweenPoints( lastValidPlayerPosition, current_path.GetEntity()->GetPhysics()->GetOrigin() );
if ( currentDist > leadDistance ) {
return PLAYER_AHEAD_OF_ME;
}
return CanSeeEntity( player ) ? PLAYER_BEHIND_ME : PLAYER_LOST;
}
return PLAYER_OK;
}
bool rvmCharSentry::find_attack_position() {
if ( Time() < nextPositionSearch || !GetEnemy() ) {
return false;
}
nextPositionSearch = Time() + 1.0f;
LocateEnemy();
MoveToAttackPosition( GetEnemy(), "range_attack_loop" );
return !DestinationUnreachable();
}
void rvmCharSentry::do_attack( int attack_flags ) {
if ( attack_flags & ATTACK_MISSILE ) {
StartAction( "combat_range" );
}
}
int rvmCharSentry::check_attacks() {
return CanHitEnemyFromAnim( "range_attack_loop" ) ? ATTACK_MISSILE : 0;
}