summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/states
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike/bot/states')
-rw-r--r--game/server/cstrike/bot/states/cs_bot_attack.cpp710
-rw-r--r--game/server/cstrike/bot/states/cs_bot_buy.cpp690
-rw-r--r--game/server/cstrike/bot/states/cs_bot_defuse_bomb.cpp82
-rw-r--r--game/server/cstrike/bot/states/cs_bot_escape_from_bomb.cpp67
-rw-r--r--game/server/cstrike/bot/states/cs_bot_fetch_bomb.cpp69
-rw-r--r--game/server/cstrike/bot/states/cs_bot_follow.cpp366
-rw-r--r--game/server/cstrike/bot/states/cs_bot_hide.cpp549
-rw-r--r--game/server/cstrike/bot/states/cs_bot_hunt.cpp234
-rw-r--r--game/server/cstrike/bot/states/cs_bot_idle.cpp887
-rw-r--r--game/server/cstrike/bot/states/cs_bot_investigate_noise.cpp139
-rw-r--r--game/server/cstrike/bot/states/cs_bot_move_to.cpp366
-rw-r--r--game/server/cstrike/bot/states/cs_bot_open_door.cpp94
-rw-r--r--game/server/cstrike/bot/states/cs_bot_plant_bomb.cpp79
-rw-r--r--game/server/cstrike/bot/states/cs_bot_use_entity.cpp63
14 files changed, 4395 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/states/cs_bot_attack.cpp b/game/server/cstrike/bot/states/cs_bot_attack.cpp
new file mode 100644
index 0000000..1f44b6c
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_attack.cpp
@@ -0,0 +1,710 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin attacking
+ */
+void AttackState::OnEnter( CCSBot *me )
+{
+ CBasePlayer *enemy = me->GetBotEnemy();
+
+ // store our posture when the attack began
+ me->PushPostureContext();
+
+ me->DestroyPath();
+
+ // if we are using a knife, try to sneak up on the enemy
+ if (enemy && me->IsUsingKnife() && !me->IsPlayerFacingMe( enemy ))
+ me->Walk();
+ else
+ me->Run();
+
+ me->GetOffLadder();
+ me->ResetStuckMonitor();
+
+ m_repathTimer.Invalidate();
+ m_haveSeenEnemy = me->IsEnemyVisible();
+ m_nextDodgeStateTimestamp = 0.0f;
+ m_firstDodge = true;
+ m_isEnemyHidden = false;
+ m_reacquireTimestamp = 0.0f;
+
+ m_pinnedDownTimestamp = gpGlobals->curtime + RandomFloat( 7.0f, 10.0f );
+
+ m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 2.0f, 10.0f );
+ m_shieldForceOpen = false;
+
+ // if we encountered someone while escaping, grab our weapon and fight!
+ if (me->IsEscapingFromBomb())
+ me->EquipBestWeapon();
+
+ if (me->IsUsingKnife())
+ {
+ // can't crouch and hold with a knife
+ m_crouchAndHold = false;
+ me->StandUp();
+ }
+ else if (me->CanSeeSniper() && !me->IsSniper())
+ {
+ // don't sit still if we see a sniper!
+ m_crouchAndHold = false;
+ me->StandUp();
+ }
+ else
+ {
+ // decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
+ if (!m_crouchAndHold)
+ {
+ if (enemy)
+ {
+ const float crouchFarRange = 750.0f;
+ float crouchChance;
+
+ // more likely to crouch if using sniper rifle or if enemy is far away
+ if (me->IsUsingSniperRifle())
+ crouchChance = 50.0f;
+ else if ((GetCentroid( me ) - GetCentroid( enemy )).IsLengthGreaterThan( crouchFarRange ))
+ crouchChance = 50.0f;
+ else
+ crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
+
+ if (RandomFloat( 0.0f, 100.0f ) < crouchChance)
+ {
+ // make sure we can still see if we crouch
+ trace_t result;
+
+ Vector origin = GetCentroid( me );
+ if (!me->IsCrouching())
+ {
+ // we are standing, adjust for lower crouch origin
+ origin.z -= 20.0f;
+ }
+
+ UTIL_TraceLine( origin, enemy->EyePosition(), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction == 1.0f)
+ {
+ m_crouchAndHold = true;
+ }
+ }
+ }
+ }
+
+ if (m_crouchAndHold)
+ {
+ me->Crouch();
+ me->PrintIfWatched( "Crouch and hold attack!\n" );
+ }
+ }
+
+ m_scopeTimestamp = 0;
+ m_didAmbushCheck = false;
+
+ float skill = me->GetProfile()->GetSkill();
+
+ // tendency to dodge is proportional to skill
+ float dodgeChance = 80.0f * skill;
+
+ // high skill bots always dodge if outnumbered, or they see a sniper
+ if (skill > 0.5f && (me->IsOutnumbered() || me->CanSeeSniper()))
+ {
+ dodgeChance = 100.0f;
+ }
+
+ m_shouldDodge = (RandomFloat( 0, 100 ) <= dodgeChance);
+
+
+ // decide whether we might bail out of this fight
+ m_isCoward = (RandomFloat( 0, 100 ) > 100.0f * me->GetProfile()->GetAggression());
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When we are done attacking, this is invoked
+ */
+void AttackState::StopAttacking( CCSBot *me )
+{
+ if (me->GetTask() == CCSBot::SNIPING)
+ {
+ // stay in our hiding spot
+ me->Hide( me->GetLastKnownArea(), -1.0f, 50.0f );
+ }
+ else
+ {
+ me->StopAttacking();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Do dodge behavior
+ */
+void AttackState::Dodge( CCSBot *me )
+{
+ //
+ // Dodge.
+ // If sniping or crouching, stand still.
+ //
+ if (m_shouldDodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
+ {
+ CBasePlayer *enemy = me->GetBotEnemy();
+ if (enemy == NULL)
+ {
+ return;
+ }
+
+ Vector toEnemy = enemy->GetAbsOrigin() - me->GetAbsOrigin();
+ float range = toEnemy.Length();
+
+ const float hysterisRange = 125.0f; // (+/-) m_combatRange
+
+ float minRange = me->GetCombatRange() - hysterisRange;
+ float maxRange = me->GetCombatRange() + hysterisRange;
+
+ if (me->IsUsingKnife())
+ {
+ // dodge when far away if armed only with a knife
+ maxRange = 999999.9f;
+ }
+
+ // move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
+ if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
+ {
+ if (range > maxRange)
+ me->MoveForward();
+ else if (range < minRange)
+ me->MoveBackward();
+ }
+
+ // don't dodge if enemy is facing away
+ const float dodgeRange = 2000.0f;
+ if (!me->CanSeeSniper() && (range > dodgeRange || !me->IsPlayerFacingMe( enemy )))
+ {
+ m_dodgeState = STEADY_ON;
+ m_nextDodgeStateTimestamp = 0.0f;
+ }
+ else if (gpGlobals->curtime >= m_nextDodgeStateTimestamp)
+ {
+ int next;
+
+ // high-skill bots keep moving and don't jump if they see a sniper
+ if (me->GetProfile()->GetSkill() > 0.5f && me->CanSeeSniper())
+ {
+ // juke back and forth
+ if (m_firstDodge)
+ {
+ next = (RandomInt( 0, 100 ) < 50) ? SLIDE_RIGHT : SLIDE_LEFT;
+ }
+ else
+ {
+ next = (m_dodgeState == SLIDE_LEFT) ? SLIDE_RIGHT : SLIDE_LEFT;
+ }
+ }
+ else
+ {
+ // select next dodge state that is different that our current one
+ do
+ {
+ // low-skill bots may jump when first engaging the enemy (if they are moving)
+ const float jumpChance = 33.3f;
+ if (m_firstDodge && me->GetProfile()->GetSkill() < 0.5f && RandomFloat( 0, 100 ) < jumpChance && !me->IsNotMoving())
+ next = RandomInt( 0, NUM_ATTACK_STATES-1 );
+ else
+ next = RandomInt( 0, NUM_ATTACK_STATES-2 );
+ }
+ while( !m_firstDodge && next == m_dodgeState );
+ }
+
+ m_dodgeState = (DodgeStateType)next;
+ m_nextDodgeStateTimestamp = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
+ m_firstDodge = false;
+ }
+
+
+ Vector forward, right;
+ me->EyeVectors( &forward, &right );
+
+ const float lookAheadRange = 30.0f;
+ float ground;
+
+ switch( m_dodgeState )
+ {
+ case STEADY_ON:
+ {
+ break;
+ }
+
+ case SLIDE_LEFT:
+ {
+ // don't move left if we will fall
+ Vector pos = me->GetAbsOrigin() - (lookAheadRange * right);
+
+ if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (me->GetAbsOrigin().z - ground < StepHeight)
+ {
+ me->StrafeLeft();
+ }
+ }
+ break;
+ }
+
+ case SLIDE_RIGHT:
+ {
+ // don't move left if we will fall
+ Vector pos = me->GetAbsOrigin() + (lookAheadRange * right);
+
+ if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (me->GetAbsOrigin().z - ground < StepHeight)
+ {
+ me->StrafeRight();
+ }
+ }
+ break;
+ }
+
+ case JUMP:
+ {
+ if (me->m_isEnemyVisible)
+ {
+ me->Jump();
+ }
+ break;
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Perform attack behavior
+ */
+void AttackState::OnUpdate( CCSBot *me )
+{
+ // can't be stuck while attacking
+ me->ResetStuckMonitor();
+
+ // if we somehow ended up with the C4 or a grenade in our hands, grab our weapon!
+ CWeaponCSBase *weapon = me->GetActiveCSWeapon();
+ if (weapon)
+ {
+ if (weapon->GetWeaponID() == WEAPON_C4 ||
+ weapon->GetWeaponID() == WEAPON_HEGRENADE ||
+ weapon->GetWeaponID() == WEAPON_FLASHBANG ||
+ weapon->GetWeaponID() == WEAPON_SMOKEGRENADE)
+ {
+ me->EquipBestWeapon();
+ }
+ }
+
+ CBasePlayer *enemy = me->GetBotEnemy();
+ if (enemy == NULL)
+ {
+ StopAttacking( me );
+ return;
+ }
+
+ Vector myOrigin = GetCentroid( me );
+ Vector enemyOrigin = GetCentroid( enemy );
+
+ // keep track of whether we have seen our enemy at least once yet
+ if (!m_haveSeenEnemy)
+ m_haveSeenEnemy = me->IsEnemyVisible();
+
+
+ //
+ // Retreat check
+ // Do not retreat if the enemy is too close
+ //
+ if (m_retreatTimer.IsElapsed())
+ {
+ // If we've been fighting this battle for awhile, we're "pinned down" and
+ // need to do something else.
+ // If we are outnumbered, retreat.
+ // If we see a sniper and we aren't a sniper, retreat.
+
+ bool isPinnedDown = (gpGlobals->curtime > m_pinnedDownTimestamp);
+
+ if (isPinnedDown ||
+ (me->CanSeeSniper() && !me->IsSniper()) ||
+ (me->IsOutnumbered() && m_isCoward) ||
+ (me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
+ {
+ // only retreat if at least one of them is aiming at me
+ if (me->IsAnyVisibleEnemyLookingAtMe( CHECK_FOV ))
+ {
+ // tell our teammates our plight
+ if (isPinnedDown)
+ me->GetChatter()->PinnedDown();
+ else if (!me->CanSeeSniper())
+ me->GetChatter()->Scared();
+
+ m_retreatTimer.Start( RandomFloat( 3.0f, 15.0f ) );
+
+ // try to retreat
+ if (me->TryToRetreat())
+ {
+ // if we are a sniper, equip our pistol so we can fire while retreating
+ /*
+ if (me->IsUsingSniperRifle())
+ {
+ // wait a moment to allow one last shot
+ me->Wait( 0.5f );
+ //me->EquipPistol();
+ }
+ */
+
+ // request backup if outnumbered
+ if (me->IsOutnumbered())
+ {
+ me->GetChatter()->NeedBackup();
+ }
+ }
+ else
+ {
+ me->PrintIfWatched( "I want to retreat, but no safe spots nearby!\n" );
+ }
+ }
+ }
+ }
+
+ //
+ // Knife fighting
+ // We need to pathfind right to the enemy to cut him
+ //
+ if (me->IsUsingKnife())
+ {
+ // can't crouch and hold with a knife
+ m_crouchAndHold = false;
+ me->StandUp();
+
+ // if we are using a knife and our prey is looking towards us, run at him
+ if (me->IsPlayerFacingMe( enemy ))
+ {
+ me->ForceRun( 5.0f );
+ me->Hurry( 10.0f );
+ }
+
+ // slash our victim
+ me->FireWeaponAtEnemy();
+
+ // if toe to toe with our enemy, don't dodge, just slash
+ const float slashRange = 70.0f;
+ if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
+ {
+ const float repathInterval = 0.5f;
+
+ // if our victim has moved, repath
+ bool repath = false;
+ if (me->HasPath())
+ {
+ const float repathRange = 100.0f; // 50
+ if ((me->GetPathEndpoint() - enemy->GetAbsOrigin()).IsLengthGreaterThan( repathRange ))
+ {
+ repath = true;
+ }
+ }
+ else
+ {
+ repath = true;
+ }
+
+ if (repath && m_repathTimer.IsElapsed())
+ {
+ Vector enemyPos = enemy->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
+ me->ComputePath( enemyPos, FASTEST_ROUTE );
+ m_repathTimer.Start( repathInterval );
+ }
+
+ // move towards victim
+ if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
+ {
+ me->DestroyPath();
+ }
+ }
+
+ return;
+ }
+
+
+
+ //
+ // Simple shield usage
+ //
+ if (me->HasShield())
+ {
+ if (me->IsEnemyVisible() && !m_shieldForceOpen)
+ {
+ if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe( enemy ))
+ {
+ // close up - enemy is pointing his gun at us
+ if (!me->IsProtectedByShield())
+ me->SecondaryAttack();
+ }
+ else
+ {
+ // enemy looking away or reloading his weapon - open up and shoot him
+ if (me->IsProtectedByShield())
+ me->SecondaryAttack();
+ }
+ }
+ else
+ {
+ // can't see enemy, open up
+ if (me->IsProtectedByShield())
+ me->SecondaryAttack();
+ }
+
+ if (gpGlobals->curtime > m_shieldToggleTimestamp)
+ {
+ m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 0.5, 2.0f );
+
+ // toggle shield force open
+ m_shieldForceOpen = !m_shieldForceOpen;
+ }
+ }
+
+
+ // check if our weapon range is bad and we should switch to pistol
+ if (me->IsUsingSniperRifle())
+ {
+ // if we have a sniper rifle and our enemy is too close, switch to pistol
+ const float sniperMinRange = 160.0f; // NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
+ if ((enemyOrigin - myOrigin).IsLengthLessThan( sniperMinRange ))
+ me->EquipPistol();
+ }
+ else if (me->IsUsingShotgun())
+ {
+ // if we have a shotgun equipped and enemy is too far away, switch to pistol
+ const float shotgunMaxRange = 600.0f;
+ if ((enemyOrigin - myOrigin).IsLengthGreaterThan( shotgunMaxRange ))
+ me->EquipPistol();
+ }
+
+ // if we're sniping, look through the scope - need to do this here in case a reload resets our scope
+ if (me->IsUsingSniperRifle())
+ {
+ // for Scouts and AWPs, we need to wait for zoom to resume
+ if (me->m_bResumeZoom)
+ {
+ m_scopeTimestamp = gpGlobals->curtime;
+ return;
+ }
+
+ Vector toAimSpot3D = me->m_aimSpot - myOrigin;
+ float targetRange = toAimSpot3D.Length();
+
+ // dont adjust zoom level if we're already zoomed in - just fire
+ if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom( targetRange ))
+ m_scopeTimestamp = gpGlobals->curtime;
+
+ const float waitScopeTime = 0.3f + me->GetProfile()->GetReactionTime();
+ if (gpGlobals->curtime - m_scopeTimestamp < waitScopeTime)
+ {
+ // force us to wait until zoomed in before firing
+ return;
+ }
+ }
+
+ // see if we "notice" that our prey is dead
+ if (me->IsAwareOfEnemyDeath())
+ {
+ // let team know if we killed the last enemy
+ if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
+ {
+ me->GetChatter()->KilledMyEnemy( enemy->entindex() );
+
+ // if there are other enemies left, wait a moment - they usually come in groups
+ if (me->GetEnemiesRemaining())
+ {
+ me->Wait( RandomFloat( 1.0f, 3.0f ) );
+ }
+ }
+
+ StopAttacking( me );
+ return;
+ }
+
+ float notSeenEnemyTime = gpGlobals->curtime - me->GetLastSawEnemyTimestamp();
+
+ // if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
+ if (!me->IsEnemyVisible())
+ {
+ // attend to nearby enemy gunfire
+ if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
+ {
+ // give up the attack, since we didn't want it in the first place
+ StopAttacking( me );
+
+ const Vector *pos = me->GetNoisePosition();
+ if (pos)
+ {
+ me->SetLookAt( "Nearby enemy gunfire", *pos, PRIORITY_HIGH, 0.0f );
+ me->PrintIfWatched( "Checking nearby threatening enemy gunfire!\n" );
+ return;
+ }
+ }
+
+ // check if we have lost track of our enemy during combat
+ if (notSeenEnemyTime > 0.25f)
+ {
+ m_isEnemyHidden = true;
+ }
+
+
+ if (notSeenEnemyTime > 0.1f)
+ {
+ if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
+ {
+ // decide whether we should hide and "ambush" our enemy
+ if (m_haveSeenEnemy && !m_didAmbushCheck)
+ {
+ float hideChance = 33.3f;
+
+ if (RandomFloat( 0.0, 100.0f ) < hideChance)
+ {
+ float ambushTime = RandomFloat( 3.0f, 15.0f );
+
+ // hide in ambush nearby
+ /// @todo look towards where we know enemy is
+ const Vector *spot = FindNearbyRetreatSpot( me, 200.0f );
+ if (spot)
+ {
+ me->IgnoreEnemies( 1.0f );
+
+ me->Run();
+ me->StandUp();
+ me->Hide( *spot, ambushTime, true );
+ return;
+ }
+ }
+
+ // don't check again
+ m_didAmbushCheck = true;
+ }
+ }
+ else
+ {
+ // give up the attack, since we didn't want it in the first place
+ StopAttacking( me );
+ return;
+ }
+ }
+ }
+ else
+ {
+ // we can see the enemy again - reset our ambush check
+ m_didAmbushCheck = false;
+
+ // if the enemy is coming out of hiding, we need time to react
+ if (m_isEnemyHidden)
+ {
+ m_reacquireTimestamp = gpGlobals->curtime + me->GetProfile()->GetReactionTime();
+ m_isEnemyHidden = false;
+ }
+ }
+
+
+ // if we haven't seen our enemy for a long time, chase after them
+ float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
+
+ // if we are sniping, be very patient
+ if (me->IsUsingSniperRifle())
+ chaseTime += 3.0f;
+ else if (me->IsCrouching()) // if we are crouching, be a little patient
+ chaseTime += 1.0f;
+
+ // if we can't see the enemy, and have either seen him but currently lost sight of him,
+ // or haven't yet seen him, chase after him (unless we are a sniper)
+ if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
+ {
+ // snipers don't chase their prey - they wait for their prey to come to them
+ if (me->GetTask() == CCSBot::SNIPING)
+ {
+ StopAttacking( me );
+ return;
+ }
+ else
+ {
+ // move to last known position of enemy
+ me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy );
+ me->MoveTo( me->GetLastKnownEnemyPosition() );
+ return;
+ }
+ }
+
+
+ // if we can't see our enemy at the moment, and were shot by
+ // a different visible enemy, engage them instead
+ const float hurtRecentlyTime = 3.0f;
+ if (!me->IsEnemyVisible() &&
+ me->GetTimeSinceAttacked() < hurtRecentlyTime &&
+ me->GetAttacker() &&
+ me->GetAttacker() != me->GetBotEnemy())
+ {
+ // if we can see them, attack, otherwise panic
+ if (me->IsVisible( me->GetAttacker(), CHECK_FOV ))
+ {
+ me->Attack( me->GetAttacker() );
+ me->PrintIfWatched( "Switching targets to retaliate against new attacker!\n" );
+ }
+ /*
+ * Rethink this
+ else
+ {
+ me->Panic( me->GetAttacker() );
+ me->PrintIfWatched( "Panicking from crossfire while attacking!\n" );
+ }
+ */
+
+ return;
+ }
+
+ if (true || gpGlobals->curtime > m_reacquireTimestamp)
+ me->FireWeaponAtEnemy();
+
+
+ // do dodge behavior
+ Dodge( me );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Finish attack
+ */
+void AttackState::OnExit( CCSBot *me )
+{
+ me->PrintIfWatched( "AttackState:OnExit()\n" );
+
+ m_crouchAndHold = false;
+
+ // clear any noises we heard during battle
+ me->ForgetNoise();
+ me->ResetStuckMonitor();
+
+ // resume our original posture
+ me->PopPostureContext();
+
+ // put shield away
+ if (me->IsProtectedByShield())
+ me->SecondaryAttack();
+
+
+ //me->StopAiming();
+}
+
diff --git a/game/server/cstrike/bot/states/cs_bot_buy.cpp b/game/server/cstrike/bot/states/cs_bot_buy.cpp
new file mode 100644
index 0000000..de06e8b
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_buy.cpp
@@ -0,0 +1,690 @@
+//========= 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"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------------------
+ConVar bot_loadout( "bot_loadout", "", FCVAR_CHEAT, "bots are given these items at round start" );
+ConVar bot_randombuy( "bot_randombuy", "0", FCVAR_CHEAT, "should bots ignore their prefered weapons and just buy weapons at random?" );
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Debug command to give a named weapon
+ */
+void CCSBot::GiveWeapon( const char *weaponAlias )
+{
+ const char *translatedAlias = GetTranslatedWeaponAlias( weaponAlias );
+
+ char wpnName[128];
+ Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
+ WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
+ if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
+ {
+ return;
+ }
+
+ CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
+ if ( !pWeaponInfo )
+ {
+ return;
+ }
+
+ if ( !Weapon_OwnsThisType( wpnName ) )
+ {
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( pWeaponInfo->iSlot );
+ if ( pWeapon )
+ {
+ if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
+ {
+ DropPistol();
+ }
+ else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
+ {
+ DropRifle();
+ }
+ }
+ }
+
+ GiveNamedItem( wpnName );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+static bool HasDefaultPistol( CCSBot *me )
+{
+ CWeaponCSBase *pistol = (CWeaponCSBase *)me->Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+
+ if (pistol == NULL)
+ return false;
+
+ if (me->GetTeamNumber() == TEAM_TERRORIST && pistol->IsA( WEAPON_GLOCK ))
+ return true;
+
+ if (me->GetTeamNumber() == TEAM_CT && pistol->IsA( WEAPON_USP ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Buy weapons, armor, etc.
+ */
+void BuyState::OnEnter( CCSBot *me )
+{
+ m_retries = 0;
+ m_prefRetries = 0;
+ m_prefIndex = 0;
+
+ const char *cheatWeaponString = bot_loadout.GetString();
+ if ( cheatWeaponString && *cheatWeaponString )
+ {
+ m_doneBuying = false; // we're going to be given weapons - ignore the eco limit
+ }
+ else
+ {
+ // check if we are saving money for the next round
+ if (me->m_iAccount < cv_bot_eco_limit.GetFloat())
+ {
+ me->PrintIfWatched( "Saving money for next round.\n" );
+ m_doneBuying = true;
+ }
+ else
+ {
+ m_doneBuying = false;
+ }
+ }
+
+ m_isInitialDelay = true;
+
+ // this will force us to stop holding live grenade
+ me->EquipBestWeapon( MUST_EQUIP );
+
+ m_buyDefuseKit = false;
+ m_buyShield = false;
+
+ if (me->GetTeamNumber() == TEAM_CT)
+ {
+ if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ {
+ // CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
+ if (CSGameRules()->IsCareer() == false)
+ {
+ const float buyDefuseKitChance = 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
+ if (RandomFloat( 0.0f, 100.0f ) < buyDefuseKitChance)
+ {
+ m_buyDefuseKit = true;
+ }
+ }
+ }
+
+ // determine if we want a tactical shield
+ if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
+ {
+ if (me->m_iAccount > 2500)
+ {
+ if (me->m_iAccount < 4000)
+ m_buyShield = (RandomFloat( 0, 100.0f ) < 33.3f) ? true : false;
+ else
+ m_buyShield = (RandomFloat( 0, 100.0f ) < 10.0f) ? true : false;
+ }
+ }
+ }
+
+ if (TheCSBots()->AllowGrenades())
+ {
+ m_buyGrenade = (RandomFloat( 0.0f, 100.0f ) < 33.3f) ? true : false;
+ }
+ else
+ {
+ m_buyGrenade = false;
+ }
+
+
+ m_buyPistol = false;
+ if (TheCSBots()->AllowPistols())
+ {
+ // check if we have a pistol
+ if (me->Weapon_GetSlot( WEAPON_SLOT_PISTOL ))
+ {
+ // if we have our default pistol, think about buying a different one
+ if (HasDefaultPistol( me ))
+ {
+ // if everything other than pistols is disallowed, buy a pistol
+ if (TheCSBots()->AllowShotguns() == false &&
+ TheCSBots()->AllowSubMachineGuns() == false &&
+ TheCSBots()->AllowRifles() == false &&
+ TheCSBots()->AllowMachineGuns() == false &&
+ TheCSBots()->AllowTacticalShield() == false &&
+ TheCSBots()->AllowSnipers() == false)
+ {
+ m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
+ }
+ else if (me->m_iAccount < 1000)
+ {
+ // if we're low on cash, buy a pistol
+ m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
+ }
+ else
+ {
+ m_buyPistol = (RandomFloat( 0, 100 ) < 33.3f);
+ }
+ }
+ }
+ else
+ {
+ // we dont have a pistol - buy one
+ m_buyPistol = true;
+ }
+ }
+}
+
+
+enum WeaponType
+{
+ PISTOL,
+ SHOTGUN,
+ SUB_MACHINE_GUN,
+ RIFLE,
+ MACHINE_GUN,
+ SNIPER_RIFLE,
+ GRENADE,
+
+ NUM_WEAPON_TYPES
+};
+
+struct BuyInfo
+{
+ WeaponType type;
+ bool preferred; ///< more challenging bots prefer these weapons
+ const char *buyAlias; ///< the buy alias for this equipment
+};
+
+#define PRIMARY_WEAPON_BUY_COUNT 13
+#define SECONDARY_WEAPON_BUY_COUNT 3
+
+/**
+ * These tables MUST be kept in sync with the CT and T buy aliases
+ */
+
+static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
+{
+ { SHOTGUN, false, "m3" }, // WEAPON_M3
+ { SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
+ { SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
+ { SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
+ { SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
+ { SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
+ { RIFLE, true, "famas" }, // WEAPON_FAMAS
+ { SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
+ { RIFLE, true, "m4a1" }, // WEAPON_M4A1
+ { RIFLE, false, "aug" }, // WEAPON_AUG
+ { SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
+ { SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
+ { MACHINE_GUN, false, "m249" } // WEAPON_M249
+};
+
+static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
+{
+// { PISTOL, false, "glock" },
+// { PISTOL, false, "usp" },
+ { PISTOL, true, "p228" },
+ { PISTOL, true, "deagle" },
+ { PISTOL, true, "fn57" }
+};
+
+
+static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
+{
+ { SHOTGUN, false, "m3" }, // WEAPON_M3
+ { SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
+ { SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
+ { SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
+ { SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
+ { SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
+ { RIFLE, true, "galil" }, // WEAPON_GALIL
+ { RIFLE, true, "ak47" }, // WEAPON_AK47
+ { SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
+ { RIFLE, true, "sg552" }, // WEAPON_SG552
+ { SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
+ { SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
+ { MACHINE_GUN, false, "m249" } // WEAPON_M249
+};
+
+static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
+{
+// { PISTOL, false, "glock" },
+// { PISTOL, false, "usp" },
+ { PISTOL, true, "p228" },
+ { PISTOL, true, "deagle" },
+ { PISTOL, true, "elites" }
+};
+
+/**
+ * Given a weapon alias, return the kind of weapon it is
+ */
+inline WeaponType GetWeaponType( const char *alias )
+{
+ int i;
+
+ for( i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
+ {
+ if (!stricmp( alias, primaryWeaponBuyInfoCT[i].buyAlias ))
+ return primaryWeaponBuyInfoCT[i].type;
+
+ if (!stricmp( alias, primaryWeaponBuyInfoT[i].buyAlias ))
+ return primaryWeaponBuyInfoT[i].type;
+ }
+
+ for( i=0; i<SECONDARY_WEAPON_BUY_COUNT; ++i )
+ {
+ if (!stricmp( alias, secondaryWeaponBuyInfoCT[i].buyAlias ))
+ return secondaryWeaponBuyInfoCT[i].type;
+
+ if (!stricmp( alias, secondaryWeaponBuyInfoT[i].buyAlias ))
+ return secondaryWeaponBuyInfoT[i].type;
+ }
+
+ return NUM_WEAPON_TYPES;
+}
+
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+void BuyState::OnUpdate( CCSBot *me )
+{
+ char cmdBuffer[256];
+
+ // wait for a Navigation Mesh
+ if (!TheNavMesh->IsLoaded())
+ return;
+
+ // apparently we cant buy things in the first few seconds, so wait a bit
+ if (m_isInitialDelay)
+ {
+ const float waitToBuyTime = 0.25f;
+ if (gpGlobals->curtime - me->GetStateTimestamp() < waitToBuyTime)
+ return;
+
+ m_isInitialDelay = false;
+ }
+
+ // if we're done buying and still in the freeze period, wait
+ if (m_doneBuying)
+ {
+ if (CSGameRules()->IsMultiplayer() && CSGameRules()->IsFreezePeriod())
+ {
+ // make sure we're locked and loaded
+ me->EquipBestWeapon( MUST_EQUIP );
+ me->Reload();
+ me->ResetStuckMonitor();
+ return;
+ }
+
+ me->Idle();
+ return;
+ }
+
+ // If we're supposed to buy a specific weapon for debugging, do so and then bail
+ const char *cheatWeaponString = bot_loadout.GetString();
+ if ( cheatWeaponString && *cheatWeaponString )
+ {
+ CUtlVector<char*, CUtlMemory<char*> > loadout;
+ Q_SplitString( cheatWeaponString, " ", loadout );
+ for ( int i=0; i<loadout.Count(); ++i )
+ {
+ const char *item = loadout[i];
+ if ( FStrEq( item, "vest" ) )
+ {
+ me->GiveNamedItem( "item_kevlar" );
+ }
+ else if ( FStrEq( item, "vesthelm" ) )
+ {
+ me->GiveNamedItem( "item_assaultsuit" );
+ }
+ else if ( FStrEq( item, "defuser" ) )
+ {
+ if ( me->GetTeamNumber() == TEAM_CT )
+ {
+ me->GiveDefuser();
+ }
+ }
+ else if ( FStrEq( item, "nvgs" ) )
+ {
+ me->m_bHasNightVision = true;
+ }
+ else if ( FStrEq( item, "primammo" ) )
+ {
+ me->AttemptToBuyAmmo( 0 );
+ }
+ else if ( FStrEq( item, "secammo" ) )
+ {
+ me->AttemptToBuyAmmo( 1 );
+ }
+ else
+ {
+ me->GiveWeapon( item );
+ }
+ }
+ m_doneBuying = true;
+ return;
+ }
+
+
+ if (!me->IsInBuyZone())
+ {
+ m_doneBuying = true;
+ CONSOLE_ECHO( "%s bot spawned outside of a buy zone (%d, %d, %d)\n",
+ (me->GetTeamNumber() == TEAM_CT) ? "CT" : "Terrorist",
+ (int)me->GetAbsOrigin().x,
+ (int)me->GetAbsOrigin().y,
+ (int)me->GetAbsOrigin().z );
+ return;
+ }
+
+ // try to buy some weapons
+ const float buyInterval = 0.02f;
+ if (gpGlobals->curtime - me->GetStateTimestamp() > buyInterval)
+ {
+ me->m_stateTimestamp = gpGlobals->curtime;
+
+ bool isPreferredAllDisallowed = true;
+
+ // try to buy our preferred weapons first
+ if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount() && bot_randombuy.GetBool() == false )
+ {
+ // need to retry because sometimes first buy fails??
+ const int maxPrefRetries = 2;
+ if (m_prefRetries >= maxPrefRetries)
+ {
+ // try to buy next preferred weapon
+ ++m_prefIndex;
+ m_prefRetries = 0;
+ return;
+ }
+
+ int weaponPreference = me->GetProfile()->GetWeaponPreference( m_prefIndex );
+
+ // don't buy it again if we still have one from last round
+ char weaponPreferenceName[32];
+ Q_snprintf( weaponPreferenceName, sizeof(weaponPreferenceName), "weapon_%s", me->GetProfile()->GetWeaponPreferenceAsString( m_prefIndex ) );
+ if( me->Weapon_OwnsThisType(weaponPreferenceName) )//Prefs and buyalias use the short version, this uses the long
+ {
+ // done with buying preferred weapon
+ m_prefIndex = 9999;
+ return;
+ }
+
+ if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
+ {
+ // done with buying preferred weapon
+ m_prefIndex = 9999;
+ return;
+ }
+
+ const char *buyAlias = NULL;
+
+ if (weaponPreference == WEAPON_SHIELDGUN)
+ {
+ if (TheCSBots()->AllowTacticalShield())
+ buyAlias = "shield";
+ }
+ else
+ {
+ buyAlias = WeaponIDToAlias( weaponPreference );
+ WeaponType type = GetWeaponType( buyAlias );
+ switch( type )
+ {
+ case PISTOL:
+ if (!TheCSBots()->AllowPistols())
+ buyAlias = NULL;
+ break;
+
+ case SHOTGUN:
+ if (!TheCSBots()->AllowShotguns())
+ buyAlias = NULL;
+ break;
+
+ case SUB_MACHINE_GUN:
+ if (!TheCSBots()->AllowSubMachineGuns())
+ buyAlias = NULL;
+ break;
+
+ case RIFLE:
+ if (!TheCSBots()->AllowRifles())
+ buyAlias = NULL;
+ break;
+
+ case MACHINE_GUN:
+ if (!TheCSBots()->AllowMachineGuns())
+ buyAlias = NULL;
+ break;
+
+ case SNIPER_RIFLE:
+ if (!TheCSBots()->AllowSnipers())
+ buyAlias = NULL;
+ break;
+ }
+ }
+
+ if (buyAlias)
+ {
+ Q_snprintf( cmdBuffer, 256, "buy %s\n", buyAlias );
+
+ CCommand args;
+ args.Tokenize( cmdBuffer );
+ me->ClientCommand( args );
+
+ me->PrintIfWatched( "Tried to buy preferred weapon %s.\n", buyAlias );
+ isPreferredAllDisallowed = false;
+ }
+
+ ++m_prefRetries;
+
+ // bail out so we dont waste money on other equipment
+ // unless everything we prefer has been disallowed, then buy at random
+ if (isPreferredAllDisallowed == false)
+ return;
+ }
+
+ // if we have no preferred primary weapon (or everything we want is disallowed), buy at random
+ if (!me->HasPrimaryWeapon() && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
+ {
+ if (m_buyShield)
+ {
+ // buy a shield
+ CCommand args;
+ args.Tokenize( "buy shield" );
+ me->ClientCommand( args );
+
+ me->PrintIfWatched( "Tried to buy a shield.\n" );
+ }
+ else
+ {
+ // build list of allowable weapons to buy
+ BuyInfo *masterPrimary = (me->GetTeamNumber() == TEAM_TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
+ BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
+ int stockPrimaryCount = 0;
+
+ // dont choose sniper rifles as often
+ const float sniperRifleChance = 50.0f;
+ bool wantSniper = (RandomFloat( 0, 100 ) < sniperRifleChance) ? true : false;
+
+ if ( bot_randombuy.GetBool() )
+ {
+ wantSniper = true;
+ }
+
+ for( int i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
+ {
+ if ((masterPrimary[i].type == SHOTGUN && TheCSBots()->AllowShotguns()) ||
+ (masterPrimary[i].type == SUB_MACHINE_GUN && TheCSBots()->AllowSubMachineGuns()) ||
+ (masterPrimary[i].type == RIFLE && TheCSBots()->AllowRifles()) ||
+ (masterPrimary[i].type == SNIPER_RIFLE && TheCSBots()->AllowSnipers() && wantSniper) ||
+ (masterPrimary[i].type == MACHINE_GUN && TheCSBots()->AllowMachineGuns()))
+ {
+ stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
+ }
+ }
+
+ if (stockPrimaryCount)
+ {
+ // buy primary weapon if we don't have one
+ int which;
+
+ // on hard difficulty levels, bots try to buy preferred weapons on the first pass
+ if (m_retries == 0 && TheCSBots()->GetDifficultyLevel() >= BOT_HARD && bot_randombuy.GetBool() == false )
+ {
+ // count up available preferred weapons
+ int prefCount = 0;
+ for( which=0; which<stockPrimaryCount; ++which )
+ if (stockPrimary[which]->preferred)
+ ++prefCount;
+
+ if (prefCount)
+ {
+ int whichPref = RandomInt( 0, prefCount-1 );
+ for( which=0; which<stockPrimaryCount; ++which )
+ if (stockPrimary[which]->preferred && whichPref-- == 0)
+ break;
+ }
+ else
+ {
+ // no preferred weapons available, just pick randomly
+ which = RandomInt( 0, stockPrimaryCount-1 );
+ }
+ }
+ else
+ {
+ which = RandomInt( 0, stockPrimaryCount-1 );
+ }
+
+ Q_snprintf( cmdBuffer, 256, "buy %s\n", stockPrimary[ which ]->buyAlias );
+
+ CCommand args;
+ args.Tokenize( cmdBuffer );
+ me->ClientCommand( args );
+
+ me->PrintIfWatched( "Tried to buy %s.\n", stockPrimary[ which ]->buyAlias );
+ }
+ }
+ }
+
+
+ //
+ // If we now have a weapon, or have tried for too long, we're done
+ //
+ if (me->HasPrimaryWeapon() || m_retries++ > 5)
+ {
+ // primary ammo
+ CCommand args;
+ if (me->HasPrimaryWeapon())
+ {
+ args.Tokenize( "buy primammo" );
+ me->ClientCommand( args );
+ }
+
+ // buy armor last, to make sure we bought a weapon first
+ args.Tokenize( "buy vesthelm" );
+ me->ClientCommand( args );
+ args.Tokenize( "buy vest" );
+ me->ClientCommand( args );
+
+ // pistols - if we have no preferred pistol, buy at random
+ if (TheCSBots()->AllowPistols() && !me->GetProfile()->HasPistolPreference())
+ {
+ if (m_buyPistol)
+ {
+ int which = RandomInt( 0, SECONDARY_WEAPON_BUY_COUNT-1 );
+
+ const char *what = NULL;
+
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ what = secondaryWeaponBuyInfoT[ which ].buyAlias;
+ else
+ what = secondaryWeaponBuyInfoCT[ which ].buyAlias;
+
+ Q_snprintf( cmdBuffer, 256, "buy %s\n", what );
+ args.Tokenize( cmdBuffer );
+ me->ClientCommand( args );
+
+
+ // only buy one pistol
+ m_buyPistol = false;
+ }
+
+ // make sure we have enough pistol ammo
+ args.Tokenize( "buy secammo" );
+ me->ClientCommand( args );
+ }
+
+ // buy a grenade if we wish, and we don't already have one
+ if (m_buyGrenade && !me->HasGrenade())
+ {
+ if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
+ {
+ // only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
+ float rnd = RandomFloat( 0, 100 );
+
+ if (rnd < 10)
+ {
+ args.Tokenize( "buy smokegrenade" );
+ me->ClientCommand( args ); // smoke grenade
+ }
+ else if (rnd < 35)
+ {
+ args.Tokenize( "buy flashbang" );
+ me->ClientCommand( args ); // flashbang
+ }
+ else
+ {
+ args.Tokenize( "buy hegrenade" );
+ me->ClientCommand( args ); // he grenade
+ }
+ }
+ else
+ {
+ if (RandomFloat( 0, 100 ) < 10)
+ {
+ args.Tokenize( "buy smokegrenade" ); // smoke grenade
+ me->ClientCommand( args );
+ }
+ else
+ {
+ args.Tokenize( "buy hegrenade" ); // he grenade
+ me->ClientCommand( args );
+ }
+ }
+ }
+
+ if (m_buyDefuseKit)
+ {
+ args.Tokenize( "buy defuser" );
+ me->ClientCommand( args );
+ }
+
+ m_doneBuying = true;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void BuyState::OnExit( CCSBot *me )
+{
+ me->ResetStuckMonitor();
+ me->EquipBestWeapon();
+}
+
diff --git a/game/server/cstrike/bot/states/cs_bot_defuse_bomb.cpp b/game/server/cstrike/bot/states/cs_bot_defuse_bomb.cpp
new file mode 100644
index 0000000..3614bcb
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_defuse_bomb.cpp
@@ -0,0 +1,82 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin defusing the bomb
+ */
+void DefuseBombState::OnEnter( CCSBot *me )
+{
+ me->Crouch();
+ me->SetDisposition( CCSBot::SELF_DEFENSE );
+ me->GetChatter()->Say( "DefusingBomb" );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Defuse the bomb
+ */
+void DefuseBombState::OnUpdate( CCSBot *me )
+{
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+
+ if (bombPos == NULL)
+ {
+ me->PrintIfWatched( "In Defuse state, but don't know where the bomb is!\n" );
+ me->Idle();
+ return;
+ }
+
+ // look at the bomb
+ me->SetLookAt( "Defuse bomb", *bombPos, PRIORITY_HIGH );
+
+ // defuse...
+ me->UseEnvironment();
+
+ if (gpGlobals->curtime - me->GetStateTimestamp() > 1.0f)
+ {
+ // if we missed starting the defuse, give up
+ if (TheCSBots()->GetBombDefuser() == NULL)
+ {
+ me->PrintIfWatched( "Failed to start defuse, giving up\n" );
+ me->Idle();
+ return;
+ }
+ else if (TheCSBots()->GetBombDefuser() != me)
+ {
+ // if someone else got the defuse, give up
+ me->PrintIfWatched( "Someone else started defusing, giving up\n" );
+ me->Idle();
+ return;
+ }
+ }
+
+ // if bomb has been defused, give up
+ if (!TheCSBots()->IsBombPlanted())
+ {
+ me->Idle();
+ return;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void DefuseBombState::OnExit( CCSBot *me )
+{
+ me->StandUp();
+ me->ResetStuckMonitor();
+ me->SetTask( CCSBot::SEEK_AND_DESTROY );
+ me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
+ me->ClearLookAt();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_escape_from_bomb.cpp b/game/server/cstrike/bot/states/cs_bot_escape_from_bomb.cpp
new file mode 100644
index 0000000..9abeb2b
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_escape_from_bomb.cpp
@@ -0,0 +1,67 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Escape from the bomb.
+ */
+void EscapeFromBombState::OnEnter( CCSBot *me )
+{
+ me->StandUp();
+ me->Run();
+ me->DestroyPath();
+ me->EquipKnife();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Escape from the bomb.
+ */
+void EscapeFromBombState::OnUpdate( CCSBot *me )
+{
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+
+ // if we don't know where the bomb is, we shouldn't be in this state
+ if (bombPos == NULL)
+ {
+ me->Idle();
+ return;
+ }
+
+ // grab our knife to move quickly
+ me->EquipKnife();
+
+ // look around
+ me->UpdateLookAround();
+
+ if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
+ {
+ // we have no path, or reached the end of one - create a new path far away from the bomb
+ FarAwayFromPositionFunctor func( *bombPos );
+ CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func );
+
+ // if this fails, we'll try again next time
+ me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Escape from the bomb.
+ */
+void EscapeFromBombState::OnExit( CCSBot *me )
+{
+ me->EquipBestWeapon();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_fetch_bomb.cpp b/game/server/cstrike/bot/states/cs_bot_fetch_bomb.cpp
new file mode 100644
index 0000000..6e78019
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_fetch_bomb.cpp
@@ -0,0 +1,69 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to the bomb on the floor and pick it up
+ */
+void FetchBombState::OnEnter( CCSBot *me )
+{
+ me->DestroyPath();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to the bomb on the floor and pick it up
+ */
+void FetchBombState::OnUpdate( CCSBot *me )
+{
+ if (me->HasC4())
+ {
+ me->PrintIfWatched( "I picked up the bomb\n" );
+ me->Idle();
+ return;
+ }
+
+
+ CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
+ if (bomb)
+ {
+ if (!me->HasPath())
+ {
+ // build a path to the bomb
+ if (me->ComputePath( bomb->GetAbsOrigin() ) == false)
+ {
+ me->PrintIfWatched( "Fetch bomb pathfind failed\n" );
+
+ // go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
+ me->Hunt();
+ return;
+ }
+ }
+ }
+ else
+ {
+ // someone picked up the bomb
+ me->PrintIfWatched( "Someone else picked up the bomb.\n" );
+ me->Idle();
+ return;
+ }
+
+ // look around
+ me->UpdateLookAround();
+
+ if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
+ me->Idle();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_follow.cpp b/game/server/cstrike/bot/states/cs_bot_follow.cpp
new file mode 100644
index 0000000..c8d4976
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_follow.cpp
@@ -0,0 +1,366 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Follow our leader
+ */
+void FollowState::OnEnter( CCSBot *me )
+{
+ me->StandUp();
+ me->Run();
+ me->DestroyPath();
+
+ m_isStopped = false;
+ m_stoppedTimestamp = 0.0f;
+
+ // to force immediate repath
+ m_lastLeaderPos.x = -99999999.9f;
+ m_lastLeaderPos.y = -99999999.9f;
+ m_lastLeaderPos.z = -99999999.9f;
+
+ m_lastSawLeaderTime = 0;
+
+ // set re-pathing frequency
+ m_repathInterval.Invalidate();
+
+ m_isSneaking = false;
+
+ m_walkTime.Invalidate();
+ m_isAtWalkSpeed = false;
+
+ m_leaderMotionState = INVALID;
+ m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine the leader's motion state by tracking his speed
+ */
+void FollowState::ComputeLeaderMotionState( float leaderSpeed )
+{
+ // walk = 130, run = 250
+ const float runWalkThreshold = 140.0f;
+ const float walkStopThreshold = 10.0f; // 120.0f;
+ LeaderMotionStateType prevState = m_leaderMotionState;
+ if (leaderSpeed > runWalkThreshold)
+ {
+ m_leaderMotionState = RUNNING;
+ m_isAtWalkSpeed = false;
+ }
+ else if (leaderSpeed > walkStopThreshold)
+ {
+ // track when began to walk
+ if (!m_isAtWalkSpeed)
+ {
+ m_walkTime.Start();
+ m_isAtWalkSpeed = true;
+ }
+
+ const float minWalkTime = 0.25f;
+ if (m_walkTime.GetElapsedTime() > minWalkTime)
+ {
+ m_leaderMotionState = WALKING;
+ }
+ }
+ else
+ {
+ m_leaderMotionState = STOPPED;
+ m_isAtWalkSpeed = false;
+ }
+
+ // track time spent in this motion state
+ if (prevState != m_leaderMotionState)
+ {
+ m_leaderMotionStateTime.Start();
+ m_waitTime = RandomFloat( 1.0f, 3.0f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Functor to collect all areas in the forward direction of the given player within a radius
+ */
+class FollowTargetCollector
+{
+public:
+ FollowTargetCollector( CBasePlayer *player )
+ {
+ m_player = player;
+
+ Vector playerVel = player->GetAbsVelocity();
+ m_forward.x = playerVel.x;
+ m_forward.y = playerVel.y;
+ float speed = m_forward.NormalizeInPlace();
+
+ Vector playerOrigin = GetCentroid( player );
+
+ const float walkSpeed = 100.0f;
+ if (speed < walkSpeed)
+ {
+ m_cutoff.x = playerOrigin.x;
+ m_cutoff.y = playerOrigin.y;
+ m_forward.x = 0.0f;
+ m_forward.y = 0.0f;
+ }
+ else
+ {
+ const float k = 1.5f; // 2.0f;
+ float trimSpeed = MIN( speed, 200.0f );
+ m_cutoff.x = playerOrigin.x + k * trimSpeed * m_forward.x;
+ m_cutoff.y = playerOrigin.y + k * trimSpeed * m_forward.y;
+ }
+
+ m_targetAreaCount = 0;
+ }
+
+ enum { MAX_TARGET_AREAS = 128 };
+
+ bool operator() ( CNavArea *area )
+ {
+ if (m_targetAreaCount >= MAX_TARGET_AREAS)
+ return false;
+
+ // only use two-way connections
+ if (!area->GetParent() || area->IsConnected( area->GetParent(), NUM_DIRECTIONS ))
+ {
+ if (m_forward.IsZero())
+ {
+ m_targetArea[ m_targetAreaCount++ ] = area;
+ }
+ else
+ {
+ // collect areas in the direction of the player's forward motion
+ Vector2D to( area->GetCenter().x - m_cutoff.x, area->GetCenter().y - m_cutoff.y );
+ to.NormalizeInPlace();
+
+ //if (DotProduct( to, m_forward ) > 0.7071f)
+ if ((to.x * m_forward.x + to.y * m_forward.y) > 0.7071f)
+ m_targetArea[ m_targetAreaCount++ ] = area;
+ }
+ }
+
+ return (m_targetAreaCount < MAX_TARGET_AREAS);
+ }
+
+
+ CBasePlayer *m_player;
+ Vector2D m_forward;
+ Vector2D m_cutoff;
+
+ CNavArea *m_targetArea[ MAX_TARGET_AREAS ];
+ int m_targetAreaCount;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Follow our leader
+ * @todo Clean up this nasty mess
+ */
+void FollowState::OnUpdate( CCSBot *me )
+{
+ // if we lost our leader, give up
+ if (m_leader == NULL || !m_leader->IsAlive())
+ {
+ me->Idle();
+ return;
+ }
+
+ // if we are carrying the bomb and at a bombsite, plant
+ if (me->HasC4() && me->IsAtBombsite())
+ {
+ // plant it
+ me->SetTask( CCSBot::PLANT_BOMB );
+ me->PlantBomb();
+
+ // radio to the team
+ me->GetChatter()->PlantingTheBomb( me->GetPlace() );
+
+ return;
+ }
+
+ // look around
+ me->UpdateLookAround();
+
+ // if we are moving, we are not idle
+ if (me->IsNotMoving() == false)
+ m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
+
+ // compute the leader's speed
+ Vector leaderVel = m_leader->GetAbsVelocity();
+ float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length();
+
+ // determine our leader's movement state
+ ComputeLeaderMotionState( leaderSpeed );
+
+ // track whether we can see the leader
+ bool isLeaderVisible;
+ Vector leaderOrigin = GetCentroid( m_leader );
+ if (me->IsVisible( leaderOrigin ))
+ {
+ m_lastSawLeaderTime = gpGlobals->curtime;
+ isLeaderVisible = true;
+ }
+ else
+ {
+ isLeaderVisible = false;
+ }
+
+
+ // determine whether we should sneak or not
+ const float farAwayRange = 750.0f;
+ Vector myOrigin = GetCentroid( me );
+ if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange ))
+ {
+ // far away from leader - run to catch up
+ m_isSneaking = false;
+ }
+ else if (isLeaderVisible)
+ {
+ // if we see leader walking and we are nearby, walk
+ if (m_leaderMotionState == WALKING)
+ m_isSneaking = true;
+
+ // if we are sneaking and our leader starts running, stop sneaking
+ if (m_isSneaking && m_leaderMotionState == RUNNING)
+ m_isSneaking = false;
+ }
+
+ // if we haven't seen the leader for a long time, run
+ const float longTime = 20.0f;
+ if (gpGlobals->curtime - m_lastSawLeaderTime > longTime)
+ m_isSneaking = false;
+
+ if (m_isSneaking)
+ me->Walk();
+ else
+ me->Run();
+
+
+ bool repath = false;
+
+ // if the leader has stopped, hide nearby
+ const float nearLeaderRange = 250.0f;
+ if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
+ {
+ // throttle how often this check occurs
+ m_waitTime += RandomFloat( 1.0f, 3.0f );
+
+ // the leader has stopped - if we are close to him, take up a hiding spot
+ if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange ))
+ {
+ const float hideRange = 250.0f;
+ if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST ))
+ {
+ me->ResetStuckMonitor();
+ return;
+ }
+ }
+ }
+
+ // if we have been idle for awhile, move
+ if (m_idleTimer.IsElapsed())
+ {
+ repath = true;
+
+ // always walk when we move such a short distance
+ m_isSneaking = true;
+ }
+
+ // if our leader has moved, repath (don't repath if leading is stopping)
+ if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
+ {
+ repath = true;
+ }
+
+ // move along our path
+ if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
+ {
+ me->DestroyPath();
+ }
+
+ // recompute our path if necessary
+ if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder())
+ {
+ // recompute our path to keep us near our leader
+ m_lastLeaderPos = leaderOrigin;
+
+ me->ResetStuckMonitor();
+
+ const float runSpeed = 200.0f;
+
+ const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200
+ FollowTargetCollector collector( m_leader );
+ SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange );
+
+ if (cv_bot_debug.GetBool())
+ {
+ for( int i=0; i<collector.m_targetAreaCount; ++i )
+ collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ );
+ }
+
+ // move to one of the collected areas
+ if (collector.m_targetAreaCount)
+ {
+ CNavArea *target = NULL;
+ Vector targetPos;
+
+ // if we are idle, pick a random area
+ if (m_idleTimer.IsElapsed())
+ {
+ target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ];
+ targetPos = target->GetCenter();
+ me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime );
+ }
+ else
+ {
+ me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime );
+
+ // find closest area to where we are
+ CNavArea *area;
+ float closeRangeSq = 9999999999.9f;
+ Vector close;
+
+ for( int a=0; a<collector.m_targetAreaCount; ++a )
+ {
+ area = collector.m_targetArea[a];
+
+ area->GetClosestPointOnArea( myOrigin, &close );
+
+ float rangeSq = (myOrigin - close).LengthSqr();
+ if (rangeSq < closeRangeSq)
+ {
+ target = area;
+ targetPos = close;
+ closeRangeSq = rangeSq;
+ }
+ }
+ }
+
+ if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == false)
+ me->PrintIfWatched( "Pathfind to leader failed.\n" );
+
+ // throttle how often we repath
+ m_repathInterval.Start( 0.5f );
+
+ m_idleTimer.Reset();
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void FollowState::OnExit( CCSBot *me )
+{
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_hide.cpp b/game/server/cstrike/bot/states/cs_bot_hide.cpp
new file mode 100644
index 0000000..9afd1e8
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_hide.cpp
@@ -0,0 +1,549 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin moving to a nearby hidey-hole.
+ * NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
+ */
+void HideState::OnEnter( CCSBot *me )
+{
+ m_isAtSpot = false;
+ m_isLookingOutward = false;
+
+ // if duration is "infinite", set it to a reasonably long time to prevent infinite camping
+ if (m_duration < 0.0f)
+ {
+ m_duration = RandomFloat( 30.0f, 60.0f );
+ }
+
+ // decide whether to "ambush" or not - never set to false so as not to override external setting
+ if (RandomFloat( 0.0f, 100.0f ) < 50.0f)
+ {
+ m_isHoldingPosition = true;
+ }
+
+ // if we are holding position, decide for how long
+ if (m_isHoldingPosition)
+ {
+ m_holdPositionTime = RandomFloat( 3.0f, 10.0f );
+ }
+ else
+ {
+ m_holdPositionTime = 0.0f;
+ }
+
+ m_heardEnemy = false;
+ m_firstHeardEnemyTime = 0.0f;
+ m_retry = 0;
+
+ if (me->IsFollowing())
+ {
+ m_leaderAnchorPos = GetCentroid( me->GetFollowLeader() );
+ }
+
+ // if we are a sniper, we need to periodically pause while we retreat to squeeze off a shot or two
+ if (me->IsSniper())
+ {
+ // start off paused to allow a final shot before retreating
+ m_isPaused = false;
+ m_pauseTimer.Invalidate();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to a nearby hidey-hole.
+ * NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
+ */
+void HideState::OnUpdate( CCSBot *me )
+{
+ Vector myOrigin = GetCentroid( me );
+
+ // wait until finished reloading to leave hide state
+ if (!me->IsReloading())
+ {
+ // if we are momentarily hiding while following someone, check to see if he has moved on
+ if (me->IsFollowing())
+ {
+ CCSPlayer *leader = static_cast<CCSPlayer *>( static_cast<CBaseEntity *>( me->GetFollowLeader() ) );
+ Vector leaderOrigin = GetCentroid( leader );
+
+ // BOTPORT: Determine walk/run velocity thresholds
+ float runThreshold = 200.0f;
+ if (leader->GetAbsVelocity().IsLengthGreaterThan( runThreshold ))
+ {
+ // leader is running, stay with him
+ me->Follow( leader );
+ return;
+ }
+
+ // if leader has moved, stay with him
+ const float followRange = 250.0f;
+ if ((m_leaderAnchorPos - leaderOrigin).IsLengthGreaterThan( followRange ))
+ {
+ me->Follow( leader );
+ return;
+ }
+ }
+
+ // if we see a nearby buddy in combat, join him
+ /// @todo - Perhaps tie in to TakeDamage(), so it works for human players, too
+
+ //
+ // Scenario logic
+ //
+ switch( TheCSBots()->GetScenario() )
+ {
+ case CCSBotManager::SCENARIO_DEFUSE_BOMB:
+ {
+ if (me->GetTeamNumber() == TEAM_CT)
+ {
+ // if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
+ if (me->GetTask() == CCSBot::HOLD_POSITION &&
+ TheCSBots()->IsBombPlanted() &&
+ TheCSBots()->GetBombPlantTimestamp() > me->GetStateTimestamp())
+ {
+ me->Idle();
+ return;
+ }
+
+ // if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
+ if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->GetBombDefuser() == NULL)
+ {
+ me->Idle();
+ return;
+ }
+
+ // if we are guarding the loose bomb and it is picked up, stop hiding
+ if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && TheCSBots()->GetLooseBomb() == NULL)
+ {
+ me->GetChatter()->TheyPickedUpTheBomb();
+ me->Idle();
+ return;
+ }
+
+ // if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
+ if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
+ {
+ me->Idle();
+ return;
+ }
+
+ // if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
+ if (me->IsDoingScenario() && me->GetTask() != CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->IsBombPlanted())
+ {
+ me->Idle();
+ return;
+ }
+
+ }
+ else // TERRORIST
+ {
+ // if we are near the ticking bomb and someone starts defusing it, attack!
+ if (TheCSBots()->GetBombDefuser())
+ {
+ Vector defuserOrigin = GetCentroid( TheCSBots()->GetBombDefuser() );
+ Vector toDefuser = defuserOrigin - myOrigin;
+
+ const float hearDefuseRange = 2000.0f;
+ if (toDefuser.IsLengthLessThan( hearDefuseRange ))
+ {
+ // if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
+ if (me->CanSeePlantedBomb())
+ {
+ me->Attack( TheCSBots()->GetBombDefuser() );
+ }
+ else
+ {
+ me->MoveTo( defuserOrigin, FASTEST_ROUTE );
+ me->InhibitLookAround( 10.0f );
+ }
+
+ return;
+ }
+ }
+ }
+ break;
+ }
+
+ //--------------------------------------------------------------------------------------------------
+ case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
+ {
+ // if we're guarding the hostages and they all die or are taken, do something else
+ if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
+ {
+ if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
+ {
+ me->Idle();
+ return;
+ }
+ }
+ else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
+ {
+ // if we stumble across a hostage, guard it
+ CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
+ if (hostage)
+ {
+ // we see a free hostage, guard it
+ Vector hostageOrigin = GetCentroid( hostage );
+ CNavArea *area = TheNavMesh->GetNearestNavArea( hostageOrigin );
+ if (area)
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGES );
+ me->Hide( area );
+ me->PrintIfWatched( "I'm guarding hostages I found\n" );
+ // don't chatter here - he'll tell us when he's in his hiding spot
+ return;
+ }
+ }
+ }
+ }
+ }
+
+
+ bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
+
+ // only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
+ // dont investigate noises if we are reloading
+ if (!me->IsReloading() &&
+ !isSettledInSniper &&
+ me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
+ {
+ // if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
+ if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->curtime - m_firstHeardEnemyTime > m_holdPositionTime))
+ {
+ /// @todo We might need to remember specific location of last enemy noise here
+ me->InvestigateNoise();
+ return;
+ }
+
+ // investigate nearby enemy noises
+ if (me->HeardInterestingNoise())
+ {
+ // if we are holding position, check if enough time has elapsed since we first heard the enemy
+ if (m_isAtSpot && m_isHoldingPosition)
+ {
+ if (!m_heardEnemy)
+ {
+ // first time we heard the enemy
+ m_heardEnemy = true;
+ m_firstHeardEnemyTime = gpGlobals->curtime;
+ me->PrintIfWatched( "Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime );
+ }
+ }
+ else
+ {
+ // not holding position - investigate enemy noise
+ me->InvestigateNoise();
+ return;
+ }
+ }
+ }
+ } // end reloading check
+
+ // look around
+ me->UpdateLookAround();
+
+ // if we are at our hiding spot, crouch and wait
+ if (m_isAtSpot)
+ {
+ me->ResetStuckMonitor();
+
+ CNavArea *area = TheNavMesh->GetNavArea( m_hidingSpot );
+ if ( !area || !( area->GetAttributes() & NAV_MESH_STAND ) )
+ {
+ me->Crouch();
+ }
+
+ // check if duration has expired
+ if (m_hideTimer.IsElapsed())
+ {
+ if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
+ {
+ // if we're guarding the loose bomb, continue to guard it but pick a new spot
+ me->Hide( TheCSBots()->GetLooseBombArea() );
+ return;
+ }
+ else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
+ {
+ // if we're guarding a bombsite, continue to guard it but pick a new spot
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
+ if (zone)
+ {
+ CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
+ if (area)
+ {
+ me->Hide( area );
+ return;
+ }
+ }
+ }
+ else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
+ {
+ // if we're guarding a rescue zone, continue to guard this or another rescue zone
+ if (me->GuardRandomZone())
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
+ me->PrintIfWatched( "Continuing to guard hostage rescue zones\n" );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
+ return;
+ }
+ }
+
+ me->Idle();
+ return;
+ }
+
+/*
+ // if we are watching for an approaching noisy enemy, anticipate and fire before they round the corner
+ /// @todo Need to check if we are looking at an ENEMY_NOISE here
+ const float veryCloseNoise = 250.0f;
+ if (me->IsLookingAtSpot() && me->GetNoiseRange() < veryCloseNoise)
+ {
+ // fire!
+ me->PrimaryAttack();
+ me->PrintIfWatched( "Firing at anticipated enemy coming around the corner!\n" );
+ }
+*/
+
+ // if we have a shield, hide behind it
+ if (me->HasShield() && !me->IsProtectedByShield())
+ me->SecondaryAttack();
+
+ // while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
+ const float hurtRecentlyTime = 1.0f;
+ if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
+ {
+ me->Idle();
+ return;
+ }
+
+ // encourage the human player
+ if (!me->IsDoingScenario())
+ {
+ if (me->GetTeamNumber() == TEAM_CT)
+ {
+ if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE &&
+ me->IsAtHidingSpot() &&
+ TheCSBots()->IsBombPlanted())
+ {
+ if (me->GetNearbyEnemyCount() == 0)
+ {
+ const float someTime = 30.0f;
+ const float littleTime = 11.0;
+
+ if (TheCSBots()->GetBombTimeLeft() > someTime)
+ me->GetChatter()->Encourage( "BombsiteSecure", RandomFloat( 10.0f, 15.0f ) );
+ else if (TheCSBots()->GetBombTimeLeft() > littleTime)
+ me->GetChatter()->Encourage( "WaitingForHumanToDefuseBomb", RandomFloat( 5.0f, 8.0f ) );
+ else
+ me->GetChatter()->Encourage( "WaitingForHumanToDefuseBombPanic", RandomFloat( 3.0f, 4.0f ) );
+ }
+ }
+
+ if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
+ {
+ if (me->GetNearbyEnemyCount() == 0)
+ {
+ CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
+ if (hostage)
+ {
+ me->GetChatter()->Encourage( "WaitingForHumanToRescueHostages", RandomFloat( 10.0f, 15.0f ) );
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // we are moving to our hiding spot
+
+ // snipers periodically pause and fire while retreating
+ if (me->IsSniper() && me->IsEnemyVisible())
+ {
+ if (m_isPaused)
+ {
+ if (m_pauseTimer.IsElapsed())
+ {
+ // get moving
+ m_isPaused = false;
+ m_pauseTimer.Start( RandomFloat( 1.0f, 3.0f ) );
+ }
+ else
+ {
+ me->Wait( 0.2f );
+ }
+ }
+ else
+ {
+ if (m_pauseTimer.IsElapsed())
+ {
+ // pause for a moment
+ m_isPaused = true;
+ m_pauseTimer.Start( RandomFloat( 0.5f, 1.5f ) );
+ }
+ }
+ }
+
+ // if a Player is using this hiding spot, give up
+ float range;
+ CCSPlayer *camper = static_cast<CCSPlayer *>( UTIL_GetClosestPlayer( m_hidingSpot, &range ) );
+
+ const float closeRange = 75.0f;
+ if (camper && camper != me && range < closeRange && me->IsVisible( camper, CHECK_FOV ))
+ {
+ // player is in our hiding spot
+ me->PrintIfWatched( "Someone's in my hiding spot - picking another...\n" );
+
+ const int maxRetries = 3;
+ if (m_retry++ >= maxRetries)
+ {
+ me->PrintIfWatched( "Can't find a free hiding spot, giving up.\n" );
+ me->Idle();
+ return;
+ }
+
+ // pick another hiding spot near where we were planning on hiding
+ me->Hide( TheNavMesh->GetNavArea( m_hidingSpot ) );
+
+ return;
+ }
+
+ Vector toSpot;
+ toSpot.x = m_hidingSpot.x - myOrigin.x;
+ toSpot.y = m_hidingSpot.y - myOrigin.y;
+ toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
+ range = toSpot.Length();
+
+ // look outwards as we get close to our hiding spot
+ if (!me->IsEnemyVisible() && !m_isLookingOutward)
+ {
+ const float lookOutwardRange = 200.0f;
+ const float nearSpotRange = 10.0f;
+ if (range < lookOutwardRange && range > nearSpotRange)
+ {
+ m_isLookingOutward = true;
+
+ toSpot.x /= range;
+ toSpot.y /= range;
+ toSpot.z /= range;
+
+ me->SetLookAt( "Face outward", me->EyePosition() - 1000.0f * toSpot, PRIORITY_HIGH, 3.0f );
+ }
+ }
+
+ const float atDist = 20.0f;
+ if (range < atDist)
+ {
+ //-------------------------------------
+ // Just reached our hiding spot
+ //
+ m_isAtSpot = true;
+ m_hideTimer.Start( m_duration );
+
+ // make sure our approach points are valid, since we'll be watching them
+ me->ComputeApproachPoints();
+ me->ClearLookAt();
+
+ // ready our weapon and prepare to attack
+ me->EquipBestWeapon( me->IsUsingGrenade() );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+
+ // if we are a sniper, update our task
+ if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
+ {
+ me->SetTask( CCSBot::SNIPING );
+ }
+ else if (me->GetTask() == CCSBot::GUARD_INITIAL_ENCOUNTER)
+ {
+ const float campChatterChance = 20.0f;
+ if (RandomFloat( 0, 100 ) < campChatterChance)
+ {
+ me->GetChatter()->Say( "WaitingHere" );
+ }
+ }
+
+
+ // determine which way to look
+ trace_t result;
+ float outAngle = 0.0f;
+ float outAngleRange = 0.0f;
+ for( float angle = 0.0f; angle < 360.0f; angle += 45.0f )
+ {
+ UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 1000.0f * Vector( BotCOS(angle), BotSIN(angle), 0.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction > outAngleRange)
+ {
+ outAngle = angle;
+ outAngleRange = result.fraction;
+ }
+ }
+
+ me->SetLookAheadAngle( outAngle );
+
+ }
+
+ // move to hiding spot
+ if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
+ {
+ // we couldn't get to our hiding spot - pick another
+ me->PrintIfWatched( "Can't get to my hiding spot - finding another...\n" );
+
+ // search from hiding spot, since we know it was valid
+ const Vector *pos = FindNearbyHidingSpot( me, m_hidingSpot, m_range, me->IsSniper() );
+ if (pos == NULL)
+ {
+ // no available hiding spots
+ me->PrintIfWatched( "No available hiding spots - hiding where I'm at.\n" );
+
+ // hide where we are
+ m_hidingSpot.x = myOrigin.x;
+ m_hidingSpot.x = myOrigin.y;
+ m_hidingSpot.z = me->GetFeetZ();
+ }
+ else
+ {
+ m_hidingSpot = *pos;
+ }
+
+ // build a path to our new hiding spot
+ if (me->ComputePath( m_hidingSpot, FASTEST_ROUTE ) == false)
+ {
+ me->PrintIfWatched( "Can't pathfind to hiding spot\n" );
+ me->Idle();
+ return;
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void HideState::OnExit( CCSBot *me )
+{
+ m_isHoldingPosition = false;
+
+ me->StandUp();
+ me->ResetStuckMonitor();
+ //me->ClearLookAt();
+ me->ClearApproachPoints();
+
+ // if we have a shield, put it away
+ if (me->HasShield() && me->IsProtectedByShield())
+ me->SecondaryAttack();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_hunt.cpp b/game/server/cstrike/bot/states/cs_bot_hunt.cpp
new file mode 100644
index 0000000..9ca6c90
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_hunt.cpp
@@ -0,0 +1,234 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin the hunt
+ */
+void HuntState::OnEnter( CCSBot *me )
+{
+ // lurking death
+ if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
+ me->Walk();
+ else
+ me->Run();
+
+
+ me->StandUp();
+ me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
+ me->SetTask( CCSBot::SEEK_AND_DESTROY );
+
+ me->DestroyPath();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Hunt down our enemies
+ */
+void HuntState::OnUpdate( CCSBot *me )
+{
+ // if we've been hunting for a long time, drop into Idle for a moment to
+ // select something else to do
+ const float huntingTooLongTime = 30.0f;
+ if (gpGlobals->curtime - me->GetStateTimestamp() > huntingTooLongTime)
+ {
+ // stop being a rogue and do the scenario, since there must not be many enemies left to hunt
+ me->PrintIfWatched( "Giving up hunting.\n" );
+ me->SetRogue( false );
+ me->Idle();
+ return;
+ }
+
+ // scenario logic
+ if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ {
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ // if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
+ if (me->HasC4())
+ {
+ const float safeTime = 3.0f;
+
+ if (TheCSBots()->IsTimeToPlantBomb() ||
+ (me->IsAtBombsite() && gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > safeTime))
+ {
+ me->Idle();
+ return;
+ }
+ }
+
+ // if we notice the bomb lying on the ground, go get it
+ if (me->NoticeLooseBomb())
+ {
+ me->FetchBomb();
+ return;
+ }
+
+ // if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+ if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos)
+ {
+ me->SetTask( CCSBot::GUARD_TICKING_BOMB );
+ me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
+ return;
+ }
+ }
+ else // CT
+ {
+ if (!me->IsRogue() && me->CanSeeLooseBomb())
+ {
+ // if we are near the loose bomb and can see it, hide nearby and guard it
+ me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
+ me->Hide( TheCSBots()->GetLooseBombArea() );
+ me->GetChatter()->GuardingLooseBomb( TheCSBots()->GetLooseBomb() );
+ return;
+ }
+ else if (TheCSBots()->IsBombPlanted())
+ {
+ // rogues will defuse a bomb, but not guard the defuser
+ if (!me->IsRogue() || !TheCSBots()->GetBombDefuser())
+ {
+ // search for the planted bomb to defuse
+ me->Idle();
+ return;
+ }
+ }
+ }
+ }
+ else if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
+ {
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ if (me->GetGameState()->AreAllHostagesBeingRescued())
+ {
+ // all hostages are being rescued, head them off at the escape zones
+ if (me->GuardRandomZone())
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
+ me->PrintIfWatched( "Trying to beat them to an escape zone!\n" );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
+ return;
+ }
+ }
+
+ // if safe time is up, and we stumble across a hostage, guard it
+ if (!me->IsRogue() && !me->IsSafe())
+ {
+ CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
+ if (hostage)
+ {
+ CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
+ if (area)
+ {
+ // we see a free hostage, guard it
+ me->SetTask( CCSBot::GUARD_HOSTAGES );
+ me->Hide( area );
+ me->PrintIfWatched( "I'm guarding hostages\n" );
+ me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // listen for enemy noises
+ if (me->HeardInterestingNoise())
+ {
+ me->InvestigateNoise();
+ return;
+ }
+
+ // look around
+ me->UpdateLookAround();
+
+ // if we have reached our destination area, pick a new one
+ // if our path fails, pick a new one
+ if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
+ {
+ // pick a new hunt area
+ const float earlyGameTime = 45.0f;
+ if (TheCSBots()->GetElapsedRoundTime() < earlyGameTime && !me->HasVisitedEnemySpawn())
+ {
+ // in the early game, rush the enemy spawn
+ CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( OtherTeam( me->GetTeamNumber() ) );
+
+ //ADRIAN: REVISIT
+ if ( enemySpawn )
+ {
+ m_huntArea = TheNavMesh->GetNavArea( enemySpawn->WorldSpaceCenter() );
+ }
+ }
+ else
+ {
+ m_huntArea = NULL;
+ float oldest = 0.0f;
+
+ int areaCount = 0;
+ const float minSize = 150.0f;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ ++areaCount;
+
+ // skip the small areas
+ Extent extent;
+ area->GetExtent(&extent);
+ if (extent.hi.x - extent.lo.x < minSize || extent.hi.y - extent.lo.y < minSize)
+ continue;
+
+ // keep track of the least recently cleared area
+ float age = gpGlobals->curtime - area->GetClearedTimestamp( me->GetTeamNumber()-1 );
+ if (age > oldest)
+ {
+ oldest = age;
+ m_huntArea = area;
+ }
+ }
+
+ // if all the areas were too small, pick one at random
+ int which = RandomInt( 0, areaCount-1 );
+
+ areaCount = 0;
+ FOR_EACH_VEC( TheNavAreas, hit )
+ {
+ m_huntArea = TheNavAreas[ hit ];
+
+ if (which == areaCount)
+ break;
+
+ --which;
+ }
+ }
+
+ if (m_huntArea)
+ {
+ // create a new path to a far away area of the map
+ me->ComputePath( m_huntArea->GetCenter() );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Done hunting
+ */
+void HuntState::OnExit( CCSBot *me )
+{
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_idle.cpp b/game/server/cstrike/bot/states/cs_bot_idle.cpp
new file mode 100644
index 0000000..64e14d2
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_idle.cpp
@@ -0,0 +1,887 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// range for snipers to select a hiding spot
+const float sniperHideRange = 2000.0f;
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The Idle state.
+ * We never stay in the Idle state - it is a "home base" for the state machine that
+ * does various checks to determine what we should do next.
+ */
+void IdleState::OnEnter( CCSBot *me )
+{
+ me->DestroyPath();
+ me->SetBotEnemy( NULL );
+
+ // lurking death
+ if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
+ me->Walk();
+
+ //
+ // Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
+ //
+ me->SetTask( CCSBot::SEEK_AND_DESTROY );
+ me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine what we should do next
+ */
+void IdleState::OnUpdate( CCSBot *me )
+{
+ // all other states assume GetLastKnownArea() is valid, ensure that it is
+ if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
+ return;
+
+ // zombies never leave the Idle state
+ if (cv_bot_zombie.GetBool())
+ {
+ me->ResetStuckMonitor();
+ return;
+ }
+
+ // if we are in the early "safe" time, grab a knife or grenade
+ if (me->IsSafe())
+ {
+ // if we have a grenade, use it
+ if (!me->EquipGrenade())
+ {
+ // high-skill bots run with the knife, unless using the Scout (which moves faster)
+ if (me->GetProfile()->GetSkill() > 0.33f && !me->IsUsing( WEAPON_SCOUT ))
+ {
+ me->EquipKnife();
+ }
+ }
+ }
+
+ // if round is over, hunt
+ if (me->GetGameState()->IsRoundOver())
+ {
+ // if we are escorting hostages, try to get to the rescue zone
+ if (me->GetHostageEscortCount())
+ {
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
+ const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
+
+ if (zonePos)
+ {
+ me->SetTask( CCSBot::RESCUE_HOSTAGES );
+ me->Run();
+ me->SetDisposition( CCSBot::SELF_DEFENSE );
+ me->MoveTo( *zonePos, FASTEST_ROUTE );
+ me->PrintIfWatched( "Trying to rescue hostages at the end of the round\n" );
+ return;
+ }
+ }
+
+ me->Hunt();
+ return;
+ }
+
+ const float defenseSniperCampChance = 75.0f;
+ const float offenseSniperCampChance = 10.0f;
+
+ // if we were following someone, continue following them
+ if (me->IsFollowing())
+ {
+ me->ContinueFollowing();
+ return;
+ }
+
+ //
+ // Scenario logic
+ //
+ switch (TheCSBots()->GetScenario())
+ {
+ //======================================================================================================
+ case CCSBotManager::SCENARIO_DEFUSE_BOMB:
+ {
+ // if this is a bomb game and we have the bomb, go plant it
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ if (me->GetGameState()->IsBombPlanted())
+ {
+ if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
+ {
+ // T's always know where the bomb is - go defend it
+ const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
+ if (zone)
+ {
+ me->SetTask( CCSBot::GUARD_TICKING_BOMB );
+
+ Place place = TheNavMesh->GetPlace( zone->m_center );
+ if (place != UNDEFINED_PLACE)
+ {
+ // pick a random hiding spot in this place
+ const Vector *spot = FindRandomHidingSpot( me, place, me->IsSniper() );
+ if (spot)
+ {
+ me->Hide( *spot );
+ return;
+ }
+ }
+
+ // hide nearby
+ me->Hide( TheNavMesh->GetNearestNavArea( zone->m_center ) );
+ return;
+ }
+ }
+ else
+ {
+ // ask our teammates where the bomb is
+ me->GetChatter()->RequestBombLocation();
+
+ // we dont know where the bomb is - we must search the bombsites
+ int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
+
+ // move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
+ const Vector *pos = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
+ if (pos)
+ {
+ me->SetTask( CCSBot::FIND_TICKING_BOMB );
+ me->MoveTo( *pos );
+ return;
+ }
+ }
+ }
+ else if (me->HasC4())
+ {
+ // if we're at a bomb site, plant the bomb
+ if (me->IsAtBombsite())
+ {
+ // plant it
+ me->SetTask( CCSBot::PLANT_BOMB );
+ me->PlantBomb();
+
+ // radio to the team
+ me->GetChatter()->PlantingTheBomb( me->GetPlace() );
+
+ return;
+ }
+ else if (TheCSBots()->IsTimeToPlantBomb())
+ {
+ // move to the closest bomb site
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
+ if (zone)
+ {
+ // pick a random spot within the bomb zone
+ const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
+ if (pos)
+ {
+ // move to bombsite
+ me->SetTask( CCSBot::PLANT_BOMB );
+ me->Run();
+ me->MoveTo( *pos );
+
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ // at the start of the round, we may decide to defend "initial encounter" areas
+ // where we will first meet the enemy rush
+ if (me->IsSafe())
+ {
+ float defendRushChance = -17.0f * (me->GetMorale() - 2);
+
+ if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
+ {
+ if (me->MoveToInitialEncounter())
+ {
+ me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
+ me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+
+ // small chance of sniper camping on offense, if we aren't carrying the bomb
+ if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->PrintIfWatched( "Sniping!\n" );
+ return;
+ }
+
+ // if the bomb is loose (on the ground), go get it
+ if (me->NoticeLooseBomb())
+ {
+ me->FetchBomb();
+ return;
+ }
+
+ // if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
+ if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
+ {
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+
+ if (bombPos)
+ {
+ me->SetTask( CCSBot::GUARD_TICKING_BOMB );
+ me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
+ return;
+ }
+ }
+ }
+ }
+ else // CT ------------------------------------------------------------------------------------------
+ {
+ if (me->GetGameState()->IsBombPlanted())
+ {
+ // if the bomb has been planted, attempt to defuse it
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+ if (bombPos)
+ {
+ // if someone is defusing the bomb, guard them
+ if (TheCSBots()->GetBombDefuser())
+ {
+ if (!me->IsRogue())
+ {
+ me->SetTask( CCSBot::GUARD_BOMB_DEFUSER );
+ me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
+ return;
+ }
+ }
+ else if (me->IsDoingScenario())
+ {
+ // move to the bomb and defuse it
+ me->SetTask( CCSBot::DEFUSE_BOMB );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->MoveTo( *bombPos );
+ return;
+ }
+ else
+ {
+ // we're not allowed to defuse, guard the bomb zone
+ me->SetTask( CCSBot::GUARD_BOMB_ZONE );
+ me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
+ {
+ // we know which bombsite, but not exactly where the bomb is, go there
+ const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
+ if (zone)
+ {
+ if (me->IsDoingScenario())
+ {
+ me->SetTask( CCSBot::DEFUSE_BOMB );
+ me->MoveTo( zone->m_center );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ else
+ {
+ // we're not allowed to defuse, guard the bomb zone
+ me->SetTask( CCSBot::GUARD_BOMB_ZONE );
+ me->Hide( TheNavMesh->GetNavArea( zone->m_center ) );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+ else
+ {
+ // we dont know where the bomb is - we must search the bombsites
+
+ // find closest un-cleared bombsite
+ const CCSBotManager::Zone *zone = NULL;
+ float travelDistance = 9999999.9f;
+
+ for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
+ {
+ if (TheCSBots()->GetZone(z)->m_areaCount == 0)
+ continue;
+
+ // don't check bombsites that have been cleared
+ if (me->GetGameState()->IsBombsiteClear( z ))
+ continue;
+
+ // just use the first overlapping nav area as a reasonable approximation
+ ShortestPathCost cost = ShortestPathCost();
+ float dist = NavAreaTravelDistance( me->GetLastKnownArea(),
+ TheNavMesh->GetNearestNavArea( TheCSBots()->GetZone(z)->m_center ),
+ cost );
+
+ if (dist >= 0.0f && dist < travelDistance)
+ {
+ zone = TheCSBots()->GetZone(z);
+ travelDistance = dist;
+ }
+ }
+
+
+ if (zone)
+ {
+ const float farAwayRange = 2000.0f;
+ if (travelDistance > farAwayRange)
+ {
+ zone = NULL;
+ }
+ }
+
+ // if closest bombsite is "far away", pick one at random
+ if (zone == NULL)
+ {
+ int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
+ zone = TheCSBots()->GetZone( zoneIndex );
+ }
+
+ // move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
+ if (zone)
+ {
+ const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
+ if (pos)
+ {
+ me->SetTask( CCSBot::FIND_TICKING_BOMB );
+ me->MoveTo( *pos );
+ return;
+ }
+ }
+ }
+ AssertMsg( 0, "A CT bot doesn't know what to do while the bomb is planted!\n" );
+ }
+
+
+ // if we have a sniper rifle, we like to camp, whether rogue or not
+ if (me->IsSniper() && !me->IsSafe())
+ {
+ if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
+ {
+ CNavArea *snipingArea = NULL;
+
+ // if the bomb is loose, snipe near it
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+ if (me->GetGameState()->IsLooseBombLocationKnown() && bombPos)
+ {
+ snipingArea = TheNavMesh->GetNearestNavArea( *bombPos );
+ me->PrintIfWatched( "Sniping near loose bomb\n" );
+ }
+ else
+ {
+ // snipe bomb zone(s)
+ const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
+ if (zone)
+ {
+ snipingArea = TheCSBots()->GetRandomAreaInZone( zone );
+ me->PrintIfWatched( "Sniping near bombsite\n" );
+ }
+ }
+
+ if (snipingArea)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( snipingArea, -1.0, sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+
+ // rogues just hunt, unless they want to snipe
+ // if the whole team has decided to rush, hunt
+ // if we know the bomb is dropped, hunt for enemies and the loose bomb
+ if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
+ {
+ me->Hunt();
+ return;
+ }
+
+ // the lower our morale gets, the more we want to camp the bomb zone(s)
+ // only decide to camp at the start of the round, or if we haven't seen anything for a long time
+ if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
+ {
+ float guardBombsiteChance = -34.0f * me->GetMorale();
+
+ if (RandomFloat( 0.0f, 100.0f ) < guardBombsiteChance)
+ {
+ float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
+
+ // guard bomb zone(s)
+ const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
+ if (zone)
+ {
+ CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
+ if (area)
+ {
+ me->PrintIfWatched( "I'm guarding a bombsite\n" );
+ me->GetChatter()->GuardingBombsite( area->GetPlace() );
+ me->SetTask( CCSBot::GUARD_BOMB_ZONE );
+ me->Hide( area, -1.0, guardRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+
+ // at the start of the round, we may decide to defend "initial encounter" areas
+ // where we will first meet the enemy rush
+ if (me->IsSafe())
+ {
+ float defendRushChance = -17.0f * (me->GetMorale() - 2);
+
+ if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
+ {
+ if (me->MoveToInitialEncounter())
+ {
+ me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
+ me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ //======================================================================================================
+ case CCSBotManager::SCENARIO_ESCORT_VIP:
+ {
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ // if we have a sniper rifle, we like to camp, whether rogue or not
+ if (me->IsSniper())
+ {
+ if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
+ {
+ // snipe escape zone(s)
+ const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
+ if (zone)
+ {
+ CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
+ if (area)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( area, -1.0, sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->PrintIfWatched( "Sniping near escape zone\n" );
+ return;
+ }
+ }
+ }
+ }
+
+ // rogues just hunt, unless they want to snipe
+ // if the whole team has decided to rush, hunt
+ if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
+ break;
+
+ // the lower our morale gets, the more we want to camp the escape zone(s)
+ float guardEscapeZoneChance = -34.0f * me->GetMorale();
+
+ if (RandomFloat( 0.0f, 100.0f ) < guardEscapeZoneChance)
+ {
+ // guard escape zone(s)
+ const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
+ if (zone)
+ {
+ CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
+ if (area)
+ {
+ // guard the escape zone - stay closer if our morale is low
+ me->SetTask( CCSBot::GUARD_VIP_ESCAPE_ZONE );
+ me->PrintIfWatched( "I'm guarding an escape zone\n" );
+
+ float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
+ me->Hide( area, -1.0, escapeGuardRange );
+
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+ }
+ else // CT
+ {
+ if (me->m_bIsVIP)
+ {
+ // if early in round, pick a random zone, otherwise pick closest zone
+ const float earlyTime = 20.0f;
+ const CCSBotManager::Zone *zone = NULL;
+
+ if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
+ {
+ // pick random zone
+ zone = TheCSBots()->GetRandomZone();
+ }
+ else
+ {
+ // pick closest zone
+ zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
+ }
+
+ if (zone)
+ {
+ // pick a random spot within the escape zone
+ const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
+ if (pos)
+ {
+ // move to escape zone
+ me->SetTask( CCSBot::VIP_ESCAPE );
+ me->Run();
+ me->MoveTo( *pos );
+
+ // tell team to follow
+ const float repeatTime = 30.0f;
+ if (me->GetFriendsRemaining() &&
+ TheCSBots()->GetRadioMessageInterval( RADIO_FOLLOW_ME, me->GetTeamNumber() ) > repeatTime)
+ me->SendRadioMessage( RADIO_FOLLOW_ME );
+ return;
+ }
+ }
+ }
+ else
+ {
+ // small chance of sniper camping on offense, if we aren't VIP
+ if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->PrintIfWatched( "Sniping!\n" );
+ return;
+ }
+ }
+ }
+ break;
+ }
+
+ //======================================================================================================
+ case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
+ {
+ if (me->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ bool campHostages;
+
+ // if we are in early game, camp the hostages
+ if (me->IsSafe())
+ {
+ campHostages = true;
+ }
+ else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
+ {
+ campHostages = false;
+ }
+ else
+ {
+ // later in the game, camp either hostages or escape zone
+ const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime())/120.0f;
+
+ campHostages = (RandomFloat( 0, 100 ) > campZoneChance) ? true : false;
+ }
+
+
+ // if we have a sniper rifle, we like to camp, whether rogue or not
+ if (me->IsSniper())
+ {
+ // the at start of the round, snipe the initial rush
+ if (me->IsSafe())
+ {
+ if (me->MoveToInitialEncounter())
+ {
+ me->PrintIfWatched( "I'm sniping an initial encounter area\n" );
+ me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+
+ if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
+ {
+ const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
+ if (hostagePos && campHostages)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->PrintIfWatched( "Sniping near hostages\n" );
+ me->Hide( TheNavMesh->GetNearestNavArea( *hostagePos ), -1.0, sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ else
+ {
+ // camp the escape zone(s)
+ if (me->GuardRandomZone( sniperHideRange ))
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->PrintIfWatched( "Sniping near a rescue zone\n" );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+ }
+
+ // if safe time is up, and we stumble across a hostage, guard it
+ if (!me->IsSafe() && !me->IsRogue())
+ {
+ CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
+ if (hostage)
+ {
+ // we see a free hostage, guard it
+ CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
+ if (area)
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGES );
+ me->Hide( area );
+ me->PrintIfWatched( "I'm guarding hostages I found\n" );
+ // don't chatter here - he'll tell us when he's in his hiding spot
+ return;
+ }
+ }
+ }
+
+
+ // decide if we want to hunt, or guard
+ const float huntChance = 70.0f + 25.0f * me->GetMorale();
+
+ // rogues just hunt, unless they want to snipe
+ // if the whole team has decided to rush, hunt
+ if (me->GetFriendsRemaining())
+ {
+ if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RandomFloat( 0, 100 ) < huntChance)
+ {
+ me->Hunt();
+ return;
+ }
+ }
+
+ // at the start of the round, we may decide to defend "initial encounter" areas
+ // where we will first meet the enemy rush
+ if (me->IsSafe())
+ {
+ float defendRushChance = -17.0f * (me->GetMorale() - 2);
+
+ if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
+ {
+ if (me->MoveToInitialEncounter())
+ {
+ me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
+ me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+
+
+ // decide whether to camp the hostages or the escape zones
+ const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
+ if (hostagePos && campHostages)
+ {
+ CNavArea *area = TheNavMesh->GetNearestNavArea( *hostagePos );
+ if (area)
+ {
+ // guard the hostages - stay closer to hostages if our morale is low
+ me->SetTask( CCSBot::GUARD_HOSTAGES );
+ me->PrintIfWatched( "I'm guarding hostages\n" );
+
+ float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3); // 2000
+ me->Hide( area, -1.0, hostageGuardRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+
+ if (RandomFloat( 0, 100 ) < 50)
+ me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
+
+ return;
+ }
+ }
+
+ // guard rescue zone(s)
+ if (me->GuardRandomZone())
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
+ me->PrintIfWatched( "I'm guarding a rescue zone\n" );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
+ return;
+ }
+ }
+ else // CT ---------------------------------------------------------------------------------
+ {
+ // only decide to do something else if we aren't already rescuing hostages
+ if (!me->GetHostageEscortCount())
+ {
+ // small chance of sniper camping on offense
+ if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->PrintIfWatched( "Sniping!\n" );
+ return;
+ }
+
+ if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
+ {
+ // rogues just hunt, unless all friends are dead
+ // if we have friends left, we might go hunting instead of hostage rescuing
+ const float huntChance = 33.3f;
+ if (me->IsRogue() || RandomFloat( 0.0f, 100.0f ) < huntChance)
+ {
+ me->Hunt();
+ return;
+ }
+ }
+ }
+
+ // at the start of the round, we may decide to defend "initial encounter" areas
+ // where we will first meet the enemy rush
+ if (me->IsSafe())
+ {
+ float defendRushChance = -17.0f * (me->GetMorale() - 2);
+
+ if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
+ {
+ if (me->MoveToInitialEncounter())
+ {
+ me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
+ me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ return;
+ }
+ }
+ }
+
+ // look for free hostages - CT's have radar so they know where hostages are at all times
+ CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
+
+ // if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
+ if (!me->IsDoingScenario())
+ {
+ if (hostage)
+ {
+ CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
+ if (area)
+ {
+ me->SetTask( CCSBot::GUARD_HOSTAGES );
+ me->Hide( area );
+ me->PrintIfWatched( "I'm securing the hostages for a human to rescue\n" );
+ return;
+ }
+ }
+
+ me->Hunt();
+ return;
+ }
+
+
+ bool fetchHostages = false;
+ bool rescueHostages = false;
+ const CCSBotManager::Zone *zone = NULL;
+ me->SetGoalEntity( NULL );
+
+ // if we are escorting hostages, determine where to take them
+ if (me->GetHostageEscortCount())
+ zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
+
+ // if we are escorting hostages and there are more hostages to rescue,
+ // determine whether it's faster to rescue the ones we have, or go get the remaining ones
+ if (hostage)
+ {
+ Vector hostageOrigin = GetCentroid( hostage );
+
+ if (zone)
+ {
+ PathCost cost( me, FASTEST_ROUTE );
+ float toZone = NavAreaTravelDistance( me->GetLastKnownArea(), zone->m_area[0], cost );
+ float toHostage = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) ), cost );
+
+ if (toHostage < 0.0f)
+ {
+ rescueHostages = true;
+ }
+ else
+ {
+ if (toZone < toHostage)
+ rescueHostages = true;
+ else
+ fetchHostages = true;
+ }
+ }
+ else
+ {
+ fetchHostages = true;
+ }
+ }
+ else if (zone)
+ {
+ rescueHostages = true;
+ }
+
+
+ if (fetchHostages)
+ {
+ // go get hostages
+ me->SetTask( CCSBot::COLLECT_HOSTAGES );
+ me->Run();
+ me->SetGoalEntity( hostage );
+ me->ResetWaitForHostagePatience();
+
+ // if we already have some hostages, move to the others by the quickest route
+ RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
+ me->MoveTo( GetCentroid( hostage ), route );
+
+ me->PrintIfWatched( "I'm collecting hostages\n" );
+ return;
+ }
+
+ const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
+ if (rescueHostages && zonePos)
+ {
+ me->SetTask( CCSBot::RESCUE_HOSTAGES );
+ me->Run();
+ me->SetDisposition( CCSBot::SELF_DEFENSE );
+ me->MoveTo( *zonePos, FASTEST_ROUTE );
+ me->PrintIfWatched( "I'm rescuing hostages\n" );
+ me->GetChatter()->EscortingHostages();
+ return;
+ }
+ }
+ break;
+ }
+
+ default: // deathmatch
+ {
+ // sniping check
+ if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
+ {
+ me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
+ me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
+ me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ me->PrintIfWatched( "Sniping!\n" );
+ return;
+ }
+ break;
+ }
+ }
+
+ // if we have nothing special to do, go hunting for enemies
+ me->Hunt();
+}
+
diff --git a/game/server/cstrike/bot/states/cs_bot_investigate_noise.cpp b/game/server/cstrike/bot/states/cs_bot_investigate_noise.cpp
new file mode 100644
index 0000000..9aa3dd0
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_investigate_noise.cpp
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move towards currently heard noise
+ */
+void InvestigateNoiseState::AttendCurrentNoise( CCSBot *me )
+{
+ if (!me->IsNoiseHeard() && me->GetNoisePosition())
+ return;
+
+ // remember where the noise we heard was
+ m_checkNoisePosition = *me->GetNoisePosition();
+
+ // tell our teammates (unless the noise is obvious, like gunfire)
+ if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
+ me->GetChatter()->HeardNoise( *me->GetNoisePosition() );
+
+ // figure out how to get to the noise
+ me->PrintIfWatched( "Attending to noise...\n" );
+ me->ComputePath( m_checkNoisePosition, FASTEST_ROUTE );
+
+ const float minAttendTime = 3.0f;
+ const float maxAttendTime = 10.0f;
+ m_minTimer.Start( RandomFloat( minAttendTime, maxAttendTime ) );
+
+ // consume the noise
+ me->ForgetNoise();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void InvestigateNoiseState::OnEnter( CCSBot *me )
+{
+ AttendCurrentNoise( me );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * @todo Use TravelDistance instead of distance...
+ */
+void InvestigateNoiseState::OnUpdate( CCSBot *me )
+{
+ Vector myOrigin = GetCentroid( me );
+
+ // keep an ear out for closer noises...
+ if (m_minTimer.IsElapsed())
+ {
+ const float nearbyRange = 500.0f;
+ if (me->HeardInterestingNoise() && me->GetNoiseRange() < nearbyRange)
+ {
+ // new sound is closer
+ AttendCurrentNoise( me );
+ }
+ }
+
+
+ // if the pathfind fails, give up
+ if (!me->HasPath())
+ {
+ me->Idle();
+ return;
+ }
+
+ // look around
+ me->UpdateLookAround();
+
+ // get distance remaining on our path until we reach the source of the noise
+ float range = me->GetPathDistanceRemaining();
+
+ if (me->IsUsingKnife())
+ {
+ if (me->IsHurrying())
+ me->Run();
+ else
+ me->Walk();
+ }
+ else
+ {
+ const float closeToNoiseRange = 1500.0f;
+ if (range < closeToNoiseRange)
+ {
+ // if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
+ if ((me->GetNearbyFriendCount() == 0 || me->GetFriendsRemaining() <= 2) && !me->IsHurrying())
+ {
+ me->Walk();
+ }
+ else
+ {
+ me->Run();
+ }
+ }
+ else
+ {
+ me->Run();
+ }
+ }
+
+
+ // if we can see the noise position and we're close enough to it and looking at it,
+ // we don't need to actually move there (it's checked enough)
+ const float closeRange = 500.0f;
+ if (range < closeRange)
+ {
+ if (me->IsVisible( m_checkNoisePosition, CHECK_FOV ))
+ {
+ // can see noise position
+ me->PrintIfWatched( "Noise location is clear.\n" );
+ me->ForgetNoise();
+ me->Idle();
+ return;
+ }
+ }
+
+ // move towards noise
+ if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
+ {
+ me->Idle();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void InvestigateNoiseState::OnExit( CCSBot *me )
+{
+ // reset to run mode in case we were sneaking about
+ me->Run();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_move_to.cpp b/game/server/cstrike/bot/states/cs_bot_move_to.cpp
new file mode 100644
index 0000000..eafba40
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_move_to.cpp
@@ -0,0 +1,366 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_bot.h"
+#include "cs_gamerules.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to a potentially far away position.
+ */
+void MoveToState::OnEnter( CCSBot *me )
+{
+ if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
+ {
+ me->Walk();
+ }
+ else
+ {
+ me->Run();
+ }
+
+
+ // if we need to find the bomb, get there as quick as we can
+ RouteType route;
+ switch (me->GetTask())
+ {
+ case CCSBot::FIND_TICKING_BOMB:
+ case CCSBot::DEFUSE_BOMB:
+ case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
+ route = FASTEST_ROUTE;
+ break;
+
+ default:
+ route = SAFEST_ROUTE;
+ break;
+ }
+
+ // build path to, or nearly to, goal position
+ me->ComputePath( m_goalPosition, route );
+
+ m_radioedPlan = false;
+ m_askedForCover = false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to a potentially far away position.
+ */
+void MoveToState::OnUpdate( CCSBot *me )
+{
+ Vector myOrigin = GetCentroid( me );
+
+ // assume that we are paying attention and close enough to know our enemy died
+ if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
+ {
+ /// @todo Account for reaction time so we take some time to realized the enemy is dead
+ CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
+ if (victim == NULL || !victim->IsAlive())
+ {
+ me->PrintIfWatched( "The enemy I was chasing was killed - giving up.\n" );
+ me->Idle();
+ return;
+ }
+ }
+
+ // look around
+ me->UpdateLookAround();
+
+ //
+ // Scenario logic
+ //
+ switch (TheCSBots()->GetScenario())
+ {
+ case CCSBotManager::SCENARIO_DEFUSE_BOMB:
+ {
+ // if the bomb has been planted, find it
+ // NOTE: This task is used by both CT and T's to find the bomb
+ if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
+ {
+ if (!me->GetGameState()->IsBombPlanted())
+ {
+ // the bomb is not planted - give up this task
+ me->Idle();
+ return;
+ }
+
+ if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
+ {
+ // we know where the bomb is planted, stop searching
+ me->Idle();
+ return;
+ }
+
+ // check off bombsites that we explore or happen to stumble into
+ for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
+ {
+ // don't re-check zones
+ if (me->GetGameState()->IsBombsiteClear( z ))
+ continue;
+
+ if (TheCSBots()->GetZone(z)->m_extent.Contains( myOrigin ))
+ {
+ // note this bombsite is clear
+ me->GetGameState()->ClearBombsite( z );
+
+ if (me->GetTeamNumber() == TEAM_CT)
+ {
+ // tell teammates this bombsite is clear
+ me->GetChatter()->BombsiteClear( z );
+ }
+
+ // find another zone to check
+ me->Idle();
+
+ return;
+ }
+ }
+
+ // move to a bombsite
+ break;
+ }
+
+
+ if (me->GetTeamNumber() == TEAM_CT)
+ {
+ if (me->GetGameState()->IsBombPlanted())
+ {
+ switch( me->GetTask() )
+ {
+ case CCSBot::DEFUSE_BOMB:
+ {
+ // if we are near the bombsite and there is time left, sneak in (unless all enemies are dead)
+ if (me->GetEnemiesRemaining())
+ {
+ const float plentyOfTime = 15.0f;
+ if (TheCSBots()->GetBombTimeLeft() > plentyOfTime)
+ {
+ // get distance remaining on our path until we reach the bombsite
+ float range = me->GetPathDistanceRemaining();
+
+ const float closeRange = 1500.0f;
+ if (range < closeRange)
+ {
+ me->Walk();
+ }
+ else
+ {
+ me->Run();
+ }
+ }
+ }
+ else
+ {
+ // everyone is dead - run!
+ me->Run();
+ }
+
+ // if we are trying to defuse the bomb, and someone has started defusing, guard them instead
+ if (me->CanSeePlantedBomb() && TheCSBots()->GetBombDefuser())
+ {
+ me->GetChatter()->Say( "CoveringFriend" );
+ me->Idle();
+ return;
+ }
+
+
+ // if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
+ const Vector *bombPos = me->GetGameState()->GetBombPosition();
+ if (bombPos && !me->IsReloading())
+ {
+ const float defuseRange = 100.0f; // 50
+ if ((*bombPos - me->EyePosition()).IsLengthLessThan( defuseRange ))
+ {
+ // make sure we can see the bomb
+ if (me->IsVisible( *bombPos ))
+ {
+ me->DefuseBomb();
+ return;
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ {
+ // we need to find the bomb
+ me->Idle();
+ return;
+ }
+ }
+ }
+ }
+ else // TERRORIST
+ {
+ if (me->GetTask() == CCSBot::PLANT_BOMB )
+ {
+ if ( me->GetFriendsRemaining() )
+ {
+ // if we are about to plant, radio for cover
+ if (!m_askedForCover)
+ {
+ const float nearPlantSite = 50.0f;
+ if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
+ {
+ // radio to the team
+ me->GetChatter()->PlantingTheBomb( me->GetPlace() );
+ m_askedForCover = true;
+ }
+
+ // after we have started to move to the bombsite, tell team we're going to plant, and where
+ // don't do this if we have already radioed that we are starting to plant
+ if (!m_radioedPlan)
+ {
+ const float radioTime = 2.0f;
+ if (gpGlobals->curtime - me->GetStateTimestamp() > radioTime)
+ {
+ // radio to the team if we're more than 10 seconds (2400 units) out
+ const float nearPlantSite = 2400.0f;
+ if ( me->GetPathDistanceRemaining() >= nearPlantSite )
+ {
+ me->GetChatter()->GoingToPlantTheBomb( TheNavMesh->GetPlace( m_goalPosition ) );
+ }
+ m_radioedPlan = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ //--------------------------------------------------------------------------------------------------
+ case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
+ {
+ if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
+ {
+ //
+ // Since CT's have a radar, they can directly look at the actual hostage state
+ //
+
+ // check if someone else collected our hostage, or the hostage died or was rescued
+ CHostage *hostage = static_cast<CHostage *>( me->GetGoalEntity() );
+ if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone())
+ {
+ me->Idle();
+ return;
+ }
+
+ Vector hostageOrigin = GetCentroid( hostage );
+
+ // if our hostage has moved, repath
+ const float repathToleranceSq = 75.0f * 75.0f;
+ float error = (hostageOrigin - m_goalPosition).LengthSqr();
+ if (error > repathToleranceSq)
+ {
+ m_goalPosition = hostageOrigin;
+ me->ComputePath( m_goalPosition, SAFEST_ROUTE );
+ }
+
+ /// @todo Generalize ladder priorities over other tasks
+ if (!me->IsUsingLadder())
+ {
+ Vector pos = hostage->EyePosition();
+ Vector to = pos - me->EyePosition(); // "Use" checks from eye position, so we should too
+
+ // look at the hostage as we approach
+ const float watchHostageRange = 100.0f;
+ if (to.IsLengthLessThan( watchHostageRange ))
+ {
+ me->SetLookAt( "Hostage", pos, PRIORITY_LOW, 0.5f );
+
+ // randomly move just a bit to avoid infinite use loops from bad hostage placement
+ NavRelativeDirType dir = (NavRelativeDirType)RandomInt( 0, 3 );
+ switch( dir )
+ {
+ case LEFT: me->StrafeLeft(); break;
+ case RIGHT: me->StrafeRight(); break;
+ case FORWARD: me->MoveForward(); break;
+ case BACKWARD: me->MoveBackward(); break;
+ }
+
+ // check if we are close enough to the hostage to talk to him
+ const float useRange = PLAYER_USE_RADIUS - 10.0f; // shave off a fudge factor to make sure we're within range
+ if (to.IsLengthLessThan( useRange ))
+ {
+ me->UseEntity( me->GetGoalEntity() );
+ return;
+ }
+ }
+ }
+ }
+ else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
+ {
+ // periodically check if we lost all our hostages
+ if (me->GetHostageEscortCount() == 0)
+ {
+ // lost our hostages - go get 'em
+ me->Idle();
+ return;
+ }
+ }
+
+ break;
+ }
+ }
+
+
+ if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
+ {
+ // reached destination
+ switch( me->GetTask() )
+ {
+ case CCSBot::PLANT_BOMB:
+ // if we are at bombsite with the bomb, plant it
+ if (me->IsAtBombsite() && me->HasC4())
+ {
+ me->PlantBomb();
+ return;
+ }
+ break;
+
+ case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
+ {
+ CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
+ if (victim && victim->IsAlive())
+ {
+ // if we got here and haven't re-acquired the enemy, we lost him
+ BotStatement *say = new BotStatement( me->GetChatter(), REPORT_ENEMY_LOST, 8.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "LostEnemy" ) );
+ say->SetStartTime( gpGlobals->curtime + RandomFloat( 3.0f, 5.0f ) );
+
+ me->GetChatter()->AddStatement( say );
+ }
+ break;
+ }
+ }
+
+ // default behavior when destination is reached
+ me->Idle();
+ return;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void MoveToState::OnExit( CCSBot *me )
+{
+ // reset to run in case we were walking near our goal position
+ me->Run();
+ me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
+ //me->StopAiming();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_open_door.cpp b/game/server/cstrike/bot/states/cs_bot_open_door.cpp
new file mode 100644
index 0000000..a945a6c
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_open_door.cpp
@@ -0,0 +1,94 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), April 2005
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "BasePropDoor.h"
+#include "doors.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//-------------------------------------------------------------------------------------------------
+/**
+ * Face the door and open it.
+ * NOTE: This state assumes we are standing in range of the door to be opened, with no obstructions.
+ */
+void OpenDoorState::OnEnter( CCSBot *me )
+{
+ m_isDone = false;
+ m_timeout.Start( 1.0f );
+}
+
+
+//-------------------------------------------------------------------------------------------------
+void OpenDoorState::SetDoor( CBaseEntity *door )
+{
+ CBaseDoor *funcDoor = dynamic_cast< CBaseDoor * >(door);
+ if ( funcDoor )
+ {
+ m_funcDoor = funcDoor;
+ return;
+ }
+
+ CBasePropDoor *propDoor = dynamic_cast< CBasePropDoor * >(door);
+ if ( propDoor )
+ {
+ m_propDoor = propDoor;
+ return;
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------
+void OpenDoorState::OnUpdate( CCSBot *me )
+{
+ me->ResetStuckMonitor();
+
+ // wait for door to swing open before leaving state
+ if (m_timeout.IsElapsed())
+ {
+ m_isDone = true;
+ return;
+ }
+
+ // look at the door
+ Vector pos;
+ bool isDoorMoving = false;
+ if ( m_funcDoor )
+ {
+ pos = m_funcDoor->WorldSpaceCenter();
+ isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
+ }
+ else
+ {
+ pos = m_propDoor->WorldSpaceCenter();
+ isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
+ }
+
+ me->SetLookAt( "Open door", pos, PRIORITY_HIGH );
+
+ // if we are looking at the door, "use" it and exit
+ if (me->IsLookingAtPosition( pos ))
+ {
+ me->UseEnvironment();
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------
+void OpenDoorState::OnExit( CCSBot *me )
+{
+ me->ClearLookAt();
+ me->ResetStuckMonitor();
+}
+
+
+
diff --git a/game/server/cstrike/bot/states/cs_bot_plant_bomb.cpp b/game/server/cstrike/bot/states/cs_bot_plant_bomb.cpp
new file mode 100644
index 0000000..fe45a3d
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_plant_bomb.cpp
@@ -0,0 +1,79 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Plant the bomb.
+ */
+void PlantBombState::OnEnter( CCSBot *me )
+{
+ me->Crouch();
+ me->SetDisposition( CCSBot::SELF_DEFENSE );
+
+ // look at the floor
+// Vector down( myOrigin.x, myOrigin.y, -1000.0f );
+
+ float yaw = me->EyeAngles().y;
+ Vector2D dir( BotCOS(yaw), BotSIN(yaw) );
+ Vector myOrigin = GetCentroid( me );
+
+ Vector down( myOrigin.x + 10.0f * dir.x, myOrigin.y + 10.0f * dir.y, me->GetFeetZ() );
+ me->SetLookAt( "Plant bomb on floor", down, PRIORITY_HIGH );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Plant the bomb.
+ */
+void PlantBombState::OnUpdate( CCSBot *me )
+{
+ CBaseCombatWeapon *gun = me->GetActiveWeapon();
+ bool holdingC4 = false;
+ if (gun)
+ {
+ if (FStrEq( gun->GetClassname(), "weapon_c4" ))
+ holdingC4 = true;
+ }
+
+ // if we aren't holding the C4, grab it, otherwise plant it
+ if (holdingC4)
+ me->PrimaryAttack();
+ else
+ me->SelectItem( "weapon_c4" );
+
+ // if we no longer have the C4, we've successfully planted
+ if (!me->HasC4())
+ {
+ // move to a hiding spot and watch the bomb
+ me->SetTask( CCSBot::GUARD_TICKING_BOMB );
+ me->Hide();
+ }
+
+ // if we time out, it's because we slipped into a non-plantable area
+ const float timeout = 5.0f;
+ if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
+ me->Idle();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void PlantBombState::OnExit( CCSBot *me )
+{
+ // equip our rifle (in case we were interrupted while holding C4)
+ me->EquipBestWeapon();
+ me->StandUp();
+ me->ResetStuckMonitor();
+ me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
+ me->ClearLookAt();
+}
diff --git a/game/server/cstrike/bot/states/cs_bot_use_entity.cpp b/game/server/cstrike/bot/states/cs_bot_use_entity.cpp
new file mode 100644
index 0000000..591b364
--- /dev/null
+++ b/game/server/cstrike/bot/states/cs_bot_use_entity.cpp
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+/**
+ * Face the entity and "use" it
+ * NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
+ */
+void UseEntityState::OnEnter( CCSBot *me )
+{
+}
+
+void UseEntityState::OnUpdate( CCSBot *me )
+{
+ // in the very rare situation where two or more bots "used" a hostage at the same time,
+ // one bot will fail and needs to time out of this state
+ const float useTimeout = 5.0f;
+ if (me->GetStateTimestamp() - gpGlobals->curtime > useTimeout)
+ {
+ me->Idle();
+ return;
+ }
+
+ // look at the entity
+ Vector pos = m_entity->EyePosition();
+ me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
+
+ // if we are looking at the entity, "use" it and exit
+ if (me->IsLookingAtPosition( pos ))
+ {
+ if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
+ me->GetTeamNumber() == TEAM_CT &&
+ me->GetTask() == CCSBot::COLLECT_HOSTAGES)
+ {
+ // we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
+ me->IncreaseHostageEscortCount();
+ }
+
+ me->UseEnvironment();
+ me->Idle();
+ }
+}
+
+void UseEntityState::OnExit( CCSBot *me )
+{
+ me->ClearLookAt();
+ me->ResetStuckMonitor();
+}
+
+
+