// // TODO: // - unarmed posture // - relaxed posture // - turning in place too much, rely more on look angles? #include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "AI_Manager.h" #include "AI_Util.h" #include "AI_Tactical.h" const idEventDef AI_ForcePosture ( "forcePosture", "d" ); CLASS_DECLARATION( idAI, rvAITactical ) EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture ) EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn ) END_CLASS static const char* aiPostureString[AIPOSTURE_MAX] = { "stand", // AIPOSTURE_STAND, "crouch", // AIPOSTURE_CROUCH, "cover_left", // AIPOSTURE_STAND_COVER_LEFT, "cover_right", // AIPOSTURE_STAND_COVER_RIGHT, "crouch_cover", // AIPOSTURE_CROUCH_COVER "crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT, "crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT, "relaxed", // AIPOSTURE_RELAXED "unarmed", // AIPOSTURE_UNARMED "at_attention", // AIPOSTURE_AT_ATTENTION }; /* ================ rvAITactical::rvAITactical ================ */ rvAITactical::rvAITactical ( void ) { shots = 0; nextWallTraceTime = 0; } void rvAITactical::InitSpawnArgsVariables ( void ) { // Initialize the posture info InitPostureInfo ( ); maxShots = spawnArgs.GetInt ( "maxShots", "1" ); minShots = spawnArgs.GetInt ( "minShots", "1" ); fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) ); healthRegen = spawnArgs.GetInt( "healthRegen", "2" ); healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" ); } /* ================ rvAITactical::Spawn ================ */ void rvAITactical::Spawn ( void ) { InitSpawnArgsVariables(); // Force a posture? const char* temp; postureForce = AIPOSTURE_DEFAULT; if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) { for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) { if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) { break; } } if ( postureForce >= AIPOSTURE_MAX ) { postureForce = AIPOSTURE_DEFAULT; } } UpdatePosture ( ); postureCurrent = postureIdeal; OnPostureChange ( ); ammo = spawnArgs.GetInt ( "ammo", "-1" ); // Initialize custom actions actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK ); actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK ); actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" ); // playerFocusTime = 0; // playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" )); healthRegenNextTime = 0; maxHealth = health; } /* ================ rvAITactical::Think ================ */ void rvAITactical::Think ( void ) { idAI::Think ( ); // If not simple thinking and not in an action, update the posture if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) { if ( UpdatePosture ( ) ) { PerformAction ( "Torso_SetPosture", 4, true ); } } // FIXME: disabled for now, its annoying people /* idPlayer* localPlayer; localPlayer = gameLocal.GetLocalPlayer(); // If the player has been standing in front of the marine and looking at him for too long he should say something if ( !aifl.dead && playerFocusTime && playerAnnoyTime && !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking() && !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now && DistanceTo( localPlayer ) < 64.0f ) { idVec3 diff; diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin(); diff.NormalizeFast(); // Is the player looking at the marine? if ( diff * localPlayer->viewAxis[0] > 0.7f ) { // Say something every 5 seconds if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) { // Debounce it against other marines if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) { Speak ( "lipsync_canihelpyou", true ); aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 ); } } } else { playerFocusTime = gameLocal.time; } } else { playerFocusTime = gameLocal.time; } */ if ( health > 0 ) { //alive if ( healthRegenEnabled && healthRegen ) { if ( gameLocal.GetTime() >= healthRegenNextTime ) { health = idMath::ClampInt( 0, maxHealth, health+healthRegen ); healthRegenNextTime = gameLocal.GetTime() + 1000; } } } //crappy place to do this, just testing bool clearPrefix = true; bool facingWall = false; if ( move.fl.moving && InCoverMode() && combat.fl.aware ) { clearPrefix = false; if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { facingWall = true; } else if ( nextWallTraceTime < gameLocal.GetTime() ) { //do an occasional check for solid architecture directly in front of us nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750; trace_t wallTrace; idVec3 start, end; idMat3 axis; if ( neckJoint != INVALID_JOINT ) { GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis ); end = start + axis[0] * 32.0f; } else { start = GetEyePosition(); start += viewAxis[0] * 8.0f;//still inside bbox end = start + viewAxis[0] * 32.0f; } //trace against solid arcitecture only, don't care about other entities gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this ); if ( wallTrace.fraction < 1.0f ) { facingWall = true; } else { clearPrefix = true; } } } if ( facingWall ) { if ( !animPrefix.Length() ) { animPrefix = "nearcover"; } } else if ( clearPrefix && animPrefix == "nearcover" ) { animPrefix = ""; } } /* ================ rvAITactical::Save ================ */ void rvAITactical::Save( idSaveGame *savefile ) const { savefile->WriteSyncId(); savefile->WriteInt ( ammo ); savefile->WriteInt ( shots ); // savefile->WriteInt ( playerFocusTime ); // savefile->WriteInt ( playerAnnoyTime ); savefile->WriteInt ( (int&)postureIdeal ); savefile->WriteInt ( (int&)postureCurrent ); savefile->WriteInt ( (int&)postureForce ); // TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson: savefile->WriteInt ( healthRegenNextTime ); savefile->WriteInt ( maxHealth ); savefile->WriteInt ( nextWallTraceTime ); actionElbowAttack.Save ( savefile ); actionKillswitchAttack.Save ( savefile ); actionTimerPeek.Save ( savefile ); } /* ================ rvAITactical::Restore ================ */ void rvAITactical::Restore( idRestoreGame *savefile ) { InitSpawnArgsVariables ( ); savefile->ReadSyncId( "rvAITactical" ); savefile->ReadInt ( ammo ); savefile->ReadInt ( shots ); // savefile->ReadInt ( playerFocusTime ); // savefile->ReadInt ( playerAnnoyTime ); savefile->ReadInt ( (int&)postureIdeal ); savefile->ReadInt ( (int&)postureCurrent ); savefile->ReadInt ( (int&)postureForce ); // TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; savefile->ReadInt ( healthRegenNextTime ); savefile->ReadInt ( maxHealth ); savefile->ReadInt ( nextWallTraceTime ); actionElbowAttack.Restore ( savefile ); actionKillswitchAttack.Restore ( savefile ); actionTimerPeek.Restore ( savefile ); UpdateAnimPrefix ( ); } /* ================ rvAITactical::CanTurn ================ */ bool rvAITactical::CanTurn ( void ) const { if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) { return false; } return idAI::CanTurn ( ); } /* ================ rvAITactical::CanMove ================ */ bool rvAITactical::CanMove ( void ) const { if ( !postureInfo[postureCurrent].fl.canMove ) { return false; } return idAI::CanMove ( ); } /* ================ rvAITactical::CheckAction_Reload ================ */ bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) { if ( ammo == 0 ) { return true; } return false; } /* ================ rvAITactical::CheckActions ================ */ bool rvAITactical::CheckActions ( void ) { // Pain? if ( CheckPainActions ( ) ) { return true; } // If we are pressed, fight-- do not break melee combat until you or the enemy is dead. if ( IsMeleeNeeded ( )) { if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { return true; } //take no actions other than fighting return false; } // Handle any posture changes if ( postureIdeal != postureCurrent ) { PerformAction ( "Torso_SetPosture", 4, true ); return true; } // Reload takes precedence if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) { PerformAction ( "Torso_Reload", 4, false ); return true; } if ( IsBehindCover ( ) ) { // If we have no enemy try peeking if ( !IsEnemyRecentlyVisible ( ) ) { if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) { if ( actionTimerPeek.IsDone ( actionTime ) ) { actionTimerPeek.Reset ( actionTime, 0.5f ); aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 ); PerformAction ( "Torso_Cover_Peek", 4, false ); return true; } } } // Attacks from cover if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { // Kill switch attack from cover? if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) { if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) { return true; } } if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } } return false; } // Standard attacks if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { return true; } // Ranged attack only if there is ammo if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } } return false; } /* ================ rvAITactical::CheckRelaxed Returns true if the marine should currently be in a relaxed state ================ */ bool rvAITactical::CheckRelaxed ( void ) const { // if ( forceRelaxed ) { // return true; // } /* // If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) { return true; } */ // Alwasy relaxed when ignoring enemies if ( !combat.fl.aware ) { return true; } if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) { return false; } if ( gameLocal.time >= focusTime ) { return false; } return true; } /* ================ rvAITactical::GetIdleAnimName ================ */ const char* rvAITactical::GetIdleAnimName ( void ) { return "idle"; } /* ================ rvAITactical::UpdateAnimPrefix ================ */ void rvAITactical::UpdateAnimPrefix ( void ) { if ( postureCurrent == AIPOSTURE_STAND ) { animPrefix = ""; } else { animPrefix = aiPostureString[postureCurrent]; } } /* ================ rvAITactical::InitPostureInfo ================ */ void rvAITactical::InitPostureInfo ( void ) { int posture; for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) { aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture]; postureCurrent = (aiPosture_t)posture; UpdateAnimPrefix ( ); info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true ); info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true ); info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true ); info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true ); info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true ); info.fl.canTurn = false; } // FIXME: this should be based on the availablity of turn anims postureInfo[AIPOSTURE_STAND].fl.canTurn = true; postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true; postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true; } /* ================ rvAITactical::UpdatePosture ================ */ bool rvAITactical::UpdatePosture ( void ) { // If the posture is being forced then use that until its no longer forced if ( postureForce != AIPOSTURE_DEFAULT ) { postureIdeal = postureForce; // Not forcing posture, determine it from our current state } else { postureIdeal = AIPOSTURE_STAND; // Behind cover? if ( IsBehindCover ( ) ) { bool left; if ( enemy.ent ) { left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f; } else if ( tether ) { left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f; } else { left = false; } // Should be crouching behind cover? if ( InCrouchCoverMode ( ) ) { if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT; } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT; } else { postureIdeal = AIPOSTURE_CROUCH_COVER; } } else { if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { postureIdeal = AIPOSTURE_STAND_COVER_LEFT; } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) { postureIdeal = AIPOSTURE_STAND_COVER_LEFT; } else { postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; } } } else if ( combat.fl.aware //aggressive && (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction && ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here //we crouch only if leader is postureIdeal = AIPOSTURE_CROUCH; } else if ( CheckRelaxed ( ) ) { postureIdeal = AIPOSTURE_RELAXED; } //never crouch in melee! if( IsMeleeNeeded() ) { postureIdeal = AIPOSTURE_STAND; } } // Default the posture if trying to move with one that doesnt support it if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) { postureIdeal = AIPOSTURE_STAND; // Default the posture if trying to turn and we cant in the posture we chose } else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) { postureIdeal = AIPOSTURE_STAND; } return (postureIdeal != postureCurrent); } /* ================ rvAITactical::OnPostureChange ================ */ void rvAITactical::OnPostureChange ( void ) { UpdateAnimPrefix ( ); } /* ============ rvAITactical::OnSetKey ============ */ void rvAITactical::OnSetKey ( const char* key, const char* value ) { idAI::OnSetKey ( key, value ); /* if ( !idStr::Icmp ( key, "annoyed" ) ) { playerAnnoyTime = SEC2MS( atof ( value ) ); } */ } /* ================ rvAITactical::OnStopMoving ================ */ void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { // Ensure the peek doesnt happen immedately every time we stop at a cover if ( IsBehindCover ( ) ){ actionTimerPeek.Clear ( actionTime ); actionTimerPeek.Add ( 2000, 0.5f ); actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity ); // We should be looking fairly close to the right direction, so just snap it TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); move.current_yaw = move.ideal_yaw; } idAI::OnStopMoving ( oldMoveCommand ); } /* ================ rvAITactical::CalculateShots ================ */ void rvAITactical::CalculateShots ( const char* fireAnim ) { // Random number of shots ( scale by aggression range) shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; if ( shots > ammo ) { shots = ammo; } // Update the firing animation playback rate int animNum; animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); if ( animNum != 0 ) { const idAnim* anim = GetAnimator()->GetAnim ( animNum ); if ( anim ) { GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); } } } /* ================ rvAITactical::UseAmmo ================ */ void rvAITactical::UseAmmo ( int amount ) { if ( ammo <= 0 ) { return; } shots--; ammo-=amount; if ( ammo < 0 ) { ammo = 0; shots = 0; } } /* ================ rvAITactical::GetDebugInfo ================ */ void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { // Base class first idAI::GetDebugInfo ( proc, userData ); proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData ); proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData ); proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData ); proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData ); proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData ); proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData ); proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData ); proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData ); } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvAITactical ) STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack ) STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack ) STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack ) STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack ) STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack ) STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek ) STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload ) STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture ) STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek ) END_CLASS_STATES /* ================ rvAITactical::State_Torso_SetPosture ================ */ stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT_RELAXED, STAGE_WAIT }; switch ( parms.stage ) { case STAGE_INIT: { idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] ); if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) { postureCurrent = postureIdeal; OnPostureChange ( ); return SRESULT_DONE; } if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT || postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT || (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) ) { // FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION move.fl.allowAnimMove = false; } else { //no need to play cover-to-stand/relaxed transition if: //scripted... //or we're moving already... //or turning away from our old cover direction... if ( aifl.scripted /*|| (move.fl.moving&&!move.fl.blocked) || (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) { postureCurrent = postureIdeal; OnPostureChange ( ); return SRESULT_DONE; } } DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames ); if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT && postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT && postureIdeal == AIPOSTURE_RELAXED ) { //we need to also play stand_to_relaxed at the end... return SRESULT_STAGE ( STAGE_WAIT_RELAXED ); } return SRESULT_STAGE ( STAGE_WAIT ); } case STAGE_WAIT_RELAXED: if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) { PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames ); } return SRESULT_STAGE ( STAGE_WAIT ); } return SRESULT_WAIT; case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { postureCurrent = postureIdeal; OnPostureChange ( ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Torso_RangedAttack ================ */ stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) { enum { STAGE_START, STAGE_START_WAIT, STAGE_SHOOT, STAGE_SHOOT_WAIT, STAGE_END, STAGE_END_WAIT, }; switch ( parms.stage ) { case STAGE_START: // If moving switch to the moving ranged attack (torso only) if ( move.fl.moving && FacingIdeal() ) { PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); return SRESULT_DONE; } // Full body animations DisableAnimState ( ANIMCHANNEL_LEGS ); CalculateShots ( "range_attack" ); // Attack lead in animation? if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames ); return SRESULT_STAGE ( STAGE_START_WAIT ); } return SRESULT_STAGE ( STAGE_SHOOT ); case STAGE_START_WAIT: // When the pre shooting animation is done head over to shooting if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_SHOOT ); } return SRESULT_WAIT; case STAGE_SHOOT: PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 ); UseAmmo ( 1 ); return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); case STAGE_SHOOT_WAIT: // When the shoot animation is done either play another shot animation // or finish up with post_shooting if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { // If our enemy is no longer in our fov we can stop shooting if ( !enemy.fl.inFov ) { return SRESULT_STAGE ( STAGE_END ); } else if ( enemy.fl.dead ) { //if enemy is dead, stop shooting soon if ( shots > 5 ) { shots = gameLocal.random.RandomInt(6); } } if ( shots <= 0 ) { return SRESULT_STAGE ( STAGE_END ); } return SRESULT_STAGE ( STAGE_SHOOT); } return SRESULT_WAIT; case STAGE_END: // Attack lead in animation? if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames ); return SRESULT_STAGE ( STAGE_END_WAIT ); } return SRESULT_DONE; case STAGE_END_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Torso_MovingRangedAttack ================ */ stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_SHOOT, STAGE_SHOOT_WAIT, }; switch ( parms.stage ) { case STAGE_INIT: CalculateShots ( "range_attack_torso" ); return SRESULT_STAGE ( STAGE_SHOOT ); case STAGE_SHOOT: UseAmmo ( 1 ); PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); case STAGE_SHOOT_WAIT: // When the shoot animation is done either play another shot animation // or finish up with post_shooting if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( enemy.fl.dead ) { //if enemy is dead, stop shooting soon if ( shots > 5 ) { shots = gameLocal.random.RandomInt(6); } } if ( shots <= 0 || !enemy.fl.inFov ) { return SRESULT_DONE; } return SRESULT_STAGE ( STAGE_SHOOT); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Torso_Reload ================ */ stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { ammo = spawnArgs.GetInt ( "ammo" ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Torso_Cover_LeanLeftAttack ================ */ stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) { PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); return SRESULT_DONE; } /* ================ rvAITactical::State_Torso_Cover_LeanRightAttack ================ */ stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) { PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); return SRESULT_DONE; } /* ================ rvAITactical::State_Torso_Cover_LeanAttack ================ */ stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) { enum { STAGE_OUT, STAGE_OUTWAIT, STAGE_FIRE, STAGE_FIREWAIT, STAGE_IN, STAGE_INWAIT, }; switch ( parms.stage ) { case STAGE_OUT: DisableAnimState ( ANIMCHANNEL_LEGS ); // The lean out animation cannot blend with any other animations since // it is essential that the movement delta out match the one back in. Therefore // we force the legs and torso to be stoped before playing any animations torsoAnim.StopAnim ( 0 ); legsAnim.StopAnim ( 0 ); PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 ); return SRESULT_STAGE ( STAGE_OUTWAIT ); case STAGE_OUTWAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { // Random number of shots CalculateShots ( "lean_attack" ); return SRESULT_STAGE ( STAGE_FIRE ); } return SRESULT_WAIT; case STAGE_FIRE: UseAmmo ( 1 ); PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 ); return SRESULT_STAGE ( STAGE_FIREWAIT ); case STAGE_FIREWAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( enemy.fl.dead ) { //if enemy is dead, stop shooting soon if ( shots > 5 ) { shots = gameLocal.random.RandomInt(6); } } if ( shots > 0 ) { return SRESULT_STAGE ( STAGE_FIRE ); } return SRESULT_STAGE ( STAGE_IN ); } return SRESULT_WAIT; case STAGE_IN: PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 ); return SRESULT_STAGE ( STAGE_INWAIT ); case STAGE_INWAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Torso_Cover_Peek ================ */ stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", parms.blendFrames ) ) { return SRESULT_DONE; } return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvAITactical::State_Frame_Peek ================ */ stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) { CheckForEnemy ( true, true ); return SRESULT_OK; } /* ================ rvAITactical::Event_ForcePosture ================ */ void rvAITactical::Event_ForcePosture ( int posture ) { postureForce = (aiPosture_t)posture; } /* =================== rvAITactical::IsCrouching =================== */ bool rvAITactical::IsCrouching( void ) const { if ( postureCurrent == AIPOSTURE_CROUCH || postureCurrent == AIPOSTURE_CROUCH_COVER || postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT || postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) { return true; } return idAI::IsCrouching(); } /* ================ rvAITactical::Event_PostSpawn ================ */ void rvAITactical::Event_PostSpawn( void ) { idAI::Event_PostSpawn(); if ( team == AITEAM_MARINE && healthRegenEnabled ) {//regen-enabled buddy marine if ( CheckDeathCausesMissionFailure() ) {//who is important to a mission if ( g_skill.GetInteger() > 2 ) {//on impossible health *= 1.5f; healthRegen *= 1.5f; } else if ( g_skill.GetInteger() > 1 ) {//on hard health *= 1.2f; healthRegen *= 1.25f; } } } }