diff options
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_statemachine.cpp')
| -rw-r--r-- | game/server/cstrike/bot/cs_bot_statemachine.cpp | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot_statemachine.cpp b/game/server/cstrike/bot/cs_bot_statemachine.cpp new file mode 100644 index 0000000..8cbf9aa --- /dev/null +++ b/game/server/cstrike/bot/cs_bot_statemachine.cpp @@ -0,0 +1,693 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 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<const BotState *>( &m_escapeFromBombState )) + return true; + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if we are defusing the bomb + */ +bool CCSBot::IsDefusingBomb( void ) const +{ + if (m_state == static_cast<const BotState *>( &m_defuseBombState )) + return true; + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if we are hiding + */ +bool CCSBot::IsHiding( void ) const +{ + if (m_state == static_cast<const BotState *>( &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<const BotState *>( &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<const BotState *>( &m_moveToState )) + return true; + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if we are buying + */ +bool CCSBot::IsBuying( void ) const +{ + if (m_state == static_cast<const BotState *>( &m_buyState )) + return true; + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +bool CCSBot::IsInvestigatingNoise( void ) const +{ + if (m_state == static_cast<const BotState *>( &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; i<path.GetSegmentCount(); ++i ) + { + if (path[i]->area->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; +} + |