//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// // Author: Michael S. Booth (mike@turtlerockstudios.com), 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; iGetDebugMessageCount(); ++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; } } }