// 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( ent )->SetRadius( 256.0f ); static_cast( 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( 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( 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; }