mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-16 08:10:32 +02:00
1042 lines
33 KiB
C++
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;
|
|
}
|