diff options
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_update.cpp')
| -rw-r--r-- | game/server/cstrike/bot/cs_bot_update.cpp | 1211 |
1 files changed, 1211 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot_update.cpp b/game/server/cstrike/bot/cs_bot_update.cpp new file mode 100644 index 0000000..d57af0a --- /dev/null +++ b/game/server/cstrike/bot/cs_bot_update.cpp @@ -0,0 +1,1211 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#include "cbase.h" +#include "cs_gamerules.h" +#include "cs_bot.h" +#include "fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------------------------------------- +float CCSBot::GetMoveSpeed( void ) +{ + return 250.0f; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Lightweight maintenance, invoked frequently + */ +void CCSBot::Upkeep( void ) +{ + VPROF_BUDGET( "CCSBot::Upkeep", VPROF_BUDGETGROUP_NPCS ); + + if (TheNavMesh->IsGenerating() || !IsAlive()) + return; + + // If bot_flipout is on, then generate some random commands. + if ( cv_bot_flipout.GetBool() ) + { + int val = RandomInt( 0, 2 ); + if ( val == 0 ) + MoveForward(); + else if ( val == 1 ) + MoveBackward(); + + val = RandomInt( 0, 2 ); + if ( val == 0 ) + StrafeLeft(); + else if ( val == 1 ) + StrafeRight(); + + if ( RandomInt( 0, 5 ) == 0 ) + Jump( true ); + + val = RandomInt( 0, 2 ); + if ( val == 0 ) + Crouch(); + else ( val == 1 ); + StandUp(); + + return; + } + + // BOTPORT: Remove this nasty hack + m_eyePosition = EyePosition(); + + Vector myOrigin = GetCentroid( this ); + + // aiming must be smooth - update often + if (IsAimingAtEnemy()) + { + UpdateAimOffset(); + + // aim at enemy, if he's still alive + if (m_enemy != NULL && m_enemy->IsAlive()) + { + Vector enemyOrigin = GetCentroid( m_enemy ); + + if (m_isEnemyVisible) + { + // + // Enemy is visible - determine which part of him to shoot at + // + const float sharpshooter = 0.8f; + VisiblePartType aimAtPart; + + if (IsUsingMachinegun()) + { + // spray the big machinegun at the enemy's gut + aimAtPart = GUT; + } + else if (IsUsing( WEAPON_AWP ) || IsUsingShotgun()) + { + // these weapons are best aimed at the chest + aimAtPart = GUT; + } + else if (GetProfile()->GetSkill() > 0.5f && IsActiveWeaponRecoilHigh() ) + { + // sprayin' and prayin' - aim at the gut since we're not going to be accurate + aimAtPart = GUT; + } + else if (GetProfile()->GetSkill() < sharpshooter) + { + // low skill bots don't go for headshots + aimAtPart = GUT; + } + else + { + // high skill - aim for the head + aimAtPart = HEAD; + } + + if (IsEnemyPartVisible( aimAtPart )) + { + m_aimSpot = GetPartPosition( GetBotEnemy(), aimAtPart ); + } + else + { + // desired part is blocked - aim at whatever part is visible + if (IsEnemyPartVisible( GUT )) + { + m_aimSpot = GetPartPosition( GetBotEnemy(), GUT ); + } + else if (IsEnemyPartVisible( HEAD )) + { + m_aimSpot = GetPartPosition( GetBotEnemy(), HEAD ); + } + else if (IsEnemyPartVisible( LEFT_SIDE )) + { + m_aimSpot = GetPartPosition( GetBotEnemy(), LEFT_SIDE ); + } + else if (IsEnemyPartVisible( RIGHT_SIDE )) + { + m_aimSpot = GetPartPosition( GetBotEnemy(), RIGHT_SIDE ); + } + else // FEET + { + m_aimSpot = GetPartPosition( GetBotEnemy(), FEET ); + } + } + + // high skill bots lead the target a little to compensate for update tick latency + /* + if (false && GetProfile()->GetSkill() > 0.5f) + { + const float k = 1.0f; + m_aimSpot += k * g_flBotCommandInterval * (m_enemy->GetAbsVelocity() - GetAbsVelocity()); + } + */ + + } + else + { + // aim where we last saw enemy - but bend the ray so we dont point directly into walls + // if we put this back, make sure you only bend the ray ONCE and keep the bent spot - dont continually recompute + //BendLineOfSight( m_eyePosition, m_lastEnemyPosition, &m_aimSpot ); + m_aimSpot = m_lastEnemyPosition; + } + + // add in aim error + m_aimSpot.x += m_aimOffset.x; + m_aimSpot.y += m_aimOffset.y; + m_aimSpot.z += m_aimOffset.z; + + Vector to = m_aimSpot - EyePositionConst(); + + QAngle idealAngle; + VectorAngles( to, idealAngle ); + + // adjust aim angle for recoil, based on bot skill + const QAngle &punchAngles = GetPunchAngle(); + idealAngle -= punchAngles * GetProfile()->GetSkill(); + + SetLookAngles( idealAngle.y, idealAngle.x ); + } + } + else + { + if (m_lookAtSpotClearIfClose) + { + // dont look at spots just in front of our face - it causes erratic view rotation + const float tooCloseRange = 100.0f; + if ((m_lookAtSpot - myOrigin).IsLengthLessThan( tooCloseRange )) + m_lookAtSpotState = NOT_LOOKING_AT_SPOT; + } + + switch( m_lookAtSpotState ) + { + case NOT_LOOKING_AT_SPOT: + { + // look ahead + SetLookAngles( m_lookAheadAngle, 0.0f ); + break; + } + + case LOOK_TOWARDS_SPOT: + { + UpdateLookAt(); + if (IsLookingAtPosition( m_lookAtSpot, m_lookAtSpotAngleTolerance )) + { + m_lookAtSpotState = LOOK_AT_SPOT; + m_lookAtSpotTimestamp = gpGlobals->curtime; + } + break; + } + + case LOOK_AT_SPOT: + { + UpdateLookAt(); + + if (m_lookAtSpotDuration >= 0.0f && gpGlobals->curtime - m_lookAtSpotTimestamp > m_lookAtSpotDuration) + { + m_lookAtSpotState = NOT_LOOKING_AT_SPOT; + m_lookAtSpotDuration = 0.0f; + } + break; + } + } + + // have view "drift" very slowly, so view looks "alive" + if (!IsUsingSniperRifle()) + { + float driftAmplitude = 2.0f; + if (IsBlind()) + { + driftAmplitude = 5.0f; + } + + m_lookYaw += driftAmplitude * BotCOS( 33.0f * gpGlobals->curtime ); + m_lookPitch += driftAmplitude * BotSIN( 13.0f * gpGlobals->curtime ); + } + } + + // view angles can change quickly + UpdateLookAngles(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Heavyweight processing, invoked less often + */ +void CCSBot::Update( void ) +{ + VPROF_BUDGET( "CCSBot::Update", VPROF_BUDGETGROUP_NPCS ); + + // If bot_flipout is on, then we only do stuff in Upkeep(). + if ( cv_bot_flipout.GetBool() ) + return; + + Vector myOrigin = GetCentroid( this ); + + // if we are spectating, get into the game + if (GetTeamNumber() == 0) + { + HandleCommand_JoinTeam( m_desiredTeam ); + int desiredClass = GetProfile()->GetSkin(); + if ( m_desiredTeam == TEAM_CT && desiredClass ) + { + desiredClass = FIRST_CT_CLASS + desiredClass - 1; + } + else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass ) + { + desiredClass = FIRST_T_CLASS + desiredClass - 1; + } + HandleCommand_JoinClass( desiredClass ); + return; + } + + + // update our radio chatter + // need to allow bots to finish their chatter even if they are dead + GetChatter()->Update(); + + // check if we are dead + if (!IsAlive()) + { + // remember that we died + m_diedLastRound = true; + + BotDeathThink(); + return; + } + + // the bot is alive and in the game at this point + m_hasJoined = true; + + // + // Debug beam rendering + // + + if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe()) + { + DebugDisplay(); + } + + if (cv_bot_stop.GetBool()) + return; + + // check if we are stuck + StuckCheck(); + + // Check for physics props and other breakables in our way that we can break + BreakablesCheck(); + + // Check for useable doors in our way that we need to open + DoorCheck(); + + // update travel distance to all players (this is an optimization) + UpdateTravelDistanceToAllPlayers(); + + // if our current 'noise' was heard a long time ago, forget it + const float rememberNoiseDuration = 20.0f; + if (m_noiseTimestamp > 0.0f && gpGlobals->curtime - m_noiseTimestamp > rememberNoiseDuration) + { + ForgetNoise(); + } + + // where are we + if (!m_currentArea || !m_currentArea->Contains( myOrigin )) + { + m_currentArea = (CCSNavArea *)TheNavMesh->GetNavArea( myOrigin ); + } + + // track the last known area we were in + if (m_currentArea && m_currentArea != m_lastKnownArea) + { + m_lastKnownArea = m_currentArea; + + OnEnteredNavArea( m_currentArea ); + } + + // keep track of how long we have been motionless + const float stillSpeed = 10.0f; + if (GetAbsVelocity().IsLengthLessThan( stillSpeed )) + { + m_stillTimer.Start(); + } + else + { + m_stillTimer.Invalidate(); + } + + // if we're blind, retreat! + if (IsBlind()) + { + if (m_blindFire) + { + PrimaryAttack(); + } + } + + UpdatePanicLookAround(); + + // + // Enemy acquisition and attack initiation + // + + // take a snapshot and update our reaction time queue + UpdateReactionQueue(); + + // "threat" may be the same as our current enemy + CCSPlayer *threat = GetRecognizedEnemy(); + if (threat) + { + Vector threatOrigin = GetCentroid( threat ); + + // adjust our personal "safe" time + AdjustSafeTime(); + + BecomeAlert(); + + const float selfDefenseRange = 500.0f; // 750.0f; + const float farAwayRange = 2000.0f; + + // + // Decide if we should attack + // + bool doAttack = false; + switch( GetDisposition() ) + { + case IGNORE_ENEMIES: + { + // never attack + doAttack = false; + break; + } + + case SELF_DEFENSE: + { + // attack if fired on + doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat )); + + // attack if enemy very close + if (!doAttack) + { + doAttack = (myOrigin - threatOrigin).IsLengthLessThan( selfDefenseRange ); + } + + break; + } + + case ENGAGE_AND_INVESTIGATE: + case OPPORTUNITY_FIRE: + { + if ((myOrigin - threatOrigin).IsLengthGreaterThan( farAwayRange )) + { + // enemy is very far away - wait to take our shot until he is closer + // unless we are a sniper or he is shooting at us + if (IsSniper()) + { + // snipers love far away targets + doAttack = true; + } + else + { + // attack if fired on + doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat )); + } + } + else + { + // normal combat range + doAttack = true; + } + + break; + } + } + + // if we aren't attacking but we are being attacked, retaliate + if (!doAttack && !IsAttacking() && GetDisposition() != IGNORE_ENEMIES) + { + const float recentAttackDuration = 1.0f; + if (GetTimeSinceAttacked() < recentAttackDuration) + { + // we may not be attacking our attacker, but at least we're not just taking it + // (since m_attacker isn't reaction-time delayed, we can't directly use it) + doAttack = true; + PrintIfWatched( "Ouch! Retaliating!\n" ); + } + } + + if (doAttack) + { + if (!IsAttacking() || threat != GetBotEnemy()) + { + if (IsUsingKnife() && IsHiding()) + { + // if hiding with a knife, wait until threat is close + const float knifeAttackRange = 250.0f; + if ((GetAbsOrigin() - threat->GetAbsOrigin()).IsLengthLessThan( knifeAttackRange )) + { + Attack( threat ); + } + } + else + { + Attack( threat ); + } + } + } + else + { + // dont attack, but keep track of nearby enemies + SetBotEnemy( threat ); + m_isEnemyVisible = true; + } + + TheCSBots()->SetLastSeenEnemyTimestamp(); + } + + // + // Validate existing enemy, if any + // + if (m_enemy != NULL) + { + if (IsAwareOfEnemyDeath()) + { + // we have noticed that our enemy has died + m_enemy = NULL; + m_isEnemyVisible = false; + } + else + { + // check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players) + // note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is + if (IsVisible( m_enemy, false, &m_visibleEnemyParts )) + { + m_isEnemyVisible = true; + m_lastSawEnemyTimestamp = gpGlobals->curtime; + m_lastEnemyPosition = GetCentroid( m_enemy ); + } + else + { + m_isEnemyVisible = false; + } + + // check if enemy died + if (m_enemy->IsAlive()) + { + m_enemyDeathTimestamp = 0.0f; + m_isLastEnemyDead = false; + } + else if (m_enemyDeathTimestamp == 0.0f) + { + // note time of death (to allow bots to overshoot for a time) + m_enemyDeathTimestamp = gpGlobals->curtime; + m_isLastEnemyDead = true; + } + } + } + else + { + m_isEnemyVisible = false; + } + + + // if we have seen an enemy recently, keep an eye on him if we can + if (!IsBlind() && !IsLookingAtSpot(PRIORITY_UNINTERRUPTABLE) ) + { + const float seenRecentTime = 3.0f; + if (m_enemy != NULL && GetTimeSinceLastSawEnemy() < seenRecentTime) + { + AimAtEnemy(); + } + else + { + StopAiming(); + } + } + else if( IsAimingAtEnemy() ) + { + StopAiming(); + } + + // + // Hack to fire while retreating + /// @todo Encapsulate aiming and firing on enemies separately from current task + // + if (GetDisposition() == IGNORE_ENEMIES) + { + FireWeaponAtEnemy(); + } + + // toss grenades + LookForGrenadeTargets(); + + // process grenade throw state machine + UpdateGrenadeThrow(); + + // avoid enemy grenades + AvoidEnemyGrenades(); + + + // check if our weapon is totally out of ammo + // or if we no longer feel "safe", equip our weapon + if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo()) + { + EquipBestWeapon(); + } + + /// @todo This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range + if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb()) + { + EquipBestWeapon(); + } + + // if we haven't seen an enemy in awhile, and we switched to our pistol during combat, + // switch back to our primary weapon (if it still has ammo left) + const float safeRearmTime = 5.0f; + if (!IsReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime) + { + EquipBestWeapon(); + } + + // reload our weapon if we must + ReloadCheck(); + + // equip silencer + SilencerCheck(); + + // listen to the radio + RespondToRadioCommands(); + + // make way + const float avoidTime = 0.33f; + if (gpGlobals->curtime - m_avoidTimestamp < avoidTime && m_avoid != NULL) + { + StrafeAwayFromPosition( GetCentroid( m_avoid ) ); + } + else + { + m_avoid = NULL; + } + + // if we're using a sniper rifle and are no longer attacking, stop looking thru scope + if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope()) + { + SecondaryAttack(); + } + + if (!IsBlind()) + { + // check encounter spots + UpdatePeripheralVision(); + + // watch for snipers + if (CanSeeSniper() && !HasSeenSniperRecently()) + { + GetChatter()->SpottedSniper(); + + const float sniperRecentInterval = 20.0f; + m_sawEnemySniperTimer.Start( sniperRecentInterval ); + } + + // + // Update gamestate + // + if (m_bomber != NULL) + GetChatter()->SpottedBomber( GetBomber() ); + + if (CanSeeLooseBomb()) + GetChatter()->SpottedLooseBomb( TheCSBots()->GetLooseBomb() ); + } + + // + // Scenario interrupts + // + switch (TheCSBots()->GetScenario()) + { + case CCSBotManager::SCENARIO_DEFUSE_BOMB: + { + // flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is + // (aggressive players wait until its almost too late) + float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression()); + + // if we have a defuse kit, can wait longer + if (m_bHasDefuser) + gonnaBlowTime *= 0.66f; + + if (!IsEscapingFromBomb() && // we aren't already escaping the bomb + TheCSBots()->IsBombPlanted() && // is the bomb planted + GetGameState()->IsPlantedBombLocationKnown() && // we know where the bomb is + TheCSBots()->GetBombTimeLeft() < gonnaBlowTime && // is the bomb about to explode + !IsDefusingBomb() && // we aren't defusing the bomb + !IsAttacking()) // we aren't in the midst of a firefight + { + EscapeFromBomb(); + break; + } + + break; + } + + case CCSBotManager::SCENARIO_RESCUE_HOSTAGES: + { + if (GetTeamNumber() == TEAM_CT) + { + UpdateHostageEscortCount(); + } + else + { + // Terrorists have imperfect information on status of hostages + unsigned char status = GetGameState()->ValidateHostagePositions(); + + if (status & CSGameState::HOSTAGES_ALL_GONE) + { + GetChatter()->HostagesTaken(); + Idle(); + } + else if (status & CSGameState::HOSTAGE_GONE) + { + GetGameState()->HostageWasTaken(); + Idle(); + } + } + break; + } + } + + + // + // Follow nearby humans if our co-op is high and we have nothing else to do + // If we were just following someone, don't auto-follow again for a short while to + // give us a chance to do something else. + // + const float earliestAutoFollowTime = 5.0f; + const float minAutoFollowTeamwork = 0.4f; + if (cv_bot_auto_follow.GetBool() && + TheCSBots()->GetElapsedRoundTime() > earliestAutoFollowTime && + GetProfile()->GetTeamwork() > minAutoFollowTeamwork && + CanAutoFollow() && + !IsBusy() && + !IsFollowing() && + !IsBlind() && + !GetGameState()->IsAtPlantedBombsite()) + { + + // chance of following is proportional to teamwork attribute + if (GetProfile()->GetTeamwork() > RandomFloat( 0.0f, 1.0f )) + { + CCSPlayer *leader = GetClosestVisibleHumanFriend(); + if (leader && leader->IsAutoFollowAllowed()) + { + // count how many bots are already following this player + const float maxFollowCount = 2; + if (GetBotFollowCount( leader ) < maxFollowCount) + { + const float autoFollowRange = 300.0f; + Vector leaderOrigin = GetCentroid( leader ); + if ((leaderOrigin - myOrigin).IsLengthLessThan( autoFollowRange )) + { + CNavArea *leaderArea = TheNavMesh->GetNavArea( leaderOrigin ); + if (leaderArea) + { + PathCost cost( this, FASTEST_ROUTE ); + float travelRange = NavAreaTravelDistance( GetLastKnownArea(), leaderArea, cost ); + if (travelRange >= 0.0f && travelRange < autoFollowRange) + { + // follow this human + Follow( leader ); + PrintIfWatched( "Auto-Following %s\n", leader->GetPlayerName() ); + + if (CSGameRules()->IsCareer()) + { + GetChatter()->Say( "FollowingCommander", 10.0f ); + } + else + { + GetChatter()->Say( "FollowingSir", 10.0f ); + } + } + } + } + } + } + } + else + { + // we decided not to follow, don't re-check for a duration + m_allowAutoFollowTime = gpGlobals->curtime + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f; + } + } + + if (IsFollowing()) + { + // if we are following someone, make sure they are still alive + CBaseEntity *leader = m_leader; + if (leader == NULL || !leader->IsAlive()) + { + StopFollowing(); + } + + // decide whether to continue following them + const float highTeamwork = 0.85f; + if (GetProfile()->GetTeamwork() < highTeamwork) + { + float minFollowDuration = 15.0f; + if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork()) + { + // we are bored of following our leader + StopFollowing(); + PrintIfWatched( "Stopping following - bored\n" ); + } + } + } + + + // + // Execute state machine + // + if (m_isOpeningDoor) + { + // opening doors takes precedence over attacking because DoorCheck() will stop opening doors if + // we don't have a knife out. + m_openDoorState.OnUpdate( this ); + + if (m_openDoorState.IsDone()) + { + // open door behavior is finished, return to previous behavior context + m_openDoorState.OnExit( this ); + m_isOpeningDoor = false; + } + } + else if (m_isAttacking) + { + m_attackState.OnUpdate( this ); + } + else + { + m_state->OnUpdate( this ); + } + + // do wait behavior + if (!IsAttacking() && IsWaiting()) + { + ResetStuckMonitor(); + ClearMovement(); + } + + // don't move while reloading unless we see an enemy + if (IsReloading() && !m_isEnemyVisible) + { + ResetStuckMonitor(); + ClearMovement(); + } + + // if we get too far ahead of the hostages we are escorting, wait for them + if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed()) + { + const float waitForHostageRange = 500.0f; + if ((GetTask() == COLLECT_HOSTAGES || GetTask() == RESCUE_HOSTAGES) && GetRangeToFarthestEscortedHostage() > waitForHostageRange) + { + if (!m_isWaitingForHostage) + { + // just started waiting + m_isWaitingForHostage = true; + m_waitForHostageTimer.Start( 10.0f ); + } + else + { + // we've been waiting + if (m_waitForHostageTimer.IsElapsed()) + { + // give up waiting for awhile + m_isWaitingForHostage = false; + m_inhibitWaitingForHostageTimer.Start( 3.0f ); + } + else + { + // keep waiting + ResetStuckMonitor(); + ClearMovement(); + } + } + } + } + + // remember our prior safe time status + m_wasSafe = IsSafe(); +} + + +//-------------------------------------------------------------------------------------------------------------- +class DrawTravelTime +{ +public: + DrawTravelTime( const CCSBot *me ) + { + m_me = me; + } + + bool operator() ( CBasePlayer *player ) + { + if (player->IsAlive() && !m_me->InSameTeam( player )) + { + CFmtStr msg; + player->EntityText( 0, + msg.sprintf( "%3.0f", m_me->GetTravelDistanceToPlayer( (CCSPlayer *)player ) ), + 0.1f ); + + + if (m_me->DidPlayerJustFireWeapon( ToCSPlayer( player ) )) + { + player->EntityText( 1, "BANG!", 0.1f ); + } + } + + return true; + } + + const CCSBot *m_me; +}; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Render bot debug info + */ +void CCSBot::DebugDisplay( void ) const +{ + const float duration = 0.15f; + CFmtStr msg; + + NDebugOverlay::ScreenText( 0.5f, 0.34f, msg.sprintf( "Skill: %d%%", (int)(100.0f * GetProfile()->GetSkill()) ), 255, 255, 255, 150, duration ); + + if ( m_pathLadder ) + { + NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "Ladder: %d", m_pathLadder->GetID() ), 255, 255, 255, 150, duration ); + } + + // show safe time + float safeTime = GetSafeTimeRemaining(); + if (safeTime > 0.0f) + { + NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "SafeTime: %3.2f", safeTime ), 255, 255, 255, 150, duration ); + } + + // show if blind + if (IsBlind()) + { + NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "<<< BLIND >>>" ), 255, 255, 255, 255, duration ); + } + + // show if alert + if (IsAlert()) + { + NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "ALERT" ), 255, 0, 0, 255, duration ); + } + + // show if panicked + if (IsPanicking()) + { + NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "PANIC" ), 255, 255, 0, 255, duration ); + } + + // show behavior variables + if (m_isAttacking) + { + NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "ATTACKING: %s", GetBotEnemy()->GetPlayerName() ), 255, 0, 0, 255, duration ); + } + else + { + NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "State: %s", m_state->GetName() ), 255, 255, 0, 255, duration ); + NDebugOverlay::ScreenText( 0.5f, 0.42f, msg.sprintf( "Task: %s", GetTaskName() ), 0, 255, 0, 255, duration ); + NDebugOverlay::ScreenText( 0.5f, 0.44f, msg.sprintf( "Disposition: %s", GetDispositionName() ), 100, 100, 255, 255, duration ); + NDebugOverlay::ScreenText( 0.5f, 0.46f, msg.sprintf( "Morale: %s", GetMoraleName() ), 0, 200, 200, 255, duration ); + } + + // show look at status + if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT) + { + const char *string = msg.sprintf( "LookAt: %s (%s)", m_lookAtDesc, (m_lookAtSpotState == LOOK_TOWARDS_SPOT) ? "LOOK_TOWARDS_SPOT" : "LOOK_AT_SPOT" ); + + NDebugOverlay::ScreenText( 0.5f, 0.60f, string, 255, 255, 0, 150, duration ); + } + + NDebugOverlay::ScreenText( 0.5f, 0.62f, msg.sprintf( "Steady view = %s", HasViewBeenSteady( 0.2f ) ? "YES" : "NO" ), 255, 255, 0, 150, duration ); + + + // show friend/foes I know of + NDebugOverlay::ScreenText( 0.5f, 0.64f, msg.sprintf( "Nearby friends = %d", m_nearbyFriendCount ), 100, 255, 100, 150, duration ); + NDebugOverlay::ScreenText( 0.5f, 0.66f, msg.sprintf( "Nearby enemies = %d", m_nearbyEnemyCount ), 255, 100, 100, 150, duration ); + + if ( m_lastNavArea ) + { + NDebugOverlay::ScreenText( 0.5f, 0.68f, msg.sprintf( "Nav Area: %d (%s)", m_lastNavArea->GetID(), m_szLastPlaceName.Get() ), 255, 255, 255, 150, duration ); + /* + if ( cv_bot_traceview.GetBool() ) + { + if ( m_currentArea ) + { + NDebugOverlay::Line( GetAbsOrigin(), m_currentArea->GetCenter(), 0, 255, 0, true, 0.1f ); + } + else if ( m_lastKnownArea ) + { + NDebugOverlay::Line( GetAbsOrigin(), m_lastKnownArea->GetCenter(), 255, 0, 0, true, 0.1f ); + } + else if ( m_lastNavArea ) + { + NDebugOverlay::Line( GetAbsOrigin(), m_lastNavArea->GetCenter(), 0, 0, 255, true, 0.1f ); + } + } + */ + } + + // show debug message history + float y = 0.8f; + const float lineHeight = 0.02f; + const float fadeAge = 7.0f; + const float maxAge = 10.0f; + for( int i=0; i<TheBots->GetDebugMessageCount(); ++i ) + { + const CBotManager::DebugMessage *msg = TheBots->GetDebugMessage( i ); + + if (msg->m_age.GetElapsedTime() < maxAge) + { + int alpha = 255; + + if (msg->m_age.GetElapsedTime() > fadeAge) + { + alpha *= (1.0f - (msg->m_age.GetElapsedTime() - fadeAge) / (maxAge - fadeAge)); + } + + NDebugOverlay::ScreenText( 0.5f, y, msg->m_string, 255, 255, 255, alpha, duration ); + y += lineHeight; + } + } + + // show noises + const Vector *noisePos = GetNoisePosition(); + if (noisePos) + { + const float size = 25.0f; + NDebugOverlay::VertArrow( *noisePos + Vector( 0, 0, size ), *noisePos, size/4.0f, 255, 255, 0, 0, true, duration ); + } + + // show aim spot + if (IsAimingAtEnemy()) + { + NDebugOverlay::Cross3D( m_aimSpot, 5.0f, 255, 0, 0, true, duration ); + } + + + + if (IsHiding()) + { + // show approach points + DrawApproachPoints(); + } + else + { + // show encounter spot data + if (false && m_spotEncounter) + { + NDebugOverlay::Line( m_spotEncounter->path.from, m_spotEncounter->path.to, 0, 150, 150, true, 0.1f ); + + Vector dir = m_spotEncounter->path.to - m_spotEncounter->path.from; + float length = dir.NormalizeInPlace(); + + const SpotOrder *order; + Vector along; + + FOR_EACH_VEC( m_spotEncounter->spots, it ) + { + order = &m_spotEncounter->spots[ it ]; + + // ignore spots the enemy could not have possibly reached yet + if (order->spot->GetArea()) + { + if (TheCSBots()->GetElapsedRoundTime() < order->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) )) + { + continue; + } + } + + along = m_spotEncounter->path.from + order->t * length * dir; + + NDebugOverlay::Line( along, order->spot->GetPosition(), 0, 255, 255, true, 0.1f ); + } + } + } + + // show aim targets + if (false) + { + CStudioHdr *pStudioHdr = const_cast< CCSBot *>( this )->GetModelPtr(); + if ( !pStudioHdr ) + return; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( const_cast< CCSBot *>( this )->GetHitboxSet() ); + if ( !set ) + return; + + Vector position, forward, right, up; + QAngle angles; + char buffer[16]; + + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox( i ); + + const_cast< CCSBot *>( this )->GetBonePosition( pbox->bone, position, angles ); + + AngleVectors( angles, &forward, &right, &up ); + + NDebugOverlay::BoxAngles( position, pbox->bbmin, pbox->bbmax, angles, 255, 0, 0, 0, 0.1f ); + + const float size = 5.0f; + NDebugOverlay::Line( position, position + size * forward, 255, 255, 0, true, 0.1f ); + NDebugOverlay::Line( position, position + size * right, 255, 0, 0, true, 0.1f ); + NDebugOverlay::Line( position, position + size * up, 0, 255, 0, true, 0.1f ); + + Q_snprintf( buffer, 16, "%d", i ); + if (i == 12) + { + // in local bone space + const float headForwardOffset = 4.0f; + const float headRightOffset = 2.0f; + position += headForwardOffset * forward + headRightOffset * right; + } + NDebugOverlay::Text( position, buffer, true, 0.1f ); + } + } + + + /* + const QAngle &angles = const_cast< CCSBot *>( this )->GetPunchAngle(); + NDebugOverlay::ScreenText( 0.3f, 0.66f, msg.sprintf( "Punch angle pitch = %3.2f", angles.x ), 255, 255, 0, 150, duration ); + */ + + DrawTravelTime drawTravelTime( this ); + ForEachPlayer( drawTravelTime ); + +/* + // show line of fire + if ((cv_bot_traceview.GetInt() == 100 && IsLocalPlayerWatchingMe()) || cv_bot_traceview.GetInt() == 101) + { + if (!IsFriendInLineOfFire()) + { + Vector vecAiming = GetViewVector(); + Vector vecSrc = EyePositionConst(); + + if (GetTeamNumber() == TEAM_TERRORIST) + UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 0, 0 ); + else + UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255 ); + } + } + + // show path navigation data + if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe()) + { + Vector from = EyePositionConst(); + + const float size = 50.0f; + //Vector arrow( size * cos( m_forwardAngle * M_PI/180.0f ), size * sin( m_forwardAngle * M_PI/180.0f ), 0.0f ); + Vector arrow( size * (float)cos( m_lookAheadAngle * M_PI/180.0f ), + size * (float)sin( m_lookAheadAngle * M_PI/180.0f ), + 0.0f ); + + UTIL_DrawBeamPoints( from, from + arrow, 1, 0, 255, 255 ); + } + + if (cv_bot_show_nav.GetBool() && m_lastKnownArea) + { + m_lastKnownArea->DrawConnectedAreas(); + } + */ + + + if (IsAttacking()) + { + const float crossSize = 2.0f; + CCSPlayer *enemy = GetBotEnemy(); + if (enemy) + { + NDebugOverlay::Cross3D( GetPartPosition( enemy, GUT ), crossSize, 0, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( GetPartPosition( enemy, HEAD ), crossSize, 0, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( GetPartPosition( enemy, FEET ), crossSize, 0, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( GetPartPosition( enemy, LEFT_SIDE ), crossSize, 0, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( GetPartPosition( enemy, RIGHT_SIDE ), crossSize, 0, 255, 0, true, 0.1f ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Periodically compute shortest path distance to each player. + * NOTE: Travel distance is NOT symmetric between players A and B. Each much be computed separately. + */ +void CCSBot::UpdateTravelDistanceToAllPlayers( void ) +{ + const unsigned char numPhases = 3; + + if (m_updateTravelDistanceTimer.IsElapsed()) + { + ShortestPathCost pathCost; + + for( int i=1; i<=gpGlobals->maxClients; ++i ) + { + CCSPlayer *player = static_cast< CCSPlayer * >( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (FNullEnt( player->edict() )) + continue; + + if (!player->IsPlayer()) + continue; + + if (!player->IsAlive()) + continue; + + // skip friends for efficiency + if (player->InSameTeam( this )) + continue; + + int which = player->entindex() % MAX_PLAYERS; + + // if player is very far away, update every third time (on phase 0) + const float veryFarAway = 4000.0f; + if (m_playerTravelDistance[ which ] < 0.0f || m_playerTravelDistance[ which ] > veryFarAway) + { + if (m_travelDistancePhase != 0) + continue; + } + else + { + // if player is far away, update two out of three times (on phases 1 and 2) + const float farAway = 2000.0f; + if (m_playerTravelDistance[ which ] > farAway && m_travelDistancePhase == 0) + continue; + } + + // if player is fairly close, update often + m_playerTravelDistance[ which ] = NavAreaTravelDistance( EyePosition(), player->EyePosition(), pathCost ); + } + + // throttle the computation frequency + const float checkInterval = 1.0f; + m_updateTravelDistanceTimer.Start( checkInterval ); + + // round-robin the phases + ++m_travelDistancePhase; + if (m_travelDistancePhase >= numPhases) + { + m_travelDistancePhase = 0; + } + } +} |