mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-04-25 11:01:30 +00:00
1299 lines
27 KiB
Text
1299 lines
27 KiB
Text
/***********************************************************************
|
|
|
|
ai_character_sentry.script
|
|
|
|
***********************************************************************/
|
|
|
|
#define SENTRY_WAIT_GETCLOSER 3
|
|
#define SENTRY_LEAD_TIMEOUT 1
|
|
#define SENTRY_FIRERATE 0.1
|
|
#define SENTRY_MIN_SHOTS 8
|
|
#define SENTRY_MIN_LOOK_DELAY 4
|
|
#define SENTRY_MAX_LOOK_DELAY 8
|
|
#define SENTRY_LOOK_TIME 1.5
|
|
#define SENTRY_LEAD_DIST 140
|
|
#define SENTRY_LOST_DIST 280
|
|
#define SENTRY_WALKTURN 65
|
|
#define SENTRY_WALKTURN2 30
|
|
#define SENTRY_ATTACK_MAX_LENGTH 4
|
|
#define SENTRY_ATTACK_MIN_LENGTH 1.2
|
|
#define SENTRY_WAIT_MAX_LENGTH 1
|
|
#define SENTRY_WAIT_MIN_LENGTH 0.3
|
|
#define SENTRY_MIN_TURN 10
|
|
|
|
#define RETREATING -1
|
|
#define NOT_MOVING 0
|
|
#define APPROACHING 1
|
|
|
|
#define RETREATING_DEST -1
|
|
#define APPROACHING_DEST 1
|
|
|
|
#define PLAYER_LOST 0
|
|
#define PLAYER_BEHIND_ME 1
|
|
#define PLAYER_OK 2
|
|
#define PLAYER_AHEAD_OF_ME 3
|
|
|
|
object char_sentry : character {
|
|
boolean lead_player;
|
|
float hanging;
|
|
boolean awake;
|
|
boolean fire;
|
|
float playerMoveState;
|
|
entity light;
|
|
boolean light_on;
|
|
float nextPositionSearch;
|
|
entity ignore_enemy;
|
|
float ignore_enemy_time;
|
|
vector lastValidPlayerPosition;
|
|
float playerPositionThread;
|
|
|
|
//
|
|
// States
|
|
//
|
|
void state_WaitForTrigger();
|
|
void state_Idle();
|
|
void state_Killed();
|
|
void state_FollowPath();
|
|
void state_Wait();
|
|
void state_GetCloser();
|
|
void state_CantReachPlayer();
|
|
void state_Lead();
|
|
void state_FindPlayer();
|
|
void state_Combat();
|
|
void combat_chase();
|
|
boolean find_attack_position();
|
|
|
|
// attack checks
|
|
float check_attacks();
|
|
void do_attack( float attack_flags );
|
|
void combat_range();
|
|
|
|
void state_Begin();
|
|
void init();
|
|
void destroy();
|
|
|
|
void spawn_flashlight();
|
|
void flashlight_off();
|
|
void flashlight_on();
|
|
void ignore( entity ignore_ent );
|
|
boolean checkForEnemy( float use_fov );
|
|
|
|
float checkDestinationDistance();
|
|
void updatePlayerPositionThread();
|
|
|
|
// path commands
|
|
void path_sentry_light_on();
|
|
void path_sentry_light_off();
|
|
void path_sentry_shutdown();
|
|
void path_sentry_unlock_door();
|
|
void path_corner();
|
|
void path_sentry_lead_player();
|
|
void path_sentry_ignore_player();
|
|
|
|
// torso anim states
|
|
void Torso_Death();
|
|
void Torso_Idle();
|
|
void Torso_Pain();
|
|
void Torso_RangeAttack();
|
|
|
|
// legs anim states
|
|
void Legs_Fold();
|
|
void Legs_Folded();
|
|
void Legs_Unfold();
|
|
void Legs_Hanging();
|
|
void Legs_Death();
|
|
void Legs_Idle();
|
|
void Legs_Walk();
|
|
void Legs_TurnLeft();
|
|
void Legs_TurnRight();
|
|
};
|
|
|
|
/***********************************************************************
|
|
|
|
Torso animation control
|
|
|
|
***********************************************************************/
|
|
|
|
void char_sentry::Torso_Death() {
|
|
finishAction( "dead" );
|
|
|
|
// never exit
|
|
waitUntil( 0 );
|
|
}
|
|
|
|
void char_sentry::Torso_Idle() {
|
|
idleAnim( ANIMCHANNEL_TORSO, "stand" );
|
|
|
|
eachFrame {
|
|
if ( AI_PAIN ) {
|
|
Torso_Pain();
|
|
idleAnim( ANIMCHANNEL_TORSO, "stand" );
|
|
}
|
|
if ( fire ) {
|
|
animState( ANIMCHANNEL_TORSO, "Torso_RangeAttack", 4 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void char_sentry::Torso_Pain() {
|
|
string animname;
|
|
|
|
animname = getPainAnim();
|
|
setBlendFrames( ANIMCHANNEL_TORSO, 2 );
|
|
playAnim( ANIMCHANNEL_TORSO, animname );
|
|
|
|
while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
// no pain for 2 seconds
|
|
preventPain( 2 );
|
|
|
|
finishAction( "pain" );
|
|
setBlendFrames( ANIMCHANNEL_TORSO, 4 );
|
|
}
|
|
|
|
void char_sentry::Torso_RangeAttack() {
|
|
float endtime;
|
|
float firetime;
|
|
float numshots;
|
|
|
|
setAnimPrefix( "" );
|
|
|
|
playAnim( ANIMCHANNEL_TORSO, "range_attack" );
|
|
while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) {
|
|
if ( AI_PAIN ) {
|
|
Torso_Pain();
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
numshots = 0;
|
|
while( fire || ( numshots < SENTRY_MIN_SHOTS ) ) {
|
|
endtime = RandomDelay( SENTRY_ATTACK_MIN_LENGTH, SENTRY_ATTACK_MAX_LENGTH );
|
|
setBlendFrames( ANIMCHANNEL_TORSO, 2 );
|
|
playCycle( ANIMCHANNEL_TORSO, "range_attack_loop" );
|
|
firetime = sys.getTime();
|
|
while( ( fire || ( numshots < SENTRY_MIN_SHOTS ) ) && ( sys.getTime() < endtime ) ) {
|
|
if ( sys.getTime() >= firetime ) {
|
|
startSound( "snd_fire", SND_CHANNEL_WEAPON, false );
|
|
attackMissile( "Barrel" );
|
|
numshots++;
|
|
firetime = sys.getTime() + SENTRY_FIRERATE;
|
|
}
|
|
if ( AI_PAIN ) {
|
|
Torso_Pain();
|
|
playCycle( ANIMCHANNEL_TORSO, "range_attack_loop" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
if ( !fire && ( numshots >= SENTRY_MIN_SHOTS ) ) {
|
|
break;
|
|
}
|
|
|
|
setBlendFrames( ANIMCHANNEL_TORSO, 2 );
|
|
playCycle( ANIMCHANNEL_TORSO, "range_attack_aim" );
|
|
endtime = RandomDelay( SENTRY_WAIT_MIN_LENGTH, SENTRY_WAIT_MAX_LENGTH );
|
|
while( ( fire || ( numshots < SENTRY_MIN_SHOTS ) ) && ( sys.getTime() < endtime ) ) {
|
|
if ( AI_PAIN ) {
|
|
Torso_Pain();
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_TORSO, "range_attack_end" );
|
|
while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
|
|
if ( AI_PAIN ) {
|
|
Torso_Pain();
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "range_attack" );
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Legs animation control
|
|
|
|
***********************************************************************/
|
|
|
|
void char_sentry::Legs_Death() {
|
|
while( AI_DEAD ) {
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
|
|
}
|
|
|
|
void char_sentry::Legs_Idle() {
|
|
float delta;
|
|
|
|
if ( !AI_FORWARD && !facingIdeal() ) {
|
|
if ( getTurnDelta() > SENTRY_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
|
|
}
|
|
|
|
if ( getTurnDelta() < -SENTRY_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
|
|
}
|
|
}
|
|
|
|
idleAnim( ANIMCHANNEL_LEGS, "stand" );
|
|
|
|
eachFrame {
|
|
if ( !awake ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Fold", 4 );
|
|
}
|
|
if ( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta <= SENTRY_WALKTURN ) && ( delta >= -SENTRY_WALKTURN ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
|
|
}
|
|
}
|
|
if ( !facingIdeal() ) {
|
|
if ( getTurnDelta() > SENTRY_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
|
|
}
|
|
if ( getTurnDelta() < -SENTRY_MIN_TURN ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void char_sentry::Legs_Walk() {
|
|
float delta;
|
|
|
|
playCycle( ANIMCHANNEL_LEGS, "walk" );
|
|
|
|
while( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta > SENTRY_WALKTURN ) || ( delta < -SENTRY_WALKTURN ) ) {
|
|
break;
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 );
|
|
}
|
|
|
|
void char_sentry::Legs_Fold() {
|
|
startSound( "snd_shutdown", SND_CHANNEL_VOICE, false );
|
|
playAnim( ANIMCHANNEL_LEGS, "fold" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
|
|
waitFrame();
|
|
}
|
|
flashlight_off();
|
|
finishAction( "fold" );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
|
|
}
|
|
|
|
void char_sentry::Legs_Folded() {
|
|
playCycle( ANIMCHANNEL_LEGS, "folded" );
|
|
while( !awake ) {
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Unfold", 4 );
|
|
}
|
|
|
|
void char_sentry::Legs_Unfold() {
|
|
playAnim( ANIMCHANNEL_LEGS, "unfold" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "unfold" );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void char_sentry::Legs_Hanging() {
|
|
playCycle( ANIMCHANNEL_LEGS, "releaseidle" );
|
|
while( hanging ) {
|
|
waitFrame();
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_LEGS, "load" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "unload" );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void char_sentry::Legs_TurnLeft() {
|
|
float turnAmount;
|
|
float delta;
|
|
|
|
turnAmount = getTurnDelta();
|
|
if ( turnAmount > 110 ) {
|
|
// do it in two turns
|
|
turnAmount *= 0.5;
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_left" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
|
|
if ( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_right" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
|
|
if ( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
void char_sentry::Legs_TurnRight() {
|
|
float turnAmount;
|
|
float delta;
|
|
|
|
turnAmount = getTurnDelta();
|
|
if ( turnAmount < -110 ) {
|
|
// do it in two turns
|
|
turnAmount *= 0.5;
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_right" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
|
|
if ( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
playAnim( ANIMCHANNEL_LEGS, "turn_left" );
|
|
while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
|
|
if ( AI_FORWARD ) {
|
|
delta = getTurnDelta();
|
|
if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
AI
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Begin
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Begin() {
|
|
fire = false;
|
|
setBoneMod( true );
|
|
|
|
light_on = false;
|
|
if ( getIntKey( "flashlight" ) ) {
|
|
spawn_flashlight();
|
|
}
|
|
|
|
hanging = getIntKey( "hanging" );
|
|
awake = getIntKey( "unfolded" );
|
|
|
|
waitFrame();
|
|
if ( hanging ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Hanging", 0 );
|
|
} else if ( !awake ) {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
|
|
} else {
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
|
|
}
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
|
|
setState( "state_WaitForTrigger" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::init
|
|
=====================
|
|
*/
|
|
void char_sentry::init() {
|
|
lead_player = getIntKey( "lead_player" );
|
|
can_talk = false;
|
|
no_cower_saved = true;
|
|
no_cower = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::destroy
|
|
=====================
|
|
*/
|
|
void char_sentry::destroy() {
|
|
light.remove();
|
|
if ( playerPositionThread ) {
|
|
sys.terminate( playerPositionThread );
|
|
playerPositionThread = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::spawn_flashlight
|
|
=====================
|
|
*/
|
|
void char_sentry::spawn_flashlight() {
|
|
string texture;
|
|
float distance;
|
|
|
|
distance = getFloatKey( "flashlight_distance" );
|
|
if ( !distance ) {
|
|
distance = 640;
|
|
}
|
|
|
|
// use ik_pose to bind light
|
|
playAnim( ANIMCHANNEL_TORSO, "ik_pose" );
|
|
if ( getIntKey( "flashlight" ) == 2 ) {
|
|
// spot light
|
|
sys.setSpawnArg( "light_target", "1 0 0" );
|
|
sys.setSpawnArg( "light_up", "0 0 .5" );
|
|
sys.setSpawnArg( "light_right", "0 -.5 0" );
|
|
sys.setSpawnArg( "light_end", distance + " 0 0" );
|
|
|
|
texture = getKey( "mtr_flashlight" );
|
|
sys.setSpawnArg( "texture", texture );
|
|
|
|
sys.setSpawnArg( "name", getName() + "_light" );
|
|
light = sys.spawn( "light" );
|
|
light.setAngles( getAngles() );
|
|
light.bindToJoint( self, "light", true );
|
|
light.setOrigin( '0 0 0' );
|
|
} else {
|
|
// radial light
|
|
sys.setSpawnArg( "name", getName() + "_light" );
|
|
light = sys.spawn( "light" );
|
|
light.setRadius( 256 );
|
|
light.setAngles( getAngles() + '0 90 0' );
|
|
light.bindToJoint( self, "light", true );
|
|
texture = getKey( "mtr_flashlight" );
|
|
light.setShader( texture );
|
|
light.setOrigin( '0 0 0' );
|
|
}
|
|
stopAnim( ANIMCHANNEL_TORSO, 0 );
|
|
|
|
flashlight_off();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::flashlight_off
|
|
=====================
|
|
*/
|
|
void char_sentry::flashlight_off() {
|
|
string skin;
|
|
|
|
if ( light ) {
|
|
light.Off();
|
|
skin = getKey( "skin_flashlight_off" );
|
|
setSkin( skin );
|
|
light_on = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::flashlight_on
|
|
=====================
|
|
*/
|
|
void char_sentry::flashlight_on() {
|
|
string skin;
|
|
|
|
if ( light ) {
|
|
light.On();
|
|
skin = getKey( "skin_flashlight_on" );
|
|
setSkin( skin );
|
|
light_on = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::ignore
|
|
=====================
|
|
*/
|
|
void char_sentry::ignore( entity ignore_ent ) {
|
|
ignore_enemy = ignore_ent;
|
|
ignore_enemy_time = sys.getTime() + 2;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::checkForEnemy
|
|
=====================
|
|
*/
|
|
boolean char_sentry::checkForEnemy( float use_fov ) {
|
|
entity enemy;
|
|
entity ent;
|
|
|
|
enemy = $null_entity;
|
|
if ( lead_player && $player1.hasEnemies() ) {
|
|
enemy = closestReachableEnemyOfEntity( $player1 );
|
|
if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
|
|
enemy = $null_entity;
|
|
}
|
|
if ( !enemy ) {
|
|
enemy = $player1.closestEnemyToPoint( getOrigin() );
|
|
if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
|
|
enemy = $null_entity;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !enemy ) {
|
|
enemy = findEnemyAI( false );
|
|
if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
|
|
enemy = $null_entity;
|
|
}
|
|
}
|
|
|
|
if ( enemy ) {
|
|
startSound( "snd_sight_enemy", SND_CHANNEL_VOICE, false );
|
|
setEnemy( enemy );
|
|
return true;
|
|
} else {
|
|
clearEnemy();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::checkDestinationDistance()
|
|
=====================
|
|
*/
|
|
float char_sentry::checkDestinationDistance() {
|
|
float playerDist;
|
|
float currentDist;
|
|
float leadDistance;
|
|
float relativeMoveDir;
|
|
vector delta;
|
|
|
|
// determine if the player is moving toward us
|
|
delta = getOrigin() - lastValidPlayerPosition;
|
|
relativeMoveDir = delta * $player1.getLinearVelocity();
|
|
if ( relativeMoveDir > 1 ) {
|
|
// Player is approaching
|
|
playerMoveState = APPROACHING;
|
|
} else if ( relativeMoveDir < -1 ) {
|
|
// Player is moving away from us
|
|
playerMoveState = RETREATING;
|
|
} else {
|
|
// Player is standing still
|
|
playerMoveState = NOT_MOVING;
|
|
}
|
|
|
|
if ( !current_path ) {
|
|
return PLAYER_OK;
|
|
}
|
|
|
|
playerDist = distanceToPoint( lastValidPlayerPosition );
|
|
if ( playerDist < SENTRY_LEAD_DIST ) {
|
|
return PLAYER_OK;
|
|
}
|
|
|
|
if ( !canSee( $player1 ) ) {
|
|
currentDist = travelDistanceToEntity( current_path );
|
|
leadDistance = travelDistanceBetweenPoints( lastValidPlayerPosition, current_path.getOrigin() );
|
|
if ( currentDist > leadDistance ) {
|
|
return PLAYER_AHEAD_OF_ME;
|
|
} else {
|
|
return PLAYER_LOST;
|
|
}
|
|
}
|
|
|
|
if ( playerDist > SENTRY_LOST_DIST ) {
|
|
currentDist = travelDistanceToEntity( current_path );
|
|
leadDistance = travelDistanceBetweenPoints( lastValidPlayerPosition, current_path.getOrigin() );
|
|
if ( currentDist > leadDistance ) {
|
|
return PLAYER_AHEAD_OF_ME;
|
|
} else {
|
|
return PLAYER_BEHIND_ME;
|
|
}
|
|
}
|
|
|
|
return PLAYER_OK;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::updatePlayerPositionThread()
|
|
=====================
|
|
*/
|
|
void char_sentry::updatePlayerPositionThread() {
|
|
vector pos;
|
|
|
|
while( 1 ) {
|
|
if ( canReachEntity( $player1 ) ) {
|
|
lastValidPlayerPosition = getReachableEntityPosition( $player1 );
|
|
}
|
|
//sys.debugBounds( '1 0 0', lastValidPlayerPosition + $player1.getMins(), lastValidPlayerPosition + $player1.getMaxs(), 0 );
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Path commands
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_light_on
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_light_on() {
|
|
flashlight_on();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_light_off
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_light_off() {
|
|
flashlight_off();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_shutdown
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_shutdown() {
|
|
vector ang;
|
|
string triggername;
|
|
entity triggerent;
|
|
|
|
stopMove();
|
|
|
|
ang = current_path.getAngles();
|
|
turnTo( ang_y );
|
|
waitUntil( facingIdeal() );
|
|
|
|
awake = false;
|
|
AI_ACTIVATED = false;
|
|
|
|
if ( playerPositionThread ) {
|
|
sys.terminate( playerPositionThread );
|
|
playerPositionThread = 0;
|
|
}
|
|
waitAction( "fold" );
|
|
|
|
// trigger any entities the path had targeted
|
|
triggername = current_path.getKey( "trigger" );
|
|
if ( triggername != "" ) {
|
|
triggerent = sys.getEntity( triggername );
|
|
if ( triggerent ) {
|
|
triggerent.activate( self );
|
|
}
|
|
}
|
|
|
|
while( !AI_ACTIVATED ) {
|
|
waitFrame();
|
|
}
|
|
|
|
if ( getIntKey( "flashlight_on" ) ) {
|
|
flashlight_on();
|
|
}
|
|
awake = true;
|
|
waitAction( "unfold" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_unlock_door
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_unlock_door() {
|
|
vector ang;
|
|
string triggername;
|
|
entity triggerent;
|
|
|
|
if ( current_path.getKey( "trigger" ) == "" ) {
|
|
// already unlocked the door
|
|
return;
|
|
}
|
|
|
|
ang = current_path.getAngles();
|
|
turnTo( ang_y );
|
|
waitUntil( facingIdeal() );
|
|
|
|
startSound( "snd_open_door", SND_CHANNEL_VOICE, false );
|
|
playCustomAnim( "unlock_door", 4, 4 );
|
|
waitAction( "customAnim" );
|
|
|
|
// trigger any entities the path had targeted
|
|
triggername = current_path.getKey( "trigger" );
|
|
if ( triggername != "" ) {
|
|
triggerent = sys.getEntity( triggername );
|
|
if ( triggerent ) {
|
|
triggerent.activate( self );
|
|
}
|
|
}
|
|
|
|
current_path.setKey( "trigger", "" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_corner
|
|
=====================
|
|
*/
|
|
void char_sentry::path_corner() {
|
|
if ( !lead_player ) {
|
|
setState( "state_FollowPath" );
|
|
}
|
|
|
|
setNeverDormant( true );
|
|
if ( !canSee( $player1 ) ) {
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
|
|
if ( checkDestinationDistance() == PLAYER_BEHIND_ME ) {
|
|
setState( "state_Wait" );
|
|
} else {
|
|
setState( "state_Lead" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_lead_player
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_lead_player() {
|
|
lead_player = true;
|
|
if ( !playerPositionThread ) {
|
|
playerPositionThread = thread updatePlayerPositionThread();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::path_sentry_ignore_player
|
|
=====================
|
|
*/
|
|
void char_sentry::path_sentry_ignore_player() {
|
|
lead_player = false;
|
|
if ( playerPositionThread ) {
|
|
sys.terminate( playerPositionThread );
|
|
playerPositionThread = 0;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
States
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Killed
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Killed() {
|
|
stopMove();
|
|
|
|
if ( playerPositionThread ) {
|
|
sys.terminate( playerPositionThread );
|
|
playerPositionThread = 0;
|
|
}
|
|
|
|
flashlight_off();
|
|
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
|
|
animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
|
|
|
|
waitAction( "dead" );
|
|
|
|
float burnDelay = getFloatKey( "burnaway" );
|
|
if ( burnDelay != 0 ) {
|
|
preBurn();
|
|
sys.wait( burnDelay );
|
|
burn();
|
|
startSound( "snd_burn", SND_CHANNEL_BODY, false );
|
|
sys.wait( 3 );
|
|
remove();
|
|
}
|
|
|
|
stopThinking();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_WaitForTrigger
|
|
=====================
|
|
*/
|
|
void char_sentry::state_WaitForTrigger() {
|
|
if ( hanging != 2 ) {
|
|
AI_ACTIVATED = false;
|
|
while( !AI_ACTIVATED ) {
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
if ( getIntKey( "flashlight_on" ) ) {
|
|
flashlight_on();
|
|
}
|
|
|
|
if ( hanging ) {
|
|
hanging = false;
|
|
awake = true;
|
|
waitAction( "unload" );
|
|
} else if ( !awake ) {
|
|
awake = true;
|
|
waitAction( "unfold" );
|
|
}
|
|
|
|
// hanging guys turn off damage untill they're out. make sure we get damage turned back on.
|
|
allowDamage();
|
|
lookAt( $player1, 3 );
|
|
|
|
current_path = randomPath();
|
|
//next_path = current_path.randomPath();
|
|
lookAt( $player1, 3 );
|
|
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Idle
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Idle() {
|
|
stopMove();
|
|
|
|
if ( lead_player && !playerPositionThread ) {
|
|
playerPositionThread = thread updatePlayerPositionThread();
|
|
}
|
|
|
|
if ( current_path ) {
|
|
executePathCommand( current_path );
|
|
}
|
|
|
|
while( 1 ) {
|
|
if ( lead_player ) {
|
|
if ( !canSee( $player1 ) ) {
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
}
|
|
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
if ( AI_PUSHED ) {
|
|
getOutOfWay();
|
|
}
|
|
if ( current_path ) {
|
|
executePathCommand( current_path );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_FollowPath
|
|
=====================
|
|
*/
|
|
void char_sentry::state_FollowPath() {
|
|
if ( !current_path ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
moveToEntity( current_path );
|
|
|
|
while( 1 ) {
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
|
|
checkBlocked();
|
|
|
|
if ( AI_MOVE_DONE ) {
|
|
if ( AI_DEST_UNREACHABLE ) {
|
|
waitFrame();
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
setNeverDormant( getFloatKey( "neverdormant" ) );
|
|
finishPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Wait
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Wait() {
|
|
float getCloserTime;
|
|
|
|
float result = checkDestinationDistance();
|
|
if ( result == PLAYER_LOST ) {
|
|
setState( "state_FindPlayer" );
|
|
} else if ( result == PLAYER_AHEAD_OF_ME ) {
|
|
setState( "state_Lead" );
|
|
} else if ( ( result == PLAYER_OK ) && ( playerMoveState == APPROACHING ) ) {
|
|
setState( "state_Lead" );
|
|
}
|
|
|
|
stopMove();
|
|
startSound( "snd_waiting_for_player", SND_CHANNEL_VOICE, false );
|
|
|
|
getCloserTime = sys.getTime() + SENTRY_WAIT_GETCLOSER;
|
|
while( 1 ) {
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
|
|
lookAt( $player1, 0.1 );
|
|
|
|
if ( AI_PUSHED ) {
|
|
getOutOfWay();
|
|
}
|
|
|
|
result = checkDestinationDistance();
|
|
if ( result == PLAYER_LOST ) {
|
|
setState( "state_FindPlayer" );
|
|
} else if ( result == PLAYER_AHEAD_OF_ME ) {
|
|
setState( "state_Lead" );
|
|
} else if ( ( result == PLAYER_OK ) && ( playerMoveState == APPROACHING ) ) {
|
|
setState( "state_Lead" );
|
|
} else if ( ( sys.getTime() > getCloserTime ) && ( result == PLAYER_BEHIND_ME ) ) {
|
|
setState( "state_GetCloser" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_GetCloser
|
|
=====================
|
|
*/
|
|
void char_sentry::state_GetCloser() {
|
|
while( 1 ) {
|
|
moveToPosition( lastValidPlayerPosition );
|
|
if ( AI_MOVE_DONE ) {
|
|
if ( !canReachEntity( $player1 ) ) {
|
|
setState( "state_CantReachPlayer" );
|
|
}
|
|
}
|
|
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
|
|
checkBlocked();
|
|
|
|
float result = checkDestinationDistance();
|
|
|
|
if ( result == PLAYER_LOST ) {
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
if ( result == PLAYER_OK ) {
|
|
setState( "state_Wait" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_CantReachPlayer
|
|
=====================
|
|
*/
|
|
void char_sentry::state_CantReachPlayer() {
|
|
startSound( "snd_cant_reach_player", SND_CHANNEL_VOICE, false );
|
|
stopMove();
|
|
while( 1 ) {
|
|
if ( canReachEntity( $player1 ) ) {
|
|
setState( "state_Wait" );
|
|
}
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Lead
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Lead() {
|
|
float nextLookTime;
|
|
float leadTimeOut;
|
|
|
|
if ( !current_path ) {
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
|
|
moveToEntity( current_path );
|
|
|
|
leadTimeOut = sys.getTime() + SENTRY_LEAD_TIMEOUT;
|
|
|
|
while( 1 ) {
|
|
if ( checkForEnemy( true ) ) {
|
|
setState( "state_Combat" );
|
|
}
|
|
|
|
if ( !light_on && ( sys.getTime() > nextLookTime ) ) {
|
|
lookAt( $player1, SENTRY_LOOK_TIME );
|
|
nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
|
|
}
|
|
|
|
checkBlocked();
|
|
|
|
float result = checkDestinationDistance();
|
|
if ( ( result != PLAYER_BEHIND_ME ) && ( result != PLAYER_LOST ) ) {
|
|
leadTimeOut = sys.getTime() + SENTRY_LEAD_TIMEOUT;
|
|
} else if ( sys.getTime() > leadTimeOut ) {
|
|
setState( "state_Wait" );
|
|
} else if ( !light_on ) {
|
|
// look back at player to see what he's up to
|
|
lookAt( $player1, 0.1 );
|
|
nextLookTime += 0.1;
|
|
}
|
|
|
|
if ( AI_MOVE_DONE ) {
|
|
if ( AI_DEST_UNREACHABLE ) {
|
|
waitFrame();
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
setNeverDormant( getFloatKey( "neverdormant" ) );
|
|
finishPathCommand();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_FindPlayer
|
|
=====================
|
|
*/
|
|
void char_sentry::state_FindPlayer() {
|
|
while( 1 ) {
|
|
moveToPosition( lastValidPlayerPosition );
|
|
if ( AI_MOVE_DONE || AI_DEST_UNREACHABLE ) {
|
|
if ( !canReachEntity( $player1 ) ) {
|
|
setState( "state_CantReachPlayer" );
|
|
}
|
|
}
|
|
|
|
if ( checkForEnemy( true ) ) {
|
|
waitFrame();
|
|
setState( "state_Combat" );
|
|
}
|
|
|
|
checkBlocked();
|
|
|
|
float result = checkDestinationDistance();
|
|
if ( canSee( $player1 ) && ( ( result == PLAYER_BEHIND_ME ) || ( result == PLAYER_OK ) ) ) {
|
|
setState( "state_Wait" );
|
|
} else if ( result == PLAYER_AHEAD_OF_ME ) {
|
|
setState( "state_Lead" );
|
|
}
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::state_Combat
|
|
=====================
|
|
*/
|
|
void char_sentry::state_Combat() {
|
|
float attack_flags;
|
|
|
|
eachFrame {
|
|
faceEnemy();
|
|
lookAtEnemy( 1 );
|
|
|
|
if ( AI_ENEMY_DEAD || !getEnemy() ) {
|
|
startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
|
|
AI_ENEMY_DEAD = false;
|
|
clearEnemy();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
attack_flags = check_attacks();
|
|
if ( attack_flags ) {
|
|
do_attack( attack_flags );
|
|
continue;
|
|
}
|
|
|
|
if ( canReachEnemy() ) {
|
|
combat_chase();
|
|
} else if ( !find_attack_position() ) {
|
|
ignore( getEnemy() );
|
|
checkForEnemy( false );
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::combat_chase
|
|
=====================
|
|
*/
|
|
void char_sentry::combat_chase() {
|
|
float attack_flags;
|
|
|
|
moveToEnemy();
|
|
while( !AI_DEST_UNREACHABLE ) {
|
|
if ( AI_ENEMY_DEAD ) {
|
|
startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
|
|
AI_ENEMY_DEAD = false;
|
|
clearEnemy();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
if ( AI_MOVE_DONE ) {
|
|
if ( !enemyPositionValid() ) {
|
|
clearEnemy();
|
|
setState( "state_Idle" );
|
|
}
|
|
moveToEnemy();
|
|
}
|
|
|
|
lookAtEnemy( 1 );
|
|
|
|
attack_flags = check_attacks();
|
|
if ( attack_flags ) {
|
|
do_attack( attack_flags );
|
|
return;
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
stopMove();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::find_attack_position
|
|
=====================
|
|
*/
|
|
boolean char_sentry::find_attack_position() {
|
|
float attack_flags;
|
|
|
|
if ( sys.getTime() < nextPositionSearch ) {
|
|
return false;
|
|
}
|
|
nextPositionSearch = sys.getTime() + 1;
|
|
locateEnemy();
|
|
moveToAttackPosition( getEnemy(), "range_attack_loop" );
|
|
if ( AI_DEST_UNREACHABLE ) {
|
|
return false;
|
|
}
|
|
|
|
while( !AI_MOVE_DONE ) {
|
|
if ( AI_ENEMY_DEAD ) {
|
|
startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
|
|
AI_ENEMY_DEAD = false;
|
|
clearEnemy();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
lookAtEnemy( 1 );
|
|
|
|
attack_flags = check_attacks();
|
|
if ( attack_flags ) {
|
|
do_attack( attack_flags );
|
|
return true;
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
stopMove();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::do_attack
|
|
=====================
|
|
*/
|
|
void char_sentry::do_attack( float attack_flags ) {
|
|
if ( attack_flags & ATTACK_MISSILE ) {
|
|
combat_range();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::check_attacks
|
|
=====================
|
|
*/
|
|
float char_sentry::check_attacks() {
|
|
float attack_flags = 0;
|
|
|
|
if ( canHitEnemyFromAnim( "range_attack_loop" ) ) {
|
|
attack_flags |= ATTACK_MISSILE;
|
|
}
|
|
|
|
return attack_flags;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
char_sentry::combat_range
|
|
=====================
|
|
*/
|
|
void char_sentry::combat_range() {
|
|
faceEnemy();
|
|
fire = true;
|
|
while( canHitEnemyFromAnim( "range_attack_loop" ) ) {
|
|
waitFrame();
|
|
}
|
|
fire = false;
|
|
waitUntil( !inAnimState( ANIMCHANNEL_TORSO, "Torso_RangeAttack" ) );
|
|
}
|