//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// // Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 #include "cbase.h" #include "cs_bot.h" #include "cs_nav_path.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //-------------------------------------------------------------------------------------------------------------- /** * This method is the ONLY legal way to change a bot's current state */ void CCSBot::SetState( BotState *state ) { PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() ); /* if ( IsDefusingBomb() ) { const Vector *bombPos = GetGameState()->GetBombPosition(); if ( bombPos != NULL ) { if ( TheCSBots()->GetBombDefuser() == this ) { if ( TheCSBots()->IsBombPlanted() ) { Msg( "Bot %s is switching from defusing the bomb to %s\n", GetPlayerName(), state->GetName() ); } } } } */ // if we changed state from within the special Attack state, we are no longer attacking if (m_isAttacking) StopAttacking(); if (m_state) m_state->OnExit( this ); state->OnEnter( this ); m_state = state; m_stateTimestamp = gpGlobals->curtime; } //-------------------------------------------------------------------------------------------------------------- void CCSBot::Idle( void ) { SetTask( SEEK_AND_DESTROY ); SetState( &m_idleState ); } //-------------------------------------------------------------------------------------------------------------- void CCSBot::EscapeFromBomb( void ) { SetTask( ESCAPE_FROM_BOMB ); SetState( &m_escapeFromBombState ); } //-------------------------------------------------------------------------------------------------------------- void CCSBot::Follow( CCSPlayer *player ) { if (player == NULL) return; // note when we began following if (!m_isFollowing || m_leader != player) m_followTimestamp = gpGlobals->curtime; m_isFollowing = true; m_leader = player; SetTask( FOLLOW ); m_followState.SetLeader( player ); SetState( &m_followState ); } //-------------------------------------------------------------------------------------------------------------- /** * Continue following our leader after finishing what we were doing */ void CCSBot::ContinueFollowing( void ) { SetTask( FOLLOW ); m_followState.SetLeader( m_leader ); SetState( &m_followState ); } //-------------------------------------------------------------------------------------------------------------- /** * Stop following */ void CCSBot::StopFollowing( void ) { m_isFollowing = false; m_leader = NULL; m_allowAutoFollowTime = gpGlobals->curtime + 10.0f; } //-------------------------------------------------------------------------------------------------------------- /** * Begin process of rescuing hostages */ void CCSBot::RescueHostages( void ) { SetTask( RESCUE_HOSTAGES ); } //-------------------------------------------------------------------------------------------------------------- /** * Use the entity */ void CCSBot::UseEntity( CBaseEntity *entity ) { m_useEntityState.SetEntity( entity ); SetState( &m_useEntityState ); } //-------------------------------------------------------------------------------------------------------------- /** * Open the door. * This assumes the bot is directly in front of the door with no obstructions. * NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done. */ void CCSBot::OpenDoor( CBaseEntity *door ) { m_openDoorState.SetDoor( door ); m_isOpeningDoor = true; m_openDoorState.OnEnter( this ); } //-------------------------------------------------------------------------------------------------------------- /** * DEPRECATED: Use TryToHide() instead. * Move to a hiding place. * If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first. */ void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition ) { DestroyPath(); CNavArea *source; Vector sourcePos; if (searchFromArea) { source = searchFromArea; sourcePos = searchFromArea->GetCenter(); } else { source = m_lastKnownArea; sourcePos = GetCentroid( this ); } if (source == NULL) { PrintIfWatched( "Hide from area is NULL.\n" ); Idle(); return; } m_hideState.SetSearchArea( source ); m_hideState.SetSearchRange( hideRange ); m_hideState.SetDuration( duration ); m_hideState.SetHoldPosition( holdPosition ); // search around source area for a good hiding spot Vector useSpot; const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() ); if (pos == NULL) { PrintIfWatched( "No available hiding spots.\n" ); // hide at our current position useSpot = GetCentroid( this ); } else { useSpot = *pos; } m_hideState.SetHidingSpot( useSpot ); // build a path to our new hiding spot if (ComputePath( useSpot, FASTEST_ROUTE ) == false) { PrintIfWatched( "Can't pathfind to hiding spot\n" ); Idle(); return; } SetState( &m_hideState ); } //-------------------------------------------------------------------------------------------------------------- /** * Move to the given hiding place */ void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition ) { CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot ); if (hideArea == NULL) { PrintIfWatched( "Hiding spot off nav mesh\n" ); Idle(); return; } DestroyPath(); m_hideState.SetSearchArea( hideArea ); m_hideState.SetSearchRange( 750.0f ); m_hideState.SetDuration( duration ); m_hideState.SetHoldPosition( holdPosition ); m_hideState.SetHidingSpot( hidingSpot ); // build a path to our new hiding spot if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false) { PrintIfWatched( "Can't pathfind to hiding spot\n" ); Idle(); return; } SetState( &m_hideState ); } //-------------------------------------------------------------------------------------------------------------- /** * Try to hide nearby. Return true if hiding, false if can't hide here. * If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first. */ bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest ) { CNavArea *source; Vector sourcePos; if (searchFromArea) { source = searchFromArea; sourcePos = searchFromArea->GetCenter(); } else { source = m_lastKnownArea; sourcePos = GetCentroid( this ); } if (source == NULL) { PrintIfWatched( "Hide from area is NULL.\n" ); return false; } m_hideState.SetSearchArea( source ); m_hideState.SetSearchRange( hideRange ); m_hideState.SetDuration( duration ); m_hideState.SetHoldPosition( holdPosition ); // search around source area for a good hiding spot const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest ); if (pos == NULL) { PrintIfWatched( "No available hiding spots.\n" ); return false; } m_hideState.SetHidingSpot( *pos ); // build a path to our new hiding spot if (ComputePath( *pos, FASTEST_ROUTE ) == false) { PrintIfWatched( "Can't pathfind to hiding spot\n" ); return false; } SetState( &m_hideState ); return true; } //-------------------------------------------------------------------------------------------------------------- /** * Retreat to a nearby hiding spot, away from enemies */ bool CCSBot::TryToRetreat( float maxRange, float duration ) { const Vector *spot = FindNearbyRetreatSpot( this, maxRange ); if (spot) { // ignore enemies for a second to give us time to hide // reaching our hiding spot clears our disposition IgnoreEnemies( 10.0f ); if (duration < 0.0f) { duration = RandomFloat( 3.0f, 15.0f ); } StandUp(); Run(); Hide( *spot, duration ); PrintIfWatched( "Retreating to a safe spot!\n" ); return true; } return false; } //-------------------------------------------------------------------------------------------------------------- void CCSBot::Hunt( void ) { SetState( &m_huntState ); } //-------------------------------------------------------------------------------------------------------------- /** * Attack our the given victim * NOTE: Attacking does not change our task. */ void CCSBot::Attack( CCSPlayer *victim ) { if (victim == NULL) return; // zombies never attack if (cv_bot_zombie.GetBool()) return; // cannot attack if we are reloading if (IsReloading()) return; // change enemy SetBotEnemy( victim ); // // Do not "re-enter" the attack state if we are already attacking // if (IsAttacking()) return; // if we're holding a grenade, throw it at the victim if (IsUsingGrenade()) { // throw towards their feet ThrowGrenade( victim->GetAbsOrigin() ); return; } // if we are currently hiding, increase our chances of crouching and holding position if (IsAtHidingSpot()) m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false ); else m_attackState.SetCrouchAndHold( false ); //SetState( &m_attackState ); //PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n", // GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() ); m_isAttacking = true; m_attackState.OnEnter( this ); Vector victimOrigin = GetCentroid( victim ); // cheat a bit and give the bot the initial location of its victim m_lastEnemyPosition = victimOrigin; m_lastSawEnemyTimestamp = gpGlobals->curtime; m_aimSpreadTimestamp = gpGlobals->curtime; // compute the angle difference between where are looking, and where we need to look Vector toEnemy = victimOrigin - GetCentroid( this ); QAngle idealAngle; VectorAngles( toEnemy, idealAngle ); float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y); while( deltaYaw > 180.0f ) deltaYaw -= 360.0f; if (deltaYaw < 0.0f) deltaYaw = -deltaYaw; // immediately aim at enemy - accuracy penalty depending on how far we must turn to aim // accuracy is halved if we have to turn 180 degrees float turn = deltaYaw / 180.0f; float accuracy = GetProfile()->GetSkill() / (1.0f + turn); SetAimOffset( accuracy ); // define time when aim offset will automatically be updated // longer time the more we had to turn (surprise) m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f ); // forget any look at targets we have ClearLookAt(); } //-------------------------------------------------------------------------------------------------------------- /** * Exit the Attack state */ void CCSBot::StopAttacking( void ) { PrintIfWatched( "ATTACK END\n" ); m_attackState.OnExit( this ); m_isAttacking = false; // if we are following someone, go to the Idle state after the attack to decide whether we still want to follow if (IsFollowing()) { Idle(); } } //-------------------------------------------------------------------------------------------------------------- bool CCSBot::IsAttacking( void ) const { return m_isAttacking; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are escaping from the bomb */ bool CCSBot::IsEscapingFromBomb( void ) const { if (m_state == static_cast( &m_escapeFromBombState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are defusing the bomb */ bool CCSBot::IsDefusingBomb( void ) const { if (m_state == static_cast( &m_defuseBombState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are hiding */ bool CCSBot::IsHiding( void ) const { if (m_state == static_cast( &m_hideState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are hiding and at our hiding spot */ bool CCSBot::IsAtHidingSpot( void ) const { if (!IsHiding()) return false; return m_hideState.IsAtSpot(); } //-------------------------------------------------------------------------------------------------------------- /** * Return number of seconds we have been at our current hiding spot */ float CCSBot::GetHidingTime( void ) const { if (IsHiding()) { return m_hideState.GetHideTime(); } return 0.0f; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are huting */ bool CCSBot::IsHunting( void ) const { if (m_state == static_cast( &m_huntState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are in the MoveTo state */ bool CCSBot::IsMovingTo( void ) const { if (m_state == static_cast( &m_moveToState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are buying */ bool CCSBot::IsBuying( void ) const { if (m_state == static_cast( &m_buyState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- bool CCSBot::IsInvestigatingNoise( void ) const { if (m_state == static_cast( &m_investigateNoiseState )) return true; return false; } //-------------------------------------------------------------------------------------------------------------- /** * Move to potentially distant position */ void CCSBot::MoveTo( const Vector &pos, RouteType route ) { m_moveToState.SetGoalPosition( pos ); m_moveToState.SetRouteType( route ); SetState( &m_moveToState ); } //-------------------------------------------------------------------------------------------------------------- void CCSBot::PlantBomb( void ) { SetState( &m_plantBombState ); } //-------------------------------------------------------------------------------------------------------------- /** * Bomb has been dropped - go get it */ void CCSBot::FetchBomb( void ) { SetState( &m_fetchBombState ); } //-------------------------------------------------------------------------------------------------------------- void CCSBot::DefuseBomb( void ) { SetState( &m_defuseBombState ); } //-------------------------------------------------------------------------------------------------------------- /** * Investigate recent enemy noise */ void CCSBot::InvestigateNoise( void ) { SetState( &m_investigateNoiseState ); } //-------------------------------------------------------------------------------------------------------------- void CCSBot::Buy( void ) { SetState( &m_buyState ); } //-------------------------------------------------------------------------------------------------------------- /** * Move to a hiding spot and wait for initial encounter with enemy team. * Return false if can't do this behavior (ie: no hiding spots available). */ bool CCSBot::MoveToInitialEncounter( void ) { int myTeam = GetTeamNumber(); int enemyTeam = OtherTeam( myTeam ); // build a path to an enemy spawn point CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam ); if (enemySpawn == NULL) { PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" ); return false; } // build a path from us to the enemy spawn CCSNavPath path; PathCost cost( this, FASTEST_ROUTE ); path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost ); if (!path.IsValid()) { PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" ); return false; } // find battlefront area where teams will first meet along this path int i; for( i=0; iarea->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam )) { break; } } if (i == path.GetSegmentCount()) { PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" ); return false; } /// @todo Remove this evil side-effect SetInitialEncounterArea( path[i]->area ); // find a hiding spot on our side of the battlefront that has LOS to it const float maxRange = 1500.0f; const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() ); if (spot == NULL) { PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" ); return false; } float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam ); float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f; if (timeToWait < minWaitTime) { timeToWait = minWaitTime; } Hide( spot->GetPosition(), timeToWait ); return true; }