summaryrefslogtreecommitdiff
path: root/game/server/cstrike
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike')
-rw-r--r--game/server/cstrike/bot/cs_bot.cpp1117
-rw-r--r--game/server/cstrike/bot/cs_bot.h2004
-rw-r--r--game/server/cstrike/bot/cs_bot_chatter.cpp2582
-rw-r--r--game/server/cstrike/bot/cs_bot_chatter.h656
-rw-r--r--game/server/cstrike/bot/cs_bot_event.cpp427
-rw-r--r--game/server/cstrike/bot/cs_bot_event_bomb.cpp158
-rw-r--r--game/server/cstrike/bot/cs_bot_event_player.cpp227
-rw-r--r--game/server/cstrike/bot/cs_bot_event_weapon.cpp167
-rw-r--r--game/server/cstrike/bot/cs_bot_init.cpp359
-rw-r--r--game/server/cstrike/bot/cs_bot_listen.cpp230
-rw-r--r--game/server/cstrike/bot/cs_bot_manager.cpp2390
-rw-r--r--game/server/cstrike/bot/cs_bot_manager.h400
-rw-r--r--game/server/cstrike/bot/cs_bot_nav.cpp963
-rw-r--r--game/server/cstrike/bot/cs_bot_pathfind.cpp2075
-rw-r--r--game/server/cstrike/bot/cs_bot_radio.cpp346
-rw-r--r--game/server/cstrike/bot/cs_bot_statemachine.cpp693
-rw-r--r--game/server/cstrike/bot/cs_bot_update.cpp1211
-rw-r--r--game/server/cstrike/bot/cs_bot_vision.cpp1834
-rw-r--r--game/server/cstrike/bot/cs_bot_weapon.cpp1363
-rw-r--r--game/server/cstrike/bot/cs_bot_weapon_id.cpp22
-rw-r--r--game/server/cstrike/bot/cs_gamestate.cpp767
-rw-r--r--game/server/cstrike/bot/cs_gamestate.h151
-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
-rw-r--r--game/server/cstrike/cs_autobuy.cpp49
-rw-r--r--game/server/cstrike/cs_autobuy.h53
-rw-r--r--game/server/cstrike/cs_bot_temp.cpp554
-rw-r--r--game/server/cstrike/cs_bot_temp.h17
-rw-r--r--game/server/cstrike/cs_client.cpp205
-rw-r--r--game/server/cstrike/cs_client.h19
-rw-r--r--game/server/cstrike/cs_eventlog.cpp321
-rw-r--r--game/server/cstrike/cs_gameinterface.cpp40
-rw-r--r--game/server/cstrike/cs_gameinterface.h14
-rw-r--r--game/server/cstrike/cs_gamestats.cpp1721
-rw-r--r--game/server/cstrike/cs_gamestats.h340
-rw-r--r--game/server/cstrike/cs_hltvdirector.cpp149
-rw-r--r--game/server/cstrike/cs_nav.h73
-rw-r--r--game/server/cstrike/cs_nav_area.cpp473
-rw-r--r--game/server/cstrike/cs_nav_area.h81
-rw-r--r--game/server/cstrike/cs_nav_edit.cpp69
-rw-r--r--game/server/cstrike/cs_nav_file.cpp1365
-rw-r--r--game/server/cstrike/cs_nav_generate.cpp269
-rw-r--r--game/server/cstrike/cs_nav_mesh.cpp127
-rw-r--r--game/server/cstrike/cs_nav_mesh.h68
-rw-r--r--game/server/cstrike/cs_nav_node.cpp383
-rw-r--r--game/server/cstrike/cs_nav_node.h135
-rw-r--r--game/server/cstrike/cs_nav_path.cpp1208
-rw-r--r--game/server/cstrike/cs_nav_path.h246
-rw-r--r--game/server/cstrike/cs_nav_pathfind.h65
-rw-r--r--game/server/cstrike/cs_player.cpp8267
-rw-r--r--game/server/cstrike/cs_player.h1116
-rw-r--r--game/server/cstrike/cs_player_resource.cpp343
-rw-r--r--game/server/cstrike/cs_player_resource.h55
-rw-r--r--game/server/cstrike/cs_playermove.cpp98
-rw-r--r--game/server/cstrike/cs_team.cpp101
-rw-r--r--game/server/cstrike/cs_team.h60
-rw-r--r--game/server/cstrike/cs_vehicle_jeep.cpp1566
-rw-r--r--game/server/cstrike/func_bomb_target.cpp84
-rw-r--r--game/server/cstrike/func_bomb_target.h37
-rw-r--r--game/server/cstrike/func_buy_zone.cpp77
-rw-r--r--game/server/cstrike/func_hostage_rescue.cpp45
-rw-r--r--game/server/cstrike/funfact_cs.cpp793
-rw-r--r--game/server/cstrike/funfact_cs.h71
-rw-r--r--game/server/cstrike/funfactmgr_cs.cpp204
-rw-r--r--game/server/cstrike/funfactmgr_cs.h45
-rw-r--r--game/server/cstrike/holiday_gift.cpp128
-rw-r--r--game/server/cstrike/holiday_gift.h36
-rw-r--r--game/server/cstrike/hostage/cs_simple_hostage.cpp1345
-rw-r--r--game/server/cstrike/hostage/cs_simple_hostage.h256
-rw-r--r--game/server/cstrike/info_view_parameters.cpp18
-rw-r--r--game/server/cstrike/info_view_parameters.h24
-rw-r--r--game/server/cstrike/item_ammo.cpp147
-rw-r--r--game/server/cstrike/item_assaultsuit.cpp56
-rw-r--r--game/server/cstrike/item_defuser.cpp107
-rw-r--r--game/server/cstrike/item_kevlar.cpp59
-rw-r--r--game/server/cstrike/item_nvgs.cpp59
-rw-r--r--game/server/cstrike/mapinfo.cpp76
-rw-r--r--game/server/cstrike/mapinfo.h42
-rw-r--r--game/server/cstrike/point_surroundtest.cpp71
-rw-r--r--game/server/cstrike/smokegrenade_projectile.cpp189
-rw-r--r--game/server/cstrike/smokegrenade_projectile.h53
-rw-r--r--game/server/cstrike/te_radioicon.cpp77
-rw-r--r--game/server/cstrike/te_shotgun_shot.cpp161
-rw-r--r--game/server/cstrike/te_shotgun_shot.h28
96 files changed, 48475 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot.cpp b/game/server/cstrike/bot/cs_bot.cpp
new file mode 100644
index 0000000..d3d8235
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot.cpp
@@ -0,0 +1,1117 @@
+//========= 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_gamerules.h"
+#include "func_breakablesurf.h"
+#include "obstacle_pushaway.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+LINK_ENTITY_TO_CLASS( cs_bot, CCSBot );
+
+BEGIN_DATADESC( CCSBot )
+
+END_DATADESC()
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the number of bots following the given player
+ */
+int GetBotFollowCount( CCSPlayer *leader )
+{
+ int count = 0;
+
+ for( int i=1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if (entity == NULL)
+ continue;
+
+ CBasePlayer *player = static_cast<CBasePlayer *>( entity );
+
+ if (!player->IsBot())
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+ if (bot && bot->GetFollowLeader() == leader)
+ ++count;
+ }
+
+ return count;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Change movement speed to walking
+ */
+void CCSBot::Walk( void )
+{
+ if (m_mustRunTimer.IsElapsed())
+ {
+ BaseClass::Walk();
+ }
+ else
+ {
+ // must run
+ Run();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if jump was started.
+ * This is extended from the base jump to disallow jumping when in a crouch area.
+ */
+bool CCSBot::Jump( bool mustJump )
+{
+ // prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
+ bool inCrouchJumpArea = (m_lastKnownArea &&
+ (m_lastKnownArea->GetAttributes() & NAV_MESH_CROUCH) &&
+ (m_lastKnownArea->GetAttributes() & NAV_MESH_JUMP));
+
+ if ( !IsUsingLadder() && IsDucked() && !inCrouchJumpArea )
+ {
+ return false;
+ }
+
+ return BaseClass::Jump( mustJump );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when injured by something
+ * NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
+ */
+int CCSBot::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CBaseEntity *attacker = info.GetInflictor();
+
+ // getting hurt makes us alert
+ BecomeAlert();
+ StopWaiting();
+
+ // if we were attacked by a teammate, rebuke
+ if (attacker->IsPlayer())
+ {
+ CCSPlayer *player = static_cast<CCSPlayer *>( attacker );
+
+ if (InSameTeam( player ) && !player->IsBot())
+ GetChatter()->FriendlyFire();
+ }
+
+ if (attacker->IsPlayer() && IsEnemy( attacker ))
+ {
+ // Track previous attacker so we don't try to panic multiple times for a shotgun blast
+ CCSPlayer *lastAttacker = m_attacker;
+ float lastAttackedTimestamp = m_attackedTimestamp;
+
+ // keep track of our last attacker
+ m_attacker = reinterpret_cast<CCSPlayer *>( attacker );
+ m_attackedTimestamp = gpGlobals->curtime;
+
+ // no longer safe
+ AdjustSafeTime();
+
+ if ( !IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp) )
+ {
+ CCSPlayer *enemy = static_cast<CCSPlayer *>( attacker );
+
+ // being hurt by an enemy we can't see causes panic
+ if (!IsVisible( enemy, CHECK_FOV ))
+ {
+ // if not attacking anything, look around to try to find attacker
+ if (!IsAttacking())
+ {
+ Panic();
+ }
+ else // we are attacking
+ {
+ if (!IsEnemyVisible())
+ {
+ // can't see our current enemy, panic to acquire new attacker
+ Panic();
+ }
+ }
+ }
+ }
+ }
+
+ // extend
+ return BaseClass::OnTakeDamage( info );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when killed
+ */
+void CCSBot::Event_Killed( const CTakeDamageInfo &info )
+{
+// PrintIfWatched( "Killed( attacker = %s )\n", STRING(pevAttacker->netname) );
+
+ GetChatter()->OnDeath();
+
+ // increase the danger where we died
+ const float deathDanger = 1.0f;
+ const float deathDangerRadius = 500.0f;
+ TheNavMesh->IncreaseDangerNearby( GetTeamNumber(), deathDanger, m_lastKnownArea, GetAbsOrigin(), deathDangerRadius );
+
+ // end voice feedback
+ m_voiceEndTimestamp = 0.0f;
+
+ // extend
+ BaseClass::Event_Killed( info );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if line segment intersects rectagular volume
+ */
+#define HI_X 0x01
+#define LO_X 0x02
+#define HI_Y 0x04
+#define LO_Y 0x08
+#define HI_Z 0x10
+#define LO_Z 0x20
+
+inline bool IsIntersectingBox( const Vector& start, const Vector& end, const Vector& boxMin, const Vector& boxMax )
+{
+ unsigned char startFlags = 0;
+ unsigned char endFlags = 0;
+
+ // classify start point
+ if (start.x < boxMin.x)
+ startFlags |= LO_X;
+ if (start.x > boxMax.x)
+ startFlags |= HI_X;
+
+ if (start.y < boxMin.y)
+ startFlags |= LO_Y;
+ if (start.y > boxMax.y)
+ startFlags |= HI_Y;
+
+ if (start.z < boxMin.z)
+ startFlags |= LO_Z;
+ if (start.z > boxMax.z)
+ startFlags |= HI_Z;
+
+ // classify end point
+ if (end.x < boxMin.x)
+ endFlags |= LO_X;
+ if (end.x > boxMax.x)
+ endFlags |= HI_X;
+
+ if (end.y < boxMin.y)
+ endFlags |= LO_Y;
+ if (end.y > boxMax.y)
+ endFlags |= HI_Y;
+
+ if (end.z < boxMin.z)
+ endFlags |= LO_Z;
+ if (end.z > boxMax.z)
+ endFlags |= HI_Z;
+
+ // trivial reject
+ if (startFlags & endFlags)
+ return false;
+
+ /// @todo Do exact line/box intersection check
+
+ return true;
+}
+
+
+extern void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue );
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When bot is touched by another entity.
+ */
+void CCSBot::Touch( CBaseEntity *other )
+{
+ // EXTEND
+ BaseClass::Touch( other );
+
+ // if we have touched a higher-priority player, make way
+ /// @todo Need to account for reaction time, etc.
+ if (other->IsPlayer())
+ {
+ // if we are defusing a bomb, don't move
+ if (IsDefusingBomb())
+ return;
+
+ // if we are on a ladder, don't move
+ if (IsUsingLadder())
+ return;
+
+ CCSPlayer *player = static_cast<CCSPlayer *>( other );
+
+ // get priority of other player
+ unsigned int otherPri = TheCSBots()->GetPlayerPriority( player );
+
+ // get our priority
+ unsigned int myPri = TheCSBots()->GetPlayerPriority( this );
+
+ // if our priority is better, don't budge
+ if (myPri < otherPri)
+ return;
+
+ // they are higher priority - make way, unless we're already making way for someone more important
+ if (m_avoid != NULL)
+ {
+ unsigned int avoidPri = TheCSBots()->GetPlayerPriority( static_cast<CBasePlayer *>( static_cast<CBaseEntity *>( m_avoid ) ) );
+ if (avoidPri < otherPri)
+ {
+ // ignore 'other' because we're already avoiding someone better
+ return;
+ }
+ }
+
+ m_avoid = other;
+ m_avoidTimestamp = gpGlobals->curtime;
+ }
+
+ // Check for breakables we're actually touching
+ // If we're not stuck or crouched, we don't care
+ if ( !m_isStuck && !IsCrouching() && !IsOnLadder() )
+ return;
+
+ // See if it's breakable
+ if ( IsBreakableEntity( other ) )
+ {
+ // it's breakable - try to shoot it.
+ SetLookAt( "Breakable", other->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are busy doing something important
+ */
+bool CCSBot::IsBusy( void ) const
+{
+ if (IsAttacking() ||
+ IsBuying() ||
+ IsDefusingBomb() ||
+ GetTask() == PLANT_BOMB ||
+ GetTask() == RESCUE_HOSTAGES ||
+ IsSniping())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::BotDeathThink( void )
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Try to join the given team
+ */
+void CCSBot::TryToJoinTeam( int team )
+{
+ m_desiredTeam = team;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Assign given player as our current enemy to attack
+ */
+void CCSBot::SetBotEnemy( CCSPlayer *enemy )
+{
+ if (m_enemy != enemy)
+ {
+ m_enemy = enemy;
+ m_currentEnemyAcquireTimestamp = gpGlobals->curtime;
+
+ PrintIfWatched( "SetBotEnemy: %s\n", (enemy) ? enemy->GetPlayerName() : "(NULL)" );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * If we are not on the navigation mesh (m_currentArea == NULL),
+ * move towards last known area.
+ * Return false if off mesh.
+ */
+bool CCSBot::StayOnNavMesh( void )
+{
+ if (m_currentArea == NULL)
+ {
+ // move back onto the area map
+
+ // if we have no lastKnownArea, we probably started off
+ // of the nav mesh - find the closest nav area and use it
+ CNavArea *goalArea;
+ if (!m_currentArea && !m_lastKnownArea)
+ {
+ goalArea = TheNavMesh->GetNearestNavArea( GetCentroid( this ) );
+ PrintIfWatched( "Started off the nav mesh - moving to closest nav area...\n" );
+ }
+ else
+ {
+ goalArea = m_lastKnownArea;
+ PrintIfWatched( "Getting out of NULL area...\n" );
+ }
+
+ if (goalArea)
+ {
+ Vector pos;
+ goalArea->GetClosestPointOnArea( GetCentroid( this ), &pos );
+
+ // move point into area
+ Vector to = pos - GetCentroid( this );
+ to.NormalizeInPlace();
+
+ const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
+ pos = pos + (stepInDist * to);
+
+ MoveTowardsPosition( pos );
+ }
+
+ // if we're stuck, try to get un-stuck
+ // do stuck movements last, so they override normal movement
+ if (m_isStuck)
+ Wiggle();
+
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we will do scenario-related tasks
+ */
+bool CCSBot::IsDoingScenario( void ) const
+{
+ // if we are deferring to humans, and there is a live human on our team, don't do the scenario
+ if (cv_bot_defer_to_human.GetBool())
+ {
+ if (UTIL_HumansOnTeam( GetTeamNumber(), IS_ALIVE ))
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we noticed the bomb on the ground or on the radar (for T's only)
+ */
+bool CCSBot::NoticeLooseBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ CBaseEntity *bomb = ctrl->GetLooseBomb();
+
+ if (bomb)
+ {
+ // T's can always see bomb on their radar
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if can see the bomb lying on the ground
+ */
+bool CCSBot::CanSeeLooseBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ CBaseEntity *bomb = ctrl->GetLooseBomb();
+
+ if (bomb)
+ {
+ if (IsVisible( bomb->GetAbsOrigin(), CHECK_FOV ))
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if can see the planted bomb
+ */
+bool CCSBot::CanSeePlantedBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ if (!GetGameState()->IsBombPlanted())
+ return false;
+
+ const Vector *bombPos = GetGameState()->GetBombPosition();
+
+ if (bombPos && IsVisible( *bombPos, CHECK_FOV ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return last enemy that hurt us
+ */
+CCSPlayer *CCSBot::GetAttacker( void ) const
+{
+ if (m_attacker && m_attacker->IsAlive())
+ return m_attacker;
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Immediately jump off of our ladder, if we're on one
+ */
+void CCSBot::GetOffLadder( void )
+{
+ if (IsUsingLadder())
+ {
+ Jump( MUST_JUMP );
+ DestroyPath();
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return time when given spot was last checked
+ */
+float CCSBot::GetHidingSpotCheckTimestamp( HidingSpot *spot ) const
+{
+ for( int i=0; i<m_checkedHidingSpotCount; ++i )
+ if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
+ return m_checkedHidingSpot[i].timestamp;
+
+ return -999999.9f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the timestamp of the given spot to now.
+ * If the spot is not in the set, overwrite the least recently checked spot.
+ */
+void CCSBot::SetHidingSpotCheckTimestamp( HidingSpot *spot )
+{
+ int leastRecent = 0;
+ float leastRecentTime = gpGlobals->curtime + 1.0f;
+
+ for( int i=0; i<m_checkedHidingSpotCount; ++i )
+ {
+ // if spot is in the set, just update its timestamp
+ if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
+ {
+ m_checkedHidingSpot[i].timestamp = gpGlobals->curtime;
+ return;
+ }
+
+ // keep track of least recent spot
+ if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
+ {
+ leastRecentTime = m_checkedHidingSpot[i].timestamp;
+ leastRecent = i;
+ }
+ }
+
+ // if there is room for more spots, append this one
+ if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
+ {
+ m_checkedHidingSpot[ m_checkedHidingSpotCount ].spot = spot;
+ m_checkedHidingSpot[ m_checkedHidingSpotCount ].timestamp = gpGlobals->curtime;
+ ++m_checkedHidingSpotCount;
+ }
+ else
+ {
+ // replace the least recent spot
+ m_checkedHidingSpot[ leastRecent ].spot = spot;
+ m_checkedHidingSpot[ leastRecent ].timestamp = gpGlobals->curtime;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Periodic check of hostage count in case we lost some
+ */
+void CCSBot::UpdateHostageEscortCount( void )
+{
+ const float updateInterval = 1.0f;
+ if (m_hostageEscortCount == 0 || gpGlobals->curtime - m_hostageEscortCountTimestamp < updateInterval)
+ return;
+
+ m_hostageEscortCountTimestamp = gpGlobals->curtime;
+
+ // recount the hostages in case we lost some
+ m_hostageEscortCount = 0;
+
+ for( int i=0; i<g_Hostages.Count(); ++i )
+ {
+ CHostage *hostage = g_Hostages[i];
+
+ // skip dead or rescued hostages
+ if ( !hostage->IsValid() || !hostage->IsAlive() )
+ continue;
+
+ // check if hostage has targeted us, and is following
+ if ( hostage->IsFollowing( this ) )
+ ++m_hostageEscortCount;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are outnumbered by enemies
+ */
+bool CCSBot::IsOutnumbered( void ) const
+{
+ return (GetNearbyFriendCount() < GetNearbyEnemyCount()-1) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return number of enemies we are outnumbered by
+ */
+int CCSBot::OutnumberedCount( void ) const
+{
+ if (IsOutnumbered())
+ return (GetNearbyEnemyCount()-1) - GetNearbyFriendCount();
+
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
+ */
+CCSPlayer *CCSBot::GetImportantEnemy( bool checkVisibility ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+ CCSPlayer *nearEnemy = NULL;
+ float nearDist = 999999999.9f;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if (entity == NULL)
+ continue;
+
+// if (FNullEnt( entity->pev ))
+// continue;
+
+// if (FStrEq( STRING( entity->pev->netname ), "" ))
+// continue;
+
+ // is it a player?
+ if (!entity->IsPlayer())
+ continue;
+
+ CCSPlayer *player = static_cast<CCSPlayer *>( entity );
+
+ // is it alive?
+ if (!player->IsAlive())
+ continue;
+
+ // skip friends
+ if (InSameTeam( player ))
+ continue;
+
+ // is it "important"
+ if (!ctrl->IsImportantPlayer( player ))
+ continue;
+
+ // is it closest?
+ Vector d = GetAbsOrigin() - player->GetAbsOrigin();
+ float distSq = d.x*d.x + d.y*d.y + d.z*d.z;
+ if (distSq < nearDist)
+ {
+ if (checkVisibility && !IsVisible( player, CHECK_FOV ))
+ continue;
+
+ nearEnemy = player;
+ nearDist = distSq;
+ }
+ }
+
+ return nearEnemy;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Sets our current disposition
+ */
+void CCSBot::SetDisposition( DispositionType disposition )
+{
+ m_disposition = disposition;
+
+ if (m_disposition != IGNORE_ENEMIES)
+ m_ignoreEnemiesTimer.Invalidate();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return our current disposition
+ */
+CCSBot::DispositionType CCSBot::GetDisposition( void ) const
+{
+ if (!m_ignoreEnemiesTimer.IsElapsed())
+ return IGNORE_ENEMIES;
+
+ return m_disposition;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Ignore enemies for a short durationy
+ */
+void CCSBot::IgnoreEnemies( float duration )
+{
+ m_ignoreEnemiesTimer.Start( duration );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Increase morale one step
+ */
+void CCSBot::IncreaseMorale( void )
+{
+ if (m_morale < EXCELLENT)
+ m_morale = static_cast<MoraleType>( m_morale + 1 );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Decrease morale one step
+ */
+void CCSBot::DecreaseMorale( void )
+{
+ if (m_morale > TERRIBLE)
+ m_morale = static_cast<MoraleType>( m_morale - 1 );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
+ * @todo Account for morale
+ */
+bool CCSBot::IsRogue( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+ if (!ctrl->AllowRogues())
+ return false;
+
+ // periodically re-evaluate our rogue status
+ if (m_rogueTimer.IsElapsed())
+ {
+ m_rogueTimer.Start( RandomFloat( 10.0f, 30.0f ) );
+
+ // our chance of going rogue is inversely proportional to our teamwork attribute
+ const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
+
+ m_isRogue = (RandomFloat( 0, 100 ) < rogueChance);
+ }
+
+ return m_isRogue;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are in a hurry
+ */
+bool CCSBot::IsHurrying( void ) const
+{
+ if (!m_hurryTimer.IsElapsed())
+ return true;
+
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ // if the bomb has been planted, we are in a hurry, CT or T (they could be defusing it!)
+ if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB && ctrl->IsBombPlanted())
+ return true;
+
+ // if we are a T and hostages are being rescued, we are in a hurry
+ if (ctrl->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
+ GetTeamNumber() == TEAM_TERRORIST &&
+ GetGameState()->AreAllHostagesBeingRescued())
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if it is the early, "safe", part of the round
+ */
+bool CCSBot::IsSafe( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetElapsedRoundTime() < m_safeTime)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if it is well past the early, "safe", part of the round
+ */
+bool CCSBot::IsWellPastSafe( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetElapsedRoundTime() > 2.0f * m_safeTime)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we were in the safe time last update, but not now
+ */
+bool CCSBot::IsEndOfSafeTime( void ) const
+{
+ return m_wasSafe && !IsSafe();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the amount of "safe time" we have left
+ */
+float CCSBot::GetSafeTimeRemaining( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ return m_safeTime - ctrl->GetElapsedRoundTime();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when enemy seen to adjust safe time for this round
+ */
+void CCSBot::AdjustSafeTime( void )
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ // if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
+ if (ctrl->GetElapsedRoundTime() < m_safeTime)
+ {
+ // since right now is not safe, adjust safe time to be a few seconds ago
+ m_safeTime = ctrl->GetElapsedRoundTime() - 2.0f;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we haven't seen an enemy for "a long time"
+ */
+bool CCSBot::HasNotSeenEnemyForLongTime( void ) const
+{
+ const float longTime = 30.0f;
+ return (GetTimeSinceLastSawEnemy() > longTime);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Pick a random zone and hide near it
+ */
+bool CCSBot::GuardRandomZone( float range )
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
+ if (zone)
+ {
+ CNavArea *rescueArea = ctrl->GetRandomAreaInZone( zone );
+ if (rescueArea)
+ {
+ Hide( rescueArea, -1.0f, range );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+class CollectRetreatSpotsFunctor
+{
+public:
+ CollectRetreatSpotsFunctor( CCSBot *me, float range )
+ {
+ m_me = me;
+ m_count = 0;
+ m_range = range;
+ }
+
+ enum { MAX_SPOTS = 256 };
+
+ bool operator() ( CNavArea *area )
+ {
+ // collect all the hiding spots in this area
+ const HidingSpotVector *pSpots = area->GetHidingSpots();
+
+ FOR_EACH_VEC( (*pSpots), it )
+ {
+ const HidingSpot *spot = (*pSpots)[ it ];
+
+ if (m_count >= MAX_SPOTS)
+ break;
+
+ // make sure hiding spot is in range
+ if (m_range > 0.0f)
+ if ((spot->GetPosition() - GetCentroid( m_me )).IsLengthGreaterThan( m_range ))
+ continue;
+
+ // if a Player is using this hiding spot, don't consider it
+ if (IsSpotOccupied( m_me, spot->GetPosition() ))
+ {
+ // player is in hiding spot
+ /// @todo Check if player is moving or sitting still
+ continue;
+ }
+
+ // don't select spot if an enemy can see it
+ if (UTIL_IsVisibleToTeam( spot->GetPosition() + Vector( 0, 0, HalfHumanHeight ), OtherTeam( m_me->GetTeamNumber() ) ))
+ continue;
+
+ // don't select spot if it is closest to an enemy
+ CBasePlayer *owner = UTIL_GetClosestPlayer( spot->GetPosition() );
+ if (owner && !m_me->InSameTeam( owner ))
+ continue;
+
+ m_spot[ m_count++ ] = &spot->GetPosition();
+ }
+
+ // if we've filled up, stop searching
+ if (m_count == MAX_SPOTS)
+ return false;
+
+ return true;
+ }
+
+ CCSBot *m_me;
+ float m_range;
+
+ const Vector *m_spot[ MAX_SPOTS ];
+ int m_count;
+};
+
+
+/**
+ * Do a breadth-first search to find a good retreat spot.
+ * Don't pick a spot that a Player is currently occupying.
+ */
+const Vector *FindNearbyRetreatSpot( CCSBot *me, float maxRange )
+{
+ CNavArea *area = me->GetLastKnownArea();
+ if (area == NULL)
+ return NULL;
+
+ // collect spots that enemies cannot see
+ CollectRetreatSpotsFunctor collector( me, maxRange );
+ SearchSurroundingAreas( area, GetCentroid( me ), collector, maxRange );
+
+ if (collector.m_count == 0)
+ return NULL;
+
+ // select a hiding spot at random
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_spot[ which ];
+}
+
+//--------------------------------------------------------------------------------------------------------------
+class FarthestHostage
+{
+public:
+ FarthestHostage( const CCSBot *me )
+ {
+ m_me = me;
+ m_farRange = -1.0f;
+ }
+
+ bool operator() ( CHostage *hostage )
+ {
+ if (hostage->IsFollowing( m_me ))
+ {
+ float range = (hostage->GetAbsOrigin() - m_me->GetAbsOrigin()).Length();
+ if (range > m_farRange)
+ {
+ m_farRange = range;
+ }
+ }
+
+ return true;
+ }
+
+ const CCSBot *m_me;
+ float m_farRange;
+};
+
+/**
+ * Return euclidean distance to farthest escorted hostage.
+ * Return -1 if no hostage is following us.
+ */
+float CCSBot::GetRangeToFarthestEscortedHostage( void ) const
+{
+ FarthestHostage away( this );
+
+ ForEachHostage( away );
+
+ return away.m_farRange;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current task
+ * NOTE: This MUST be kept in sync with the CCSBot::TaskType enum
+ */
+const char *CCSBot::GetTaskName( void ) const
+{
+ static const char *name[ NUM_TASKS ] =
+ {
+ "SEEK_AND_DESTROY",
+ "PLANT_BOMB",
+ "FIND_TICKING_BOMB",
+ "DEFUSE_BOMB",
+ "GUARD_TICKING_BOMB",
+ "GUARD_BOMB_DEFUSER",
+ "GUARD_LOOSE_BOMB",
+ "GUARD_BOMB_ZONE",
+ "GUARD_INITIAL_ENCOUNTER",
+ "ESCAPE_FROM_BOMB",
+ "HOLD_POSITION",
+ "FOLLOW",
+ "VIP_ESCAPE",
+ "GUARD_VIP_ESCAPE_ZONE",
+ "COLLECT_HOSTAGES",
+ "RESCUE_HOSTAGES",
+ "GUARD_HOSTAGES",
+ "GUARD_HOSTAGE_RESCUE_ZONE",
+ "MOVE_TO_LAST_KNOWN_ENEMY_POSITION",
+ "MOVE_TO_SNIPER_SPOT",
+ "SNIPING",
+ };
+
+ return name[ (int)GetTask() ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current disposition
+ * NOTE: This MUST be kept in sync with the CCSBot::DispositionType enum
+ */
+const char *CCSBot::GetDispositionName( void ) const
+{
+ static const char *name[ NUM_DISPOSITIONS ] =
+ {
+ "ENGAGE_AND_INVESTIGATE",
+ "OPPORTUNITY_FIRE",
+ "SELF_DEFENSE",
+ "IGNORE_ENEMIES"
+ };
+
+ return name[ (int)GetDisposition() ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current morale
+ * NOTE: This MUST be kept in sync with the CCSBot::MoraleType enum
+ */
+const char *CCSBot::GetMoraleName( void ) const
+{
+ static const char *name[ EXCELLENT - TERRIBLE + 1 ] =
+ {
+ "TERRIBLE",
+ "BAD",
+ "NEGATIVE",
+ "NEUTRAL",
+ "POSITIVE",
+ "GOOD",
+ "EXCELLENT"
+ };
+
+ return name[ (int)GetMorale() + 3 ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Fill in a CUserCmd with our data
+ */
+void CCSBot::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
+{
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+ if ( !RunMimicCommand( cmd ) )
+ {
+ // Don't walk when ducked - it's painfully slow
+ if ( m_Local.m_bDucked || m_Local.m_bDucking )
+ {
+ buttons &= ~IN_SPEED;
+ }
+
+ cmd.command_number = gpGlobals->tickcount;
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
diff --git a/game/server/cstrike/bot/cs_bot.h b/game/server/cstrike/bot/cs_bot.h
new file mode 100644
index 0000000..e9530d5
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot.h
@@ -0,0 +1,2004 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+//
+// Author: Michael S. Booth ([email protected]), 2003
+//
+// NOTE: The CS Bot code uses Doxygen-style comments. If you run Doxygen over this code, it will
+// auto-generate documentation. Visit www.doxygen.org to download the system for free.
+//
+
+#ifndef _CS_BOT_H_
+#define _CS_BOT_H_
+
+#include "bot/bot.h"
+#include "bot/cs_bot_manager.h"
+#include "bot/cs_bot_chatter.h"
+#include "cs_gamestate.h"
+#include "cs_player.h"
+#include "weapon_csbase.h"
+#include "cs_nav_pathfind.h"
+#include "cs_nav_area.h"
+
+class CBaseDoor;
+class CBasePropDoor;
+class CCSBot;
+class CPushAwayEnumerator;
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * For use with player->m_rgpPlayerItems[]
+ */
+enum InventorySlotType
+{
+ PRIMARY_WEAPON_SLOT = 1,
+ PISTOL_SLOT,
+ KNIFE_SLOT,
+ GRENADE_SLOT,
+ C4_SLOT
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The definition of a bot's behavior state. One or more finite state machines
+ * using these states implement a bot's behaviors.
+ */
+class BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot ) { } ///< when state is entered
+ virtual void OnUpdate( CCSBot *bot ) { } ///< state behavior
+ virtual void OnExit( CCSBot *bot ) { } ///< when state exited
+ virtual const char *GetName( void ) const = 0; ///< return state name
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The state is invoked when a bot has nothing to do, or has finished what it was doing.
+ * A bot never stays in this state - it is the main action selection mechanism.
+ */
+class IdleState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Idle"; }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is actively searching for an enemy.
+ */
+class HuntState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Hunt"; }
+
+ void ClearHuntArea( void ) { m_huntArea = NULL; }
+
+private:
+ CNavArea *m_huntArea; ///< "far away" area we are moving to
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot has an enemy and is attempting to kill it
+ */
+class AttackState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Attack"; }
+
+ void SetCrouchAndHold( bool crouch ) { m_crouchAndHold = crouch; }
+
+protected:
+ enum DodgeStateType
+ {
+ STEADY_ON,
+ SLIDE_LEFT,
+ SLIDE_RIGHT,
+ JUMP,
+
+ NUM_ATTACK_STATES
+ };
+ DodgeStateType m_dodgeState;
+ float m_nextDodgeStateTimestamp;
+
+ CountdownTimer m_repathTimer;
+ float m_scopeTimestamp;
+
+ bool m_haveSeenEnemy; ///< false if we haven't yet seen the enemy since we started this attack (told by a friend, etc)
+ bool m_isEnemyHidden; ///< true we if we have lost line-of-sight to our enemy
+ float m_reacquireTimestamp; ///< time when we can fire again, after losing enemy behind cover
+ float m_shieldToggleTimestamp; ///< time to toggle shield deploy state
+ bool m_shieldForceOpen; ///< if true, open up and shoot even if in danger
+
+ float m_pinnedDownTimestamp; ///< time when we'll consider ourselves "pinned down" by the enemy
+
+ bool m_crouchAndHold;
+ bool m_didAmbushCheck;
+ bool m_shouldDodge;
+ bool m_firstDodge;
+
+ bool m_isCoward; ///< if true, we'll retreat if outnumbered during this fight
+ CountdownTimer m_retreatTimer;
+
+ void StopAttacking( CCSBot *bot );
+ void Dodge( CCSBot *bot ); ///< do dodge behavior
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot has heard an enemy noise and is moving to find out what it was.
+ */
+class InvestigateNoiseState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "InvestigateNoise"; }
+
+private:
+ void AttendCurrentNoise( CCSBot *bot ); ///< move towards currently heard noise
+ Vector m_checkNoisePosition; ///< the position of the noise we're investigating
+ CountdownTimer m_minTimer; ///< minimum time we will investigate our current noise
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is buying equipment at the start of a round.
+ */
+class BuyState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Buy"; }
+
+private:
+ bool m_isInitialDelay;
+ int m_prefRetries; ///< for retrying buying preferred weapon at current index
+ int m_prefIndex; ///< where are we in our list of preferred weapons
+
+ int m_retries;
+ bool m_doneBuying;
+ bool m_buyDefuseKit;
+ bool m_buyGrenade;
+ bool m_buyShield;
+ bool m_buyPistol;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is moving to a potentially far away position in the world.
+ */
+class MoveToState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "MoveTo"; }
+ void SetGoalPosition( const Vector &pos ) { m_goalPosition = pos; }
+ void SetRouteType( RouteType route ) { m_routeType = route; }
+
+private:
+ Vector m_goalPosition; ///< goal position of move
+ RouteType m_routeType; ///< the kind of route to build
+ bool m_radioedPlan;
+ bool m_askedForCover;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a Terrorist bot is moving to pick up a dropped bomb.
+ */
+class FetchBombState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "FetchBomb"; }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a Terrorist bot is actually planting the bomb.
+ */
+class PlantBombState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "PlantBomb"; }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a CT bot is actually defusing a live bomb.
+ */
+class DefuseBombState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "DefuseBomb"; }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is hiding in a corner.
+ * NOTE: This state also includes MOVING TO that hiding spot, which may be all the way
+ * across the map!
+ */
+class HideState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Hide"; }
+
+ void SetHidingSpot( const Vector &pos ) { m_hidingSpot = pos; }
+ const Vector &GetHidingSpot( void ) const { return m_hidingSpot; }
+
+ void SetSearchArea( CNavArea *area ) { m_searchFromArea = area; }
+ void SetSearchRange( float range ) { m_range = range; }
+ void SetDuration( float time ) { m_duration = time; }
+ void SetHoldPosition( bool hold ) { m_isHoldingPosition = hold; }
+
+ bool IsAtSpot( void ) const { return m_isAtSpot; }
+
+ float GetHideTime( void ) const
+ {
+ if (IsAtSpot())
+ {
+ return m_duration - m_hideTimer.GetRemainingTime();
+ }
+
+ return 0.0f;
+ }
+
+private:
+ CNavArea *m_searchFromArea;
+ float m_range;
+
+ Vector m_hidingSpot;
+ bool m_isLookingOutward;
+ bool m_isAtSpot;
+ float m_duration;
+ CountdownTimer m_hideTimer; ///< how long to hide
+
+ bool m_isHoldingPosition;
+ float m_holdPositionTime; ///< how long to hold our position after we hear nearby enemy noise
+
+ bool m_heardEnemy; ///< set to true when we first hear an enemy
+ float m_firstHeardEnemyTime; ///< when we first heard the enemy
+
+ int m_retry; ///< counter for retrying hiding spot
+
+ Vector m_leaderAnchorPos; ///< the position of our follow leader when we decided to hide
+
+ bool m_isPaused; ///< if true, we have paused in our retreat for a moment
+ CountdownTimer m_pauseTimer; ///< for stoppping and starting our pauses while we retreat
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is attempting to flee from a bomb that is about to explode.
+ */
+class EscapeFromBombState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "EscapeFromBomb"; }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is following another player.
+ */
+class FollowState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "Follow"; }
+
+ void SetLeader( CCSPlayer *player ) { m_leader = player; }
+
+private:
+ CHandle< CCSPlayer > m_leader; ///< the player we are following
+ Vector m_lastLeaderPos; ///< where the leader was when we computed our follow path
+ bool m_isStopped;
+ float m_stoppedTimestamp;
+
+ enum LeaderMotionStateType
+ {
+ INVALID,
+ STOPPED,
+ WALKING,
+ RUNNING
+ };
+ LeaderMotionStateType m_leaderMotionState;
+ IntervalTimer m_leaderMotionStateTime;
+
+ bool m_isSneaking;
+ float m_lastSawLeaderTime;
+ CountdownTimer m_repathInterval;
+
+ IntervalTimer m_walkTime;
+ bool m_isAtWalkSpeed;
+
+ float m_waitTime;
+ CountdownTimer m_idleTimer;
+
+ void ComputeLeaderMotionState( float leaderSpeed );
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is actually using another entity (ie: facing towards it and pressing the use key)
+ */
+class UseEntityState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "UseEntity"; }
+
+ void SetEntity( CBaseEntity *entity ) { m_entity = entity; }
+
+private:
+ EHANDLE m_entity; ///< the entity we will use
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When a bot is opening a door
+ */
+class OpenDoorState : public BotState
+{
+public:
+ virtual void OnEnter( CCSBot *bot );
+ virtual void OnUpdate( CCSBot *bot );
+ virtual void OnExit( CCSBot *bot );
+ virtual const char *GetName( void ) const { return "OpenDoor"; }
+
+ void SetDoor( CBaseEntity *door );
+
+ bool IsDone( void ) const { return m_isDone; } ///< return true if behavior is done
+
+private:
+ CHandle< CBaseDoor > m_funcDoor; ///< the func_door we are opening
+ CHandle< CBasePropDoor > m_propDoor; ///< the prop_door we are opening
+ bool m_isDone;
+ CountdownTimer m_timeout;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The Counter-strike Bot
+ */
+class CCSBot : public CBot< CCSPlayer >
+{
+public:
+ DECLARE_CLASS( CCSBot, CBot< CCSPlayer > );
+ DECLARE_DATADESC();
+
+ CCSBot( void ); ///< constructor initializes all values to zero
+ virtual ~CCSBot();
+ virtual bool Initialize( const BotProfile *profile, int team ); ///< (EXTEND) prepare bot for action
+
+ virtual void Spawn( void ); ///< (EXTEND) spawn the bot into the game
+ virtual void Touch( CBaseEntity *other ); ///< (EXTEND) when touched by another entity
+
+ virtual void Upkeep( void ); ///< lightweight maintenance, invoked frequently
+ virtual void Update( void ); ///< heavyweight algorithms, invoked less often
+ virtual void BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse );
+ virtual float GetMoveSpeed( void ); ///< returns current movement speed (for walk/run)
+
+ virtual void Walk( void );
+ virtual bool Jump( bool mustJump = false ); ///< returns true if jump was started
+
+ //- behavior properties ------------------------------------------------------------------------------------------
+ float GetCombatRange( void ) const;
+ bool IsRogue( void ) const; ///< return true if we dont listen to teammates or pursue scenario goals
+ void SetRogue( bool rogue );
+ bool IsHurrying( void ) const; ///< return true if we are in a hurry
+ void Hurry( float duration ); ///< force bot to hurry
+ bool IsSafe( void ) const; ///< return true if we are in a safe region
+ bool IsWellPastSafe( void ) const; ///< return true if it is well past the early, "safe", part of the round
+ bool IsEndOfSafeTime( void ) const; ///< return true if we were in the safe time last update, but not now
+ float GetSafeTimeRemaining( void ) const; ///< return the amount of "safe time" we have left
+ float GetSafeTime( void ) const; ///< return what we think the total "safe time" for this map is
+ virtual void Blind( float holdTime, float fadeTime, float startingAlpha = 255 ); // player blinded by a flashbang
+ bool IsUnhealthy( void ) const; ///< returns true if bot is low on health
+
+ bool IsAlert( void ) const; ///< return true if bot is in heightened "alert" mode
+ void BecomeAlert( void ); ///< bot becomes "alert" for immediately nearby enemies
+
+ bool IsSneaking( void ) const; ///< return true if bot is sneaking
+ void Sneak( float duration ); ///< sneak for given duration
+
+ //- behaviors ---------------------------------------------------------------------------------------------------
+ void Idle( void );
+
+ void Hide( CNavArea *searchFromArea = NULL, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false ); ///< DEPRECATED: Use TryToHide() instead
+ #define USE_NEAREST true
+ bool TryToHide( CNavArea *searchFromArea = NULL, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false, bool useNearest = false ); ///< try to hide nearby, return false if cannot
+ void Hide( const Vector &hidingSpot, float duration = -1.0f, bool holdPosition = false ); ///< move to the given hiding place
+ bool IsHiding( void ) const; ///< returns true if bot is currently hiding
+ bool IsAtHidingSpot( void ) const; ///< return true if we are hiding and at our hiding spot
+ float GetHidingTime( void ) const; ///< return number of seconds we have been at our current hiding spot
+
+ bool MoveToInitialEncounter( void ); ///< move to a hiding spot and wait for initial encounter with enemy team (return false if no spots are available)
+
+ bool TryToRetreat( float maxRange = 1000.0f, float duration = -1.0f ); ///< retreat to a nearby hiding spot, away from enemies
+
+ void Hunt( void );
+ bool IsHunting( void ) const; ///< returns true if bot is currently hunting
+
+ void Attack( CCSPlayer *victim );
+ void FireWeaponAtEnemy( void ); ///< fire our active weapon towards our current enemy
+ void StopAttacking( void );
+ bool IsAttacking( void ) const; ///< returns true if bot is currently engaging a target
+
+ void MoveTo( const Vector &pos, RouteType route = SAFEST_ROUTE ); ///< move to potentially distant position
+ bool IsMovingTo( void ) const; ///< return true if we are in the MoveTo state
+
+ void PlantBomb( void );
+
+ void FetchBomb( void ); ///< bomb has been dropped - go get it
+ bool NoticeLooseBomb( void ) const; ///< return true if we noticed the bomb on the ground or on radar
+ bool CanSeeLooseBomb( void ) const; ///< return true if we directly see the loose bomb
+
+ void DefuseBomb( void );
+ bool IsDefusingBomb( void ) const; ///< returns true if bot is currently defusing the bomb
+ bool CanSeePlantedBomb( void ) const; ///< return true if we directly see the planted bomb
+
+ void EscapeFromBomb( void );
+ bool IsEscapingFromBomb( void ) const; ///< return true if we are escaping from the bomb
+
+ void RescueHostages( void ); ///< begin process of rescuing hostages
+
+ void UseEntity( CBaseEntity *entity ); ///< use the entity
+
+ void OpenDoor( CBaseEntity *door ); ///< open the door (assumes we are right in front of it)
+ bool IsOpeningDoor( void ) const; ///< return true if we are in the process of opening a door
+
+ void Buy( void ); ///< enter the buy state
+ bool IsBuying( void ) const;
+
+ void Panic( void ); ///< look around in panic
+ bool IsPanicking( void ) const; ///< return true if bot is panicked
+ void StopPanicking( void ); ///< end our panic
+ void UpdatePanicLookAround( void ); ///< do panic behavior
+
+ void TryToJoinTeam( int team ); ///< try to join the given team
+
+ void Follow( CCSPlayer *player ); ///< begin following given Player
+ void ContinueFollowing( void ); ///< continue following our leader after finishing what we were doing
+ void StopFollowing( void ); ///< stop following
+ bool IsFollowing( void ) const; ///< return true if we are following someone (not necessarily in the follow state)
+ CCSPlayer *GetFollowLeader( void ) const; ///< return the leader we are following
+ float GetFollowDuration( void ) const; ///< return how long we've been following our leader
+ bool CanAutoFollow( void ) const; ///< return true if we can auto-follow
+
+ bool IsNotMoving( float minDuration = 0.0f ) const; ///< return true if we are currently standing still and have been for minDuration
+
+ void AimAtEnemy( void ); ///< point our weapon towards our enemy
+ void StopAiming( void ); ///< stop aiming at enemy
+ bool IsAimingAtEnemy( void ) const; ///< returns true if we are trying to aim at an enemy
+
+ float GetStateTimestamp( void ) const; ///< get time current state was entered
+
+ bool IsDoingScenario( void ) const; ///< return true if we will do scenario-related tasks
+
+ //- scenario / gamestate -----------------------------------------------------------------------------------------
+ CSGameState *GetGameState( void ); ///< return an interface to this bot's gamestate
+ const CSGameState *GetGameState( void ) const; ///< return an interface to this bot's gamestate
+
+ bool IsAtBombsite( void ); ///< return true if we are in a bomb planting zone
+ bool GuardRandomZone( float range = 500.0f ); ///< pick a random zone and hide near it
+
+ bool IsBusy( void ) const; ///< return true if we are busy doing something important
+
+ //- high-level tasks ---------------------------------------------------------------------------------------------
+ enum TaskType
+ {
+ SEEK_AND_DESTROY,
+ PLANT_BOMB,
+ FIND_TICKING_BOMB,
+ DEFUSE_BOMB,
+ GUARD_TICKING_BOMB,
+ GUARD_BOMB_DEFUSER,
+ GUARD_LOOSE_BOMB,
+ GUARD_BOMB_ZONE,
+ GUARD_INITIAL_ENCOUNTER,
+ ESCAPE_FROM_BOMB,
+ HOLD_POSITION,
+ FOLLOW,
+ VIP_ESCAPE,
+ GUARD_VIP_ESCAPE_ZONE,
+ COLLECT_HOSTAGES,
+ RESCUE_HOSTAGES,
+ GUARD_HOSTAGES,
+ GUARD_HOSTAGE_RESCUE_ZONE,
+ MOVE_TO_LAST_KNOWN_ENEMY_POSITION,
+ MOVE_TO_SNIPER_SPOT,
+ SNIPING,
+
+ NUM_TASKS
+ };
+ void SetTask( TaskType task, CBaseEntity *entity = NULL ); ///< set our current "task"
+ TaskType GetTask( void ) const;
+ CBaseEntity *GetTaskEntity( void );
+ const char *GetTaskName( void ) const; ///< return string describing current task
+
+ //- behavior modifiers ------------------------------------------------------------------------------------------
+ enum DispositionType
+ {
+ ENGAGE_AND_INVESTIGATE, ///< engage enemies on sight and investigate enemy noises
+ OPPORTUNITY_FIRE, ///< engage enemies on sight, but only look towards enemy noises, dont investigate
+ SELF_DEFENSE, ///< only engage if fired on, or very close to enemy
+ IGNORE_ENEMIES, ///< ignore all enemies - useful for ducking around corners, running away, etc
+
+ NUM_DISPOSITIONS
+ };
+ void SetDisposition( DispositionType disposition ); ///< define how we react to enemies
+ DispositionType GetDisposition( void ) const;
+ const char *GetDispositionName( void ) const; ///< return string describing current disposition
+
+ void IgnoreEnemies( float duration ); ///< ignore enemies for a short duration
+
+ enum MoraleType
+ {
+ TERRIBLE = -3,
+ BAD = -2,
+ NEGATIVE = -1,
+ NEUTRAL = 0,
+ POSITIVE = 1,
+ GOOD = 2,
+ EXCELLENT = 3,
+ };
+ MoraleType GetMorale( void ) const;
+ const char *GetMoraleName( void ) const; ///< return string describing current morale
+ void IncreaseMorale( void );
+ void DecreaseMorale( void );
+
+ void Surprise( float duration ); ///< become "surprised" - can't attack
+ bool IsSurprised( void ) const; ///< return true if we are "surprised"
+
+
+ //- listening for noises ----------------------------------------------------------------------------------------
+ bool IsNoiseHeard( void ) const; ///< return true if we have heard a noise
+ bool HeardInterestingNoise( void ); ///< return true if we heard an enemy noise worth checking in to
+ void InvestigateNoise( void ); ///< investigate recent enemy noise
+ bool IsInvestigatingNoise( void ) const; ///< return true if we are investigating a noise
+ const Vector *GetNoisePosition( void ) const; ///< return position of last heard noise, or NULL if none heard
+ CNavArea *GetNoiseArea( void ) const; ///< return area where noise was heard
+ void ForgetNoise( void ); ///< clear the last heard noise
+ bool CanSeeNoisePosition( void ) const; ///< return true if we directly see where we think the noise came from
+ float GetNoiseRange( void ) const; ///< return approximate distance to last noise heard
+
+ bool CanHearNearbyEnemyGunfire( float range = -1.0f ) const;///< return true if we hear nearby threatening enemy gunfire within given range (-1 == infinite)
+ PriorityType GetNoisePriority( void ) const; ///< return priority of last heard noise
+
+ //- radio and chatter--------------------------------------------------------------------------------------------
+ void SendRadioMessage( RadioType event ); ///< send a radio message
+ void SpeakAudio( const char *voiceFilename, float duration, int pitch ); ///< send voice chatter
+ BotChatterInterface *GetChatter( void ); ///< return an interface to this bot's chatter system
+ bool RespondToHelpRequest( CCSPlayer *player, Place place, float maxRange = -1.0f ); ///< decide if we should move to help the player, return true if we will
+ bool IsUsingVoice() const; ///< new-style "voice" chatter gets voice feedback
+
+
+ //- enemies ------------------------------------------------------------------------------------------------------
+ // BOTPORT: GetEnemy() collides with GetEnemy() in CBaseEntity - need to use different nomenclature
+ void SetBotEnemy( CCSPlayer *enemy ); ///< set given player as our current enemy
+ CCSPlayer *GetBotEnemy( void ) const;
+ int GetNearbyEnemyCount( void ) const; ///< return max number of nearby enemies we've seen recently
+ unsigned int GetEnemyPlace( void ) const; ///< return location where we see the majority of our enemies
+ bool CanSeeBomber( void ) const; ///< return true if we can see the bomb carrier
+ CCSPlayer *GetBomber( void ) const;
+
+ int GetNearbyFriendCount( void ) const; ///< return number of nearby teammates
+ CCSPlayer *GetClosestVisibleFriend( void ) const; ///< return the closest friend that we can see
+ CCSPlayer *GetClosestVisibleHumanFriend( void ) const; ///< return the closest human friend that we can see
+
+ bool IsOutnumbered( void ) const; ///< return true if we are outnumbered by enemies
+ int OutnumberedCount( void ) const; ///< return number of enemies we are outnumbered by
+
+ #define ONLY_VISIBLE_ENEMIES true
+ CCSPlayer *GetImportantEnemy( bool checkVisibility = false ) const; ///< return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
+
+ void UpdateReactionQueue( void ); ///< update our reaction time queue
+ CCSPlayer *GetRecognizedEnemy( void ); ///< return the most dangerous threat we are "conscious" of
+ bool IsRecognizedEnemyReloading( void ); ///< return true if the enemy we are "conscious" of is reloading
+ bool IsRecognizedEnemyProtectedByShield( void ); ///< return true if the enemy we are "conscious" of is hiding behind a shield
+ float GetRangeToNearestRecognizedEnemy( void ); ///< return distance to closest enemy we are "conscious" of
+
+ CCSPlayer *GetAttacker( void ) const; ///< return last enemy that hurt us
+ float GetTimeSinceAttacked( void ) const; ///< return duration since we were last injured by an attacker
+ float GetFirstSawEnemyTimestamp( void ) const; ///< time since we saw any enemies
+ float GetLastSawEnemyTimestamp( void ) const;
+ float GetTimeSinceLastSawEnemy( void ) const;
+ float GetTimeSinceAcquiredCurrentEnemy( void ) const;
+ bool HasNotSeenEnemyForLongTime( void ) const; ///< return true if we haven't seen an enemy for "a long time"
+ const Vector &GetLastKnownEnemyPosition( void ) const;
+ bool IsEnemyVisible( void ) const; ///< is our current enemy visible
+ float GetEnemyDeathTimestamp( void ) const;
+ bool IsFriendInLineOfFire( void ); ///< return true if a friend is in our weapon's way
+ bool IsAwareOfEnemyDeath( void ) const; ///< return true if we *noticed* that our enemy died
+ int GetLastVictimID( void ) const; ///< return the ID (entindex) of the last victim we killed, or zero
+
+ bool CanSeeSniper( void ) const; ///< return true if we can see an enemy sniper
+ bool HasSeenSniperRecently( void ) const; ///< return true if we have seen a sniper recently
+
+ float GetTravelDistanceToPlayer( CCSPlayer *player ) const; ///< return shortest path travel distance to this player
+ bool DidPlayerJustFireWeapon( const CCSPlayer *player ) const; ///< return true if the given player just fired their weapon
+
+ //- navigation --------------------------------------------------------------------------------------------------
+ bool HasPath( void ) const;
+ void DestroyPath( void );
+
+ float GetFeetZ( void ) const; ///< return Z of bottom of feet
+
+ enum PathResult
+ {
+ PROGRESSING, ///< we are moving along the path
+ END_OF_PATH, ///< we reached the end of the path
+ PATH_FAILURE ///< we failed to reach the end of the path
+ };
+ #define NO_SPEED_CHANGE false
+ PathResult UpdatePathMovement( bool allowSpeedChange = true ); ///< move along our computed path - if allowSpeedChange is true, bot will walk when near goal to ensure accuracy
+
+ //bool AStarSearch( CNavArea *startArea, CNavArea *goalArea ); ///< find shortest path from startArea to goalArea - don't actually buid the path
+ bool ComputePath( const Vector &goal, RouteType route = SAFEST_ROUTE ); ///< compute path to goal position
+ bool StayOnNavMesh( void );
+ CNavArea *GetLastKnownArea( void ) const; ///< return the last area we know we were inside of
+ const Vector &GetPathEndpoint( void ) const; ///< return final position of our current path
+ float GetPathDistanceRemaining( void ) const; ///< return estimated distance left to travel along path
+ void ResetStuckMonitor( void );
+ bool IsAreaVisible( const CNavArea *area ) const; ///< is any portion of the area visible to this bot
+ const Vector &GetPathPosition( int index ) const;
+ bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ); ///< find "simple" ground height, treating current nav area as part of the floor
+ void BreakablesCheck( void );
+ void DoorCheck( void ); ///< Check for any doors along our path that need opening
+
+ virtual void PushawayTouch( CBaseEntity *pOther );
+
+ Place GetPlace( void ) const; ///< get our current radio chatter place
+
+ bool IsUsingLadder( void ) const; ///< returns true if we are in the process of negotiating a ladder
+ void GetOffLadder( void ); ///< immediately jump off of our ladder, if we're on one
+
+ void SetGoalEntity( CBaseEntity *entity );
+ CBaseEntity *GetGoalEntity( void );
+
+ bool IsNearJump( void ) const; ///< return true if nearing a jump in the path
+ float GetApproximateFallDamage( float height ) const; ///< return how much damage will will take from the given fall height
+
+ void ForceRun( float duration ); ///< force the bot to run if it moves for the given duration
+ virtual bool IsRunning( void ) const;
+
+ void Wait( float duration ); ///< wait where we are for the given duration
+ bool IsWaiting( void ) const; ///< return true if we are waiting
+ void StopWaiting( void ); ///< stop waiting
+
+ void Wiggle( void ); ///< random movement, for getting un-stuck
+
+ bool IsFriendInTheWay( const Vector &goalPos ); ///< return true if a friend is between us and the given position
+ void FeelerReflexAdjustment( Vector *goalPosition ); ///< do reflex avoidance movements if our "feelers" are touched
+
+ bool HasVisitedEnemySpawn( void ) const; ///< return true if we have visited enemy spawn at least once
+ bool IsAtEnemySpawn( void ) const; ///< return true if we are at the/an enemy spawn right now
+
+ //- looking around ----------------------------------------------------------------------------------------------
+
+ // BOTPORT: EVIL VILE HACK - why is EyePosition() not const?!?!?
+ const Vector &EyePositionConst( void ) const;
+
+ void SetLookAngles( float yaw, float pitch ); ///< set our desired look angles
+ void UpdateLookAngles( void ); ///< move actual view angles towards desired ones
+ void UpdateLookAround( bool updateNow = false ); ///< update "looking around" mechanism
+ void InhibitLookAround( float duration ); ///< block all "look at" and "looking around" behavior for given duration - just look ahead
+
+ /// @todo Clean up notion of "forward angle" and "look ahead angle"
+ void SetForwardAngle( float angle ); ///< define our forward facing
+ void SetLookAheadAngle( float angle ); ///< define default look ahead angle
+
+ /// look at the given point in space for the given duration (-1 means forever)
+ void SetLookAt( const char *desc, const Vector &pos, PriorityType pri, float duration = -1.0f, bool clearIfClose = false, float angleTolerance = 5.0f, bool attack = false );
+ void ClearLookAt( void ); ///< stop looking at a point in space and just look ahead
+ bool IsLookingAtSpot( PriorityType pri = PRIORITY_LOW ) const; ///< return true if we are looking at spot with equal or higher priority
+ bool IsViewMoving( float angleVelThreshold = 1.0f ) const; ///< returns true if bot's view angles are rotating (not still)
+ bool HasViewBeenSteady( float duration ) const; ///< how long has our view been "steady" (ie: not moving) for given duration
+
+ bool HasLookAtTarget( void ) const; ///< return true if we are in the process of looking at a target
+
+ enum VisiblePartType
+ {
+ NONE = 0x00,
+ GUT = 0x01,
+ HEAD = 0x02,
+ LEFT_SIDE = 0x04, ///< the left side of the object from our point of view (not their left side)
+ RIGHT_SIDE = 0x08, ///< the right side of the object from our point of view (not their right side)
+ FEET = 0x10
+ };
+
+ #define CHECK_FOV true
+ bool IsVisible( const Vector &pos, bool testFOV = false, const CBaseEntity *ignore = NULL ) const; ///< return true if we can see the point
+ bool IsVisible( CCSPlayer *player, bool testFOV = false, unsigned char *visParts = NULL ) const; ///< return true if we can see any part of the player
+
+ bool IsNoticable( const CCSPlayer *player, unsigned char visibleParts ) const; ///< return true if we "notice" given player
+
+ bool IsEnemyPartVisible( VisiblePartType part ) const; ///< if enemy is visible, return the part we see for our current enemy
+ const Vector &GetPartPosition( CCSPlayer *player, VisiblePartType part ) const; ///< return world space position of given part on player
+
+ float ComputeWeaponSightRange( void ); ///< return line-of-sight distance to obstacle along weapon fire ray
+
+ bool IsAnyVisibleEnemyLookingAtMe( bool testFOV = false ) const;///< return true if any enemy I have LOS to is looking directly at me
+
+ bool IsSignificantlyCloser( const CCSPlayer *testPlayer, const CCSPlayer *referencePlayer ) const; ///< return true if testPlayer is significantly closer than referencePlayer
+
+ //- approach points ---------------------------------------------------------------------------------------------
+ void ComputeApproachPoints( void ); ///< determine the set of "approach points" representing where the enemy can enter this region
+ void UpdateApproachPoints( void ); ///< recompute the approach point set if we have moved far enough to invalidate the current ones
+ void ClearApproachPoints( void );
+ void DrawApproachPoints( void ) const; ///< for debugging
+ float GetHidingSpotCheckTimestamp( HidingSpot *spot ) const; ///< return time when given spot was last checked
+ void SetHidingSpotCheckTimestamp( HidingSpot *spot ); ///< set the timestamp of the given spot to now
+
+ const CNavArea *GetInitialEncounterArea( void ) const; ///< return area where we think we will first meet the enemy
+ void SetInitialEncounterArea( const CNavArea *area );
+
+ //- weapon query and equip --------------------------------------------------------------------------------------
+ #define MUST_EQUIP true
+ void EquipBestWeapon( bool mustEquip = false ); ///< equip the best weapon we are carrying that has ammo
+ void EquipPistol( void ); ///< equip our pistol
+ void EquipKnife( void ); ///< equip the knife
+
+ #define DONT_USE_SMOKE_GRENADE true
+ bool EquipGrenade( bool noSmoke = false ); ///< equip a grenade, return false if we cant
+
+ bool IsUsingKnife( void ) const; ///< returns true if we have knife equipped
+ bool IsUsingPistol( void ) const; ///< returns true if we have pistol equipped
+ bool IsUsingGrenade( void ) const; ///< returns true if we have grenade equipped
+ bool IsUsingSniperRifle( void ) const; ///< returns true if using a "sniper" rifle
+ bool IsUsing( CSWeaponID weapon ) const; ///< returns true if using the specific weapon
+ bool IsSniper( void ) const; ///< return true if we have a sniper rifle in our inventory
+ bool IsSniping( void ) const; ///< return true if we are actively sniping (moving to sniper spot or settled in)
+ bool IsUsingShotgun( void ) const; ///< returns true if using a shotgun
+ bool IsUsingMachinegun( void ) const; ///< returns true if using the big 'ol machinegun
+ void ThrowGrenade( const Vector &target ); ///< begin the process of throwing the grenade
+ bool IsThrowingGrenade( void ) const; ///< return true if we are in the process of throwing a grenade
+ bool HasGrenade( void ) const; ///< return true if we have a grenade in our inventory
+ void AvoidEnemyGrenades( void ); ///< react to enemy grenades we see
+ bool IsAvoidingGrenade( void ) const; ///< return true if we are in the act of avoiding a grenade
+ bool DoesActiveWeaponHaveSilencer( void ) const; ///< returns true if we are using a weapon with a removable silencer
+ bool CanActiveWeaponFire( void ) const; ///< returns true if our current weapon can attack
+ CWeaponCSBase *GetActiveCSWeapon( void ) const; ///< get our current Counter-Strike weapon
+
+ void GiveWeapon( const char *weaponAlias ); ///< Debug command to give a named weapon
+
+ virtual void PrimaryAttack( void ); ///< presses the fire button, unless we're holding a pistol that can't fire yet (so we can just always call PrimaryAttack())
+
+ enum ZoomType { NO_ZOOM, LOW_ZOOM, HIGH_ZOOM };
+ ZoomType GetZoomLevel( void ); ///< return the current zoom level of our weapon
+
+ bool AdjustZoom( float range ); ///< change our zoom level to be appropriate for the given range
+ bool IsWaitingForZoom( void ) const; ///< return true if we are reacquiring after our zoom
+
+ bool IsPrimaryWeaponEmpty( void ) const; ///< return true if primary weapon doesn't exist or is totally out of ammo
+ bool IsPistolEmpty( void ) const; ///< return true if pistol doesn't exist or is totally out of ammo
+
+ int GetHostageEscortCount( void ) const; ///< return the number of hostages following me
+ void IncreaseHostageEscortCount( void );
+ float GetRangeToFarthestEscortedHostage( void ) const; ///< return euclidean distance to farthest escorted hostage
+ void ResetWaitForHostagePatience( void );
+
+ //------------------------------------------------------------------------------------
+ // Event hooks
+ //
+
+ /// invoked when injured by something (EXTEND) - returns the amount of damage inflicted
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+ /// invoked when killed (EXTEND)
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+ virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon ); ///< invoked when in contact with a CWeaponBox
+
+
+ /// invoked when event occurs in the game (some events have NULL entity)
+ void OnPlayerFootstep( IGameEvent *event );
+ void OnPlayerRadio( IGameEvent *event );
+ void OnPlayerDeath( IGameEvent *event );
+ void OnPlayerFallDamage( IGameEvent *event );
+
+ void OnBombPickedUp( IGameEvent *event );
+ void OnBombPlanted( IGameEvent *event );
+ void OnBombBeep( IGameEvent *event );
+ void OnBombDefuseBegin( IGameEvent *event );
+ void OnBombDefused( IGameEvent *event );
+ void OnBombDefuseAbort( IGameEvent *event );
+ void OnBombExploded( IGameEvent *event );
+
+ void OnRoundEnd( IGameEvent *event );
+ void OnRoundStart( IGameEvent *event );
+
+ void OnDoorMoving( IGameEvent *event );
+
+ void OnBreakProp( IGameEvent *event );
+ void OnBreakBreakable( IGameEvent *event );
+
+ void OnHostageFollows( IGameEvent *event );
+ void OnHostageRescuedAll( IGameEvent *event );
+
+ void OnWeaponFire( IGameEvent *event );
+ void OnWeaponFireOnEmpty( IGameEvent *event );
+ void OnWeaponReload( IGameEvent *event );
+ void OnWeaponZoom( IGameEvent *event );
+
+ void OnBulletImpact( IGameEvent *event );
+
+ void OnHEGrenadeDetonate( IGameEvent *event );
+ void OnFlashbangDetonate( IGameEvent *event );
+ void OnSmokeGrenadeDetonate( IGameEvent *event );
+ void OnGrenadeBounce( IGameEvent *event );
+
+ void OnNavBlocked( IGameEvent *event );
+
+ void OnEnteredNavArea( CNavArea *newArea ); ///< invoked when bot enters a nav area
+
+private:
+ #define IS_FOOTSTEP true
+ void OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep = false, const Vector *actualOrigin = NULL ); ///< Checks if the bot can hear the event
+
+private:
+ friend class CCSBotManager;
+
+ /// @todo Get rid of these
+ friend class AttackState;
+ friend class BuyState;
+
+ // BOTPORT: Remove this vile hack
+ Vector m_eyePosition;
+
+ void ResetValues( void ); ///< reset internal data to initial state
+ void BotDeathThink( void );
+
+ char m_name[64]; ///< copied from STRING(pev->netname) for debugging
+ void DebugDisplay( void ) const; ///< render bot debug info
+
+ //- behavior properties ------------------------------------------------------------------------------------------
+ float m_combatRange; ///< desired distance between us and them during gunplay
+ mutable bool m_isRogue; ///< if true, the bot is a "rogue" and listens to no-one
+ mutable CountdownTimer m_rogueTimer;
+ MoraleType m_morale; ///< our current morale, based on our win/loss history
+ bool m_diedLastRound; ///< true if we died last round
+ float m_safeTime; ///< duration at the beginning of the round where we feel "safe"
+ bool m_wasSafe; ///< true if we were in the safe time last update
+ void AdjustSafeTime( void ); ///< called when enemy seen to adjust safe time for this round
+ NavRelativeDirType m_blindMoveDir; ///< which way to move when we're blind
+ bool m_blindFire; ///< if true, fire weapon while blinded
+ CountdownTimer m_surpriseTimer; ///< when we were surprised
+
+ bool m_isFollowing; ///< true if we are following someone
+ CHandle< CCSPlayer > m_leader; ///< the ID of who we are following
+ float m_followTimestamp; ///< when we started following
+ float m_allowAutoFollowTime; ///< time when we can auto follow
+
+ CountdownTimer m_hurryTimer; ///< if valid, bot is in a hurry
+ CountdownTimer m_alertTimer; ///< if valid, bot is alert
+ CountdownTimer m_sneakTimer; ///< if valid, bot is sneaking
+ CountdownTimer m_panicTimer; ///< if valid, bot is panicking
+
+
+ // instances of each possible behavior state, to avoid dynamic memory allocation during runtime
+ IdleState m_idleState;
+ HuntState m_huntState;
+ AttackState m_attackState;
+ InvestigateNoiseState m_investigateNoiseState;
+ BuyState m_buyState;
+ MoveToState m_moveToState;
+ FetchBombState m_fetchBombState;
+ PlantBombState m_plantBombState;
+ DefuseBombState m_defuseBombState;
+ HideState m_hideState;
+ EscapeFromBombState m_escapeFromBombState;
+ FollowState m_followState;
+ UseEntityState m_useEntityState;
+ OpenDoorState m_openDoorState;
+
+ /// @todo Allow multiple simultaneous state machines (look around, etc)
+ void SetState( BotState *state ); ///< set the current behavior state
+ BotState *m_state; ///< current behavior state
+ float m_stateTimestamp; ///< time state was entered
+ bool m_isAttacking; ///< if true, special Attack state is overriding the state machine
+ bool m_isOpeningDoor; ///< if true, special OpenDoor state is overriding the state machine
+
+ TaskType m_task; ///< our current task
+ EHANDLE m_taskEntity; ///< an entity used for our task
+
+ //- navigation ---------------------------------------------------------------------------------------------------
+ Vector m_goalPosition;
+ EHANDLE m_goalEntity;
+ void MoveTowardsPosition( const Vector &pos ); ///< move towards position, independant of view angle
+ void MoveAwayFromPosition( const Vector &pos ); ///< move away from position, independant of view angle
+ void StrafeAwayFromPosition( const Vector &pos ); ///< strafe (sidestep) away from position, independant of view angle
+ void StuckCheck( void ); ///< check if we have become stuck
+ CCSNavArea *m_currentArea; ///< the nav area we are standing on
+ CCSNavArea *m_lastKnownArea; ///< the last area we were in
+ EHANDLE m_avoid; ///< higher priority player we need to make way for
+ float m_avoidTimestamp;
+ bool m_isStopping; ///< true if we're trying to stop because we entered a 'stop' nav area
+ bool m_hasVisitedEnemySpawn; ///< true if we have been at the enemy spawn
+ IntervalTimer m_stillTimer; ///< how long we have been not moving
+
+ //- path navigation data ----------------------------------------------------------------------------------------
+ enum { MAX_PATH_LENGTH = 256 };
+ struct ConnectInfo
+ {
+ CNavArea *area; ///< the area along the path
+ NavTraverseType how; ///< how to enter this area from the previous one
+ Vector pos; ///< our movement goal position at this point in the path
+ const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it
+ }
+ m_path[ MAX_PATH_LENGTH ];
+ int m_pathLength;
+ int m_pathIndex; ///< index of next area on path
+ float m_areaEnteredTimestamp;
+ void BuildTrivialPath( const Vector &goal ); ///< build trivial path to goal, assuming we are already in the same area
+
+ CountdownTimer m_repathTimer; ///< must have elapsed before bot can pathfind again
+
+ bool ComputePathPositions( void ); ///< determine actual path positions bot will move between along the path
+ void SetupLadderMovement( void );
+ void SetPathIndex( int index ); ///< set the current index along the path
+ void DrawPath( void );
+ int FindOurPositionOnPath( Vector *close, bool local = false ) const; ///< compute the closest point to our current position on our path
+ int FindPathPoint( float aheadRange, Vector *point, int *prevIndex = NULL ); ///< compute a point a fixed distance ahead along our path.
+ bool FindClosestPointOnPath( const Vector &pos, int startIndex, int endIndex, Vector *close ) const; ///< compute closest point on path to given point
+ bool IsStraightLinePathWalkable( const Vector &goal ) const; ///< test for un-jumpable height change, or unrecoverable fall
+ void ComputeLadderAngles( float *yaw, float *pitch ); ///< computes ideal yaw/pitch for traversing the current ladder on our path
+
+ mutable CountdownTimer m_avoidFriendTimer; ///< used to throttle how often we check for friends in our path
+ mutable bool m_isFriendInTheWay; ///< true if a friend is blocking our path
+ CountdownTimer m_politeTimer; ///< we'll wait for friend to move until this runs out
+ bool m_isWaitingBehindFriend; ///< true if we are waiting for a friend to move
+
+ #define ONLY_JUMP_DOWN true
+ bool DiscontinuityJump( float ground, bool onlyJumpDown = false, bool mustJump = false ); ///< check if we need to jump due to height change
+
+ enum LadderNavState
+ {
+ APPROACH_ASCENDING_LADDER, ///< prepare to scale a ladder
+ APPROACH_DESCENDING_LADDER, ///< prepare to go down ladder
+ FACE_ASCENDING_LADDER,
+ FACE_DESCENDING_LADDER,
+ MOUNT_ASCENDING_LADDER, ///< move toward ladder until "on" it
+ MOUNT_DESCENDING_LADDER, ///< move toward ladder until "on" it
+ ASCEND_LADDER, ///< go up the ladder
+ DESCEND_LADDER, ///< go down the ladder
+ DISMOUNT_ASCENDING_LADDER, ///< get off of the ladder
+ DISMOUNT_DESCENDING_LADDER, ///< get off of the ladder
+ MOVE_TO_DESTINATION, ///< dismount ladder and move to destination area
+ }
+ m_pathLadderState;
+ bool m_pathLadderFaceIn; ///< if true, face towards ladder, otherwise face away
+ const CNavLadder *m_pathLadder; ///< the ladder we need to use to reach the next area
+ bool UpdateLadderMovement( void ); ///< called by UpdatePathMovement()
+ NavRelativeDirType m_pathLadderDismountDir; ///< which way to dismount
+ float m_pathLadderDismountTimestamp; ///< time when dismount started
+ float m_pathLadderEnd; ///< if ascending, z of top, if descending z of bottom
+ void ComputeLadderEndpoint( bool ascending );
+ float m_pathLadderTimestamp; ///< time when we started using ladder - for timeout check
+
+ CountdownTimer m_mustRunTimer; ///< if nonzero, bot cannot walk
+ CountdownTimer m_waitTimer; ///< if nonzero, we are waiting where we are
+
+ void UpdateTravelDistanceToAllPlayers( void ); ///< periodically compute shortest path distance to each player
+ CountdownTimer m_updateTravelDistanceTimer; ///< for throttling travel distance computations
+ float m_playerTravelDistance[ MAX_PLAYERS ]; ///< current distance from this bot to each player
+ unsigned char m_travelDistancePhase; ///< a counter for optimizing when to compute travel distance
+
+ //- game scenario mechanisms -------------------------------------------------------------------------------------
+ CSGameState m_gameState; ///< our current knowledge about the state of the scenario
+
+ byte m_hostageEscortCount; ///< the number of hostages we're currently escorting
+ void UpdateHostageEscortCount( void ); ///< periodic check of hostage count in case we lost some
+ float m_hostageEscortCountTimestamp;
+
+ int m_desiredTeam; ///< the team we want to be on
+ bool m_hasJoined; ///< true if bot has actually joined the game
+
+ bool m_isWaitingForHostage;
+ CountdownTimer m_inhibitWaitingForHostageTimer; ///< if active, inhibits us waiting for lagging hostages
+ CountdownTimer m_waitForHostageTimer; ///< stops us waiting too long
+
+ //- listening mechanism ------------------------------------------------------------------------------------------
+ Vector m_noisePosition; ///< position we last heard non-friendly noise
+ float m_noiseTravelDistance; ///< the travel distance to the noise
+ float m_noiseTimestamp; ///< when we heard it (can get zeroed)
+ CNavArea *m_noiseArea; ///< the nav area containing the noise
+ PriorityType m_noisePriority; ///< priority of currently heard noise
+ bool UpdateLookAtNoise( void ); ///< return true if we decided to look towards the most recent noise source
+ CountdownTimer m_noiseBendTimer; ///< for throttling how often we bend our line of sight to the noise location
+ Vector m_bentNoisePosition; ///< the last computed bent line of sight
+ bool m_bendNoisePositionValid;
+
+ //- "looking around" mechanism -----------------------------------------------------------------------------------
+ float m_lookAroundStateTimestamp; ///< time of next state change
+ float m_lookAheadAngle; ///< our desired forward look angle
+ float m_forwardAngle; ///< our current forward facing direction
+ float m_inhibitLookAroundTimestamp; ///< time when we can look around again
+
+ enum LookAtSpotState
+ {
+ NOT_LOOKING_AT_SPOT, ///< not currently looking at a point in space
+ LOOK_TOWARDS_SPOT, ///< in the process of aiming at m_lookAtSpot
+ LOOK_AT_SPOT, ///< looking at m_lookAtSpot
+ NUM_LOOK_AT_SPOT_STATES
+ }
+ m_lookAtSpotState;
+ Vector m_lookAtSpot; ///< the spot we're currently looking at
+ PriorityType m_lookAtSpotPriority;
+ float m_lookAtSpotDuration; ///< how long we need to look at the spot
+ float m_lookAtSpotTimestamp; ///< when we actually began looking at the spot
+ float m_lookAtSpotAngleTolerance; ///< how exactly we must look at the spot
+ bool m_lookAtSpotClearIfClose; ///< if true, the look at spot is cleared if it gets close to us
+ bool m_lookAtSpotAttack; ///< if true, the look at spot should be attacked
+ const char *m_lookAtDesc; ///< for debugging
+ void UpdateLookAt( void );
+ void UpdatePeripheralVision(); ///< update enounter spot timestamps, etc
+ float m_peripheralTimestamp;
+
+ enum { MAX_APPROACH_POINTS = 16 };
+ struct ApproachPoint
+ {
+ Vector m_pos;
+ CNavArea *m_area;
+ };
+
+ ApproachPoint m_approachPoint[ MAX_APPROACH_POINTS ];
+ unsigned char m_approachPointCount;
+ Vector m_approachPointViewPosition; ///< the position used when computing current approachPoint set
+
+ CBaseEntity * FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck );
+
+ IntervalTimer m_viewSteadyTimer; ///< how long has our view been "steady" (ie: not moving)
+
+ bool BendLineOfSight( const Vector &eye, const Vector &target, Vector *bend, float angleLimit = 135.0f ) const; ///< "bend" our line of sight until we can see the target point. Return bend point, false if cant bend.
+ bool FindApproachPointNearestPath( Vector *pos ); ///< find the approach point that is nearest to our current path, ahead of us
+ bool FindGrenadeTossPathTarget( Vector *pos ); ///< find spot to throw grenade ahead of us and "around the corner" along our path
+ enum GrenadeTossState
+ {
+ NOT_THROWING, ///< not yet throwing
+ START_THROW, ///< lining up throw
+ THROW_LINED_UP, ///< pause for a moment when on-line
+ FINISH_THROW, ///< throwing
+ };
+ GrenadeTossState m_grenadeTossState;
+ CountdownTimer m_tossGrenadeTimer; ///< timeout timer for grenade tossing
+ const CNavArea *m_initialEncounterArea; ///< area where we think we will initially encounter the enemy
+ void LookForGrenadeTargets( void ); ///< look for grenade throw targets and throw our grenade at them
+ void UpdateGrenadeThrow( void ); ///< process grenade throwing
+ CountdownTimer m_isAvoidingGrenade; ///< if nonzero we are in the act of avoiding a grenade
+
+
+ SpotEncounter *m_spotEncounter; ///< the spots we will encounter as we move thru our current area
+ float m_spotCheckTimestamp; ///< when to check next encounter spot
+
+ /// @todo Add timestamp for each possible client to hiding spots
+ enum { MAX_CHECKED_SPOTS = 64 };
+ struct HidingSpotCheckInfo
+ {
+ HidingSpot *spot;
+ float timestamp;
+ }
+ m_checkedHidingSpot[ MAX_CHECKED_SPOTS ];
+ int m_checkedHidingSpotCount;
+
+ //- view angle mechanism -----------------------------------------------------------------------------------------
+ float m_lookPitch; ///< our desired look pitch angle
+ float m_lookPitchVel;
+ float m_lookYaw; ///< our desired look yaw angle
+ float m_lookYawVel;
+
+ //- aim angle mechanism -----------------------------------------------------------------------------------------
+ Vector m_aimOffset; ///< current error added to victim's position to get actual aim spot
+ Vector m_aimOffsetGoal; ///< desired aim offset
+ float m_aimOffsetTimestamp; ///< time of next offset adjustment
+ float m_aimSpreadTimestamp; ///< time used to determine max spread as it begins to tighten up
+ void SetAimOffset( float accuracy ); ///< set the current aim offset
+ void UpdateAimOffset( void ); ///< wiggle aim error based on m_accuracy
+ Vector m_aimSpot; ///< the spot we are currently aiming to fire at
+
+ struct PartInfo
+ {
+ Vector m_headPos; ///< current head position
+ Vector m_gutPos; ///< current gut position
+ Vector m_feetPos; ///< current feet position
+ Vector m_leftSidePos; ///< current left side position
+ Vector m_rightSidePos; ///< current right side position
+ int m_validFrame; ///< frame of last computation (for lazy evaluation)
+ };
+ static PartInfo m_partInfo[ MAX_PLAYERS ]; ///< part positions for each player
+ void ComputePartPositions( CCSPlayer *player ); ///< compute part positions from bone location
+
+ //- attack state data --------------------------------------------------------------------------------------------
+ DispositionType m_disposition; ///< how we will react to enemies
+ CountdownTimer m_ignoreEnemiesTimer; ///< how long will we ignore enemies
+ mutable CHandle< CCSPlayer > m_enemy; ///< our current enemy
+ bool m_isEnemyVisible; ///< result of last visibility test on enemy
+ unsigned char m_visibleEnemyParts; ///< which parts of the visible enemy do we see
+ Vector m_lastEnemyPosition; ///< last place we saw the enemy
+ float m_lastSawEnemyTimestamp;
+ float m_firstSawEnemyTimestamp;
+ float m_currentEnemyAcquireTimestamp;
+ float m_enemyDeathTimestamp; ///< if m_enemy is dead, this is when he died
+ float m_friendDeathTimestamp; ///< time since we saw a friend die
+ bool m_isLastEnemyDead; ///< true if we killed or saw our last enemy die
+ int m_nearbyEnemyCount; ///< max number of enemies we've seen recently
+ unsigned int m_enemyPlace; ///< the location where we saw most of our enemies
+
+ struct WatchInfo
+ {
+ float timestamp; ///< time we last saw this player, zero if never seen
+ bool isEnemy;
+ }
+ m_watchInfo[ MAX_PLAYERS ];
+ mutable CHandle< CCSPlayer > m_bomber; ///< points to bomber if we can see him
+
+ int m_nearbyFriendCount; ///< number of nearby teammates
+ mutable CHandle< CCSPlayer > m_closestVisibleFriend; ///< the closest friend we can see
+ mutable CHandle< CCSPlayer > m_closestVisibleHumanFriend; ///< the closest human friend we can see
+
+ IntervalTimer m_attentionInterval; ///< time between attention checks
+
+ mutable CHandle< CCSPlayer > m_attacker; ///< last enemy that hurt us (may not be same as m_enemy)
+ float m_attackedTimestamp; ///< when we were hurt by the m_attacker
+
+ int m_lastVictimID; ///< the entindex of the last victim we killed, or zero
+ bool m_isAimingAtEnemy; ///< if true, we are trying to aim at our enemy
+ bool m_isRapidFiring; ///< if true, RunUpkeep() will toggle our primary attack as fast as it can
+ IntervalTimer m_equipTimer; ///< how long have we had our current weapon equipped
+ CountdownTimer m_zoomTimer; ///< for delaying firing immediately after zoom
+ bool DoEquip( CWeaponCSBase *gun ); ///< equip the given item
+
+ void ReloadCheck( void ); ///< reload our weapon if we must
+ void SilencerCheck( void ); ///< use silencer
+
+ float m_fireWeaponTimestamp;
+
+ bool m_isEnemySniperVisible; ///< do we see an enemy sniper right now
+ CountdownTimer m_sawEnemySniperTimer; ///< tracking time since saw enemy sniper
+
+ //- reaction time system -----------------------------------------------------------------------------------------
+ enum { MAX_ENEMY_QUEUE = 20 };
+ struct ReactionState
+ {
+ // NOTE: player position & orientation is not currently stored separately
+ CHandle<CCSPlayer> player;
+ bool isReloading;
+ bool isProtectedByShield;
+ }
+ m_enemyQueue[ MAX_ENEMY_QUEUE ]; ///< round-robin queue for simulating reaction times
+ byte m_enemyQueueIndex;
+ byte m_enemyQueueCount;
+ byte m_enemyQueueAttendIndex; ///< index of the timeframe we are "conscious" of
+
+ CCSPlayer *FindMostDangerousThreat( void ); ///< return most dangerous threat in my field of view (feeds into reaction time queue)
+
+
+ //- stuck detection ---------------------------------------------------------------------------------------------
+ bool m_isStuck;
+ float m_stuckTimestamp; ///< time when we got stuck
+ Vector m_stuckSpot; ///< the location where we became stuck
+ NavRelativeDirType m_wiggleDirection;
+ CountdownTimer m_wiggleTimer;
+ CountdownTimer m_stuckJumpTimer; ///< time for next jump when stuck
+
+ enum { MAX_VEL_SAMPLES = 10 };
+ float m_avgVel[ MAX_VEL_SAMPLES ];
+ int m_avgVelIndex;
+ int m_avgVelCount;
+ Vector m_lastOrigin;
+
+ //- radio --------------------------------------------------------------------------------------------------------
+ RadioType m_lastRadioCommand; ///< last radio command we recieved
+ float m_lastRadioRecievedTimestamp; ///< time we recieved a radio message
+ float m_lastRadioSentTimestamp; ///< time when we send a radio message
+ CHandle< CCSPlayer > m_radioSubject; ///< who issued the radio message
+ Vector m_radioPosition; ///< position referred to in radio message
+ void RespondToRadioCommands( void );
+ bool IsRadioCommand( RadioType event ) const; ///< returns true if the radio message is an order to do something
+
+ /// new-style "voice" chatter gets voice feedback
+ float m_voiceEndTimestamp;
+
+ BotChatterInterface m_chatter; ///< chatter mechanism
+};
+
+
+//
+// Inlines
+//
+
+inline float CCSBot::GetFeetZ( void ) const
+{
+ return GetAbsOrigin().z;
+}
+
+inline const Vector *CCSBot::GetNoisePosition( void ) const
+{
+ if (m_noiseTimestamp > 0.0f)
+ return &m_noisePosition;
+
+ return NULL;
+}
+
+inline bool CCSBot::IsAwareOfEnemyDeath( void ) const
+{
+ if (GetEnemyDeathTimestamp() == 0.0f)
+ return false;
+
+ if (m_enemy == NULL)
+ return true;
+
+ if (!m_enemy->IsAlive() && gpGlobals->curtime - GetEnemyDeathTimestamp() > (1.0f - 0.8f * GetProfile()->GetSkill()))
+ return true;
+
+ return false;
+}
+
+inline void CCSBot::Panic( void )
+{
+ // we are stunned for a moment
+ Surprise( RandomFloat( 0.2f, 0.3f ) );
+
+ const float panicTime = 3.0f;
+ m_panicTimer.Start( panicTime );
+
+ const float panicRetreatRange = 300.0f;
+ TryToRetreat( panicRetreatRange, 0.0f );
+
+ PrintIfWatched( "*** PANIC ***\n" );
+}
+
+inline bool CCSBot::IsPanicking( void ) const
+{
+ return !m_panicTimer.IsElapsed();
+}
+
+inline void CCSBot::StopPanicking( void )
+{
+ m_panicTimer.Invalidate();
+}
+
+inline bool CCSBot::IsNotMoving( float minDuration ) const
+{
+ return (m_stillTimer.HasStarted() && m_stillTimer.GetElapsedTime() >= minDuration);
+}
+
+inline CWeaponCSBase *CCSBot::GetActiveCSWeapon( void ) const
+{
+ return reinterpret_cast<CWeaponCSBase *>( GetActiveWeapon() );
+}
+
+
+inline float CCSBot::GetCombatRange( void ) const
+{
+ return m_combatRange;
+}
+
+inline void CCSBot::SetRogue( bool rogue )
+{
+ m_isRogue = rogue;
+}
+
+inline void CCSBot::Hurry( float duration )
+{
+ m_hurryTimer.Start( duration );
+}
+
+inline float CCSBot::GetSafeTime( void ) const
+{
+ return m_safeTime;
+}
+
+inline bool CCSBot::IsUnhealthy( void ) const
+{
+ return (GetHealth() <= 40);
+}
+
+inline bool CCSBot::IsAlert( void ) const
+{
+ return !m_alertTimer.IsElapsed();
+}
+
+inline void CCSBot::BecomeAlert( void )
+{
+ const float alertCooldownTime = 10.0f;
+ m_alertTimer.Start( alertCooldownTime );
+}
+
+inline bool CCSBot::IsSneaking( void ) const
+{
+ return !m_sneakTimer.IsElapsed();
+}
+
+inline void CCSBot::Sneak( float duration )
+{
+ m_sneakTimer.Start( duration );
+}
+
+inline bool CCSBot::IsFollowing( void ) const
+{
+ return m_isFollowing;
+}
+
+inline CCSPlayer *CCSBot::GetFollowLeader( void ) const
+{
+ return m_leader;
+}
+
+inline float CCSBot::GetFollowDuration( void ) const
+{
+ return gpGlobals->curtime - m_followTimestamp;
+}
+
+inline bool CCSBot::CanAutoFollow( void ) const
+{
+ return (gpGlobals->curtime > m_allowAutoFollowTime);
+}
+
+inline void CCSBot::AimAtEnemy( void )
+{
+ m_isAimingAtEnemy = true;
+}
+
+inline void CCSBot::StopAiming( void )
+{
+ m_isAimingAtEnemy = false;
+}
+
+inline bool CCSBot::IsAimingAtEnemy( void ) const
+{
+ return m_isAimingAtEnemy;
+}
+
+inline float CCSBot::GetStateTimestamp( void ) const
+{
+ return m_stateTimestamp;
+}
+
+inline CSGameState *CCSBot::GetGameState( void )
+{
+ return &m_gameState;
+}
+
+inline const CSGameState *CCSBot::GetGameState( void ) const
+{
+ return &m_gameState;
+}
+
+inline bool CCSBot::IsAtBombsite( void )
+{
+ return m_bInBombZone;
+}
+
+inline void CCSBot::SetTask( TaskType task, CBaseEntity *entity )
+{
+ m_task = task;
+ m_taskEntity = entity;
+}
+
+inline CCSBot::TaskType CCSBot::GetTask( void ) const
+{
+ return m_task;
+}
+
+inline CBaseEntity *CCSBot::GetTaskEntity( void )
+{
+ return static_cast<CBaseEntity *>( m_taskEntity );
+}
+
+inline CCSBot::MoraleType CCSBot::GetMorale( void ) const
+{
+ return m_morale;
+}
+
+inline void CCSBot::Surprise( float duration )
+{
+ m_surpriseTimer.Start( duration );
+}
+
+inline bool CCSBot::IsSurprised( void ) const
+{
+ return !m_surpriseTimer.IsElapsed();
+}
+
+inline CNavArea *CCSBot::GetNoiseArea( void ) const
+{
+ return m_noiseArea;
+}
+
+inline void CCSBot::ForgetNoise( void )
+{
+ m_noiseTimestamp = 0.0f;
+}
+
+inline float CCSBot::GetNoiseRange( void ) const
+{
+ if (IsNoiseHeard())
+ return m_noiseTravelDistance;
+
+ return 999999999.9f;
+}
+
+inline PriorityType CCSBot::GetNoisePriority( void ) const
+{
+ return m_noisePriority;
+}
+
+inline BotChatterInterface *CCSBot::GetChatter( void )
+{
+ return &m_chatter;
+}
+
+inline CCSPlayer *CCSBot::GetBotEnemy( void ) const
+{
+ return m_enemy;
+}
+
+inline int CCSBot::GetNearbyEnemyCount( void ) const
+{
+ return MIN( GetEnemiesRemaining(), m_nearbyEnemyCount );
+}
+
+inline unsigned int CCSBot::GetEnemyPlace( void ) const
+{
+ return m_enemyPlace;
+}
+
+inline bool CCSBot::CanSeeBomber( void ) const
+{
+ return (m_bomber == NULL) ? false : true;
+}
+
+inline CCSPlayer *CCSBot::GetBomber( void ) const
+{
+ return m_bomber;
+}
+
+inline int CCSBot::GetNearbyFriendCount( void ) const
+{
+ return MIN( GetFriendsRemaining(), m_nearbyFriendCount );
+}
+
+inline CCSPlayer *CCSBot::GetClosestVisibleFriend( void ) const
+{
+ return m_closestVisibleFriend;
+}
+
+inline CCSPlayer *CCSBot::GetClosestVisibleHumanFriend( void ) const
+{
+ return m_closestVisibleHumanFriend;
+}
+
+inline float CCSBot::GetTimeSinceAttacked( void ) const
+{
+ return gpGlobals->curtime - m_attackedTimestamp;
+}
+
+inline float CCSBot::GetFirstSawEnemyTimestamp( void ) const
+{
+ return m_firstSawEnemyTimestamp;
+}
+
+inline float CCSBot::GetLastSawEnemyTimestamp( void ) const
+{
+ return m_lastSawEnemyTimestamp;
+}
+
+inline float CCSBot::GetTimeSinceLastSawEnemy( void ) const
+{
+ return gpGlobals->curtime - m_lastSawEnemyTimestamp;
+}
+
+inline float CCSBot::GetTimeSinceAcquiredCurrentEnemy( void ) const
+{
+ return gpGlobals->curtime - m_currentEnemyAcquireTimestamp;
+}
+
+inline const Vector &CCSBot::GetLastKnownEnemyPosition( void ) const
+{
+ return m_lastEnemyPosition;
+}
+
+inline bool CCSBot::IsEnemyVisible( void ) const
+{
+ return m_isEnemyVisible;
+}
+
+inline float CCSBot::GetEnemyDeathTimestamp( void ) const
+{
+ return m_enemyDeathTimestamp;
+}
+
+inline int CCSBot::GetLastVictimID( void ) const
+{
+ return m_lastVictimID;
+}
+
+inline bool CCSBot::CanSeeSniper( void ) const
+{
+ return m_isEnemySniperVisible;
+}
+
+inline bool CCSBot::HasSeenSniperRecently( void ) const
+{
+ return !m_sawEnemySniperTimer.IsElapsed();
+}
+
+inline float CCSBot::GetTravelDistanceToPlayer( CCSPlayer *player ) const
+{
+ if (player == NULL)
+ return -1.0f;
+
+ if (!player->IsAlive())
+ return -1.0f;
+
+ return m_playerTravelDistance[ player->entindex() % MAX_PLAYERS ];
+}
+
+inline bool CCSBot::HasPath( void ) const
+{
+ return (m_pathLength) ? true : false;
+}
+
+inline void CCSBot::DestroyPath( void )
+{
+ m_isStopping = false;
+ m_pathLength = 0;
+ m_pathLadder = NULL;
+}
+
+inline CNavArea *CCSBot::GetLastKnownArea( void ) const
+{
+ return m_lastKnownArea;
+}
+
+inline const Vector &CCSBot::GetPathEndpoint( void ) const
+{
+ return m_path[ m_pathLength-1 ].pos;
+}
+
+inline const Vector &CCSBot::GetPathPosition( int index ) const
+{
+ return m_path[ index ].pos;
+}
+
+inline bool CCSBot::IsUsingLadder( void ) const
+{
+ return (m_pathLadder) ? true : false;
+}
+
+inline void CCSBot::SetGoalEntity( CBaseEntity *entity )
+{
+ m_goalEntity = entity;
+}
+
+inline CBaseEntity *CCSBot::GetGoalEntity( void )
+{
+ return m_goalEntity;
+}
+
+inline void CCSBot::ForceRun( float duration )
+{
+ Run();
+ m_mustRunTimer.Start( duration );
+}
+
+inline void CCSBot::Wait( float duration )
+{
+ m_waitTimer.Start( duration );
+}
+
+inline bool CCSBot::IsWaiting( void ) const
+{
+ return !m_waitTimer.IsElapsed();
+}
+
+inline void CCSBot::StopWaiting( void )
+{
+ m_waitTimer.Invalidate();
+}
+
+inline bool CCSBot::HasVisitedEnemySpawn( void ) const
+{
+ return m_hasVisitedEnemySpawn;
+}
+
+inline const Vector &CCSBot::EyePositionConst( void ) const
+{
+ return m_eyePosition;
+}
+
+inline void CCSBot::SetLookAngles( float yaw, float pitch )
+{
+ m_lookYaw = yaw;
+ m_lookPitch = pitch;
+}
+
+inline void CCSBot::SetForwardAngle( float angle )
+{
+ m_forwardAngle = angle;
+}
+
+inline void CCSBot::SetLookAheadAngle( float angle )
+{
+ m_lookAheadAngle = angle;
+}
+
+inline void CCSBot::ClearLookAt( void )
+{
+ //PrintIfWatched( "ClearLookAt()\n" );
+ m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
+ m_lookAtDesc = NULL;
+}
+
+inline bool CCSBot::IsLookingAtSpot( PriorityType pri ) const
+{
+ if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority >= pri)
+ return true;
+
+ return false;
+}
+
+inline bool CCSBot::IsViewMoving( float angleVelThreshold ) const
+{
+ if (m_lookYawVel < angleVelThreshold && m_lookYawVel > -angleVelThreshold &&
+ m_lookPitchVel < angleVelThreshold && m_lookPitchVel > -angleVelThreshold)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+inline bool CCSBot::HasViewBeenSteady( float duration ) const
+{
+ return (m_viewSteadyTimer.GetElapsedTime() > duration);
+}
+
+inline bool CCSBot::HasLookAtTarget( void ) const
+{
+ return (m_lookAtSpotState != NOT_LOOKING_AT_SPOT);
+}
+
+inline bool CCSBot::IsEnemyPartVisible( VisiblePartType part ) const
+{
+ VPROF_BUDGET( "CCSBot::IsEnemyPartVisible", VPROF_BUDGETGROUP_NPCS );
+
+ if (!IsEnemyVisible())
+ return false;
+
+ return (m_visibleEnemyParts & part) ? true : false;
+}
+
+inline bool CCSBot::IsSignificantlyCloser( const CCSPlayer *testPlayer, const CCSPlayer *referencePlayer ) const
+{
+ if ( !referencePlayer )
+ return true;
+
+ if ( !testPlayer )
+ return false;
+
+ float testDist = ( GetAbsOrigin() - testPlayer->GetAbsOrigin() ).Length();
+ float referenceDist = ( GetAbsOrigin() - referencePlayer->GetAbsOrigin() ).Length();
+
+ const float significantRangeFraction = 0.7f;
+ if ( testDist < referenceDist * significantRangeFraction )
+ return true;
+
+ return false;
+}
+
+inline void CCSBot::ClearApproachPoints( void )
+{
+ m_approachPointCount = 0;
+}
+
+inline const CNavArea *CCSBot::GetInitialEncounterArea( void ) const
+{
+ return m_initialEncounterArea;
+}
+
+inline void CCSBot::SetInitialEncounterArea( const CNavArea *area )
+{
+ m_initialEncounterArea = area;
+}
+
+inline bool CCSBot::IsThrowingGrenade( void ) const
+{
+ return m_grenadeTossState != NOT_THROWING;
+}
+
+inline bool CCSBot::IsAvoidingGrenade( void ) const
+{
+ return !m_isAvoidingGrenade.IsElapsed();
+}
+
+inline void CCSBot::PrimaryAttack( void )
+{
+ if ( IsUsingPistol() && !CanActiveWeaponFire() )
+ return;
+
+ BaseClass::PrimaryAttack();
+}
+
+inline CCSBot::ZoomType CCSBot::GetZoomLevel( void )
+{
+ if (GetFOV() > 60.0f)
+ return NO_ZOOM;
+ if (GetFOV() > 25.0f)
+ return LOW_ZOOM;
+ return HIGH_ZOOM;
+}
+
+inline bool CCSBot::IsWaitingForZoom( void ) const
+{
+ return !m_zoomTimer.IsElapsed();
+}
+
+inline int CCSBot::GetHostageEscortCount( void ) const
+{
+ return m_hostageEscortCount;
+}
+
+inline void CCSBot::IncreaseHostageEscortCount( void )
+{
+ ++m_hostageEscortCount;
+}
+
+inline void CCSBot::ResetWaitForHostagePatience( void )
+{
+ m_isWaitingForHostage = false;
+ m_inhibitWaitingForHostageTimer.Invalidate();
+}
+
+
+inline bool CCSBot::IsUsingVoice() const
+{
+ return m_voiceEndTimestamp > gpGlobals->curtime;
+}
+
+inline bool CCSBot::IsOpeningDoor( void ) const
+{
+ return m_isOpeningDoor;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the given weapon is a sniper rifle
+ */
+inline bool IsSniperRifle( CWeaponCSBase *weapon )
+{
+ if (weapon == NULL)
+ return false;
+
+ return weapon->IsKindOf(WEAPONTYPE_SNIPER_RIFLE);
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Functor used with NavAreaBuildPath()
+ */
+class PathCost
+{
+public:
+ PathCost( CCSBot *bot, RouteType route = SAFEST_ROUTE )
+ {
+ m_bot = bot;
+ m_route = route;
+ }
+
+ // HPE_TODO[pmf]: check that these new parameters are okay to be ignored
+ float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
+ {
+ float baseDangerFactor = 100.0f; // 100
+
+ // respond to the danger modulated by our aggression (even super-aggressives pay SOME attention to danger)
+ float dangerFactor = (1.0f - (0.95f * m_bot->GetProfile()->GetAggression())) * baseDangerFactor;
+
+ if (fromArea == NULL)
+ {
+ if (m_route == FASTEST_ROUTE)
+ return 0.0f;
+
+ // first area in path, cost is just danger
+ return dangerFactor * area->GetDanger( m_bot->GetTeamNumber() );
+ }
+ else if ((fromArea->GetAttributes() & NAV_MESH_JUMP) && (area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ // cannot actually walk in jump areas - disallow moving from jump area to jump area
+ return -1.0f;
+ }
+ if ( area->GetAttributes() & NAV_MESH_NO_HOSTAGES && m_bot->GetHostageEscortCount() )
+ {
+ // if we're leading hostages, don't try to go where they can't
+ return -1.0f;
+ }
+ else
+ {
+ // compute distance from previous area to this area
+ float dist;
+ if (ladder)
+ {
+ // ladders are slow to use
+ const float ladderPenalty = 1.0f; // 3.0f;
+ dist = ladderPenalty * ladder->m_length;
+
+ // if we are currently escorting hostages, avoid ladders (hostages are confused by them)
+ //if (m_bot->GetHostageEscortCount())
+ // dist *= 100.0f;
+ }
+ else
+ {
+ dist = (area->GetCenter() - fromArea->GetCenter()).Length();
+ }
+
+ // compute distance travelled along path so far
+ float cost = dist + fromArea->GetCostSoFar();
+
+ // zombies ignore all path penalties
+ if (cv_bot_zombie.GetBool())
+ return cost;
+
+ // add cost of "jump down" pain unless we're jumping into water
+ if (!area->IsUnderwater() && area->IsConnected( fromArea, NUM_DIRECTIONS ) == false)
+ {
+ // this is a "jump down" (one way drop) transition - estimate damage we will take to traverse it
+ float fallDistance = -fromArea->ComputeGroundHeightChange( area );
+
+ // if it's a drop-down ladder, estimate height from the bottom of the ladder to the lower area
+ if ( ladder && ladder->m_bottom.z < fromArea->GetCenter().z && ladder->m_bottom.z > area->GetCenter().z )
+ {
+ fallDistance = ladder->m_bottom.z - area->GetCenter().z;
+ }
+
+ float fallDamage = m_bot->GetApproximateFallDamage( fallDistance );
+
+ if (fallDamage > 0.0f)
+ {
+ // if the fall would kill us, don't use it
+ const float deathFallMargin = 10.0f;
+ if (fallDamage + deathFallMargin >= m_bot->GetHealth())
+ return -1.0f;
+
+ // if we need to get there in a hurry, ignore minor pain
+ const float painTolerance = 15.0f * m_bot->GetProfile()->GetAggression() + 10.0f;
+ if (m_route != FASTEST_ROUTE || fallDamage > painTolerance)
+ {
+ // cost is proportional to how much it hurts when we fall
+ // 10 points - not a big deal, 50 points - ouch!
+ cost += 100.0f * fallDamage * fallDamage;
+ }
+ }
+ }
+
+ // if this is a "crouch" or "walk" area, add penalty
+ if (area->GetAttributes() & (NAV_MESH_CROUCH | NAV_MESH_WALK))
+ {
+ // these areas are very slow to move through
+ float penalty = (m_route == FASTEST_ROUTE) ? 20.0f : 5.0f;
+
+ // avoid crouch areas if we are rescuing hostages
+ if ((area->GetAttributes() & NAV_MESH_CROUCH) && m_bot->GetHostageEscortCount())
+ {
+ penalty *= 3.0f;
+ }
+
+ cost += penalty * dist;
+ }
+
+ // if this is a "jump" area, add penalty
+ if (area->GetAttributes() & NAV_MESH_JUMP)
+ {
+ // jumping can slow you down
+ //const float jumpPenalty = (m_route == FASTEST_ROUTE) ? 100.0f : 0.5f;
+ const float jumpPenalty = 1.0f;
+ cost += jumpPenalty * dist;
+ }
+
+ // if this is an area to avoid, add penalty
+ if (area->GetAttributes() & NAV_MESH_AVOID)
+ {
+ const float avoidPenalty = 20.0f;
+ cost += avoidPenalty * dist;
+ }
+
+ if (m_route == SAFEST_ROUTE)
+ {
+ // add in the danger of this path - danger is per unit length travelled
+ cost += dist * dangerFactor * area->GetDanger( m_bot->GetTeamNumber() );
+ }
+
+ if (!m_bot->IsAttacking())
+ {
+ // add in cost of teammates in the way
+
+ // approximate density of teammates based on area
+ float size = (area->GetSizeX() + area->GetSizeY())/2.0f;
+
+ // degenerate check
+ if (size >= 1.0f)
+ {
+ // cost is proportional to the density of teammates in this area
+ const float costPerFriendPerUnit = 50000.0f;
+ cost += costPerFriendPerUnit * (float)area->GetPlayerCount( m_bot->GetTeamNumber() ) / size;
+ }
+ }
+
+ return cost;
+ }
+ }
+
+private:
+ CCSBot *m_bot;
+ RouteType m_route;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+//
+// Prototypes
+//
+extern int GetBotFollowCount( CCSPlayer *leader );
+extern const Vector *FindNearbyRetreatSpot( CCSBot *me, float maxRange = 250.0f );
+extern const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper );
+
+
+#endif // _CS_BOT_H_
+
diff --git a/game/server/cstrike/bot/cs_bot_chatter.cpp b/game/server/cstrike/bot/cs_bot_chatter.cpp
new file mode 100644
index 0000000..4d8b4dc
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_chatter.cpp
@@ -0,0 +1,2582 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "cs_player.h"
+#include "shared_util.h"
+#include "engine/IEngineSound.h"
+#include "KeyValues.h"
+
+#include "bot.h"
+#include "bot_util.h"
+#include "cs_bot.h"
+#include "cs_bot_chatter.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+/**
+ * @todo Fix this
+ */
+const Vector *GetRandomSpotAtPlace( Place place )
+{
+ int count = 0;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if (area->GetPlace() == place)
+ ++count;
+ }
+
+ if (count == 0)
+ return NULL;
+
+ int which = RandomInt( 0, count-1 );
+
+ FOR_EACH_VEC( TheNavAreas, rit )
+ {
+ CNavArea *area = TheNavAreas[ rit ];
+
+ if (area->GetPlace() == place && which == 0)
+ return &area->GetCenter();
+ }
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Transmit meme to other bots
+ */
+void BotMeme::Transmit( CCSBot *sender ) const
+{
+ for( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+// if (FNullEnt( player->pev ))
+// continue;
+
+// if (FStrEq( STRING( player->pev->netname ), "" ))
+// continue;
+
+ // skip self
+ if (sender == player)
+ continue;
+
+ // ignore dead humans
+ if (!player->IsBot() && !player->IsAlive())
+ continue;
+
+ // ignore enemies, since we can't hear them talk
+ if (!player->InSameTeam( sender ))
+ continue;
+
+ // if not a bot, fail the test
+ if (!player->IsBot())
+ continue;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+
+ if ( !bot )
+ continue;
+
+ // allow bot to interpret our meme
+ Interpret( sender, bot );
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate called for help - respond
+ */
+void BotHelpMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ const float maxHelpRange = 3000.0f; // 2000
+ receiver->RespondToHelpRequest( sender, m_place, maxHelpRange );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate reported information about a bombsite
+ */
+void BotBombsiteStatusMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ // remember this bombsite's status
+ if (m_status == CLEAR)
+ receiver->GetGameState()->ClearBombsite( m_zoneIndex );
+ else
+ receiver->GetGameState()->MarkBombsiteAsPlanted( m_zoneIndex );
+
+ // if we were heading to the just-cleared bombsite, pick another one to search
+ // if our target bombsite wasn't cleared, will will continue going to it,
+ // because GetNextBombsiteToSearch() will return the same zone (since its not cleared)
+ // if the bomb was planted, we will head to that bombsite
+ if (receiver->GetTask() == CCSBot::FIND_TICKING_BOMB)
+ {
+ receiver->Idle();
+ receiver->GetChatter()->Affirmative();
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate reported information about the bomb
+ */
+void BotBombStatusMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ // update our gamestate based on teammate's report
+ switch( m_state )
+ {
+ case CSGameState::MOVING:
+ receiver->GetGameState()->UpdateBomber( m_pos );
+
+ // if we are hunting and see no enemies, respond
+ if (!receiver->IsRogue() && receiver->IsHunting() && receiver->GetNearbyEnemyCount() == 0)
+ receiver->RespondToHelpRequest( sender, TheNavMesh->GetPlace( m_pos ) );
+
+ break;
+
+ case CSGameState::LOOSE:
+ receiver->GetGameState()->UpdateLooseBomb( m_pos );
+
+ if (receiver->GetTask() == CCSBot::GUARD_BOMB_ZONE)
+ {
+ receiver->Idle();
+ receiver->GetChatter()->Affirmative();
+ }
+ break;
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate has asked that we follow him
+ */
+void BotFollowMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ if (receiver->IsRogue())
+ return;
+
+ // if we're busy, ignore
+ if (receiver->IsBusy())
+ return;
+
+ // if we are too far away, ignore
+ // compute actual travel distance
+ Vector senderOrigin = GetCentroid( sender );
+ PathCost cost( receiver );
+ float travelDistance = NavAreaTravelDistance( receiver->GetLastKnownArea(),
+ TheNavMesh->GetNearestNavArea( senderOrigin ),
+ cost );
+ if (travelDistance < 0.0f)
+ return;
+
+ const float tooFar = 1000.0f;
+ if (travelDistance > tooFar)
+ return;
+
+ // begin following
+ receiver->Follow( sender );
+
+ // acknowledge
+ receiver->GetChatter()->Say( "CoveringFriend" );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate has asked us to defend a place
+ */
+void BotDefendHereMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ if (receiver->IsRogue())
+ return;
+
+ // if we're busy, ignore
+ if (receiver->IsBusy())
+ return;
+
+ Place place = TheNavMesh->GetPlace( m_pos );
+ if (place != UNDEFINED_PLACE)
+ {
+ // pick a random hiding spot in this place
+ const Vector *spot = FindRandomHidingSpot( receiver, place, receiver->IsSniper() );
+ if (spot)
+ {
+ receiver->SetTask( CCSBot::HOLD_POSITION );
+ receiver->Hide( *spot );
+ return;
+ }
+ }
+
+ // hide nearby
+ receiver->SetTask( CCSBot::HOLD_POSITION );
+ receiver->Hide( TheNavMesh->GetNearestNavArea( m_pos ) );
+
+ // acknowledge
+ receiver->GetChatter()->Say( "Affirmative" );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate has asked where the bomb is planted
+ */
+void BotWhereBombMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ int zone = receiver->GetGameState()->GetPlantedBombsite();
+
+ if (zone != CSGameState::UNKNOWN)
+ receiver->GetChatter()->FoundPlantedBomb( zone );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate has asked us to report in
+ */
+void BotRequestReportMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ receiver->GetChatter()->ReportingIn();
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate told us all the hostages are gone
+ */
+void BotAllHostagesGoneMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ receiver->GetGameState()->AllHostagesGone();
+
+ // acknowledge
+ receiver->GetChatter()->Say( "Affirmative" );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate told us a CT is talking to a hostage
+ */
+void BotHostageBeingTakenMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ receiver->GetGameState()->HostageWasTaken();
+
+ // if we're busy, ignore
+ if (receiver->IsBusy())
+ return;
+
+ receiver->Idle();
+
+ // acknowledge
+ receiver->GetChatter()->Say( "Affirmative" );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate heard a noise, so we shouldn't report noises for a while
+ */
+void BotHeardNoiseMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ receiver->GetChatter()->FriendHeardNoise();
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * A teammate warned about snipers, so we shouldn't warn again for awhile
+ */
+void BotWarnSniperMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
+{
+ receiver->GetChatter()->FriendSpottedSniper();
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+BotSpeakable::BotSpeakable()
+{
+ m_phrase = NULL;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+BotSpeakable::~BotSpeakable()
+{
+ if ( m_phrase )
+ {
+ delete[] m_phrase;
+ m_phrase = NULL;
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------
+
+BotPhrase::BotPhrase( bool isPlace )
+{
+ m_name = NULL;
+ m_place = UNDEFINED_PLACE;
+ m_isPlace = isPlace;
+ m_radioEvent = RADIO_INVALID;
+ m_isImportant = false;
+ ClearCriteria();
+ m_numVoiceBanks = 0;
+ InitVoiceBank( 0 );
+}
+
+BotPhrase::~BotPhrase()
+{
+ for( int bank=0; bank<m_voiceBank.Count(); ++bank )
+ {
+ for( int speakable=0; speakable<m_voiceBank[bank]->Count(); ++speakable )
+ {
+ delete (*m_voiceBank[bank])[speakable];
+ }
+ delete m_voiceBank[bank];
+ }
+
+ if ( m_name )
+ delete [] m_name;
+}
+
+void BotPhrase::InitVoiceBank( int bankIndex )
+{
+ while ( m_numVoiceBanks <= bankIndex )
+ {
+ m_count.AddToTail(0);
+ m_index.AddToTail(0);
+ m_voiceBank.AddToTail( new BotSpeakableVector );
+ ++m_numVoiceBanks;
+ }
+}
+
+/**
+ * Return a random speakable - avoid repeating
+ */
+char *BotPhrase::GetSpeakable( int bankIndex, float *duration ) const
+{
+ if (bankIndex < 0 || bankIndex >= m_numVoiceBanks || m_count[bankIndex] == 0)
+ {
+ if (duration)
+ *duration = 0.0f;
+
+ return NULL;
+ }
+
+ // find phrase that meets the current criteria
+ int start = m_index[bankIndex];
+ while(true)
+ {
+ BotSpeakableVector *speakables = m_voiceBank[bankIndex];
+ int& index = m_index[bankIndex];
+ const BotSpeakable *speak = (*speakables)[index++];
+
+ if (m_index[bankIndex] >= m_count[bankIndex])
+ m_index[bankIndex] = 0;
+
+ // check place criteria
+ // if this speakable has a place criteria, it must match to be used
+ // speakables with Place of ANY will match any place
+ // speakables with a specific Place will only be used if Place matches
+ // speakables with Place of UNDEFINED only match Place of UNDEFINED
+ if (speak->m_place == ANY_PLACE || speak->m_place == m_placeCriteria)
+ {
+ // check count criteria
+ // if this speakable has a count criteria, it must match to be used
+ // if this speakable does not have a count criteria, we dont care what the count is set to
+ if (speak->m_count == UNDEFINED_COUNT || speak->m_count == MIN( m_countCriteria, COUNT_MANY ))
+ {
+ if (duration)
+ *duration = speak->m_duration;
+
+ return speak->m_phrase;
+ }
+ }
+
+ // check if we exhausted all speakables
+ if (m_index[bankIndex] == start)
+ {
+ if (duration)
+ *duration = 0.0f;
+
+ return NULL;
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Randomly shuffle the speakable order
+ */
+void BotPhrase::Randomize( void )
+{
+ for ( int bank = 0; bank < m_voiceBank.Count(); ++bank )
+ {
+ BotSpeakableVector *speakables = m_voiceBank[bank];
+ if ( speakables->Count() == 1 )
+ continue;
+
+ // A simple shuffle: for each array index, swap it with a random index
+ for ( int index = 0; index < speakables->Count(); ++index )
+ {
+ int newIndex = RandomInt( 0, speakables->Count()-1 );
+
+ BotSpeakable *speakable = (*speakables)[index];
+ (*speakables)[index] = (*speakables)[newIndex];
+ (*speakables)[newIndex] = speakable;
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------
+
+BotPhraseManager *TheBotPhrases = NULL;
+
+BotPhraseManager::BotPhraseManager( void )
+{
+ m_placeCount = 0;
+}
+
+
+/**
+ * Invoked when map changes
+ */
+void BotPhraseManager::OnMapChange( void )
+{
+ m_placeCount = 0;
+}
+
+/**
+ * Removes everything from memory
+ */
+void BotPhraseManager::Reset( void )
+{
+ int i;
+
+ // free phrase resources
+ for( i=0; i<m_list.Count(); ++i )
+ {
+ delete m_list[i];
+ }
+
+ for( i=0; i<m_placeList.Count(); ++i )
+ {
+ delete m_placeList[i];
+ }
+
+ m_list.RemoveAll();
+ m_placeList.RemoveAll();
+
+ m_painPhrase = NULL;
+ m_agreeWithPlanPhrase = NULL;
+}
+
+
+/**
+ * Invoked when the round resets
+ */
+void BotPhraseManager::OnRoundRestart( void )
+{
+ // effectively reset all interval timers
+ m_placeCount = 0;
+
+ // shuffle all the speakables
+ int i;
+ for( i=0; i<m_placeList.Count(); ++i )
+ m_placeList[i]->Randomize();
+
+ for( i=0; i<m_list.Count(); ++i )
+ m_list[i]->Randomize();
+}
+
+BotChatterOutputType BotPhraseManager::GetOutputType( int voiceBank ) const
+{
+ if ( voiceBank >= 0 && voiceBank < m_output.Count() )
+ {
+ return m_output[voiceBank];
+ }
+ return BOT_CHATTER_RADIO;
+}
+
+/**
+ * Initialize phrase system from database file
+ */
+bool BotPhraseManager::Initialize( const char *filename, int bankIndex )
+{
+ bool isDefault = (bankIndex == 0);
+
+ FileHandle_t file = filesystem->Open( filename, "r" );
+ if (!file)
+ {
+ CONSOLE_ECHO( "WARNING: Cannot access bot phrase database '%s'\n", filename );
+ return false;
+ }
+
+ // BOTPORT: Redo file reading to avoid loading whole file into memory at once
+ int phraseDataLength = filesystem->Size( filename );
+ char *phraseDataFile = new char[ phraseDataLength ];
+
+ int dataReadLength = filesystem->Read( phraseDataFile, phraseDataLength, file );
+
+ filesystem->Close( file );
+
+ if ( dataReadLength > 0 )
+ {
+ // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
+ // return fewer bytes than we were expecting.
+ phraseDataFile[ dataReadLength - 1 ] = 0;
+ }
+
+ const char *phraseData = phraseDataFile;
+
+
+ const int RadioPathLen = 128; // wav filenames need to be shorter than this to go over the net anyway.
+ char baseDir[RadioPathLen] = "";
+ char compositeFilename[RadioPathLen];
+
+ //
+ // Parse the BotChatter.db into BotPhrase collections
+ //
+ while( true )
+ {
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ break;
+
+ char *token = SharedGetToken();
+
+ if ( !stricmp( token, "Output" ) )
+ {
+ // get name of this output device
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+
+ while ( m_output.Count() <= bankIndex )
+ {
+ m_output.AddToTail(BOT_CHATTER_RADIO);
+ }
+
+ char *token = SharedGetToken();
+ if ( !stricmp( token, "Voice" ) )
+ {
+ m_output[bankIndex] = BOT_CHATTER_VOICE;
+ }
+ }
+ else if ( !stricmp( token, "BaseDir" ) )
+ {
+ // get name of this output device
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ char *token = SharedGetToken();
+ Q_strncpy( baseDir, token, RadioPathLen );
+ Q_strncat( baseDir, "\\", RadioPathLen, -1 );
+ baseDir[RadioPathLen-1] = 0;
+ }
+ else if (!stricmp( token, "Place" ) || !stricmp( token, "Chatter" ))
+ {
+ bool isPlace = (stricmp( token, "Place" )) ? false : true;
+
+ // encountered a new phrase collection
+ BotPhrase *phrase = NULL;
+ if ( isDefault )
+ {
+ phrase = new BotPhrase( isPlace );
+ }
+
+ // get name of this phrase
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ if ( isDefault )
+ {
+ phrase->m_name = CloneString( SharedGetToken() );
+
+ phrase->m_place = (isPlace) ? TheNavMesh->NameToPlace( phrase->m_name ) : UNDEFINED_PLACE;
+ }
+ else // look up the existing phrase
+ {
+ if ( isPlace )
+ {
+ phrase = const_cast<BotPhrase *>(GetPlace( SharedGetToken() ));
+ }
+ else
+ {
+ phrase = const_cast<BotPhrase *>(GetPhrase( SharedGetToken() ));
+ }
+
+ if ( !phrase )
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - phrase '%s' is invalid\n", filename, SharedGetToken() );
+ delete [] phraseDataFile;
+ return false;
+ }
+ }
+ phrase->InitVoiceBank( bankIndex );
+
+ PlaceCriteria placeCriteria = ANY_PLACE;
+ CountCriteria countCriteria = UNDEFINED_COUNT;
+ RadioType radioEvent = RADIO_INVALID;
+ bool isImportant = false;
+
+ // read attributes of this phrase
+ while( true )
+ {
+ // get next token
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ token = SharedGetToken();
+
+ // check for Place criteria
+ if (!stricmp( token, "Place" ))
+ {
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected Place name\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ token = SharedGetToken();
+
+ // update place criteria for subsequent speak lines
+ // NOTE: this assumes places must be first in the chatter database
+
+ // check for special identifiers
+ if (!stricmp( "ANY", token ))
+ placeCriteria = ANY_PLACE;
+ else if (!stricmp( "UNDEFINED", token ))
+ placeCriteria = UNDEFINED_PLACE;
+ else
+ placeCriteria = TheNavMesh->NameToPlace( token );
+
+ continue;
+ }
+
+ // check for Count criteria
+ if (!stricmp( token, "Count" ))
+ {
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected Count value\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ token = SharedGetToken();
+
+ // update count criteria for subsequent speak lines
+ if (!stricmp( token, "Many" ))
+ countCriteria = COUNT_MANY;
+ else
+ countCriteria = atoi( token );
+
+ continue;
+ }
+
+ // check for radio equivalent
+ if (!stricmp( token, "Radio" ))
+ {
+ phraseData = SharedParse( phraseData );
+ if (!phraseData)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected radio event\n", filename );
+ delete [] phraseDataFile;
+ return false;
+ }
+ token = SharedGetToken();
+
+ RadioType event = NameToRadioEvent( token );
+ if (event <= RADIO_START_1 || event >= RADIO_END)
+ {
+ CONSOLE_ECHO( "Error parsing %s - invalid radio event '%s'\n", filename, token );
+ delete [] phraseDataFile;
+ return false;
+ }
+
+ radioEvent = event;
+
+ continue;
+ }
+
+ // check for "important" flag
+ if (!stricmp( token, "Important" ))
+ {
+ isImportant = true;
+ continue;
+ }
+
+ // check for End delimiter
+ if (!stricmp( token, "End" ))
+ break;
+
+ // found a phrase - add it to the collection
+ BotSpeakable *speak = new BotSpeakable;
+ if ( baseDir[0] )
+ {
+ Q_snprintf( compositeFilename, RadioPathLen, "%s%s", baseDir, token );
+ speak->m_phrase = CloneString( compositeFilename );
+ }
+ else
+ {
+ speak->m_phrase = CloneString( token );
+ }
+ speak->m_place = placeCriteria;
+ speak->m_count = countCriteria;
+#ifdef POSIX
+ Q_FixSlashes( speak->m_phrase );
+ Q_strlower( speak->m_phrase );
+#endif
+
+ speak->m_duration = enginesound->GetSoundDuration( speak->m_phrase );
+
+ if (speak->m_duration <= 0.0f)
+ {
+ if ( !engine->IsDedicatedServer() )
+ {
+ DevMsg( "Warning: Couldn't get duration of phrase '%s'\n", speak->m_phrase );
+ }
+ speak->m_duration = 1.0f;
+ }
+
+ BotSpeakableVector * speakables = phrase->m_voiceBank[ bankIndex ];
+ speakables->AddToTail( speak );
+
+ ++phrase->m_count[ bankIndex ];
+ }
+
+ if ( isDefault )
+ {
+ phrase->m_radioEvent = radioEvent;
+ phrase->m_isImportant = isImportant;
+ }
+
+ // add phrase collection to the appropriate master list
+ if (isPlace)
+ m_placeList.AddToTail( phrase );
+ else
+ m_list.AddToTail( phrase );
+ }
+ }
+
+ delete [] phraseDataFile;
+
+ m_painPhrase = GetPhrase( "Pain" );
+ m_agreeWithPlanPhrase = GetPhrase( "AgreeWithPlan" );
+
+ return true;
+}
+
+BotPhraseManager::~BotPhraseManager()
+{
+ Reset();
+}
+
+/**
+ * Given a name, return the associated phrase collection
+ */
+const BotPhrase *BotPhraseManager::GetPhrase( const char *name ) const
+{
+ for( int i=0; i<m_list.Count(); ++i )
+ {
+ if (!stricmp( m_list[i]->m_name, name ))
+ return m_list[i];
+ }
+
+ //CONSOLE_ECHO( "GetPhrase: ERROR - Invalid phrase '%s'\n", name );
+ return NULL;
+}
+
+/**
+ * Given an id, return the associated phrase collection
+ * @todo Store phrases in a vector to make this fast
+ */
+/*
+const BotPhrase *BotPhraseManager::GetPhrase( unsigned int place ) const
+{
+ for( BotPhraseList::const_iterator iter = m_list.begin(); iter != m_list.end(); ++iter )
+ {
+ const BotPhrase *phrase = *iter;
+ if (phrase->m_place == id)
+ return phrase;
+ }
+
+ CONSOLE_ECHO( "GetPhrase: ERROR - Invalid phrase id #%d\n", id );
+ return NULL;
+}
+*/
+
+/**
+ * Given a name, return the associated Place phrase collection
+ */
+const BotPhrase *BotPhraseManager::GetPlace( const char *name ) const
+{
+ if (name == NULL)
+ return NULL;
+
+ for( int i=0; i<m_placeList.Count(); ++i )
+ {
+ if (!stricmp( m_placeList[i]->m_name, name ))
+ return m_placeList[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * Given a name, return the associated Place phrase collection
+ */
+const BotPhrase *BotPhraseManager::GetPlace( PlaceCriteria place ) const
+{
+ if (place == UNDEFINED_PLACE)
+ return NULL;
+
+ for( int i=0; i<m_placeList.Count(); ++i )
+ {
+ if (m_placeList[i]->m_place == place)
+ return m_placeList[i];
+ }
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------
+
+BotStatement::BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration )
+{
+ m_chatter = chatter;
+
+ m_next = NULL;
+ m_prev = NULL;
+ m_timestamp = gpGlobals->curtime;
+ m_speakTimestamp = 0.0f;
+
+ m_type = type;
+ m_subject = UNDEFINED_SUBJECT;
+ m_place = UNDEFINED_PLACE;
+ m_meme = NULL;
+
+ m_startTime = gpGlobals->curtime;
+ m_expireTime = gpGlobals->curtime + expireDuration;
+ m_isSpeaking = false;
+
+ m_nextTime = 0.0f;
+ m_index = -1;
+ m_count = 0;
+
+ m_conditionCount = 0;
+}
+
+BotStatement::~BotStatement()
+{
+ if (m_meme)
+ delete m_meme;
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+CCSBot *BotStatement::GetOwner( void ) const
+{
+ return m_chatter->GetOwner();
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Attach a meme to this statement, to be transmitted to other friendly bots when spoken
+ */
+void BotStatement::AttachMeme( BotMeme *meme )
+{
+ m_meme = meme;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Add a conditions that must be true for the statement to be spoken
+ */
+void BotStatement::AddCondition( ConditionType condition )
+{
+ if (m_conditionCount < MAX_BOT_CONDITIONS)
+ m_condition[ m_conditionCount++ ] = condition;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this statement is "important" and not personality chatter
+ */
+bool BotStatement::IsImportant( void ) const
+{
+ // if a statement contains any important phrases, it is important
+ for( int i=0; i<m_count; ++i )
+ {
+ if (m_statement[i].isPhrase && m_statement[i].phrase->IsImportant())
+ return true;
+
+ // hack for now - phrases with enemy counts are important
+ if (!m_statement[i].isPhrase && m_statement[i].context == BotStatement::CURRENT_ENEMY_COUNT)
+ return true;
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Verify all attached conditions
+ */
+bool BotStatement::IsValid( void ) const
+{
+ for( int i=0; i<m_conditionCount; ++i )
+ {
+ switch( m_condition[i] )
+ {
+ case IS_IN_COMBAT:
+ {
+ if (!GetOwner()->IsAttacking())
+ return false;
+ break;
+ }
+
+/*
+ case RADIO_SILENCE:
+ {
+ if (GetOwner()->GetChatter()->GetRadioSilenceDuration() < 10.0f)
+ return false;
+ break;
+ }
+*/
+
+ case ENEMIES_REMAINING:
+ {
+ if (GetOwner()->GetEnemiesRemaining() == 0)
+ return false;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this statement is essentially the same as the given one
+ */
+bool BotStatement::IsRedundant( const BotStatement *say ) const
+{
+ // special cases
+ if (GetType() == REPORT_MY_PLAN ||
+ GetType() == REPORT_REQUEST_HELP ||
+ GetType() == REPORT_CRITICAL_EVENT ||
+ GetType() == REPORT_ACKNOWLEDGE)
+ return false;
+
+ // check if topics are different
+ if (say->GetType() != GetType())
+ return false;
+
+ if (!say->HasPlace() && !HasPlace() && !say->HasSubject() && !HasSubject())
+ {
+ // neither has place or subject, so they are the same
+ return true;
+ }
+
+ // check if subject matter is the same
+ if (say->HasPlace() && HasPlace() && say->GetPlace() == GetPlace())
+ {
+ // talking about the same place
+ return true;
+ }
+
+ if (say->HasSubject() && HasSubject() && say->GetSubject() == GetSubject())
+ {
+ // talking about the same player
+ return true;
+ }
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this statement is no longer appropriate to say
+ */
+bool BotStatement::IsObsolete( void ) const
+{
+ // if the round is over, the only things we should say are emotes
+ if (GetOwner()->GetGameState()->IsRoundOver())
+ {
+ if (m_type != REPORT_EMOTE)
+ return true;
+ }
+
+ // If we're wanting to say "I lost him" but we've spotted another enemy,
+ // we no longer need to report losing someone.
+ if ( GetOwner()->GetChatter()->SeesAtLeastOneEnemy() && m_type == REPORT_ENEMY_LOST )
+ {
+ return true;
+ }
+
+ // check if statement lifetime has expired
+ return (gpGlobals->curtime > m_expireTime);
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Possibly change what were going to say base on what teammate is saying
+ */
+void BotStatement::Convert( const BotStatement *say )
+{
+ if (GetType() == REPORT_MY_PLAN && say->GetType() == REPORT_MY_PLAN)
+ {
+ const BotPhrase *meToo = TheBotPhrases->GetAgreeWithPlanPhrase();
+
+ // don't reconvert
+ if (m_statement[0].phrase == meToo)
+ return;
+
+ // if our plans are the same, change our statement to "me too"
+ if (m_statement[0].phrase == say->m_statement[0].phrase)
+ {
+ if (m_place == say->m_place)
+ {
+ // same plan at the same place - convert to "me too"
+ m_statement[0].phrase = meToo;
+ m_startTime = gpGlobals->curtime + RandomFloat( 0.5f, 1.0f );
+ }
+ else
+ {
+ // same plan at different place - wait a bit to allow others to respond "me too"
+ m_startTime = gpGlobals->curtime + RandomFloat( 3.0f, 4.0f );
+ }
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotStatement::AppendPhrase( const BotPhrase *phrase )
+{
+ if (phrase == NULL)
+ return;
+
+ if (m_count < MAX_BOT_PHRASES)
+ {
+ m_statement[ m_count ].isPhrase = true;
+ m_statement[ m_count++ ].phrase = phrase;
+ }
+}
+
+/**
+ * Special phrases that depend on the context
+ */
+void BotStatement::AppendPhrase( ContextType contextPhrase )
+{
+ if (m_count < MAX_BOT_PHRASES)
+ {
+ m_statement[ m_count ].isPhrase = false;
+ m_statement[ m_count++ ].context = contextPhrase;
+ }
+}
+
+/**
+ * Say our statement
+ * m_index refers to the phrase currently being spoken, or -1 if we havent started yet
+ */
+bool BotStatement::Update( void )
+{
+ CCSBot *me = GetOwner();
+
+ // if all of our teammates are dead, the only non-redundant statements are emotes
+ if (me->GetFriendsRemaining() == 0 && GetType() != REPORT_EMOTE)
+ return false;
+
+ if (!m_isSpeaking)
+ {
+ m_isSpeaking = true;
+ m_speakTimestamp = gpGlobals->curtime;
+ }
+
+ // special case - context dependent delay
+ if (m_index >= 0 && m_statement[ m_index ].context == ACCUMULATE_ENEMIES_DELAY)
+ {
+ // report if we see a lot of enemies, or if enough time has passed
+ const float reportTime = 2.0f; // 1
+ if (me->GetNearbyEnemyCount() > 3 || gpGlobals->curtime - m_speakTimestamp > reportTime)
+ {
+ // enough enemies have accumulated to expire this delay
+ m_nextTime = 0.0f;
+ }
+ }
+
+
+ if (gpGlobals->curtime > m_nextTime)
+ {
+ // check for end of statement
+ if (++m_index == m_count)
+ {
+ // transmit any memes carried in this statement to our teammates
+ if (m_meme)
+ m_meme->Transmit( me );
+
+ return false;
+ }
+
+ // start next part of statement
+ float duration = 0.0f;
+ const BotPhrase *phrase = NULL;
+
+ if (m_statement[ m_index ].isPhrase)
+ {
+ // normal phrase
+ phrase = m_statement[ m_index ].phrase;
+ }
+ else
+ {
+ // context-dependant phrase
+ switch( m_statement[ m_index ].context )
+ {
+ case CURRENT_ENEMY_COUNT:
+ {
+ int enemyCount = me->GetNearbyEnemyCount();
+
+ // if we are outnumbered, ask for help
+ if (enemyCount-1 > me->GetNearbyFriendCount())
+ {
+ phrase = TheBotPhrases->GetPhrase( "Help" );
+ AttachMeme( new BotHelpMeme() );
+ }
+ else if (enemyCount > 1)
+ {
+ phrase = TheBotPhrases->GetPhrase( "EnemySpotted" );
+ phrase->SetCountCriteria( enemyCount );
+ }
+ break;
+ }
+
+ case REMAINING_ENEMY_COUNT:
+ {
+ static const char *speak[] =
+ {
+ "NoEnemiesLeft", "OneEnemyLeft", "TwoEnemiesLeft", "ThreeEnemiesLeft"
+ };
+
+ int enemyCount = me->GetEnemiesRemaining();
+
+ // dont report if there are lots of enemies left
+ if (enemyCount < 0 || enemyCount > 3)
+ {
+ phrase = NULL;
+ }
+ else
+ {
+ phrase = TheBotPhrases->GetPhrase( speak[ enemyCount ] );
+ }
+ break;
+ }
+
+ case SHORT_DELAY:
+ {
+ m_nextTime = gpGlobals->curtime + RandomFloat( 0.1f, 0.5f );
+ return true;
+ }
+
+ case LONG_DELAY:
+ {
+ m_nextTime = gpGlobals->curtime + RandomFloat( 1.0f, 2.0f );
+ return true;
+ }
+
+ case ACCUMULATE_ENEMIES_DELAY:
+ {
+ // wait until test becomes true
+ m_nextTime = 99999999.9f;
+ return true;
+ }
+ }
+ }
+
+ if (phrase)
+ {
+ // if chatter system is in "standard radio" mode, send the equivalent radio command
+ if (me->GetChatter()->GetVerbosity() == BotChatterInterface::RADIO)
+ {
+ RadioType radioEvent = phrase->GetRadioEquivalent();
+ if (radioEvent == RADIO_INVALID)
+ {
+ // skip directly to the next phrase
+ m_nextTime = 0.0f;
+ }
+ else
+ {
+ // use the standard radio
+ me->GetChatter()->ResetRadioSilenceDuration();
+ me->SendRadioMessage( radioEvent );
+ duration = 2.0f;
+ }
+ }
+ else
+ {
+ // set place criteria
+ phrase->SetPlaceCriteria( m_place );
+
+ const char *filename = phrase->GetSpeakable( me->GetProfile()->GetVoiceBank(), &duration );
+ // CONSOLE_ECHO( "%s: Radio( '%s' )\n", STRING( me->pev->netname ), filename );
+
+ bool sayIt = true;
+
+ if (phrase->IsPlace())
+ {
+ // don't repeat the place if someone just mentioned it not too long ago
+ float timeSince = TheBotPhrases->GetPlaceStatementInterval( phrase->GetPlace() );
+ const float minRepeatTime = 20.0f; // 30
+ if (timeSince < minRepeatTime)
+ {
+ sayIt = false;
+ }
+ else
+ {
+ TheBotPhrases->ResetPlaceStatementInterval( phrase->GetPlace() );
+ }
+ }
+
+ if (sayIt)
+ {
+ if ( !filename )
+ {
+ RadioType radioEvent = phrase->GetRadioEquivalent();
+ if (radioEvent == RADIO_INVALID)
+ {
+ // skip directly to the next phrase
+ m_nextTime = 0.0f;
+ }
+ else
+ {
+ // use the standard radio
+ me->SendRadioMessage( radioEvent );
+ me->GetChatter()->ResetRadioSilenceDuration();
+ duration = 2.0f;
+ }
+ }
+ /* BOTPORT: Wire up bot voice over IP
+ else if ( g_engfuncs.pfnPlayClientVoice && TheBotPhrases->GetOutputType( me->GetProfile()->GetVoiceBank() ) == BOT_CHATTER_VOICE )
+ {
+ me->GetChatter()->ResetRadioSilenceDuration();
+ g_engfuncs.pfnPlayClientVoice( me->entindex() - 1, filename );
+ }
+ */
+ else
+ {
+ me->SpeakAudio( filename, duration + 1.0f, me->GetProfile()->GetVoicePitch() );
+ }
+ }
+ }
+
+ const float gap = 0.1f;
+ m_nextTime = gpGlobals->curtime + duration + gap;
+ }
+ else
+ {
+ // skip directly to the next phrase
+ m_nextTime = 0.0f;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * If this statement refers to a specific place, return that place
+ * Places can be implicit in the statement, or explicitly defined
+ */
+unsigned int BotStatement::GetPlace( void ) const
+{
+ // return any explicitly set place if we have one
+ if (m_place != UNDEFINED_PLACE)
+ return m_place;
+
+ // look for an implicit place in our statement
+ for( int i=0; i<m_count; ++i )
+ if (m_statement[i].isPhrase && m_statement[i].phrase->IsPlace())
+ return m_statement[i].phrase->GetPlace();
+
+ return 0;
+}
+
+/**
+ * Return true if this statement has an associated count
+ */
+bool BotStatement::HasCount( void ) const
+{
+ for( int i=0; i<m_count; ++i )
+ if (!m_statement[i].isPhrase && m_statement[i].context == CURRENT_ENEMY_COUNT)
+ return true;
+
+ return false;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------
+
+CountdownTimer BotChatterInterface::m_encourageTimer;
+IntervalTimer BotChatterInterface::m_radioSilenceInterval[ 2 ];
+
+
+enum PitchHack
+{
+ P_HI,
+ P_NORMAL,
+ P_LOW
+};
+
+static int nextPitch = P_HI;
+
+BotChatterInterface::BotChatterInterface( CCSBot *me )
+{
+ m_me = me;
+ m_statementList = NULL;
+
+ switch( nextPitch )
+ {
+ case P_HI:
+ m_pitch = RandomInt( 105, 110 );
+ break;
+
+ case P_NORMAL:
+ m_pitch = RandomInt( 95, 105 );
+ break;
+
+ case P_LOW:
+ m_pitch = RandomInt( 85, 95 );
+ break;
+ }
+
+ nextPitch = (nextPitch + 1) % 3;
+
+ Reset();
+}
+
+//---------------------------------------------------------------------------------------------------------------
+BotChatterInterface::~BotChatterInterface()
+{
+ // free pending statements
+ BotStatement *next;
+ for( BotStatement *msg = m_statementList; msg; msg = next )
+ {
+ next = msg->m_next;
+ delete msg;
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Reset to initial state
+ */
+void BotChatterInterface::Reset( void )
+{
+ BotStatement *msg, *nextMsg;
+
+ // removing pending statements - except for those about the round results
+ for( msg = m_statementList; msg; msg = nextMsg )
+ {
+ nextMsg = msg->m_next;
+
+ if (msg->GetType() != REPORT_ROUND_END)
+ RemoveStatement( msg );
+ }
+
+ m_seeAtLeastOneEnemy = false;
+ m_timeWhenSawFirstEnemy = 0.0f;
+ m_reportedEnemies = false;
+ m_requestedBombLocation = false;
+
+ ResetRadioSilenceDuration();
+
+ m_needBackupInterval.Invalidate();
+ m_spottedBomberInterval.Invalidate();
+ m_spottedLooseBombTimer.Invalidate();
+ m_heardNoiseTimer.Invalidate();
+ m_scaredInterval.Invalidate();
+ m_planInterval.Invalidate();
+ m_encourageTimer.Invalidate();
+ m_escortingHostageTimer.Invalidate();
+ m_warnSniperTimer.Invalidate();
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Register a statement for speaking
+ */
+void BotChatterInterface::AddStatement( BotStatement *statement, bool mustAdd )
+{
+ // don't add statements if bot chatter is shut off
+ if (GetVerbosity() == OFF)
+ {
+ delete statement;
+ return;
+ }
+
+ // if we only want mission-critical radio chatter, ignore non-important phrases
+ if (GetVerbosity() == MINIMAL && !statement->IsImportant())
+ {
+ delete statement;
+ return;
+ }
+
+ // don't add statements if we're dead
+ if (!m_me->IsAlive() && !mustAdd)
+ {
+ delete statement;
+ return;
+ }
+
+ // don't add empty statements
+ if (statement->m_count == 0)
+ {
+ delete statement;
+ return;
+ }
+
+ // don't add statements that are redundant with something we're already waiting to say
+ BotStatement *s;
+ for( s=m_statementList; s; s = s->m_next )
+ {
+ if (statement->IsRedundant( s ))
+ {
+ m_me->PrintIfWatched( "I tried to say something I'm already saying.\n" );
+ delete statement;
+ return;
+ }
+ }
+
+ // keep statements in order of start time
+
+ // check list is empty
+ if (m_statementList == NULL)
+ {
+ statement->m_next = NULL;
+ statement->m_prev = NULL;
+ m_statementList = statement;
+ return;
+ }
+
+ // list has at least one statement on it
+
+ // insert into list in order
+ BotStatement *earlier = NULL;
+ for( s=m_statementList; s; s = s->m_next )
+ {
+ if (s->GetStartTime() > statement->GetStartTime())
+ break;
+
+ earlier = s;
+ }
+
+ // insert just after "earlier"
+ if (earlier)
+ {
+ if (earlier->m_next)
+ earlier->m_next->m_prev = statement;
+
+ statement->m_next = earlier->m_next;
+
+ earlier->m_next = statement;
+ statement->m_prev = earlier;
+ }
+ else
+ {
+ // insert at head
+ statement->m_prev = NULL;
+ statement->m_next = m_statementList;
+ m_statementList->m_prev = statement;
+ m_statementList = statement;
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Remove a statement
+ */
+void BotChatterInterface::RemoveStatement( BotStatement *statement )
+{
+ if (statement->m_next)
+ statement->m_next->m_prev = statement->m_prev;
+
+ if (statement->m_prev)
+ statement->m_prev->m_next = statement->m_next;
+ else
+ m_statementList = statement->m_next;
+
+ delete statement;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Track nearby enemy count and report enemy activity
+ */
+void BotChatterInterface::ReportEnemies( void )
+{
+ if (!m_me->IsAlive())
+ return;
+
+ if (m_me->GetNearbyEnemyCount() == 0)
+ {
+ m_seeAtLeastOneEnemy = false;
+ m_reportedEnemies = false;
+ }
+ else if (!m_seeAtLeastOneEnemy)
+ {
+ m_seeAtLeastOneEnemy = true;
+ m_timeWhenSawFirstEnemy = gpGlobals->curtime;
+ }
+
+ // determine whether we should report enemy activity
+ if (!m_reportedEnemies && m_seeAtLeastOneEnemy)
+ {
+ // request backup if we're outnumbered
+ if (m_me->IsOutnumbered() && NeedBackup())
+ {
+ m_reportedEnemies = true;
+ return;
+ }
+
+ m_me->GetChatter()->EnemySpotted();
+ m_reportedEnemies = true;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when we die
+ */
+void BotChatterInterface::OnDeath( void )
+{
+ if (IsTalking())
+ {
+ if (m_me->GetChatter()->GetVerbosity() == BotChatterInterface::MINIMAL ||
+ m_me->GetChatter()->GetVerbosity() == BotChatterInterface::NORMAL)
+ {
+ // we've died mid-sentance - emit a gargle of pain
+ const BotPhrase *pain = TheBotPhrases->GetPainPhrase();
+ if (pain)
+ {
+ /*
+ if ( g_engfuncs.pfnPlayClientVoice && TheBotPhrases->GetOutputType( m_me->GetProfile()->GetVoiceBank() ) == BOT_CHATTER_VOICE )
+ {
+ g_engfuncs.pfnPlayClientVoice( m_me->entindex() - 1, pain->GetSpeakable(m_me->GetProfile()->GetVoiceBank()) );
+ m_me->GetChatter()->ResetRadioSilenceDuration();
+ }
+ else
+ */
+ {
+ m_me->SpeakAudio( pain->GetSpeakable( m_me->GetProfile()->GetVoiceBank() ), 0.0f, m_me->GetProfile()->GetVoicePitch() );
+ }
+ }
+ }
+ }
+
+ // remove all of our statements
+ Reset();
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Process ongoing chatter for this bot
+ */
+void BotChatterInterface::Update( void )
+{
+ // report enemy activity
+ ReportEnemies();
+
+ // ask team to report in if we havent heard anything in awhile
+ if (ShouldSpeak())
+ {
+ const float longTime = 30.0f;
+ if (m_me->GetEnemiesRemaining() > 0 && GetRadioSilenceDuration() > longTime)
+ {
+ ReportIn();
+ }
+ }
+
+ // speak if it is our turn
+ BotStatement *say = GetActiveStatement();
+
+ if (say)
+ {
+ // if our statement is active, speak it
+ if (say->GetOwner() == m_me)
+ {
+ if (say->Update() == false)
+ {
+ // this statement is complete - destroy it
+ RemoveStatement( say );
+ }
+ }
+ }
+
+
+ //
+ // Process active statements.
+ // Removed expired statements, re-order statements according to their relavence and importance
+ // Remove redundant statements (ie: our teammates already said them)
+ //
+ const BotStatement *friendSay = GetActiveStatement();
+ if (friendSay && friendSay->GetOwner() == m_me)
+ friendSay = NULL;
+
+ BotStatement *nextSay;
+ for( say = m_statementList; say; say = nextSay )
+ {
+ nextSay = say->m_next;
+
+ // check statement conditions
+ if (!say->IsValid())
+ {
+ RemoveStatement( say );
+ continue;
+ }
+
+ // don't interrupt ourselves
+ if (say->IsSpeaking())
+ continue;
+
+ // check for obsolete statements
+ if (say->IsObsolete())
+ {
+ m_me->PrintIfWatched( "Statement obsolete - removing.\n" );
+ RemoveStatement( say );
+ continue;
+ }
+
+ // if a teammate is saying what we were going to say, dont repeat it
+ if (friendSay)
+ {
+ // convert what we're about to say based on what our teammate is currently saying
+ say->Convert( friendSay );
+
+ // don't say things our teammates have just said
+ if (say->IsRedundant( friendSay ))
+ {
+ // thie statement is redundant - destroy it
+ //m_me->PrintIfWatched( "Teammate said what I was going to say - shutting up.\n" );
+ m_me->PrintIfWatched( "Teammate said what I was going to say - shutting up.\n" );
+ RemoveStatement( say );
+ }
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
+ */
+BotStatement *BotChatterInterface::GetActiveStatement( void )
+{
+ // keep track of statement waiting longest to be spoken - it is next
+ BotStatement *earliest = NULL;
+ float earlyTime = 999999999.9f;
+
+ for( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ // ignore dead humans
+ if (!player->IsBot() && !player->IsAlive())
+ continue;
+
+ // ignore enemies, since we can't hear them talk
+ if (!m_me->InSameTeam( player ))
+ continue;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>(player);
+
+ // if not a bot, fail the test
+ /// @todo Check if human is currently talking
+ if (!bot)
+ continue;
+
+ for( BotStatement *say = bot->GetChatter()->m_statementList; say; say = say->m_next )
+ {
+ // if this statement is currently being spoken, return it
+ if (say->IsSpeaking())
+ return say;
+
+ // keep track of statement that has been waiting longest to be spoken of anyone on our team
+ if (say->GetStartTime() < earlyTime)
+ {
+ earlyTime = say->GetTimestamp();
+ earliest = say;
+ }
+ }
+ }
+
+ // make sure it is time to start this statement
+ if (earliest && earliest->GetStartTime() > gpGlobals->curtime)
+ return NULL;
+
+ return earliest;
+}
+
+/**
+ * Return true if we speaking makes sense now
+ */
+bool BotChatterInterface::ShouldSpeak( void ) const
+{
+ // don't talk to non-existent friends
+ if (m_me->GetFriendsRemaining() == 0)
+ return false;
+
+ // if everyone is together, no need to tell them what's going on
+ if (m_me->GetNearbyFriendCount() == m_me->GetFriendsRemaining())
+ return false;
+
+ return true;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+float BotChatterInterface::GetRadioSilenceDuration( void )
+{
+ return m_radioSilenceInterval[ m_me->GetTeamNumber() % 2 ].GetElapsedTime();
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::ResetRadioSilenceDuration( void )
+{
+ m_radioSilenceInterval[ m_me->GetTeamNumber() % 2 ].Reset();
+}
+
+
+
+//---------------------------------------------------------------------------------------------------------------
+inline const BotPhrase *GetPlacePhrase( CCSBot *me )
+{
+ Place place = me->GetPlace();
+ if (place != UNDEFINED_PLACE)
+ return TheBotPhrases->GetPlace( place );
+
+ return NULL;
+}
+
+
+inline void SayWhere( BotStatement *say, Place place )
+{
+ say->AppendPhrase( TheBotPhrases->GetPlace( place ) );
+}
+
+/**
+ * Report enemy sightings
+ */
+void BotChatterInterface::EnemySpotted( void )
+{
+ // NOTE: This could be a few seconds out of date (enemy is in an adjacent place)
+ Place place = m_me->GetEnemyPlace();
+
+ BotStatement *say = new BotStatement( this, REPORT_VISIBLE_ENEMIES, 10.0f );
+
+ // where are the enemies
+ say->AppendPhrase( TheBotPhrases->GetPlace( place ) );
+
+ // how many are there
+ say->AppendPhrase( BotStatement::ACCUMULATE_ENEMIES_DELAY );
+ say->AppendPhrase( BotStatement::CURRENT_ENEMY_COUNT );
+ say->AddCondition( BotStatement::IS_IN_COMBAT );
+
+ AddStatement( say );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * If a friend warned of snipers, don't warn again for awhile
+ */
+void BotChatterInterface::FriendSpottedSniper( void )
+{
+ m_warnSniperTimer.Start( 60.0f );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Warn of an enemy sniper
+ */
+void BotChatterInterface::SpottedSniper( void )
+{
+ if (!m_warnSniperTimer.IsElapsed())
+ {
+ return;
+ }
+
+ if (m_me->GetFriendsRemaining() == 0)
+ {
+ // no-one to warn
+ return;
+ }
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "SniperWarning" ) );
+ say->AttachMeme( new BotWarnSniperMeme() );
+
+ AddStatement( say );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::Clear( Place place )
+{
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ SayWhere( say, place );
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Clear" ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Request enemy activity report
+ */
+void BotChatterInterface::ReportIn( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_REQUEST_INFORMATION, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "RequestReport" ) );
+ say->AddCondition( BotStatement::RADIO_SILENCE );
+ say->AttachMeme( new BotRequestReportMeme() );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * Report our situtation
+ */
+void BotChatterInterface::ReportingIn( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ // where are we
+ Place place = m_me->GetPlace();
+ SayWhere( say, place );
+
+ // what are we doing
+ switch( m_me->GetTask() )
+ {
+ case CCSBot::PLANT_BOMB:
+ {
+ m_me->GetChatter()->GoingToPlantTheBomb( UNDEFINED_PLACE );
+ break;
+ }
+
+ case CCSBot::DEFUSE_BOMB:
+ {
+ m_me->GetChatter()->Say( "DefusingBomb" );
+ break;
+ }
+
+ case CCSBot::GUARD_LOOSE_BOMB:
+ {
+ if (TheCSBots()->GetLooseBomb())
+ {
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "GuardingLooseBomb" ) );
+ say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, TheCSBots()->GetLooseBomb()->GetAbsOrigin() ) );
+ }
+ break;
+ }
+
+ case CCSBot::GUARD_HOSTAGES:
+ {
+ m_me->GetChatter()->GuardingHostages( UNDEFINED_PLACE, !m_me->IsAtHidingSpot() );
+ break;
+ }
+
+ case CCSBot::GUARD_HOSTAGE_RESCUE_ZONE:
+ {
+ m_me->GetChatter()->GuardingHostageEscapeZone( !m_me->IsAtHidingSpot() );
+ break;
+ }
+
+ case CCSBot::COLLECT_HOSTAGES:
+ {
+ break;
+ }
+
+ case CCSBot::RESCUE_HOSTAGES:
+ {
+ m_me->GetChatter()->EscortingHostages();
+ break;
+ }
+
+ case CCSBot::GUARD_VIP_ESCAPE_ZONE:
+ {
+ break;
+ }
+
+ }
+
+
+ // what do we see
+ if (m_me->IsAttacking())
+ {
+ if (m_me->IsOutnumbered())
+ {
+ // in trouble in a firefight
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Help" ) );
+ say->AttachMeme( new BotHelpMeme( place ) );
+ }
+ else
+ {
+ // battling enemies
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "InCombat" ) );
+ }
+ }
+ else
+ {
+ // not in combat, start our report a little later
+ say->SetStartTime( gpGlobals->curtime + 2.0f );
+
+ const float recentTime = 10.0f;
+ if (m_me->GetEnemyDeathTimestamp() < recentTime && m_me->GetEnemyDeathTimestamp() >= m_me->GetTimeSinceLastSawEnemy() + 0.5f)
+ {
+ // recently saw an enemy die
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "EnemyDown" ) );
+ }
+ else if (m_me->GetTimeSinceLastSawEnemy() < recentTime)
+ {
+ // recently saw an enemy
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "EnemySpotted" ) );
+ }
+ else
+ {
+ // haven't seen enemies
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Clear" ) );
+ }
+ }
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+bool BotChatterInterface::NeedBackup( void )
+{
+ const float minRequestInterval = 10.0f;
+ if (m_needBackupInterval.IsLessThen( minRequestInterval ))
+ return false;
+
+ m_needBackupInterval.Reset();
+
+ if (m_me->GetFriendsRemaining() == 0)
+ {
+ // we're all alone...
+ Scared();
+ return true;
+ }
+ else
+ {
+ // ask friends for help
+ BotStatement *say = new BotStatement( this, REPORT_REQUEST_HELP, 10.0f );
+
+ // where are we
+ Place place = m_me->GetPlace();
+ SayWhere( say, place );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Help" ) );
+ say->AttachMeme( new BotHelpMeme( place ) );
+
+ AddStatement( say );
+ }
+
+ return true;
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::PinnedDown( void )
+{
+ // this is a form of "need backup"
+ const float minRequestInterval = 10.0f;
+ if (m_needBackupInterval.IsLessThen( minRequestInterval ))
+ return;
+
+ m_needBackupInterval.Reset();
+
+ BotStatement *say = new BotStatement( this, REPORT_REQUEST_HELP, 10.0f );
+
+ // where are we
+ Place place = m_me->GetPlace();
+ SayWhere( say, place );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "PinnedDown" ) );
+ say->AttachMeme( new BotHelpMeme( place ) );
+ say->AddCondition( BotStatement::IS_IN_COMBAT );
+
+ AddStatement( say );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+/**
+ * If a friend said that they heard something, we don't want to say something similar
+ * for a while.
+ */
+void BotChatterInterface::FriendHeardNoise( void )
+{
+ m_heardNoiseTimer.Start( 20.0f );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::HeardNoise( const Vector &pos )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ if (m_heardNoiseTimer.IsElapsed())
+ {
+ // throttle frequency
+ m_heardNoiseTimer.Start( 20.0f );
+
+ // make rare, since many teammates may try to say this
+ if (RandomFloat( 0, 100 ) < 33)
+ {
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 5.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "HeardNoise" ) );
+ say->SetPlace( TheNavMesh->GetPlace( pos ) );
+ say->AttachMeme( new BotHeardNoiseMeme() );
+
+ AddStatement( say );
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::KilledMyEnemy( int victimID )
+{
+ // only report if we killed the last enemy in the area
+ if (m_me->GetNearbyEnemyCount() <= 1)
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_ENEMY_ACTION, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledMyEnemy" ) );
+ say->SetSubject( victimID );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::EnemiesRemaining( void )
+{
+ // only report if we killed the last enemy in the area
+ if (m_me->GetNearbyEnemyCount() > 1)
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_ENEMIES_REMAINING, 5.0f );
+ say->AppendPhrase( BotStatement::REMAINING_ENEMY_COUNT );
+ say->SetStartTime( gpGlobals->curtime + RandomFloat( 2.0f, 4.0f ) );
+
+ AddStatement( say );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::Affirmative( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_ACKNOWLEDGE, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Affirmative" ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::Negative( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_ACKNOWLEDGE, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "Negative" ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::GoingToPlantTheBomb( Place place )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ const float minInterval = 20.0f;
+ if (m_planInterval.IsLessThen( minInterval ))
+ return;
+
+ m_planInterval.Reset();
+
+ BotStatement *say = new BotStatement( this, REPORT_CRITICAL_EVENT, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "GoingToPlantBomb" ) );
+ say->SetPlace( place );
+ say->AttachMeme( new BotFollowMeme() );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::PlantingTheBomb( Place place )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_CRITICAL_EVENT, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "PlantingBomb" ) );
+ say->SetPlace( place );
+
+ Vector myOrigin = GetCentroid( m_me );
+ say->AttachMeme( new BotDefendHereMeme( myOrigin ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::TheyPickedUpTheBomb( void )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ // if we already know the bomb is not loose, this is old news
+ if (!m_me->GetGameState()->IsBombLoose())
+ return;
+
+ // update our gamestate - use our own position for now
+ const Vector &myOrigin = GetCentroid( m_me );
+ m_me->GetGameState()->UpdateBomber( myOrigin );
+
+ // tell our teammates
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "TheyPickedUpTheBomb" ) );
+
+ say->AttachMeme( new BotBombStatusMeme( CSGameState::MOVING, myOrigin ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::SpottedBomber( CBasePlayer *bomber )
+{
+ const Vector &bomberOrigin = GetCentroid( bomber );
+
+ if (m_me->GetGameState()->IsBombMoving())
+ {
+ // if we knew where the bomber was, this is old news
+ const Vector *bomberPos = m_me->GetGameState()->GetBombPosition();
+ const float closeRangeSq = 1000.0f * 1000.0f;
+ if (bomberPos && (bomberOrigin - *bomberPos).LengthSqr() < closeRangeSq)
+ return;
+ }
+
+ // update our gamestate
+ m_me->GetGameState()->UpdateBomber( bomberOrigin );
+
+ // tell our teammates
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ // where is the bomber
+ Place place = TheNavMesh->GetPlace( bomberOrigin );
+ SayWhere( say, place );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "SpottedBomber" ) );
+
+ say->SetSubject( bomber->entindex() );
+
+ //say->AttachMeme( new BotHelpMeme( place ) );
+ say->AttachMeme( new BotBombStatusMeme( CSGameState::MOVING, bomberOrigin ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::SpottedLooseBomb( CBaseEntity *bomb )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ // if we already know the bomb is loose, this is old news
+ if (m_me->GetGameState()->IsBombLoose())
+ return;
+
+ // update our gamestate
+ m_me->GetGameState()->UpdateLooseBomb( bomb->GetAbsOrigin() );
+
+ if (m_spottedLooseBombTimer.IsElapsed())
+ {
+ // throttle frequency
+ m_spottedLooseBombTimer.Start( 10.0f );
+
+ // tell our teammates
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ // where is the bomb
+ Place place = TheNavMesh->GetPlace( bomb->GetAbsOrigin() );
+ SayWhere( say, place );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "SpottedLooseBomb" ) );
+
+ if (TheCSBots()->GetLooseBomb())
+ say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, bomb->GetAbsOrigin() ) );
+
+ AddStatement( say );
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::GuardingLooseBomb( CBaseEntity *bomb )
+{
+ // if we already know the bomb is loose, this is old news
+// if (m_me->GetGameState()->IsBombLoose())
+// return;
+
+ if (TheCSBots()->IsRoundOver() || !bomb)
+ return;
+
+ const float minInterval = 20.0f;
+ if (m_planInterval.IsLessThen( minInterval ))
+ return;
+
+ m_planInterval.Reset();
+
+ // update our gamestate
+ m_me->GetGameState()->UpdateLooseBomb( bomb->GetAbsOrigin() );
+
+ // tell our teammates
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ // where is the bomb
+ Place place = TheNavMesh->GetPlace( bomb->GetAbsOrigin() );
+ SayWhere( say, place );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "GuardingLooseBomb" ) );
+
+ if (TheCSBots()->GetLooseBomb())
+ say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, bomb->GetAbsOrigin() ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::RequestBombLocation( void )
+{
+ // only ask once per round
+ if (m_requestedBombLocation)
+ return;
+
+ m_requestedBombLocation = true;
+
+ // tell our teammates
+ BotStatement *say = new BotStatement( this, REPORT_REQUEST_INFORMATION, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "WhereIsTheBomb" ) );
+
+ say->AttachMeme( new BotWhereBombMeme() );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::BombsiteClear( int zoneIndex )
+{
+ const CCSBotManager::Zone *zone = TheCSBots()->GetZone( zoneIndex );
+ if (zone == NULL)
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
+
+ SayWhere( say, TheNavMesh->GetPlace( zone->m_center ) );
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "BombsiteClear" ) );
+
+ say->AttachMeme( new BotBombsiteStatusMeme( zoneIndex, BotBombsiteStatusMeme::CLEAR ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::FoundPlantedBomb( int zoneIndex )
+{
+ const CCSBotManager::Zone *zone = TheCSBots()->GetZone( zoneIndex );
+ if (zone == NULL)
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "PlantedBombPlace" ) );
+ say->SetPlace( TheNavMesh->GetPlace( zone->m_center ) );
+
+ say->AttachMeme( new BotBombsiteStatusMeme( zoneIndex, BotBombsiteStatusMeme::PLANTED ) );
+
+ AddStatement( say );
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::Scared( void )
+{
+ const float minInterval = 10.0f;
+ if (m_scaredInterval.IsLessThen( minInterval ))
+ return;
+
+ m_scaredInterval.Reset();
+
+ BotStatement *say = new BotStatement( this, REPORT_EMOTE, 1.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "ScaredEmote" ) );
+ say->AddCondition( BotStatement::IS_IN_COMBAT );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::CelebrateWin( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_EMOTE, 15.0f );
+
+ // wait a bit before speaking
+ say->SetStartTime( gpGlobals->curtime + RandomFloat( 2.0f, 5.0f ) );
+
+ const float quickRound = 45.0f;
+
+ if (m_me->GetFriendsRemaining() == 0)
+ {
+ // we were the last man standing
+ if (TheCSBots()->GetElapsedRoundTime() < quickRound)
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRoundQuickly" ) );
+ else if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "LastManStanding" ) );
+ }
+ else
+ {
+ if (TheCSBots()->GetElapsedRoundTime() < quickRound)
+ {
+ if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRoundQuickly" ) );
+ }
+ else if (RandomFloat( 0.0f, 100.0f ) < 10.0f)
+ {
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRound" ) );
+ }
+ }
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::AnnouncePlan( const char *phraseName, Place place )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_MY_PLAN, 10.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
+ say->SetPlace( place );
+
+ // wait at least a short time after round start
+ say->SetStartTime( TheCSBots()->GetRoundStartTime() + RandomFloat( 2.0, 3.0f ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::GuardingBombsite( Place place )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ const float minInterval = 20.0f;
+ if (m_planInterval.IsLessThen( minInterval ))
+ return;
+
+ m_planInterval.Reset();
+
+ AnnouncePlan( "GoingToDefendBombsite", place );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::GuardingHostages( Place place, bool isPlan )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ const float minInterval = 20.0f;
+ if (m_planInterval.IsLessThen( minInterval ))
+ return;
+
+ m_planInterval.Reset();
+
+ if (isPlan)
+ AnnouncePlan( "GoingToGuardHostages", place );
+ else
+ Say( "GuardingHostages" );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::GuardingHostageEscapeZone( bool isPlan )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ const float minInterval = 20.0f;
+ if (m_planInterval.IsLessThen( minInterval ))
+ return;
+
+ m_planInterval.Reset();
+
+ if (isPlan)
+ AnnouncePlan( "GoingToGuardHostageEscapeZone", UNDEFINED_PLACE );
+ else
+ Say( "GuardingHostageEscapeZone" );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::HostagesBeingTaken( void )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "HostagesBeingTaken" ) );
+ say->AttachMeme( new BotHostageBeingTakenMeme() );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::HostagesTaken( void )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "HostagesTaken" ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::TalkingToHostages( void )
+{
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::EscortingHostages( void )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ if (m_escortingHostageTimer.IsElapsed())
+ {
+ // throttle frequency
+ m_escortingHostageTimer.Start( 10.0f );
+
+ BotStatement *say = new BotStatement( this, REPORT_MY_PLAN, 5.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "EscortingHostages" ) );
+
+ AddStatement( say );
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::HostageDown( void )
+{
+ if (TheCSBots()->IsRoundOver())
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "HostageDown" ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::Encourage( const char *phraseName, float repeatInterval, float lifetime )
+{
+ if (m_encourageTimer.IsElapsed())
+ {
+ Say( phraseName, lifetime );
+ m_encourageTimer.Start( repeatInterval );
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::KilledFriend( void )
+{
+ BotStatement *say = new BotStatement( this, REPORT_KILLED_FRIEND, 2.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledFriend" ) );
+
+ // give them time to react
+ say->SetStartTime( gpGlobals->curtime + RandomFloat( 0.5f, 1.0f ) );
+
+ AddStatement( say );
+}
+
+//---------------------------------------------------------------------------------------------------------------
+void BotChatterInterface::FriendlyFire( void )
+{
+ if ( !friendlyfire.GetBool() )
+ return;
+
+ BotStatement *say = new BotStatement( this, REPORT_FRIENDLY_FIRE, 1.0f );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( "FriendlyFire" ) );
+
+ // give them time to react
+ say->SetStartTime( gpGlobals->curtime + RandomFloat( 0.3f, 0.5f ) );
+
+ AddStatement( say );
+}
+
+
diff --git a/game/server/cstrike/bot/cs_bot_chatter.h b/game/server/cstrike/bot/cs_bot_chatter.h
new file mode 100644
index 0000000..47ba802
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_chatter.h
@@ -0,0 +1,656 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bot radio chatter system
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#ifndef CS_BOT_CHATTER_H
+#define CS_BOT_CHATTER_H
+
+#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
+
+#include "nav_mesh.h"
+#include "cs_gamestate.h"
+
+class CCSBot;
+class BotChatterInterface;
+
+#define MAX_PLACES_PER_MAP 64
+
+typedef unsigned int PlaceCriteria;
+
+typedef unsigned int CountCriteria;
+#define UNDEFINED_COUNT 0xFFFF
+#define COUNT_CURRENT_ENEMIES 0xFF // use the number of enemies we see right when we speak
+#define COUNT_MANY 4 // equal to or greater than this is "many"
+
+#define UNDEFINED_SUBJECT (-1)
+
+/// @todo Make Place a class with member fuctions for this
+const Vector *GetRandomSpotAtPlace( Place place );
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * A meme is a unit information that bots use to
+ * transmit information to each other via the radio
+ */
+class BotMeme
+{
+public:
+ void Transmit( CCSBot *sender ) const; ///< transmit meme to other bots
+ // It is a best practice to always have a virtual destructor in an interface
+ // class. Otherwise if the derived classes have destructors they will not be
+ // called.
+ virtual ~BotMeme() {}
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const = 0; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotHelpMeme : public BotMeme
+{
+public:
+ BotHelpMeme( Place place = UNDEFINED_PLACE )
+ {
+ m_place = place;
+ }
+
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+
+private:
+ Place m_place; ///< where the help is needed
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotBombsiteStatusMeme : public BotMeme
+{
+public:
+ enum StatusType { CLEAR, PLANTED };
+
+ BotBombsiteStatusMeme( int zoneIndex, StatusType status )
+ {
+ m_zoneIndex = zoneIndex;
+ m_status = status;
+ }
+
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+
+private:
+ int m_zoneIndex; ///< the bombsite
+ StatusType m_status; ///< whether it is cleared or the bomb is there (planted)
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotBombStatusMeme : public BotMeme
+{
+public:
+ BotBombStatusMeme( CSGameState::BombState state, const Vector &pos )
+ {
+ m_state = state;
+ m_pos = pos;
+ }
+
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+
+private:
+ CSGameState::BombState m_state;
+ Vector m_pos;
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotFollowMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotDefendHereMeme : public BotMeme
+{
+public:
+ BotDefendHereMeme( const Vector &pos )
+ {
+ m_pos = pos;
+ }
+
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+
+private:
+ Vector m_pos;
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotWhereBombMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotRequestReportMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotAllHostagesGoneMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotHostageBeingTakenMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotHeardNoiseMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+class BotWarnSniperMeme : public BotMeme
+{
+public:
+ virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
+};
+
+//----------------------------------------------------------------------------------------------------
+enum BotStatementType
+{
+ REPORT_VISIBLE_ENEMIES,
+ REPORT_ENEMY_ACTION,
+ REPORT_MY_CURRENT_TASK,
+ REPORT_MY_INTENTION,
+ REPORT_CRITICAL_EVENT,
+ REPORT_REQUEST_HELP,
+ REPORT_REQUEST_INFORMATION,
+ REPORT_ROUND_END,
+ REPORT_MY_PLAN,
+ REPORT_INFORMATION,
+ REPORT_EMOTE,
+ REPORT_ACKNOWLEDGE, ///< affirmative or negative
+ REPORT_ENEMIES_REMAINING,
+ REPORT_FRIENDLY_FIRE,
+ REPORT_KILLED_FRIEND,
+ REPORT_ENEMY_LOST,
+
+ NUM_BOT_STATEMENT_TYPES
+};
+
+//----------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------
+/**
+ * BotSpeakables are the smallest unit of bot chatter.
+ * They represent a specific wav file of a phrase, and the criteria for which it is useful
+ */
+class BotSpeakable
+{
+public:
+ BotSpeakable();
+ ~BotSpeakable();
+ char *m_phrase;
+ float m_duration;
+ PlaceCriteria m_place;
+ CountCriteria m_count;
+};
+typedef CUtlVector< BotSpeakable * > BotSpeakableVector;
+typedef CUtlVector< BotSpeakableVector * > BotVoiceBankVector;
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
+ */
+class BotPhrase
+{
+public:
+ char *GetSpeakable( int bankIndex, float *duration = NULL ) const; ///< return a random speakable and its duration in seconds that meets the current criteria
+
+ // NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
+ void ClearCriteria( void ) const;
+ void SetPlaceCriteria( PlaceCriteria place ) const; ///< all returned phrases must have this place criteria
+ void SetCountCriteria( CountCriteria count ) const; ///< all returned phrases must have this count criteria
+
+ const char *GetName( void ) const { return m_name; }
+ const unsigned int GetPlace( void ) const { return m_place; }
+ RadioType GetRadioEquivalent( void ) const { return m_radioEvent; } ///< return equivalent "standard radio" event
+ bool IsImportant( void ) const { return m_isImportant; } ///< return true if this phrase is part of an important statement
+
+ bool IsPlace( void ) const { return m_isPlace; }
+
+ void Randomize( void ); ///< randomly shuffle the speakable order
+
+private:
+ friend class BotPhraseManager;
+ BotPhrase( bool isPlace );
+ ~BotPhrase();
+
+ char *m_name;
+ Place m_place;
+ bool m_isPlace; ///< true if this is a Place phrase
+ RadioType m_radioEvent; ///< equivalent radio event
+ bool m_isImportant; ///< mission-critical statement
+
+ mutable BotVoiceBankVector m_voiceBank; ///< array of voice banks (arrays of speakables)
+ CUtlVector< int > m_count; ///< number of speakables
+ mutable CUtlVector< int > m_index; ///< index of next speakable to return
+ int m_numVoiceBanks; ///< number of voice banks that have been initialized
+ void InitVoiceBank( int bankIndex ); ///< sets up the vector of voice banks for the first bankIndex voice banks
+
+ mutable PlaceCriteria m_placeCriteria;
+ mutable CountCriteria m_countCriteria;
+};
+typedef CUtlVector<BotPhrase *> BotPhraseList;
+
+inline void BotPhrase::ClearCriteria( void ) const
+{
+ m_placeCriteria = ANY_PLACE;
+ m_countCriteria = UNDEFINED_COUNT;
+}
+
+inline void BotPhrase::SetPlaceCriteria( PlaceCriteria place ) const
+{
+ m_placeCriteria = place;
+}
+
+inline void BotPhrase::SetCountCriteria( CountCriteria count ) const
+{
+ m_countCriteria = count;
+}
+
+enum BotChatterOutputType
+{
+ BOT_CHATTER_RADIO,
+ BOT_CHATTER_VOICE
+};
+typedef CUtlVector<BotChatterOutputType> BotOutputList;
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
+ */
+class BotPhraseManager
+{
+public:
+ BotPhraseManager( void );
+ ~BotPhraseManager();
+
+ bool Initialize( const char *filename, int bankIndex ); ///< initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
+
+ void OnRoundRestart( void ); ///< invoked when round resets
+ void OnMapChange( void ); ///< invoked when map changes
+ void Reset( void );
+
+ const BotPhrase *GetPhrase( const char *name ) const; ///< given a name, return the associated phrase collection
+ const BotPhrase *GetPainPhrase( void ) const { return m_painPhrase; } ///< optimization, replaces a static pointer to the phrase
+ const BotPhrase *GetAgreeWithPlanPhrase( void ) const { return m_agreeWithPlanPhrase; } ///< optimization, replaces a static pointer to the phrase
+
+ const BotPhrase *GetPlace( const char *name ) const; ///< given a name, return the associated Place phrase collection
+ const BotPhrase *GetPlace( unsigned int id ) const; ///< given an id, return the associated Place phrase collection
+
+ const BotPhraseList *GetPlaceList( void ) const { return &m_placeList; }
+
+ float GetPlaceStatementInterval( Place where ) const; ///< return time last statement of given type was emitted by a teammate for the given place
+ void ResetPlaceStatementInterval( Place where ); ///< set time of last statement of given type was emitted by a teammate for the given place
+
+ BotChatterOutputType GetOutputType( int voiceBank ) const;
+
+private:
+ BotPhraseList m_list; ///< master list of all phrase collections
+ BotPhraseList m_placeList; ///< master list of all Place phrases
+
+ BotOutputList m_output;
+
+ const BotPhrase *m_painPhrase;
+ const BotPhrase *m_agreeWithPlanPhrase;
+
+ struct PlaceTimeInfo
+ {
+ Place placeID;
+ IntervalTimer timer;
+ };
+ mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
+ mutable int m_placeCount;
+ int FindPlaceIndex( Place where ) const;
+};
+
+inline int BotPhraseManager::FindPlaceIndex( Place where ) const
+{
+ for( int i=0; i<m_placeCount; ++i )
+ if (m_placeStatementHistory[i].placeID == where)
+ return i;
+
+ // no such place - allocate it
+ if (m_placeCount < MAX_PLACES_PER_MAP)
+ {
+ m_placeStatementHistory[ ++m_placeCount ].placeID = where;
+ m_placeStatementHistory[ ++m_placeCount ].timer.Invalidate();
+ return m_placeCount-1;
+ }
+
+ // place directory is full
+ return -1;
+}
+
+/**
+ * Return time last statement of given type was emitted by a teammate for the given place
+ */
+inline float BotPhraseManager::GetPlaceStatementInterval( Place place ) const
+{
+ int index = FindPlaceIndex( place );
+
+ if (index < 0)
+ return 999999.9f;
+
+ if (index >= m_placeCount)
+ return 999999.9f;
+
+ return m_placeStatementHistory[ index ].timer.GetElapsedTime();
+}
+
+/**
+ * Set time of last statement of given type was emitted by a teammate for the given place
+ */
+inline void BotPhraseManager::ResetPlaceStatementInterval( Place place )
+{
+ int index = FindPlaceIndex( place );
+
+ if (index < 0)
+ return;
+
+ if (index >= m_placeCount)
+ return;
+
+ // update entry
+ m_placeStatementHistory[ index ].timer.Reset();
+}
+
+extern BotPhraseManager *TheBotPhrases;
+
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Statements are meaningful collections of phrases
+ */
+class BotStatement
+{
+public:
+ BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration );
+ ~BotStatement();
+
+ BotChatterInterface *GetChatter( void ) const { return m_chatter; }
+ CCSBot *GetOwner( void ) const;
+
+ BotStatementType GetType( void ) const { return m_type; } ///< return the type of statement this is
+ bool IsImportant( void ) const; ///< return true if this statement is "important" and not personality chatter
+
+ bool HasSubject( void ) const { return (m_subject == UNDEFINED_SUBJECT) ? false : true; }
+ void SetSubject( int playerID ) { m_subject = playerID; } ///< who this statement is about
+ int GetSubject( void ) const { return m_subject; } ///< who this statement is about
+
+ bool HasPlace( void ) const { return (GetPlace()) ? true : false; }
+ Place GetPlace( void ) const; ///< if this statement refers to a specific place, return that place
+ void SetPlace( Place where ) { m_place = where; } ///< explicitly set place
+
+ bool HasCount( void ) const; ///< return true if this statement has an associated count
+
+ bool IsRedundant( const BotStatement *say ) const; ///< return true if this statement is the same as the given one
+ bool IsObsolete( void ) const; ///< return true if this statement is no longer appropriate to say
+ void Convert( const BotStatement *say ); ///< possibly change what were going to say base on what teammate is saying
+
+ void AppendPhrase( const BotPhrase *phrase );
+
+ void SetStartTime( float timestamp ) { m_startTime = timestamp; } ///< define the earliest time this statement can be spoken
+ float GetStartTime( void ) const { return m_startTime; }
+
+ enum ConditionType
+ {
+ IS_IN_COMBAT,
+ RADIO_SILENCE,
+ ENEMIES_REMAINING,
+
+ NUM_CONDITIONS
+ };
+
+ void AddCondition( ConditionType condition ); ///< conditions must be true for the statement to be spoken
+ bool IsValid( void ) const; ///< verify all attached conditions
+
+ enum ContextType
+ {
+ CURRENT_ENEMY_COUNT,
+ REMAINING_ENEMY_COUNT,
+ SHORT_DELAY,
+ LONG_DELAY,
+ ACCUMULATE_ENEMIES_DELAY
+ };
+ void AppendPhrase( ContextType contextPhrase ); ///< special phrases that depend on the context
+
+ bool Update( void ); ///< emit statement over time, return false if statement is done
+ bool IsSpeaking( void ) const { return m_isSpeaking; } ///< return true if this statement is currently being spoken
+ float GetTimestamp( void ) const { return m_timestamp; } ///< get time statement was created (but not necessarily started talking)
+
+ void AttachMeme( BotMeme *meme ); ///< attach a meme to this statement, to be transmitted to other friendly bots when spoken
+
+private:
+ friend class BotChatterInterface;
+
+ BotChatterInterface *m_chatter; ///< the chatter system this statement is part of
+
+ BotStatement *m_next, *m_prev; ///< linked list hooks
+
+ BotStatementType m_type; ///< what kind of statement this is
+ int m_subject; ///< who this subject is about
+ Place m_place; ///< explicit place - note some phrases have implicit places as well
+ BotMeme *m_meme; ///< a statement can only have a single meme for now
+
+ float m_timestamp; ///< time when message was created
+ float m_startTime; ///< the earliest time this statement can be spoken
+ float m_expireTime; ///< time when this statement is no longer valid
+ float m_speakTimestamp; ///< time when message began being spoken
+ bool m_isSpeaking; ///< true if this statement is current being spoken
+
+ float m_nextTime; ///< time for next phrase to begin
+
+ enum { MAX_BOT_PHRASES = 4 };
+ struct
+ {
+ bool isPhrase;
+ union
+ {
+ const BotPhrase *phrase;
+ ContextType context;
+ };
+ }
+ m_statement[ MAX_BOT_PHRASES ];
+
+ enum { MAX_BOT_CONDITIONS = 4 };
+ ConditionType m_condition[ MAX_BOT_CONDITIONS ]; ///< conditions that must be true for the statement to be said
+ int m_conditionCount;
+
+ int m_index; ///< m_index refers to the phrase currently being spoken, or -1 if we havent started yet
+ int m_count;
+};
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * This class defines the interface to the bot radio chatter system
+ */
+class BotChatterInterface
+{
+public:
+ BotChatterInterface( CCSBot *me );
+ ~BotChatterInterface( );
+
+ void Reset( void ); ///< reset to initial state
+ void Update( void ); ///< process ongoing chatter
+
+ /// invoked when event occurs in the game (some events have NULL entities)
+ void OnDeath( void ); ///< invoked when we die
+
+ enum VerbosityType
+ {
+ NORMAL, ///< full chatter
+ MINIMAL, ///< only scenario-critical events
+ RADIO, ///< use the standard radio instead
+ OFF ///< no chatter at all
+ };
+ VerbosityType GetVerbosity( void ) const; ///< return our current level of verbosity
+
+ CCSBot *GetOwner( void ) const { return m_me; }
+
+ bool IsTalking( void ) const; ///< return true if we are currently talking
+ float GetRadioSilenceDuration( void ); ///< return time since any teammate said anything
+ void ResetRadioSilenceDuration( void );
+
+ enum { MUST_ADD = 1 };
+ void AddStatement( BotStatement *statement, bool mustAdd = false ); ///< register a statement for speaking
+ void RemoveStatement( BotStatement *statement ); ///< remove a statement
+
+ BotStatement *GetActiveStatement( void ); ///< returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
+ BotStatement *GetStatement( void ) const; ///< returns our current statement, or NULL if we aren't speaking
+
+ int GetPitch( void ) const { return m_pitch; }
+
+
+ //-- things the bots can say ---------------------------------------------------------------------
+ void Say( const char *phraseName, float lifetime = 3.0f, float delay = 0.0f );
+
+ void AnnouncePlan( const char *phraseName, Place where );
+ void Affirmative( void );
+ void Negative( void );
+
+ void EnemySpotted( void ); ///< report enemy sightings
+ void KilledMyEnemy( int victimID );
+ void EnemiesRemaining( void );
+
+ void SpottedSniper( void );
+ void FriendSpottedSniper( void );
+
+ void Clear( Place where );
+
+ void ReportIn( void ); ///< ask for current situation
+ void ReportingIn( void ); ///< report current situation
+
+ bool NeedBackup( void );
+ void PinnedDown( void );
+ void Scared( void );
+ void HeardNoise( const Vector &pos );
+ void FriendHeardNoise( void );
+
+ void TheyPickedUpTheBomb( void );
+ void GoingToPlantTheBomb( Place where );
+ void BombsiteClear( int zoneIndex );
+ void FoundPlantedBomb( int zoneIndex );
+ void PlantingTheBomb( Place where );
+ void SpottedBomber( CBasePlayer *bomber );
+ void SpottedLooseBomb( CBaseEntity *bomb );
+ void GuardingLooseBomb( CBaseEntity *bomb );
+ void RequestBombLocation( void );
+
+ #define IS_PLAN true
+ void GuardingHostages( Place where, bool isPlan = false );
+ void GuardingHostageEscapeZone( bool isPlan = false );
+ void HostagesBeingTaken( void );
+ void HostagesTaken( void );
+ void TalkingToHostages( void );
+ void EscortingHostages( void );
+ void HostageDown( void );
+ void GuardingBombsite( Place where );
+
+ void CelebrateWin( void );
+
+ void Encourage( const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f ); ///< "encourage" the player to do the scenario
+
+ void KilledFriend( void );
+ void FriendlyFire( void );
+
+ bool SeesAtLeastOneEnemy( void ) const { return m_seeAtLeastOneEnemy; }
+
+private:
+ BotStatement *m_statementList; ///< list of all active/pending messages for this bot
+
+ void ReportEnemies( void ); ///< track nearby enemy count and generate enemy activity statements
+ bool ShouldSpeak( void ) const; ///< return true if we speaking makes sense now
+
+ CCSBot *m_me; ///< the bot this chatter is for
+
+ bool m_seeAtLeastOneEnemy;
+ float m_timeWhenSawFirstEnemy;
+ bool m_reportedEnemies;
+ bool m_requestedBombLocation; ///< true if we already asked where the bomb has been planted
+
+ int m_pitch;
+
+ static IntervalTimer m_radioSilenceInterval[ 2 ]; ///< one timer for each team
+
+ IntervalTimer m_needBackupInterval;
+ IntervalTimer m_spottedBomberInterval;
+ IntervalTimer m_scaredInterval;
+ IntervalTimer m_planInterval;
+ CountdownTimer m_spottedLooseBombTimer;
+ CountdownTimer m_heardNoiseTimer;
+ CountdownTimer m_escortingHostageTimer;
+ CountdownTimer m_warnSniperTimer;
+ static CountdownTimer m_encourageTimer; ///< timer to know when we can "encourage" the human player again - shared by all bots
+};
+
+inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity( void ) const
+{
+ const char *string = cv_bot_chatter.GetString();
+
+ if (string == NULL)
+ return NORMAL;
+
+ if (string[0] == 'm' || string[0] == 'M')
+ return MINIMAL;
+
+ if (string[0] == 'r' || string[0] == 'R')
+ return RADIO;
+
+ if (string[0] == 'o' || string[0] == 'O')
+ return OFF;
+
+ return NORMAL;
+}
+
+
+inline bool BotChatterInterface::IsTalking( void ) const
+{
+ if (m_statementList)
+ return m_statementList->IsSpeaking();
+
+ return false;
+}
+
+inline BotStatement *BotChatterInterface::GetStatement( void ) const
+{
+ return m_statementList;
+}
+
+
+inline void BotChatterInterface::Say( const char *phraseName, float lifetime, float delay )
+{
+ BotStatement *say = new BotStatement( this, REPORT_MY_INTENTION, lifetime );
+
+ say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
+
+ if (delay > 0.0f)
+ say->SetStartTime( gpGlobals->curtime + delay );
+
+ AddStatement( say );
+}
+
+
+
+
+#endif // CS_BOT_CHATTER_H
diff --git a/game/server/cstrike/bot/cs_bot_event.cpp b/game/server/cstrike/bot/cs_bot_event.cpp
new file mode 100644
index 0000000..24bdae4
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_event.cpp
@@ -0,0 +1,427 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "KeyValues.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Checks if the bot can hear the event
+ */
+void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
+{
+ /// @todo Listen to non-player sounds
+ if (player == NULL)
+ return;
+
+ // don't pay attention to noise that friends make
+ if (!IsEnemy( player ))
+ return;
+
+ Vector playerOrigin = GetCentroid( player );
+ Vector myOrigin = GetCentroid( this );
+
+ // If the event occurs far from the triggering player, it may override the origin
+ if ( actualOrigin )
+ {
+ playerOrigin = *actualOrigin;
+ }
+
+ // check if noise is close enough for us to hear
+ const Vector *newNoisePosition = &playerOrigin;
+ float newNoiseDist = (myOrigin - *newNoisePosition).Length();
+ if (newNoiseDist < range)
+ {
+ // we heard the sound
+ if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
+ {
+ PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n",
+ (FStrEq( "weapon_fire", event->GetName() )) ? "Weapon fire " : "",
+ (player) ? player->GetPlayerName() : "NULL",
+ (priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
+ gpGlobals->curtime );
+ }
+
+ // should we pay attention to it
+ // if noise timestamp is zero, there is no prior noise
+ if (m_noiseTimestamp > 0.0f)
+ {
+ // only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
+ const float shortTermMemoryTime = 3.0f;
+ if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
+ {
+ // prior noise is more important - ignore new one
+ if (priority < m_noisePriority)
+ return;
+
+ float oldNoiseDist = (myOrigin - m_noisePosition).Length();
+ if (newNoiseDist >= oldNoiseDist)
+ return;
+ }
+ }
+
+ // find the area in which the noise occured
+ /// @todo Better handle when noise occurs off the nav mesh
+ /// @todo Make sure noise area is not through a wall or ceiling from source of noise
+ /// @todo Change GetNavTravelTime to better deal with NULL destination areas
+ CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
+ if (noiseArea == NULL)
+ {
+ PrintIfWatched( " *** Noise occurred off the nav mesh - ignoring!\n" );
+ return;
+ }
+
+ m_noiseArea = noiseArea;
+
+ // remember noise priority
+ m_noisePriority = priority;
+
+ // randomize noise position in the area a bit - hearing isn't very accurate
+ // the closer the noise is, the more accurate our placement
+ /// @todo Make sure not to pick a position on the opposite side of ourselves.
+ const float maxErrorRadius = 400.0f;
+ const float maxHearingRange = 2000.0f;
+ float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;
+
+ m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
+ m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );
+
+ // note the *travel distance* to the noise
+ m_noiseTravelDistance = GetTravelDistanceToPlayer( (CCSPlayer *)player );
+
+ // make sure noise position remains in the same area
+ m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );
+
+ // note when we heard the noise
+ m_noiseTimestamp = gpGlobals->curtime;
+
+ // if we hear a nearby enemy, become alert
+ const float nearbyNoiseRange = 1000.0f;
+ if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
+ {
+ BecomeAlert();
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnFlashbangDetonate( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnGrenadeBounce( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBulletImpact( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // Construct an origin for the sound, since it can be far from the originating player
+ Vector actualOrigin;
+ actualOrigin.x = event->GetFloat( "x", 0.0f );
+ actualOrigin.y = event->GetFloat( "y", 0.0f );
+ actualOrigin.z = event->GetFloat( "z", 0.0f );
+
+ /// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
+ //OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBreakProp( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBreakBreakable( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnDoorMoving( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnHostageFollows( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // player_follows needs a player
+ if (player == NULL)
+ return;
+
+ // don't pay attention to noise that friends make
+ if (!IsEnemy( player ))
+ return;
+
+ Vector playerOrigin = GetCentroid( player );
+ Vector myOrigin = GetCentroid( this );
+ const float range = 1200.0f;
+
+ // this is here so T's not only act on the noise, but look at it, too
+ if (GetTeamNumber() == TEAM_TERRORIST)
+ {
+ // make sure we can hear the noise
+ if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
+ return;
+
+ // tell our teammates that the hostages are being taken
+ GetChatter()->HostagesBeingTaken();
+
+ // only move if we hear them being rescued and can't see any hostages
+ if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
+ {
+ // since we are guarding the hostages, presumably we know where they are
+ // if we're close enough to "hear" this event, either go to where the event occured,
+ // or head for an escape zone to head them off
+ if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
+ {
+ //const float headOffChance = 33.3f;
+ if (true) // || RandomFloat( 0, 100 ) < headOffChance)
+ {
+ // head them off at a rescue zone
+ if (GuardRandomZone())
+ {
+ SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
+ SetDisposition( CCSBot::OPPORTUNITY_FIRE );
+ PrintIfWatched( "Trying to beat them to an escape zone!\n" );
+ }
+ }
+ else
+ {
+ SetTask( SEEK_AND_DESTROY );
+ StandUp();
+ Run();
+ MoveTo( playerOrigin, FASTEST_ROUTE );
+ }
+ }
+ }
+ }
+ else
+ {
+ // CT's don't care about this noise
+ return;
+ }
+
+ OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnRoundEnd( IGameEvent *event )
+{
+ // Morale adjustments happen even for dead players
+ int winner = event->GetInt( "winner" );
+ switch ( winner )
+ {
+ case WINNER_TER:
+ if (GetTeamNumber() == TEAM_CT)
+ {
+ DecreaseMorale();
+ }
+ else
+ {
+ IncreaseMorale();
+ }
+ break;
+
+ case WINNER_CT:
+ if (GetTeamNumber() == TEAM_CT)
+ {
+ IncreaseMorale();
+ }
+ else
+ {
+ DecreaseMorale();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ m_gameState.OnRoundEnd( event );
+
+ if ( !IsAlive() )
+ return;
+
+ if ( event->GetInt( "winner" ) == WINNER_TER )
+ {
+ if (GetTeamNumber() == TEAM_TERRORIST)
+ GetChatter()->CelebrateWin();
+ }
+ else if ( event->GetInt( "winner" ) == WINNER_CT )
+ {
+ if (GetTeamNumber() == TEAM_CT)
+ GetChatter()->CelebrateWin();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnRoundStart( IGameEvent *event )
+{
+ m_gameState.OnRoundStart( event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnHostageRescuedAll( IGameEvent *event )
+{
+ m_gameState.OnHostageRescuedAll( event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnNavBlocked( IGameEvent *event )
+{
+ if ( event->GetBool( "blocked" ) )
+ {
+ unsigned int areaID = event->GetInt( "area" );
+ if ( areaID )
+ {
+ // An area was blocked off. Reset our path if it has this area on it.
+ for( int i=0; i<m_pathLength; ++i )
+ {
+ const ConnectInfo *info = &m_path[ i ];
+ if ( info->area && info->area->GetID() == areaID )
+ {
+ DestroyPath();
+ return;
+ }
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when bot enters a nav area
+ */
+void CCSBot::OnEnteredNavArea( CNavArea *newArea )
+{
+ // assume that we "clear" an area of enemies when we enter it
+ newArea->SetClearedTimestamp( GetTeamNumber()-1 );
+
+ // if we just entered a 'stop' area, set the flag
+ if ( newArea->GetAttributes() & NAV_MESH_STOP )
+ {
+ m_isStopping = true;
+ }
+
+ /// @todo Flag these areas as spawn areas during load
+ if (IsAtEnemySpawn())
+ {
+ m_hasVisitedEnemySpawn = true;
+ }
+}
diff --git a/game/server/cstrike/bot/cs_bot_event_bomb.cpp b/game/server/cstrike/bot/cs_bot_event_bomb.cpp
new file mode 100644
index 0000000..b1dd75d
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_event_bomb.cpp
@@ -0,0 +1,158 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "KeyValues.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombPickedUp( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ if (GetTeamNumber() == TEAM_CT && player)
+ {
+ // check if we're close enough to hear it
+ const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
+ Vector myOrigin = GetCentroid( this );
+
+ if ((myOrigin - player->GetAbsOrigin()).LengthSqr() < bombPickupHearRangeSq)
+ {
+ GetChatter()->TheyPickedUpTheBomb();
+ GetGameState()->UpdateBomber( player->GetAbsOrigin() );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombPlanted( IGameEvent *event )
+{
+ m_gameState.OnBombPlanted( event );
+
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // if we're a TEAM_CT, forget what we're doing and go after the bomb
+ if (GetTeamNumber() == TEAM_CT)
+ {
+ Idle();
+ }
+
+ // if we are following someone, stop following
+ if (IsFollowing())
+ {
+ StopFollowing();
+ Idle();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombBeep( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ CBaseEntity *entity = UTIL_EntityByIndex( event->GetInt( "entindex" ) );
+ Vector myOrigin = GetCentroid( this );
+
+ // if we don't know where the bomb is, but heard it beep, we've discovered it
+ if (GetGameState()->IsPlantedBombLocationKnown() == false && entity)
+ {
+ // check if we're close enough to hear it
+ const float bombBeepHearRangeSq = 1500.0f * 1500.0f;
+ if ((myOrigin - entity->GetAbsOrigin()).LengthSqr() < bombBeepHearRangeSq)
+ {
+ // radio the news to our team
+ if (GetTeamNumber() == TEAM_CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
+ {
+ const CCSBotManager::Zone *zone = TheCSBots()->GetZone( entity->GetAbsOrigin() );
+ if (zone)
+ GetChatter()->FoundPlantedBomb( zone->m_index );
+ }
+
+ // remember where the bomb is
+ GetGameState()->UpdatePlantedBomb( entity->GetAbsOrigin() );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombDefuseBegin( IGameEvent *event )
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombDefused( IGameEvent *event )
+{
+ m_gameState.OnBombDefused( event );
+
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ if (GetTeamNumber() == TEAM_CT)
+ {
+ if (TheCSBots()->GetBombTimeLeft() < 2.0f)
+ GetChatter()->Say( "BarelyDefused" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombDefuseAbort( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ PrintIfWatched( "BOMB DEFUSE ABORTED\n" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnBombExploded( IGameEvent *event )
+{
+ m_gameState.OnBombExploded( event );
+}
+
+
+
diff --git a/game/server/cstrike/bot/cs_bot_event_player.cpp b/game/server/cstrike/bot/cs_bot_event_player.cpp
new file mode 100644
index 0000000..d5734ab
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_event_player.cpp
@@ -0,0 +1,227 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "KeyValues.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnPlayerDeath( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ Vector playerOrigin = (player) ? GetCentroid( player ) : Vector( 0, 0, 0 );
+
+ CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
+ CBasePlayer *victim = player;
+
+ CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>( other ) : NULL;
+
+ // if the human player died in the single player game, tell the team
+ if (CSGameRules()->IsCareer() && victim && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
+ {
+ GetChatter()->Say( "CommanderDown", 20.0f );
+ }
+
+ // keep track of the last player we killed
+ if (killer == this)
+ {
+ m_lastVictimID = victim ? victim->entindex() : 0;
+ }
+
+ // react to teammate death
+ if (victim && victim->GetTeamNumber() == GetTeamNumber())
+ {
+ // note time of death
+ m_friendDeathTimestamp = gpGlobals->curtime;
+
+ // chastise friendly fire from humans
+ if (killer && !killer->IsBot() && killer->GetTeamNumber() == GetTeamNumber() && killer != this)
+ {
+ GetChatter()->KilledFriend();
+ }
+
+ if (IsAttacking())
+ {
+ if (GetTimeSinceLastSawEnemy() > 0.4f)
+ {
+ PrintIfWatched( "Rethinking my attack due to teammate death\n" );
+
+ // allow us to sneak past windows, doors, etc
+ IgnoreEnemies( 1.0f );
+
+ // move to last known position of enemy - this could cause us to flank if
+ // the danger has changed due to our teammate's recent death
+ SetTask( MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetBotEnemy() );
+ MoveTo( GetLastKnownEnemyPosition() );
+ return;
+ }
+ }
+ else // not attacking
+ {
+ //
+ // If we just saw a nearby friend die, and we haven't yet acquired an enemy
+ // automatically acquire our dead friend's killer
+ //
+ if (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)
+ {
+ CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
+
+ // check that attacker is an enemy (for friendly fire, etc)
+ if (other && other->IsPlayer())
+ {
+ CCSPlayer *killer = static_cast<CCSPlayer *>( other );
+ if (killer->GetTeamNumber() != GetTeamNumber())
+ {
+ // check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
+ // snipers stay put
+ if (!IsSniper() && IsVisible( playerOrigin ))
+ {
+ // people are dying - we should hurry
+ Hurry( RandomFloat( 10.0f, 15.0f ) );
+
+ // if we're hiding with only our knife, be a little more cautious
+ const float knifeAmbushChance = 50.0f;
+ if (!IsHiding() || !IsUsingKnife() || RandomFloat( 0, 100 ) < knifeAmbushChance)
+ {
+ PrintIfWatched( "Attacking our friend's killer!\n" );
+ Attack( killer );
+ return;
+ }
+ }
+
+ // if friend was far away and we haven't seen an enemy in awhile, go to where our friend was killed
+ const float longHidingTime = 20.0f;
+ if (IsHunting() || IsInvestigatingNoise() || (IsHiding() && GetTask() != FOLLOW && GetHidingTime() > longHidingTime))
+ {
+ const float someTime = 10.0f;
+ const float farAway = 750.0f;
+ if (GetTimeSinceLastSawEnemy() > someTime && (playerOrigin - GetAbsOrigin()).IsLengthGreaterThan( farAway ))
+ {
+ PrintIfWatched( "Checking out where our friend was killed\n" );
+ MoveTo( playerOrigin, FASTEST_ROUTE );
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else // an enemy was killed
+ {
+ // forget our current noise - it may have come from the now dead enemy
+ ForgetNoise();
+
+ if (killer && killer->GetTeamNumber() == GetTeamNumber())
+ {
+ // only chatter about enemy kills if we see them occur, and they were the last one we see
+ if (GetNearbyEnemyCount() <= 1)
+ {
+ // report if number of enemies left is few and we killed the last one we saw locally
+ GetChatter()->EnemiesRemaining();
+
+ Vector victimOrigin = (victim) ? GetCentroid( victim ) : Vector( 0, 0, 0 );
+ if (IsVisible( victimOrigin, CHECK_FOV ))
+ {
+ // congratulate teammates on their kills
+ if (killer && killer != this)
+ {
+ float delay = RandomFloat( 2.0f, 3.0f );
+ if (killer->IsBot())
+ {
+ if (RandomFloat( 0.0f, 100.0f ) < 40.0f)
+ GetChatter()->Say( "NiceShot", 3.0f, delay );
+ }
+ else
+ {
+ // humans get the honorific
+ if (CSGameRules()->IsCareer())
+ GetChatter()->Say( "NiceShotCommander", 3.0f, delay );
+ else
+ GetChatter()->Say( "NiceShotSir", 3.0f, delay );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnPlayerRadio( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CCSPlayer *player = ToCSPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
+ if ( player == this )
+ return;
+
+ //
+ // Process radio events from our team
+ //
+ if (player && player->GetTeamNumber() == GetTeamNumber() )
+ {
+ /// @todo Distinguish between radio commands and responses
+ RadioType radioEvent = (RadioType)event->GetInt( "slot" );
+
+ if (radioEvent != RADIO_INVALID && radioEvent != RADIO_AFFIRMATIVE && radioEvent != RADIO_NEGATIVE && radioEvent != RADIO_REPORTING_IN)
+ {
+ m_lastRadioCommand = radioEvent;
+ m_lastRadioRecievedTimestamp = gpGlobals->curtime;
+ m_radioSubject = player;
+ m_radioPosition = GetCentroid( player );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnPlayerFallDamage( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // player_falldamage
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnPlayerFootstep( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false, IS_FOOTSTEP ); // player_footstep
+}
+
+
diff --git a/game/server/cstrike/bot/cs_bot_event_weapon.cpp b/game/server/cstrike/bot/cs_bot_event_weapon.cpp
new file mode 100644
index 0000000..90f8b9f
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_event_weapon.cpp
@@ -0,0 +1,167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "KeyValues.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnWeaponFire( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // for knife fighting - if our victim is attacking or reloading, rush him
+ /// @todo Propagate events into active state
+ if (GetEnemy() == player && IsUsingKnife())
+ {
+ ForceRun( 5.0f );
+ }
+
+ const float ShortRange = 1000.0f;
+ const float NormalRange = 2000.0f;
+
+ float range;
+
+ /// @todo Check weapon type (knives are pretty quiet)
+ /// @todo Use actual volume, account for silencers, etc.
+ CWeaponCSBase *weapon = (CWeaponCSBase *)((player)?player->GetActiveWeapon():NULL);
+
+ if (weapon == NULL)
+ return;
+
+ switch( weapon->GetWeaponID() )
+ {
+ // silent "firing"
+ case WEAPON_HEGRENADE:
+ case WEAPON_SMOKEGRENADE:
+ case WEAPON_FLASHBANG:
+ case WEAPON_SHIELDGUN:
+ case WEAPON_C4:
+ return;
+
+ // quiet
+ case WEAPON_KNIFE:
+ case WEAPON_TMP:
+ range = ShortRange;
+ break;
+
+ // M4A1 - check for silencer
+ case WEAPON_M4A1:
+ {
+ if (weapon->IsSilenced())
+ {
+ range = ShortRange;
+ }
+ else
+ {
+ range = NormalRange;
+ }
+ break;
+ }
+
+ // USP - check for silencer
+ case WEAPON_USP:
+ {
+ if (weapon->IsSilenced())
+ {
+ range = ShortRange;
+ }
+ else
+ {
+ range = NormalRange;
+ }
+ break;
+ }
+
+ // loud
+ case WEAPON_AWP:
+ range = 99999.0f;
+ break;
+
+ // normal
+ default:
+ range = NormalRange;
+ break;
+ }
+
+ OnAudibleEvent( event, player, range, PRIORITY_HIGH, true ); // weapon_fire
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnWeaponFireOnEmpty( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // for knife fighting - if our victim is attacking or reloading, rush him
+ /// @todo Propagate events into active state
+ if (GetEnemy() == player && IsUsingKnife())
+ {
+ ForceRun( 5.0f );
+ }
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_fire_on_empty
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnWeaponReload( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ // for knife fighting - if our victim is attacking or reloading, rush him
+ /// @todo Propagate events into active state
+ if (GetEnemy() == player && IsUsingKnife())
+ {
+ ForceRun( 5.0f );
+ }
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_reload
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::OnWeaponZoom( IGameEvent *event )
+{
+ if ( !IsAlive() )
+ return;
+
+ // don't react to our own events
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( player == this )
+ return;
+
+ OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_zoom
+}
+
+
+
diff --git a/game/server/cstrike/bot/cs_bot_init.cpp b/game/server/cstrike/bot/cs_bot_init.cpp
new file mode 100644
index 0000000..d6a5c77
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_init.cpp
@@ -0,0 +1,359 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "cs_shareddefs.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
+
+
+//--------------------------------------------------------------------------------------------------------------
+static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
+{
+ if ( TheCSBots() && TheCSBots()->IsServerActive() )
+ {
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if ( !player )
+ continue;
+
+ if ( !player->IsBot() || !IsEntityValid( player ) )
+ continue;
+
+ CCSBot *bot = dynamic_cast< CCSBot * >( player );
+
+ if ( !bot )
+ continue;
+
+ // set the bot's name
+ char botName[MAX_PLAYER_NAME_LENGTH];
+ UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
+
+ engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
+ }
+ }
+}
+
+
+ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
+ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
+ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
+ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
+ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
+ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
+ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
+ConVar cv_bot_quota( "bot_quota", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines the total number of bots in the game." );
+ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
+ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
+ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
+ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
+ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
+ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
+ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
+ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
+ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
+ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
+#ifdef CS_SHIELD_ENABLED
+ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
+#endif // CS_SHIELD_ENABLED
+ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
+ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED, "If nonzero, bots wait until a player joins before entering the game." );
+ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
+ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
+ConVar cv_bot_defer_to_human( "bot_defer_to_human", "0", FCVAR_REPLICATED, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
+ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
+ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
+ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
+ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
+ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
+ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
+
+
+extern void FinishClientPutInServer( CCSPlayer *pPlayer );
+
+
+//--------------------------------------------------------------------------------------------------------------
+// Engine callback for custom server commands
+void Bot_ServerCommand( void )
+{
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Constructor
+ */
+CCSBot::CCSBot( void ) : m_chatter( this ), m_gameState( this )
+{
+ m_hasJoined = false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Destructor
+ */
+CCSBot::~CCSBot()
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Prepare bot for action
+ */
+bool CCSBot::Initialize( const BotProfile *profile, int team )
+{
+ // extend
+ BaseClass::Initialize( profile, team );
+
+ // CS bot initialization
+ m_diedLastRound = false;
+ m_morale = POSITIVE; // starting a new round makes everyone a little happy
+
+ m_combatRange = RandomFloat( 325.0f, 425.0f );
+
+ // set initial safe time guess for this map
+ m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression();
+
+ m_name[0] = '\000';
+
+ ResetValues();
+
+ m_desiredTeam = team;
+
+ if (GetTeamNumber() == 0)
+ {
+ HandleCommand_JoinTeam( m_desiredTeam );
+ int desiredClass = GetProfile()->GetSkin();
+ if ( m_desiredTeam == TEAM_CT && desiredClass )
+ {
+ desiredClass = FIRST_CT_CLASS + desiredClass - 1;
+ }
+ else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass )
+ {
+ desiredClass = FIRST_T_CLASS + desiredClass - 1;
+ }
+ HandleCommand_JoinClass( desiredClass );
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset internal data to initial state
+ */
+void CCSBot::ResetValues( void )
+{
+ m_chatter.Reset();
+ m_gameState.Reset();
+
+ m_avoid = NULL;
+ m_avoidTimestamp = 0.0f;
+
+ m_hurryTimer.Invalidate();
+ m_alertTimer.Invalidate();
+ m_sneakTimer.Invalidate();
+ m_noiseBendTimer.Invalidate();
+ m_bendNoisePositionValid = false;
+
+ m_isStuck = false;
+ m_stuckTimestamp = 0.0f;
+ m_wiggleTimer.Invalidate();
+ m_stuckJumpTimer.Invalidate();
+
+ m_pathLength = 0;
+ m_pathIndex = 0;
+ m_areaEnteredTimestamp = 0.0f;
+ m_currentArea = NULL;
+ m_lastKnownArea = NULL;
+ m_isStopping = false;
+
+ m_avoidFriendTimer.Invalidate();
+ m_isFriendInTheWay = false;
+ m_isWaitingBehindFriend = false;
+ m_isAvoidingGrenade.Invalidate();
+
+ StopPanicking();
+
+ m_disposition = ENGAGE_AND_INVESTIGATE;
+
+ m_enemy = NULL;
+
+ m_grenadeTossState = NOT_THROWING;
+ m_initialEncounterArea = NULL;
+
+ m_wasSafe = true;
+
+ m_nearbyEnemyCount = 0;
+ m_enemyPlace = 0;
+ m_nearbyFriendCount = 0;
+ m_closestVisibleFriend = NULL;
+ m_closestVisibleHumanFriend = NULL;
+
+ for( int w=0; w<MAX_PLAYERS; ++w )
+ {
+ m_watchInfo[w].timestamp = 0.0f;
+ m_watchInfo[w].isEnemy = false;
+
+ m_playerTravelDistance[ w ] = -1.0f;
+ }
+
+ // randomly offset each bot's timer to spread computation out
+ m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
+ m_travelDistancePhase = 0;
+
+ m_isEnemyVisible = false;
+ m_visibleEnemyParts = NONE;
+ m_lastSawEnemyTimestamp = -999.9f;
+ m_firstSawEnemyTimestamp = 0.0f;
+ m_currentEnemyAcquireTimestamp = 0.0f;
+ m_isLastEnemyDead = true;
+ m_attacker = NULL;
+ m_attackedTimestamp = 0.0f;
+ m_enemyDeathTimestamp = 0.0f;
+ m_friendDeathTimestamp = 0.0f;
+ m_lastVictimID = 0;
+ m_isAimingAtEnemy = false;
+ m_fireWeaponTimestamp = 0.0f;
+ m_equipTimer.Invalidate();
+ m_zoomTimer.Invalidate();
+
+ m_isFollowing = false;
+ m_leader = NULL;
+ m_followTimestamp = 0.0f;
+ m_allowAutoFollowTime = 0.0f;
+
+ m_enemyQueueIndex = 0;
+ m_enemyQueueCount = 0;
+ m_enemyQueueAttendIndex = 0;
+ m_bomber = NULL;
+
+ m_isEnemySniperVisible = false;
+ m_sawEnemySniperTimer.Invalidate();
+
+ m_lookAroundStateTimestamp = 0.0f;
+ m_inhibitLookAroundTimestamp = 0.0f;
+
+ m_lookPitch = 0.0f;
+ m_lookPitchVel = 0.0f;
+ m_lookYaw = 0.0f;
+ m_lookYawVel = 0.0f;
+
+ m_aimOffsetTimestamp = 0.0f;
+ m_aimSpreadTimestamp = 0.0f;
+ m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
+
+ for( int p=0; p<MAX_PLAYERS; ++p )
+ {
+ m_partInfo[p].m_validFrame = 0;
+ }
+
+ m_spotEncounter = NULL;
+ m_spotCheckTimestamp = 0.0f;
+ m_peripheralTimestamp = 0.0f;
+
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+
+ m_lastOrigin = GetCentroid( this );
+
+ m_lastRadioCommand = RADIO_INVALID;
+ m_lastRadioRecievedTimestamp = 0.0f;
+ m_lastRadioSentTimestamp = 0.0f;
+ m_radioSubject = NULL;
+ m_voiceEndTimestamp = 0.0f;
+
+ m_hostageEscortCount = 0;
+ m_hostageEscortCountTimestamp = 0.0f;
+
+ m_noisePosition = Vector( 0, 0, 0 );
+ m_noiseTimestamp = 0.0f;
+
+ m_stateTimestamp = 0.0f;
+ m_task = SEEK_AND_DESTROY;
+ m_taskEntity = NULL;
+
+ m_approachPointCount = 0;
+ m_approachPointViewPosition.x = 99999999999.9f;
+ m_approachPointViewPosition.y = 0.0f;
+ m_approachPointViewPosition.z = 0.0f;
+
+ m_checkedHidingSpotCount = 0;
+
+ StandUp();
+ Run();
+ m_mustRunTimer.Invalidate();
+ m_waitTimer.Invalidate();
+ m_pathLadder = NULL;
+
+ m_repathTimer.Invalidate();
+
+ m_huntState.ClearHuntArea();
+ m_hasVisitedEnemySpawn = false;
+ m_stillTimer.Invalidate();
+
+ // adjust morale - if we died, our morale decreased,
+ // but if we live, no adjustement (round win/loss also adjusts morale)
+ if (m_diedLastRound)
+ DecreaseMorale();
+
+ m_diedLastRound = false;
+
+
+ // IsRogue() randomly changes this
+ m_isRogue = false;
+
+ m_surpriseTimer.Invalidate();
+
+ // even though these are EHANDLEs, they need to be NULL-ed
+ m_goalEntity = NULL;
+ m_avoid = NULL;
+ m_enemy = NULL;
+
+ for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
+ {
+ m_enemyQueue[i].player = NULL;
+ m_enemyQueue[i].isReloading = false;
+ m_enemyQueue[i].isProtectedByShield = false;
+ }
+
+ // start in idle state
+ m_isOpeningDoor = false;
+ StopAttacking();
+ Idle();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when bot is placed in map, and when bots are reset after a round ends.
+ * NOTE: For some reason, this can be called twice when a bot is added.
+ */
+void CCSBot::Spawn( void )
+{
+ // do the normal player spawn process
+ BaseClass::Spawn();
+
+ ResetValues();
+
+ V_strcpy_safe( m_name, GetPlayerName() );
+
+ Buy();
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_listen.cpp b/game/server/cstrike/bot/cs_bot_listen.cpp
new file mode 100644
index 0000000..54e47e2
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_listen.cpp
@@ -0,0 +1,230 @@
+//========= 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"
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CCSBot::IsNoiseHeard( void ) const
+{
+ if (m_noiseTimestamp <= 0.0f)
+ return false;
+
+ // primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
+ if (gpGlobals->curtime - m_noiseTimestamp >= GetProfile()->GetReactionTime())
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Listen for enemy noises, and determine if we should react to them.
+ * Returns true if heard a noise and should move to investigate.
+ */
+bool CCSBot::HeardInterestingNoise( void )
+{
+ if (IsBlind())
+ return false;
+
+ // don't investigate noises during safe time
+ if (!IsWellPastSafe())
+ return false;
+
+ // if our disposition is not to investigate, dont investigate
+ if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
+ return false;
+
+ // listen for enemy noises
+ if (IsNoiseHeard())
+ {
+ // if we are hiding, only react to noises very nearby, depending on how aggressive we are
+ if (IsAtHidingSpot() && GetNoiseRange() > 100.0f + 400.0f * GetProfile()->GetAggression())
+ return false;
+
+ // chance of investigating is inversely proportional to distance
+ const float maxNoiseDist = 3000.0f;
+ float chance = 100.0f * (1.0f - (GetNoiseRange()/maxNoiseDist));
+
+ // modify chance by number of friends remaining
+ // if we have lots of friends, presumably one of them is closer and will check it out
+ if (GetFriendsRemaining() >= 3)
+ {
+ float friendFactor = 5.0f * GetFriendsRemaining();
+ if (friendFactor > 50.0f)
+ friendFactor = 50.0f;
+
+ chance -= friendFactor;
+ }
+
+ if (RandomFloat( 0.0f, 100.0f ) <= chance)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we hear nearby threatening enemy gunfire within given range
+ * -1 == infinite range
+ */
+bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
+{
+ Vector myOrigin = GetCentroid( this );
+
+ // only attend to noise if it just happened
+ if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
+ return false;
+
+ // gunfire is high priority
+ if (m_noisePriority < PRIORITY_HIGH)
+ return false;
+
+ // check noise range
+ if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
+ return false;
+
+ // if we dont have line of sight, it's not threatening (cant get shot)
+ if (!CanSeeNoisePosition())
+ return false;
+
+ if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
+ {
+ // gunfire is only threatening if it is closer than our current enemy
+ float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
+ float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
+ const float muchCloserSq = 100.0f * 100.0f;
+ if (gunfireDistSq > enemyDistSq - muchCloserSq)
+ return false;
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we directly see where we think the noise came from
+ * NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
+ * NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
+ */
+bool CCSBot::CanSeeNoisePosition( void ) const
+{
+ trace_t result;
+ CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
+ if (result.fraction == 1.0f)
+ {
+ // we can see the source of the noise
+ return true;
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we decided to look towards the most recent noise source
+ * Assumes m_noisePosition is valid.
+ */
+bool CCSBot::UpdateLookAtNoise( void )
+{
+ // make sure a noise exists
+ if (!IsNoiseHeard())
+ {
+ return false;
+ }
+
+ Vector spot;
+
+ // if we have clear line of sight to noise position, look directly at it
+ if (CanSeeNoisePosition())
+ {
+ /// @todo adjust noise Z to keep consistent with current height while fighting
+ spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
+
+ // since we can see the noise spot, forget about it
+ ForgetNoise();
+ }
+ else
+ {
+ // line of sight is blocked, bend it
+
+ // the bending algorithm is very expensive, throttle how often it is done
+ if (m_noiseBendTimer.IsElapsed())
+ {
+ const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
+ m_noiseBendTimer.Start( noiseBendLOSInterval );
+
+ // line of sight is blocked, bend it
+ if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
+ {
+ m_bendNoisePositionValid = false;
+ return false;
+ }
+
+ m_bentNoisePosition = spot;
+ m_bendNoisePositionValid = true;
+ }
+ else if (m_bendNoisePositionValid)
+ {
+ // use result of prior bend computation
+ spot = m_bentNoisePosition;
+ }
+ else
+ {
+ // prior bend failed
+ return false;
+ }
+ }
+
+ // it's always important to look at enemy noises, because they come from ... enemies!
+ PriorityType pri = PRIORITY_HIGH;
+
+ // look longer if we're hiding
+ if (IsAtHidingSpot())
+ {
+ // if there is only one enemy left, look for a long time
+ if (GetEnemiesRemaining() == 1)
+ {
+ SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
+ }
+ else
+ {
+ SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
+ }
+ }
+ else
+ {
+ const float closeRange = 500.0f;
+ if (GetNoiseRange() < closeRange)
+ {
+ // look at nearby enemy noises for a longer time
+ SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
+ }
+ else
+ {
+ SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
+ }
+ }
+
+ return true;
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_manager.cpp b/game/server/cstrike/bot/cs_bot_manager.cpp
new file mode 100644
index 0000000..aec0ef3
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_manager.cpp
@@ -0,0 +1,2390 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
+
+#include "cbase.h"
+
+#include "cs_bot.h"
+#include "nav_area.h"
+#include "cs_gamerules.h"
+#include "shared_util.h"
+#include "KeyValues.h"
+#include "tier0/icommandline.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+CBotManager *TheBots = NULL;
+
+bool CCSBotManager::m_isMapDataLoaded = false;
+
+int g_nClientPutInServerOverrides = 0;
+
+
+void DrawOccupyTime( void );
+ConVar bot_show_occupy_time( "bot_show_occupy_time", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show when each nav area can first be reached by each team." );
+
+void DrawBattlefront( void );
+ConVar bot_show_battlefront( "bot_show_battlefront", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas where rushing players will initially meet." );
+
+int UTIL_CSSBotsInGame( void );
+
+ConVar bot_join_delay( "bot_join_delay", "0", FCVAR_GAMEDLL, "Prevents bots from joining the server for this many seconds after a map change." );
+
+/**
+ * Determine whether bots can be used or not
+ */
+inline bool AreBotsAllowed()
+{
+ // If they pass in -nobots, don't allow bots. This is for people who host servers, to
+ // allow them to disallow bots to enforce CPU limits.
+ const char *nobots = CommandLine()->CheckParm( "-nobots" );
+ if ( nobots )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void InstallBotControl( void )
+{
+ if ( TheBots != NULL )
+ delete TheBots;
+
+ TheBots = new CCSBotManager;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void RemoveBotControl( void )
+{
+ if ( TheBots != NULL )
+ delete TheBots;
+
+ TheBots = NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername )
+{
+ CBasePlayer *pPlayer = TheBots->AllocateAndBindBotEntity( pEdict );
+ if ( pPlayer )
+ {
+ pPlayer->SetPlayerName( playername );
+ }
+ ++g_nClientPutInServerOverrides;
+
+ return pPlayer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// Constructor
+CCSBotManager::CCSBotManager()
+{
+ m_zoneCount = 0;
+ SetLooseBomb( NULL );
+ m_serverActive = false;
+
+ m_isBombPlanted = false;
+ m_bombDefuser = NULL;
+ m_roundStartTimestamp = 0.0f;
+
+ m_eventListenersEnabled = true;
+ m_commonEventListeners.AddToTail( &m_PlayerFootstepEvent );
+ m_commonEventListeners.AddToTail( &m_PlayerRadioEvent );
+ m_commonEventListeners.AddToTail( &m_PlayerFallDamageEvent );
+ m_commonEventListeners.AddToTail( &m_BombBeepEvent );
+ m_commonEventListeners.AddToTail( &m_DoorMovingEvent );
+ m_commonEventListeners.AddToTail( &m_BreakPropEvent );
+ m_commonEventListeners.AddToTail( &m_BreakBreakableEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponFireEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponFireOnEmptyEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponReloadEvent );
+ m_commonEventListeners.AddToTail( &m_WeaponZoomEvent );
+ m_commonEventListeners.AddToTail( &m_BulletImpactEvent );
+ m_commonEventListeners.AddToTail( &m_GrenadeBounceEvent );
+ m_commonEventListeners.AddToTail( &m_NavBlockedEvent );
+
+ TheBotPhrases = new BotPhraseManager;
+ TheBotProfiles = new BotProfileManager;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a new round begins
+ */
+void CCSBotManager::RestartRound( void )
+{
+ // extend
+ CBotManager::RestartRound();
+
+ SetLooseBomb( NULL );
+ m_isBombPlanted = false;
+ m_earliestBombPlantTimestamp = gpGlobals->curtime + RandomFloat( 10.0f, 30.0f ); // 60
+ m_bombDefuser = NULL;
+
+ ResetRadioMessageTimestamps();
+
+ m_lastSeenEnemyTimestamp = -9999.9f;
+
+ m_roundStartTimestamp = gpGlobals->curtime + mp_freezetime.GetFloat();
+
+ // randomly decide if defensive team wants to "rush" as a whole
+ const float defenseRushChance = 33.3f; // 25.0f;
+ m_isDefenseRushing = (RandomFloat( 0.0f, 100.0f ) <= defenseRushChance) ? true : false;
+
+ TheBotPhrases->OnRoundRestart();
+
+ m_isRoundOver = false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+
+void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue )
+{
+ int darkRed = red/2;
+ int darkGreen = green/2;
+ int darkBlue = blue/2;
+
+ Vector v[8];
+ v[0].x = extent->lo.x; v[0].y = extent->lo.y; v[0].z = extent->lo.z;
+ v[1].x = extent->hi.x; v[1].y = extent->lo.y; v[1].z = extent->lo.z;
+ v[2].x = extent->hi.x; v[2].y = extent->hi.y; v[2].z = extent->lo.z;
+ v[3].x = extent->lo.x; v[3].y = extent->hi.y; v[3].z = extent->lo.z;
+ v[4].x = extent->lo.x; v[4].y = extent->lo.y; v[4].z = extent->hi.z;
+ v[5].x = extent->hi.x; v[5].y = extent->lo.y; v[5].z = extent->hi.z;
+ v[6].x = extent->hi.x; v[6].y = extent->hi.y; v[6].z = extent->hi.z;
+ v[7].x = extent->lo.x; v[7].y = extent->hi.y; v[7].z = extent->hi.z;
+
+ static int edge[] =
+ {
+ 1, 2, 3, 4, -1,
+ 5, 6, 7, 8, -5,
+ 1, -5,
+ 2, -6,
+ 3, -7,
+ 4, -8,
+ 0
+ };
+
+ Vector from, to;
+ bool restart = true;
+ for( int i=0; edge[i] != 0; ++i )
+ {
+ if (restart)
+ {
+ to = v[ edge[i]-1 ];
+ restart = false;
+ continue;
+ }
+
+ from = to;
+
+ int index = edge[i];
+ if (index < 0)
+ {
+ restart = true;
+ index = -index;
+ }
+
+ to = v[ index-1 ];
+
+ NDebugOverlay::Line( from, to, darkRed, darkGreen, darkBlue, true, 0.1f );
+ NDebugOverlay::Line( from, to, red, green, blue, false, 0.15f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::EnableEventListeners( bool enable )
+{
+ if ( m_eventListenersEnabled == enable )
+ {
+ return;
+ }
+
+ m_eventListenersEnabled = enable;
+
+ // enable/disable the most frequent event listeners, to improve performance when no bots are present.
+ for ( int i=0; i<m_commonEventListeners.Count(); ++i )
+ {
+ if ( enable )
+ {
+ gameeventmanager->AddListener( m_commonEventListeners[i], m_commonEventListeners[i]->GetEventName(), true );
+ }
+ else
+ {
+ gameeventmanager->RemoveListener( m_commonEventListeners[i] );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called each frame
+ */
+void CCSBotManager::StartFrame( void )
+{
+ if ( !AreBotsAllowed() )
+ {
+ EnableEventListeners( false );
+ return;
+ }
+
+ // EXTEND
+ CBotManager::StartFrame();
+
+ MaintainBotQuota();
+ EnableEventListeners( UTIL_CSSBotsInGame() > 0 );
+
+ // debug zone extent visualization
+ if (cv_bot_debug.GetInt() == 5)
+ {
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ Zone *zone = &m_zone[z];
+
+ if ( zone->m_isBlocked )
+ {
+ UTIL_DrawBox( &zone->m_extent, 1, 255, 0, 200 );
+ }
+ else
+ {
+ UTIL_DrawBox( &zone->m_extent, 1, 255, 100, 0 );
+ }
+ }
+ }
+
+ if (bot_show_occupy_time.GetBool())
+ {
+ DrawOccupyTime();
+ }
+
+ if (bot_show_battlefront.GetBool())
+ {
+ DrawBattlefront();
+ }
+
+ if ( m_checkTransientAreasTimer.IsElapsed() && !nav_edit.GetBool() )
+ {
+ CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
+ for ( int i=0; i<transientAreas.Count(); ++i )
+ {
+ CNavArea *area = transientAreas[i];
+ if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
+ {
+ area->UpdateBlocked();
+ }
+ }
+
+ m_checkTransientAreasTimer.Start( 2.0f );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the bot can use this weapon
+ */
+bool CCSBotManager::IsWeaponUseable( const CWeaponCSBase *weapon ) const
+{
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( WEAPON_C4 ))
+ return true;
+
+ if ((!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
+ (!AllowMachineGuns() && weapon->IsKindOf( WEAPONTYPE_MACHINEGUN )) ||
+ (!AllowRifles() && weapon->IsKindOf( WEAPONTYPE_RIFLE )) ||
+ (!AllowShotguns() && weapon->IsKindOf( WEAPONTYPE_SHOTGUN )) ||
+ (!AllowSnipers() && weapon->IsKindOf( WEAPONTYPE_SNIPER_RIFLE )) ||
+ (!AllowSubMachineGuns() && weapon->IsKindOf( WEAPONTYPE_SUBMACHINEGUN )) ||
+ (!AllowPistols() && weapon->IsKindOf( WEAPONTYPE_PISTOL )) ||
+ (!AllowGrenades() && weapon->IsKindOf( WEAPONTYPE_GRENADE )))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this player is on "defense"
+ */
+bool CCSBotManager::IsOnDefense( const CCSPlayer *player ) const
+{
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ return (player->GetTeamNumber() == TEAM_CT);
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ return (player->GetTeamNumber() == TEAM_TERRORIST);
+
+ case SCENARIO_ESCORT_VIP:
+ return (player->GetTeamNumber() == TEAM_TERRORIST);
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this player is on "offense"
+ */
+bool CCSBotManager::IsOnOffense( const CCSPlayer *player ) const
+{
+ return !IsOnDefense( player );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a map has just been loaded
+ */
+void CCSBotManager::ServerActivate( void )
+{
+ m_isMapDataLoaded = false;
+
+ // load the database of bot radio chatter
+ TheBotPhrases->Reset();
+ TheBotPhrases->Initialize( "BotChatter.db", 0 );
+
+ TheBotProfiles->Reset();
+ TheBotProfiles->FindVoiceBankIndex( "BotChatter.db" ); // make sure default voice bank is first
+ const char *filename;
+ if ( false ) // g_engfuncs.pfnIsCareerMatch() )
+ {
+ filename = "MissionPacks/BotPackList.db";
+ }
+ else
+ {
+ filename = "BotPackList.db";
+ }
+
+ // read in the list of bot profile DBs
+ FileHandle_t file = filesystem->Open( filename, "r" );
+
+ if ( !file )
+ {
+ TheBotProfiles->Init( "BotProfile.db" );
+ }
+ else
+ {
+ int dataLength = filesystem->Size( filename );
+ char *dataPointer = new char[ dataLength ];
+
+ filesystem->Read( dataPointer, dataLength, file );
+ filesystem->Close( file );
+
+ const char *dataFile = SharedParse( dataPointer );
+ const char *token;
+
+ while ( dataFile )
+ {
+ token = SharedGetToken();
+ char *clone = CloneString( token );
+ TheBotProfiles->Init( clone );
+ delete[] clone;
+ dataFile = SharedParse( dataFile );
+ }
+
+ delete [] dataPointer;
+ }
+
+ // Now that we've parsed all the profiles, we have a list of the voice banks they're using.
+ // Go back and parse the custom voice speakables.
+ const BotProfileManager::VoiceBankList *voiceBanks = TheBotProfiles->GetVoiceBanks();
+ for ( int i=1; i<voiceBanks->Count(); ++i )
+ {
+ TheBotPhrases->Initialize( (*voiceBanks)[i], i );
+ }
+
+ // tell the Navigation Mesh system what CS spawn points are named
+ TheNavMesh->SetPlayerSpawnName( "info_player_terrorist" );
+
+ ExtractScenarioData();
+
+ RestartRound();
+
+ TheBotPhrases->OnMapChange();
+
+ m_serverActive = true;
+}
+
+
+void CCSBotManager::ServerDeactivate( void )
+{
+ m_serverActive = false;
+}
+
+void CCSBotManager::ClientDisconnect( CBaseEntity *entity )
+{
+/*
+ if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) )
+ {
+ FREE_PRIVATE( entity );
+ }
+*/
+
+ /*
+ // make sure voice feedback is turned off
+ CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pEntity );
+ if ( pPlayer && pPlayer->IsBot() )
+ {
+ CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
+ if (pBot)
+ {
+ pBot->EndVoiceFeedback( true );
+ }
+ }
+ */
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Parses out bot name/template/etc params from the current ConCommand
+*/
+void BotArgumentsFromArgv( const CCommand &args, const char **name, CSWeaponType *weaponType, BotDifficultyType *difficulty, int *team = NULL, bool *all = NULL )
+{
+ static char s_name[MAX_PLAYER_NAME_LENGTH];
+
+ s_name[0] = 0;
+ *name = s_name;
+ *difficulty = NUM_DIFFICULTY_LEVELS;
+ if ( team )
+ {
+ *team = TEAM_UNASSIGNED;
+ }
+ if ( all )
+ {
+ *all = false;
+ }
+
+ *weaponType = WEAPONTYPE_UNKNOWN;
+
+ for ( int arg=1; arg<args.ArgC(); ++arg )
+ {
+ bool found = false;
+
+ const char *token = args[arg];
+ if ( all && FStrEq( token, "all" ) )
+ {
+ *all = true;
+ found = true;
+ }
+ else if ( team && FStrEq( token, "t" ) )
+ {
+ *team = TEAM_TERRORIST;
+ found = true;
+ }
+ else if ( team && FStrEq( token, "ct" ) )
+ {
+ *team = TEAM_CT;
+ found = true;
+ }
+
+ for( int i=0; i<NUM_DIFFICULTY_LEVELS && !found; ++i )
+ {
+ if (!stricmp( BotDifficultyName[i], token ))
+ {
+ *difficulty = (BotDifficultyType)i;
+ found = true;
+ }
+ }
+
+ if ( !found )
+ {
+ *weaponType = WeaponClassFromString( token );
+ if ( *weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ found = true;
+ }
+ }
+
+ if ( !found )
+ {
+ Q_strncpy( s_name, token, sizeof( s_name ) );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add, "bot_add <t|ct> <type> <difficulty> <name> - Adds a bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team );
+ TheCSBots()->BotAddCommand( team, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add_t, "bot_add_t <type> <difficulty> <name> - Adds a terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
+ TheCSBots()->BotAddCommand( TEAM_TERRORIST, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_add_ct, "bot_add_ct <type> <difficulty> <name> - Adds a Counter-Terrorist bot matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty );
+ TheCSBots()->BotAddCommand( TEAM_CT, FROM_CONSOLE, name, weaponType, difficulty );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Collects all bots matching the given criteria (player name, profile template name, difficulty, and team)
+ */
+class CollectBots
+{
+public:
+ CollectBots( const char *name, CSWeaponType weaponType, BotDifficultyType difficulty, int team )
+ {
+ m_name = name;
+ m_difficulty = difficulty;
+ m_team = team;
+ m_weaponType = weaponType;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if ( !player->IsBot() )
+ {
+ return true;
+ }
+
+ CCSBot *bot = dynamic_cast< CCSBot * >(player);
+ if ( !bot || !bot->GetProfile() )
+ {
+ return true;
+ }
+
+ if ( m_name && *m_name )
+ {
+ // accept based on name
+ if ( FStrEq( m_name, bot->GetProfile()->GetName() ) )
+ {
+ m_bots.RemoveAll();
+ m_bots.AddToTail( bot );
+ return false;
+ }
+
+ // Reject based on profile template name
+ if ( !bot->GetProfile()->InheritsFrom( m_name ) )
+ {
+ return true;
+ }
+ }
+
+ // reject based on difficulty
+ if ( m_difficulty != NUM_DIFFICULTY_LEVELS )
+ {
+ if ( !bot->GetProfile()->IsDifficulty( m_difficulty ) )
+ {
+ return true;
+ }
+ }
+
+ // reject based on team
+ if ( m_team == TEAM_CT || m_team == TEAM_TERRORIST )
+ {
+ if ( bot->GetTeamNumber() != m_team )
+ {
+ return true;
+ }
+ }
+
+ // reject based on weapon preference
+ if ( m_weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ if ( !bot->GetProfile()->GetWeaponPreferenceCount() )
+ {
+ return true;
+ }
+
+ if ( m_weaponType != WeaponClassFromWeaponID( (CSWeaponID)bot->GetProfile()->GetWeaponPreference( 0 ) ) )
+ {
+ return true;
+ }
+ }
+
+ // A match!
+ m_bots.AddToTail( bot );
+
+ return true;
+ }
+
+ CUtlVector< CCSBot * > m_bots;
+
+private:
+ const char *m_name;
+ CSWeaponType m_weaponType;
+ BotDifficultyType m_difficulty;
+ int m_team;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_kill, "bot_kill <all> <t|ct> <type> <difficulty> <name> - Kills a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ bool all;
+
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
+ if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ all = true;
+ }
+
+ CollectBots collector( name, weaponType, difficulty, team );
+ ForEachPlayer( collector );
+
+ for ( int i=0; i<collector.m_bots.Count(); ++i )
+ {
+ CCSBot *bot = collector.m_bots[i];
+ if ( !bot->IsAlive() )
+ continue;
+
+ bot->CommitSuicide();
+
+ if ( !all )
+ {
+ return;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_kick, "bot_kick <all> <t|ct> <type> <difficulty> <name> - Kicks a specific bot, or all bots, matching the given criteria.", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ const char *name;
+ BotDifficultyType difficulty;
+ CSWeaponType weaponType;
+ int team;
+ bool all;
+
+ BotArgumentsFromArgv( args, &name, &weaponType, &difficulty, &team, &all );
+ if ( (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ all = true;
+ }
+
+ CollectBots collector( name, weaponType, difficulty, team );
+ ForEachPlayer( collector );
+
+ for ( int i=0; i<collector.m_bots.Count(); ++i )
+ {
+ CCSBot *bot = collector.m_bots[i];
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", bot->GetPlayerName() ) );
+ if ( !all )
+ {
+ // adjust bot quota so kicked bot is not immediately added back in
+ int newQuota = cv_bot_quota.GetInt() - 1;
+ cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
+ return;
+ }
+ }
+
+ // adjust bot quota so kicked bot is not immediately added back in
+ if ( all && (!name || !*name) && team == TEAM_UNASSIGNED && difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ cv_bot_quota.SetValue( 0 );
+ }
+ else
+ {
+ int newQuota = cv_bot_quota.GetInt() - collector.m_bots.Count();
+ cv_bot_quota.SetValue( clamp( newQuota, 0, cv_bot_quota.GetInt() ) );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_knives_only, "Restricts the bots to only using knives", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 0 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 0 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_pistols_only, "Restricts the bots to only using pistols", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 1 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 0 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_snipers_only, "Restricts the bots to only using sniper rifles", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 0 );
+ cv_bot_allow_shotguns.SetValue( 0 );
+ cv_bot_allow_sub_machine_guns.SetValue( 0 );
+ cv_bot_allow_rifles.SetValue( 0 );
+ cv_bot_allow_machine_guns.SetValue( 0 );
+ cv_bot_allow_grenades.SetValue( 0 );
+ cv_bot_allow_snipers.SetValue( 1 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 0 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_all_weapons, "Allows the bots to use all weapons", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ cv_bot_allow_pistols.SetValue( 1 );
+ cv_bot_allow_shotguns.SetValue( 1 );
+ cv_bot_allow_sub_machine_guns.SetValue( 1 );
+ cv_bot_allow_rifles.SetValue( 1 );
+ cv_bot_allow_machine_guns.SetValue( 1 );
+ cv_bot_allow_grenades.SetValue( 1 );
+ cv_bot_allow_snipers.SetValue( 1 );
+#ifdef CS_SHIELD_ENABLED
+ cv_bot_allow_shield.SetValue( 1 );
+#endif // CS_SHIELD_ENABLED
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( bot_goto_mark, "Sends a bot to the selected nav area (useful for testing navigation meshes)", FCVAR_GAMEDLL | FCVAR_CHEAT )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ // tell the first bot we find to go to our marked area
+ CNavArea *area = TheNavMesh->GetMarkedArea();
+ if (area)
+ {
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (player->IsBot())
+ {
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+
+ if ( bot )
+ {
+ bot->MoveTo( area->GetCenter(), FASTEST_ROUTE );
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+#if 0
+CON_COMMAND_F( bot_memory_usage, "Reports on the bots' memory usage", FCVAR_GAMEDLL )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ Msg( "Memory usage:\n" );
+
+ Msg( " %d bytes per bot\n", sizeof(CCSBot) );
+
+ Msg( " %d Navigation Areas @ %d bytes each = %d bytes\n",
+ TheNavMesh->GetNavAreaCount(),
+ sizeof( CNavArea ),
+ TheNavMesh->GetNavAreaCount() * sizeof( CNavArea ) );
+
+ Msg( " %d Hiding Spots @ %d bytes each = %d bytes\n",
+ TheHidingSpotList.Count(),
+ sizeof( HidingSpot ),
+ TheHidingSpotList.Count() * sizeof( HidingSpot ) );
+
+/*
+ unsigned int encounterMem = 0;
+ FOR_EACH_LL( TheNavAreaList, it )
+ {
+ CNavArea *area = TheNavAreaList[ it ];
+
+ FOR_EACH_LL( area->m_spotEncounterList, it )
+ {
+ SpotEncounter *se = area->m_spotEncounterList[ it ];
+
+ encounterMem += sizeof( SpotEncounter );
+ encounterMem += se->spotList.Count() * sizeof( SpotOrder );
+ }
+ }
+
+ Msg( " Encounter Spot data = %d bytes\n", encounterMem );
+*/
+}
+#endif
+
+
+bool CCSBotManager::ServerCommand( const char *cmd )
+{
+ return false;
+}
+
+
+bool CCSBotManager::ClientCommand( CBasePlayer *player, const CCommand &args )
+{
+ return false;
+}
+
+
+/**
+ * Process the "bot_add" console command
+ */
+bool CCSBotManager::BotAddCommand( int team, bool isFromConsole, const char *profileName, CSWeaponType weaponType, BotDifficultyType difficulty )
+{
+ if ( !TheNavMesh->IsLoaded() )
+ {
+ // If there isn't a Navigation Mesh in memory, create one
+ if ( !TheNavMesh->IsGenerating() )
+ {
+ if ( !m_isMapDataLoaded )
+ {
+ TheNavMesh->BeginGeneration();
+ m_isMapDataLoaded = true;
+ }
+ return false;
+ }
+ }
+
+ // dont allow bots to join if the Navigation Mesh is being generated
+ if (TheNavMesh->IsGenerating())
+ return false;
+
+ const BotProfile *profile = NULL;
+
+ if ( !isFromConsole )
+ {
+ profileName = NULL;
+ difficulty = GetDifficultyLevel();
+ }
+ else
+ {
+ if ( difficulty == NUM_DIFFICULTY_LEVELS )
+ {
+ difficulty = GetDifficultyLevel();
+ }
+
+ // if team not specified, check bot_join_team cvar for preference
+ if (team == TEAM_UNASSIGNED)
+ {
+ if (!stricmp( cv_bot_join_team.GetString(), "T" ))
+ team = TEAM_TERRORIST;
+ else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
+ team = TEAM_CT;
+ else
+ team = CSGameRules()->SelectDefaultTeam();
+ }
+ }
+
+ if ( profileName && *profileName )
+ {
+ // in career, ignore humans, since we want to add anyway
+ bool ignoreHumans = CSGameRules()->IsCareer();
+ if (UTIL_IsNameTaken( profileName, ignoreHumans ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error - %s is already in the game.\n", profileName );
+ }
+ return true;
+ }
+
+ // try to add a bot by name
+ profile = TheBotProfiles->GetProfile( profileName, team );
+ if ( !profile )
+ {
+ // try to add a bot by template
+ profile = TheBotProfiles->GetProfileMatchingTemplate( profileName, team, difficulty );
+ if ( !profile )
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error - no profile for '%s' exists.\n", profileName );
+ }
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // if team not specified, check bot_join_team cvar for preference
+ if (team == TEAM_UNASSIGNED)
+ {
+ if (!stricmp( cv_bot_join_team.GetString(), "T" ))
+ team = TEAM_TERRORIST;
+ else if (!stricmp( cv_bot_join_team.GetString(), "CT" ))
+ team = TEAM_CT;
+ else
+ team = CSGameRules()->SelectDefaultTeam();
+ }
+
+ profile = TheBotProfiles->GetRandomProfile( difficulty, team, weaponType );
+ if (profile == NULL)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "All bot profiles at this difficulty level are in use.\n" );
+ }
+ return true;
+ }
+ }
+
+ if (team == TEAM_UNASSIGNED || team == TEAM_SPECTATOR)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: The game is full\n" );
+ }
+ return false;
+ }
+
+ if (CSGameRules()->TeamFull( team ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: Team is full\n" );
+ }
+ return false;
+ }
+
+ if (CSGameRules()->TeamStacked( team, TEAM_UNASSIGNED ))
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Could not add bot to the game: Team is stacked (to disable this check, set mp_autoteambalance to zero, increase mp_limitteams, and restart the round).\n" );
+ }
+ return false;
+ }
+
+ // create the actual bot
+ CCSBot *bot = CreateBot<CCSBot>( profile, team );
+
+ if (bot == NULL)
+ {
+ if ( isFromConsole )
+ {
+ Msg( "Error: CreateBot() failed.\n" );
+ }
+ return false;
+ }
+
+ if (isFromConsole)
+ {
+ // increase the bot quota to account for manually added bot
+ cv_bot_quota.SetValue( cv_bot_quota.GetInt() + 1 );
+ }
+
+ return true;
+}
+
+int UTIL_CSSBotsInGame()
+{
+ int count = 0;
+
+ for (int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>(UTIL_PlayerByIndex( i ));
+
+ if ( player == NULL )
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+bool UTIL_CSSKickBotFromTeam( int kickTeam )
+{
+ int i;
+
+ // try to kick a dead bot first
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsAlive() && player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ // no dead bots, kick any bot on the given team
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSBot *player = dynamic_cast<CCSBot *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Keep a minimum quota of bots in the game
+ */
+void CCSBotManager::MaintainBotQuota( void )
+{
+ if ( !AreBotsAllowed() )
+ return;
+
+ if (TheNavMesh->IsGenerating())
+ return;
+
+ int totalHumansInGame = UTIL_HumansInGame();
+ int humanPlayersInGame = UTIL_HumansInGame( IGNORE_SPECTATORS );
+
+ // don't add bots until local player has been registered, to make sure he's player ID #1
+ if (!engine->IsDedicatedServer() && totalHumansInGame == 0)
+ return;
+
+ // new players can't spawn immediately after the round has been going for some time
+ if ( !CSGameRules() || !TheCSBots() )
+ {
+ return;
+ }
+
+ int desiredBotCount = cv_bot_quota.GetInt();
+ int botsInGame = UTIL_CSSBotsInGame();
+
+ /// isRoundInProgress is true if the round has progressed far enough that new players will join as dead.
+ bool isRoundInProgress = CSGameRules()->m_bFirstConnected &&
+ !TheCSBots()->IsRoundOver() &&
+ ( CSGameRules()->GetRoundElapsedTime() >= 20.0f );
+
+ if ( FStrEq( cv_bot_quota_mode.GetString(), "fill" ) )
+ {
+ // If bot_quota_mode is 'fill', we want the number of bots and humans together to equal bot_quota
+ // unless the round is already in progress, in which case we play with what we've been dealt
+ if ( !isRoundInProgress )
+ {
+ desiredBotCount = MAX( 0, desiredBotCount - humanPlayersInGame );
+ }
+ else
+ {
+ desiredBotCount = botsInGame;
+ }
+ }
+ else if ( FStrEq( cv_bot_quota_mode.GetString(), "match" ) )
+ {
+ // If bot_quota_mode is 'match', we want the number of bots to be bot_quota * total humans
+ // unless the round is already in progress, in which case we play with what we've been dealt
+ if ( !isRoundInProgress )
+ {
+ desiredBotCount = (int)MAX( 0, cv_bot_quota.GetFloat() * humanPlayersInGame );
+ }
+ else
+ {
+ desiredBotCount = botsInGame;
+ }
+ }
+
+ // wait for a player to join, if necessary
+ if (cv_bot_join_after_player.GetBool())
+ {
+ if (humanPlayersInGame == 0)
+ desiredBotCount = 0;
+ }
+
+ // wait until the map has been loaded for a bit, to allow players to transition across
+ // the transition without missing the pistol round
+ if ( bot_join_delay.GetInt() > CSGameRules()->GetMapElapsedTime() )
+ {
+ desiredBotCount = 0;
+ }
+
+ // if bots will auto-vacate, we need to keep one slot open to allow players to join
+ if (cv_bot_auto_vacate.GetBool())
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - (humanPlayersInGame + 1) );
+ else
+ desiredBotCount = MIN( desiredBotCount, gpGlobals->maxClients - humanPlayersInGame );
+
+ // Try to balance teams, if we are in the first 20 seconds of a round and bots can join either team.
+ if ( botsInGame > 0 && desiredBotCount == botsInGame && CSGameRules()->m_bFirstConnected )
+ {
+ if ( CSGameRules()->GetRoundElapsedTime() < 20.0f ) // new bots can still spawn during this time
+ {
+ if ( mp_autoteambalance.GetBool() )
+ {
+ int numAliveTerrorist;
+ int numAliveCT;
+ int numDeadTerrorist;
+ int numDeadCT;
+ CSGameRules()->InitializePlayerCounts( numAliveTerrorist, numAliveCT, numDeadTerrorist, numDeadCT );
+
+ if ( !FStrEq( cv_bot_join_team.GetString(), "T" ) &&
+ !FStrEq( cv_bot_join_team.GetString(), "CT" ) )
+ {
+ if ( numAliveTerrorist > CSGameRules()->m_iNumCT + 1 )
+ {
+ if ( UTIL_KickBotFromTeam( TEAM_TERRORIST ) )
+ return;
+ }
+ else if ( numAliveCT > CSGameRules()->m_iNumTerrorist + 1 )
+ {
+ if ( UTIL_KickBotFromTeam( TEAM_CT ) )
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // add bots if necessary
+ if (desiredBotCount > botsInGame)
+ {
+ // don't try to add a bot if all teams are full
+ if (!CSGameRules()->TeamFull( TEAM_TERRORIST ) || !CSGameRules()->TeamFull( TEAM_CT ))
+ TheCSBots()->BotAddCommand( TEAM_UNASSIGNED );
+ }
+ else if (desiredBotCount < botsInGame)
+ {
+ // kick a bot to maintain quota
+
+ // first remove any unassigned bots
+ if (UTIL_CSSKickBotFromTeam( TEAM_UNASSIGNED ))
+ return;
+
+ int kickTeam;
+
+ // remove from the team that has more players
+ if (CSGameRules()->m_iNumTerrorist > CSGameRules()->m_iNumCT)
+ {
+ kickTeam = TEAM_TERRORIST;
+ }
+ else if (CSGameRules()->m_iNumTerrorist < CSGameRules()->m_iNumCT)
+ {
+ kickTeam = TEAM_CT;
+ }
+
+ // remove from the team that's winning
+ else if (CSGameRules()->m_iNumTerroristWins > CSGameRules()->m_iNumCTWins)
+ {
+ kickTeam = TEAM_TERRORIST;
+ }
+ else if (CSGameRules()->m_iNumCTWins > CSGameRules()->m_iNumTerroristWins)
+ {
+ kickTeam = TEAM_CT;
+ }
+ else
+ {
+ // teams and scores are equal, pick a team at random
+ kickTeam = (RandomInt( 0, 1 ) == 0) ? TEAM_CT : TEAM_TERRORIST;
+ }
+
+ // attempt to kick a bot from the given team
+ if (UTIL_CSSKickBotFromTeam( kickTeam ))
+ return;
+
+ // if there were no bots on the team, kick a bot from the other team
+ if (kickTeam == TEAM_TERRORIST)
+ UTIL_CSSKickBotFromTeam( TEAM_CT );
+ else
+ UTIL_CSSKickBotFromTeam( TEAM_TERRORIST );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Collect all nav areas that overlap the given zone
+ */
+class CollectOverlappingAreas
+{
+public:
+ CollectOverlappingAreas( CCSBotManager::Zone *zone )
+ {
+ m_zone = zone;
+
+ zone->m_areaCount = 0;
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+
+ if (areaExtent.hi.x >= m_zone->m_extent.lo.x && areaExtent.lo.x <= m_zone->m_extent.hi.x &&
+ areaExtent.hi.y >= m_zone->m_extent.lo.y && areaExtent.lo.y <= m_zone->m_extent.hi.y &&
+ areaExtent.hi.z >= m_zone->m_extent.lo.z && areaExtent.lo.z <= m_zone->m_extent.hi.z)
+ {
+ // area overlaps m_zone
+ m_zone->m_area[ m_zone->m_areaCount++ ] = area;
+ if (m_zone->m_areaCount == CCSBotManager::MAX_ZONE_NAV_AREAS)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ CCSBotManager::Zone *m_zone;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Search the map entities to determine the game scenario and define important zones.
+ */
+void CCSBotManager::ExtractScenarioData( void )
+{
+ if (!TheNavMesh->IsLoaded())
+ return;
+
+ m_zoneCount = 0;
+ m_gameScenario = SCENARIO_DEATHMATCH;
+
+
+ //
+ // Search all entities in the map and set the game type and
+ // store all zones (bomb target, etc).
+ //
+ CBaseEntity *entity;
+ int i;
+ for( i=1; i<gpGlobals->maxEntities; ++i )
+ {
+ entity = CBaseEntity::Instance( engine->PEntityOfEntIndex( i ) );
+
+ if (entity == NULL)
+ continue;
+
+ bool found = false;
+ bool isLegacy = false;
+
+ if (FClassnameIs( entity, "func_bomb_target" ))
+ {
+ m_gameScenario = SCENARIO_DEFUSE_BOMB;
+ found = true;
+ isLegacy = false;
+ }
+ else if (FClassnameIs( entity, "info_bomb_target" ))
+ {
+ m_gameScenario = SCENARIO_DEFUSE_BOMB;
+ found = true;
+ isLegacy = true;
+ }
+ else if (FClassnameIs( entity, "func_hostage_rescue" ))
+ {
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ found = true;
+ isLegacy = false;
+ }
+ else if (FClassnameIs( entity, "info_hostage_rescue" ))
+ {
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ found = true;
+ isLegacy = true;
+ }
+ else if (FClassnameIs( entity, "hostage_entity" ))
+ {
+ // some very old maps (ie: cs_assault) use info_player_start
+ // as rescue zones, so set the scenario if there are hostages
+ // in the map
+ m_gameScenario = SCENARIO_RESCUE_HOSTAGES;
+ }
+ else if (FClassnameIs( entity, "func_vip_safetyzone" ))
+ {
+ m_gameScenario = SCENARIO_ESCORT_VIP;
+ found = true;
+ isLegacy = false;
+ }
+
+ if (found)
+ {
+ if (m_zoneCount < MAX_ZONES)
+ {
+ Vector absmin, absmax;
+ entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
+
+ m_zone[ m_zoneCount ].m_isBlocked = false;
+ m_zone[ m_zoneCount ].m_center = (isLegacy) ? entity->GetAbsOrigin() : (absmin + absmax)/2.0f;
+ m_zone[ m_zoneCount ].m_isLegacy = isLegacy;
+ m_zone[ m_zoneCount ].m_index = m_zoneCount;
+ m_zone[ m_zoneCount++ ].m_entity = entity;
+ }
+ else
+ Msg( "Warning: Too many zones, some will be ignored.\n" );
+ }
+ }
+
+ //
+ // If there are no zones and the scenario is hostage rescue,
+ // use the info_player_start entities as rescue zones.
+ //
+ if (m_zoneCount == 0 && m_gameScenario == SCENARIO_RESCUE_HOSTAGES)
+ {
+ for( entity = gEntList.FindEntityByClassname( NULL, "info_player_start" );
+ entity && !FNullEnt( entity->edict() );
+ entity = gEntList.FindEntityByClassname( entity, "info_player_start" ) )
+ {
+ if (m_zoneCount < MAX_ZONES)
+ {
+ m_zone[ m_zoneCount ].m_isBlocked = false;
+ m_zone[ m_zoneCount ].m_center = entity->GetAbsOrigin();
+ m_zone[ m_zoneCount ].m_isLegacy = true;
+ m_zone[ m_zoneCount ].m_index = m_zoneCount;
+ m_zone[ m_zoneCount++ ].m_entity = entity;
+ }
+ else
+ {
+ Msg( "Warning: Too many zones, some will be ignored.\n" );
+ }
+ }
+ }
+
+ //
+ // Collect nav areas that overlap each zone
+ //
+ for( i=0; i<m_zoneCount; ++i )
+ {
+ Zone *zone = &m_zone[i];
+
+ if (zone->m_isLegacy)
+ {
+ const float legacyRange = 256.0f;
+ zone->m_extent.lo.x = zone->m_center.x - legacyRange;
+ zone->m_extent.lo.y = zone->m_center.y - legacyRange;
+ zone->m_extent.lo.z = zone->m_center.z - legacyRange;
+ zone->m_extent.hi.x = zone->m_center.x + legacyRange;
+ zone->m_extent.hi.y = zone->m_center.y + legacyRange;
+ zone->m_extent.hi.z = zone->m_center.z + legacyRange;
+ }
+ else
+ {
+ Vector absmin, absmax;
+ zone->m_entity->CollisionProp()->WorldSpaceAABB( &absmin, &absmax );
+
+ zone->m_extent.lo = absmin;
+ zone->m_extent.hi = absmax;
+ }
+
+ // ensure Z overlap
+ const float zFudge = 50.0f;
+ zone->m_extent.lo.z -= zFudge;
+ zone->m_extent.hi.z += zFudge;
+
+ // build a list of nav areas that overlap this zone
+ CollectOverlappingAreas collector( zone );
+ TheNavMesh->ForAllAreas( collector );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the zone that contains the given position
+ */
+const CCSBotManager::Zone *CCSBotManager::GetZone( const Vector &pos ) const
+{
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ if (m_zone[z].m_extent.Contains( pos ))
+ {
+ return &m_zone[z];
+ }
+ }
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest zone to the given position
+ */
+const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const Vector &pos ) const
+{
+ const Zone *close = NULL;
+ float closeRangeSq = 999999999.9f;
+
+ for( int z=0; z<m_zoneCount; ++z )
+ {
+ if ( m_zone[z].m_isBlocked )
+ continue;
+
+ float rangeSq = (m_zone[z].m_center - pos).LengthSqr();
+
+ if (rangeSq < closeRangeSq)
+ {
+ closeRangeSq = rangeSq;
+ close = &m_zone[z];
+ }
+ }
+
+ return close;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return a random position inside the given zone
+ */
+const Vector *CCSBotManager::GetRandomPositionInZone( const Zone *zone ) const
+{
+ static Vector pos;
+
+ if (zone == NULL)
+ return NULL;
+
+ if (zone->m_areaCount == 0)
+ return NULL;
+
+ // pick a random overlapping area
+ CNavArea *area = GetRandomAreaInZone(zone);
+
+ // pick a location inside both the nav area and the zone
+ /// @todo Randomize this
+
+ if (zone->m_isLegacy)
+ {
+ /// @todo It is possible that the radius might not overlap this area at all...
+ area->GetClosestPointOnArea( zone->m_center, &pos );
+ }
+ else
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+ Extent overlap;
+ overlap.lo.x = MAX( areaExtent.lo.x, zone->m_extent.lo.x );
+ overlap.lo.y = MAX( areaExtent.lo.y, zone->m_extent.lo.y );
+ overlap.hi.x = MIN( areaExtent.hi.x, zone->m_extent.hi.x );
+ overlap.hi.y = MIN( areaExtent.hi.y, zone->m_extent.hi.y );
+
+ pos.x = (overlap.lo.x + overlap.hi.x)/2.0f;
+ pos.y = (overlap.lo.y + overlap.hi.y)/2.0f;
+ pos.z = area->GetZ( pos );
+ }
+
+ return &pos;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return a random area inside the given zone
+ */
+CNavArea *CCSBotManager::GetRandomAreaInZone( const Zone *zone ) const
+{
+ int areaCount = zone->m_areaCount;
+ if( areaCount == 0 )
+ {
+ assert( false && "CCSBotManager::GetRandomAreaInZone: No areas for this zone" );
+ return NULL;
+ }
+
+ // Random, but weighted. Jump areas score zero, since you aren't ever meant to stop on one of those.
+ // Avoid areas score 1 to a normal area's 20 because pathfinding treats Avoid as a 20x penalty.
+ int totalWeight = 0;
+ for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
+ {
+ CNavArea *currentArea = zone->m_area[areaIndex];
+ if( currentArea->GetAttributes() & NAV_MESH_JUMP )
+ totalWeight += 0;
+ else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
+ totalWeight += 1;
+ else
+ totalWeight += 20;
+ }
+
+ if( totalWeight == 0 )
+ {
+ assert( false && "CCSBotManager::GetRandomAreaInZone: No real areas for this zone" );
+ return NULL;
+ }
+
+ int randomPick = RandomInt( 1, totalWeight );
+
+ for( int areaIndex = 0; areaIndex < areaCount; areaIndex++ )
+ {
+ CNavArea *currentArea = zone->m_area[areaIndex];
+ if( currentArea->GetAttributes() & NAV_MESH_JUMP )
+ randomPick -= 0;
+ else if( currentArea->GetAttributes() & NAV_MESH_AVOID )
+ randomPick -= 1;
+ else
+ randomPick -= 20;
+
+ if( randomPick <= 0 )
+ return currentArea;
+ }
+
+ // Won't ever get here, but the compiler will cry without it.
+ return zone->m_area[0];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnServerShutdown( IGameEvent *event )
+{
+ if ( !engine->IsDedicatedServer() )
+ {
+ // Since we're a listenserver, save some config info for the next time we start up
+ static const char *botVars[] =
+ {
+ "bot_quota",
+ "bot_difficulty",
+ "bot_chatter",
+ "bot_prefix",
+ "bot_join_team",
+ "bot_defer_to_human",
+#ifdef CS_SHIELD_ENABLED
+ "bot_allow_shield",
+#endif // CS_SHIELD_ENABLED
+ "bot_join_after_player",
+ "bot_allow_rogues",
+ "bot_allow_pistols",
+ "bot_allow_shotguns",
+ "bot_allow_sub_machine_guns",
+ "bot_allow_machine_guns",
+ "bot_allow_rifles",
+ "bot_allow_snipers",
+ "bot_allow_grenades"
+ };
+
+ KeyValues *data = new KeyValues( "ServerConfig" );
+
+ // load the config data
+ if (data)
+ {
+ data->LoadFromFile( filesystem, "ServerConfig.vdf", "GAME" );
+ for ( int i=0; i<sizeof(botVars)/sizeof(botVars[0]); ++i )
+ {
+ const char *varName = botVars[i];
+ if ( varName )
+ {
+ ConVar *var = cvar->FindVar( varName );
+ if ( var )
+ {
+ data->SetString( varName, var->GetString() );
+ }
+ }
+ }
+ data->SaveToFile( filesystem, "ServerConfig.vdf", "GAME" );
+ data->deleteThis();
+ }
+ return;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerFootstep( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFootstep, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerRadio( IGameEvent *event )
+{
+ // if it's an Enemy Spotted radio, update our enemy spotted timestamp
+ if ( event->GetInt( "slot" ) == RADIO_ENEMY_SPOTTED )
+ {
+ // to have some idea of when a human Player has seen an enemy
+ SetLastSeenEnemyTimestamp();
+ }
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerRadio, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerDeath( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerDeath, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnPlayerFallDamage( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnPlayerFallDamage, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombPickedUp( IGameEvent *event )
+{
+ // bomb no longer loose
+ SetLooseBomb( NULL );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombPickedUp, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombPlanted( IGameEvent *event )
+{
+ m_isBombPlanted = true;
+ m_bombPlantTimestamp = gpGlobals->curtime;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombPlanted, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombBeep( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombBeep, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefuseBegin( IGameEvent *event )
+{
+ m_bombDefuser = static_cast<CCSPlayer *>( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseBegin, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefused( IGameEvent *event )
+{
+ m_isBombPlanted = false;
+ m_bombDefuser = NULL;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefused, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombDefuseAbort( IGameEvent *event )
+{
+ m_bombDefuser = NULL;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombDefuseAbort, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBombExploded( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBombExploded, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundEnd( IGameEvent *event )
+{
+ m_isRoundOver = true;
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnRoundEnd, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundStart( IGameEvent *event )
+{
+ RestartRound();
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnRoundStart, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+static CBaseEntity * SelectSpawnSpot( const char *pEntClassName )
+{
+ CBaseEntity* pSpot = NULL;
+
+ // Find the next spawn spot.
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ if ( pSpot == NULL ) // skip over the null point
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ CBaseEntity *pFirstSpot = pSpot;
+ do
+ {
+ if ( pSpot )
+ {
+ // check if pSpot is valid
+ if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
+ {
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ continue;
+ }
+
+ // if so, go to pSpot
+ return pSpot;
+ }
+ // increment pSpot
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ } while ( pSpot != pFirstSpot ); // loop if we're not back to the start
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Pathfind from each zone to a spawn point to ensure it is valid. Assumes that every spawn can pathfind to
+ * every other spawn.
+ */
+void CCSBotManager::CheckForBlockedZones( void )
+{
+ CBaseEntity *pSpot = SelectSpawnSpot( "info_player_counterterrorist" );
+ if ( !pSpot )
+ pSpot = SelectSpawnSpot( "info_player_terrorist" );
+
+ if ( !pSpot )
+ return;
+
+ Vector spawnPos = pSpot->GetAbsOrigin();
+ CNavArea *spawnArea = TheNavMesh->GetNearestNavArea( spawnPos );
+ if ( !spawnArea )
+ return;
+
+ ShortestPathCost costFunc;
+
+ for( int i=0; i<m_zoneCount; ++i )
+ {
+ if (m_zone[i].m_areaCount == 0)
+ continue;
+
+ // just use the first overlapping nav area as a reasonable approximation
+ float dist = NavAreaTravelDistance( spawnArea, m_zone[i].m_area[0], costFunc );
+ m_zone[i].m_isBlocked = (dist < 0.0f );
+
+ if ( cv_bot_debug.GetInt() == 5 )
+ {
+ if ( m_zone[i].m_isBlocked )
+ DevMsg( "%.1f: Zone %d, area %d (%.0f %.0f %.0f) is blocked from spawn area %d (%.0f %.0f %.0f)\n",
+ gpGlobals->curtime, i, m_zone[i].m_area[0]->GetID(),
+ m_zone[i].m_area[0]->GetCenter().x, m_zone[i].m_area[0]->GetCenter().y, m_zone[i].m_area[0]->GetCenter().z,
+ spawnArea->GetID(),
+ spawnPos.x, spawnPos.y, spawnPos.z );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnRoundFreezeEnd( IGameEvent *event )
+{
+ bool reenableEvents = m_NavBlockedEvent.IsEnabled();
+
+ m_NavBlockedEvent.Enable( false ); // don't listen to nav_blocked events - there could be several, and we don't have bots pathing
+ CUtlVector< CNavArea * >& transientAreas = TheNavMesh->GetTransientAreas();
+ for ( int i=0; i<transientAreas.Count(); ++i )
+ {
+ CNavArea *area = transientAreas[i];
+ if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
+ {
+ area->UpdateBlocked();
+ }
+ }
+ if ( reenableEvents )
+ {
+ m_NavBlockedEvent.Enable( true );
+ }
+
+ CheckForBlockedZones();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnNavBlocked( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnNavBlocked, event );
+ CheckForBlockedZones();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnDoorMoving( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnDoorMoving, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check all nav areas inside the breakable's extent to see if players would now fall through
+ */
+class CheckAreasOverlappingBreakable
+{
+public:
+ CheckAreasOverlappingBreakable( CBaseEntity *breakable )
+ {
+ m_breakable = breakable;
+ ICollideable *collideable = breakable->GetCollideable();
+ collideable->WorldSpaceSurroundingBounds( &m_breakableExtent.lo, &m_breakableExtent.hi );
+
+ const float expand = 10.0f;
+ m_breakableExtent.lo += Vector( -expand, -expand, -expand );
+ m_breakableExtent.hi += Vector( expand, expand, expand );
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+
+ if (areaExtent.hi.x >= m_breakableExtent.lo.x && areaExtent.lo.x <= m_breakableExtent.hi.x &&
+ areaExtent.hi.y >= m_breakableExtent.lo.y && areaExtent.lo.y <= m_breakableExtent.hi.y &&
+ areaExtent.hi.z >= m_breakableExtent.lo.z && areaExtent.lo.z <= m_breakableExtent.hi.z)
+ {
+ // area overlaps the breakable
+ area->CheckFloor( m_breakable );
+ }
+
+ return true;
+ }
+
+private:
+ Extent m_breakableExtent;
+ CBaseEntity *m_breakable;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBreakBreakable( IGameEvent *event )
+{
+ CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
+ TheNavMesh->ForAllAreas( collector );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBreakBreakable, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBreakProp( IGameEvent *event )
+{
+ CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( event->GetInt( "entindex" ) ) );
+ TheNavMesh->ForAllAreas( collector );
+
+ CCSBOTMANAGER_ITERATE_BOTS( OnBreakProp, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHostageFollows( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHostageFollows, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHostageRescuedAll( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHostageRescuedAll, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponFire( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFire, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponFireOnEmpty( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponFireOnEmpty, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponReload( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponReload, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnWeaponZoom( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnWeaponZoom, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnBulletImpact( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnBulletImpact, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnHEGrenadeDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnHEGrenadeDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnFlashbangDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnFlashbangDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnSmokeGrenadeDetonate( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnSmokeGrenadeDetonate, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::OnGrenadeBounce( IGameEvent *event )
+{
+ CCSBOTMANAGER_ITERATE_BOTS( OnGrenadeBounce, event );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Get the time remaining before the planted bomb explodes
+ */
+float CCSBotManager::GetBombTimeLeft( void ) const
+{
+ return (mp_c4timer.GetFloat() - (gpGlobals->curtime - m_bombPlantTimestamp));
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBotManager::SetLooseBomb( CBaseEntity *bomb )
+{
+ m_looseBomb = bomb;
+
+ if (bomb)
+ {
+ m_looseBombArea = TheNavMesh->GetNearestNavArea( bomb->GetAbsOrigin() );
+ }
+ else
+ {
+ m_looseBombArea = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if player is important to scenario (VIP, bomb carrier, etc)
+ */
+bool CCSBotManager::IsImportantPlayer( CCSPlayer *player ) const
+{
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ {
+ if (player->GetTeamNumber() == TEAM_TERRORIST && player->HasC4())
+ return true;
+
+ /// @todo TEAM_CT's defusing the bomb are important
+
+ return false;
+ }
+
+ case SCENARIO_ESCORT_VIP:
+ {
+ if (player->GetTeamNumber() == TEAM_CT && player->IsVIP())
+ return true;
+
+ return false;
+ }
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ {
+ /// @todo TEAM_CT's escorting hostages are important
+ return false;
+ }
+ }
+
+ // everyone is equally important in a deathmatch
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return priority of player (0 = max pri)
+ */
+unsigned int CCSBotManager::GetPlayerPriority( CBasePlayer *player ) const
+{
+ const unsigned int lowestPriority = 0xFFFFFFFF;
+
+ if (!player->IsPlayer())
+ return lowestPriority;
+
+ // human players have highest priority
+ if (!player->IsBot())
+ return 0;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+
+ if ( !bot )
+ return 0;
+
+ // bots doing something important for the current scenario have high priority
+ switch (GetScenario())
+ {
+ case SCENARIO_DEFUSE_BOMB:
+ {
+ // the bomb carrier has high priority
+ if (bot->GetTeamNumber() == TEAM_TERRORIST && bot->HasC4())
+ return 1;
+
+ break;
+ }
+
+ case SCENARIO_ESCORT_VIP:
+ {
+ // the VIP has high priority
+ if (bot->GetTeamNumber() == TEAM_CT && bot->m_bIsVIP)
+ return 1;
+
+ break;
+ }
+
+ case SCENARIO_RESCUE_HOSTAGES:
+ {
+ // TEAM_CT's rescuing hostages have high priority
+ if (bot->GetTeamNumber() == TEAM_CT && bot->GetHostageEscortCount())
+ return 1;
+
+ break;
+ }
+ }
+
+ // everyone else is ranked by their unique ID (which cannot be zero)
+ return 1 + bot->GetID();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns a random spawn point for the given team (no arg means use both team spawnpoints)
+ */
+CBaseEntity *CCSBotManager::GetRandomSpawn( int team ) const
+{
+ CUtlVector< CBaseEntity * > spawnSet;
+ CBaseEntity *spot;
+
+ if (team == TEAM_TERRORIST || team == TEAM_MAXCOUNT)
+ {
+ // collect T spawns
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
+ {
+ spawnSet.AddToTail( spot );
+ }
+ }
+
+ if (team == TEAM_CT || team == TEAM_MAXCOUNT)
+ {
+ // collect CT spawns
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
+ {
+ spawnSet.AddToTail( spot );
+ }
+ }
+
+ if (spawnSet.Count() == 0)
+ {
+ return NULL;
+ }
+
+ // select one at random
+ int which = RandomInt( 0, spawnSet.Count()-1 );
+ return spawnSet[ which ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the last time the given radio message was sent for given team
+ * 'teamID' can be TEAM_CT or TEAM_TERRORIST
+ */
+float CCSBotManager::GetRadioMessageTimestamp( RadioType event, int teamID ) const
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ return m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
+
+ return 0.0f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the interval since the last time this message was sent
+ */
+float CCSBotManager::GetRadioMessageInterval( RadioType event, int teamID ) const
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ return gpGlobals->curtime - m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ];
+
+ return 99999999.9f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the given radio message timestamp.
+ * 'teamID' can be TEAM_CT or TEAM_TERRORIST
+ */
+void CCSBotManager::SetRadioMessageTimestamp( RadioType event, int teamID )
+{
+ int i = (teamID == TEAM_TERRORIST) ? 0 : 1;
+
+ if (event > RADIO_START_1 && event < RADIO_END)
+ m_radioMsgTimestamp[ event - RADIO_START_1 ][ i ] = gpGlobals->curtime;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset all radio message timestamps
+ */
+void CCSBotManager::ResetRadioMessageTimestamps( void )
+{
+ for( int t=0; t<2; ++t )
+ {
+ for( int m=0; m<(RADIO_END - RADIO_START_1); ++m )
+ m_radioMsgTimestamp[ m ][ t ] = 0.0f;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Display nav areas as they become reachable by each team
+ */
+void DrawOccupyTime( void )
+{
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ int r, g, b;
+
+ if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_TERRORIST ))
+ {
+ if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
+ {
+ r = 255; g = 0; b = 255;
+ }
+ else
+ {
+ r = 255; g = 0; b = 0;
+ }
+ }
+ else if (TheCSBots()->GetElapsedRoundTime() > area->GetEarliestOccupyTime( TEAM_CT ))
+ {
+ r = 0; g = 0; b = 255;
+ }
+ else
+ {
+ continue;
+ }
+
+ const Vector &nw = area->GetCorner( NORTH_WEST );
+ const Vector &ne = area->GetCorner( NORTH_EAST );
+ const Vector &sw = area->GetCorner( SOUTH_WEST );
+ const Vector &se = area->GetCorner( SOUTH_EAST );
+
+ NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Display areas where players will likely have initial battles
+ */
+void DrawBattlefront( void )
+{
+ const float epsilon = 1.0f;
+ int r = 255, g = 50, b = 0;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) > epsilon )
+ {
+ continue;
+ }
+
+
+ const Vector &nw = area->GetCorner( NORTH_WEST );
+ const Vector &ne = area->GetCorner( NORTH_EAST );
+ const Vector &sw = area->GetCorner( SOUTH_WEST );
+ const Vector &se = area->GetCorner( SOUTH_EAST );
+
+ NDebugOverlay::Line( nw, ne, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( nw, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, sw, r, g, b, true, 0.1f );
+ NDebugOverlay::Line( se, ne, r, g, b, true, 0.1f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+static bool CheckAreaAgainstAllZoneAreas(CNavArea *queryArea)
+{
+ // A marked area means they just want to double check this one spot
+ int goalZoneCount = TheCSBots()->GetZoneCount();
+
+ for( int zoneIndex = 0; zoneIndex < goalZoneCount; zoneIndex++ )
+ {
+ const CCSBotManager::Zone *currentZone = TheCSBots()->GetZone(zoneIndex);
+
+ int zoneAreaCount = currentZone->m_areaCount;
+ for( int areaIndex = 0; areaIndex < zoneAreaCount; areaIndex++ )
+ {
+ CNavArea *zoneArea = currentZone->m_area[areaIndex];
+ // We need to be connected to every area in the zone, since we don't know what other code might pick for an area
+ ShortestPathCost cost;
+ if( NavAreaTravelDistance(queryArea, zoneArea, cost) == -1.0f )
+ {
+ Msg( "Area #%d is disconnected from goal area #%d.\n",
+ queryArea->GetID(),
+ zoneArea->GetID()
+ );
+ return false;
+ }
+ }
+
+ }
+ return true;
+}
+
+CON_COMMAND_F( nav_check_connectivity, "Checks to be sure every (or just the marked) nav area can get to every goal area for the map (hostages or bomb site).", FCVAR_CHEAT )
+{
+ //Nav command in here since very CS specific.
+
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( TheNavMesh->GetMarkedArea() )
+ {
+ CNavArea *markedArea = TheNavMesh->GetMarkedArea();
+ bool fine = CheckAreaAgainstAllZoneAreas( markedArea );
+ if( fine )
+ {
+ Msg( "Area #%d is connected to all goal areas.\n", markedArea->GetID() );
+ }
+ }
+ else
+ {
+ // Otherwise, loop through every area, and make sure they can all get to the goal.
+ float start = engine->Time();
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CheckAreaAgainstAllZoneAreas(TheNavAreas[ nit ]);
+ }
+
+ float end = engine->Time();
+ float time = (end - start) * 1000.0f;
+ Msg( "nav_check_connectivity took %2.2f ms\n", time );
+ }
+}
+
+
+
+
diff --git a/game/server/cstrike/bot/cs_bot_manager.h b/game/server/cstrike/bot/cs_bot_manager.h
new file mode 100644
index 0000000..3f1da76
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_manager.h
@@ -0,0 +1,400 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#ifndef CS_CONTROL_H
+#define CS_CONTROL_H
+
+
+#include "bot_manager.h"
+#include "nav_area.h"
+#include "bot_util.h"
+#include "bot_profile.h"
+#include "cs_shareddefs.h"
+#include "cs_player.h"
+
+extern ConVar friendlyfire;
+
+class CBasePlayerWeapon;
+
+/**
+ * Given one team, return the other
+ */
+inline int OtherTeam( int team )
+{
+ return (team == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
+}
+
+class CCSBotManager;
+
+// accessor for CS-specific bots
+inline CCSBotManager *TheCSBots( void )
+{
+ return reinterpret_cast< CCSBotManager * >( TheBots );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+class BotEventInterface : public IGameEventListener2
+{
+public:
+ virtual const char *GetEventName( void ) const = 0;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Macro to set up an OnEventClass() in TheCSBots.
+ */
+#define DECLARE_BOTMANAGER_EVENT_LISTENER( BotManagerSingleton, EventClass, EventName ) \
+ public: \
+ virtual void On##EventClass( IGameEvent *data ); \
+ private: \
+ class EventClass##Event : public BotEventInterface \
+ { \
+ bool m_enabled; \
+ public: \
+ EventClass##Event( void ) \
+ { \
+ gameeventmanager->AddListener( this, #EventName, true ); \
+ m_enabled = true; \
+ } \
+ ~EventClass##Event( void ) \
+ { \
+ if ( m_enabled ) gameeventmanager->RemoveListener( this ); \
+ } \
+ virtual const char *GetEventName( void ) const \
+ { \
+ return #EventName; \
+ } \
+ void Enable( bool enable ) \
+ { \
+ m_enabled = enable; \
+ if ( enable ) \
+ gameeventmanager->AddListener( this, #EventName, true ); \
+ else \
+ gameeventmanager->RemoveListener( this ); \
+ } \
+ bool IsEnabled( void ) const { return m_enabled; } \
+ void FireGameEvent( IGameEvent *event ) \
+ { \
+ BotManagerSingleton()->On##EventClass( event ); \
+ } \
+ }; \
+ EventClass##Event m_##EventClass##Event;
+
+
+//--------------------------------------------------------------------------------------------------------------
+#define DECLARE_CSBOTMANAGER_EVENT_LISTENER( EventClass, EventName ) DECLARE_BOTMANAGER_EVENT_LISTENER( TheCSBots, EventClass, EventName )
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Macro to propogate an event from the bot manager to all bots
+ */
+#define CCSBOTMANAGER_ITERATE_BOTS( Callback, arg1 ) \
+ { \
+ for ( int idx = 1; idx <= gpGlobals->maxClients; ++idx ) \
+ { \
+ CBasePlayer *player = UTIL_PlayerByIndex( idx ); \
+ if (player == NULL) continue; \
+ if (!player->IsBot()) continue; \
+ CCSBot *bot = dynamic_cast< CCSBot * >(player); \
+ if ( !bot ) continue; \
+ bot->Callback( arg1 ); \
+ } \
+ }
+
+
+//--------------------------------------------------------------------------------------------------------------
+//
+// The manager for Counter-Strike specific bots
+//
+class CCSBotManager : public CBotManager
+{
+public:
+ CCSBotManager();
+
+ virtual CBasePlayer *AllocateBotEntity( void ); ///< factory method to allocate the appropriate entity for the bot
+
+ virtual void ClientDisconnect( CBaseEntity *entity );
+ virtual bool ClientCommand( CBasePlayer *player, const CCommand &args );
+
+ virtual void ServerActivate( void );
+ virtual void ServerDeactivate( void );
+ virtual bool ServerCommand( const char *cmd );
+ bool IsServerActive( void ) const { return m_serverActive; }
+
+ virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
+ virtual void StartFrame( void ); ///< (EXTEND) called each frame
+
+ virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const; ///< return priority of player (0 = max pri)
+ virtual bool IsImportantPlayer( CCSPlayer *player ) const; ///< return true if player is important to scenario (VIP, bomb carrier, etc)
+
+ void ExtractScenarioData( void ); ///< search the map entities to determine the game scenario and define important zones
+
+ // difficulty levels -----------------------------------------------------------------------------------------
+ static BotDifficultyType GetDifficultyLevel( void )
+ {
+ if (cv_bot_difficulty.GetFloat() < 0.9f)
+ return BOT_EASY;
+ if (cv_bot_difficulty.GetFloat() < 1.9f)
+ return BOT_NORMAL;
+ if (cv_bot_difficulty.GetFloat() < 2.9f)
+ return BOT_HARD;
+
+ return BOT_EXPERT;
+ }
+
+ // the supported game scenarios ------------------------------------------------------------------------------
+ enum GameScenarioType
+ {
+ SCENARIO_DEATHMATCH,
+ SCENARIO_DEFUSE_BOMB,
+ SCENARIO_RESCUE_HOSTAGES,
+ SCENARIO_ESCORT_VIP
+ };
+ GameScenarioType GetScenario( void ) const { return m_gameScenario; }
+
+ // "zones" ---------------------------------------------------------------------------------------------------
+ // depending on the game mode, these are bomb zones, rescue zones, etc.
+
+ enum { MAX_ZONES = 4 }; ///< max # of zones in a map
+ enum { MAX_ZONE_NAV_AREAS = 16 }; ///< max # of nav areas in a zone
+ struct Zone
+ {
+ CBaseEntity *m_entity; ///< the map entity
+ CNavArea *m_area[ MAX_ZONE_NAV_AREAS ]; ///< nav areas that overlap this zone
+ int m_areaCount;
+ Vector m_center;
+ bool m_isLegacy; ///< if true, use pev->origin and 256 unit radius as zone
+ int m_index;
+ bool m_isBlocked;
+ Extent m_extent;
+ };
+
+ const Zone *GetZone( int i ) const { return &m_zone[i]; }
+ const Zone *GetZone( const Vector &pos ) const; ///< return the zone that contains the given position
+ const Zone *GetClosestZone( const Vector &pos ) const; ///< return the closest zone to the given position
+ const Zone *GetClosestZone( const CBaseEntity *entity ) const; ///< return the closest zone to the given entity
+ int GetZoneCount( void ) const { return m_zoneCount; }
+ void CheckForBlockedZones( void );
+
+
+ const Vector *GetRandomPositionInZone( const Zone *zone ) const; ///< return a random position inside the given zone
+ CNavArea *GetRandomAreaInZone( const Zone *zone ) const; ///< return a random area inside the given zone
+
+ /**
+ * Return the zone closest to the given position, using the given cost heuristic
+ */
+ template< typename CostFunctor >
+ const Zone *GetClosestZone( CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL ) const
+ {
+ const Zone *closeZone = NULL;
+ float closeDist = 99999999.9f;
+
+ if (startArea == NULL)
+ return NULL;
+
+ for( int i=0; i<m_zoneCount; ++i )
+ {
+ if (m_zone[i].m_areaCount == 0)
+ continue;
+
+ if ( m_zone[i].m_isBlocked )
+ continue;
+
+ // just use the first overlapping nav area as a reasonable approximation
+ float dist = NavAreaTravelDistance( startArea, m_zone[i].m_area[0], costFunc );
+
+ if (dist >= 0.0f && dist < closeDist)
+ {
+ closeZone = &m_zone[i];
+ closeDist = dist;
+ }
+ }
+
+ if (travelDistance)
+ *travelDistance = closeDist;
+
+ return closeZone;
+ }
+
+ /// pick a zone at random and return it
+ const Zone *GetRandomZone( void ) const
+ {
+ if (m_zoneCount == 0)
+ return NULL;
+
+ int i;
+ CUtlVector< const Zone * > unblockedZones;
+ for ( i=0; i<m_zoneCount; ++i )
+ {
+ if ( m_zone[i].m_isBlocked )
+ continue;
+
+ unblockedZones.AddToTail( &(m_zone[i]) );
+ }
+
+ if ( unblockedZones.Count() == 0 )
+ return NULL;
+
+ return unblockedZones[ RandomInt( 0, unblockedZones.Count()-1 ) ];
+ }
+
+
+ /// returns a random spawn point for the given team (no arg means use both team spawnpoints)
+ CBaseEntity *GetRandomSpawn( int team = TEAM_MAXCOUNT ) const;
+
+
+ bool IsBombPlanted( void ) const { return m_isBombPlanted; } ///< returns true if bomb has been planted
+ float GetBombPlantTimestamp( void ) const { return m_bombPlantTimestamp; } ///< return time bomb was planted
+ bool IsTimeToPlantBomb( void ) const; ///< return true if it's ok to try to plant bomb
+ CCSPlayer *GetBombDefuser( void ) const { return m_bombDefuser; } ///< return the player currently defusing the bomb, or NULL
+ float GetBombTimeLeft( void ) const; ///< get the time remaining before the planted bomb explodes
+ CBaseEntity *GetLooseBomb( void ) { return m_looseBomb; } ///< return the bomb if it is loose on the ground
+ CNavArea *GetLooseBombArea( void ) const { return m_looseBombArea; } ///< return area that bomb is in/near
+ void SetLooseBomb( CBaseEntity *bomb );
+
+
+ float GetRadioMessageTimestamp( RadioType event, int teamID ) const; ///< return the last time the given radio message was sent for given team
+ float GetRadioMessageInterval( RadioType event, int teamID ) const; ///< return the interval since the last time this message was sent
+ void SetRadioMessageTimestamp( RadioType event, int teamID );
+ void ResetRadioMessageTimestamps( void );
+
+ float GetLastSeenEnemyTimestamp( void ) const { return m_lastSeenEnemyTimestamp; } ///< return the last time anyone has seen an enemy
+ void SetLastSeenEnemyTimestamp( void ) { m_lastSeenEnemyTimestamp = gpGlobals->curtime; }
+
+ float GetRoundStartTime( void ) const { return m_roundStartTimestamp; }
+ float GetElapsedRoundTime( void ) const { return gpGlobals->curtime - m_roundStartTimestamp; } ///< return the elapsed time since the current round began
+
+ bool AllowRogues( void ) const { return cv_bot_allow_rogues.GetBool(); }
+ bool AllowPistols( void ) const { return cv_bot_allow_pistols.GetBool(); }
+ bool AllowShotguns( void ) const { return cv_bot_allow_shotguns.GetBool(); }
+ bool AllowSubMachineGuns( void ) const { return cv_bot_allow_sub_machine_guns.GetBool(); }
+ bool AllowRifles( void ) const { return cv_bot_allow_rifles.GetBool(); }
+ bool AllowMachineGuns( void ) const { return cv_bot_allow_machine_guns.GetBool(); }
+ bool AllowGrenades( void ) const { return cv_bot_allow_grenades.GetBool(); }
+ bool AllowSnipers( void ) const { return cv_bot_allow_snipers.GetBool(); }
+#ifdef CS_SHIELD_ENABLED
+ bool AllowTacticalShield( void ) const { return cv_bot_allow_shield.GetBool(); }
+#else
+ bool AllowTacticalShield( void ) const { return false; }
+#endif // CS_SHIELD_ENABLED
+
+ bool AllowFriendlyFireDamage( void ) const { return friendlyfire.GetBool(); }
+
+ bool IsWeaponUseable( const CWeaponCSBase *weapon ) const; ///< return true if the bot can use this weapon
+
+ bool IsDefenseRushing( void ) const { return m_isDefenseRushing; } ///< returns true if defense team has "decided" to rush this round
+ bool IsOnDefense( const CCSPlayer *player ) const; ///< return true if this player is on "defense"
+ bool IsOnOffense( const CCSPlayer *player ) const; ///< return true if this player is on "offense"
+
+ bool IsRoundOver( void ) const { return m_isRoundOver; } ///< return true if the round has ended
+
+ #define FROM_CONSOLE true
+ bool BotAddCommand( int team, bool isFromConsole = false, const char *profileName = NULL, CSWeaponType weaponType = WEAPONTYPE_UNKNOWN, BotDifficultyType difficulty = NUM_DIFFICULTY_LEVELS ); ///< process the "bot_add" console command
+
+private:
+ enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
+
+ void MaintainBotQuota( void );
+
+ static bool m_isMapDataLoaded; ///< true if we've attempted to load map data
+ bool m_serverActive; ///< true between ServerActivate() and ServerDeactivate()
+
+ GameScenarioType m_gameScenario; ///< what kind of game are we playing
+
+ Zone m_zone[ MAX_ZONES ];
+ int m_zoneCount;
+
+ bool m_isBombPlanted; ///< true if bomb has been planted
+ float m_bombPlantTimestamp; ///< time bomb was planted
+ float m_earliestBombPlantTimestamp; ///< don't allow planting until after this time has elapsed
+ CCSPlayer *m_bombDefuser; ///< the player currently defusing a bomb
+ EHANDLE m_looseBomb; ///< will be non-NULL if bomb is loose on the ground
+ CNavArea *m_looseBombArea; ///< area that bomb is is/near
+
+ bool m_isRoundOver; ///< true if the round has ended
+
+ CountdownTimer m_checkTransientAreasTimer; ///< when elapsed, all transient nav areas should be checked for blockage
+
+ float m_radioMsgTimestamp[ RADIO_END - RADIO_START_1 ][ 2 ];
+
+ float m_lastSeenEnemyTimestamp;
+ float m_roundStartTimestamp; ///< the time when the current round began
+
+ bool m_isDefenseRushing; ///< whether defensive team is rushing this round or not
+
+ // Event Handlers --------------------------------------------------------------------------------------------
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFootstep, player_footstep )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerRadio, player_radio )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerDeath, player_death )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFallDamage, player_falldamage )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPickedUp, bomb_pickup )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPlanted, bomb_planted )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombBeep, bomb_beep )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseBegin, bomb_begindefuse )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefused, bomb_defused )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseAbort, bomb_abortdefuse )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombExploded, bomb_exploded )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundEnd, round_end )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundStart, round_start )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundFreezeEnd, round_freeze_end )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( DoorMoving, door_moving )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakProp, break_prop )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakBreakable, break_breakable )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageFollows, hostage_follows )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageRescuedAll, hostage_rescued_all )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFire, weapon_fire )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFireOnEmpty, weapon_fire_on_empty )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponReload, weapon_reload )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponZoom, weapon_zoom )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( BulletImpact, bullet_impact )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( HEGrenadeDetonate, hegrenade_detonate )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( FlashbangDetonate, flashbang_detonate )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( SmokeGrenadeDetonate, smokegrenade_detonate )
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( GrenadeBounce, grenade_bounce )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( NavBlocked, nav_blocked )
+
+ DECLARE_CSBOTMANAGER_EVENT_LISTENER( ServerShutdown, server_shutdown )
+
+ CUtlVector< BotEventInterface * > m_commonEventListeners; // These event listeners fire often, and can be disabled for performance gains when no bots are present.
+ bool m_eventListenersEnabled;
+ void EnableEventListeners( bool enable );
+};
+
+inline CBasePlayer *CCSBotManager::AllocateBotEntity( void )
+{
+ return static_cast<CBasePlayer *>( CreateEntityByName( "cs_bot" ) );
+}
+
+inline bool CCSBotManager::IsTimeToPlantBomb( void ) const
+{
+ return (gpGlobals->curtime >= m_earliestBombPlantTimestamp);
+}
+
+inline const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const CBaseEntity *entity ) const
+{
+ if (entity == NULL)
+ return NULL;
+
+ Vector centroid = entity->GetAbsOrigin();
+ centroid.z += HalfHumanHeight;
+ return GetClosestZone( centroid );
+}
+
+#endif
diff --git a/game/server/cstrike/bot/cs_bot_nav.cpp b/game/server/cstrike/bot/cs_bot_nav.cpp
new file mode 100644
index 0000000..8406101
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_nav.cpp
@@ -0,0 +1,963 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "obstacle_pushaway.h"
+#include "fmtstr.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+const float NearBreakableCheckDist = 20.0f;
+const float FarBreakableCheckDist = 300.0f;
+
+#define DEBUG_BREAKABLES 0
+#define DEBUG_DOORS 0
+
+//--------------------------------------------------------------------------------------------------------------
+#if DEBUG_BREAKABLES
+static void DrawOutlinedQuad( const Vector &p1,
+ const Vector &p2,
+ const Vector &p3,
+ const Vector &p4,
+ int r, int g, int b,
+ float duration )
+{
+ NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
+ NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
+ NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
+ NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
+ NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
+ NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
+}
+ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
+#endif // DEBUG_BREAKABLES
+
+
+//--------------------------------------------------------------------------------------------------------------
+CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
+{
+ CBaseEntity *entity = NULL;
+
+ Ray_t ray;
+ ray.Init( start, end, mins, maxs );
+
+ partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
+ if ( enumerator->m_nAlreadyHit > 0 )
+ {
+ entity = enumerator->m_AlreadyHit[0];
+ }
+
+#if DEBUG_BREAKABLES
+ if ( entity )
+ {
+ DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
+ }
+ else
+ {
+ DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
+ }
+#endif // DEBUG_BREAKABLES
+
+ return entity;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
+ */
+CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
+{
+ Vector goal;
+
+ int pathIndex = FindPathPoint( distance, &goal, NULL );
+ bool isDegeneratePath = ( pathIndex == m_pathLength );
+ if ( isDegeneratePath )
+ {
+ goal = m_goalPosition;
+ }
+ goal.z += HalfHumanHeight;
+
+ Vector mins, maxs;
+ mins = Vector( 0, 0, -HalfHumanWidth );
+ maxs = Vector( 0, 0, HalfHumanHeight );
+
+ if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
+ {
+ mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
+ maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
+ }
+
+ CBaseEntity *entity = NULL;
+ if ( isDegeneratePath )
+ {
+ entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
+#if DEBUG_BREAKABLES
+ if ( entity )
+ {
+ NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
+ }
+#endif // DEBUG_BREAKABLES
+ }
+ else
+ {
+ int startIndex = MAX( 0, m_pathIndex );
+ float distanceLeft = distance;
+ // HACK: start with an index one lower than normal, so we can trace from the bot's location to the
+ // start of the path nodes.
+ for( int i=startIndex-1; i<m_pathLength-1; ++i )
+ {
+ Vector start, end;
+ if ( i == startIndex - 1 )
+ {
+ start = GetAbsOrigin();
+ end = m_path[i+1].pos;
+ }
+ else
+ {
+ start = m_path[i].pos;
+ end = m_path[i+1].pos;
+
+ if ( m_path[i+1].how == GO_LADDER_UP )
+ {
+ // Need two checks. First we'll check along the ladder
+ start = m_path[i].pos;
+ end = m_path[i+1].ladder->m_top;
+ }
+ else if ( m_path[i].how == GO_LADDER_UP )
+ {
+ start = m_path[i].ladder->m_top;
+ }
+ else if ( m_path[i+1].how == GO_LADDER_DOWN )
+ {
+ // Need two checks. First we'll check along the ladder
+ start = m_path[i].pos;
+ end = m_path[i+1].ladder->m_bottom;
+ }
+ else if ( m_path[i].how == GO_LADDER_DOWN )
+ {
+ start = m_path[i].ladder->m_bottom;
+ }
+ }
+
+ float segmentLength = (start - end).Length();
+ if ( distanceLeft - segmentLength < 0 )
+ {
+ // scale our segment back so we don't look too far
+ Vector direction = end - start;
+ direction.NormalizeInPlace();
+
+ end = start + direction * distanceLeft;
+ }
+ entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
+ if ( entity )
+ {
+#if DEBUG_BREAKABLES
+ NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
+#endif // DEBUG_BREAKABLES
+ break;
+ }
+
+ if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
+ break;
+
+ distanceLeft -= segmentLength;
+ if ( distanceLeft < 0 )
+ break;
+
+ if ( i != startIndex - 1 && m_path[i+1].ladder )
+ {
+ // Now we'll check from the ladder out to the endpoint
+ start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
+ end = m_path[i+1].pos;
+
+ entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
+ if ( entity )
+ {
+#if DEBUG_BREAKABLES
+ NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
+#endif // DEBUG_BREAKABLES
+ break;
+ }
+ }
+ }
+ }
+
+ if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
+ return NULL;
+
+ return entity;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::PushawayTouch( CBaseEntity *pOther )
+{
+#if DEBUG_BREAKABLES
+ NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
+#endif // DEBUG_BREAKABLES
+
+ // if we're not stuck or crouched, we don't care
+ if ( !m_isStuck && !IsCrouching() )
+ return;
+
+ // See if it's breakable
+ CBaseEntity *props[1];
+ CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
+ enumerator.EnumElement( pOther );
+
+ if ( enumerator.m_nAlreadyHit == 1 )
+ {
+ // it's breakable - try to shoot it.
+ SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check for breakable physics props and other breakable entities. We do this here instead of catching them
+ * in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
+ * looking ahead like this lets us anticipate when we'll need to break something, and do it before being
+ * stopped by it.
+ */
+void CCSBot::BreakablesCheck( void )
+{
+#if DEBUG_BREAKABLES
+ /*
+ // Debug code to visually mark all breakables near us
+ {
+ Ray_t ray;
+ Vector origin = WorldSpaceCenter();
+ Vector mins( -400, -400, -400 );
+ Vector maxs( 400, 400, 400 );
+ ray.Init( origin, origin, mins, maxs );
+
+ CBaseEntity *props[40];
+ CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
+ partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
+ for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
+ {
+ CBaseEntity *prop = props[i];
+ if ( prop && prop->m_takedamage == DAMAGE_YES )
+ {
+ CFmtStr msg;
+ const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
+ if ( prop->m_iHealth > 200 )
+ {
+ NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
+ prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
+ }
+ else
+ {
+ NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
+ prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
+ }
+ }
+ }
+ }
+ */
+#endif // DEBUG_BREAKABLES
+
+ if ( IsAttacking() )
+ {
+ // make sure we aren't running into a breakable trying to knife an enemy
+ if ( IsUsingKnife() && m_enemy != NULL )
+ {
+ CBaseEntity *breakables[1];
+ CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
+
+ CBaseEntity *breakable = NULL;
+ Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
+ Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
+ breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
+ if ( breakable )
+ {
+#if DEBUG_BREAKABLES
+ NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
+#endif // DEBUG_BREAKABLES
+
+ // look at it (chances are we'll already be looking at it, since it's between us and our enemy)
+ SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
+
+ // break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
+ PrimaryAttack();
+ }
+ }
+ return;
+ }
+
+ if ( !HasPath() )
+ return;
+
+ bool isNear = true;
+
+ // Check just in front of us on the path
+ CBaseEntity *breakables[4];
+ CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
+ CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
+
+ // If we don't have an object right in front of us, check a ways out
+ if ( !breakable )
+ {
+ breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
+ isNear = false;
+ }
+
+ // Try to shoot a breakable we know about
+ if ( breakable )
+ {
+ // look at it
+ SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
+ }
+
+ // break it
+ if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
+ {
+ if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
+ {
+ EquipBestWeapon( MUST_EQUIP );
+ }
+ else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
+ {
+ bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
+
+ if ( !shouldShoot )
+ {
+ CBaseEntity *breakables[1];
+ CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
+
+ // compute the unit vector along our view
+ Vector aimDir = GetViewVector();
+
+ // trace the potential bullet's path
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+ if ( result.DidHitNonWorldEntity() )
+ {
+ LOSbreakable.EnumElement( result.m_pEnt );
+ if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[0] == breakable )
+ {
+ shouldShoot = true;
+ }
+ }
+ }
+
+ shouldShoot = shouldShoot && !IsFriendInLineOfFire();
+
+ if ( shouldShoot )
+ {
+ PrimaryAttack();
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check for doors that need +use to open.
+ */
+void CCSBot::DoorCheck( void )
+{
+ if ( IsAttacking() && !IsUsingKnife() )
+ {
+ // If we're attacking with a gun or nade, don't bother with doors. If we're trying to
+ // knife someone, we might need to open a door.
+ m_isOpeningDoor = false;
+ return;
+ }
+
+ if ( !HasPath() )
+ return;
+
+ // Find any doors that need a +use to open just in front of us along the path.
+ CBaseEntity *doors[4];
+ CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
+ CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
+
+ if ( door )
+ {
+ if ( !IsLookingAtSpot( PRIORITY_HIGH ) )
+ {
+ if ( !IsOpeningDoor() )
+ {
+ OpenDoor( door );
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset the stuck-checker.
+ */
+void CCSBot::ResetStuckMonitor( void )
+{
+ if (m_isStuck)
+ {
+ if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
+ {
+ CBasePlayer *localPlayer = UTIL_GetListenServerHost();
+ CSingleUserRecipientFilter filter( localPlayer );
+ EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
+ }
+ }
+
+ m_isStuck = false;
+ m_stuckTimestamp = 0.0f;
+ m_stuckJumpTimer.Invalidate();
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Test if we have become stuck
+ */
+void CCSBot::StuckCheck( void )
+{
+ if (m_isStuck)
+ {
+ // we are stuck - see if we have moved far enough to be considered unstuck
+ Vector delta = GetAbsOrigin() - m_stuckSpot;
+
+ const float unstuckRange = 75.0f;
+ if (delta.IsLengthGreaterThan( unstuckRange ))
+ {
+ // we are no longer stuck
+ ResetStuckMonitor();
+ PrintIfWatched( "UN-STUCK\n" );
+ }
+ }
+ else
+ {
+ // check if we are stuck
+
+ // compute average velocity over a short period (for stuck check)
+ Vector vel = GetAbsOrigin() - m_lastOrigin;
+
+ // if we are jumping, ignore Z
+ if (IsJumping())
+ vel.z = 0.0f;
+
+ // cannot be Length2D, or will break ladder movement (they are only Z)
+ float moveDist = vel.Length();
+
+ float deltaT = g_BotUpdateInterval;
+
+ m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
+
+ if (m_avgVelIndex == MAX_VEL_SAMPLES)
+ m_avgVelIndex = 0;
+
+ if (m_avgVelCount < MAX_VEL_SAMPLES)
+ {
+ m_avgVelCount++;
+ }
+ else
+ {
+ // we have enough samples to know if we're stuck
+
+ float avgVel = 0.0f;
+ for( int t=0; t<m_avgVelCount; ++t )
+ avgVel += m_avgVel[t];
+
+ avgVel /= m_avgVelCount;
+
+ // cannot make this velocity too high, or bots will get "stuck" when going down ladders
+ float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f;
+
+ if (avgVel < stuckVel)
+ {
+ // we are stuck - note when and where we initially become stuck
+ m_stuckTimestamp = gpGlobals->curtime;
+ m_stuckSpot = GetAbsOrigin();
+ m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
+
+ PrintIfWatched( "STUCK\n" );
+ if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0.0f && UTIL_GetListenServerHost())
+ {
+ CBasePlayer *localPlayer = UTIL_GetListenServerHost();
+ CSingleUserRecipientFilter filter( localPlayer );
+ EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
+ }
+
+ m_isStuck = true;
+ }
+ }
+ }
+
+ // always need to track this
+ m_lastOrigin = GetAbsOrigin();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check if we need to jump due to height change
+ */
+bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
+{
+ // Don't try to jump if in the air.
+ if( !(GetFlags() & FL_ONGROUND) )
+ {
+ return false;
+ }
+
+ float dz = ground - GetFeetZ();
+
+ if (dz > StepHeight && !onlyJumpDown)
+ {
+ // dont restrict jump time when going up
+ if (Jump( MUST_JUMP ))
+ {
+ return true;
+ }
+ }
+ else if (!IsUsingLadder() && dz < -JumpHeight)
+ {
+ if (Jump( mustJump ))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Find "simple" ground height, treating current nav area as part of the floor
+ */
+bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
+{
+ if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
+ {
+ // our current nav area also serves as a ground polygon
+ if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
+ *height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
+
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Get our current radio chatter place
+ */
+Place CCSBot::GetPlace( void ) const
+{
+ if (m_lastKnownArea)
+ return m_lastKnownArea->GetPlace();
+
+ return UNDEFINED_PLACE;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move towards position, independant of view angle
+ */
+void CCSBot::MoveTowardsPosition( const Vector &pos )
+{
+ Vector myOrigin = GetCentroid( this );
+
+ //
+ // Jump up on ledges
+ // Because we may not be able to get to our goal position and enter the next
+ // area because our extent collides with a nearby vertical ledge, make sure
+ // we look far enough ahead to avoid this situation.
+ // Can't look too far ahead, or bots will try to jump up slopes.
+ //
+ // NOTE: We need to do this frequently to catch edges at the right time
+ // @todo Look ahead *along path* instead of straight line
+ //
+ if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) &&
+ !IsOnLadder())
+ {
+ float ground;
+ Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
+ aheadRay.NormalizeInPlace();
+
+ // look far ahead to allow us to smoothly jump over gaps, ledges, etc
+ // only jump if ground is flat at lookahead spot to avoid jumping up slopes
+ bool jumped = false;
+ if (IsRunning())
+ {
+ const float farLookAheadRange = 80.0f; // 60
+ Vector normal;
+ Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
+ stepAhead.z += HalfHumanHeight;
+
+ if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
+ {
+ if (normal.z > 0.9f)
+ jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
+ }
+ }
+
+ if (!jumped)
+ {
+ // close up jumping
+ const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
+ Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
+ stepAhead.z += HalfHumanHeight;
+ if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
+ {
+ jumped = DiscontinuityJump( ground );
+ }
+ }
+
+ if (!jumped)
+ {
+ // about to fall gap-jumping
+ const float lookAheadRange = 10.0f;
+ Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
+ stepAhead.z += HalfHumanHeight;
+ if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
+ {
+ jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
+ }
+ }
+ }
+
+
+ // compute our current forward and lateral vectors
+ float angle = EyeAngles().y;
+
+ Vector2D dir( BotCOS(angle), BotSIN(angle) );
+ Vector2D lat( -dir.y, dir.x );
+
+ // compute unit vector to goal position
+ Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
+ to.NormalizeInPlace();
+
+ // move towards the position independant of our view direction
+ float toProj = to.x * dir.x + to.y * dir.y;
+ float latProj = to.x * lat.x + to.y * lat.y;
+
+ const float c = 0.25f; // 0.5
+ if (toProj > c)
+ MoveForward();
+ else if (toProj < -c)
+ MoveBackward();
+
+ // if we are avoiding someone via strafing, don't override
+ if (m_avoid != NULL)
+ return;
+
+ if (latProj >= c)
+ StrafeLeft();
+ else if (latProj <= -c)
+ StrafeRight();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move away from position, independant of view angle
+ */
+void CCSBot::MoveAwayFromPosition( const Vector &pos )
+{
+ // compute our current forward and lateral vectors
+ float angle = EyeAngles().y;
+
+ Vector2D dir( BotCOS(angle), BotSIN(angle) );
+ Vector2D lat( -dir.y, dir.x );
+
+ // compute unit vector to goal position
+ Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
+ to.NormalizeInPlace();
+
+ // move away from the position independant of our view direction
+ float toProj = to.x * dir.x + to.y * dir.y;
+ float latProj = to.x * lat.x + to.y * lat.y;
+
+ const float c = 0.5f;
+ if (toProj > c)
+ MoveBackward();
+ else if (toProj < -c)
+ MoveForward();
+
+ if (latProj >= c)
+ StrafeRight();
+ else if (latProj <= -c)
+ StrafeLeft();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Strafe (sidestep) away from position, independant of view angle
+ */
+void CCSBot::StrafeAwayFromPosition( const Vector &pos )
+{
+ // compute our current forward and lateral vectors
+ float angle = EyeAngles().y;
+
+ Vector2D dir( BotCOS(angle), BotSIN(angle) );
+ Vector2D lat( -dir.y, dir.x );
+
+ // compute unit vector to goal position
+ Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
+ to.NormalizeInPlace();
+
+ float latProj = to.x * lat.x + to.y * lat.y;
+
+ if (latProj >= 0.0f)
+ StrafeRight();
+ else
+ StrafeLeft();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * For getting un-stuck
+ */
+void CCSBot::Wiggle( void )
+{
+ if (IsCrouching())
+ {
+ return;
+ }
+
+ // for wiggling
+ if (m_wiggleTimer.IsElapsed())
+ {
+ m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
+ m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
+ }
+
+ Vector forward, right;
+ EyeVectors( &forward, &right );
+
+ const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
+ float ground;
+
+ switch( m_wiggleDirection )
+ {
+ case LEFT:
+ {
+ // don't move left if we will fall
+ Vector pos = GetAbsOrigin() - (lookAheadRange * right);
+
+ if (GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (GetAbsOrigin().z - ground < StepHeight)
+ {
+ StrafeLeft();
+ }
+ }
+ break;
+ }
+
+ case RIGHT:
+ {
+ // don't move right if we will fall
+ Vector pos = GetAbsOrigin() + (lookAheadRange * right);
+
+ if (GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (GetAbsOrigin().z - ground < StepHeight)
+ {
+ StrafeRight();
+ }
+ }
+ break;
+ }
+
+ case FORWARD:
+ {
+ // don't move forward if we will fall
+ Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
+
+ if (GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (GetAbsOrigin().z - ground < StepHeight)
+ {
+ MoveForward();
+ }
+ }
+ break;
+ }
+
+ case BACKWARD:
+ {
+ // don't move backward if we will fall
+ Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
+
+ if (GetSimpleGroundHeightWithFloor( pos, &ground ))
+ {
+ if (GetAbsOrigin().z - ground < StepHeight)
+ {
+ MoveBackward();
+ }
+ }
+ break;
+ }
+ }
+
+ if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
+ {
+ if (Jump())
+ {
+ m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine approach points from eye position and approach areas of current area
+ */
+void CCSBot::ComputeApproachPoints( void )
+{
+ m_approachPointCount = 0;
+
+ if (m_lastKnownArea == NULL)
+ {
+ return;
+ }
+
+ // assume we're crouching for now
+ Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
+
+ Vector ap;
+ float halfWidth;
+ for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
+ {
+ const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
+
+ if (info->here.area == NULL || info->prev.area == NULL)
+ {
+ continue;
+ }
+
+ // compute approach point (approach area is "info->here")
+ if (info->prevToHereHow <= GO_WEST)
+ {
+ info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
+ ap.z = info->here.area->GetZ( ap );
+ }
+ else
+ {
+ // use the area's center as an approach point
+ ap = info->here.area->GetCenter();
+ }
+
+ // "bend" our line of sight around corners until we can see the approach point
+ Vector bendPoint;
+ if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
+ {
+ // put point on the ground
+ if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
+ {
+ bendPoint.z = ap.z;
+ }
+
+ m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
+ m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
+ ++m_approachPointCount;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::DrawApproachPoints( void ) const
+{
+ for( int i=0; i<m_approachPointCount; ++i )
+ {
+ if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
+ NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
+ else
+ NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Find the approach point that is nearest to our current path, ahead of us
+ */
+bool CCSBot::FindApproachPointNearestPath( Vector *pos )
+{
+ if (!HasPath())
+ return false;
+
+ // make sure approach points are accurate
+ ComputeApproachPoints();
+
+ if (m_approachPointCount == 0)
+ return false;
+
+ Vector target = Vector( 0, 0, 0 ), close;
+ float targetRangeSq = 0.0f;
+ bool found = false;
+
+ int start = m_pathIndex;
+ int end = m_pathLength;
+
+ //
+ // We dont want the strictly closest point, but the farthest approach point
+ // from us that is near our path
+ //
+ const float nearPathSq = 10000.0f; // (100)
+
+ for( int i=0; i<m_approachPointCount; ++i )
+ {
+ if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
+ continue;
+
+ float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
+ if (rangeSq > nearPathSq)
+ continue;
+
+ if (rangeSq > targetRangeSq)
+ {
+ target = close;
+ targetRangeSq = rangeSq;
+ found = true;
+ }
+ }
+
+ if (found)
+ {
+ *pos = target + Vector( 0, 0, HalfHumanHeight );
+ return true;
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are at the/an enemy spawn right now
+ */
+bool CCSBot::IsAtEnemySpawn( void ) const
+{
+ CBaseEntity *spot;
+ const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
+
+ // check if we are at any of the enemy's spawn points
+ for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
+ {
+ CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
+ if (area && GetLastKnownArea() == area)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_pathfind.cpp b/game/server/cstrike/bot/cs_bot_pathfind.cpp
new file mode 100644
index 0000000..8604f9b
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_pathfind.cpp
@@ -0,0 +1,2075 @@
+//========= 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"
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Finds a point from which we can approach a descending ladder. First it tries behind the ladder,
+ * then in front of ladder, based on LOS. Once we know the direction, we snap to the aproaching nav
+ * area. Returns true if we're approaching from behind the ladder.
+ */
+static bool FindDescendingLadderApproachPoint( const CNavLadder *ladder, const CNavArea *area, Vector *pos )
+{
+ *pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth;
+
+ trace_t result;
+ UTIL_TraceLine( ladder->m_top, *pos, MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
+ if (result.fraction < 1.0f)
+ {
+ *pos = ladder->m_top + ladder->GetNormal() * 2.0f * HalfHumanWidth;
+
+ area->GetClosestPointOnArea( *pos, pos );
+ }
+
+ // Use a cross product to determine which side of the ladder 'pos' is on
+ Vector posToLadder = *pos - ladder->m_top;
+ float dot = posToLadder.Dot( ladder->GetNormal() );
+ return ( dot < 0.0f );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine actual path positions bot will move between along the path
+ */
+bool CCSBot::ComputePathPositions( void )
+{
+ if (m_pathLength == 0)
+ return false;
+
+ // start in first area's center
+ m_path[0].pos = m_path[0].area->GetCenter();
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ for( int i=1; i<m_pathLength; ++i )
+ {
+ const ConnectInfo *from = &m_path[ i-1 ];
+ ConnectInfo *to = &m_path[ i ];
+
+ if (to->how <= GO_WEST) // walk along the floor to the next area
+ {
+ to->ladder = NULL;
+
+ // compute next point, keeping path as straight as possible
+ from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos );
+
+ // move goal position into the goal area a bit
+ const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
+ AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist );
+
+ // we need to walk out of "from" area, so keep Z where we can reach it
+ to->pos.z = from->area->GetZ( to->pos );
+
+ // if this is a "jump down" connection, we must insert an additional point on the path
+ if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false)
+ {
+ // this is a "jump down" link
+
+ // compute direction of path just prior to "jump down"
+ Vector2D dir;
+ DirectionToVector2D( (NavDirType)to->how, &dir );
+
+ // shift top of "jump down" out a bit to "get over the ledge"
+ const float pushDist = 75.0f; // 25.0f;
+ to->pos.x += pushDist * dir.x;
+ to->pos.y += pushDist * dir.y;
+
+ // insert a duplicate node to represent the bottom of the fall
+ if (m_pathLength < MAX_PATH_LENGTH-1)
+ {
+ // copy nodes down
+ for( int j=m_pathLength; j>i; --j )
+ m_path[j] = m_path[j-1];
+
+ // path is one node longer
+ ++m_pathLength;
+
+ // move index ahead into the new node we just duplicated
+ ++i;
+
+ m_path[i].pos.x = to->pos.x;
+ m_path[i].pos.y = to->pos.y;
+
+ // put this one at the bottom of the fall
+ m_path[i].pos.z = to->area->GetZ( m_path[i].pos );
+ }
+ }
+ }
+ else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder
+ {
+ // find our ladder
+ const NavLadderConnectVector *pLadders = from->area->GetLadders( CNavLadder::LADDER_UP );
+ int it;
+ for ( it = 0; it < pLadders->Count(); ++it)
+ {
+ CNavLadder *ladder = (*pLadders)[ it ].ladder;
+
+ // can't use "behind" area when ascending...
+ if (ladder->m_topForwardArea == to->area ||
+ ladder->m_topLeftArea == to->area ||
+ ladder->m_topRightArea == to->area)
+ {
+ to->ladder = ladder;
+ to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth;
+ break;
+ }
+ }
+
+ if (it == pLadders->Count())
+ {
+ PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder
+ {
+ // find our ladder
+ const NavLadderConnectVector *pLadders = from->area->GetLadders( CNavLadder::LADDER_DOWN );
+ int it;
+ for ( it = 0; it < pLadders->Count(); ++it)
+ {
+ CNavLadder *ladder = (*pLadders)[ it ].ladder;
+
+ if (ladder->m_bottomArea == to->area)
+ {
+ to->ladder = ladder;
+
+ FindDescendingLadderApproachPoint( to->ladder, from->area, &to->pos );
+ break;
+ }
+ }
+
+ if (it == pLadders->Count())
+ {
+ PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * If next step of path uses a ladder, prepare to traverse it
+ */
+void CCSBot::SetupLadderMovement( void )
+{
+ if (m_pathIndex < 1 || m_pathLength == 0)
+ return;
+
+ const ConnectInfo *to = &m_path[ m_pathIndex ];
+ const ConnectInfo *from = &m_path[ m_pathIndex - 1 ];
+
+ if (to->ladder)
+ {
+ m_spotEncounter = NULL;
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+
+ m_pathLadder = to->ladder;
+ m_pathLadderTimestamp = gpGlobals->curtime;
+
+ QAngle ladderAngles;
+ VectorAngles( m_pathLadder->GetNormal(), ladderAngles );
+
+ // to get to next area, we must traverse a ladder
+ if (to->how == GO_LADDER_UP)
+ {
+ m_pathLadderState = APPROACH_ASCENDING_LADDER;
+ m_pathLadderFaceIn = true;
+ PrintIfWatched( "APPROACH_ASCENDING_LADDER\n" );
+ m_goalPosition = m_pathLadder->m_bottom + m_pathLadder->GetNormal() * 2.0f * HalfHumanWidth;
+ m_lookAheadAngle = AngleNormalizePositive( ladderAngles[ YAW ] + 180.0f );
+ }
+ else
+ {
+ // try to mount ladder "face out" first
+ bool behind = FindDescendingLadderApproachPoint( m_pathLadder, from->area, &m_goalPosition );
+
+ if ( behind )
+ {
+ PrintIfWatched( "APPROACH_DESCENDING_LADDER (face out)\n" );
+ m_pathLadderState = APPROACH_DESCENDING_LADDER;
+ m_pathLadderFaceIn = false;
+ m_lookAheadAngle = ladderAngles[ YAW ];
+ }
+ else
+ {
+ PrintIfWatched( "APPROACH_DESCENDING_LADDER (face in)\n" );
+ m_pathLadderState = APPROACH_DESCENDING_LADDER;
+ m_pathLadderFaceIn = true;
+ m_lookAheadAngle = AngleNormalizePositive( ladderAngles[ YAW ] + 180.0f );
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/// @todo What about ladders whose top AND bottom are messed up?
+void CCSBot::ComputeLadderEndpoint( bool isAscending )
+{
+ trace_t result;
+ Vector from, to;
+
+ if (isAscending)
+ {
+ // find actual top in case m_pathLadder penetrates the ceiling
+ // trace from our chest height at m_pathLadder base
+ from = m_pathLadder->m_bottom + m_pathLadder->GetNormal() * HalfHumanWidth;
+ from.z = GetAbsOrigin().z + HalfHumanHeight;
+ to = m_pathLadder->m_top;
+ }
+ else
+ {
+ // find actual bottom in case m_pathLadder penetrates the floor
+ // trace from our chest height at m_pathLadder top
+ from = m_pathLadder->m_top + m_pathLadder->GetNormal() * HalfHumanWidth;
+ from.z = GetAbsOrigin().z + HalfHumanHeight;
+ to = m_pathLadder->m_bottom;
+ }
+
+ UTIL_TraceLine( from, m_pathLadder->m_bottom, MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction == 1.0f)
+ m_pathLadderEnd = to.z;
+ else
+ m_pathLadderEnd = from.z + result.fraction * (to.z - from.z);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Navigate our current ladder. Return true if we are doing ladder navigation.
+ * @todo Need Push() and Pop() for run/walk context to keep ladder speed contained.
+ */
+bool CCSBot::UpdateLadderMovement( void )
+{
+ if (m_pathLadder == NULL)
+ return false;
+
+ bool giveUp = false;
+
+ // check for timeout
+ const float ladderTimeoutDuration = 10.0f;
+ if (gpGlobals->curtime - m_pathLadderTimestamp > ladderTimeoutDuration && !cv_bot_debug.GetBool())
+ {
+ PrintIfWatched( "Ladder timeout!\n" );
+ giveUp = true;
+ }
+ else if (m_pathLadderState == APPROACH_ASCENDING_LADDER ||
+ m_pathLadderState == APPROACH_DESCENDING_LADDER ||
+ m_pathLadderState == ASCEND_LADDER ||
+ m_pathLadderState == DESCEND_LADDER ||
+ m_pathLadderState == DISMOUNT_ASCENDING_LADDER ||
+ m_pathLadderState == MOVE_TO_DESTINATION)
+ {
+ if (m_isStuck)
+ {
+ PrintIfWatched( "Giving up ladder - stuck\n" );
+ giveUp = true;
+ }
+ }
+
+ if (giveUp)
+ {
+ // jump off ladder and give up
+ Jump( MUST_JUMP );
+ Wiggle();
+ ResetStuckMonitor();
+ DestroyPath();
+ Run();
+ return false;
+ }
+ else
+ {
+ ResetStuckMonitor();
+ }
+
+ Vector myOrigin = GetCentroid( this );
+
+ // check if somehow we totally missed the ladder
+ switch( m_pathLadderState )
+ {
+ case MOUNT_ASCENDING_LADDER:
+ case MOUNT_DESCENDING_LADDER:
+ case ASCEND_LADDER:
+ case DESCEND_LADDER:
+ {
+ const float farAway = 200.0f;
+ const Vector &ladderPos = (m_pathLadderState == MOUNT_ASCENDING_LADDER ||
+ m_pathLadderState == ASCEND_LADDER) ? m_pathLadder->m_bottom : m_pathLadder->m_top;
+ if ((ladderPos.AsVector2D() - myOrigin.AsVector2D()).IsLengthGreaterThan( farAway ))
+ {
+ PrintIfWatched( "Missed ladder\n" );
+ Jump( MUST_JUMP );
+ DestroyPath();
+ Run();
+ return false;
+ }
+ break;
+ }
+ }
+
+
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+
+ const float tolerance = 10.0f;
+ const float closeToGoal = 25.0f;
+
+ switch( m_pathLadderState )
+ {
+ case APPROACH_ASCENDING_LADDER:
+ {
+ bool approached = false;
+
+ Vector2D d( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
+
+ if (d.x * m_pathLadder->GetNormal().x + d.y * m_pathLadder->GetNormal().y < 0.0f)
+ {
+ Vector2D perp( -m_pathLadder->GetNormal().y, m_pathLadder->GetNormal().x );
+
+ if (fabs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal)
+ approached = true;
+ }
+
+ // small radius will just slow them down a little for more accuracy in hitting their spot
+ const float walkRange = 50.0f;
+ if (d.IsLengthLessThan( walkRange ))
+ {
+ Walk();
+ StandUp();
+ }
+
+ if ( d.IsLengthLessThan( 100.0f ) )
+ {
+ if ( !IsOnLadder() && (m_pathLadder->m_bottom.z - GetAbsOrigin().z > JumpCrouchHeight ) )
+ {
+ // find yaw to directly aim at ladder
+ QAngle idealAngle;
+ VectorAngles( GetAbsVelocity(), idealAngle );
+ const float angleTolerance = 15.0f;
+ if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
+ {
+ Jump();
+ }
+ }
+ }
+
+ /// @todo Check that we are on the ladder we think we are
+ if (IsOnLadder())
+ {
+ m_pathLadderState = ASCEND_LADDER;
+ PrintIfWatched( "ASCEND_LADDER\n" );
+
+ // find actual top in case m_pathLadder penetrates the ceiling
+ ComputeLadderEndpoint( true );
+ }
+ else if (approached)
+ {
+ // face the m_pathLadder
+ m_pathLadderState = FACE_ASCENDING_LADDER;
+ PrintIfWatched( "FACE_ASCENDING_LADDER\n" );
+ }
+ else
+ {
+ // move toward ladder mount point
+ MoveTowardsPosition( m_goalPosition );
+ }
+ break;
+ }
+
+ case APPROACH_DESCENDING_LADDER:
+ {
+ // fall check
+ if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
+ {
+ PrintIfWatched( "Fell from ladder.\n" );
+
+ m_pathLadderState = MOVE_TO_DESTINATION;
+ m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
+ m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
+
+ PrintIfWatched( "MOVE_TO_DESTINATION\n" );
+ }
+ else
+ {
+ bool approached = false;
+
+ Vector2D d( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
+
+ if (d.x * m_pathLadder->GetNormal().x + d.y * m_pathLadder->GetNormal().y > 0.0f)
+ {
+ Vector2D perp( -m_pathLadder->GetNormal().y, m_pathLadder->GetNormal().x );
+
+ if (fabs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal)
+ approached = true;
+ }
+
+ // if approaching ladder from the side or "ahead", walk
+ if (m_pathLadder->m_topBehindArea != m_lastKnownArea)
+ {
+ const float walkRange = 150.0f;
+ if (!IsCrouching() && d.IsLengthLessThan( walkRange ))
+ Walk();
+ }
+
+ /// @todo Check that we are on the ladder we think we are
+ if (IsOnLadder())
+ {
+ // we slipped onto the ladder - climb it
+ m_pathLadderState = DESCEND_LADDER;
+ Run();
+ PrintIfWatched( "DESCEND_LADDER\n" );
+
+ // find actual bottom in case m_pathLadder penetrates the floor
+ ComputeLadderEndpoint( false );
+ }
+ else if (approached)
+ {
+ // face the ladder
+ m_pathLadderState = FACE_DESCENDING_LADDER;
+ PrintIfWatched( "FACE_DESCENDING_LADDER\n" );
+ }
+ else
+ {
+ // move toward ladder mount point
+ MoveTowardsPosition( m_goalPosition );
+ }
+ }
+ break;
+ }
+
+ case FACE_ASCENDING_LADDER:
+ {
+ // find yaw to directly aim at ladder
+ Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
+
+ QAngle idealAngle;
+ VectorAngles( to, idealAngle );
+
+ if (m_path[ m_pathIndex ].area == m_pathLadder->m_topForwardArea)
+ {
+ m_pathLadderDismountDir = FORWARD;
+ }
+ else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topLeftArea)
+ {
+ m_pathLadderDismountDir = LEFT;
+ idealAngle[ YAW ] = AngleNormalizePositive( idealAngle[ YAW ] + 90.0f );
+ }
+ else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topRightArea)
+ {
+ m_pathLadderDismountDir = RIGHT;
+ idealAngle[ YAW ] = AngleNormalizePositive( idealAngle[ YAW ] - 90.0f );
+ }
+
+ const float angleTolerance = 5.0f;
+ if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
+ {
+ // move toward ladder until we become "on" it
+ Run();
+ ResetStuckMonitor();
+ m_pathLadderState = MOUNT_ASCENDING_LADDER;
+ switch (m_pathLadderDismountDir)
+ {
+ case LEFT: PrintIfWatched( "MOUNT_ASCENDING_LADDER LEFT\n" ); break;
+ case RIGHT: PrintIfWatched( "MOUNT_ASCENDING_LADDER RIGHT\n" ); break;
+ default: PrintIfWatched( "MOUNT_ASCENDING_LADDER FORWARD\n" ); break;
+ }
+ }
+ break;
+ }
+
+ case FACE_DESCENDING_LADDER:
+ {
+ // find yaw to directly aim at ladder
+ Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
+
+ QAngle idealAngle;
+ VectorAngles( to, idealAngle );
+
+ const float angleTolerance = 5.0f;
+ if (AnglesAreEqual( EyeAngles().y, idealAngle.y, angleTolerance ))
+ {
+ // move toward ladder until we become "on" it
+ m_pathLadderState = MOUNT_DESCENDING_LADDER;
+ ResetStuckMonitor();
+ PrintIfWatched( "MOUNT_DESCENDING_LADDER\n" );
+ }
+ break;
+ }
+
+ case MOUNT_ASCENDING_LADDER:
+ if (IsOnLadder())
+ {
+ m_pathLadderState = ASCEND_LADDER;
+ PrintIfWatched( "ASCEND_LADDER\n" );
+
+ // find actual top in case m_pathLadder penetrates the ceiling
+ ComputeLadderEndpoint( true );
+ }
+
+ // move toward ladder mount point
+ if ( !IsOnLadder() && (m_pathLadder->m_bottom.z - GetAbsOrigin().z > JumpCrouchHeight ) )
+ {
+ Jump();
+ }
+
+ switch( m_pathLadderDismountDir )
+ {
+ case RIGHT: StrafeLeft(); break;
+ case LEFT: StrafeRight(); break;
+ default: MoveForward(); break;
+ }
+ break;
+
+ case MOUNT_DESCENDING_LADDER:
+ // fall check
+ if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
+ {
+ PrintIfWatched( "Fell from ladder.\n" );
+
+ m_pathLadderState = MOVE_TO_DESTINATION;
+ m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
+ m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
+
+ PrintIfWatched( "MOVE_TO_DESTINATION\n" );
+ }
+ else
+ {
+ if (IsOnLadder())
+ {
+ m_pathLadderState = DESCEND_LADDER;
+ PrintIfWatched( "DESCEND_LADDER\n" );
+
+ // find actual bottom in case m_pathLadder penetrates the floor
+ ComputeLadderEndpoint( false );
+ }
+
+ // move toward ladder mount point
+ MoveForward();
+ }
+ break;
+
+ case ASCEND_LADDER:
+ // run, so we can make our dismount jump to the side, if necessary
+ Run();
+
+ // if our destination area requires us to crouch, do it
+ if (m_path[ m_pathIndex ].area->GetAttributes() & NAV_MESH_CROUCH)
+ Crouch();
+
+ // did we reach the top?
+ if (GetFeetZ() >= m_pathLadderEnd)
+ {
+ // we reached the top - dismount
+ m_pathLadderState = DISMOUNT_ASCENDING_LADDER;
+ PrintIfWatched( "DISMOUNT_ASCENDING_LADDER\n" );
+
+ if (m_path[ m_pathIndex ].area == m_pathLadder->m_topForwardArea)
+ m_pathLadderDismountDir = FORWARD;
+ else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topLeftArea)
+ m_pathLadderDismountDir = LEFT;
+ else if (m_path[ m_pathIndex ].area == m_pathLadder->m_topRightArea)
+ m_pathLadderDismountDir = RIGHT;
+
+ m_pathLadderDismountTimestamp = gpGlobals->curtime;
+ }
+ else if (!IsOnLadder())
+ {
+ // we fall off the ladder, repath
+ DestroyPath();
+ return false;
+ }
+
+ // move up ladder
+ switch( m_pathLadderDismountDir )
+ {
+ case RIGHT: StrafeLeft(); break;
+ case LEFT: StrafeRight(); break;
+ default: MoveForward(); break;
+ }
+ break;
+
+ case DESCEND_LADDER:
+ {
+ Run();
+ float destHeight = m_pathLadderEnd;
+ if ( (m_path[ m_pathIndex ].area->GetAttributes() & NAV_MESH_NO_JUMP) == 0 )
+ {
+ destHeight += HalfHumanHeight;
+ }
+ if ( !IsOnLadder() || GetFeetZ() <= destHeight )
+ {
+ // we reached the bottom, or we fell off - dismount
+ m_pathLadderState = MOVE_TO_DESTINATION;
+ m_path[ m_pathIndex ].area->GetClosestPointOnArea( m_pathLadder->m_bottom, &m_goalPosition );
+ m_goalPosition += m_pathLadder->GetNormal() * HalfHumanWidth;
+
+ PrintIfWatched( "MOVE_TO_DESTINATION\n" );
+ }
+
+ // Move down ladder
+ MoveForward();
+
+ break;
+ }
+
+ case DISMOUNT_ASCENDING_LADDER:
+ {
+ if (gpGlobals->curtime - m_pathLadderDismountTimestamp >= 0.4f)
+ {
+ m_pathLadderState = MOVE_TO_DESTINATION;
+ m_path[ m_pathIndex ].area->GetClosestPointOnArea( myOrigin, &m_goalPosition );
+ PrintIfWatched( "MOVE_TO_DESTINATION\n" );
+ }
+
+ // We should already be facing the dismount point
+ MoveForward();
+ break;
+ }
+
+ case MOVE_TO_DESTINATION:
+ if (m_path[ m_pathIndex ].area->Contains( myOrigin ))
+ {
+ // successfully traversed ladder and reached destination area
+ // exit ladder state machine
+ PrintIfWatched( "Ladder traversed.\n" );
+ m_pathLadder = NULL;
+
+ // incrememnt path index to next step beyond this ladder
+ SetPathIndex( m_pathIndex+1 );
+
+ ClearLookAt();
+
+ return false;
+ }
+
+ MoveTowardsPosition( m_goalPosition );
+ break;
+ }
+
+ if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
+ {
+ DrawPath();
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute closest point on path to given point
+ * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
+ */
+bool CCSBot::FindClosestPointOnPath( const Vector &worldPos, int startIndex, int endIndex, Vector *close ) const
+{
+ if (!HasPath() || close == NULL)
+ return false;
+
+ Vector along, toWorldPos;
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ float distSq;
+
+ for( int i=startIndex; i<=endIndex; ++i )
+ {
+ from = &m_path[i-1].pos;
+ to = &m_path[i].pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toWorldPos = worldPos - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toWorldPos, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - worldPos).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ closeDistSq = distSq;
+ *close = pos;
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest point to our current position on our current path
+ * If "local" is true, only check the portion of the path surrounding m_pathIndex.
+ */
+int CCSBot::FindOurPositionOnPath( Vector *close, bool local ) const
+{
+ if (!HasPath())
+ return -1;
+
+ Vector along, toFeet;
+ Vector feet = GetAbsOrigin();
+ Vector eyes = feet + Vector( 0, 0, HalfHumanHeight ); // in case we're crouching
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ int closeIndex = -1;
+ float distSq;
+
+ int start, end;
+
+ if (local)
+ {
+ start = m_pathIndex - 3;
+ if (start < 1)
+ start = 1;
+
+ end = m_pathIndex + 3;
+ if (end > m_pathLength)
+ end = m_pathLength;
+ }
+ else
+ {
+ start = 1;
+ end = m_pathLength;
+ }
+
+ for( int i=start; i<end; ++i )
+ {
+ from = &m_path[i-1].pos;
+ to = &m_path[i].pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toFeet = feet - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toFeet, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - feet).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ))
+ continue;
+
+ // don't use points we cant reach
+ if (!IsStraightLinePathWalkable( pos ))
+ continue;
+
+ closeDistSq = distSq;
+ if (close)
+ *close = pos;
+ closeIndex = i-1;
+ }
+ }
+
+ return closeIndex;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Test for un-jumpable height change, or unrecoverable fall
+ */
+bool CCSBot::IsStraightLinePathWalkable( const Vector &goal ) const
+{
+// this is causing hang-up problems when crawling thru ducts/windows that drop off into rooms (they fail the "falling" check)
+return true;
+
+ const float inc = GenerationStepSize;
+
+ Vector feet = GetAbsOrigin();
+ Vector dir = goal - feet;
+ float length = dir.NormalizeInPlace();
+
+ float lastGround;
+ //if (!GetSimpleGroundHeight( &pev->origin, &lastGround ))
+ // return false;
+ lastGround = feet.z;
+
+
+ float along=0.0f;
+ Vector pos;
+ float ground;
+ bool done = false;
+ while( !done )
+ {
+ along += inc;
+ if (along > length)
+ {
+ along = length;
+ done = true;
+ }
+
+ // compute step along path
+ pos = feet + along * dir;
+
+ pos.z += HalfHumanHeight;
+
+ if (!TheNavMesh->GetSimpleGroundHeight( pos, &ground ))
+ return false;
+
+ // check for falling
+ if (ground - lastGround < -StepHeight)
+ return false;
+
+ // check for unreachable jump
+ // use slightly shorter jump limit, to allow for some fudge room
+ if (ground - lastGround > JumpHeight)
+ return false;
+
+ lastGround = ground;
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute a point a fixed distance ahead along our path.
+ * Returns path index just after point.
+ */
+int CCSBot::FindPathPoint( float aheadRange, Vector *point, int *prevIndex )
+{
+ Vector myOrigin = GetCentroid( this );
+
+ // find path index just past aheadRange
+ int afterIndex;
+
+ // finds the closest point on local area of path, and returns the path index just prior to it
+ Vector close;
+ int startIndex = FindOurPositionOnPath( &close, true );
+
+ if (prevIndex)
+ *prevIndex = startIndex;
+
+ if (startIndex <= 0)
+ {
+ // went off the end of the path
+ // or next point in path is unwalkable (ie: jump-down)
+ // keep same point
+ return m_pathIndex;
+ }
+
+ // if we are crouching, just follow the path exactly
+ if (IsCrouching())
+ {
+ // we want to move to the immediately next point along the path from where we are now
+ int index = startIndex+1;
+ if (index >= m_pathLength)
+ index = m_pathLength-1;
+
+ *point = m_path[ index ].pos;
+
+ // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f; // 10
+ while ((*point - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++index;
+
+ if (index >= m_pathLength)
+ {
+ index = m_pathLength-1;
+ break;
+ }
+
+ *point = m_path[ index ].pos;
+ }
+
+ return index;
+ }
+
+ // make sure we use a node a minimum distance ahead of us, to avoid wiggling
+ while (startIndex < m_pathLength-1)
+ {
+ Vector pos = m_path[ startIndex+1 ].pos;
+
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f;
+ if ((pos - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++startIndex;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // if we hit a ladder, stop, or jump area, must stop (dont use ladder behind us)
+ if (startIndex > m_pathIndex && startIndex < m_pathLength &&
+ (m_path[ startIndex ].ladder || m_path[ startIndex ].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP)))
+ {
+ *point = m_path[ startIndex ].pos;
+ return startIndex;
+ }
+
+ // we need the point just *ahead* of us
+ ++startIndex;
+ if (startIndex >= m_pathLength)
+ startIndex = m_pathLength-1;
+
+ // if we hit a ladder, stop, or jump area, must stop
+ if (startIndex < m_pathLength &&
+ (m_path[ startIndex ].ladder || m_path[ startIndex ].area->GetAttributes() & (NAV_MESH_JUMP | NAV_MESH_STOP)))
+ {
+ *point = m_path[ startIndex ].pos;
+ return startIndex;
+ }
+
+ // note direction of path segment we are standing on
+ Vector initDir = m_path[ startIndex ].pos - m_path[ startIndex-1 ].pos;
+ initDir.NormalizeInPlace();
+
+ Vector feet = GetAbsOrigin();
+ Vector eyes = feet + Vector( 0, 0, HalfHumanHeight );
+ float rangeSoFar = 0;
+
+ // this flag is true if our ahead point is visible
+ bool visible = true;
+
+ Vector prevDir = initDir;
+
+ // step along the path until we pass aheadRange
+ bool isCorner = false;
+ int i;
+ for( i=startIndex; i<m_pathLength; ++i )
+ {
+ Vector pos = m_path[i].pos;
+ Vector to = pos - m_path[i-1].pos;
+ Vector dir = to;
+ dir.NormalizeInPlace();
+
+ // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
+ if (DotProduct( dir, initDir ) < 0.0f) // -0.25f
+ {
+ --i;
+ break;
+ }
+
+ // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
+ if (DotProduct( dir, prevDir ) < 0.5f)
+ {
+ isCorner = true;
+ --i;
+ break;
+ }
+ prevDir = dir;
+
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ))
+ {
+ // presumably, the previous point is visible, so we will interpolate
+ visible = false;
+ break;
+ }
+
+ // if we encounter a ladder or jump area, we must stop
+ if (i < m_pathLength &&
+ (m_path[ i ].ladder || m_path[ i ].area->GetAttributes() & NAV_MESH_JUMP))
+ break;
+
+ // Check straight-line path from our current position to this position
+ // Test for un-jumpable height change, or unrecoverable fall
+ if (!IsStraightLinePathWalkable( pos ))
+ {
+ --i;
+ break;
+ }
+
+ Vector along = (i == startIndex) ? (pos - feet) : (pos - m_path[i-1].pos);
+ rangeSoFar += along.Length2D();
+
+ // stop if we have gone farther than aheadRange
+ if (rangeSoFar >= aheadRange)
+ break;
+ }
+
+ if (i < startIndex)
+ afterIndex = startIndex;
+ else if (i < m_pathLength)
+ afterIndex = i;
+ else
+ afterIndex = m_pathLength-1;
+
+
+ // compute point on the path at aheadRange
+ if (afterIndex == 0)
+ {
+ *point = m_path[0].pos;
+ }
+ else
+ {
+ // interpolate point along path segment
+ const Vector *afterPoint = &m_path[ afterIndex ].pos;
+ const Vector *beforePoint = &m_path[ afterIndex-1 ].pos;
+
+ Vector to = *afterPoint - *beforePoint;
+ float length = to.Length2D();
+
+ float t = 1.0f - ((rangeSoFar - aheadRange) / length);
+
+ if (t < 0.0f)
+ t = 0.0f;
+ else if (t > 1.0f)
+ t = 1.0f;
+
+ *point = *beforePoint + t * to;
+
+ // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
+ if (!visible)
+ {
+ const float sightStepSize = 25.0f;
+ float dt = sightStepSize / length;
+
+ Vector probe = *point + Vector( 0, 0, HalfHumanHeight );
+ while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) )
+ {
+ t -= dt;
+ *point = *beforePoint + t * to;
+ }
+
+ if (t <= 0.0f)
+ *point = *beforePoint;
+ }
+ }
+
+ // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
+ if (!isCorner)
+ {
+ const float epsilon = 50.0f;
+ Vector2D toPoint;
+ toPoint.x = point->x - myOrigin.x;
+ toPoint.y = point->y - myOrigin.y;
+ if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon ))
+ {
+ int i;
+ for( i=startIndex; i<m_pathLength; ++i )
+ {
+ toPoint.x = m_path[i].pos.x - myOrigin.x;
+ toPoint.y = m_path[i].pos.y - myOrigin.y;
+ if (m_path[i].ladder || m_path[i].area->GetAttributes() & NAV_MESH_JUMP || toPoint.IsLengthGreaterThan( epsilon ))
+ {
+ *point = m_path[i].pos;
+ startIndex = i;
+ break;
+ }
+ }
+
+ if (i == m_pathLength)
+ {
+ *point = GetPathEndpoint();
+ startIndex = m_pathLength-1;
+ }
+ }
+ }
+
+ // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
+ return startIndex;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the current index along the path
+ */
+void CCSBot::SetPathIndex( int newIndex )
+{
+ m_pathIndex = MIN( newIndex, m_pathLength-1 );
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+
+ if (m_path[ m_pathIndex ].ladder)
+ {
+ SetupLadderMovement();
+ }
+ else
+ {
+ // get our "encounter spots" for this leg of the path
+ if (m_pathIndex < m_pathLength && m_pathIndex >= 2)
+ m_spotEncounter = m_path[ m_pathIndex-1 ].area->GetSpotEncounter( m_path[ m_pathIndex-2 ].area, m_path[ m_pathIndex ].area );
+ else
+ m_spotEncounter = NULL;
+
+ m_pathLadder = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if nearing a jump in the path
+ */
+bool CCSBot::IsNearJump( void ) const
+{
+ if (m_pathIndex == 0 || m_pathIndex >= m_pathLength)
+ return false;
+
+ for( int i=m_pathIndex-1; i<m_pathIndex; ++i )
+ {
+ if (m_path[ i ].area->GetAttributes() & NAV_MESH_JUMP)
+ {
+ float dz = m_path[ i+1 ].pos.z - m_path[ i ].pos.z;
+
+ if (dz > 0.0f)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return approximately how much damage will will take from the given fall height
+ */
+float CCSBot::GetApproximateFallDamage( float height ) const
+{
+ // empirically discovered height values
+ const float slope = 0.2178f;
+ const float intercept = 26.0f;
+
+ float damage = slope * height - intercept;
+
+ if (damage < 0.0f)
+ return 0.0f;
+
+ return damage;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if a friend is between us and the given position
+ */
+bool CCSBot::IsFriendInTheWay( const Vector &goalPos )
+{
+ // do this check less often to ease CPU burden
+ if (!m_avoidFriendTimer.IsElapsed())
+ {
+ return m_isFriendInTheWay;
+ }
+
+ const float avoidFriendInterval = 0.5f;
+ m_avoidFriendTimer.Start( avoidFriendInterval );
+
+ // compute ray along intended path
+ Vector myOrigin = GetCentroid( this );
+ Vector moveDir = goalPos - myOrigin;
+
+ // make it a unit vector
+ float length = moveDir.NormalizeInPlace();
+
+ m_isFriendInTheWay = false;
+
+ // check if any friends are overlapping this linear path
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSPlayer *player = static_cast<CCSPlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ if (!player->InSameTeam( this ))
+ continue;
+
+ if (player->entindex() == entindex())
+ continue;
+
+ // compute vector from us to our friend
+ Vector toFriend = player->GetAbsOrigin() - GetAbsOrigin();
+
+ // check if friend is in our "personal space"
+ const float personalSpace = 100.0f;
+ if (toFriend.IsLengthGreaterThan( personalSpace ))
+ continue;
+
+ // find distance of friend along our movement path
+ float friendDistAlong = DotProduct( toFriend, moveDir );
+
+ // if friend is behind us, ignore him
+ if (friendDistAlong <= 0.0f)
+ continue;
+
+ // constrain point to be on path segment
+ Vector pos;
+ if (friendDistAlong >= length)
+ pos = goalPos;
+ else
+ pos = myOrigin + friendDistAlong * moveDir;
+
+ // check if friend overlaps our intended line of movement
+ const float friendRadius = 30.0f;
+ if ((pos - GetCentroid( player )).IsLengthLessThan( friendRadius ))
+ {
+ // friend is in our personal space and overlaps our intended line of movement
+ m_isFriendInTheWay = true;
+ break;
+ }
+ }
+
+ return m_isFriendInTheWay;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Do reflex avoidance movements if our "feelers" are touched
+ */
+void CCSBot::FeelerReflexAdjustment( Vector *goalPosition )
+{
+ // if we are in a "precise" area, do not do feeler adjustments
+ if (m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_PRECISE)
+ return;
+
+ Vector dir( BotCOS( m_forwardAngle ), BotSIN( m_forwardAngle ), 0.0f );
+ Vector lat( -dir.y, dir.x, 0.0f );
+
+ const float feelerOffset = (IsCrouching()) ? 15.0f : 20.0f;
+ const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
+ const float feelerLengthWalk = 30.0f;
+ const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
+
+ float feelerLength = (IsRunning()) ? feelerLengthRun : feelerLengthWalk;
+
+ feelerLength = (IsCrouching()) ? 20.0f : feelerLength;
+
+ //
+ // Feelers must follow floor slope
+ //
+ float ground;
+ Vector normal;
+ Vector eye = EyePosition();
+ if (GetSimpleGroundHeightWithFloor( eye, &ground, &normal ) == false)
+ return;
+
+ // get forward vector along floor
+ dir = CrossProduct( lat, normal );
+
+ // correct the sideways vector
+ lat = CrossProduct( dir, normal );
+
+
+ Vector feet = GetAbsOrigin();
+ feet.z += feelerHeight;
+
+ Vector from = feet + feelerOffset * lat;
+ Vector to = from + feelerLength * dir;
+
+ bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+ // avoid ledges, too
+ // use 'from' so it doesn't interfere with legitimate gap jumping (its at our feet)
+ /// @todo Rethink this - it causes lots of wiggling when bots jump down from vents, etc
+/*
+ float ground;
+ if (GetSimpleGroundHeightWithFloor( &from, &ground ))
+ {
+ if (GetFeetZ() - ground > JumpHeight)
+ leftClear = false;
+ }
+*/
+
+ if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
+ {
+ if (leftClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+ from = feet - feelerOffset * lat;
+ to = from + feelerLength * dir;
+
+ bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+/*
+ // avoid ledges, too
+ if (GetSimpleGroundHeightWithFloor( &from, &ground ))
+ {
+ if (GetFeetZ() - ground > JumpHeight)
+ rightClear = false;
+ }
+*/
+
+ if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
+ {
+ if (rightClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+ const float avoidRange = (IsCrouching()) ? 150.0f : 300.0f; // 50, 300
+
+ if (!rightClear)
+ {
+ if (leftClear)
+ {
+ // right hit, left clear - veer left
+ *goalPosition = *goalPosition + avoidRange * lat;
+ }
+ }
+ else if (!leftClear)
+ {
+ // right clear, left hit - veer right
+ *goalPosition = *goalPosition - avoidRange * lat;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Allows the current nav area to make us run/walk without messing with our state
+ */
+bool CCSBot::IsRunning( void ) const
+{
+ // if we've forced running, go with it
+ if ( !m_mustRunTimer.IsElapsed() )
+ {
+ return BaseClass::IsRunning();
+ }
+
+ if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_RUN )
+ {
+ return true;
+ }
+
+ if ( m_lastKnownArea && m_lastKnownArea->GetAttributes() & NAV_MESH_WALK )
+ {
+ return false;
+ }
+
+ return BaseClass::IsRunning();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move along the path. Return false if end of path reached.
+ */
+CCSBot::PathResult CCSBot::UpdatePathMovement( bool allowSpeedChange )
+{
+ VPROF_BUDGET( "CCSBot::UpdatePathMovement", VPROF_BUDGETGROUP_NPCS );
+
+ if (m_pathLength == 0)
+ return PATH_FAILURE;
+
+ if (cv_bot_walk.GetBool())
+ Walk();
+
+ //
+ // If we are navigating a ladder, it overrides all other path movement until complete
+ //
+ if (UpdateLadderMovement())
+ return PROGRESSING;
+
+ // ladder failure can destroy the path
+ if (m_pathLength == 0)
+ return PATH_FAILURE;
+
+
+ // we are not supposed to be on a ladder - if we are, jump off
+ if (IsOnLadder())
+ Jump( MUST_JUMP );
+
+
+ assert( m_pathIndex < m_pathLength );
+
+ //
+ // Stop path attribute
+ //
+ if (!IsUsingLadder())
+ {
+ // if the m_isStopping flag is set, clear our movement
+ // if the m_isStopping flag is set and movement is stopped, clear m_isStopping
+ if ( m_lastKnownArea && m_isStopping )
+ {
+ ResetStuckMonitor();
+ ClearMovement();
+
+ if ( GetAbsVelocity().LengthSqr() < 0.1f )
+ {
+ m_isStopping = false;
+ }
+ else
+ {
+ return PROGRESSING;
+ }
+ }
+ } // end stop logic
+
+
+ //
+ // Check if reached the end of the path
+ //
+ bool nearEndOfPath = false;
+ if (m_pathIndex >= m_pathLength-1)
+ {
+ Vector toEnd = GetPathEndpoint() - GetAbsOrigin();
+ Vector d = toEnd; // can't use 2D because path end may be below us (jump down)
+
+ const float walkRange = 200.0f;
+
+ // walk as we get close to the goal position to ensure we hit it
+ if (d.IsLengthLessThan( walkRange ))
+ {
+ // don't walk if crouching - too slow
+ if (allowSpeedChange && !IsCrouching())
+ Walk();
+
+ // note if we are near the end of the path
+ const float nearEndRange = 50.0f;
+ if (d.IsLengthLessThan( nearEndRange ))
+ nearEndOfPath = true;
+
+ const float closeEpsilon = 20.0f;
+ if (d.IsLengthLessThan( closeEpsilon ))
+ {
+ // reached goal position - path complete
+ DestroyPath();
+
+ /// @todo We should push and pop walk state here, in case we want to continue walking after reaching goal
+ if (allowSpeedChange)
+ Run();
+
+ return END_OF_PATH;
+ }
+ }
+ }
+
+
+ //
+ // To keep us moving smoothly, we will move towards
+ // a point farther ahead of us down our path.
+ //
+ int prevIndex = 0; // closest index on path just prior to where we are now
+ const float aheadRange = 300.0f;
+ int newIndex = FindPathPoint( aheadRange, &m_goalPosition, &prevIndex );
+
+ // BOTPORT: Why is prevIndex sometimes -1?
+ if (prevIndex < 0)
+ prevIndex = 0;
+
+ // if goal position is near to us, we must be about to go around a corner - so look ahead!
+ Vector myOrigin = GetCentroid( this );
+ const float nearCornerRange = 100.0f;
+ if (m_pathIndex < m_pathLength-1 && (m_goalPosition - myOrigin).IsLengthLessThan( nearCornerRange ))
+ {
+ if (!IsLookingAtSpot( PRIORITY_HIGH ))
+ {
+ ClearLookAt();
+ InhibitLookAround( 0.5f );
+ }
+ }
+
+ // if we moved to a new node on the path, setup movement
+ if (newIndex > m_pathIndex)
+ {
+ SetPathIndex( newIndex );
+ }
+
+ //
+ // Crouching
+ //
+ if (!IsUsingLadder())
+ {
+ // if we are approaching a crouch area, crouch
+ // if there are no crouch areas coming up, stand
+ const float crouchRange = 50.0f;
+ bool didCrouch = false;
+ for( int i=prevIndex; i<m_pathLength; ++i )
+ {
+ const CNavArea *to = m_path[i].area;
+
+ // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
+ // unless we are already higher than the jump area - we must've jumped already but not moved into next area
+ if (to->GetAttributes() & NAV_MESH_JUMP && to->GetCenter().z > GetFeetZ())
+ break;
+
+ Vector close;
+ to->GetClosestPointOnArea( myOrigin, &close );
+
+ if ((close - myOrigin).AsVector2D().IsLengthGreaterThan( crouchRange ))
+ break;
+
+ if (to->GetAttributes() & NAV_MESH_CROUCH)
+ {
+ Crouch();
+ didCrouch = true;
+ ResetStuckMonitor();
+ break;
+ }
+ }
+
+ if (!didCrouch && !IsJumping())
+ {
+ // no crouch areas coming up
+ StandUp();
+ }
+
+ } // end crouching logic
+
+
+ // compute our forward facing angle
+ m_forwardAngle = UTIL_VecToYaw( m_goalPosition - myOrigin );
+
+ //
+ // Look farther down the path to "lead" our view around corners
+ //
+ Vector toGoal;
+ bool isWaitingForLadder = false;
+
+ // if we are crouching, look towards where we are moving to negotiate tight corners
+ if (IsCrouching())
+ {
+ m_lookAheadAngle = m_forwardAngle;
+ }
+ else
+ {
+ if (m_pathIndex == 0)
+ {
+ toGoal = m_path[1].pos;
+ }
+ else if (m_pathIndex < m_pathLength)
+ {
+ toGoal = m_path[ m_pathIndex ].pos - myOrigin;
+
+ // actually aim our view farther down the path
+ const float lookAheadRange = 500.0f;
+ if (!m_path[ m_pathIndex ].ladder &&
+ !IsNearJump() &&
+ toGoal.AsVector2D().IsLengthLessThan( lookAheadRange ))
+ {
+ float along = toGoal.Length2D();
+ int i;
+ for( i=m_pathIndex+1; i<m_pathLength; ++i )
+ {
+ Vector delta = m_path[i].pos - m_path[i-1].pos;
+ float segmentLength = delta.Length2D();
+
+ if (along + segmentLength >= lookAheadRange)
+ {
+ // interpolate between points to keep look ahead point at fixed distance
+ float t = (lookAheadRange - along) / (segmentLength + along);
+ Vector target;
+
+ if (t <= 0.0f)
+ target = m_path[i-1].pos;
+ else if (t >= 1.0f)
+ target = m_path[i].pos;
+ else
+ target = m_path[i-1].pos + t * delta;
+
+ toGoal = target - myOrigin;
+ break;
+ }
+
+ // if we are coming up to a ladder or a jump, look at it
+ if (m_path[i].ladder ||
+ (m_path[i].area->GetAttributes() & NAV_MESH_JUMP) ||
+ (m_path[i].area->GetAttributes() & NAV_MESH_PRECISE) ||
+ (m_path[i].area->GetAttributes() & NAV_MESH_STOP))
+ {
+ toGoal = m_path[i].pos - myOrigin;
+
+ // if anyone is on the ladder, wait
+ if (m_path[i].ladder && m_path[i].ladder->IsInUse( this ))
+ {
+ isWaitingForLadder = true;
+ ResetStuckMonitor();
+
+ // if we are too close to the ladder, back off a bit
+ const float tooCloseRange = 100.0f;
+ Vector2D delta( m_path[i].ladder->m_top.x - myOrigin.x,
+ m_path[i].ladder->m_top.y - myOrigin.y );
+ if (delta.IsLengthLessThan( tooCloseRange ))
+ {
+ MoveAwayFromPosition( m_path[i].ladder->m_top );
+ }
+ }
+
+ break;
+ }
+
+ along += segmentLength;
+ }
+
+ if (i == m_pathLength)
+ toGoal = GetPathEndpoint() - myOrigin;
+ }
+ }
+ else
+ {
+ toGoal = GetPathEndpoint() - myOrigin;
+ }
+
+ m_lookAheadAngle = UTIL_VecToYaw( toGoal );
+ }
+
+ // initialize "adjusted" goal to current goal
+ Vector adjustedGoal = m_goalPosition;
+
+ //
+ // Use short "feelers" to veer away from close-range obstacles
+ // Feelers come from our ankles, just above StepHeight, so we avoid short walls, too
+ // Don't use feelers if very near the end of the path, or about to jump
+ //
+ /// @todo Consider having feelers at several heights to deal with overhangs, etc.
+ if (!nearEndOfPath && !IsNearJump() && !IsJumping())
+ {
+ FeelerReflexAdjustment( &adjustedGoal );
+ }
+
+ // draw debug visualization
+ if ( ( cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.GetInt() == 10 )
+ {
+ DrawPath();
+
+ const Vector *pos = &m_path[ m_pathIndex ].pos;
+ UTIL_DrawBeamPoints( *pos, *pos + Vector( 0, 0, 50 ), 1, 255, 255, 0 );
+
+ UTIL_DrawBeamPoints( adjustedGoal, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
+ UTIL_DrawBeamPoints( myOrigin, adjustedGoal + Vector( 0, 0, 50 ), 1, 255, 0, 255 );
+ }
+
+ // dont use adjustedGoal, as it can vary wildly from the feeler adjustment
+ if (!IsAttacking() && IsFriendInTheWay( m_goalPosition ))
+ {
+ if (!m_isWaitingBehindFriend)
+ {
+ m_isWaitingBehindFriend = true;
+
+ const float politeDuration = 5.0f - 3.0f * GetProfile()->GetAggression();
+ m_politeTimer.Start( politeDuration );
+ }
+ else if (m_politeTimer.IsElapsed())
+ {
+ // we have run out of patience
+ m_isWaitingBehindFriend = false;
+ ResetStuckMonitor();
+
+ // repath to avoid clump of friends in the way
+ DestroyPath();
+ }
+ }
+ else if (m_isWaitingBehindFriend)
+ {
+ // we're done waiting for our friend to move
+ m_isWaitingBehindFriend = false;
+ ResetStuckMonitor();
+ }
+
+ //
+ // Move along our path if there are no friends blocking our way,
+ // or we have run out of patience
+ //
+ if (!isWaitingForLadder && (!m_isWaitingBehindFriend || m_politeTimer.IsElapsed()))
+ {
+ //
+ // Move along path
+ //
+ MoveTowardsPosition( adjustedGoal );
+
+ //
+ // Stuck check
+ //
+ if (m_isStuck && !IsJumping())
+ {
+ Wiggle();
+ }
+ }
+
+ // if our goal is high above us, we must have fallen
+ bool didFall = false;
+ if (m_goalPosition.z - GetFeetZ() > JumpCrouchHeight)
+ {
+ const float closeRange = 75.0f;
+ Vector2D to( myOrigin.x - m_goalPosition.x, myOrigin.y - m_goalPosition.y );
+ if (to.IsLengthLessThan( closeRange ))
+ {
+ // we can't reach the goal position
+ // check if we can reach the next node, in case this was a "jump down" situation
+ if (m_pathIndex < m_pathLength-1)
+ {
+ if (m_path[ m_pathIndex+1 ].pos.z - GetFeetZ() > JumpCrouchHeight)
+ {
+ // the next node is too high, too - we really did fall of the path
+ didFall = true;
+
+ for ( int i=m_pathIndex; i<=m_pathIndex+1; ++i )
+ {
+ if ( m_path[i].how == GO_LADDER_UP )
+ {
+ // if we're going up a ladder, and we're within reach of the ladder bottom, we haven't fallen
+ if ( m_path[i].pos.z - GetFeetZ() <= JumpCrouchHeight )
+ {
+ didFall = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // fell trying to get to the last node in the path
+ didFall = true;
+ }
+ }
+ }
+
+ //
+ // This timeout check is needed if the bot somehow slips way off
+ // of its path and cannot progress, but also moves around
+ // enough that it never becomes "stuck"
+ //
+ const float giveUpDuration = 4.0f;
+ if (didFall || gpGlobals->curtime - m_areaEnteredTimestamp > giveUpDuration)
+ {
+ if (didFall)
+ {
+ PrintIfWatched( "I fell off!\n" );
+ if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
+ {
+ CBasePlayer *localPlayer = UTIL_GetListenServerHost();
+ CSingleUserRecipientFilter filter( localPlayer );
+ EmitSound( filter, localPlayer->entindex(), "Bot.FellOff" );
+ }
+ }
+
+ // if we havent made any progress in a long time, give up
+ if (m_pathIndex < m_pathLength-1)
+ {
+ PrintIfWatched( "Giving up trying to get to area #%d\n", m_path[ m_pathIndex ].area->GetID() );
+ }
+ else
+ {
+ PrintIfWatched( "Giving up trying to get to end of path\n" );
+ }
+
+ Run();
+ StandUp();
+ DestroyPath();
+ ClearLookAt();
+
+ // See if we should be on a different nav area
+ CNavArea *area = TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
+ if (area && area != m_lastNavArea)
+ {
+ if (m_lastNavArea)
+ {
+ m_lastNavArea->DecrementPlayerCount( GetTeamNumber(), entindex() );
+ }
+
+ area->IncrementPlayerCount( GetTeamNumber(), entindex() );
+
+ m_lastNavArea = area;
+ if ( area->GetPlace() != UNDEFINED_PLACE )
+ {
+ const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() );
+ if ( placeName && *placeName )
+ {
+ Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH );
+ }
+ }
+
+ // generate event
+ //KeyValues *event = new KeyValues( "player_entered_area" );
+ //event->SetInt( "userid", GetUserID() );
+ //event->SetInt( "areaid", area->GetID() );
+ //gameeventmanager->FireEvent( event );
+ }
+
+ return PATH_FAILURE;
+ }
+
+ return PROGRESSING;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Build trivial path to goal, assuming we are already in the same area
+ */
+void CCSBot::BuildTrivialPath( const Vector &goal )
+{
+ Vector myOrigin = GetCentroid( this );
+
+ m_pathIndex = 1;
+ m_pathLength = 2;
+
+ m_path[0].area = m_lastKnownArea;
+ m_path[0].pos = myOrigin;
+ m_path[0].pos.z = m_lastKnownArea->GetZ( myOrigin );
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ m_path[1].area = m_lastKnownArea;
+ m_path[1].pos = goal;
+ m_path[1].pos.z = m_lastKnownArea->GetZ( goal );
+ m_path[1].ladder = NULL;
+ m_path[1].how = NUM_TRAVERSE_TYPES;
+
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+ m_spotEncounter = NULL;
+ m_pathLadder = NULL;
+
+ m_goalPosition = goal;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute shortest path to goal position via A* algorithm
+ * If 'goalArea' is NULL, path will get as close as it can.
+ */
+bool CCSBot::ComputePath( const Vector &goal, RouteType route )
+{
+ VPROF_BUDGET( "CCSBot::ComputePath", VPROF_BUDGETGROUP_NPCS );
+
+ //
+ // Throttle re-pathing
+ //
+ if (!m_repathTimer.IsElapsed())
+ return false;
+
+ // randomize to distribute CPU load
+ m_repathTimer.Start( RandomFloat( 0.4f, 0.6f ) );
+
+
+ DestroyPath();
+
+ m_pathLadder = NULL;
+
+ CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
+
+ CNavArea *startArea = m_lastKnownArea;
+ if (startArea == NULL)
+ return false;
+
+ // if we fell off a ledge onto an area off the mesh, we will path from the
+ // ledge above our heads, resulting in a path we can't follow.
+ Vector close;
+ startArea->GetClosestPointOnArea( EyePosition(), &close );
+ if (close.z - GetAbsOrigin().z > JumpCrouchHeight)
+ {
+ // we can't reach our last known area - find nearest area to us
+ PrintIfWatched( "Last known area is above my head - resetting to nearest area.\n" );
+ m_lastKnownArea = (CCSNavArea*)TheNavMesh->GetNearestNavArea( GetAbsOrigin(), false, 500.0f, true );
+ if (m_lastKnownArea == NULL)
+ {
+ return false;
+ }
+
+ startArea = m_lastKnownArea;
+ }
+
+ // note final specific position
+ Vector pathEndPosition = goal;
+
+ // make sure path end position is on the ground
+ if (goalArea)
+ pathEndPosition.z = goalArea->GetZ( pathEndPosition );
+ else
+ TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
+
+ // if we are already in the goal area, build trivial path
+ if (startArea == goalArea)
+ {
+ BuildTrivialPath( pathEndPosition );
+ return true;
+ }
+
+ //
+ // Compute shortest path to goal
+ //
+ CNavArea *closestArea = NULL;
+ PathCost cost( this, route );
+ bool pathToGoalExists = NavAreaBuildPath( startArea, goalArea, &goal, cost, &closestArea );
+
+ CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;
+
+ //
+ // Build path by following parent links
+ //
+
+ // get count
+ int count = 0;
+ CNavArea *area;
+ for( area = effectiveGoalArea; area; area = area->GetParent() )
+ ++count;
+
+ // save room for endpoint
+ if (count > MAX_PATH_LENGTH-1)
+ count = MAX_PATH_LENGTH-1;
+
+ if (count == 0)
+ return false;
+
+ if (count == 1)
+ {
+ BuildTrivialPath( pathEndPosition );
+ return true;
+ }
+
+ // build path
+ m_pathLength = count;
+ for( area = effectiveGoalArea; count && area; area = area->GetParent() )
+ {
+ --count;
+ m_path[ count ].area = area;
+ m_path[ count ].how = area->GetParentHow();
+ }
+
+ // compute path positions
+ if (ComputePathPositions() == false)
+ {
+ PrintIfWatched( "Error building path\n" );
+ DestroyPath();
+ return false;
+ }
+
+ // append path end position
+ m_path[ m_pathLength ].area = effectiveGoalArea;
+ m_path[ m_pathLength ].pos = pathEndPosition;
+ m_path[ m_pathLength ].ladder = NULL;
+ m_path[ m_pathLength ].how = NUM_TRAVERSE_TYPES;
+ ++m_pathLength;
+
+ // do movement setup
+ m_pathIndex = 1;
+ m_areaEnteredTimestamp = gpGlobals->curtime;
+ m_spotEncounter = NULL;
+ m_goalPosition = m_path[1].pos;
+
+ if (m_path[1].ladder)
+ SetupLadderMovement();
+ else
+ m_pathLadder = NULL;
+
+ // find initial encounter area along this path, if we are in the early part of the round
+ if (IsSafe())
+ {
+ int myTeam = GetTeamNumber();
+ int enemyTeam = OtherTeam( myTeam );
+ int i;
+
+ for( i=0; i<m_pathLength; ++i )
+ {
+ if (m_path[i].area->GetEarliestOccupyTime( myTeam ) > m_path[i].area->GetEarliestOccupyTime( enemyTeam ))
+ {
+ break;
+ }
+ }
+
+ if (i < m_pathLength)
+ {
+ SetInitialEncounterArea( m_path[i].area );
+ }
+ else
+ {
+ SetInitialEncounterArea( NULL );
+ }
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return estimated distance left to travel along path
+ */
+float CCSBot::GetPathDistanceRemaining( void ) const
+{
+ if (!HasPath())
+ return -1.0f;
+
+ int idx = (m_pathIndex < m_pathLength) ? m_pathIndex : m_pathLength-1;
+
+ float dist = 0.0f;
+ Vector prevCenter = m_path[m_pathIndex].area->GetCenter();
+
+ for( int i=idx+1; i<m_pathLength; ++i )
+ {
+ dist += (m_path[i].area->GetCenter() - prevCenter).Length();
+ prevCenter = m_path[i].area->GetCenter();
+ }
+
+ return dist;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Draw a portion of our current path for debugging.
+ */
+void CCSBot::DrawPath( void )
+{
+ if (!HasPath())
+ return;
+
+ for( int i=1; i<m_pathLength; ++i )
+ {
+ UTIL_DrawBeamPoints( m_path[i-1].pos, m_path[i].pos, 2, 255, 75, 0 );
+ }
+
+ Vector close;
+ if (FindOurPositionOnPath( &close, true ) >= 0)
+ {
+ UTIL_DrawBeamPoints( close + Vector( 0, 0, 25 ), close, 1, 0, 255, 0 );
+ UTIL_DrawBeamPoints( close + Vector( 25, 0, 0 ), close + Vector( -25, 0, 0 ), 1, 0, 255, 0 );
+ UTIL_DrawBeamPoints( close + Vector( 0, 25, 0 ), close + Vector( 0, -25, 0 ), 1, 0, 255, 0 );
+ }
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_radio.cpp b/game/server/cstrike/bot/cs_bot_radio.cpp
new file mode 100644
index 0000000..08acb4e
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_radio.cpp
@@ -0,0 +1,346 @@
+//========= 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"
+
+extern int gmsgBotVoice;
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if the radio message is an order to do something
+ * NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
+ */
+bool CCSBot::IsRadioCommand( RadioType event ) const
+{
+ if (event == RADIO_AFFIRMATIVE ||
+ event == RADIO_NEGATIVE ||
+ event == RADIO_ENEMY_SPOTTED ||
+ event == RADIO_SECTOR_CLEAR ||
+ event == RADIO_REPORTING_IN ||
+ event == RADIO_REPORT_IN_TEAM ||
+ event == RADIO_ENEMY_DOWN ||
+ event == RADIO_INVALID )
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Respond to radio commands from HUMAN players
+ */
+void CCSBot::RespondToRadioCommands( void )
+{
+ // bots use the chatter system to respond to each other
+ if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
+ {
+ CCSPlayer *player = m_radioSubject;
+ if (player->IsBot())
+ {
+ m_lastRadioCommand = RADIO_INVALID;
+ return;
+ }
+ }
+
+ if (m_lastRadioCommand == RADIO_INVALID)
+ return;
+
+ // a human player has issued a radio command
+ GetChatter()->ResetRadioSilenceDuration();
+
+
+ // if we are doing something important, ignore the radio
+ // unless it is a "report in" request - we can do that while we continue to do other things
+ /// @todo Create "uninterruptable" flag
+ if (m_lastRadioCommand != RADIO_REPORT_IN_TEAM)
+ {
+ if (IsBusy())
+ {
+ // consume command
+ m_lastRadioCommand = RADIO_INVALID;
+ return;
+ }
+ }
+
+ // wait for reaction time before responding
+ // delay needs to be long enough for the radio message we're responding to to finish
+ float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
+ if (IsRogue())
+ respondTime += 2.0f;
+
+ if (gpGlobals->curtime - m_lastRadioRecievedTimestamp < respondTime)
+ return;
+
+ // rogues won't follow commands, unless already following the player
+ if (!IsFollowing() && IsRogue())
+ {
+ if (IsRadioCommand( m_lastRadioCommand ))
+ {
+ GetChatter()->Negative();
+ }
+
+ // consume command
+ m_lastRadioCommand = RADIO_INVALID;
+ return;
+ }
+
+ CCSPlayer *player = m_radioSubject;
+ if (player == NULL)
+ return;
+
+ // respond to command
+ bool canDo = false;
+ const float inhibitAutoFollowDuration = 60.0f;
+ switch( m_lastRadioCommand )
+ {
+ case RADIO_REPORT_IN_TEAM:
+ {
+ GetChatter()->ReportingIn();
+ break;
+ }
+
+ case RADIO_FOLLOW_ME:
+ case RADIO_COVER_ME:
+ case RADIO_STICK_TOGETHER_TEAM:
+ case RADIO_REGROUP_TEAM:
+ {
+ if (!IsFollowing())
+ {
+ Follow( player );
+ player->AllowAutoFollow();
+ canDo = true;
+ }
+ break;
+ }
+
+ case RADIO_ENEMY_SPOTTED:
+ case RADIO_NEED_BACKUP:
+ case RADIO_TAKING_FIRE:
+ if (!IsFollowing())
+ {
+ Follow( player );
+ GetChatter()->Say( "OnMyWay" );
+ player->AllowAutoFollow();
+ canDo = false;
+ }
+ break;
+
+ case RADIO_TEAM_FALL_BACK:
+ {
+ if (TryToRetreat())
+ canDo = true;
+ break;
+ }
+
+ case RADIO_HOLD_THIS_POSITION:
+ {
+ // find the leader's area
+ SetTask( HOLD_POSITION );
+ StopFollowing();
+ player->InhibitAutoFollow( inhibitAutoFollowDuration );
+ Hide( TheNavMesh->GetNearestNavArea( m_radioPosition ) );
+ canDo = true;
+ break;
+ }
+
+ case RADIO_GO_GO_GO:
+ case RADIO_STORM_THE_FRONT:
+ StopFollowing();
+ Hunt();
+ canDo = true;
+ player->InhibitAutoFollow( inhibitAutoFollowDuration );
+ break;
+
+ case RADIO_GET_OUT_OF_THERE:
+ if (TheCSBots()->IsBombPlanted())
+ {
+ EscapeFromBomb();
+ player->InhibitAutoFollow( inhibitAutoFollowDuration );
+ canDo = true;
+ }
+ break;
+
+ case RADIO_SECTOR_CLEAR:
+ {
+ // if this is a defusal scenario, and the bomb is planted,
+ // and a human player cleared a bombsite, check it off our list too
+ if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ {
+ if (GetTeamNumber() == TEAM_CT && TheCSBots()->IsBombPlanted())
+ {
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( player );
+
+ if (zone)
+ {
+ GetGameState()->ClearBombsite( zone->m_index );
+
+ // if we are huting for the planted bomb, re-select bombsite
+ if (GetTask() == FIND_TICKING_BOMB)
+ Idle();
+
+ canDo = true;
+ }
+ }
+ }
+ break;
+ }
+
+ default:
+ // ignore all other radio commands for now
+ return;
+ }
+
+ if (canDo)
+ {
+ // affirmative
+ GetChatter()->Affirmative();
+
+ // if we agreed to follow a new command, put away our grenade
+ if (IsRadioCommand( m_lastRadioCommand ) && IsUsingGrenade())
+ {
+ EquipBestWeapon();
+ }
+ }
+
+ // consume command
+ m_lastRadioCommand = RADIO_INVALID;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Decide if we should move to help the player, return true if we will
+ */
+bool CCSBot::RespondToHelpRequest( CCSPlayer *them, Place place, float maxRange )
+{
+ if (IsRogue())
+ return false;
+
+ // if we're busy, ignore
+ if (IsBusy())
+ return false;
+
+ Vector themOrigin = GetCentroid( them );
+
+ // if we are too far away, ignore
+ if (maxRange > 0.0f)
+ {
+ // compute actual travel distance
+ PathCost cost(this);
+ float travelDistance = NavAreaTravelDistance( m_lastKnownArea, TheNavMesh->GetNearestNavArea( themOrigin ), cost );
+ if (travelDistance < 0.0f)
+ return false;
+
+ if (travelDistance > maxRange)
+ return false;
+ }
+
+
+ if (place == UNDEFINED_PLACE)
+ {
+ // if we have no "place" identifier, go directly to them
+
+ // if we are already there, ignore
+ float rangeSq = (them->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
+ const float close = 750.0f * 750.0f;
+ if (rangeSq < close)
+ return true;
+
+ MoveTo( themOrigin, FASTEST_ROUTE );
+ }
+ else
+ {
+ // if we are already there, ignore
+ if (GetPlace() == place)
+ return true;
+
+ // go to where help is needed
+ const Vector *pos = GetRandomSpotAtPlace( place );
+ if (pos)
+ {
+ MoveTo( *pos, FASTEST_ROUTE );
+ }
+ else
+ {
+ MoveTo( themOrigin, FASTEST_ROUTE );
+ }
+ }
+
+ // acknowledge
+ GetChatter()->Say( "OnMyWay" );
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Send a radio message
+ */
+void CCSBot::SendRadioMessage( RadioType event )
+{
+ // make sure this is a radio event
+ if (event <= RADIO_START_1 || event >= RADIO_END)
+ return;
+
+ PrintIfWatched( "%3.1f: SendRadioMessage( %s )\n", gpGlobals->curtime, RadioEventName[ event ] );
+
+ // note the time the message was sent
+ TheCSBots()->SetRadioMessageTimestamp( event, GetTeamNumber() );
+
+ m_lastRadioSentTimestamp = gpGlobals->curtime;
+
+ char slot[2];
+ slot[1] = '\000';
+
+ if (event > RADIO_START_1 && event < RADIO_START_2)
+ {
+ HandleMenu_Radio1( event - RADIO_START_1 );
+ }
+ else if (event > RADIO_START_2 && event < RADIO_START_3)
+ {
+ HandleMenu_Radio2( event - RADIO_START_2 );
+ }
+ else
+ {
+ HandleMenu_Radio3( event - RADIO_START_3 );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Send voice chatter. Also sends the entindex and duration for voice feedback.
+ */
+void CCSBot::SpeakAudio( const char *voiceFilename, float duration, int pitch )
+{
+ if( !IsAlive() )
+ return;
+
+ if ( IsObserver() )
+ return;
+
+ CRecipientFilter filter;
+ ConstructRadioFilter( filter );
+
+ UserMessageBegin ( filter, "RawAudio" );
+ WRITE_BYTE( pitch );
+ WRITE_BYTE( entindex() );
+ WRITE_FLOAT( duration );
+ WRITE_STRING( voiceFilename );
+ MessageEnd();
+
+ GetChatter()->ResetRadioSilenceDuration();
+
+ m_voiceEndTimestamp = gpGlobals->curtime + duration;
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_statemachine.cpp b/game/server/cstrike/bot/cs_bot_statemachine.cpp
new file mode 100644
index 0000000..8cbf9aa
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_statemachine.cpp
@@ -0,0 +1,693 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "cs_nav_path.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * This method is the ONLY legal way to change a bot's current state
+ */
+void CCSBot::SetState( BotState *state )
+{
+ PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() );
+
+ /*
+ if ( IsDefusingBomb() )
+ {
+ const Vector *bombPos = GetGameState()->GetBombPosition();
+ if ( bombPos != NULL )
+ {
+ if ( TheCSBots()->GetBombDefuser() == this )
+ {
+ if ( TheCSBots()->IsBombPlanted() )
+ {
+ Msg( "Bot %s is switching from defusing the bomb to %s\n",
+ GetPlayerName(), state->GetName() );
+ }
+ }
+ }
+ }
+ */
+
+ // if we changed state from within the special Attack state, we are no longer attacking
+ if (m_isAttacking)
+ StopAttacking();
+
+ if (m_state)
+ m_state->OnExit( this );
+
+ state->OnEnter( this );
+
+ m_state = state;
+ m_stateTimestamp = gpGlobals->curtime;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::Idle( void )
+{
+ SetTask( SEEK_AND_DESTROY );
+ SetState( &m_idleState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::EscapeFromBomb( void )
+{
+ SetTask( ESCAPE_FROM_BOMB );
+ SetState( &m_escapeFromBombState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::Follow( CCSPlayer *player )
+{
+ if (player == NULL)
+ return;
+
+ // note when we began following
+ if (!m_isFollowing || m_leader != player)
+ m_followTimestamp = gpGlobals->curtime;
+
+ m_isFollowing = true;
+ m_leader = player;
+
+ SetTask( FOLLOW );
+ m_followState.SetLeader( player );
+ SetState( &m_followState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Continue following our leader after finishing what we were doing
+ */
+void CCSBot::ContinueFollowing( void )
+{
+ SetTask( FOLLOW );
+
+ m_followState.SetLeader( m_leader );
+
+ SetState( &m_followState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Stop following
+ */
+void CCSBot::StopFollowing( void )
+{
+ m_isFollowing = false;
+ m_leader = NULL;
+ m_allowAutoFollowTime = gpGlobals->curtime + 10.0f;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin process of rescuing hostages
+ */
+void CCSBot::RescueHostages( void )
+{
+ SetTask( RESCUE_HOSTAGES );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Use the entity
+ */
+void CCSBot::UseEntity( CBaseEntity *entity )
+{
+ m_useEntityState.SetEntity( entity );
+ SetState( &m_useEntityState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Open the door.
+ * This assumes the bot is directly in front of the door with no obstructions.
+ * NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done.
+ */
+void CCSBot::OpenDoor( CBaseEntity *door )
+{
+ m_openDoorState.SetDoor( door );
+ m_isOpeningDoor = true;
+ m_openDoorState.OnEnter( this );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * DEPRECATED: Use TryToHide() instead.
+ * Move to a hiding place.
+ * If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
+ */
+void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition )
+{
+ DestroyPath();
+
+ CNavArea *source;
+ Vector sourcePos;
+ if (searchFromArea)
+ {
+ source = searchFromArea;
+ sourcePos = searchFromArea->GetCenter();
+ }
+ else
+ {
+ source = m_lastKnownArea;
+ sourcePos = GetCentroid( this );
+ }
+
+ if (source == NULL)
+ {
+ PrintIfWatched( "Hide from area is NULL.\n" );
+ Idle();
+ return;
+ }
+
+ m_hideState.SetSearchArea( source );
+ m_hideState.SetSearchRange( hideRange );
+ m_hideState.SetDuration( duration );
+ m_hideState.SetHoldPosition( holdPosition );
+
+ // search around source area for a good hiding spot
+ Vector useSpot;
+
+ const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() );
+ if (pos == NULL)
+ {
+ PrintIfWatched( "No available hiding spots.\n" );
+ // hide at our current position
+ useSpot = GetCentroid( this );
+ }
+ else
+ {
+ useSpot = *pos;
+ }
+
+ m_hideState.SetHidingSpot( useSpot );
+
+ // build a path to our new hiding spot
+ if (ComputePath( useSpot, FASTEST_ROUTE ) == false)
+ {
+ PrintIfWatched( "Can't pathfind to hiding spot\n" );
+ Idle();
+ return;
+ }
+
+ SetState( &m_hideState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to the given hiding place
+ */
+void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition )
+{
+ CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot );
+ if (hideArea == NULL)
+ {
+ PrintIfWatched( "Hiding spot off nav mesh\n" );
+ Idle();
+ return;
+ }
+
+ DestroyPath();
+
+ m_hideState.SetSearchArea( hideArea );
+ m_hideState.SetSearchRange( 750.0f );
+ m_hideState.SetDuration( duration );
+ m_hideState.SetHoldPosition( holdPosition );
+ m_hideState.SetHidingSpot( hidingSpot );
+
+ // build a path to our new hiding spot
+ if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false)
+ {
+ PrintIfWatched( "Can't pathfind to hiding spot\n" );
+ Idle();
+ return;
+ }
+
+ SetState( &m_hideState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Try to hide nearby. Return true if hiding, false if can't hide here.
+ * If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
+ */
+bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest )
+{
+ CNavArea *source;
+ Vector sourcePos;
+ if (searchFromArea)
+ {
+ source = searchFromArea;
+ sourcePos = searchFromArea->GetCenter();
+ }
+ else
+ {
+ source = m_lastKnownArea;
+ sourcePos = GetCentroid( this );
+ }
+
+ if (source == NULL)
+ {
+ PrintIfWatched( "Hide from area is NULL.\n" );
+ return false;
+ }
+
+ m_hideState.SetSearchArea( source );
+ m_hideState.SetSearchRange( hideRange );
+ m_hideState.SetDuration( duration );
+ m_hideState.SetHoldPosition( holdPosition );
+
+ // search around source area for a good hiding spot
+ const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest );
+ if (pos == NULL)
+ {
+ PrintIfWatched( "No available hiding spots.\n" );
+ return false;
+ }
+
+ m_hideState.SetHidingSpot( *pos );
+
+ // build a path to our new hiding spot
+ if (ComputePath( *pos, FASTEST_ROUTE ) == false)
+ {
+ PrintIfWatched( "Can't pathfind to hiding spot\n" );
+ return false;
+ }
+
+ SetState( &m_hideState );
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Retreat to a nearby hiding spot, away from enemies
+ */
+bool CCSBot::TryToRetreat( float maxRange, float duration )
+{
+ const Vector *spot = FindNearbyRetreatSpot( this, maxRange );
+ if (spot)
+ {
+ // ignore enemies for a second to give us time to hide
+ // reaching our hiding spot clears our disposition
+ IgnoreEnemies( 10.0f );
+
+ if (duration < 0.0f)
+ {
+ duration = RandomFloat( 3.0f, 15.0f );
+ }
+
+ StandUp();
+ Run();
+ Hide( *spot, duration );
+
+ PrintIfWatched( "Retreating to a safe spot!\n" );
+
+ return true;
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::Hunt( void )
+{
+ SetState( &m_huntState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Attack our the given victim
+ * NOTE: Attacking does not change our task.
+ */
+void CCSBot::Attack( CCSPlayer *victim )
+{
+ if (victim == NULL)
+ return;
+
+ // zombies never attack
+ if (cv_bot_zombie.GetBool())
+ return;
+
+ // cannot attack if we are reloading
+ if (IsReloading())
+ return;
+
+ // change enemy
+ SetBotEnemy( victim );
+
+ //
+ // Do not "re-enter" the attack state if we are already attacking
+ //
+ if (IsAttacking())
+ return;
+
+ // if we're holding a grenade, throw it at the victim
+ if (IsUsingGrenade())
+ {
+ // throw towards their feet
+ ThrowGrenade( victim->GetAbsOrigin() );
+ return;
+ }
+
+
+ // if we are currently hiding, increase our chances of crouching and holding position
+ if (IsAtHidingSpot())
+ m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false );
+ else
+ m_attackState.SetCrouchAndHold( false );
+
+ //SetState( &m_attackState );
+ //PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n",
+ // GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() );
+ m_isAttacking = true;
+ m_attackState.OnEnter( this );
+
+
+ Vector victimOrigin = GetCentroid( victim );
+
+ // cheat a bit and give the bot the initial location of its victim
+ m_lastEnemyPosition = victimOrigin;
+ m_lastSawEnemyTimestamp = gpGlobals->curtime;
+ m_aimSpreadTimestamp = gpGlobals->curtime;
+
+ // compute the angle difference between where are looking, and where we need to look
+ Vector toEnemy = victimOrigin - GetCentroid( this );
+
+ QAngle idealAngle;
+ VectorAngles( toEnemy, idealAngle );
+
+ float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y);
+
+ while( deltaYaw > 180.0f )
+ deltaYaw -= 360.0f;
+
+ if (deltaYaw < 0.0f)
+ deltaYaw = -deltaYaw;
+
+ // immediately aim at enemy - accuracy penalty depending on how far we must turn to aim
+ // accuracy is halved if we have to turn 180 degrees
+ float turn = deltaYaw / 180.0f;
+ float accuracy = GetProfile()->GetSkill() / (1.0f + turn);
+
+ SetAimOffset( accuracy );
+
+ // define time when aim offset will automatically be updated
+ // longer time the more we had to turn (surprise)
+ m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f );
+
+ // forget any look at targets we have
+ ClearLookAt();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Exit the Attack state
+ */
+void CCSBot::StopAttacking( void )
+{
+ PrintIfWatched( "ATTACK END\n" );
+ m_attackState.OnExit( this );
+ m_isAttacking = false;
+
+ // if we are following someone, go to the Idle state after the attack to decide whether we still want to follow
+ if (IsFollowing())
+ {
+ Idle();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CCSBot::IsAttacking( void ) const
+{
+ return m_isAttacking;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are escaping from the bomb
+ */
+bool CCSBot::IsEscapingFromBomb( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_escapeFromBombState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are defusing the bomb
+ */
+bool CCSBot::IsDefusingBomb( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_defuseBombState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are hiding
+ */
+bool CCSBot::IsHiding( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_hideState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are hiding and at our hiding spot
+ */
+bool CCSBot::IsAtHidingSpot( void ) const
+{
+ if (!IsHiding())
+ return false;
+
+ return m_hideState.IsAtSpot();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return number of seconds we have been at our current hiding spot
+ */
+float CCSBot::GetHidingTime( void ) const
+{
+ if (IsHiding())
+ {
+ return m_hideState.GetHideTime();
+ }
+
+ return 0.0f;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are huting
+ */
+bool CCSBot::IsHunting( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_huntState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are in the MoveTo state
+ */
+bool CCSBot::IsMovingTo( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_moveToState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are buying
+ */
+bool CCSBot::IsBuying( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_buyState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CCSBot::IsInvestigatingNoise( void ) const
+{
+ if (m_state == static_cast<const BotState *>( &m_investigateNoiseState ))
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to potentially distant position
+ */
+void CCSBot::MoveTo( const Vector &pos, RouteType route )
+{
+ m_moveToState.SetGoalPosition( pos );
+ m_moveToState.SetRouteType( route );
+ SetState( &m_moveToState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::PlantBomb( void )
+{
+ SetState( &m_plantBombState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Bomb has been dropped - go get it
+ */
+void CCSBot::FetchBomb( void )
+{
+ SetState( &m_fetchBombState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::DefuseBomb( void )
+{
+ SetState( &m_defuseBombState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Investigate recent enemy noise
+ */
+void CCSBot::InvestigateNoise( void )
+{
+ SetState( &m_investigateNoiseState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::Buy( void )
+{
+ SetState( &m_buyState );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move to a hiding spot and wait for initial encounter with enemy team.
+ * Return false if can't do this behavior (ie: no hiding spots available).
+ */
+bool CCSBot::MoveToInitialEncounter( void )
+{
+ int myTeam = GetTeamNumber();
+ int enemyTeam = OtherTeam( myTeam );
+
+ // build a path to an enemy spawn point
+ CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam );
+
+ if (enemySpawn == NULL)
+ {
+ PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" );
+ return false;
+ }
+
+ // build a path from us to the enemy spawn
+ CCSNavPath path;
+ PathCost cost( this, FASTEST_ROUTE );
+ path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost );
+
+ if (!path.IsValid())
+ {
+ PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" );
+ return false;
+ }
+
+ // find battlefront area where teams will first meet along this path
+ int i;
+ for( i=0; i<path.GetSegmentCount(); ++i )
+ {
+ if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam ))
+ {
+ break;
+ }
+ }
+
+ if (i == path.GetSegmentCount())
+ {
+ PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" );
+ return false;
+ }
+
+ /// @todo Remove this evil side-effect
+ SetInitialEncounterArea( path[i]->area );
+
+ // find a hiding spot on our side of the battlefront that has LOS to it
+ const float maxRange = 1500.0f;
+ const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() );
+
+ if (spot == NULL)
+ {
+ PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" );
+ return false;
+ }
+
+ float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam );
+ float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f;
+ if (timeToWait < minWaitTime)
+ {
+ timeToWait = minWaitTime;
+ }
+
+ Hide( spot->GetPosition(), timeToWait );
+
+ return true;
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_update.cpp b/game/server/cstrike/bot/cs_bot_update.cpp
new file mode 100644
index 0000000..d57af0a
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_update.cpp
@@ -0,0 +1,1211 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "cs_bot.h"
+#include "fmtstr.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------------------------------------
+float CCSBot::GetMoveSpeed( void )
+{
+ return 250.0f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Lightweight maintenance, invoked frequently
+ */
+void CCSBot::Upkeep( void )
+{
+ VPROF_BUDGET( "CCSBot::Upkeep", VPROF_BUDGETGROUP_NPCS );
+
+ if (TheNavMesh->IsGenerating() || !IsAlive())
+ return;
+
+ // If bot_flipout is on, then generate some random commands.
+ if ( cv_bot_flipout.GetBool() )
+ {
+ int val = RandomInt( 0, 2 );
+ if ( val == 0 )
+ MoveForward();
+ else if ( val == 1 )
+ MoveBackward();
+
+ val = RandomInt( 0, 2 );
+ if ( val == 0 )
+ StrafeLeft();
+ else if ( val == 1 )
+ StrafeRight();
+
+ if ( RandomInt( 0, 5 ) == 0 )
+ Jump( true );
+
+ val = RandomInt( 0, 2 );
+ if ( val == 0 )
+ Crouch();
+ else ( val == 1 );
+ StandUp();
+
+ return;
+ }
+
+ // BOTPORT: Remove this nasty hack
+ m_eyePosition = EyePosition();
+
+ Vector myOrigin = GetCentroid( this );
+
+ // aiming must be smooth - update often
+ if (IsAimingAtEnemy())
+ {
+ UpdateAimOffset();
+
+ // aim at enemy, if he's still alive
+ if (m_enemy != NULL && m_enemy->IsAlive())
+ {
+ Vector enemyOrigin = GetCentroid( m_enemy );
+
+ if (m_isEnemyVisible)
+ {
+ //
+ // Enemy is visible - determine which part of him to shoot at
+ //
+ const float sharpshooter = 0.8f;
+ VisiblePartType aimAtPart;
+
+ if (IsUsingMachinegun())
+ {
+ // spray the big machinegun at the enemy's gut
+ aimAtPart = GUT;
+ }
+ else if (IsUsing( WEAPON_AWP ) || IsUsingShotgun())
+ {
+ // these weapons are best aimed at the chest
+ aimAtPart = GUT;
+ }
+ else if (GetProfile()->GetSkill() > 0.5f && IsActiveWeaponRecoilHigh() )
+ {
+ // sprayin' and prayin' - aim at the gut since we're not going to be accurate
+ aimAtPart = GUT;
+ }
+ else if (GetProfile()->GetSkill() < sharpshooter)
+ {
+ // low skill bots don't go for headshots
+ aimAtPart = GUT;
+ }
+ else
+ {
+ // high skill - aim for the head
+ aimAtPart = HEAD;
+ }
+
+ if (IsEnemyPartVisible( aimAtPart ))
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), aimAtPart );
+ }
+ else
+ {
+ // desired part is blocked - aim at whatever part is visible
+ if (IsEnemyPartVisible( GUT ))
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), GUT );
+ }
+ else if (IsEnemyPartVisible( HEAD ))
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), HEAD );
+ }
+ else if (IsEnemyPartVisible( LEFT_SIDE ))
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), LEFT_SIDE );
+ }
+ else if (IsEnemyPartVisible( RIGHT_SIDE ))
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), RIGHT_SIDE );
+ }
+ else // FEET
+ {
+ m_aimSpot = GetPartPosition( GetBotEnemy(), FEET );
+ }
+ }
+
+ // high skill bots lead the target a little to compensate for update tick latency
+ /*
+ if (false && GetProfile()->GetSkill() > 0.5f)
+ {
+ const float k = 1.0f;
+ m_aimSpot += k * g_flBotCommandInterval * (m_enemy->GetAbsVelocity() - GetAbsVelocity());
+ }
+ */
+
+ }
+ else
+ {
+ // aim where we last saw enemy - but bend the ray so we dont point directly into walls
+ // if we put this back, make sure you only bend the ray ONCE and keep the bent spot - dont continually recompute
+ //BendLineOfSight( m_eyePosition, m_lastEnemyPosition, &m_aimSpot );
+ m_aimSpot = m_lastEnemyPosition;
+ }
+
+ // add in aim error
+ m_aimSpot.x += m_aimOffset.x;
+ m_aimSpot.y += m_aimOffset.y;
+ m_aimSpot.z += m_aimOffset.z;
+
+ Vector to = m_aimSpot - EyePositionConst();
+
+ QAngle idealAngle;
+ VectorAngles( to, idealAngle );
+
+ // adjust aim angle for recoil, based on bot skill
+ const QAngle &punchAngles = GetPunchAngle();
+ idealAngle -= punchAngles * GetProfile()->GetSkill();
+
+ SetLookAngles( idealAngle.y, idealAngle.x );
+ }
+ }
+ else
+ {
+ if (m_lookAtSpotClearIfClose)
+ {
+ // dont look at spots just in front of our face - it causes erratic view rotation
+ const float tooCloseRange = 100.0f;
+ if ((m_lookAtSpot - myOrigin).IsLengthLessThan( tooCloseRange ))
+ m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
+ }
+
+ switch( m_lookAtSpotState )
+ {
+ case NOT_LOOKING_AT_SPOT:
+ {
+ // look ahead
+ SetLookAngles( m_lookAheadAngle, 0.0f );
+ break;
+ }
+
+ case LOOK_TOWARDS_SPOT:
+ {
+ UpdateLookAt();
+ if (IsLookingAtPosition( m_lookAtSpot, m_lookAtSpotAngleTolerance ))
+ {
+ m_lookAtSpotState = LOOK_AT_SPOT;
+ m_lookAtSpotTimestamp = gpGlobals->curtime;
+ }
+ break;
+ }
+
+ case LOOK_AT_SPOT:
+ {
+ UpdateLookAt();
+
+ if (m_lookAtSpotDuration >= 0.0f && gpGlobals->curtime - m_lookAtSpotTimestamp > m_lookAtSpotDuration)
+ {
+ m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
+ m_lookAtSpotDuration = 0.0f;
+ }
+ break;
+ }
+ }
+
+ // have view "drift" very slowly, so view looks "alive"
+ if (!IsUsingSniperRifle())
+ {
+ float driftAmplitude = 2.0f;
+ if (IsBlind())
+ {
+ driftAmplitude = 5.0f;
+ }
+
+ m_lookYaw += driftAmplitude * BotCOS( 33.0f * gpGlobals->curtime );
+ m_lookPitch += driftAmplitude * BotSIN( 13.0f * gpGlobals->curtime );
+ }
+ }
+
+ // view angles can change quickly
+ UpdateLookAngles();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Heavyweight processing, invoked less often
+ */
+void CCSBot::Update( void )
+{
+ VPROF_BUDGET( "CCSBot::Update", VPROF_BUDGETGROUP_NPCS );
+
+ // If bot_flipout is on, then we only do stuff in Upkeep().
+ if ( cv_bot_flipout.GetBool() )
+ return;
+
+ Vector myOrigin = GetCentroid( this );
+
+ // if we are spectating, get into the game
+ if (GetTeamNumber() == 0)
+ {
+ HandleCommand_JoinTeam( m_desiredTeam );
+ int desiredClass = GetProfile()->GetSkin();
+ if ( m_desiredTeam == TEAM_CT && desiredClass )
+ {
+ desiredClass = FIRST_CT_CLASS + desiredClass - 1;
+ }
+ else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass )
+ {
+ desiredClass = FIRST_T_CLASS + desiredClass - 1;
+ }
+ HandleCommand_JoinClass( desiredClass );
+ return;
+ }
+
+
+ // update our radio chatter
+ // need to allow bots to finish their chatter even if they are dead
+ GetChatter()->Update();
+
+ // check if we are dead
+ if (!IsAlive())
+ {
+ // remember that we died
+ m_diedLastRound = true;
+
+ BotDeathThink();
+ return;
+ }
+
+ // the bot is alive and in the game at this point
+ m_hasJoined = true;
+
+ //
+ // Debug beam rendering
+ //
+
+ if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ {
+ DebugDisplay();
+ }
+
+ if (cv_bot_stop.GetBool())
+ return;
+
+ // check if we are stuck
+ StuckCheck();
+
+ // Check for physics props and other breakables in our way that we can break
+ BreakablesCheck();
+
+ // Check for useable doors in our way that we need to open
+ DoorCheck();
+
+ // update travel distance to all players (this is an optimization)
+ UpdateTravelDistanceToAllPlayers();
+
+ // if our current 'noise' was heard a long time ago, forget it
+ const float rememberNoiseDuration = 20.0f;
+ if (m_noiseTimestamp > 0.0f && gpGlobals->curtime - m_noiseTimestamp > rememberNoiseDuration)
+ {
+ ForgetNoise();
+ }
+
+ // where are we
+ if (!m_currentArea || !m_currentArea->Contains( myOrigin ))
+ {
+ m_currentArea = (CCSNavArea *)TheNavMesh->GetNavArea( myOrigin );
+ }
+
+ // track the last known area we were in
+ if (m_currentArea && m_currentArea != m_lastKnownArea)
+ {
+ m_lastKnownArea = m_currentArea;
+
+ OnEnteredNavArea( m_currentArea );
+ }
+
+ // keep track of how long we have been motionless
+ const float stillSpeed = 10.0f;
+ if (GetAbsVelocity().IsLengthLessThan( stillSpeed ))
+ {
+ m_stillTimer.Start();
+ }
+ else
+ {
+ m_stillTimer.Invalidate();
+ }
+
+ // if we're blind, retreat!
+ if (IsBlind())
+ {
+ if (m_blindFire)
+ {
+ PrimaryAttack();
+ }
+ }
+
+ UpdatePanicLookAround();
+
+ //
+ // Enemy acquisition and attack initiation
+ //
+
+ // take a snapshot and update our reaction time queue
+ UpdateReactionQueue();
+
+ // "threat" may be the same as our current enemy
+ CCSPlayer *threat = GetRecognizedEnemy();
+ if (threat)
+ {
+ Vector threatOrigin = GetCentroid( threat );
+
+ // adjust our personal "safe" time
+ AdjustSafeTime();
+
+ BecomeAlert();
+
+ const float selfDefenseRange = 500.0f; // 750.0f;
+ const float farAwayRange = 2000.0f;
+
+ //
+ // Decide if we should attack
+ //
+ bool doAttack = false;
+ switch( GetDisposition() )
+ {
+ case IGNORE_ENEMIES:
+ {
+ // never attack
+ doAttack = false;
+ break;
+ }
+
+ case SELF_DEFENSE:
+ {
+ // attack if fired on
+ doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat ));
+
+ // attack if enemy very close
+ if (!doAttack)
+ {
+ doAttack = (myOrigin - threatOrigin).IsLengthLessThan( selfDefenseRange );
+ }
+
+ break;
+ }
+
+ case ENGAGE_AND_INVESTIGATE:
+ case OPPORTUNITY_FIRE:
+ {
+ if ((myOrigin - threatOrigin).IsLengthGreaterThan( farAwayRange ))
+ {
+ // enemy is very far away - wait to take our shot until he is closer
+ // unless we are a sniper or he is shooting at us
+ if (IsSniper())
+ {
+ // snipers love far away targets
+ doAttack = true;
+ }
+ else
+ {
+ // attack if fired on
+ doAttack = (IsPlayerLookingAtMe( threat, 0.99f ) && DidPlayerJustFireWeapon( threat ));
+ }
+ }
+ else
+ {
+ // normal combat range
+ doAttack = true;
+ }
+
+ break;
+ }
+ }
+
+ // if we aren't attacking but we are being attacked, retaliate
+ if (!doAttack && !IsAttacking() && GetDisposition() != IGNORE_ENEMIES)
+ {
+ const float recentAttackDuration = 1.0f;
+ if (GetTimeSinceAttacked() < recentAttackDuration)
+ {
+ // we may not be attacking our attacker, but at least we're not just taking it
+ // (since m_attacker isn't reaction-time delayed, we can't directly use it)
+ doAttack = true;
+ PrintIfWatched( "Ouch! Retaliating!\n" );
+ }
+ }
+
+ if (doAttack)
+ {
+ if (!IsAttacking() || threat != GetBotEnemy())
+ {
+ if (IsUsingKnife() && IsHiding())
+ {
+ // if hiding with a knife, wait until threat is close
+ const float knifeAttackRange = 250.0f;
+ if ((GetAbsOrigin() - threat->GetAbsOrigin()).IsLengthLessThan( knifeAttackRange ))
+ {
+ Attack( threat );
+ }
+ }
+ else
+ {
+ Attack( threat );
+ }
+ }
+ }
+ else
+ {
+ // dont attack, but keep track of nearby enemies
+ SetBotEnemy( threat );
+ m_isEnemyVisible = true;
+ }
+
+ TheCSBots()->SetLastSeenEnemyTimestamp();
+ }
+
+ //
+ // Validate existing enemy, if any
+ //
+ if (m_enemy != NULL)
+ {
+ if (IsAwareOfEnemyDeath())
+ {
+ // we have noticed that our enemy has died
+ m_enemy = NULL;
+ m_isEnemyVisible = false;
+ }
+ else
+ {
+ // check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players)
+ // note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is
+ if (IsVisible( m_enemy, false, &m_visibleEnemyParts ))
+ {
+ m_isEnemyVisible = true;
+ m_lastSawEnemyTimestamp = gpGlobals->curtime;
+ m_lastEnemyPosition = GetCentroid( m_enemy );
+ }
+ else
+ {
+ m_isEnemyVisible = false;
+ }
+
+ // check if enemy died
+ if (m_enemy->IsAlive())
+ {
+ m_enemyDeathTimestamp = 0.0f;
+ m_isLastEnemyDead = false;
+ }
+ else if (m_enemyDeathTimestamp == 0.0f)
+ {
+ // note time of death (to allow bots to overshoot for a time)
+ m_enemyDeathTimestamp = gpGlobals->curtime;
+ m_isLastEnemyDead = true;
+ }
+ }
+ }
+ else
+ {
+ m_isEnemyVisible = false;
+ }
+
+
+ // if we have seen an enemy recently, keep an eye on him if we can
+ if (!IsBlind() && !IsLookingAtSpot(PRIORITY_UNINTERRUPTABLE) )
+ {
+ const float seenRecentTime = 3.0f;
+ if (m_enemy != NULL && GetTimeSinceLastSawEnemy() < seenRecentTime)
+ {
+ AimAtEnemy();
+ }
+ else
+ {
+ StopAiming();
+ }
+ }
+ else if( IsAimingAtEnemy() )
+ {
+ StopAiming();
+ }
+
+ //
+ // Hack to fire while retreating
+ /// @todo Encapsulate aiming and firing on enemies separately from current task
+ //
+ if (GetDisposition() == IGNORE_ENEMIES)
+ {
+ FireWeaponAtEnemy();
+ }
+
+ // toss grenades
+ LookForGrenadeTargets();
+
+ // process grenade throw state machine
+ UpdateGrenadeThrow();
+
+ // avoid enemy grenades
+ AvoidEnemyGrenades();
+
+
+ // check if our weapon is totally out of ammo
+ // or if we no longer feel "safe", equip our weapon
+ if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo())
+ {
+ EquipBestWeapon();
+ }
+
+ /// @todo This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range
+ if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb())
+ {
+ EquipBestWeapon();
+ }
+
+ // if we haven't seen an enemy in awhile, and we switched to our pistol during combat,
+ // switch back to our primary weapon (if it still has ammo left)
+ const float safeRearmTime = 5.0f;
+ if (!IsReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime)
+ {
+ EquipBestWeapon();
+ }
+
+ // reload our weapon if we must
+ ReloadCheck();
+
+ // equip silencer
+ SilencerCheck();
+
+ // listen to the radio
+ RespondToRadioCommands();
+
+ // make way
+ const float avoidTime = 0.33f;
+ if (gpGlobals->curtime - m_avoidTimestamp < avoidTime && m_avoid != NULL)
+ {
+ StrafeAwayFromPosition( GetCentroid( m_avoid ) );
+ }
+ else
+ {
+ m_avoid = NULL;
+ }
+
+ // if we're using a sniper rifle and are no longer attacking, stop looking thru scope
+ if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope())
+ {
+ SecondaryAttack();
+ }
+
+ if (!IsBlind())
+ {
+ // check encounter spots
+ UpdatePeripheralVision();
+
+ // watch for snipers
+ if (CanSeeSniper() && !HasSeenSniperRecently())
+ {
+ GetChatter()->SpottedSniper();
+
+ const float sniperRecentInterval = 20.0f;
+ m_sawEnemySniperTimer.Start( sniperRecentInterval );
+ }
+
+ //
+ // Update gamestate
+ //
+ if (m_bomber != NULL)
+ GetChatter()->SpottedBomber( GetBomber() );
+
+ if (CanSeeLooseBomb())
+ GetChatter()->SpottedLooseBomb( TheCSBots()->GetLooseBomb() );
+ }
+
+ //
+ // Scenario interrupts
+ //
+ switch (TheCSBots()->GetScenario())
+ {
+ case CCSBotManager::SCENARIO_DEFUSE_BOMB:
+ {
+ // flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is
+ // (aggressive players wait until its almost too late)
+ float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression());
+
+ // if we have a defuse kit, can wait longer
+ if (m_bHasDefuser)
+ gonnaBlowTime *= 0.66f;
+
+ if (!IsEscapingFromBomb() && // we aren't already escaping the bomb
+ TheCSBots()->IsBombPlanted() && // is the bomb planted
+ GetGameState()->IsPlantedBombLocationKnown() && // we know where the bomb is
+ TheCSBots()->GetBombTimeLeft() < gonnaBlowTime && // is the bomb about to explode
+ !IsDefusingBomb() && // we aren't defusing the bomb
+ !IsAttacking()) // we aren't in the midst of a firefight
+ {
+ EscapeFromBomb();
+ break;
+ }
+
+ break;
+ }
+
+ case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
+ {
+ if (GetTeamNumber() == TEAM_CT)
+ {
+ UpdateHostageEscortCount();
+ }
+ else
+ {
+ // Terrorists have imperfect information on status of hostages
+ unsigned char status = GetGameState()->ValidateHostagePositions();
+
+ if (status & CSGameState::HOSTAGES_ALL_GONE)
+ {
+ GetChatter()->HostagesTaken();
+ Idle();
+ }
+ else if (status & CSGameState::HOSTAGE_GONE)
+ {
+ GetGameState()->HostageWasTaken();
+ Idle();
+ }
+ }
+ break;
+ }
+ }
+
+
+ //
+ // Follow nearby humans if our co-op is high and we have nothing else to do
+ // If we were just following someone, don't auto-follow again for a short while to
+ // give us a chance to do something else.
+ //
+ const float earliestAutoFollowTime = 5.0f;
+ const float minAutoFollowTeamwork = 0.4f;
+ if (cv_bot_auto_follow.GetBool() &&
+ TheCSBots()->GetElapsedRoundTime() > earliestAutoFollowTime &&
+ GetProfile()->GetTeamwork() > minAutoFollowTeamwork &&
+ CanAutoFollow() &&
+ !IsBusy() &&
+ !IsFollowing() &&
+ !IsBlind() &&
+ !GetGameState()->IsAtPlantedBombsite())
+ {
+
+ // chance of following is proportional to teamwork attribute
+ if (GetProfile()->GetTeamwork() > RandomFloat( 0.0f, 1.0f ))
+ {
+ CCSPlayer *leader = GetClosestVisibleHumanFriend();
+ if (leader && leader->IsAutoFollowAllowed())
+ {
+ // count how many bots are already following this player
+ const float maxFollowCount = 2;
+ if (GetBotFollowCount( leader ) < maxFollowCount)
+ {
+ const float autoFollowRange = 300.0f;
+ Vector leaderOrigin = GetCentroid( leader );
+ if ((leaderOrigin - myOrigin).IsLengthLessThan( autoFollowRange ))
+ {
+ CNavArea *leaderArea = TheNavMesh->GetNavArea( leaderOrigin );
+ if (leaderArea)
+ {
+ PathCost cost( this, FASTEST_ROUTE );
+ float travelRange = NavAreaTravelDistance( GetLastKnownArea(), leaderArea, cost );
+ if (travelRange >= 0.0f && travelRange < autoFollowRange)
+ {
+ // follow this human
+ Follow( leader );
+ PrintIfWatched( "Auto-Following %s\n", leader->GetPlayerName() );
+
+ if (CSGameRules()->IsCareer())
+ {
+ GetChatter()->Say( "FollowingCommander", 10.0f );
+ }
+ else
+ {
+ GetChatter()->Say( "FollowingSir", 10.0f );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // we decided not to follow, don't re-check for a duration
+ m_allowAutoFollowTime = gpGlobals->curtime + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f;
+ }
+ }
+
+ if (IsFollowing())
+ {
+ // if we are following someone, make sure they are still alive
+ CBaseEntity *leader = m_leader;
+ if (leader == NULL || !leader->IsAlive())
+ {
+ StopFollowing();
+ }
+
+ // decide whether to continue following them
+ const float highTeamwork = 0.85f;
+ if (GetProfile()->GetTeamwork() < highTeamwork)
+ {
+ float minFollowDuration = 15.0f;
+ if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork())
+ {
+ // we are bored of following our leader
+ StopFollowing();
+ PrintIfWatched( "Stopping following - bored\n" );
+ }
+ }
+ }
+
+
+ //
+ // Execute state machine
+ //
+ if (m_isOpeningDoor)
+ {
+ // opening doors takes precedence over attacking because DoorCheck() will stop opening doors if
+ // we don't have a knife out.
+ m_openDoorState.OnUpdate( this );
+
+ if (m_openDoorState.IsDone())
+ {
+ // open door behavior is finished, return to previous behavior context
+ m_openDoorState.OnExit( this );
+ m_isOpeningDoor = false;
+ }
+ }
+ else if (m_isAttacking)
+ {
+ m_attackState.OnUpdate( this );
+ }
+ else
+ {
+ m_state->OnUpdate( this );
+ }
+
+ // do wait behavior
+ if (!IsAttacking() && IsWaiting())
+ {
+ ResetStuckMonitor();
+ ClearMovement();
+ }
+
+ // don't move while reloading unless we see an enemy
+ if (IsReloading() && !m_isEnemyVisible)
+ {
+ ResetStuckMonitor();
+ ClearMovement();
+ }
+
+ // if we get too far ahead of the hostages we are escorting, wait for them
+ if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed())
+ {
+ const float waitForHostageRange = 500.0f;
+ if ((GetTask() == COLLECT_HOSTAGES || GetTask() == RESCUE_HOSTAGES) && GetRangeToFarthestEscortedHostage() > waitForHostageRange)
+ {
+ if (!m_isWaitingForHostage)
+ {
+ // just started waiting
+ m_isWaitingForHostage = true;
+ m_waitForHostageTimer.Start( 10.0f );
+ }
+ else
+ {
+ // we've been waiting
+ if (m_waitForHostageTimer.IsElapsed())
+ {
+ // give up waiting for awhile
+ m_isWaitingForHostage = false;
+ m_inhibitWaitingForHostageTimer.Start( 3.0f );
+ }
+ else
+ {
+ // keep waiting
+ ResetStuckMonitor();
+ ClearMovement();
+ }
+ }
+ }
+ }
+
+ // remember our prior safe time status
+ m_wasSafe = IsSafe();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class DrawTravelTime
+{
+public:
+ DrawTravelTime( const CCSBot *me )
+ {
+ m_me = me;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if (player->IsAlive() && !m_me->InSameTeam( player ))
+ {
+ CFmtStr msg;
+ player->EntityText( 0,
+ msg.sprintf( "%3.0f", m_me->GetTravelDistanceToPlayer( (CCSPlayer *)player ) ),
+ 0.1f );
+
+
+ if (m_me->DidPlayerJustFireWeapon( ToCSPlayer( player ) ))
+ {
+ player->EntityText( 1, "BANG!", 0.1f );
+ }
+ }
+
+ return true;
+ }
+
+ const CCSBot *m_me;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Render bot debug info
+ */
+void CCSBot::DebugDisplay( void ) const
+{
+ const float duration = 0.15f;
+ CFmtStr msg;
+
+ NDebugOverlay::ScreenText( 0.5f, 0.34f, msg.sprintf( "Skill: %d%%", (int)(100.0f * GetProfile()->GetSkill()) ), 255, 255, 255, 150, duration );
+
+ if ( m_pathLadder )
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "Ladder: %d", m_pathLadder->GetID() ), 255, 255, 255, 150, duration );
+ }
+
+ // show safe time
+ float safeTime = GetSafeTimeRemaining();
+ if (safeTime > 0.0f)
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "SafeTime: %3.2f", safeTime ), 255, 255, 255, 150, duration );
+ }
+
+ // show if blind
+ if (IsBlind())
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "<<< BLIND >>>" ), 255, 255, 255, 255, duration );
+ }
+
+ // show if alert
+ if (IsAlert())
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.38f, msg.sprintf( "ALERT" ), 255, 0, 0, 255, duration );
+ }
+
+ // show if panicked
+ if (IsPanicking())
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.36f, msg.sprintf( "PANIC" ), 255, 255, 0, 255, duration );
+ }
+
+ // show behavior variables
+ if (m_isAttacking)
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "ATTACKING: %s", GetBotEnemy()->GetPlayerName() ), 255, 0, 0, 255, duration );
+ }
+ else
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.4f, msg.sprintf( "State: %s", m_state->GetName() ), 255, 255, 0, 255, duration );
+ NDebugOverlay::ScreenText( 0.5f, 0.42f, msg.sprintf( "Task: %s", GetTaskName() ), 0, 255, 0, 255, duration );
+ NDebugOverlay::ScreenText( 0.5f, 0.44f, msg.sprintf( "Disposition: %s", GetDispositionName() ), 100, 100, 255, 255, duration );
+ NDebugOverlay::ScreenText( 0.5f, 0.46f, msg.sprintf( "Morale: %s", GetMoraleName() ), 0, 200, 200, 255, duration );
+ }
+
+ // show look at status
+ if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT)
+ {
+ const char *string = msg.sprintf( "LookAt: %s (%s)", m_lookAtDesc, (m_lookAtSpotState == LOOK_TOWARDS_SPOT) ? "LOOK_TOWARDS_SPOT" : "LOOK_AT_SPOT" );
+
+ NDebugOverlay::ScreenText( 0.5f, 0.60f, string, 255, 255, 0, 150, duration );
+ }
+
+ NDebugOverlay::ScreenText( 0.5f, 0.62f, msg.sprintf( "Steady view = %s", HasViewBeenSteady( 0.2f ) ? "YES" : "NO" ), 255, 255, 0, 150, duration );
+
+
+ // show friend/foes I know of
+ NDebugOverlay::ScreenText( 0.5f, 0.64f, msg.sprintf( "Nearby friends = %d", m_nearbyFriendCount ), 100, 255, 100, 150, duration );
+ NDebugOverlay::ScreenText( 0.5f, 0.66f, msg.sprintf( "Nearby enemies = %d", m_nearbyEnemyCount ), 255, 100, 100, 150, duration );
+
+ if ( m_lastNavArea )
+ {
+ NDebugOverlay::ScreenText( 0.5f, 0.68f, msg.sprintf( "Nav Area: %d (%s)", m_lastNavArea->GetID(), m_szLastPlaceName.Get() ), 255, 255, 255, 150, duration );
+ /*
+ if ( cv_bot_traceview.GetBool() )
+ {
+ if ( m_currentArea )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), m_currentArea->GetCenter(), 0, 255, 0, true, 0.1f );
+ }
+ else if ( m_lastKnownArea )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), m_lastKnownArea->GetCenter(), 255, 0, 0, true, 0.1f );
+ }
+ else if ( m_lastNavArea )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), m_lastNavArea->GetCenter(), 0, 0, 255, true, 0.1f );
+ }
+ }
+ */
+ }
+
+ // show debug message history
+ float y = 0.8f;
+ const float lineHeight = 0.02f;
+ const float fadeAge = 7.0f;
+ const float maxAge = 10.0f;
+ for( int i=0; i<TheBots->GetDebugMessageCount(); ++i )
+ {
+ const CBotManager::DebugMessage *msg = TheBots->GetDebugMessage( i );
+
+ if (msg->m_age.GetElapsedTime() < maxAge)
+ {
+ int alpha = 255;
+
+ if (msg->m_age.GetElapsedTime() > fadeAge)
+ {
+ alpha *= (1.0f - (msg->m_age.GetElapsedTime() - fadeAge) / (maxAge - fadeAge));
+ }
+
+ NDebugOverlay::ScreenText( 0.5f, y, msg->m_string, 255, 255, 255, alpha, duration );
+ y += lineHeight;
+ }
+ }
+
+ // show noises
+ const Vector *noisePos = GetNoisePosition();
+ if (noisePos)
+ {
+ const float size = 25.0f;
+ NDebugOverlay::VertArrow( *noisePos + Vector( 0, 0, size ), *noisePos, size/4.0f, 255, 255, 0, 0, true, duration );
+ }
+
+ // show aim spot
+ if (IsAimingAtEnemy())
+ {
+ NDebugOverlay::Cross3D( m_aimSpot, 5.0f, 255, 0, 0, true, duration );
+ }
+
+
+
+ if (IsHiding())
+ {
+ // show approach points
+ DrawApproachPoints();
+ }
+ else
+ {
+ // show encounter spot data
+ if (false && m_spotEncounter)
+ {
+ NDebugOverlay::Line( m_spotEncounter->path.from, m_spotEncounter->path.to, 0, 150, 150, true, 0.1f );
+
+ Vector dir = m_spotEncounter->path.to - m_spotEncounter->path.from;
+ float length = dir.NormalizeInPlace();
+
+ const SpotOrder *order;
+ Vector along;
+
+ FOR_EACH_VEC( m_spotEncounter->spots, it )
+ {
+ order = &m_spotEncounter->spots[ it ];
+
+ // ignore spots the enemy could not have possibly reached yet
+ if (order->spot->GetArea())
+ {
+ if (TheCSBots()->GetElapsedRoundTime() < order->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
+ {
+ continue;
+ }
+ }
+
+ along = m_spotEncounter->path.from + order->t * length * dir;
+
+ NDebugOverlay::Line( along, order->spot->GetPosition(), 0, 255, 255, true, 0.1f );
+ }
+ }
+ }
+
+ // show aim targets
+ if (false)
+ {
+ CStudioHdr *pStudioHdr = const_cast< CCSBot *>( this )->GetModelPtr();
+ if ( !pStudioHdr )
+ return;
+
+ mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( const_cast< CCSBot *>( this )->GetHitboxSet() );
+ if ( !set )
+ return;
+
+ Vector position, forward, right, up;
+ QAngle angles;
+ char buffer[16];
+
+ for ( int i = 0; i < set->numhitboxes; i++ )
+ {
+ mstudiobbox_t *pbox = set->pHitbox( i );
+
+ const_cast< CCSBot *>( this )->GetBonePosition( pbox->bone, position, angles );
+
+ AngleVectors( angles, &forward, &right, &up );
+
+ NDebugOverlay::BoxAngles( position, pbox->bbmin, pbox->bbmax, angles, 255, 0, 0, 0, 0.1f );
+
+ const float size = 5.0f;
+ NDebugOverlay::Line( position, position + size * forward, 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Line( position, position + size * right, 255, 0, 0, true, 0.1f );
+ NDebugOverlay::Line( position, position + size * up, 0, 255, 0, true, 0.1f );
+
+ Q_snprintf( buffer, 16, "%d", i );
+ if (i == 12)
+ {
+ // in local bone space
+ const float headForwardOffset = 4.0f;
+ const float headRightOffset = 2.0f;
+ position += headForwardOffset * forward + headRightOffset * right;
+ }
+ NDebugOverlay::Text( position, buffer, true, 0.1f );
+ }
+ }
+
+
+ /*
+ const QAngle &angles = const_cast< CCSBot *>( this )->GetPunchAngle();
+ NDebugOverlay::ScreenText( 0.3f, 0.66f, msg.sprintf( "Punch angle pitch = %3.2f", angles.x ), 255, 255, 0, 150, duration );
+ */
+
+ DrawTravelTime drawTravelTime( this );
+ ForEachPlayer( drawTravelTime );
+
+/*
+ // show line of fire
+ if ((cv_bot_traceview.GetInt() == 100 && IsLocalPlayerWatchingMe()) || cv_bot_traceview.GetInt() == 101)
+ {
+ if (!IsFriendInLineOfFire())
+ {
+ Vector vecAiming = GetViewVector();
+ Vector vecSrc = EyePositionConst();
+
+ if (GetTeamNumber() == TEAM_TERRORIST)
+ UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 0, 0 );
+ else
+ UTIL_DrawBeamPoints( vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255 );
+ }
+ }
+
+ // show path navigation data
+ if (cv_bot_traceview.GetInt() == 1 && IsLocalPlayerWatchingMe())
+ {
+ Vector from = EyePositionConst();
+
+ const float size = 50.0f;
+ //Vector arrow( size * cos( m_forwardAngle * M_PI/180.0f ), size * sin( m_forwardAngle * M_PI/180.0f ), 0.0f );
+ Vector arrow( size * (float)cos( m_lookAheadAngle * M_PI/180.0f ),
+ size * (float)sin( m_lookAheadAngle * M_PI/180.0f ),
+ 0.0f );
+
+ UTIL_DrawBeamPoints( from, from + arrow, 1, 0, 255, 255 );
+ }
+
+ if (cv_bot_show_nav.GetBool() && m_lastKnownArea)
+ {
+ m_lastKnownArea->DrawConnectedAreas();
+ }
+ */
+
+
+ if (IsAttacking())
+ {
+ const float crossSize = 2.0f;
+ CCSPlayer *enemy = GetBotEnemy();
+ if (enemy)
+ {
+ NDebugOverlay::Cross3D( GetPartPosition( enemy, GUT ), crossSize, 0, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetPartPosition( enemy, HEAD ), crossSize, 0, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetPartPosition( enemy, FEET ), crossSize, 0, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetPartPosition( enemy, LEFT_SIDE ), crossSize, 0, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetPartPosition( enemy, RIGHT_SIDE ), crossSize, 0, 255, 0, true, 0.1f );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Periodically compute shortest path distance to each player.
+ * NOTE: Travel distance is NOT symmetric between players A and B. Each much be computed separately.
+ */
+void CCSBot::UpdateTravelDistanceToAllPlayers( void )
+{
+ const unsigned char numPhases = 3;
+
+ if (m_updateTravelDistanceTimer.IsElapsed())
+ {
+ ShortestPathCost pathCost;
+
+ for( int i=1; i<=gpGlobals->maxClients; ++i )
+ {
+ CCSPlayer *player = static_cast< CCSPlayer * >( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (FNullEnt( player->edict() ))
+ continue;
+
+ if (!player->IsPlayer())
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ // skip friends for efficiency
+ if (player->InSameTeam( this ))
+ continue;
+
+ int which = player->entindex() % MAX_PLAYERS;
+
+ // if player is very far away, update every third time (on phase 0)
+ const float veryFarAway = 4000.0f;
+ if (m_playerTravelDistance[ which ] < 0.0f || m_playerTravelDistance[ which ] > veryFarAway)
+ {
+ if (m_travelDistancePhase != 0)
+ continue;
+ }
+ else
+ {
+ // if player is far away, update two out of three times (on phases 1 and 2)
+ const float farAway = 2000.0f;
+ if (m_playerTravelDistance[ which ] > farAway && m_travelDistancePhase == 0)
+ continue;
+ }
+
+ // if player is fairly close, update often
+ m_playerTravelDistance[ which ] = NavAreaTravelDistance( EyePosition(), player->EyePosition(), pathCost );
+ }
+
+ // throttle the computation frequency
+ const float checkInterval = 1.0f;
+ m_updateTravelDistanceTimer.Start( checkInterval );
+
+ // round-robin the phases
+ ++m_travelDistancePhase;
+ if (m_travelDistancePhase >= numPhases)
+ {
+ m_travelDistancePhase = 0;
+ }
+ }
+}
diff --git a/game/server/cstrike/bot/cs_bot_vision.cpp b/game/server/cstrike/bot/cs_bot_vision.cpp
new file mode 100644
index 0000000..a7e8500
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_vision.cpp
@@ -0,0 +1,1834 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "datacache/imdlcache.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Used to update view angles to stay on a ladder
+ */
+inline float StayOnLadderLine( CCSBot *me, const CNavLadder *ladder )
+{
+ // determine our facing
+ NavDirType faceDir = AngleToDirection( me->EyeAngles().y );
+
+ const float stiffness = 1.0f;
+
+ // move toward ladder mount point
+ switch( faceDir )
+ {
+ case NORTH:
+ return stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);
+
+ case SOUTH:
+ return -stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);
+
+ case WEST:
+ return -stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);
+
+ case EAST:
+ return stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);
+ }
+
+ return 0.0f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::ComputeLadderAngles( float *yaw, float *pitch )
+{
+ if ( !yaw || !pitch )
+ return;
+
+ Vector myOrigin = GetCentroid( this );
+
+ // set yaw to aim at ladder
+ Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
+ float idealYaw = UTIL_VecToYaw( to );
+
+ Vector faceDir = (m_pathLadderFaceIn) ? -m_pathLadder->GetNormal() : m_pathLadder->GetNormal();
+ QAngle faceAngles;
+ VectorAngles( faceDir, faceAngles );
+
+ const float lookAlongLadderRange = 50.0f;
+ const float ladderPitchUpApproach = -30.0f;
+ const float ladderPitchUpTraverse = -60.0f; // -80
+ const float ladderPitchDownApproach = 0.0f;
+ const float ladderPitchDownTraverse = 80.0f;
+
+ // adjust pitch to look up/down ladder as we ascend/descend
+ switch( m_pathLadderState )
+ {
+ case APPROACH_ASCENDING_LADDER:
+ {
+ Vector to = m_goalPosition - myOrigin;
+ *yaw = idealYaw;
+
+ if (to.IsLengthLessThan( lookAlongLadderRange ))
+ *pitch = ladderPitchUpApproach;
+ break;
+ }
+
+ case APPROACH_DESCENDING_LADDER:
+ {
+ Vector to = m_goalPosition - myOrigin;
+ *yaw = idealYaw;
+
+ if (to.IsLengthLessThan( lookAlongLadderRange ))
+ *pitch = ladderPitchDownApproach;
+ break;
+ }
+
+ case FACE_ASCENDING_LADDER:
+ if ( m_pathLadderDismountDir == LEFT )
+ {
+ *yaw = AngleNormalizePositive( idealYaw + 90.0f );
+ }
+ else if ( m_pathLadderDismountDir == RIGHT )
+ {
+ *yaw = AngleNormalizePositive( idealYaw - 90.0f );
+ }
+ else
+ {
+ *yaw = idealYaw;
+ }
+ *pitch = ladderPitchUpApproach;
+ break;
+
+ case FACE_DESCENDING_LADDER:
+ *yaw = idealYaw;
+ *pitch = ladderPitchDownApproach;
+ break;
+
+ case MOUNT_ASCENDING_LADDER:
+ case ASCEND_LADDER:
+ if ( m_pathLadderDismountDir == LEFT )
+ {
+ *yaw = AngleNormalizePositive( idealYaw + 90.0f );
+ }
+ else if ( m_pathLadderDismountDir == RIGHT )
+ {
+ *yaw = AngleNormalizePositive( idealYaw - 90.0f );
+ }
+ else
+ {
+ *yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
+ }
+ *pitch = ( m_pathLadderState == ASCEND_LADDER ) ? ladderPitchUpTraverse : ladderPitchUpApproach;
+ break;
+
+ case MOUNT_DESCENDING_LADDER:
+ case DESCEND_LADDER:
+ *yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
+ *pitch = ( m_pathLadderState == DESCEND_LADDER ) ? ladderPitchDownTraverse : ladderPitchDownApproach;
+ break;
+
+ case DISMOUNT_ASCENDING_LADDER:
+ if ( m_pathLadderDismountDir == LEFT )
+ {
+ *yaw = AngleNormalizePositive( faceAngles[ YAW ] + 90.0f );
+ }
+ else if ( m_pathLadderDismountDir == RIGHT )
+ {
+ *yaw = AngleNormalizePositive( faceAngles[ YAW ] - 90.0f );
+ }
+ else
+ {
+ *yaw = faceAngles[ YAW ];
+ }
+ break;
+
+ case DISMOUNT_DESCENDING_LADDER:
+ *yaw = faceAngles[ YAW ];
+ break;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move actual view angles towards desired ones.
+ * This is the only place v_angle is altered.
+ * @todo Make stiffness and turn rate constants timestep invariant.
+ */
+void CCSBot::UpdateLookAngles( void )
+{
+ VPROF_BUDGET( "CCSBot::UpdateLookAngles", VPROF_BUDGETGROUP_NPCS );
+
+ const float deltaT = g_BotUpkeepInterval;
+ float maxAccel;
+ float stiffness;
+ float damping;
+
+ // If mimicing the player, don't modify the view angles.
+ if ( bot_mimic.GetInt() )
+ return;
+
+ // springs are stiffer when attacking, so we can track and move between targets better
+ if (IsAttacking())
+ {
+ stiffness = 300.0f;
+ damping = 30.0f; // 20
+ maxAccel = 3000.0f; // 4000
+ }
+ else
+ {
+ stiffness = 200.0f;
+ damping = 25.0f;
+ maxAccel = 3000.0f;
+ }
+
+ // these may be overridden by ladder logic
+ float useYaw = m_lookYaw;
+ float usePitch = m_lookPitch;
+
+ //
+ // Ladders require precise movement, therefore we need to look at the
+ // ladder as we approach and ascend/descend it.
+ // If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
+ //
+ // If we're trying to break something, though, we actually need to look at it before we can
+ // look at the ladder
+ //
+ if ( IsUsingLadder() && !(IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack) )
+ {
+ ComputeLadderAngles( &useYaw, &usePitch );
+ }
+
+ // get current view angles
+ QAngle viewAngles = EyeAngles();
+
+ //
+ // Yaw
+ //
+ float angleDiff = AngleNormalize( useYaw - viewAngles.y );
+
+ /*
+ * m_forwardAngle is unreliable. Need to simulate mouse sliding & centering
+ if (!IsAttacking())
+ {
+ // do not allow rotation through our reverse facing angle - go the "long" way instead
+ float toCurrent = AngleNormalize( pev->v_angle.y - m_forwardAngle );
+ float toDesired = AngleNormalize( useYaw - m_forwardAngle );
+
+ // if angle differences are different signs, they cross the forward facing
+ if (toCurrent * toDesired < 0.0f)
+ {
+ // if the sum of the angles is greater than 180, turn the "long" way around
+ if (abs( toCurrent - toDesired ) >= 180.0f)
+ {
+ if (angleDiff > 0.0f)
+ angleDiff -= 360.0f;
+ else
+ angleDiff += 360.0f;
+ }
+ }
+ }
+ */
+
+ // if almost at target angle, snap to it
+ const float onTargetTolerance = 1.0f; // 3
+ if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
+ {
+ m_lookYawVel = 0.0f;
+ viewAngles.y = useYaw;
+ }
+ else
+ {
+ // simple angular spring/damper
+ float accel = stiffness * angleDiff - damping * m_lookYawVel;
+
+ // limit rate
+ if (accel > maxAccel)
+ accel = maxAccel;
+ else if (accel < -maxAccel)
+ accel = -maxAccel;
+
+ m_lookYawVel += deltaT * accel;
+ viewAngles.y += deltaT * m_lookYawVel;
+
+ // keep track of how long our view remains steady
+ const float steadyYaw = 1000.0f;
+ if (fabs( accel ) > steadyYaw)
+ {
+ m_viewSteadyTimer.Start();
+ }
+ }
+
+ //
+ // Pitch
+ // Actually, this is negative pitch.
+ //
+ angleDiff = usePitch - viewAngles.x;
+
+ angleDiff = AngleNormalize( angleDiff );
+
+
+ if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
+ {
+ m_lookPitchVel = 0.0f;
+ viewAngles.x = usePitch;
+ }
+ else
+ {
+ // simple angular spring/damper
+ // double the stiffness since pitch is only +/- 90 and yaw is +/- 180
+ float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;
+
+ // limit rate
+ if (accel > maxAccel)
+ accel = maxAccel;
+ else if (accel < -maxAccel)
+ accel = -maxAccel;
+
+ m_lookPitchVel += deltaT * accel;
+ viewAngles.x += deltaT * m_lookPitchVel;
+
+ // keep track of how long our view remains steady
+ const float steadyPitch = 1000.0f;
+ if (fabs( accel ) > steadyPitch)
+ {
+ m_viewSteadyTimer.Start();
+ }
+ }
+
+ //PrintIfWatched( "yawVel = %g, pitchVel = %g\n", m_lookYawVel, m_lookPitchVel );
+
+ // limit range - avoid gimbal lock
+ if (viewAngles.x < -89.0f)
+ viewAngles.x = -89.0f;
+ else if (viewAngles.x > 89.0f)
+ viewAngles.x = 89.0f;
+
+ // update view angles
+ SnapEyeAngles( viewAngles );
+
+ // if our weapon is zooming, our view is not steady
+ if (IsWaitingForZoom())
+ {
+ m_viewSteadyTimer.Start();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we can see the point
+ */
+bool CCSBot::IsVisible( const Vector &pos, bool testFOV, const CBaseEntity *ignore ) const
+{
+ VPROF_BUDGET( "CCSBot::IsVisible( pos )", VPROF_BUDGETGROUP_NPCS );
+
+ // we can't see anything if we're blind
+ if (IsBlind())
+ return false;
+
+ // is it in my general viewcone?
+ if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( pos )))
+ return false;
+
+ // check line of sight against smoke
+ if (TheCSBots()->IsLineBlockedBySmoke( EyePositionConst(), pos ))
+ return false;
+
+ // check line of sight
+ // Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
+ trace_t result;
+ CTraceFilterNoNPCsOrPlayer traceFilter( ignore, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( EyePositionConst(), pos, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
+ if (result.fraction != 1.0f)
+ return false;
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we can see any part of the player
+ * Check parts in order of importance. Return the first part seen in "visPart" if it is non-NULL.
+ */
+bool CCSBot::IsVisible( CCSPlayer *player, bool testFOV, unsigned char *visParts ) const
+{
+ VPROF_BUDGET( "CCSBot::IsVisible( player )", VPROF_BUDGETGROUP_NPCS );
+
+ // optimization - assume if center is not in FOV, nothing is
+ // we're using WorldSpaceCenter instead of GUT so we can skip GetPartPosition below - that's
+ // the most expensive part of this, and if we can skip it, so much the better.
+ if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( player->WorldSpaceCenter() )))
+ {
+ return false;
+ }
+
+ unsigned char testVisParts = NONE;
+
+ // check gut
+ Vector partPos = GetPartPosition( player, GUT );
+
+ // finish gut check
+ if (IsVisible( partPos, testFOV ))
+ {
+ if (visParts == NULL)
+ return true;
+
+ testVisParts |= GUT;
+ }
+
+
+ // check top of head
+ partPos = GetPartPosition( player, HEAD );
+ if (IsVisible( partPos, testFOV ))
+ {
+ if (visParts == NULL)
+ return true;
+
+ testVisParts |= HEAD;
+ }
+
+ // check feet
+ partPos = GetPartPosition( player, FEET );
+ if (IsVisible( partPos, testFOV ))
+ {
+ if (visParts == NULL)
+ return true;
+
+ testVisParts |= FEET;
+ }
+
+ // check "edges"
+ partPos = GetPartPosition( player, LEFT_SIDE );
+ if (IsVisible( partPos, testFOV ))
+ {
+ if (visParts == NULL)
+ return true;
+
+ testVisParts |= LEFT_SIDE;
+ }
+
+ partPos = GetPartPosition( player, RIGHT_SIDE );
+ if (IsVisible( partPos, testFOV ))
+ {
+ if (visParts == NULL)
+ return true;
+
+ testVisParts |= RIGHT_SIDE;
+ }
+
+ if (visParts)
+ *visParts = testVisParts;
+
+ if (testVisParts)
+ return true;
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Interesting part positions
+ */
+CCSBot::PartInfo CCSBot::m_partInfo[ MAX_PLAYERS ];
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute part positions from bone location.
+ */
+void CCSBot::ComputePartPositions( CCSPlayer *player )
+{
+ const int headBox = 12;
+ const int gutBox = 9;
+ const int leftElbowBox = 14;
+ const int rightElbowBox = 17;
+ //const int hipBox = 0;
+ //const int leftFootBox = 4;
+ //const int rightFootBox = 8;
+ const int maxBoxIndex = rightElbowBox;
+
+ VPROF_BUDGET( "CCSBot::ComputePartPositions", VPROF_BUDGETGROUP_NPCS );
+
+ // which PartInfo corresponds to the given player
+ PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];
+
+ // always compute feet, since it doesn't rely on bones
+ info->m_feetPos = player->GetAbsOrigin();
+ info->m_feetPos.z += 5.0f;
+
+ // get bone positions for interesting points on the player
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr *studioHdr = player->GetModelPtr();
+ if (studioHdr)
+ {
+ mstudiohitboxset_t *set = studioHdr->pHitboxSet( player->GetHitboxSet() );
+ if (set && maxBoxIndex < set->numhitboxes)
+ {
+ QAngle angles;
+ mstudiobbox_t *box;
+
+ // gut
+ box = set->pHitbox( gutBox );
+ player->GetBonePosition( box->bone, info->m_gutPos, angles );
+
+ // head
+ box = set->pHitbox( headBox );
+ player->GetBonePosition( box->bone, info->m_headPos, angles );
+
+ Vector forward, right;
+ AngleVectors( angles, &forward, &right, NULL );
+
+ // in local bone space
+ const float headForwardOffset = 4.0f;
+ const float headRightOffset = 2.0f;
+ info->m_headPos += headForwardOffset * forward + headRightOffset * right;
+
+ /// @todo Fix this hack - lower the head target because it's a bit too high for the current T model
+ info->m_headPos.z -= 2.0f;
+
+
+ // left side
+ box = set->pHitbox( leftElbowBox );
+ player->GetBonePosition( box->bone, info->m_leftSidePos, angles );
+
+ // right side
+ box = set->pHitbox( rightElbowBox );
+ player->GetBonePosition( box->bone, info->m_rightSidePos, angles );
+
+ return;
+ }
+ }
+
+
+ // default values if bones are not available
+ info->m_headPos = GetCentroid( player );
+ info->m_gutPos = info->m_headPos;
+ info->m_leftSidePos = info->m_headPos;
+ info->m_rightSidePos = info->m_headPos;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return world space position of given part on player.
+ * Uses hitboxes to get accurate positions.
+ * @todo Optimize by computing once for each player and storing.
+ */
+const Vector &CCSBot::GetPartPosition( CCSPlayer *player, VisiblePartType part ) const
+{
+ VPROF_BUDGET( "CCSBot::GetPartPosition", VPROF_BUDGETGROUP_NPCS );
+
+ // which PartInfo corresponds to the given player
+ PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];
+
+ if (gpGlobals->framecount > info->m_validFrame)
+ {
+ // update part positions
+ const_cast< CCSBot * >( this )->ComputePartPositions( player );
+ info->m_validFrame = gpGlobals->framecount;
+ }
+
+ // return requested part position
+ switch( part )
+ {
+ default:
+ {
+ AssertMsg( false, "GetPartPosition: Invalid part" );
+ // fall thru to GUT
+ }
+
+ case GUT:
+ return info->m_gutPos;
+
+ case HEAD:
+ return info->m_headPos;
+
+ case FEET:
+ return info->m_feetPos;
+
+ case LEFT_SIDE:
+ return info->m_leftSidePos;
+
+ case RIGHT_SIDE:
+ return info->m_rightSidePos;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update desired view angles to point towards m_lookAtSpot
+ */
+void CCSBot::UpdateLookAt( void )
+{
+ Vector to = m_lookAtSpot - EyePositionConst();
+
+ QAngle idealAngle;
+ VectorAngles( to, idealAngle );
+
+ //Vector idealAngle = UTIL_VecToAngles( to );
+ //idealAngle.x = 360.0f - idealAngle.x;
+
+ SetLookAngles( idealAngle.y, idealAngle.x );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Look at the given point in space for the given duration (-1 means forever)
+ */
+void CCSBot::SetLookAt( const char *desc, const Vector &pos, PriorityType pri, float duration, bool clearIfClose, float angleTolerance, bool attack )
+{
+ if (IsBlind())
+ return;
+
+ // if currently looking at a point in space with higher priority, ignore this request
+ if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
+ return;
+
+ // if already looking at this spot, just extend the time
+ const float tolerance = 10.0f;
+ if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual( pos, m_lookAtSpot, tolerance ))
+ {
+ m_lookAtSpotDuration = duration;
+
+ if (m_lookAtSpotPriority < pri)
+ m_lookAtSpotPriority = pri;
+ }
+ else
+ {
+ // look at new spot
+ m_lookAtSpot = pos;
+ m_lookAtSpotState = LOOK_TOWARDS_SPOT;
+ m_lookAtSpotDuration = duration;
+ m_lookAtSpotPriority = pri;
+ }
+
+ m_lookAtSpotAngleTolerance = angleTolerance;
+ m_lookAtSpotClearIfClose = clearIfClose;
+ m_lookAtDesc = desc;
+ m_lookAtSpotAttack = attack;
+
+ PrintIfWatched( "%3.1f SetLookAt( %s ), duration = %f\n", gpGlobals->curtime, desc, duration );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Block all "look at" and "look around" behavior for given duration - just look ahead
+ */
+void CCSBot::InhibitLookAround( float duration )
+{
+ m_inhibitLookAroundTimestamp = gpGlobals->curtime + duration;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update enounter spot timestamps, etc
+ */
+void CCSBot::UpdatePeripheralVision()
+{
+ VPROF_BUDGET( "CCSBot::UpdatePeripheralVision", VPROF_BUDGETGROUP_NPCS );
+
+ const float peripheralUpdateInterval = 0.29f; // if we update at 10Hz, this ensures we test once every three
+ if (gpGlobals->curtime - m_peripheralTimestamp < peripheralUpdateInterval)
+ return;
+
+ m_peripheralTimestamp = gpGlobals->curtime;
+
+ if (m_spotEncounter)
+ {
+ // check LOS to all spots in case we see them with our "peripheral vision"
+ const SpotOrder *spotOrder;
+ Vector pos;
+
+ FOR_EACH_VEC( m_spotEncounter->spots, it )
+ {
+ spotOrder = &m_spotEncounter->spots[ it ];
+
+ const Vector &spotPos = spotOrder->spot->GetPosition();
+
+ pos.x = spotPos.x;
+ pos.y = spotPos.y;
+ pos.z = spotPos.z + HalfHumanHeight;
+
+ if (!IsVisible( pos, CHECK_FOV ))
+ continue;
+
+ // can see hiding spot, remember when we saw it last
+ SetHidingSpotCheckTimestamp( spotOrder->spot );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update the "looking around" behavior.
+ */
+void CCSBot::UpdateLookAround( bool updateNow )
+{
+ VPROF_BUDGET( "CCSBot::UpdateLookAround", VPROF_BUDGETGROUP_NPCS );
+
+ //
+ // If we recently saw an enemy, look towards where we last saw them
+ // Unless we can hear them moving, in which case look towards the noise
+ //
+ const float closeRange = 500.0f;
+ if (!IsNoiseHeard() || GetNoiseRange() > closeRange)
+ {
+ const float recentThreatTime = 1.0f; // 0.25f;
+ if (!IsLookingAtSpot( PRIORITY_MEDIUM ) && gpGlobals->curtime - m_lastSawEnemyTimestamp < recentThreatTime)
+ {
+ ClearLookAt();
+
+ Vector spot = m_lastEnemyPosition;
+
+ // find enemy position on the ground
+ if (TheNavMesh->GetSimpleGroundHeight( m_lastEnemyPosition, &spot.z ))
+ {
+ spot.z += HalfHumanHeight;
+ SetLookAt( "Last Enemy Position", spot, PRIORITY_MEDIUM, RandomFloat( 2.0f, 3.0f ), true );
+ return;
+ }
+ }
+ }
+
+ //
+ // Look at nearby enemy noises
+ //
+ if (UpdateLookAtNoise())
+ return;
+
+
+ // check if looking around has been inhibited
+ // Moved inhibit to allow high priority enemy lookats to still occur
+ if (gpGlobals->curtime < m_inhibitLookAroundTimestamp)
+ return;
+
+ //
+ // If we are hiding (or otherwise standing still), watch all approach points leading into this region
+ //
+ const float minStillTime = 2.0f;
+ if (IsAtHidingSpot() || IsNotMoving( minStillTime ))
+ {
+ // update approach points
+ const float recomputeApproachPointTolerance = 50.0f;
+ if ((m_approachPointViewPosition - GetAbsOrigin()).IsLengthGreaterThan( recomputeApproachPointTolerance ))
+ {
+ ComputeApproachPoints();
+ m_approachPointViewPosition = GetAbsOrigin();
+ }
+
+ // if we're sniping, zoom in to watch our approach points
+ if (IsUsingSniperRifle())
+ {
+ // low skill bots don't pre-zoom
+ if (GetProfile()->GetSkill() > 0.4f)
+ {
+ if (!IsViewMoving())
+ {
+ float range = ComputeWeaponSightRange();
+ AdjustZoom( range );
+ }
+ else
+ {
+ // zoom out
+ if (GetZoomLevel() != NO_ZOOM)
+ SecondaryAttack();
+ }
+ }
+ }
+
+ if (m_lastKnownArea == NULL)
+ return;
+
+ if (gpGlobals->curtime < m_lookAroundStateTimestamp)
+ return;
+
+ // if we're sniping, switch look-at spots less often
+ if (IsUsingSniperRifle())
+ m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 5.0f, 10.0f );
+ else
+ m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 1.0f, 2.0f ); // 0.5, 1.0
+
+
+ #define MAX_APPROACHES 16
+ Vector validSpot[ MAX_APPROACHES ];
+ int validSpotCount = 0;
+
+ Vector *earlySpot = NULL;
+ float earliest = 999999.9f;
+
+ for( int i=0; i<m_approachPointCount; ++i )
+ {
+ float spotTime = m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) );
+
+ // ignore approach areas the enemy could not have possibly reached yet
+ if (TheCSBots()->GetElapsedRoundTime() >= spotTime)
+ {
+ validSpot[ validSpotCount++ ] = m_approachPoint[i].m_pos;
+ }
+ else
+ {
+ // keep track of earliest spot we can see in case we get there very early
+ if (spotTime < earliest)
+ {
+ earlySpot = &m_approachPoint[i].m_pos;
+ earliest = spotTime;
+ }
+ }
+ }
+
+ Vector spot;
+
+ if (validSpotCount)
+ {
+ int which = RandomInt( 0, validSpotCount-1 );
+ spot = validSpot[ which ];
+ }
+ else if (earlySpot)
+ {
+ // all of the spots we can see can't be reached yet by the enemy - look at the earliest spot
+ spot = *earlySpot;
+ }
+ else
+ {
+ return;
+ }
+
+ // don't look at the floor, look roughly at chest level
+ /// @todo If this approach point is very near, this will cause us to aim up in the air if were crouching
+ spot.z += HalfHumanHeight;
+
+ SetLookAt( "Approach Point (Hiding)", spot, PRIORITY_LOW );
+
+ return;
+ }
+
+ //
+ // Glance at "encouter spots" as we move past them
+ //
+ if (m_spotEncounter)
+ {
+ //
+ // Check encounter spots
+ //
+ if (!IsSafe() && !IsLookingAtSpot( PRIORITY_LOW ))
+ {
+ // allow a short time to look where we're going
+ if (gpGlobals->curtime < m_spotCheckTimestamp)
+ return;
+
+ /// @todo Use skill parameter instead of accuracy
+
+ // lower skills have exponentially longer delays
+ float asleep = (1.0f - GetProfile()->GetSkill());
+ asleep *= asleep;
+ asleep *= asleep;
+
+ m_spotCheckTimestamp = gpGlobals->curtime + asleep * RandomFloat( 10.0f, 30.0f );
+
+
+ // figure out how far along the path segment we are
+ Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
+ float length = delta.Length();
+ float adx = (float)fabs(delta.x);
+ float ady = (float)fabs(delta.y);
+ float t;
+ Vector myOrigin = GetCentroid( this );
+
+ if (adx > ady)
+ t = (myOrigin.x - m_spotEncounter->path.from.x) / delta.x;
+ else
+ t = (myOrigin.y - m_spotEncounter->path.from.y) / delta.y;
+
+ // advance parameter a bit so we "lead" our checks
+ const float leadCheckRange = 50.0f;
+ t += leadCheckRange / length;
+
+ if (t < 0.0f)
+ t = 0.0f;
+ else if (t > 1.0f)
+ t = 1.0f;
+
+ // collect the unchecked spots so far
+ #define MAX_DANGER_SPOTS 16
+ HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
+ int dangerSpotCount = 0;
+ int dangerIndex = 0;
+
+ const float checkTime = 10.0f;
+ const SpotOrder *spotOrder;
+ FOR_EACH_VEC( m_spotEncounter->spots, it )
+ {
+ spotOrder = &(m_spotEncounter->spots[ it ]);
+
+ // if we have seen this spot recently, we don't need to look at it
+ if (gpGlobals->curtime - GetHidingSpotCheckTimestamp( spotOrder->spot ) <= checkTime)
+ continue;
+
+ if (spotOrder->t > t)
+ break;
+
+ // ignore spots the enemy could not have possibly reached yet
+ if (spotOrder->spot->GetArea())
+ {
+ if (TheCSBots()->GetElapsedRoundTime() < spotOrder->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
+ {
+ continue;
+ }
+ }
+
+ dangerSpot[ dangerIndex++ ] = spotOrder->spot;
+ if (dangerIndex >= MAX_DANGER_SPOTS)
+ dangerIndex = 0;
+ if (dangerSpotCount < MAX_DANGER_SPOTS)
+ ++dangerSpotCount;
+ }
+
+ if (dangerSpotCount)
+ {
+ // pick one of the spots at random
+ int which = RandomInt( 0, dangerSpotCount-1 );
+
+ // glance at the spot for minimum time
+ SetLookAt( "Encounter Spot", dangerSpot[which]->GetPosition() + Vector( 0, 0, HalfHumanHeight ), PRIORITY_LOW, 0.2f, true, 10.0f );
+
+ // immediately mark it as "checked", so we don't check it again
+ // if we get distracted before we check it - that's the way it goes
+ SetHidingSpotCheckTimestamp( dangerSpot[which] );
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * "Bend" our line of sight around corners until we can "see" the point.
+ */
+bool CCSBot::BendLineOfSight( const Vector &eye, const Vector &target, Vector *bend, float angleLimit ) const
+{
+ VPROF_BUDGET( "CCSBot::BendLineOfSight", VPROF_BUDGETGROUP_NPCS );
+
+ bool doDebug = false;
+ const float debugDuration = 0.04f;
+ if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ NDebugOverlay::Line( eye, target, 255, 255, 255, true, debugDuration );
+
+ // if we can directly see the point, use it
+ trace_t result;
+ CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( eye, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
+ if (result.fraction == 1.0f && !result.startsolid)
+ {
+ // can directly see point, no bending needed
+ *bend = target;
+ return true;
+ }
+
+ // "bend" our line of sight until we can see the approach point
+ Vector to = target - eye;
+ float startAngle = UTIL_VecToYaw( to );
+ float length = to.Length2D();
+ to.NormalizeInPlace();
+
+ struct Color3
+ {
+ int r, g, b;
+ };
+ const int colorCount = 6;
+ Color3 colorSet[ colorCount ] =
+ {
+ { 255, 0, 0 },
+ { 0, 255, 0 },
+ { 0, 0, 255 },
+ { 255, 255, 0 },
+ { 0, 255, 255 },
+ { 255, 0, 255 },
+ };
+
+ int color = 0;
+
+ // optiming assumption - previous rays cast "shadow" on subsequent rays since they already
+ // enumerated visible space along their length.
+ // We should do a dot product and compute the exact length, but since the angular changes
+ // are incremental, using the direct length should be close enough.
+ float priorVisibleLength[2] = { 0.0f, 0.0f };
+
+ float angleInc = 5.0f;
+ for( float angle = angleInc; angle <= angleLimit; angle += angleInc )
+ {
+ // check both sides at this angle offset
+ for( int side=0; side<2; ++side )
+ {
+ float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);
+
+ float dx = cos( 3.141592f * actualAngle / 180.0f );
+ float dy = sin( 3.141592f * actualAngle / 180.0f );
+
+ // compute rotated point ray endpoint
+ Vector rotPoint( eye.x + length * dx, eye.y + length * dy, target.z );
+
+ // check LOS to find length to test along ray
+ UTIL_TraceLine( eye, rotPoint, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
+
+ // if this ray started in an obstacle, skip it
+ if (result.startsolid)
+ {
+ continue;
+ }
+
+ Vector ray = rotPoint - eye;
+ float rayLength = ray.NormalizeInPlace();
+ float visibleLength = rayLength * result.fraction;
+
+ if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ {
+ NDebugOverlay::Line( eye, eye + visibleLength * ray, colorSet[color].r, colorSet[color].g, colorSet[color].b, true, debugDuration );
+ }
+
+ // step along ray, checking if point is visible from ray point
+ const float bendStepSize = 50.0f;
+
+ // start from point that prior rays couldn't see
+ float startLength = priorVisibleLength[ side ];
+
+ for( float bendLength=startLength; bendLength <= visibleLength; bendLength += bendStepSize )
+ {
+ // compute point along ray
+ Vector bendPoint = eye + bendLength * ray;
+
+ // check if we can see approach point from this bend point
+ UTIL_TraceLine( bendPoint, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
+
+ if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ {
+ NDebugOverlay::Line( bendPoint, result.endpos, colorSet[color].r/2, colorSet[color].g/2, colorSet[color].b/2, true, debugDuration );
+ }
+
+ if (result.fraction == 1.0f && !result.startsolid)
+ {
+ // target is visible from this bend point on the ray - use this point on the ray as our point
+
+ // keep "bent" point at correct height along line of sight
+ bendPoint.z = eye.z + bendLength * to.z;
+
+ *bend = bendPoint;
+
+ return true;
+ }
+ }
+
+ priorVisibleLength[ side ] = visibleLength;
+
+ ++color;
+ if (color >= colorCount)
+ {
+ color = 0;
+ }
+ } // side
+ }
+
+ // bending rays didn't help - still can't see the point
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we "notice" given player
+ * @todo Increase chance if player is rotating
+ * @todo Decrease chance as nears edge of FOV
+ */
+bool CCSBot::IsNoticable( const CCSPlayer *player, unsigned char visParts ) const
+{
+ // if this player has just fired his weapon, we notice him
+ if (DidPlayerJustFireWeapon( player ))
+ {
+ return true;
+ }
+
+ float deltaT = m_attentionInterval.GetElapsedTime();
+
+ // all chances are specified in terms of a standard "quantum" of time
+ // in which a normal person would notice something
+ const float noticeQuantum = 0.25f;
+
+ // determine percentage of player that is visible
+ float coverRatio = 0.0f;
+
+ if (visParts & GUT)
+ {
+ const float chance = 40.0f;
+ coverRatio += chance;
+ }
+
+ if (visParts & HEAD)
+ {
+ const float chance = 10.0f;
+ coverRatio += chance;
+ }
+
+ if (visParts & LEFT_SIDE)
+ {
+ const float chance = 20.0f;
+ coverRatio += chance;
+ }
+
+ if (visParts & RIGHT_SIDE)
+ {
+ const float chance = 20.0f;
+ coverRatio += chance;
+ }
+
+ if (visParts & FEET)
+ {
+ const float chance = 10.0f;
+ coverRatio += chance;
+ }
+
+
+ // compute range modifier - farther away players are harder to notice, depeding on what they are doing
+ float range = (player->GetAbsOrigin() - GetAbsOrigin()).Length();
+ const float closeRange = 300.0f;
+ const float farRange = 1000.0f;
+
+ float rangeModifier;
+ if (range < closeRange)
+ {
+ rangeModifier = 0.0f;
+ }
+ else if (range > farRange)
+ {
+ rangeModifier = 1.0f;
+ }
+ else
+ {
+ rangeModifier = (range - closeRange)/(farRange - closeRange);
+ }
+
+
+ // harder to notice when crouched
+ bool isCrouching = (player->GetFlags() & FL_DUCKING);
+
+
+ // moving players are easier to spot
+ float playerSpeedSq = player->GetAbsVelocity().LengthSqr();
+ const float runSpeed = 200.0f;
+ const float walkSpeed = 30.0f;
+ float farChance, closeChance;
+ if (playerSpeedSq > runSpeed * runSpeed)
+ {
+ // running players are always easy to spot (must be standing to run)
+ return true;
+ }
+ else if (playerSpeedSq > walkSpeed * walkSpeed)
+ {
+ // walking players are less noticable far away
+ if (isCrouching)
+ {
+ closeChance = 90.0f;
+ farChance = 60.0f;
+ }
+ else // standing
+ {
+ closeChance = 100.0f;
+ farChance = 75.0f;
+ }
+ }
+ else
+ {
+ // motionless players are hard to notice
+ if (isCrouching)
+ {
+ // crouching and motionless - very tough to notice
+ closeChance = 80.0f;
+ farChance = 5.0f; // takes about three seconds to notice (50% chance)
+ }
+ else // standing
+ {
+ closeChance = 100.0f;
+ farChance = 10.0f;
+ }
+ }
+
+ // combine posture, speed, and range chances
+ float dispositionChance = closeChance + (farChance - closeChance) * rangeModifier;
+
+ // determine actual chance of noticing player
+ float noticeChance = dispositionChance * coverRatio/100.0f;
+
+ // scale by skill level
+ noticeChance *= (0.5f + 0.5f * GetProfile()->GetSkill());
+
+ // if we are alert, our chance of noticing is much higher
+ if (IsAlert())
+ {
+ const float alertBonus = 50.0f;
+ noticeChance += alertBonus;
+ }
+
+ // scale by time quantum
+ noticeChance *= deltaT / noticeQuantum;
+
+ // there must always be a chance of detecting the enemy
+ const float minChance = 0.1f;
+ if (noticeChance < minChance)
+ {
+ noticeChance = minChance;
+ }
+
+ //PrintIfWatched( "Notice chance = %3.2f\n", noticeChance );
+
+ return (RandomFloat( 0.0f, 100.0f ) < noticeChance);
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return most dangerous threat in my field of view (feeds into reaction time queue).
+ * @todo Account for lighting levels, cover, and distance to see if we notice enemy
+ */
+CCSPlayer *CCSBot::FindMostDangerousThreat( void )
+{
+ VPROF_BUDGET( "CCSBot::FindMostDangerousThreat", VPROF_BUDGETGROUP_NPCS );
+
+ if (IsBlind())
+ {
+ return NULL;
+ }
+
+ enum { MAX_THREATS = 16 }; // maximum number of simulataneously attendable threats
+ struct CloseInfo
+ {
+ CCSPlayer *enemy;
+ float range;
+ }
+ threat[ MAX_THREATS ];
+ int threatCount = 0;
+
+ int prevIndex = m_enemyQueueIndex - 1;
+ if ( prevIndex < 0 )
+ prevIndex = MAX_ENEMY_QUEUE - 1;
+ CCSPlayer *currentThreat = m_enemyQueue[ prevIndex ].player;
+
+ m_bomber = NULL;
+ m_isEnemySniperVisible = false;
+
+ m_closestVisibleFriend = NULL;
+ float closeFriendRange = 99999999999.9f;
+
+ m_closestVisibleHumanFriend = NULL;
+ float closeHumanFriendRange = 99999999999.9f;
+
+ CCSPlayer *sniperThreat = NULL;
+ float sniperThreatRange = 99999999999.9f;
+ bool sniperThreatIsFacingMe = false;
+
+ const float lookingAtMeTolerance = 0.7071f;
+
+ int i;
+
+ {
+ VPROF_BUDGET( "CCSBot::Collect Threats", VPROF_BUDGETGROUP_NPCS );
+
+ for( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if (entity == NULL)
+ continue;
+
+ // is it a player?
+ if (!entity->IsPlayer())
+ continue;
+
+ CCSPlayer *player = static_cast<CCSPlayer *>( entity );
+
+ // ignore self
+ if (player->entindex() == entindex())
+ continue;
+
+ // is it alive?
+ if (!player->IsAlive())
+ continue;
+
+ // is it an enemy?
+ if (player->InSameTeam( this ))
+ {
+ // keep track of nearby friends - use less exact visibility check
+ if (IsVisible( entity->WorldSpaceCenter(), false, this ))
+ {
+ // update watch timestamp
+ int idx = player->entindex();
+ m_watchInfo[idx].timestamp = gpGlobals->curtime;
+ m_watchInfo[idx].isEnemy = false;
+
+ // keep track of our closest friend
+ Vector to = GetAbsOrigin() - player->GetAbsOrigin();
+ float rangeSq = to.LengthSqr();
+ if (rangeSq < closeFriendRange)
+ {
+ m_closestVisibleFriend = player;
+ closeFriendRange = rangeSq;
+ }
+
+ // keep track of our closest human friend
+ if (!player->IsBot() && rangeSq < closeHumanFriendRange)
+ {
+ m_closestVisibleHumanFriend = player;
+ closeHumanFriendRange = rangeSq;
+ }
+ }
+
+ continue;
+ }
+
+ // check if this enemy is fully or partially visible
+ unsigned char visParts;
+ if (!IsVisible( player, CHECK_FOV, &visParts ))
+ continue;
+
+ // do we notice this enemy? (always notice current enemy)
+ if (player != currentThreat)
+ {
+ if (!IsNoticable( player, visParts ))
+ {
+ continue;
+ }
+ }
+
+ // update watch timestamp
+ int idx = player->entindex();
+ m_watchInfo[idx].timestamp = gpGlobals->curtime;
+ m_watchInfo[idx].isEnemy = true;
+
+ // note if we see the bomber
+ if (player->HasC4())
+ {
+ m_bomber = player;
+ }
+
+ // keep track of all visible threats
+ Vector d = GetAbsOrigin() - player->GetAbsOrigin();
+ float distSq = d.LengthSqr();
+
+ // track enemy sniper threats
+ if (IsSniperRifle( player->GetActiveCSWeapon() ))
+ {
+ m_isEnemySniperVisible = true;
+
+ // keep track of the most dangerous sniper we see
+ if (sniperThreat)
+ {
+ if (IsPlayerLookingAtMe( player, lookingAtMeTolerance ))
+ {
+ if (sniperThreatIsFacingMe)
+ {
+ // several snipers are facing us - keep closest
+ if (distSq < sniperThreatRange)
+ {
+ sniperThreat = player;
+ sniperThreatRange = distSq;
+ sniperThreatIsFacingMe = true;
+ }
+ }
+ else
+ {
+ // even if this sniper is farther away, keep it because he's aiming at us
+ sniperThreat = player;
+ sniperThreatRange = distSq;
+ sniperThreatIsFacingMe = true;
+ }
+ }
+ else
+ {
+ // this sniper is not looking at us, only consider it if we dont have a sniper facing us
+ if (!sniperThreatIsFacingMe && distSq < sniperThreatRange)
+ {
+ sniperThreat = player;
+ sniperThreatRange = distSq;
+ }
+ }
+ }
+ else
+ {
+ // first sniper we see
+ sniperThreat = player;
+ sniperThreatRange = distSq;
+ sniperThreatIsFacingMe = IsPlayerLookingAtMe( player, lookingAtMeTolerance );
+ }
+ }
+
+
+ {
+ VPROF_BUDGET( "CCSBot::Sort Threats", VPROF_BUDGETGROUP_NPCS );
+
+ // maintain set of visible threats, sorted by increasing distance
+ if (threatCount == 0)
+ {
+ threat[0].enemy = player;
+ threat[0].range = distSq;
+ threatCount = 1;
+ }
+ else
+ {
+ // find insertion point
+ int j;
+ for( j=0; j<threatCount; ++j )
+ {
+ if (distSq < threat[j].range)
+ break;
+ }
+
+ // shift lower half down a notch
+ for( int k=threatCount-1; k>=j; --k )
+ threat[k+1] = threat[k];
+
+ // insert threat into sorted list
+ threat[j].enemy = player;
+ threat[j].range = distSq;
+
+ if (threatCount < MAX_THREATS)
+ ++threatCount;
+ }
+ }
+ }
+ }
+
+
+ {
+ VPROF_BUDGET( "CCSBot::Count nearby Friends & Enemies", VPROF_BUDGETGROUP_NPCS );
+
+ // track the maximum enemy and friend counts we've seen recently
+ int prevEnemies = m_nearbyEnemyCount;
+ m_nearbyEnemyCount = 0;
+ m_nearbyFriendCount = 0;
+ for( i=0; i<MAX_PLAYERS; ++i )
+ {
+ if (m_watchInfo[i].timestamp <= 0.0f)
+ continue;
+
+ const float recentTime = 3.0f;
+ if (gpGlobals->curtime - m_watchInfo[i].timestamp < recentTime)
+ {
+ if (m_watchInfo[i].isEnemy)
+ ++m_nearbyEnemyCount;
+ else
+ ++m_nearbyFriendCount;
+ }
+ }
+
+ // note when we saw this batch of enemies
+ if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
+ {
+ m_firstSawEnemyTimestamp = gpGlobals->curtime;
+ }
+ }
+
+
+ {
+ VPROF_BUDGET( "CCSBot::Track enemy Place", VPROF_BUDGETGROUP_NPCS );
+
+ //
+ // Track the place where we saw most of our enemies
+ //
+ struct PlaceRank
+ {
+ unsigned int place;
+ int count;
+ };
+ static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
+ int locCount = 0;
+
+ PlaceRank common;
+ common.place = 0;
+ common.count = 0;
+
+ for( i=0; i<threatCount; ++i )
+ {
+ // find the area the player/bot is standing on
+ CNavArea *area;
+ CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
+ if (bot && bot->IsBot())
+ {
+ area = bot->GetLastKnownArea();
+ }
+ else
+ {
+ Vector enemyOrigin = GetCentroid( threat[i].enemy );
+ area = TheNavMesh->GetNearestNavArea( enemyOrigin );
+ }
+
+ if (area == NULL)
+ continue;
+
+ unsigned int threatLoc = area->GetPlace();
+ if (!threatLoc)
+ continue;
+
+ // if place is already in set, increment count
+ int j;
+ for( j=0; j<locCount; ++j )
+ if (placeRank[j].place == threatLoc)
+ break;
+
+ if (j == locCount)
+ {
+ // new place
+ if (locCount < MAX_PLACES_PER_MAP)
+ {
+ placeRank[ locCount ].place = threatLoc;
+ placeRank[ locCount ].count = 1;
+
+ if (common.count == 0)
+ common = placeRank[locCount];
+
+ ++locCount;
+ }
+ }
+ else
+ {
+ // others are in that place, increment
+ ++placeRank[j].count;
+
+ // keep track of the most common place
+ if (placeRank[j].count > common.count)
+ common = placeRank[j];
+ }
+ }
+
+ // remember most common place
+ m_enemyPlace = common.place;
+ }
+
+
+ {
+ VPROF_BUDGET( "CCSBot::Select Threat", VPROF_BUDGETGROUP_NPCS );
+
+ if (threatCount == 0)
+ return NULL;
+
+ // if we can still see our current threat, keep it
+ // unless a new one is much closer
+ bool sawCloserThreat = false;
+ bool sawCurrentThreat = false;
+ int t;
+ for( t=0; t<threatCount; ++t )
+ {
+ if ( threat[t].enemy == currentThreat )
+ {
+ sawCurrentThreat = true;
+ }
+ else if ( threat[t].enemy != currentThreat &&
+ IsSignificantlyCloser( threat[t].enemy, currentThreat ) )
+ {
+ sawCloserThreat = true;
+ }
+ }
+
+ if ( sawCurrentThreat && !sawCloserThreat )
+ {
+ return currentThreat;
+ }
+
+ // if we are a sniper and we see a sniper threat, attack it unless
+ // there are other close enemies facing me
+ if (IsSniper() && sniperThreat)
+ {
+ const float closeCombatRange = 500.0f;
+
+ for( t=0; t<threatCount; ++t )
+ {
+ if (threat[t].range < closeCombatRange && IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
+ {
+ return threat[t].enemy;
+ }
+ }
+
+ return sniperThreat;
+ }
+
+ // otherwise, find the closest threat that is looking at me
+ for( t=0; t<threatCount; ++t )
+ {
+ if (IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
+ {
+ return threat[t].enemy;
+ }
+ }
+ }
+
+
+ // return closest threat
+ return threat[0].enemy;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update our reaction time queue
+ */
+void CCSBot::UpdateReactionQueue( void )
+{
+ VPROF_BUDGET( "CCSBot::UpdateReactionQueue", VPROF_BUDGETGROUP_NPCS );
+
+ // zombies dont see any threats
+ if (cv_bot_zombie.GetBool())
+ return;
+
+ // find biggest threat at this instant
+ CCSPlayer *threat = FindMostDangerousThreat();
+
+ // reset timer
+ m_attentionInterval.Start();
+
+
+ int now = m_enemyQueueIndex;
+
+ // store a snapshot of its state at the end of the reaction time queue
+ if (threat)
+ {
+ m_enemyQueue[ now ].player = threat;
+ m_enemyQueue[ now ].isReloading = threat->IsReloading();
+ m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
+ }
+ else
+ {
+ m_enemyQueue[ now ].player = NULL;
+ m_enemyQueue[ now ].isReloading = false;
+ m_enemyQueue[ now ].isProtectedByShield = false;
+ }
+
+ // queue is round-robin
+ ++m_enemyQueueIndex;
+ if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
+ m_enemyQueueIndex = 0;
+
+ if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
+ ++m_enemyQueueCount;
+
+ // clamp reaction time to enemy queue size
+ float reactionTime = GetProfile()->GetReactionTime() - g_BotUpdateInterval;
+ float maxReactionTime = (MAX_ENEMY_QUEUE * g_BotUpdateInterval) - 0.01f;
+ if (reactionTime > maxReactionTime)
+ reactionTime = maxReactionTime;
+
+ // "rewind" time back to our reaction time
+ int reactionTimeSteps = (int)((reactionTime / g_BotUpdateInterval) + 0.5f);
+
+ int i = now - reactionTimeSteps;
+ if (i < 0)
+ i += MAX_ENEMY_QUEUE;
+
+ m_enemyQueueAttendIndex = (unsigned char)i;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the most dangerous threat we are "conscious" of
+ */
+CCSPlayer *CCSBot::GetRecognizedEnemy( void )
+{
+ if (m_enemyQueueAttendIndex >= m_enemyQueueCount || IsBlind())
+ {
+ return NULL;
+ }
+
+ return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the enemy we are "conscious" of is reloading
+ */
+bool CCSBot::IsRecognizedEnemyReloading( void )
+{
+ if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
+ return false;
+
+ return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the enemy we are "conscious" of is hiding behind a shield
+ */
+bool CCSBot::IsRecognizedEnemyProtectedByShield( void )
+{
+ if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
+ return false;
+
+ return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return distance to closest enemy we are "conscious" of
+ */
+float CCSBot::GetRangeToNearestRecognizedEnemy( void )
+{
+ const CCSPlayer *enemy = GetRecognizedEnemy();
+
+ if (enemy)
+ return (GetAbsOrigin() - enemy->GetAbsOrigin()).Length();
+
+ return 99999999.9f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Blind the bot for the given duration
+ */
+void CCSBot::Blind( float holdTime, float fadeTime, float startingAlpha )
+{
+ PrintIfWatched( "Blinded: holdTime = %3.2f, fadeTime = %3.2f, alpha = %3.2f\n", holdTime, fadeTime, startingAlpha );
+
+ // if we were only blinded a little bit, shake it off
+ const float mildBlindTime = 3.0f;
+ if (holdTime < mildBlindTime)
+ {
+ Wait( 0.75f * holdTime );
+ BecomeAlert();
+ BaseClass::Blind( holdTime, fadeTime, startingAlpha );
+ return;
+ }
+
+
+ // if blinded while in combat - then spray and pray!
+ m_blindFire = IsAttacking();
+
+ // retreat
+ // do this first, so spot selection happens before IsBlind() is set
+ const float hideRange = 400.0f;
+ TryToRetreat( hideRange );
+
+ PrintIfWatched( "I'm blind!\n" );
+
+ if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
+ {
+ GetChatter()->Say( "Blinded", 1.0f );
+ }
+
+ // no longer safe
+ AdjustSafeTime();
+
+ // decide which way to move while blind
+ m_blindMoveDir = static_cast<NavRelativeDirType>( RandomInt( 1, NUM_RELATIVE_DIRECTIONS-1 ) );
+
+ // if we're defusing, don't give up
+ if (IsDefusingBomb())
+ {
+ return;
+ }
+
+ // can't see to aim at enemy
+ StopAiming();
+
+ // dont override "facing away" behavior unless we are going to spray and pray
+ if (m_blindFire)
+ {
+ ClearLookAt();
+
+ // just look straight ahead while blind
+ Vector forward;
+ EyeVectors( &forward );
+ SetLookAt( "Blind", EyePosition() + 10000.0f * forward, PRIORITY_UNINTERRUPTABLE, holdTime + 0.5f * fadeTime );
+ }
+
+ StopWaiting();
+ BecomeAlert();
+
+ BaseClass::Blind( holdTime, fadeTime, startingAlpha );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class CheckLookAt
+{
+public:
+ CheckLookAt( const CCSBot *me, bool testFOV )
+ {
+ m_me = me;
+ m_testFOV = testFOV;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if (!m_me->IsEnemy( player ))
+ return true;
+
+ if (m_testFOV && !(const_cast< CCSBot * >(m_me)->FInViewCone( player->WorldSpaceCenter() )))
+ return true;
+
+ if (!m_me->IsPlayerLookingAtMe( player ))
+ return true;
+
+ if (m_me->IsVisible( (CCSPlayer *)player ))
+ return false;
+
+ return true;
+ }
+
+ const CCSBot *m_me;
+ bool m_testFOV;
+};
+
+/**
+ * Return true if any enemy I have LOS to is looking directly at me
+ * @todo Use reaction time pipeline
+ */
+bool CCSBot::IsAnyVisibleEnemyLookingAtMe( bool testFOV ) const
+{
+ CheckLookAt checkLookAt( this, testFOV );
+ return (ForEachPlayer( checkLookAt ) == false) ? true : false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Do panic behavior
+ */
+void CCSBot::UpdatePanicLookAround( void )
+{
+ if (m_panicTimer.IsElapsed())
+ {
+ return;
+ }
+
+ if (IsEnemyVisible())
+ {
+ StopPanicking();
+ return;
+ }
+
+ if (HasLookAtTarget())
+ {
+ // wait until we finish our current look at
+ return;
+ }
+
+ // select a spot somewhere behind us to look at as we search for our attacker
+ const QAngle &eyeAngles = EyeAngles();
+
+ QAngle newAngles;
+ newAngles.x = RandomFloat( -30.0f, 30.0f );
+
+ // Look directly behind at a random offset in a 90 window.
+ float yaw = RandomFloat( 135.0f, 225.0f );
+ newAngles.y = eyeAngles.y + yaw;
+ newAngles.z = 0.0f;
+
+ Vector forward;
+ AngleVectors( newAngles, &forward );
+
+ Vector spot;
+ spot = EyePosition() + 1000.0f * forward;
+
+ SetLookAt( "Panic", spot, PRIORITY_HIGH, 0.0f );
+ PrintIfWatched( "Panic yaw angle = %3.2f\n", newAngles.y );
+}
diff --git a/game/server/cstrike/bot/cs_bot_weapon.cpp b/game/server/cstrike/bot/cs_bot_weapon.cpp
new file mode 100644
index 0000000..4c737f5
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_weapon.cpp
@@ -0,0 +1,1363 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "basecsgrenade_projectile.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Fire our active weapon towards our current enemy
+ * NOTE: Aiming our weapon is handled in RunBotUpkeep()
+ */
+void CCSBot::FireWeaponAtEnemy( void )
+{
+ if (cv_bot_dont_shoot.GetBool())
+ {
+ return;
+ }
+
+ CBasePlayer *enemy = GetBotEnemy();
+ if (enemy == NULL)
+ {
+ return;
+ }
+
+ Vector myOrigin = GetCentroid( this );
+
+ if (IsUsingSniperRifle())
+ {
+ // if we're using a sniper rifle, don't fire until we are standing still, are zoomed in, and not rapidly moving our view
+ if (!IsNotMoving() || IsWaitingForZoom() || !HasViewBeenSteady( GetProfile()->GetReactionTime() ) )
+ {
+ return;
+ }
+ }
+
+ if (gpGlobals->curtime > m_fireWeaponTimestamp &&
+ GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() &&
+ !IsSurprised())
+ {
+ if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe( enemy )) && // don't shoot at enemies behind shields
+ !IsReloading() &&
+ !IsActiveWeaponClipEmpty() &&
+ //gpGlobals->curtime > m_reacquireTimestamp &&
+ IsEnemyVisible())
+ {
+ // we have a clear shot - pull trigger if we are aiming at enemy
+ Vector toAimSpot = m_aimSpot - EyePosition();
+ float rangeToEnemy = toAimSpot.NormalizeInPlace();
+
+ if ( IsUsingSniperRifle() )
+ {
+ // check our accuracy versus our target distance
+ float fProjectedSpread = rangeToEnemy * GetActiveCSWeapon()->GetInaccuracy();
+ float fRequiredSpread = IsUsing( WEAPON_AWP ) ? 50.0f : 25.0f; // AWP will kill with any hit
+ if ( fProjectedSpread > fRequiredSpread )
+ return;
+ }
+
+ // get actual view direction vector
+ Vector aimDir = GetViewVector();
+
+ float onTarget = DotProduct( toAimSpot, aimDir );
+
+ // aim more precisely with a sniper rifle
+ // because rifles' bullets spray, don't have to be very precise
+ const float halfSize = (IsUsingSniperRifle()) ? HalfHumanWidth : 2.0f * HalfHumanWidth;
+
+ // aiming tolerance depends on how close the target is - closer targets subtend larger angles
+ float aimTolerance = (float)cos( atan( halfSize / rangeToEnemy ) );
+
+ if (onTarget > aimTolerance)
+ {
+ bool doAttack;
+
+ // if friendly fire is on, don't fire if a teammate is blocking our line of fire
+ if (TheCSBots()->AllowFriendlyFireDamage())
+ {
+ if (IsFriendInLineOfFire())
+ doAttack = false;
+ else
+ doAttack = true;
+ }
+ else
+ {
+ // fire freely
+ doAttack = true;
+ }
+
+ if (doAttack)
+ {
+ // if we are using a knife, only swing it if we're close
+ if (IsUsingKnife())
+ {
+ const float knifeRange = 75.0f; // 50
+ if (rangeToEnemy < knifeRange)
+ {
+ // since we've given ourselves away - run!
+ ForceRun( 5.0f );
+
+ // if our prey is facing away, backstab him!
+ if (!IsPlayerFacingMe( enemy ))
+ {
+ SecondaryAttack();
+ }
+ else
+ {
+ // randomly choose primary and secondary attacks with knife
+ const float knifeStabChance = 33.3f;
+ if (RandomFloat( 0, 100 ) < knifeStabChance)
+ SecondaryAttack();
+ else
+ PrimaryAttack();
+ }
+ }
+ }
+ else
+ {
+ PrimaryAttack();
+ }
+ }
+
+ if (IsUsingPistol())
+ {
+ // high-skill bots fire their pistols quickly at close range
+ const float closePistolRange = 360.0f;
+ if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
+ {
+ // fire as fast as possible
+ m_fireWeaponTimestamp = 0.0f;
+ }
+ else
+ {
+ // fire somewhat quickly
+ m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.4f );
+ }
+ }
+ else // not using a pistol
+ {
+ const float sprayRange = 400.0f;
+ if (GetProfile()->GetSkill() < 0.5f || rangeToEnemy < sprayRange || IsUsingMachinegun())
+ {
+ // spray 'n pray if enemy is close, or we're not that good, or we're using the big machinegun
+ m_fireWeaponTimestamp = 0.0f;
+ }
+ else
+ {
+ const float distantTargetRange = 800.0f;
+ if (!IsUsingSniperRifle() && rangeToEnemy > distantTargetRange)
+ {
+ // if very far away, fire slowly for better accuracy
+ m_fireWeaponTimestamp = RandomFloat( 0.3f, 0.7f );
+ }
+ else
+ {
+ // fire short bursts for accuracy
+ m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.25f ); // 0.15, 0.5
+ }
+ }
+ }
+
+ // subtract system latency
+ m_fireWeaponTimestamp -= g_BotUpdateInterval;
+
+ m_fireWeaponTimestamp += gpGlobals->curtime;
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the current aim offset using given accuracy (1.0 = perfect aim, 0.0f = terrible aim)
+ */
+void CCSBot::SetAimOffset( float accuracy )
+{
+ // if our accuracy is less than perfect, it will improve as we "focus in" while not rotating our view
+ if (accuracy < 1.0f)
+ {
+ // if we moved our view, reset our "focus" mechanism
+ if (IsViewMoving( 100.0f ))
+ m_aimSpreadTimestamp = gpGlobals->curtime;
+
+ // focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
+ const float focusTime = MAX( 5.0f * (1.0f - accuracy), 2.0f );
+ float focusInterval = gpGlobals->curtime - m_aimSpreadTimestamp;
+
+ float focusAccuracy = focusInterval / focusTime;
+
+ // limit how much "focus" will help
+ const float maxFocusAccuracy = 0.75f;
+ if (focusAccuracy > maxFocusAccuracy)
+ focusAccuracy = maxFocusAccuracy;
+
+ accuracy = MAX( accuracy, focusAccuracy );
+ }
+
+ //PrintIfWatched( "Accuracy = %4.3f\n", accuracy );
+
+ // aim error increases with distance, such that actual crosshair error stays about the same
+ float range = (m_lastEnemyPosition - EyePosition()).Length();
+ float maxOffset = (GetFOV()/GetDefaultFOV()) * 0.05f * range; // 0.1
+ float error = maxOffset * (1.0f - accuracy);
+
+ m_aimOffsetGoal.x = RandomFloat( -error, error );
+ m_aimOffsetGoal.y = RandomFloat( -error, error );
+ m_aimOffsetGoal.z = RandomFloat( -error, error );
+
+ // define time when aim offset will automatically be updated
+ m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f, 1.0f ); // 0.25, 1.5f
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Wiggle aim error based on GetProfile()->GetSkill()
+ */
+void CCSBot::UpdateAimOffset( void )
+{
+ if (gpGlobals->curtime >= m_aimOffsetTimestamp)
+ {
+ SetAimOffset( GetProfile()->GetSkill() );
+ }
+
+ // move current offset towards goal offset
+ Vector d = m_aimOffsetGoal - m_aimOffset;
+ const float stiffness = 0.1f;
+ m_aimOffset.x += stiffness * d.x;
+ m_aimOffset.y += stiffness * d.y;
+ m_aimOffset.z += stiffness * d.z;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Change our zoom level to be appropriate for the given range.
+ * Return true if the zoom level changed.
+ */
+bool CCSBot::AdjustZoom( float range )
+{
+ bool adjustZoom = false;
+
+ if (IsUsingSniperRifle())
+ {
+ const float sniperZoomRange = 150.0f; // NOTE: This must be less than sniperMinRange in AttackState
+ const float sniperFarZoomRange = 1500.0f;
+
+ // if range is too close, don't zoom
+ if (range <= sniperZoomRange)
+ {
+ // zoom out
+ if (GetZoomLevel() != NO_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ else if (range < sniperFarZoomRange)
+ {
+ // maintain low zoom
+ if (GetZoomLevel() != LOW_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ else
+ {
+ // maintain high zoom
+ if (GetZoomLevel() != HIGH_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ }
+ else
+ {
+ // zoom out
+ if (GetZoomLevel() != NO_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+
+ if (adjustZoom)
+ {
+ SecondaryAttack();
+
+ // pause after zoom to allow "eyes" to refocus
+// m_zoomTimer.Start( 0.25f + (1.0f - GetProfile()->GetSkill()) );
+ m_zoomTimer.Start( 0.25f );
+ }
+
+ return adjustZoom;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if using the specific weapon
+ */
+bool CCSBot::IsUsing( CSWeaponID weaponID ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( weaponID ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we are using a weapon with a removable silencer
+ */
+bool CCSBot::DoesActiveWeaponHaveSilencer( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( WEAPON_M4A1 ) || weapon->IsA( WEAPON_USP ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are using a sniper rifle
+ */
+bool CCSBot::IsUsingSniperRifle( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && IsSniperRifle( weapon ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we have a sniper rifle in our inventory
+ */
+bool CCSBot::IsSniper( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ if (weapon && IsSniperRifle( weapon ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are actively sniping (moving to sniper spot or settled in)
+ */
+bool CCSBot::IsSniping( void ) const
+{
+ if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are using a shotgun
+ */
+bool CCSBot::IsUsingShotgun( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ return weapon->IsKindOf(WEAPONTYPE_SHOTGUN);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if using the big 'ol machinegun
+ */
+bool CCSBot::IsUsingMachinegun( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsA( WEAPON_M249 ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if primary weapon doesn't exist or is totally out of ammo
+ */
+bool CCSBot::IsPrimaryWeaponEmpty( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ if (weapon == NULL)
+ return true;
+
+ // check if gun has any ammo left
+ if (weapon->HasAnyAmmo())
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if pistol doesn't exist or is totally out of ammo
+ */
+bool CCSBot::IsPistolEmpty( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
+
+ if (weapon == NULL)
+ return true;
+
+ // check if gun has any ammo left
+ if (weapon->HasAnyAmmo())
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the given item
+ */
+bool CCSBot::DoEquip( CWeaponCSBase *weapon )
+{
+ if (weapon == NULL)
+ return false;
+
+ // check if weapon has any ammo left
+ if (!weapon->HasAnyAmmo())
+ return false;
+
+ // equip it
+ SelectItem( weapon->GetClassname() );
+ m_equipTimer.Start();
+
+ return true;
+}
+
+
+// throttle how often equipping is allowed
+const float minEquipInterval = 5.0f;
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the best weapon we are carrying that has ammo
+ */
+void CCSBot::EquipBestWeapon( bool mustEquip )
+{
+ // throttle how often equipping is allowed
+ if (!mustEquip && m_equipTimer.GetElapsedTime() < minEquipInterval)
+ return;
+
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheBots );
+
+ CWeaponCSBase *primary = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+ if (primary)
+ {
+ CSWeaponType weaponClass = primary->GetCSWpnData().m_WeaponType;
+
+ if ((ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
+ (ctrl->AllowMachineGuns() && weaponClass == WEAPONTYPE_MACHINEGUN) ||
+ (ctrl->AllowRifles() && weaponClass == WEAPONTYPE_RIFLE) ||
+ (ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
+ (ctrl->AllowSnipers() && weaponClass == WEAPONTYPE_SNIPER_RIFLE) ||
+ (ctrl->AllowSubMachineGuns() && weaponClass == WEAPONTYPE_SUBMACHINEGUN))
+ {
+ if (DoEquip( primary ))
+ return;
+ }
+ }
+
+ if (ctrl->AllowPistols())
+ {
+ if (DoEquip( static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) ) ))
+ return;
+ }
+
+ // always have a knife
+ EquipKnife();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip our pistol
+ */
+void CCSBot::EquipPistol( void )
+{
+ // throttle how often equipping is allowed
+ if (m_equipTimer.GetElapsedTime() < minEquipInterval)
+ return;
+
+ if (TheCSBots()->AllowPistols() && !IsUsingPistol())
+ {
+ CWeaponCSBase *pistol = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
+ DoEquip( pistol );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the knife
+ */
+void CCSBot::EquipKnife( void )
+{
+ if (!IsUsingKnife())
+ {
+ SelectItem( "weapon_knife" );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we have a grenade in our inventory
+ */
+bool CCSBot::HasGrenade( void ) const
+{
+ CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
+ return (grenade) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip a grenade, return false if we cant
+ */
+bool CCSBot::EquipGrenade( bool noSmoke )
+{
+ // snipers don't use grenades
+ if (IsSniper())
+ return false;
+
+ if (IsUsingGrenade())
+ return true;
+
+ if (HasGrenade())
+ {
+ CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
+
+ if (noSmoke && grenade->IsA( WEAPON_SMOKEGRENADE ))
+ return false;
+
+ SelectItem( grenade->GetClassname() );
+
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have knife equipped
+ */
+bool CCSBot::IsUsingKnife( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsA( WEAPON_KNIFE ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have pistol equipped
+ */
+bool CCSBot::IsUsingPistol( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsPistol())
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have a grenade equipped
+ */
+bool CCSBot::IsUsingGrenade( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (!weapon)
+ return false;
+
+ if (weapon->IsA( WEAPON_FLASHBANG ) ||
+ weapon->IsA( WEAPON_SMOKEGRENADE ) ||
+ weapon->IsA( WEAPON_HEGRENADE ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin the process of throwing the grenade
+ */
+void CCSBot::ThrowGrenade( const Vector &target )
+{
+ if (IsUsingGrenade() && m_grenadeTossState == NOT_THROWING && !IsOnLadder())
+ {
+ m_grenadeTossState = START_THROW;
+ m_tossGrenadeTimer.Start( 2.0f );
+
+ const float angleTolerance = 3.0f;
+ SetLookAt( "GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 4.0f, false, angleTolerance );
+
+ Wait( RandomFloat( 2.0f, 4.0f ) );
+
+ if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ {
+ NDebugOverlay::Cross3D( target, 25.0f, 255, 125, 0, true, 3.0f );
+ }
+
+ PrintIfWatched( "%3.2f: Grenade: START_THROW\n", gpGlobals->curtime );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if our weapon can attack
+ */
+bool CCSBot::CanActiveWeaponFire( void ) const
+{
+ return ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Find spot to throw grenade ahead of us and "around the corner" along our path
+ */
+bool CCSBot::FindGrenadeTossPathTarget( Vector *pos )
+{
+ if (!HasPath())
+ return false;
+
+ // find farthest point we can see on the path
+ int i;
+ for( i=m_pathIndex; i<m_pathLength; ++i )
+ {
+ if (!FVisible( m_path[i].pos + Vector( 0, 0, HalfHumanHeight ) ))
+ break;
+ }
+
+ if (i == m_pathIndex)
+ return false;
+
+ // find exact spot where we lose sight
+ Vector dir = m_path[i].pos - m_path[i-1].pos;
+ float length = dir.NormalizeInPlace();
+
+ const float inc = 25.0f;
+ Vector p;
+ Vector visibleSpot = m_path[i-1].pos;
+ for( float t = 0.0f; t<length; t += inc )
+ {
+ p = m_path[i-1].pos + t * dir;
+ p.z += HalfHumanHeight;
+ if (!FVisible( p ))
+ break;
+
+ visibleSpot = p;
+ }
+
+ // massage the location a bit
+ visibleSpot.z += 10.0f;
+
+ const float bufferRange = 50.0f;
+
+ trace_t result;
+ Vector check;
+
+ // check +X
+ check = visibleSpot + Vector( 999.9f, 0, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = result.endpos.x - visibleSpot.x;
+ if (range < bufferRange)
+ {
+ visibleSpot.x = result.endpos.x - bufferRange;
+ }
+ }
+
+ // check -X
+ check = visibleSpot + Vector( -999.9f, 0, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = visibleSpot.x - result.endpos.x;
+ if (range < bufferRange)
+ {
+ visibleSpot.x = result.endpos.x + bufferRange;
+ }
+ }
+
+ // check +Y
+ check = visibleSpot + Vector( 0, 999.9f, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = result.endpos.y - visibleSpot.y;
+ if (range < bufferRange)
+ {
+ visibleSpot.y = result.endpos.y - bufferRange;
+ }
+ }
+
+ // check -Y
+ check = visibleSpot + Vector( 0, -999.9f, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = visibleSpot.y - result.endpos.y;
+ if (range < bufferRange)
+ {
+ visibleSpot.y = result.endpos.y + bufferRange;
+ }
+ }
+
+ *pos = visibleSpot;
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Look for grenade throw targets and throw the grenade
+ */
+void CCSBot::LookForGrenadeTargets( void )
+{
+ if (!IsUsingGrenade() || IsThrowingGrenade())
+ {
+ return;
+ }
+
+ const CNavArea *tossArea = GetInitialEncounterArea();
+ if (tossArea == NULL)
+ {
+ return;
+ }
+
+ int enemyTeam = OtherTeam( GetTeamNumber() );
+
+ // check if we should put our grenade away
+ if (tossArea->GetEarliestOccupyTime( enemyTeam ) > gpGlobals->curtime)
+ {
+ EquipBestWeapon( MUST_EQUIP );
+ return;
+ }
+
+ // throw grenades at initial encounter area
+ Vector tossTarget = Vector( 0, 0, 0 );
+ if (!tossArea->IsVisible( EyePosition(), &tossTarget ))
+ {
+ return;
+ }
+
+
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+ if (weapon && weapon->IsA( WEAPON_SMOKEGRENADE ))
+ {
+ // don't worry so much about smokes
+ ThrowGrenade( tossTarget );
+ PrintIfWatched( "Throwing smoke grenade!" );
+ SetInitialEncounterArea( NULL );
+ return;
+ }
+ else // explosive and flashbang grenades
+ {
+ // initial encounter area is visible, wait to throw until timing is right
+
+ const float leadTime = 1.5f;
+ float enemyTime = tossArea->GetEarliestOccupyTime( enemyTeam );
+ if (enemyTime - TheCSBots()->GetElapsedRoundTime() > leadTime)
+ {
+ // don't throw yet
+ return;
+ }
+
+
+ Vector to = tossTarget - EyePosition();
+ float range = to.Length();
+
+ const float slope = 0.2f; // 0.25f;
+ float tossHeight = slope * range;
+
+ trace_t result;
+ CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
+
+ const float heightInc = tossHeight / 10.0f;
+ Vector target;
+ float safeSpace = tossHeight / 2.0f;
+
+ // Build a box to sweep along the ray when looking for obstacles
+ const Vector& eyePosition = EyePosition();
+ Vector mins = VEC_HULL_MIN;
+ Vector maxs = VEC_HULL_MAX;
+ mins.z = 0;
+ maxs.z = heightInc;
+
+
+ // find low and high bounds of toss window
+ float low = 0.0f;
+ float high = tossHeight + safeSpace;
+ bool gotLow = false;
+ float lastH = 0.0f;
+ for( float h = 0.0f; h < 3.0f * tossHeight; h += heightInc )
+ {
+ target = tossTarget + Vector( 0, 0, h );
+
+ // make sure toss line is clear
+
+ QAngle angles( 0, 0, 0 );
+ Ray_t ray;
+ ray.Init( eyePosition, target, mins, maxs );
+ enginetrace->TraceRay( ray, MASK_VISIBLE_AND_NPCS | CONTENTS_GRATE, &traceFilter, &result );
+ if (result.fraction == 1.0f)
+ {
+ //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 0, 0, 255, 40, 10.0f );
+
+ // line is clear
+ if (!gotLow)
+ {
+ low = h;
+ gotLow = true;
+ }
+ }
+ else
+ {
+ //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 255, 0, 0, 5, 10.0f );
+
+ // line is blocked
+ if (gotLow)
+ {
+ high = lastH;
+ break;
+ }
+ }
+
+ lastH = h;
+ }
+
+ if (gotLow)
+ {
+ // throw grenade into toss window
+ if (tossHeight < low)
+ {
+ if (low + safeSpace > high)
+ {
+ // narrow window
+ tossHeight = (high + low)/2.0f;
+ }
+ else
+ {
+ tossHeight = low + safeSpace;
+ }
+ }
+ else if (tossHeight > high - safeSpace)
+ {
+ if (high - safeSpace < low)
+ {
+ // narrow window
+ tossHeight = (high + low)/2.0f;
+ }
+ else
+ {
+ tossHeight = high - safeSpace;
+ }
+ }
+
+ ThrowGrenade( tossTarget + Vector( 0, 0, tossHeight ) );
+ SetInitialEncounterArea( NULL );
+ return;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class FOVClearOfFriends
+{
+public:
+ FOVClearOfFriends( CCSBot *me )
+ {
+ m_me = me;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if (player == m_me || !player->IsAlive())
+ return true;
+
+ if (m_me->InSameTeam( player ))
+ {
+ Vector to = player->EyePosition() - m_me->EyePosition();
+ to.NormalizeInPlace();
+
+ Vector forward;
+ m_me->EyeVectors( &forward );
+
+ if (DotProduct( to, forward ) > 0.95f)
+ {
+ if (m_me->IsVisible( (CCSPlayer *)player ))
+ {
+ // we see a friend in our FOV
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ CCSBot *m_me;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Process the grenade throw state machine
+ */
+void CCSBot::UpdateGrenadeThrow( void )
+{
+ switch( m_grenadeTossState )
+ {
+ case START_THROW:
+ {
+ if (m_tossGrenadeTimer.IsElapsed())
+ {
+ // something prevented the throw - give up
+ EquipBestWeapon( MUST_EQUIP );
+ ClearLookAt();
+ m_grenadeTossState = NOT_THROWING;
+ PrintIfWatched( "%3.2f: Grenade: THROW FAILED\n", gpGlobals->curtime );
+ return;
+ }
+
+ if (m_lookAtSpotState == LOOK_AT_SPOT)
+ {
+ // don't throw if there are friends ahead of us
+ FOVClearOfFriends fovClear( this );
+ if (ForEachPlayer( fovClear ))
+ {
+ m_grenadeTossState = FINISH_THROW;
+ m_tossGrenadeTimer.Start( 1.0f );
+ PrintIfWatched( "%3.2f: Grenade: FINISH_THROW\n", gpGlobals->curtime );
+ }
+ else
+ {
+ PrintIfWatched( "%3.2f: Grenade: Friend is in the way...\n", gpGlobals->curtime );
+ }
+ }
+
+ // hold in the trigger and be ready to throw
+ PrimaryAttack();
+
+ break;
+ }
+
+ case FINISH_THROW:
+ {
+ // throw the grenade and hold our aiming line for a moment
+ if (m_tossGrenadeTimer.IsElapsed())
+ {
+ ClearLookAt();
+
+ m_grenadeTossState = NOT_THROWING;
+ PrintIfWatched( "%3.2f: Grenade: THROW COMPLETE\n", gpGlobals->curtime );
+ }
+ break;
+ }
+
+ default:
+ {
+ if (IsUsingGrenade())
+ {
+ // pull the pin
+ PrimaryAttack();
+ }
+ break;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class GrenadeResponse
+{
+public:
+ GrenadeResponse( CCSBot *me )
+ {
+ m_me = me;
+ }
+
+ bool operator() ( ActiveGrenade *ag ) const
+ {
+ const float retreatRange = 300.0f;
+ const float hideTime = 1.0f;
+
+ // do we see this grenade
+ if (m_me->IsVisible( ag->GetPosition(), CHECK_FOV, (CBaseEntity *)ag->GetEntity() ))
+ {
+ // we see it
+ if (ag->IsSmoke())
+ {
+ // ignore smokes
+ return true;
+ }
+
+ Vector velDir = ag->GetEntity()->GetAbsVelocity();
+ float grenadeSpeed = velDir.NormalizeInPlace();
+ const float atRestSpeed = 50.0f;
+
+ const float aboutToBlow = 0.5f;
+ if (ag->IsFlashbang() && ag->GetEntity()->m_flDetonateTime - gpGlobals->curtime < aboutToBlow)
+ {
+ // turn away from flashbangs about to explode
+ QAngle eyeAngles = m_me->EyeAngles();
+
+ float yaw = RandomFloat( 100.0f, 135.0f );
+ eyeAngles.y += (RandomFloat( -1.0f, 1.0f ) < 0.0f) ? (-yaw) : yaw;
+
+ Vector forward;
+ AngleVectors( eyeAngles, &forward );
+
+ Vector away = m_me->EyePosition() - 1000.0f * forward;
+ const float duration = 2.0f;
+
+ m_me->ClearLookAt();
+ m_me->SetLookAt( "Avoid Flashbang", away, PRIORITY_UNINTERRUPTABLE, duration );
+
+ m_me->StopAiming();
+
+ return false;
+ }
+
+
+ // flee from grenades if close by or thrown towards us
+ const float throwDangerRange = 750.0f;
+ const float nearDangerRange = 300.0f;
+ Vector to = ag->GetPosition() - m_me->GetAbsOrigin();
+ float range = to.NormalizeInPlace();
+ if (range > throwDangerRange)
+ {
+ return true;
+ }
+
+ if (grenadeSpeed > atRestSpeed)
+ {
+ // grenade is moving
+ if (DotProduct( to, velDir ) >= -0.5f)
+ {
+ // going away from us
+ return true;
+ }
+
+ m_me->PrintIfWatched( "Retreating from a grenade thrown towards me!\n" );
+ }
+ else if (range < nearDangerRange)
+ {
+ // grenade has come to rest near us
+ m_me->PrintIfWatched( "Retreating from a grenade that landed near me!\n" );
+ }
+
+ // retreat!
+ m_me->TryToRetreat( retreatRange, hideTime );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ CCSBot *m_me;
+};
+
+/**
+ * React to enemy grenades we see
+ */
+void CCSBot::AvoidEnemyGrenades( void )
+{
+ // low skill bots dont avoid grenades
+ if (GetProfile()->GetSkill() < 0.5)
+ {
+ return;
+ }
+
+ if (IsAvoidingGrenade())
+ {
+ // already avoiding one
+ return;
+ }
+
+ // low skill bots don't avoid grenades
+ if (GetProfile()->GetSkill() < 0.6f)
+ {
+ return;
+ }
+
+ GrenadeResponse respond( this );
+ if (TheBots->ForEachGrenade( respond ) == false)
+ {
+ const float avoidTime = 4.0f;
+ m_isAvoidingGrenade.Start( avoidTime );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reload our weapon if we must
+ */
+void CCSBot::ReloadCheck( void )
+{
+ const float safeReloadWaitTime = 3.0f;
+ const float reloadAmmoRatio = 0.6f;
+
+ // don't bother to reload if there are no enemies left
+ if (GetEnemiesRemaining() == 0)
+ return;
+
+ if (IsDefusingBomb() || IsReloading())
+ return;
+
+ if (IsActiveWeaponClipEmpty())
+ {
+ // high-skill players switch to pistol instead of reloading during combat
+ if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
+ {
+ if (!GetActiveCSWeapon()->IsPistol() && !IsPistolEmpty())
+ {
+ // switch to pistol instead of reloading
+ EquipPistol();
+ return;
+ }
+ }
+ }
+ else if (GetTimeSinceLastSawEnemy() > safeReloadWaitTime && GetActiveWeaponAmmoRatio() <= reloadAmmoRatio)
+ {
+ // high-skill players use all their ammo and switch to pistol instead of reloading during combat
+ if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
+ return;
+ }
+ else
+ {
+ // do not need to reload
+ return;
+ }
+
+ // don't reload the AWP until it is totally out of ammo
+ if (IsUsing( WEAPON_AWP ) && !IsActiveWeaponClipEmpty())
+ return;
+
+ Reload();
+
+ // move to cover to reload if there are enemies nearby
+ if (GetNearbyEnemyCount())
+ {
+ // avoid enemies while reloading (above 0.75 skill always hide to reload)
+ const float hideChance = 25.0f + 100.0f * GetProfile()->GetSkill();
+
+ if (!IsHiding() && RandomFloat( 0, 100 ) < hideChance)
+ {
+ const float safeTime = 5.0f;
+ if (GetTimeSinceLastSawEnemy() < safeTime)
+ {
+ PrintIfWatched( "Retreating to a safe spot to reload!\n" );
+ const Vector *spot = FindNearbyRetreatSpot( this, 1000.0f );
+ if (spot)
+ {
+ // ignore enemies for a second to give us time to hide
+ // reaching our hiding spot clears our disposition
+ IgnoreEnemies( 10.0f );
+
+ Run();
+ StandUp();
+ Hide( *spot, 0.0f );
+ }
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Silence/unsilence our weapon if we must
+ */
+void CCSBot::SilencerCheck( void )
+{
+ const float safeSilencerWaitTime = 3.5f; // longer than reload check because reloading should take precedence
+
+ if (IsDefusingBomb() || IsReloading() || IsAttacking())
+ return;
+
+ // M4A1 and USP are the only weapons with removable silencers
+ if (!DoesActiveWeaponHaveSilencer())
+ return;
+
+ if (GetTimeSinceLastSawEnemy() < safeSilencerWaitTime)
+ return;
+
+ // don't touch the silencer if there are enemies nearby
+ if (GetNearbyEnemyCount() == 0)
+ {
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+ if (weapon == NULL)
+ return;
+
+ bool isSilencerOn = weapon->IsSilenced();
+
+ if ( weapon->m_flNextSecondaryAttack >= gpGlobals->curtime )
+ return;
+
+ // equip silencer if we want to and we don't have a shield.
+ if ( isSilencerOn != (GetProfile()->PrefersSilencer() || GetProfile()->GetSkill() > 0.7f) && !HasShield() )
+ {
+ PrintIfWatched( "%s silencer!\n", (isSilencerOn) ? "Unequipping" : "Equipping" );
+ weapon->SecondaryAttack();
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when in contact with a CBaseCombatWeapon
+ */
+bool CCSBot::BumpWeapon( CBaseCombatWeapon *pWeapon )
+{
+ CWeaponCSBase *droppedGun = dynamic_cast< CWeaponCSBase* >( pWeapon );
+
+ // right now we only care about primary weapons on the ground
+ if ( droppedGun && droppedGun->GetSlot() == WEAPON_SLOT_RIFLE )
+ {
+ CWeaponCSBase *myGun = dynamic_cast< CWeaponCSBase* >( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ // if the gun on the ground is the same one we have, dont bother
+ if ( myGun && droppedGun->GetWeaponID() != myGun->GetWeaponID() )
+ {
+ // if we don't have a weapon preference, give up
+ if ( GetProfile()->HasPrimaryPreference() )
+ {
+ // don't change weapons if we've seen enemies recently
+ const float safeTime = 2.5f;
+ if ( GetTimeSinceLastSawEnemy() >= safeTime )
+ {
+ // we have a primary weapon - drop it if the one on the ground is better
+ for( int i = 0; i < GetProfile()->GetWeaponPreferenceCount(); ++i )
+ {
+ CSWeaponID prefID = GetProfile()->GetWeaponPreference( i );
+
+ if (!IsPrimaryWeapon( prefID ))
+ continue;
+
+ // if the gun we are using is more desirable, give up
+ if ( prefID == myGun->GetWeaponID() )
+ break;
+
+ if ( prefID == droppedGun->GetWeaponID() )
+ {
+ // the gun on the ground is better than the one we have - drop our gun
+ DropRifle();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return BaseClass::BumpWeapon( droppedGun );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if a friend is in our weapon's way
+ * @todo Check more rays for safety.
+ */
+bool CCSBot::IsFriendInLineOfFire( void )
+{
+ // compute the unit vector along our view
+ Vector aimDir = GetViewVector();
+
+ // trace the bullet's path
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.DidHitNonWorldEntity())
+ {
+ CBaseEntity *victim = result.m_pEnt;
+
+ if (victim && victim->IsPlayer() && victim->IsAlive())
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( victim );
+
+ if (player->InSameTeam( this ))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return line-of-sight distance to obstacle along weapon fire ray
+ * @todo Re-use this computation with IsFriendInLineOfFire()
+ */
+float CCSBot::ComputeWeaponSightRange( void )
+{
+ // compute the unit vector along our view
+ Vector aimDir = GetViewVector();
+
+ // trace the bullet's path
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ return (EyePosition() - result.endpos).Length();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the given player just fired their weapon
+ */
+bool CCSBot::DidPlayerJustFireWeapon( const CCSPlayer *player ) const
+{
+ // if this player has just fired his weapon, we notice him
+ CWeaponCSBase *weapon = player->GetActiveCSWeapon();
+ return (weapon && !weapon->IsSilenced() && weapon->m_flNextPrimaryAttack > gpGlobals->curtime);
+}
+
diff --git a/game/server/cstrike/bot/cs_bot_weapon_id.cpp b/game/server/cstrike/bot/cs_bot_weapon_id.cpp
new file mode 100644
index 0000000..21c423e
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_weapon_id.cpp
@@ -0,0 +1,22 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael Booth ([email protected]), 2003
+// Author: Matthew D. Campbell ([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"
+
+//--------------------------------------------------------------------------------------------------------
+//
+// Temporary solution until we have time to build something more elegant
+// Very nasty - need to keep in sync with the buy aliases
+// NOTE: Array must be NULL terminated
+//
diff --git a/game/server/cstrike/bot/cs_gamestate.cpp b/game/server/cstrike/bot/cs_gamestate.cpp
new file mode 100644
index 0000000..61f537b
--- /dev/null
+++ b/game/server/cstrike/bot/cs_gamestate.cpp
@@ -0,0 +1,767 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Encapsulation of the current scenario/game state. Allows each bot imperfect knowledge.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "KeyValues.h"
+
+#include "cs_bot.h"
+#include "cs_gamestate.h"
+#include "cs_simple_hostage.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+CSGameState::CSGameState( CCSBot *owner )
+{
+ m_owner = owner;
+ m_isRoundOver = false;
+
+ m_bombState = MOVING;
+ m_lastSawBomber.Invalidate();
+ m_lastSawLooseBomb.Invalidate();
+ m_isPlantedBombPosKnown = false;
+ m_plantedBombsite = UNKNOWN;
+
+ m_bombsiteCount = 0;
+ m_bombsiteSearchIndex = 0;
+
+ for( int i=0; i<MAX_HOSTAGES; ++i )
+ {
+ m_hostage[i].hostage = NULL;
+ m_hostage[i].isValid = false;
+ m_hostage[i].isAlive = false;
+ m_hostage[i].isFree = true;
+ m_hostage[i].knownPos = Vector( 0, 0, 0 );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset at round start
+ */
+void CSGameState::Reset( void )
+{
+ m_isRoundOver = false;
+
+ // bomb -----------------------------------------------------------------------
+ m_bombState = MOVING;
+ m_lastSawBomber.Invalidate();
+ m_lastSawLooseBomb.Invalidate();
+ m_isPlantedBombPosKnown = false;
+ m_plantedBombsite = UNKNOWN;
+
+ m_bombsiteCount = TheCSBots()->GetZoneCount();
+
+ int i;
+ for( i=0; i<m_bombsiteCount; ++i )
+ {
+ m_isBombsiteClear[i] = false;
+ m_bombsiteSearchOrder[i] = i;
+ }
+
+ // shuffle the bombsite search order
+ // allows T's to plant at random site, and TEAM_CT's to search in a random order
+ // NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
+ for( i=0; i < m_bombsiteCount; ++i )
+ {
+ int swap = m_bombsiteSearchOrder[i];
+ int rnd = RandomInt( i, m_bombsiteCount-1 );
+ m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[ rnd ];
+ m_bombsiteSearchOrder[ rnd ] = swap;
+ }
+
+ m_bombsiteSearchIndex = 0;
+
+ // hostage ---------------------------------------------------------------------
+ InitializeHostageInfo();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnHostageRescuedAll( IGameEvent *event )
+{
+ m_allHostagesRescued = true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnRoundEnd( IGameEvent *event )
+{
+ m_isRoundOver = true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnRoundStart( IGameEvent *event )
+{
+ Reset();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnBombPlanted( IGameEvent *event )
+{
+ // change state - the event is announced to everyone
+ SetBombState( PLANTED );
+
+ CBasePlayer *plantingPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+
+ // Terrorists always know where the bomb is
+ if (m_owner->GetTeamNumber() == TEAM_TERRORIST && plantingPlayer)
+ {
+ UpdatePlantedBomb( plantingPlayer->GetAbsOrigin() );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnBombDefused( IGameEvent *event )
+{
+ // change state - the event is announced to everyone
+ SetBombState( DEFUSED );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Update game state based on events we have received
+ */
+void CSGameState::OnBombExploded( IGameEvent *event )
+{
+ // change state - the event is announced to everyone
+ SetBombState( EXPLODED );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * True if round has been won or lost (but not yet reset)
+ */
+bool CSGameState::IsRoundOver( void ) const
+{
+ return m_isRoundOver;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CSGameState::SetBombState( BombState state )
+{
+ // if state changed, reset "last seen" timestamps
+ if (m_bombState != state)
+ {
+ m_bombState = state;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CSGameState::UpdateLooseBomb( const Vector &pos )
+{
+ m_looseBombPos = pos;
+ m_lastSawLooseBomb.Reset();
+
+ // we saw the loose bomb, update our state
+ SetBombState( LOOSE );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+float CSGameState::TimeSinceLastSawLooseBomb( void ) const
+{
+ return m_lastSawLooseBomb.GetElapsedTime();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+bool CSGameState::IsLooseBombLocationKnown( void ) const
+{
+ if (m_bombState != LOOSE)
+ return false;
+
+ return (m_lastSawLooseBomb.HasStarted()) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CSGameState::UpdateBomber( const Vector &pos )
+{
+ m_bomberPos = pos;
+ m_lastSawBomber.Reset();
+
+ // we saw the bomber, update our state
+ SetBombState( MOVING );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+float CSGameState::TimeSinceLastSawBomber( void ) const
+{
+ return m_lastSawBomber.GetElapsedTime();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+bool CSGameState::IsPlantedBombLocationKnown( void ) const
+{
+ if (m_bombState != PLANTED)
+ return false;
+
+ return m_isPlantedBombPosKnown;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the zone index of the planted bombsite, or UNKNOWN
+ */
+int CSGameState::GetPlantedBombsite( void ) const
+{
+ if (m_bombState != PLANTED)
+ return UNKNOWN;
+
+ return m_plantedBombsite;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are currently in the bombsite where the bomb is planted
+ */
+bool CSGameState::IsAtPlantedBombsite( void ) const
+{
+ if (m_bombState != PLANTED)
+ return false;
+
+ Vector myOrigin = GetCentroid( m_owner );
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
+
+ if (zone)
+ {
+ return (m_plantedBombsite == zone->m_index);
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the zone index of the next bombsite to search
+ */
+int CSGameState::GetNextBombsiteToSearch( void )
+{
+ if (m_bombsiteCount <= 0)
+ return 0;
+
+ int i;
+
+ // return next non-cleared bombsite index
+ for( i=m_bombsiteSearchIndex; i<m_bombsiteCount; ++i )
+ {
+ int z = m_bombsiteSearchOrder[i];
+ if (!m_isBombsiteClear[z])
+ {
+ m_bombsiteSearchIndex = i;
+ return z;
+ }
+ }
+
+ // all the bombsites are clear, someone must have been mistaken - start search over
+ for( i=0; i<m_bombsiteCount; ++i )
+ m_isBombsiteClear[i] = false;
+ m_bombsiteSearchIndex = 0;
+
+ return GetNextBombsiteToSearch();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns position of bomb in its various states (moving, loose, planted),
+ * or NULL if we don't know where the bomb is
+ */
+const Vector *CSGameState::GetBombPosition( void ) const
+{
+ switch( m_bombState )
+ {
+ case MOVING:
+ {
+ if (!m_lastSawBomber.HasStarted())
+ return NULL;
+
+ return &m_bomberPos;
+ }
+
+ case LOOSE:
+ {
+ if (IsLooseBombLocationKnown())
+ return &m_looseBombPos;
+
+ return NULL;
+ }
+
+ case PLANTED:
+ {
+ if (IsPlantedBombLocationKnown())
+ return &m_plantedBombPos;
+
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * We see the planted bomb at 'pos'
+ */
+void CSGameState::UpdatePlantedBomb( const Vector &pos )
+{
+ const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( pos );
+
+ if (zone == NULL)
+ {
+ CONSOLE_ECHO( "ERROR: Bomb planted outside of a zone!\n" );
+ m_plantedBombsite = UNKNOWN;
+ }
+ else
+ {
+ m_plantedBombsite = zone->m_index;
+ }
+
+ m_plantedBombPos = pos;
+ m_isPlantedBombPosKnown = true;
+ SetBombState( PLANTED );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Someone told us where the bomb is planted
+ */
+void CSGameState::MarkBombsiteAsPlanted( int zoneIndex )
+{
+ m_plantedBombsite = zoneIndex;
+ SetBombState( PLANTED );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Someone told us a bombsite is clear
+ */
+void CSGameState::ClearBombsite( int zoneIndex )
+{
+ if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
+ m_isBombsiteClear[ zoneIndex ] = true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+bool CSGameState::IsBombsiteClear( int zoneIndex ) const
+{
+ if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
+ return m_isBombsiteClear[ zoneIndex ];
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Initialize our knowledge of the number and location of hostages
+ */
+void CSGameState::InitializeHostageInfo( void )
+{
+ m_hostageCount = 0;
+ m_allHostagesRescued = false;
+ m_haveSomeHostagesBeenTaken = false;
+
+ for( int i=0; i<g_Hostages.Count(); ++i )
+ {
+ m_hostage[ m_hostageCount ].hostage = g_Hostages[i];
+ m_hostage[ m_hostageCount ].knownPos = g_Hostages[i]->GetAbsOrigin();
+ m_hostage[ m_hostageCount ].isValid = true;
+ m_hostage[ m_hostageCount ].isAlive = true;
+ m_hostage[ m_hostageCount ].isFree = true;
+ ++m_hostageCount;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest free and live hostage
+ * If we are a CT this information is perfect.
+ * Otherwise, this is based on our individual memory of the game state.
+ * If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
+ * NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
+ * returned, since CHostages get deleted when they die.
+ */
+CHostage *CSGameState::GetNearestFreeHostage( Vector *knowPos ) const
+{
+ if (m_owner == NULL)
+ return NULL;
+
+ CNavArea *startArea = m_owner->GetLastKnownArea();
+ if (startArea == NULL)
+ return NULL;
+
+ CHostage *close = NULL;
+ Vector closePos( 0, 0, 0 );
+ float closeDistance = 9999999999.9f;
+
+ for( int i=0; i<m_hostageCount; ++i )
+ {
+ CHostage *hostage = m_hostage[i].hostage;
+ Vector hostagePos;
+
+ if (m_owner->GetTeamNumber() == TEAM_CT)
+ {
+ // we know exactly where the hostages are, and if they are alive
+ if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
+ continue;
+
+ if (m_hostage[i].hostage->IsFollowingSomeone())
+ continue;
+
+ hostagePos = m_hostage[i].hostage->GetAbsOrigin();
+ }
+ else
+ {
+ // use our memory of where we think the hostages are
+ if (m_hostage[i].isValid == false)
+ continue;
+
+ hostagePos = m_hostage[i].knownPos;
+ }
+
+ CNavArea *hostageArea = TheNavMesh->GetNearestNavArea( hostagePos );
+ if (hostageArea)
+ {
+ ShortestPathCost cost;
+ float travelDistance = NavAreaTravelDistance( startArea, hostageArea, cost );
+
+ if (travelDistance >= 0.0f && travelDistance < closeDistance)
+ {
+ closeDistance = travelDistance;
+ closePos = hostagePos;
+ close = hostage;
+ }
+ }
+ }
+
+ // return where we think the hostage is
+ if (knowPos && close)
+ *knowPos = closePos;
+
+ return close;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the location of a "free" hostage, or NULL if we dont know of any
+ */
+const Vector *CSGameState::GetRandomFreeHostagePosition( void ) const
+{
+ if (m_owner == NULL)
+ return NULL;
+
+ static Vector freePos[ MAX_HOSTAGES ];
+ int freeCount = 0;
+
+ for( int i=0; i<m_hostageCount; ++i )
+ {
+ const HostageInfo *info = &m_hostage[i];
+
+ if (m_owner->GetTeamNumber() == TEAM_CT)
+ {
+ // we know exactly where the hostages are, and if they are alive
+ if (!info->hostage || !info->hostage->IsAlive())
+ continue;
+
+ // escorted hostages are not "free"
+ if (info->hostage->IsFollowingSomeone())
+ continue;
+
+ freePos[ freeCount++ ] = info->hostage->GetAbsOrigin();
+ }
+ else
+ {
+ // use our memory of where we think the hostages are
+ if (info->isValid == false)
+ continue;
+
+ freePos[ freeCount++ ] = info->knownPos;
+ }
+ }
+
+ if (freeCount)
+ {
+ return &freePos[ RandomInt( 0, freeCount-1 ) ];
+ }
+
+ return NULL;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * If we can see any of the positions where we think a hostage is, validate it
+ * Return status of any changes (a hostage died or was moved)
+ */
+unsigned char CSGameState::ValidateHostagePositions( void )
+{
+ // limit how often we validate
+ if (!m_validateInterval.IsElapsed())
+ return NO_CHANGE;
+
+ const float validateInterval = 0.5f;
+ m_validateInterval.Start( validateInterval );
+
+
+ // check the status of hostages
+ unsigned char status = NO_CHANGE;
+
+ int i;
+ int startValidCount = 0;
+ for( i=0; i<m_hostageCount; ++i )
+ if (m_hostage[i].isValid)
+ ++startValidCount;
+
+ for( i=0; i<m_hostageCount; ++i )
+ {
+ HostageInfo *info = &m_hostage[i];
+
+ if (!info->hostage )
+ continue;
+
+ // if we can see a hostage, update our knowledge of it
+ Vector pos = info->hostage->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
+ if (m_owner->IsVisible( pos, CHECK_FOV ))
+ {
+ if (info->hostage->IsAlive())
+ {
+ // live hostage
+
+ // if hostage is being escorted by a CT, we don't "see" it, we see the CT
+ if (info->hostage->IsFollowingSomeone())
+ {
+ info->isValid = false;
+ }
+ else
+ {
+ info->knownPos = info->hostage->GetAbsOrigin();
+ info->isValid = true;
+ }
+ }
+ else
+ {
+ // dead hostage
+
+ // if we thought it was alive, this is news to us
+ if (info->isAlive)
+ status |= HOSTAGE_DIED;
+
+ info->isAlive = false;
+ info->isValid = false;
+ }
+
+ continue;
+ }
+
+ // if we dont know where this hostage is, nothing to validate
+ if (!info->isValid)
+ continue;
+
+ // can't directly see this hostage
+ // check line of sight to where we think this hostage is, to see if we noticed that is has moved
+ pos = info->knownPos + Vector( 0, 0, HalfHumanHeight );
+ if (m_owner->IsVisible( pos, CHECK_FOV ))
+ {
+ // we can see where we thought the hostage was - verify it is still there and alive
+
+ if (!info->hostage->IsValid())
+ {
+ // since we have line of sight to an invalid hostage, it must be dead
+ // discovered that hostage has been killed
+ status |= HOSTAGE_DIED;
+ info->isAlive = false;
+ info->isValid = false;
+ continue;
+ }
+
+ if (info->hostage->IsFollowingSomeone())
+ {
+ // discovered the hostage has been taken
+ status |= HOSTAGE_GONE;
+ info->isValid = false;
+ continue;
+ }
+
+ const float tolerance = 50.0f;
+ if ((info->hostage->GetAbsOrigin() - info->knownPos).IsLengthGreaterThan( tolerance ))
+ {
+ // discovered that hostage has been moved
+ status |= HOSTAGE_GONE;
+ info->isValid = false;
+ continue;
+ }
+ }
+ }
+
+ int endValidCount = 0;
+ for( i=0; i<m_hostageCount; ++i )
+ if (m_hostage[i].isValid)
+ ++endValidCount;
+
+ if (endValidCount == 0 && startValidCount > 0)
+ {
+ // we discovered all the hostages are gone
+ status &= ~HOSTAGE_GONE;
+ status |= HOSTAGES_ALL_GONE;
+ }
+
+ return status;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the nearest visible free hostage
+ * Since we can actually see any hostage we return, we know its actual position
+ */
+CHostage *CSGameState::GetNearestVisibleFreeHostage( void ) const
+{
+ CHostage *close = NULL;
+ float closeRangeSq = 999999999.9f;
+ float rangeSq;
+
+ Vector pos;
+ Vector myOrigin = GetCentroid( m_owner );
+
+ for( int i=0; i<m_hostageCount; ++i )
+ {
+ const HostageInfo *info = &m_hostage[i];
+
+ if ( !info->hostage )
+ continue;
+
+ // if the hostage is dead or rescued, its not free
+ if (!info->hostage->IsAlive())
+ continue;
+
+ // if this hostage is following someone, its not free
+ if (info->hostage->IsFollowingSomeone())
+ continue;
+
+ /// @todo Use travel distance here
+ pos = info->hostage->GetAbsOrigin();
+ rangeSq = (pos - myOrigin).LengthSqr();
+
+ if (rangeSq < closeRangeSq)
+ {
+ if (!m_owner->IsVisible( pos ))
+ continue;
+
+ close = info->hostage;
+ closeRangeSq = rangeSq;
+ }
+ }
+
+ return close;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if there are no free hostages
+ */
+bool CSGameState::AreAllHostagesBeingRescued( void ) const
+{
+ // if the hostages have all been rescued, they are not being rescued any longer
+ if (m_allHostagesRescued)
+ return false;
+
+ bool isAllDead = true;
+
+ for( int i=0; i<m_hostageCount; ++i )
+ {
+ const HostageInfo *info = &m_hostage[i];
+
+ if (m_owner->GetTeamNumber() == TEAM_CT)
+ {
+ // CT's have perfect knowledge via their radar
+ if (info->hostage && info->hostage->IsValid())
+ {
+ if (!info->hostage->IsFollowingSomeone())
+ return false;
+
+ isAllDead = false;
+ }
+ }
+ else
+ {
+ if (info->isValid && info->isAlive)
+ return false;
+
+ if (info->isAlive)
+ isAllDead = false;
+ }
+ }
+
+ // if all of the remaining hostages are dead, they arent being rescued
+ if (isAllDead)
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * All hostages have been rescued or are dead
+ */
+bool CSGameState::AreAllHostagesGone( void ) const
+{
+ if (m_allHostagesRescued)
+ return true;
+
+ // do we know that all the hostages are dead
+ for( int i=0; i<m_hostageCount; ++i )
+ {
+ const HostageInfo *info = &m_hostage[i];
+
+ if (m_owner->GetTeamNumber() == TEAM_CT)
+ {
+ // CT's have perfect knowledge via their radar
+ if (info->hostage && info->hostage->IsAlive())
+ return false;
+ }
+ else
+ {
+ if (info->isValid && info->isAlive)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Someone told us all the hostages are gone
+ */
+void CSGameState::AllHostagesGone( void )
+{
+ for( int i=0; i<m_hostageCount; ++i )
+ m_hostage[i].isValid = false;
+}
+
diff --git a/game/server/cstrike/bot/cs_gamestate.h b/game/server/cstrike/bot/cs_gamestate.h
new file mode 100644
index 0000000..79bb8d2
--- /dev/null
+++ b/game/server/cstrike/bot/cs_gamestate.h
@@ -0,0 +1,151 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#ifndef _GAME_STATE_H_
+#define _GAME_STATE_H_
+
+
+#include "bot_util.h"
+
+
+class CHostage;
+class CCSBot;
+
+/**
+ * This class represents the game state as known by a particular bot
+ */
+class CSGameState
+{
+public:
+ CSGameState( CCSBot *owner );
+
+ void Reset( void );
+
+ // Event handling
+ void OnHostageRescuedAll( IGameEvent *event );
+ void OnRoundEnd( IGameEvent *event );
+ void OnRoundStart( IGameEvent *event );
+ void OnBombPlanted( IGameEvent *event );
+ void OnBombDefused( IGameEvent *event );
+ void OnBombExploded( IGameEvent *event );
+
+ bool IsRoundOver( void ) const; ///< true if round has been won or lost (but not yet reset)
+
+ // bomb defuse scenario -----------------------------------------------------------------------------
+
+ enum BombState
+ {
+ MOVING, ///< being carried by a Terrorist
+ LOOSE, ///< loose on the ground somewhere
+ PLANTED, ///< planted and ticking
+ DEFUSED, ///< the bomb has been defused
+ EXPLODED ///< the bomb has exploded
+ };
+
+ bool IsBombMoving( void ) const { return (m_bombState == MOVING); }
+ bool IsBombLoose( void ) const { return (m_bombState == LOOSE); }
+ bool IsBombPlanted( void ) const { return (m_bombState == PLANTED); }
+ bool IsBombDefused( void ) const { return (m_bombState == DEFUSED); }
+ bool IsBombExploded( void ) const { return (m_bombState == EXPLODED); }
+
+ void UpdateLooseBomb( const Vector &pos ); ///< we see the loose bomb
+ float TimeSinceLastSawLooseBomb( void ) const; ///< how long has is been since we saw the loose bomb
+ bool IsLooseBombLocationKnown( void ) const; ///< do we know where the loose bomb is
+
+ void UpdateBomber( const Vector &pos ); ///< we see the bomber
+ float TimeSinceLastSawBomber( void ) const; ///< how long has is been since we saw the bomber
+
+ void UpdatePlantedBomb( const Vector &pos ); ///< we see the planted bomb
+ bool IsPlantedBombLocationKnown( void ) const; ///< do we know where the bomb was planted
+ void MarkBombsiteAsPlanted( int zoneIndex ); ///< mark bombsite as the location of the planted bomb
+
+ enum { UNKNOWN = -1 };
+ int GetPlantedBombsite( void ) const; ///< return the zone index of the planted bombsite, or UNKNOWN
+ bool IsAtPlantedBombsite( void ) const; ///< return true if we are currently in the bombsite where the bomb is planted
+
+ int GetNextBombsiteToSearch( void ); ///< return the zone index of the next bombsite to search
+ bool IsBombsiteClear( int zoneIndex ) const; ///< return true if given bombsite has been cleared
+ void ClearBombsite( int zoneIndex ); ///< mark bombsite as clear
+
+ const Vector *GetBombPosition( void ) const; ///< return where we think the bomb is, or NULL if we don't know
+
+ // hostage rescue scenario ------------------------------------------------------------------------
+ CHostage *GetNearestFreeHostage( Vector *knowPos = NULL ) const; ///< return the closest free hostage, and where we think it is (knowPos)
+ const Vector *GetRandomFreeHostagePosition( void ) const;
+ bool AreAllHostagesBeingRescued( void ) const; ///< return true if there are no free hostages
+ bool AreAllHostagesGone( void ) const; ///< all hostages have been rescued or are dead
+ void AllHostagesGone( void ); ///< someone told us all the hostages are gone
+ bool HaveSomeHostagesBeenTaken( void ) const ///< return true if one or more hostages have been moved by the CT's
+ {
+ return m_haveSomeHostagesBeenTaken;
+ }
+ void HostageWasTaken( void ) ///< someone told us a CT is talking to a hostage
+ {
+ m_haveSomeHostagesBeenTaken = true;
+ }
+
+ CHostage *GetNearestVisibleFreeHostage( void ) const;
+
+ enum ValidateStatusType
+ {
+ NO_CHANGE = 0x00,
+ HOSTAGE_DIED = 0x01,
+ HOSTAGE_GONE = 0x02,
+ HOSTAGES_ALL_GONE = 0x04
+ };
+ unsigned char ValidateHostagePositions( void ); ///< update our knowledge with what we currently see - returns bitflag events
+
+private:
+ CCSBot *m_owner; ///< who owns this gamestate
+
+ bool m_isRoundOver; ///< true if round is over, but no yet reset
+
+ // bomb defuse scenario ---------------------------------------------------------------------------
+ void SetBombState( BombState state );
+ BombState GetBombState( void ) const { return m_bombState; }
+
+ BombState m_bombState; ///< what we think the bomb is doing
+
+ IntervalTimer m_lastSawBomber;
+ Vector m_bomberPos;
+
+ IntervalTimer m_lastSawLooseBomb;
+ Vector m_looseBombPos;
+
+ bool m_isBombsiteClear[ CCSBotManager::MAX_ZONES ]; ///< corresponds to zone indices in CCSBotManager
+ int m_bombsiteSearchOrder[ CCSBotManager::MAX_ZONES ]; ///< randomized order of bombsites to search
+ int m_bombsiteCount;
+ int m_bombsiteSearchIndex; ///< the next step in the search
+
+ int m_plantedBombsite; ///< zone index of the bombsite where the planted bomb is
+
+ bool m_isPlantedBombPosKnown; ///< if true, we know the exact location of the bomb
+ Vector m_plantedBombPos;
+
+ // hostage rescue scenario ------------------------------------------------------------------------
+ struct HostageInfo
+ {
+ CHandle<CHostage> hostage;
+ Vector knownPos;
+ bool isValid;
+ bool isAlive;
+ bool isFree; ///< not being escorted by a CT
+ }
+ m_hostage[ MAX_HOSTAGES ];
+ int m_hostageCount; ///< number of hostages left in map
+ CountdownTimer m_validateInterval;
+
+ CBaseEntity *GetNearestHostage( void ) const; ///< return the closest live hostage
+ void InitializeHostageInfo( void ); ///< initialize our knowledge of the number and location of hostages
+
+ bool m_allHostagesRescued;
+ bool m_haveSomeHostagesBeenTaken; ///< true if a hostage has been moved by a CT (and we've seen it)
+};
+
+#endif // _GAME_STATE_
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();
+}
+
+
+
diff --git a/game/server/cstrike/cs_autobuy.cpp b/game/server/cstrike/cs_autobuy.cpp
new file mode 100644
index 0000000..e7ed336
--- /dev/null
+++ b/game/server/cstrike/cs_autobuy.cpp
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Data for Autobuy and Rebuy
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "cs_autobuy.h"
+
+// Weapon class information for each weapon including the class name and the buy command alias.
+AutoBuyInfoStruct g_autoBuyInfo[] =
+{
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "galil", "weapon_galil" }, // galil
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "ak47", "weapon_ak47" }, // ak47
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "scout", "weapon_scout" }, // scout
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "sg552", "weapon_sg552" }, // sg552
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "awp", "weapon_awp" }, // awp
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "g3sg1", "weapon_g3sg1" }, // g3sg1
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "famas", "weapon_famas" }, // famas
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "m4a1", "weapon_m4a1" }, // m4a1
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "aug", "weapon_aug" }, // aug
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "sg550", "weapon_sg550" }, // sg550
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "glock", "weapon_glock" }, // glock
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "usp", "weapon_usp" }, // usp
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "p228", "weapon_p228" }, // p228
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "deagle", "weapon_deagle" }, // deagle
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "elite", "weapon_elite" }, // elites
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "fn57", "weapon_fiveseven" }, // fn57
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "m3", "weapon_m3" }, // m3
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "xm1014", "weapon_xm1014" }, // xm1014
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mac10", "weapon_mac10" }, // mac10
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "tmp", "weapon_tmp" }, // tmp
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mp5navy", "weapon_mp5navy" }, // mp5navy
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "ump45", "weapon_ump45" }, // ump45
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "p90", "weapon_p90" }, // p90
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), "m249", "weapon_m249" }, // m249
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_AMMO), "primammo", "primammo" }, // primammo
+ { (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_AMMO), "secammo", "secammo" }, // secmmo
+ { (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vest", "item_kevlar" }, // vest
+ { (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vesthelm", "item_assaultsuit" }, // vesthelm
+ { (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "flashbang", "weapon_flashbang" }, // flash
+ { (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "hegrenade", "weapon_hegrenade" }, // hegren
+ { (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "smokegrenade", "weapon_smokegrenade" }, // sgren
+ { (AutoBuyClassType)(AUTOBUYCLASS_NIGHTVISION), "nvgs", "nvgs" }, // nvgs
+ { (AutoBuyClassType)(AUTOBUYCLASS_DEFUSER), "defuser", "defuser" }, // defuser
+ { (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHIELD), "shield", "shield" }, // shield
+
+ { (AutoBuyClassType)0, "", "" } // last one, must be at end.
+};
diff --git a/game/server/cstrike/cs_autobuy.h b/game/server/cstrike/cs_autobuy.h
new file mode 100644
index 0000000..9fc8346
--- /dev/null
+++ b/game/server/cstrike/cs_autobuy.h
@@ -0,0 +1,53 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Headers and defines for Autobuy and Rebuy
+//
+//=============================================================================//
+
+/**
+ * Weapon classes as used by the AutoBuy
+ * Has to be different that the previous ones because these are bitmasked values as a weapon can be from
+ * more than one class. This also includes all the classes of equipment that a player can buy.
+ */
+enum AutoBuyClassType
+{
+ AUTOBUYCLASS_PRIMARY = 1,
+ AUTOBUYCLASS_SECONDARY = 2,
+ AUTOBUYCLASS_AMMO = 4,
+ AUTOBUYCLASS_ARMOR = 8,
+ AUTOBUYCLASS_DEFUSER = 16,
+ AUTOBUYCLASS_PISTOL = 32,
+ AUTOBUYCLASS_SMG = 64,
+ AUTOBUYCLASS_RIFLE = 128,
+ AUTOBUYCLASS_SNIPERRIFLE = 256,
+ AUTOBUYCLASS_SHOTGUN = 512,
+ AUTOBUYCLASS_MACHINEGUN = 1024,
+ AUTOBUYCLASS_GRENADE = 2048,
+ AUTOBUYCLASS_NIGHTVISION = 4096,
+ AUTOBUYCLASS_SHIELD = 8192,
+};
+
+struct AutoBuyInfoStruct
+{
+ AutoBuyClassType m_class;
+ const char *m_command;
+ const char *m_classname;
+};
+
+struct RebuyStruct
+{
+ char m_szPrimaryWeapon[64]; //"weapon_" string of the primary weapon
+ char m_szSecondaryWeapon[64]; //"weapon_" string of the secondary weapon
+
+ int m_primaryAmmo; // number of rounds the player had (not including rounds in the gun)
+ int m_secondaryAmmo; // number of rounds the player had (not including rounds in the gun)
+ int m_heGrenade; // number of grenades to buy
+ int m_flashbang; // number of grenades to buy
+ int m_smokeGrenade; // number of grenades to buy
+ int m_armor; // 0, 1, or 2 (0 = none, 1 = vest, 2 = vest + helmet)
+
+ bool m_defuser; // do we want a defuser
+ bool m_nightVision; // do we want night vision
+};
+
+extern AutoBuyInfoStruct g_autoBuyInfo[];
diff --git a/game/server/cstrike/cs_bot_temp.cpp b/game/server/cstrike/cs_bot_temp.cpp
new file mode 100644
index 0000000..b154911
--- /dev/null
+++ b/game/server/cstrike/cs_bot_temp.cpp
@@ -0,0 +1,554 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Basic BOT handling.
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "cs_player.h"
+#include "in_buttons.h"
+#include "movehelper_server.h"
+#include "team.h"
+#include "cs_gamerules.h"
+#include "client.h"
+
+
+void Bot_Think( CCSPlayer *pBot );
+
+ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
+ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
+ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
+ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
+ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
+static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
+
+static int BotNumber = 1;
+static int g_iNextBotTeam = -1;
+
+
+typedef struct
+{
+ bool backwards;
+
+ float nextturntime;
+ bool lastturntoright;
+
+ float nextstrafetime;
+ float sidemove;
+
+ QAngle forwardAngle;
+ QAngle lastAngles;
+
+ int m_WantedTeam;
+ float m_flJoinTeamTime;
+
+ bool m_bTempBot; // Is this slot a dump temp bot or a real bot?
+} botdata_t;
+
+static botdata_t g_BotData[ MAX_PLAYERS ];
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a new Bot and put it in the game.
+// Output : Pointer to the new Bot, or NULL if there's no free clients.
+//-----------------------------------------------------------------------------
+CBasePlayer *BotPutInServer( bool bFrozen, int iTeam )
+{
+ g_iNextBotTeam = iTeam;
+
+ char botname[ 64 ];
+ Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
+
+ edict_t *pEdict = engine->CreateFakeClient( botname );
+
+ if (!pEdict)
+ {
+ Msg( "Failed to create Bot.\n");
+ return NULL;
+ }
+
+ // Allocate a CBasePlayer for the bot, and call spawn
+ //ClientPutInServer( pEdict, botname );
+ //ClientActive( pEdict, false );
+
+ CCSPlayer *pPlayer = ((CCSPlayer *)CBaseEntity::Instance( pEdict ));
+ pPlayer->ClearFlags();
+ pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
+
+ if ( bFrozen )
+ pPlayer->AddEFlags( EFL_BOT_FROZEN );
+
+ if ( iTeam == -1 )
+ iTeam = ( pPlayer->entindex() & 1 ) ? TEAM_TERRORIST : TEAM_CT;
+
+ botdata_t *pData = &g_BotData[pPlayer->entindex()-1];
+ pData->m_WantedTeam = iTeam;
+ pData->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
+ pData->m_bTempBot = true;
+
+ BotNumber++;
+ return pPlayer;
+}
+
+bool IsTempBot( CBaseEntity *pEnt )
+{
+ if ( !pEnt )
+ return false;
+
+ if ( !(pEnt->GetFlags() & FL_FAKECLIENT) )
+ return false;
+
+ int i = pEnt->entindex();
+ if ( i >= 1 && i < MAX_PLAYERS )
+ return g_BotData[i-1].m_bTempBot;
+ else
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run through all the Bots in the game and let them think.
+//-----------------------------------------------------------------------------
+void Bot_RunAll( void )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( IsTempBot( pPlayer ) )
+ {
+ Bot_Think( pPlayer );
+ }
+ }
+}
+
+bool RunMimicCommand( CUserCmd& cmd )
+{
+ if ( bot_mimic.GetInt() <= 0 )
+ return false;
+
+ if ( bot_mimic.GetInt() > gpGlobals->maxClients )
+ return false;
+
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
+ if ( !pPlayer )
+ return false;
+
+ if ( !pPlayer->GetLastUserCommand() )
+ return false;
+
+ cmd = *pPlayer->GetLastUserCommand();
+ cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Simulates a single frame of movement for a player
+// Input : *fakeclient -
+// *viewangles -
+// forwardmove -
+// sidemove -
+// upmove -
+// buttons -
+// impulse -
+// msec -
+// Output : virtual void
+//-----------------------------------------------------------------------------
+static void RunPlayerMove( CCSPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
+{
+ if ( !fakeclient )
+ return;
+
+ CUserCmd cmd;
+
+ // Store off the globals.. they're gonna get whacked
+ float flOldFrametime = gpGlobals->frametime;
+ float flOldCurtime = gpGlobals->curtime;
+
+ float flTimeBase = gpGlobals->curtime;
+ fakeclient->SetTimeBase( flTimeBase );
+
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+
+ if ( !RunMimicCommand( cmd ) )
+ {
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+
+ MoveHelperServer()->SetHost( fakeclient );
+ fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
+
+ // save off the last good usercmd
+ fakeclient->SetLastUserCommand( cmd );
+
+ // Clear out any fixangle that has been set
+ fakeclient->pl.fixangle = FIXANGLE_NONE;
+
+ // Restore the globals..
+ gpGlobals->frametime = flOldFrametime;
+ gpGlobals->curtime = flOldCurtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run this Bot's AI for one frame.
+//-----------------------------------------------------------------------------
+void Bot_Think( CCSPlayer *pBot )
+{
+ // Make sure we stay being a bot
+ pBot->AddFlag( FL_FAKECLIENT );
+
+ botdata_t *botdata = &g_BotData[ pBot->entindex() - 1 ];
+
+ float forwardmove = 0.0;
+ float sidemove = botdata->sidemove;
+ float upmove = 0.0;
+ unsigned short buttons = 0;
+ byte impulse = 0;
+ float frametime = gpGlobals->frametime;
+
+ if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
+ {
+ pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam );
+ }
+ else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->PlayerClass() == CS_CLASS_NONE )
+ {
+ // If they're on a team but haven't picked a class, choose a random class..
+ pBot->HandleCommand_JoinClass( 0 );
+ }
+ else
+ {
+ QAngle vecViewAngles;
+ vecViewAngles = pBot->GetLocalAngles();
+
+ // Create some random values
+ if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
+ {
+ trace_t trace;
+
+ // Stop when shot
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
+ {
+ if ( pBot->m_iHealth == 100 )
+ {
+ forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
+ if ( botdata->sidemove != 0.0f )
+ {
+ forwardmove *= random->RandomFloat( 0.1, 1.0f );
+ }
+ }
+ else
+ {
+ forwardmove = 0;
+ }
+ }
+
+ // Only turn if I haven't been hurt
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
+ {
+ Vector vecEnd;
+ Vector forward;
+
+ QAngle angle;
+ float angledelta = 15.0;
+
+ int maxtries = (int)360.0/angledelta;
+
+ if ( botdata->lastturntoright )
+ {
+ angledelta = -angledelta;
+ }
+
+ angle = pBot->GetLocalAngles();
+
+ Vector vecSrc;
+ while ( --maxtries >= 0 )
+ {
+ AngleVectors( angle, &forward, NULL, NULL );
+
+ vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
+
+ vecEnd = vecSrc + forward * 10;
+
+ UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
+ MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
+
+ if ( trace.fraction == 1.0 )
+ {
+ //if ( gpGlobals->curtime < botdata->nextturntime )
+ //{
+ break;
+ //}
+ }
+
+ angle.y += angledelta;
+
+ if ( angle.y > 180 )
+ angle.y -= 360;
+ else if ( angle.y < -180 )
+ angle.y += 360;
+
+ botdata->nextturntime = gpGlobals->curtime + 2.0;
+ botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
+
+ botdata->forwardAngle = angle;
+ botdata->lastAngles = angle;
+
+ }
+
+
+ /*
+ if ( gpGlobals->curtime >= botdata->nextstrafetime )
+ {
+ botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
+
+ if ( random->RandomInt( 0, 5 ) == 0 )
+ {
+ botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
+ }
+ else
+ {
+ botdata->sidemove = 0;
+ }
+ sidemove = botdata->sidemove;
+
+ if ( random->RandomInt( 0, 20 ) == 0 )
+ {
+ botdata->backwards = true;
+ }
+ else
+ {
+ botdata->backwards = false;
+ }
+ }
+ */
+
+ pBot->SetLocalAngles( angle );
+ vecViewAngles = angle;
+ }
+
+ // If bots are being forced to fire a weapon, see if I have it
+ else if ( bot_forcefireweapon.GetString() )
+ {
+ CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
+ if ( pWeapon )
+ {
+ // Switch to it if we don't have it out
+ CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
+
+ // Switch?
+ if ( pActiveWeapon != pWeapon )
+ {
+ pBot->Weapon_Switch( pWeapon );
+ }
+ else
+ {
+ // Start firing
+ // Some weapons require releases, so randomise firing
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() )
+ {
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+ }
+ else
+ {
+ // Wait for Reinforcement wave
+ if ( !pBot->IsAlive() )
+ {
+ // Try hitting my buttons occasionally
+ if ( random->RandomInt( 0, 100 ) > 80 )
+ {
+ // Respawn the bot
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ buttons |= IN_JUMP;
+ }
+ else
+ {
+ buttons = 0;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() >= 2 )
+ {
+
+ QAngle angOffset = RandomAngle( -1, 1 );
+
+ botdata->lastAngles += angOffset;
+
+ for ( int i = 0 ; i < 2; i++ )
+ {
+ if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
+ {
+ if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
+ }
+ else
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
+ }
+ }
+ }
+
+ botdata->lastAngles[ 2 ] = 0;
+
+ pBot->SetLocalAngles( botdata->lastAngles );
+ }
+ }
+
+ pBot->SetPunchAngle( QAngle( 0, 0, 0 ) );
+ RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
+}
+
+
+
+// Handler for the "bot" command.
+CON_COMMAND_F( "bot_old", "Add a bot.", FCVAR_CHEAT )
+{
+ // Disable the CS bots, otherwise they'll interfere with the bot code here.
+ //extern bool g_bEnableCSBots;
+ //g_bEnableCSBots = false;
+
+ CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
+
+ // The bot command uses switches like command-line switches.
+ // -count <count> tells how many bots to spawn.
+ // -team <index> selects the bot's team. Default is -1 which chooses randomly.
+ // Note: if you do -team !, then it
+ // -class <index> selects the bot's class. Default is -1 which chooses randomly.
+ // -frozen prevents the bots from running around when they spawn in.
+
+ int count = args.FindArgInt( "-count", 1 );
+ count = clamp( count, 1, 16 );
+
+ int iTeam = -1;
+ const char *pVal = args.FindArg( "-team" );
+ if ( pVal )
+ {
+ if ( pVal[0] == '!' )
+ {
+ if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
+ iTeam = TEAM_CT;
+ else
+ iTeam = TEAM_TERRORIST;
+ }
+ else if ( pVal[0] == 't' || pVal[0] == 'T' )
+ {
+ iTeam = TEAM_TERRORIST;
+ }
+ else if ( pVal[0] == 'c' || pVal[0] == 'C' )
+ {
+ iTeam = TEAM_CT;
+ }
+ else
+ {
+ iTeam = atoi( pVal );
+ if ( iTeam == 1 )
+ iTeam = TEAM_TERRORIST;
+ else
+ iTeam = TEAM_CT;
+ }
+ }
+
+ // Look at -frozen.
+ bool bFrozen = !!args.FindArg( "-frozen" );
+
+ // Ok, spawn all the bots.
+ while ( --count >= 0 )
+ {
+ extern CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
+ BotPutInServer( bFrozen, iTeam );
+ }
+}
+
+
+// Handle the "PossessBot" command.
+void PossessBot_f( const CCommand &args )
+{
+ CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
+ if ( !pPlayer )
+ return;
+
+ // Put the local player in control of this bot.
+ if ( args.ArgC() != 2 )
+ {
+ Warning( "PossessBot <client index>\n" );
+ return;
+ }
+
+ int iBotClient = atoi( args[1] );
+ int iBotEnt = iBotClient + 1;
+
+ if ( iBotClient < 0 ||
+ iBotClient >= gpGlobals->maxClients ||
+ pPlayer->entindex() == iBotEnt )
+ {
+ Warning( "PossessBot <client index>\n" );
+ }
+ else
+ {
+ edict_t *pPlayerData = pPlayer->edict();
+ edict_t *pBotData = engine->PEntityOfEntIndex( iBotEnt );
+ if ( pBotData && pBotData )
+ {
+ // SWAP EDICTS
+
+ // Backup things we don't want to swap.
+ edict_t oldPlayerData = *pPlayerData;
+ edict_t oldBotData = *pBotData;
+
+ // Swap edicts.
+ edict_t tmp = *pPlayerData;
+ *pPlayerData = *pBotData;
+ *pBotData = tmp;
+
+ // Restore things we didn't want to swap.
+ //pPlayerData->m_EntitiesTouched = oldPlayerData.m_EntitiesTouched;
+ //pBotData->m_EntitiesTouched = oldBotData.m_EntitiesTouched;
+
+ CBaseEntity *pPlayerBaseEnt = CBaseEntity::Instance( pPlayerData );
+ CBaseEntity *pBotBaseEnt = CBaseEntity::Instance( pBotData );
+
+ // Make the other a bot and make the player not a bot.
+ pPlayerBaseEnt->RemoveFlag( FL_FAKECLIENT );
+ pBotBaseEnt->AddFlag( FL_FAKECLIENT );
+
+
+ // Point the CBaseEntities at the right players.
+ pPlayerBaseEnt->NetworkProp()->SetEdict( pPlayerData );
+ pBotBaseEnt->NetworkProp()->SetEdict( pBotData );
+
+ // Freeze the bot.
+ pBotBaseEnt->AddEFlags( EFL_BOT_FROZEN );
+ }
+ }
+}
+
+
+ConCommand cc_PossessBot( "PossessBot", PossessBot_f, "Toggle. Possess a bot.\n\tArguments: <bot client number>", FCVAR_CHEAT );
+
diff --git a/game/server/cstrike/cs_bot_temp.h b/game/server/cstrike/cs_bot_temp.h
new file mode 100644
index 0000000..b7dfdb5
--- /dev/null
+++ b/game/server/cstrike/cs_bot_temp.h
@@ -0,0 +1,17 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef CS_BOT_TEMP_H
+#define CS_BOT_TEMP_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+extern ConVar bot_mimic;
+
+
+#endif // CS_BOT_TEMP_H
diff --git a/game/server/cstrike/cs_client.cpp b/game/server/cstrike/cs_client.cpp
new file mode 100644
index 0000000..b7f025e
--- /dev/null
+++ b/game/server/cstrike/cs_client.cpp
@@ -0,0 +1,205 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== tf_client.cpp ========================================================
+
+ HL2 client/server game specific stuff
+
+*/
+
+#include "cbase.h"
+#include "player.h"
+#include "gamerules.h"
+#include "entitylist.h"
+#include "physics.h"
+#include "game.h"
+#include "ai_network.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "shake.h"
+#include "player_resource.h"
+#include "engine/IEngineSound.h"
+#include "cs_player.h"
+#include "cs_gamerules.h"
+#include "cs_bot.h"
+#include "tier0/vprof.h"
+#include "teamplayroundbased_gamerules.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
+
+extern bool g_fGameOver;
+
+
+void FinishClientPutInServer( CCSPlayer *pPlayer )
+{
+ pPlayer->InitialSpawn();
+ pPlayer->Spawn();
+
+ if (!pPlayer->IsBot())
+ {
+ // When the player first joins the server, they
+ pPlayer->m_iNumSpawns = 0;
+ pPlayer->m_takedamage = DAMAGE_NO;
+ pPlayer->pl.deadflag = true;
+ pPlayer->m_lifeState = LIFE_DEAD;
+ pPlayer->AddEffects( EF_NODRAW );
+ pPlayer->ChangeTeam( TEAM_UNASSIGNED );
+ pPlayer->SetThink( NULL );
+ pPlayer->AddAccount( CSGameRules()->GetStartMoney() );
+
+ // Move them to the first intro camera.
+ pPlayer->MoveToNextIntroCamera();
+ pPlayer->SetMoveType( MOVETYPE_NONE );
+ }
+
+
+ char sName[128];
+ Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
+
+ // First parse the name and remove any %'s
+ for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
+ {
+ // Replace it with a space
+ if ( *pApersand == '%' )
+ *pApersand = ' ';
+ }
+
+ // notify other clients of player joining the game
+ UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
+}
+
+/*
+===========
+ClientPutInServer
+
+called each time a player is spawned into the game
+============
+*/
+void ClientPutInServer( edict_t *pEdict, const char *playername )
+{
+ // Allocate a CBaseTFPlayer for pev, and call spawn
+ CCSPlayer *pPlayer = CCSPlayer::CreatePlayer( "player", pEdict );
+
+ pPlayer->SetPlayerName( playername );
+}
+
+
+void ClientActive( edict_t *pEdict, bool bLoadGame )
+{
+ // Can't load games in CS!
+ Assert( !bLoadGame );
+
+ CCSPlayer *pPlayer = ToCSPlayer( CBaseEntity::Instance( pEdict ) );
+ FinishClientPutInServer( pPlayer );
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ // send the 4 end of match conditions. long frag limit, long max rounds, long rounds needed won, and long time
+ UserMessageBegin( user, "MatchEndConditions" );
+ WRITE_LONG( fraglimit.GetInt() );
+ WRITE_LONG( mp_maxrounds.GetInt() );
+ WRITE_LONG( mp_winlimit.GetInt() );
+ WRITE_LONG( mp_timelimit.GetInt() );
+ MessageEnd();
+}
+
+
+/*
+===============
+const char *GetGameDescription()
+
+Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
+===============
+*/
+const char *GetGameDescription()
+{
+ if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
+ return g_pGameRules->GetGameDescription();
+ else
+ return "Counter-Strike: Source";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache game-specific models & sounds
+//-----------------------------------------------------------------------------
+void ClientGamePrecache( void )
+{
+ // Materials used by the client effects
+ CBaseEntity::PrecacheModel( "sprites/white.vmt" );
+ CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" );
+
+ // Legacy temp ents sounds
+ CBaseEntity::PrecacheScriptSound( "Bounce.PistolShell" );
+ CBaseEntity::PrecacheScriptSound( "Bounce.RifleShell" );
+ CBaseEntity::PrecacheScriptSound( "Bounce.ShotgunShell" );
+
+// Moved to pure_server_minimal.txt
+// // Flashbang-related files
+// engine->ForceExactFile( "sprites/white.vmt" );
+// engine->ForceExactFile( "sprites/white.vtf" );
+// engine->ForceExactFile( "vgui/white.vmt" );
+// engine->ForceExactFile( "vgui/white.vtf" );
+// engine->ForceExactFile( "effects/flashbang.vmt" );
+// engine->ForceExactFile( "effects/flashbang_white.vmt" );
+//
+// // Smoke grenade-related files
+// engine->ForceExactFile( "particle/particle_smokegrenade1.vmt" );
+// engine->ForceExactFile( "particle/particle_smokegrenade.vtf" );
+//
+// // Sniper scope
+// engine->ForceExactFile( "sprites/scope_arc.vmt" );
+// engine->ForceExactFile( "sprites/scope_arc.vtf" );
+//
+// // DSP presets - don't want people avoiding the deafening + ear ring
+// engine->ForceExactFile( "scripts/dsp_presets.txt" );
+}
+
+
+// called by ClientKill and DeadThink
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
+{
+ if (gpGlobals->coop || gpGlobals->deathmatch)
+ {
+ if ( fCopyCorpse )
+ {
+ // make a copy of the dead body for appearances sake
+ dynamic_cast< CBasePlayer* >( pEdict )->CreateCorpse();
+ }
+
+ // respawn player
+ pEdict->Spawn();
+ }
+ else
+ { // restart the entire server
+ engine->ServerCommand("reload\n");
+ }
+}
+
+void GameStartFrame( void )
+{
+ VPROF( "GameStartFrame" );
+
+ if ( g_fGameOver )
+ return;
+
+ gpGlobals->teamplay = teamplay.GetInt() ? true : false;
+}
+
+//=========================================================
+// instantiate the proper game rules object
+//=========================================================
+void InstallGameRules()
+{
+ CreateGameRulesObject( "CCSGameRules" );
+}
diff --git a/game/server/cstrike/cs_client.h b/game/server/cstrike/cs_client.h
new file mode 100644
index 0000000..a56f2c4
--- /dev/null
+++ b/game/server/cstrike/cs_client.h
@@ -0,0 +1,19 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef CS_CLIENT_H
+#define CS_CLIENT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
+
+void FinishClientPutInServer( CCSPlayer *pPlayer );
+
+
+#endif // CS_CLIENT_H
diff --git a/game/server/cstrike/cs_eventlog.cpp b/game/server/cstrike/cs_eventlog.cpp
new file mode 100644
index 0000000..095f6f2
--- /dev/null
+++ b/game/server/cstrike/cs_eventlog.cpp
@@ -0,0 +1,321 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "../EventLog.h"
+#include "team.h"
+#include "cs_gamerules.h"
+#include "KeyValues.h"
+
+#define LOG_DETAIL_ENEMY_ATTACKS 0x01
+#define LOG_DETAIL_TEAMMATE_ATTACKS 0x02
+
+ConVar mp_logdetail( "mp_logdetail", "0", FCVAR_NONE, "Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)", true, 0.0f, true, 3.0f );
+
+class CCSEventLog : public CEventLog
+{
+private:
+ typedef CEventLog BaseClass;
+
+public:
+ bool PrintEvent( IGameEvent *event ) // override virtual function
+ {
+ if ( !PrintCStrikeEvent( event ) ) // allow CS to override logging
+ {
+ return BaseClass::PrintEvent( event );
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ bool Init()
+ {
+ BaseClass::Init();
+
+ // listen to CS events
+ ListenForGameEvent( "round_end" );
+ ListenForGameEvent( "round_start" );
+ ListenForGameEvent( "bomb_pickup" );
+ ListenForGameEvent( "bomb_begindefuse" );
+ ListenForGameEvent( "bomb_dropped" );
+ ListenForGameEvent( "bomb_defused" );
+ ListenForGameEvent( "bomb_planted" );
+ ListenForGameEvent( "hostage_rescued" );
+ ListenForGameEvent( "hostage_killed" );
+ ListenForGameEvent( "hostage_follows" );
+ ListenForGameEvent( "player_hurt" );
+
+ return true;
+ }
+
+protected:
+
+ bool PrintCStrikeEvent( IGameEvent *event ) // print Mod specific logs
+ {
+ const char *eventName = event->GetName();
+
+ // messages that don't have a user associated to them
+ if ( !Q_strncmp( eventName, "round_end", Q_strlen("round_end") ) )
+ {
+ const int winner = event->GetInt( "winner" );
+ const int reason = event->GetInt( "reason" );
+ const char *msg = event->GetString( "message" );
+ msg++; // remove the '#' char
+
+ switch( reason )
+ {
+ case Game_Commencing:
+ UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
+ return true;
+ break;
+ }
+
+ CTeam *ct = GetGlobalTeam( TEAM_CT );
+ CTeam *ter = GetGlobalTeam( TEAM_TERRORIST );
+ Assert( ct && ter );
+
+ switch ( winner )
+ {
+ case WINNER_CT:
+ UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ct->GetName(), msg, ct->GetScore(), ter->GetScore() );
+ break;
+ case WINNER_TER:
+ UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ter->GetName(), msg, ct->GetScore(), ter->GetScore() );
+ break;
+ case WINNER_DRAW:
+ default:
+ UTIL_LogPrintf( "World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", msg, ct->GetScore(), ter->GetScore() );
+ break;
+ }
+
+ UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", ct->GetScore(), ct->GetNumPlayers() );
+ UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", ter->GetScore(), ter->GetNumPlayers() );
+
+ UTIL_LogPrintf("World triggered \"Round_End\"\n");
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "server_", strlen("server_")) )
+ {
+ return false; // ignore server_ messages
+ }
+
+ const int userid = event->GetInt( "userid" );
+ CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
+ if ( !pPlayer )
+ {
+ return false;
+ }
+
+ if ( FStrEq( eventName, "player_hurt" ) )
+ {
+ const int attackerid = event->GetInt("attacker" );
+ const char *weapon = event->GetString( "weapon" );
+ CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
+ if ( !pAttacker )
+ {
+ return false;
+ }
+
+ bool isTeamAttack = ( (pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() ) && (pPlayer != pAttacker) );
+ int detail = mp_logdetail.GetInt();
+ if ( ( isTeamAttack && ( detail & LOG_DETAIL_TEAMMATE_ATTACKS ) ) ||
+ ( !isTeamAttack && ( detail & LOG_DETAIL_ENEMY_ATTACKS ) ) )
+ {
+ int hitgroup = event->GetInt( "hitgroup" );
+ const char *hitgroupStr = "GENERIC";
+ switch ( hitgroup )
+ {
+ case HITGROUP_GENERIC:
+ hitgroupStr = "generic";
+ break;
+ case HITGROUP_HEAD:
+ hitgroupStr = "head";
+ break;
+ case HITGROUP_CHEST:
+ hitgroupStr = "chest";
+ break;
+ case HITGROUP_STOMACH:
+ hitgroupStr = "stomach";
+ break;
+ case HITGROUP_LEFTARM:
+ hitgroupStr = "left arm";
+ break;
+ case HITGROUP_RIGHTARM:
+ hitgroupStr = "right arm";
+ break;
+ case HITGROUP_LEFTLEG:
+ hitgroupStr = "left leg";
+ break;
+ case HITGROUP_RIGHTLEG:
+ hitgroupStr = "right leg";
+ break;
+ }
+
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" attacked \"%s<%i><%s><%s>\" with \"%s\" (damage \"%d\") (damage_armor \"%d\") (health \"%d\") (armor \"%d\") (hitgroup \"%s\")\n",
+ pAttacker->GetPlayerName(),
+ attackerid,
+ pAttacker->GetNetworkIDString(),
+ pAttacker->GetTeam()->GetName(),
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ pPlayer->GetTeam()->GetName(),
+ weapon,
+ event->GetInt( "dmg_health" ),
+ event->GetInt( "dmg_armor" ),
+ event->GetInt( "health" ),
+ event->GetInt( "armor" ),
+ hitgroupStr );
+ }
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "player_death", Q_strlen("player_death") ) )
+ {
+ const int attackerid = event->GetInt("attacker" );
+ const char *weapon = event->GetString( "weapon" );
+ const bool headShot = (event->GetInt( "headshot" ) == 1);
+ CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
+
+ if ( pPlayer == pAttacker )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ pPlayer->GetTeam()->GetName(),
+ weapon
+ );
+ }
+ else if ( pAttacker )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"%s\n",
+ pAttacker->GetPlayerName(),
+ attackerid,
+ pAttacker->GetNetworkIDString(),
+ pAttacker->GetTeam()->GetName(),
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ pPlayer->GetTeam()->GetName(),
+ weapon,
+ headShot ? " (headshot)":""
+ );
+ }
+ else
+ {
+ // killed by the world
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"world\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ pPlayer->GetTeam()->GetName()
+ );
+ }
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "round_start", Q_strlen("round_start") ) )
+ {
+ UTIL_LogPrintf("World triggered \"Round_Start\"\n");
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "hostage_follows", Q_strlen("hostage_follows") ) )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><CT>\" triggered \"Touched_A_Hostage\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "hostage_killed", Q_strlen("hostage_killed") ) )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_A_Hostage\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ pPlayer->GetTeam()->GetName()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "hostage_rescued", Q_strlen("hostage_rescued") ) )
+ {
+ UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Rescued_A_Hostage\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "bomb_planted", Q_strlen("bomb_planted") ) )
+ {
+ UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Planted_The_Bomb\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "bomb_defused", Q_strlen("bomb_defused") ) )
+ {
+ UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Defused_The_Bomb\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "bomb_dropped", Q_strlen("bomb_dropped") ) )
+ {
+ UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Dropped_The_Bomb\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "bomb_begindefuse", Q_strlen("bomb_begindefuse") ) )
+ {
+ const bool haskit = (event->GetInt( "haskit" ) == 1);
+ UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"%s\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString(),
+ haskit ? "Begin_Bomb_Defuse_With_Kit" : "Begin_Bomb_Defuse_Without_Kit"
+ );
+ return true;
+ }
+ else if ( !Q_strncmp( eventName, "bomb_pickup", Q_strlen("bomb_pickup") ) )
+ {
+ UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Got_The_Bomb\"\n",
+ pPlayer->GetPlayerName(),
+ userid,
+ pPlayer->GetNetworkIDString()
+ );
+ return true;
+ }
+
+// unused events:
+//hostage_hurt
+//bomb_exploded
+
+ return false;
+ }
+
+};
+
+CCSEventLog g_CSEventLog;
+
+//-----------------------------------------------------------------------------
+// Singleton access
+//-----------------------------------------------------------------------------
+IGameSystem* GameLogSystem()
+{
+ return &g_CSEventLog;
+}
+
diff --git a/game/server/cstrike/cs_gameinterface.cpp b/game/server/cstrike/cs_gameinterface.cpp
new file mode 100644
index 0000000..869dc64
--- /dev/null
+++ b/game/server/cstrike/cs_gameinterface.cpp
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "gameinterface.h"
+#include "mapentities.h"
+#include "cs_gameinterface.h"
+#include "AI_ResponseSystem.h"
+
+// -------------------------------------------------------------------------------------------- //
+// Mod-specific CServerGameClients implementation.
+// -------------------------------------------------------------------------------------------- //
+
+void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
+{
+ minplayers = 1; // allow single player for the test maps (but we default to multi)
+ maxplayers = MAX_PLAYERS;
+
+ defaultMaxPlayers = 32; // Default to 32 players unless they change it.
+}
+
+
+// -------------------------------------------------------------------------------------------- //
+// Mod-specific CServerGameDLL implementation.
+// -------------------------------------------------------------------------------------------- //
+
+void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
+{
+ if ( Q_strcmp( STRING(gpGlobals->mapname), "cs_" ) )
+ {
+ // don't precache AI responses (hostages) if it's not a hostage rescure map
+ extern IResponseSystem *g_pResponseSystem;
+ g_pResponseSystem->PrecacheResponses( false );
+ }
+}
+
+
diff --git a/game/server/cstrike/cs_gameinterface.h b/game/server/cstrike/cs_gameinterface.h
new file mode 100644
index 0000000..df3d028
--- /dev/null
+++ b/game/server/cstrike/cs_gameinterface.h
@@ -0,0 +1,14 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef CS_GAMEINTERFACE_H
+#define CS_GAMEINTERFACE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#endif // CS_GAMEINTERFACE_H
diff --git a/game/server/cstrike/cs_gamestats.cpp b/game/server/cstrike/cs_gamestats.cpp
new file mode 100644
index 0000000..afb02a0
--- /dev/null
+++ b/game/server/cstrike/cs_gamestats.cpp
@@ -0,0 +1,1721 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: CS game stats
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Some tricky business here - we don't want to include the precompiled header for the statreader
+// and trying to #ifdef it out does funky things like ignoring the #endif. Define our header file
+// separately and include it based on the switch
+
+#include "cbase.h"
+
+#include <tier0/platform.h>
+#include "cs_gamerules.h"
+#include "cs_gamestats.h"
+#include "weapon_csbase.h"
+#include "props.h"
+#include "cs_achievement_constants.h"
+#include "../../shared/cstrike/weapon_c4.h"
+
+#include <time.h>
+#include "filesystem.h"
+#include "bot_util.h"
+#include "cdll_int.h"
+#include "steamworks_gamestats.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+float g_flGameStatsUpdateTime = 0.0f;
+short g_iTerroristVictories[CS_NUM_LEVELS];
+short g_iCounterTVictories[CS_NUM_LEVELS];
+short g_iWeaponPurchases[WEAPON_MAX];
+
+short g_iAutoBuyPurchases = 0;
+short g_iReBuyPurchases = 0;
+short g_iAutoBuyM4A1Purchases = 0;
+short g_iAutoBuyAK47Purchases = 0;
+short g_iAutoBuyFamasPurchases = 0;
+short g_iAutoBuyGalilPurchases = 0;
+short g_iAutoBuyVestHelmPurchases = 0;
+short g_iAutoBuyVestPurchases = 0;
+
+struct PropModelStats_t
+{
+ const char* szPropModelName;
+ CSStatType_t statType;
+} PropModelStatsTableInit[] =
+{
+ { "models/props/cs_office/computer_caseb.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/computer_monitor.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/phone.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/projector.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/TV_plasma.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/computer_keyboard.mdl", CSSTAT_PROPSBROKEN_OFFICEELECTRONICS },
+ { "models/props/cs_office/radio.mdl", CSSTAT_PROPSBROKEN_OFFICERADIO },
+ { "models/props/cs_office/trash_can.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
+ { "models/props/cs_office/file_box.mdl", CSSTAT_PROPSBROKEN_OFFICEJUNK },
+ { "models/props_junk/watermelon01.mdl", CSSTAT_PROPSBROKEN_ITALY_MELON },
+ // models/props/de_inferno/claypot01.mdl
+ // models/props/de_inferno/claypot02.mdl
+ // models/props/de_dust/grainbasket01c.mdl
+ // models/props_junk/wood_crate001a.mdl
+ // models/props/cs_office/file_box_p1.mdl
+};
+
+
+struct ServerStats_t
+{
+ int achievementId;
+ int statId;
+ int roundRequirement;
+ int matchRequirement;
+ const char* mapFilter;
+
+ bool IsMet(int roundStat, int matchStat)
+ {
+ return roundStat >= roundRequirement && matchStat >= matchRequirement;
+ }
+} ServerStatBasedAchievements[] =
+{
+ { CSBreakWindows, CSSTAT_NUM_BROKEN_WINDOWS, AchievementConsts::BreakWindowsInOfficeRound_Windows, 0, "cs_office" },
+ { CSBreakProps, CSSTAT_PROPSBROKEN_ALL, AchievementConsts::BreakPropsInRound_Props, 0, NULL },
+ { CSUnstoppableForce, CSSTAT_KILLS, AchievementConsts::UnstoppableForce_Kills, 0, NULL },
+ { CSHeadshotsInRound, CSSTAT_KILLS_HEADSHOT, AchievementConsts::HeadshotsInRound_Kills, 0, NULL },
+ { CSDominationOverkillsMatch, CSSTAT_DOMINATION_OVERKILLS, 0, 10, NULL },
+};
+
+//=============================================================================
+// HPE_BEGIN:
+// [Forrest] Allow nemesis/revenge to be turned off for a server
+//=============================================================================
+static void SvNoNemesisChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ if ( var.IsValid() && var.GetBool() )
+ {
+ // Clear all nemesis relationships.
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CCSPlayer *pTemp = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if ( pTemp )
+ {
+ pTemp->RemoveNemesisRelationships();
+ }
+ }
+ }
+}
+
+ConVar sv_nonemesis( "sv_nonemesis", "0", 0, "Disable nemesis and revenge.", SvNoNemesisChangeCallback );
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+int GetCSLevelIndex( const char *pLevelName )
+{
+ for ( int i = 0; MapName_StatId_Table[i].statWinsId != CSSTAT_UNDEFINED; i ++ )
+ {
+ if ( Q_strcmp( pLevelName, MapName_StatId_Table[i].szMapName ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+ConVar sv_debugroundstats( "sv_debugroundstats", "0", 0, "A temporary variable that will print extra information about stats upload which may be useful in debugging any problems." );
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Addition of CCS_GameStats class
+//=============================================================================
+
+CCSGameStats CCS_GameStats;
+CCSGameStats::StatContainerList_t* CCSGameStats::s_StatLists = new CCSGameStats::StatContainerList_t();
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : -
+//-----------------------------------------------------------------------------
+CCSGameStats::CCSGameStats()
+{
+ gamestats = this;
+ Clear();
+ m_fDisseminationTimerLow = m_fDisseminationTimerHigh = 0.0f;
+
+ // create table for mapping prop models to stats
+ for ( int i = 0; i < ARRAYSIZE(PropModelStatsTableInit); ++i)
+ {
+ m_PropStatTable.Insert(PropModelStatsTableInit[i].szPropModelName, PropModelStatsTableInit[i].statType);
+ }
+
+ m_numberOfRoundsForDirectAverages = 0;
+ m_numberOfTerroristEntriesForDirectAverages = 0;
+ m_numberOfCounterTerroristEntriesForDirectAverages = 0;
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+// Input : -
+//-----------------------------------------------------------------------------
+CCSGameStats::~CCSGameStats()
+{
+ Clear();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clear out game stats
+// Input : -
+//-----------------------------------------------------------------------------
+void CCSGameStats::Clear( void )
+{
+ m_PlayerSnipedPosition.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CCSGameStats::Init( void )
+{
+ ListenForGameEvent( "round_start" );
+ ListenForGameEvent( "round_end" );
+ ListenForGameEvent( "break_prop" );
+ ListenForGameEvent( "player_decal");
+ ListenForGameEvent( "hegrenade_detonate");
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::PostInit( void )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::LevelShutdownPreClearSteamAPIContext( void )
+{
+ // If we have any unsent round stats we better send them now or we'll lose them
+ UploadRoundStats();
+
+ GetSteamWorksSGameStatsUploader().EndSession();
+}
+
+extern double g_rowCommitTime;
+extern double g_rowWriteTime;
+
+void CCSGameStats::UploadRoundStats( void )
+{
+ CFastTimer totalTimer, purchasesAndDeathsStatTimer, weaponsStatBuildTimer, weaponsStatSubmitTimer, submitTimer, cleanupTimer;
+
+ // Prevent uploading empty data
+ if ( !m_bInRound )
+ return;
+
+ if ( sv_noroundstats.GetBool() )
+ {
+ if ( sv_debugroundstats.GetBool() )
+ {
+ Msg( "sv_noroundstats is enabled. Not uploading round stats.\n" );
+ }
+
+ m_MarketPurchases.PurgeAndDeleteElements();
+ m_WeaponData.PurgeAndDeleteElements();
+ m_DeathData.PurgeAndDeleteElements();
+ return;
+ }
+
+ m_bInRound = false;
+
+ KeyValues *pKV = new KeyValues( "basedata" );
+ if ( !pKV )
+ return;
+
+ totalTimer.Start();
+ purchasesAndDeathsStatTimer.Start();
+
+ const char *pzMapName = gpGlobals->mapname.ToCStr();
+ pKV->SetString( "MapID", pzMapName );
+
+ for ( int k=0 ; k < m_MarketPurchases.Count() ; ++k )
+ SubmitStat( m_MarketPurchases[ k ] );
+
+ for ( int k=0 ; k < m_DeathData.Count() ; ++k )
+ SubmitStat( m_DeathData[ k ] );
+
+ purchasesAndDeathsStatTimer.End();
+ weaponsStatBuildTimer.Start();
+
+ // Now add the weapon stats that HPE collected
+ for (int iWeapon = 0; iWeapon < WEAPON_MAX; ++iWeapon)
+ {
+ CCSWeaponInfo* pInfo = GetWeaponInfo( (CSWeaponID)iWeapon );
+ if ( !pInfo )
+ continue;
+
+ const char* pWeaponName = pInfo->szClassName;
+ if ( !pWeaponName || !pWeaponName[0] || ( m_weaponStats[iWeapon][0].shots == 0 && m_weaponStats[iWeapon][1].shots == 0 ) )
+ continue;
+
+
+ m_WeaponData.AddToTail( new SCSSWeaponData( pWeaponName, m_weaponStats[iWeapon][0] ) );
+
+ // Now add the bot data if we're collecting detailled data
+ if ( GetSteamWorksSGameStatsUploader().IsCollectingDetails() )
+ {
+ char pWeaponNameModified[64];
+ V_snprintf( pWeaponNameModified, ARRAYSIZE(pWeaponNameModified), "%s_bot", pWeaponName );
+ m_WeaponData.AddToTail( new SCSSWeaponData( pWeaponNameModified, m_weaponStats[iWeapon][1] ) );
+ }
+ }
+
+ weaponsStatBuildTimer.End();
+ weaponsStatSubmitTimer.Start();
+
+ for ( int k=0 ; k < m_WeaponData.Count() ; ++k )
+ SubmitStat( m_WeaponData[ k ] );
+
+ weaponsStatSubmitTimer.End();
+
+ // Perform the actual submission
+ submitTimer.Start();
+ SubmitGameStats( pKV );
+ submitTimer.End();
+
+ int iPurchases, iDeathData, iWeaponData;
+ int listCount = s_StatLists->Count();
+ iPurchases = m_MarketPurchases.Count();
+ iDeathData = m_DeathData.Count();
+ iWeaponData = m_WeaponData.Count();
+
+ // Clear out the per round stats
+ cleanupTimer.Start();
+ m_MarketPurchases.Purge();
+ m_WeaponData.Purge();
+ m_DeathData.Purge();
+ pKV->deleteThis();
+ cleanupTimer.End();
+
+ totalTimer.End();
+
+ if ( sv_debugroundstats.GetBool() )
+ {
+ Msg( "**** ROUND STAT DEBUG ****\n" );
+ Msg( "UploadRoundStats completed. %.3f msec. Breakdown:\n a: %.3f msec\n b: %.3f msec\n c: %.3f msec\n d: %.3f msec\n e: %.3f msec\n objects: %d %d %d %d\n commit: %.3fms\n write: %.3fms.\n\n",
+ totalTimer.GetDuration().GetMillisecondsF(),
+ purchasesAndDeathsStatTimer.GetDuration().GetMillisecondsF(),
+ weaponsStatBuildTimer.GetDuration().GetMillisecondsF(),
+ weaponsStatSubmitTimer.GetDuration().GetMillisecondsF(),
+ submitTimer.GetDuration().GetMillisecondsF(),
+ cleanupTimer.GetDuration().GetMillisecondsF(),
+ iPurchases, iDeathData, iWeaponData, listCount,
+ g_rowCommitTime, g_rowWriteTime);
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND ( teststats, "Test command" )
+{
+ CFastTimer totalTimer;
+ double uploadTime = 0.0f;
+ g_rowCommitTime = 0.0f;
+ g_rowWriteTime = 0.0f;
+
+
+ for( int i = 0; i < 1000; i++ )
+ {
+ KeyValues *pKV = new KeyValues( "basedata" );
+ if ( !pKV )
+ return;
+
+ pKV->SetName( "foobartest" );
+ pKV->SetUint64( "test1", 1234 );
+ pKV->SetUint64( "test2", 1234 );
+ pKV->SetUint64( "test3", 1234 );
+ pKV->SetUint64( "test4", 1234 );
+ pKV->SetString( "test5", "TEST1234567890TEST1234567890TEST!");
+ /*pKV->SetString( "test6", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test7", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test8", "TEST1234567890TEST1234567890TEST!");
+ pKV->SetString( "test9", "TEST1234567890TEST1234567890TEST!");*/
+
+ totalTimer.Start();
+ GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKV, args.ArgC() == 1 );
+ totalTimer.End();
+
+ uploadTime += totalTimer.GetDuration().GetMillisecondsF();
+ }
+
+ Msg( "teststats took %.3f msec commit: %.3fms write: %.3fms.\n", uploadTime, g_rowCommitTime, g_rowWriteTime );
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::SubmitGameStats( KeyValues *pKV )
+{
+ int listCount = s_StatLists->Count();
+ for( int i=0; i < listCount; ++i )
+ {
+ // Create a master key value that has stats everybody should share (map name, session ID, etc)
+ (*s_StatLists)[i]->SendData(pKV);
+ (*s_StatLists)[i]->Clear();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pWeapon);
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] adding tracking for weapon used fun fact
+ //=============================================================================
+
+ if ( pCSPlayer )
+ {
+ // [dwenger] Update the player's tracking of which weapon type they fired
+ pCSPlayer->PlayerUsedFirearm( pWeapon );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ IncrementStat( pCSPlayer, CSSTAT_SHOTS_FIRED, 1 );
+ // Increment the individual weapon
+ if( pCSWeapon )
+ {
+ CSWeaponID weaponId = pCSWeapon->GetWeaponID();
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].shotStatId, 1 );
+ break;
+ }
+ }
+
+ int iType = pCSPlayer->IsBot();
+ ++m_weaponStats[weaponId][iType].shots;
+ }
+ }
+}
+
+void CCSGameStats::Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_SHOTS_HIT, 1 );
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ if ( pInflictor )
+ {
+ if ( pInflictor == pPlayer )
+ {
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->GetActiveWeapon());
+ if (pCSWeapon)
+ {
+ CSWeaponID weaponId = pCSWeapon->GetWeaponID();
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pCSPlayer, WeaponName_StatId_Table[i].hitStatId, 1 );
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+void CCSGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_DEATHS, 1 );
+}
+
+void CCSGameStats::Event_PlayerSprayedDecal( CCSPlayer* pPlayer )
+{
+ IncrementStat( pPlayer, CSSTAT_DECAL_SPRAYS, 1 );
+}
+
+void CCSGameStats::Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+ CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
+ bool victimZoomed = ( pCSPlayer->GetFOV() != pCSPlayer->GetDefaultFOV() );
+
+ if (victimZoomed)
+ {
+ IncrementStat(pAttacker, CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 1);
+ }
+
+
+ //Check for knife fight
+ if (pAttacker && pCSPlayer && pAttacker == info.GetInflictor() && pAttacker->GetTeamNumber() != pCSPlayer->GetTeamNumber())
+ {
+ CWeaponCSBase* attackerWeapon = pAttacker->GetActiveCSWeapon();
+ CWeaponCSBase* victimWeapon = pCSPlayer->GetActiveCSWeapon();
+
+ if (attackerWeapon && victimWeapon)
+ {
+ CSWeaponID attackerWeaponID = attackerWeapon->GetWeaponID();
+ CSWeaponID victimWeaponID = victimWeapon->GetWeaponID();
+
+ if (attackerWeaponID == WEAPON_KNIFE && victimWeaponID == WEAPON_KNIFE)
+ {
+ IncrementStat(pAttacker, CSSTAT_KILLS_KNIFE_FIGHT, 1);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombPlanted( CCSPlayer* pPlayer)
+{
+ IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_PLANTED, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombDefused( CCSPlayer* pPlayer)
+{
+
+ IncrementStat( pPlayer, CSSTAT_NUM_BOMBS_DEFUSED, 1 );
+ IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+ if( pPlayer && pPlayer->HasDefuser() )
+ {
+ IncrementStat( pPlayer, CSSTAT_BOMBS_DEFUSED_WITHKIT, 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increment terrorist team stat
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_BombExploded( CCSPlayer* pPlayer )
+{
+ IncrementStat( pPlayer, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_HostageRescued( CCSPlayer* pPlayer)
+{
+ IncrementStat( pPlayer, CSSTAT_NUM_HOSTAGES_RESCUED, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increment counter-terrorist team stat
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_AllHostagesRescued()
+{
+ IncrementTeamStat( TEAM_CT, CSSTAT_OBJECTIVES_COMPLETED, 1 );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_WindowShattered( CBasePlayer *pPlayer)
+{
+ Assert( pPlayer );
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+
+ IncrementStat( pCSPlayer, CSSTAT_NUM_BROKEN_WINDOWS, 1 );
+}
+
+
+void CCSGameStats::Event_BreakProp( CCSPlayer* pPlayer, CBreakableProp *pProp )
+{
+ if (!pPlayer)
+ return;
+
+ DevMsg("Player %s broke a %s (%i)\n", pPlayer->GetPlayerName(), pProp->GetModelName().ToCStr(), pProp->entindex());
+
+ int iIndex = m_PropStatTable.Find(pProp->GetModelName().ToCStr());
+ if (m_PropStatTable.IsValidIndex(iIndex))
+ {
+ IncrementStat(pPlayer, m_PropStatTable[iIndex], 1);
+ }
+ IncrementStat(pPlayer, CSSTAT_PROPSBROKEN_ALL, 1);
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::UpdatePlayerRoundStats(int winner)
+{
+ int mapIndex = GetCSLevelIndex(gpGlobals->mapname.ToCStr());
+ CSStatType_t mapStatWinIndex = CSSTAT_UNDEFINED, mapStatRoundIndex = CSSTAT_UNDEFINED;
+
+ if ( mapIndex != -1 )
+ {
+ mapStatWinIndex = MapName_StatId_Table[mapIndex].statWinsId;
+ mapStatRoundIndex = MapName_StatId_Table[mapIndex].statRoundsId;
+ }
+
+ // increment the team specific stats
+ IncrementTeamStat( winner, CSSTAT_ROUNDS_WON, 1 );
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementTeamStat( winner, mapStatWinIndex, 1 );
+ }
+ if ( CSGameRules()->IsPistolRound() )
+ {
+ IncrementTeamStat( winner, CSSTAT_PISTOLROUNDS_WON, 1 );
+ }
+ IncrementTeamStat( TEAM_TERRORIST, CSSTAT_ROUNDS_PLAYED, 1 );
+ IncrementTeamStat( TEAM_CT, CSSTAT_ROUNDS_PLAYED, 1 );
+ IncrementTeamStat( TEAM_TERRORIST, mapStatRoundIndex, 1 );
+ IncrementTeamStat( TEAM_CT, mapStatRoundIndex, 1 );
+
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected() )
+ {
+ if ( winner == TEAM_CT )
+ {
+ IncrementStat( pPlayer, CSSTAT_CT_ROUNDS_WON, 1, true );
+ }
+ else if ( winner == TEAM_TERRORIST )
+ {
+ IncrementStat( pPlayer, CSSTAT_T_ROUNDS_WON, 1, true );
+ }
+
+ if ( winner == TEAM_CT || winner == TEAM_TERRORIST )
+ {
+ // Increment the win stats if this player is on the winning team
+ if ( pPlayer->GetTeamNumber() == winner )
+ {
+ IncrementStat( pPlayer, CSSTAT_ROUNDS_WON, 1, true );
+
+ if ( CSGameRules()->IsPistolRound() )
+ {
+ IncrementStat( pPlayer, CSSTAT_PISTOLROUNDS_WON, 1, true );
+ }
+
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementStat( pPlayer, mapStatWinIndex, 1, true );
+ }
+ }
+
+ IncrementStat( pPlayer, CSSTAT_ROUNDS_PLAYED, 1 );
+ if ( mapStatWinIndex != CSSTAT_UNDEFINED )
+ {
+ IncrementStat( pPlayer, mapStatRoundIndex, 1, true );
+ }
+
+ // set the play time for the round
+ IncrementStat( pPlayer, CSSTAT_PLAYTIME, (int)CSGameRules()->GetRoundElapsedTime(), true );
+ }
+ }
+ }
+
+ // send a stats update to all players
+ for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ SendStatsToPlayer(pPlayer, CSSTAT_PRIORITY_ENDROUND);
+ }
+ }
+}
+
+void CCSGameStats::SendRollingStatsAveragesToAllPlayers()
+{
+ //The most stats we can send at once is the max message size minus the header, divided by the total size of each stat.
+ const int maxStatsPerMessage = (MAX_USER_MSG_DATA - sizeof(CSStatType_t)) / (3 * sizeof(float));
+
+ const int numMessagesNeeded = ((CSSTAT_MAX - CSSTAT_FIRST) / maxStatsPerMessage) + 1;
+
+ for (int batchIndex = 0 ; batchIndex < numMessagesNeeded ; ++batchIndex)
+ {
+ int firstStatInThisBatch = (batchIndex * maxStatsPerMessage) + CSSTAT_FIRST;
+ int lastMessageInThisBatch = firstStatInThisBatch + maxStatsPerMessage - 1;
+
+ CRecipientFilter filter;
+ filter.AddAllPlayers();
+ UserMessageBegin( filter, "MatchStatsUpdate" );
+
+ WRITE_SHORT(firstStatInThisBatch);
+
+ for ( int iStat = firstStatInThisBatch; iStat < CSSTAT_MAX && iStat <= lastMessageInThisBatch; ++iStat )
+ {
+ WRITE_FLOAT(m_rollingTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_rollingCTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_rollingPlayerStatAverages.m_fStat[iStat]);
+ }
+
+ MessageEnd();
+ }
+}
+
+
+void CCSGameStats::SendDirectStatsAveragesToAllPlayers()
+{
+ //The most stats we can send at once is the max message size minus the header, divided by the total size of each stat.
+ const int maxStatsPerMessage = (MAX_USER_MSG_DATA - sizeof(CSStatType_t)) / (3 * sizeof(float));
+
+ const int numMessagesNeeded = ((CSSTAT_MAX - CSSTAT_FIRST) / maxStatsPerMessage) + 1;
+
+ for (int batchIndex = 0 ; batchIndex < numMessagesNeeded ; ++batchIndex)
+ {
+ int firstStatInThisBatch = (batchIndex * maxStatsPerMessage) + CSSTAT_FIRST;
+ int lastMessageInThisBatch = firstStatInThisBatch + maxStatsPerMessage - 1;
+
+ CRecipientFilter filter;
+ filter.AddAllPlayers();
+ UserMessageBegin( filter, "MatchStatsUpdate" );
+
+ WRITE_SHORT(firstStatInThisBatch);
+
+ for ( int iStat = firstStatInThisBatch; iStat < CSSTAT_MAX && iStat <= lastMessageInThisBatch; ++iStat )
+ {
+ WRITE_FLOAT(m_directTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_directCTStatAverages.m_fStat[iStat]);
+ WRITE_FLOAT(m_directPlayerStatAverages.m_fStat[iStat]);
+ }
+
+ MessageEnd();
+ }
+}
+
+void CCSGameStats::ComputeRollingStatAverages()
+{
+ int numPlayers = 0;
+ int numCTs = 0;
+ int numTs = 0;
+
+ RoundStatsRollingAverage_t currentRoundTStatsAverage;
+ RoundStatsRollingAverage_t currentRoundCTStatsAverage;
+ RoundStatsRollingAverage_t currentRoundPlayerStatsAverage;
+
+ currentRoundTStatsAverage.Reset();
+ currentRoundCTStatsAverage.Reset();
+ currentRoundPlayerStatsAverage.Reset();
+
+ //for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &roundStats = m_aPlayerStats[pPlayer->entindex()].statsCurrentRound;
+
+ int teamNumber = pPlayer->GetTeamNumber();
+ if (teamNumber == TEAM_CT)
+ {
+ numCTs++;
+ numPlayers++;
+ currentRoundCTStatsAverage += roundStats;
+ currentRoundPlayerStatsAverage += roundStats;
+ }
+ else if (teamNumber == TEAM_TERRORIST)
+ {
+ numTs++;
+ numPlayers++;
+ currentRoundTStatsAverage += roundStats;
+ currentRoundPlayerStatsAverage += roundStats;
+ }
+ }
+ }
+
+ if (numTs > 0)
+ {
+ currentRoundTStatsAverage /= numTs;
+ }
+
+ if (numCTs > 0)
+ {
+ currentRoundCTStatsAverage /= numCTs;
+ }
+
+ if (numPlayers > 0)
+ {
+ currentRoundPlayerStatsAverage /= numPlayers;
+ }
+
+ m_rollingTStatAverages.RollDataSetIntoAverage(currentRoundTStatsAverage);
+ m_rollingCTStatAverages.RollDataSetIntoAverage(currentRoundCTStatsAverage);
+ m_rollingPlayerStatAverages.RollDataSetIntoAverage(currentRoundPlayerStatsAverage);
+ }
+}
+
+
+void CCSGameStats::ComputeDirectStatAverages()
+{
+ m_numberOfRoundsForDirectAverages++;
+
+ m_directCTStatAverages.Reset();
+ m_directTStatAverages.Reset();
+ m_directPlayerStatAverages.Reset();
+
+
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &matchStats = m_aPlayerStats[pPlayer->entindex()].statsCurrentMatch;
+
+ int teamNumber = pPlayer->GetTeamNumber();
+ if (teamNumber == TEAM_CT)
+ {
+ m_numberOfCounterTerroristEntriesForDirectAverages++;
+ m_directCTStatAverages += matchStats;
+ m_directPlayerStatAverages += matchStats;
+ }
+ else if (teamNumber == TEAM_TERRORIST)
+ {
+ m_numberOfTerroristEntriesForDirectAverages++;
+ m_directTStatAverages += matchStats;
+ m_directPlayerStatAverages += matchStats;
+ }
+ }
+ }
+
+ if (m_numberOfTerroristEntriesForDirectAverages > 0)
+ {
+ m_directTStatAverages /= m_numberOfTerroristEntriesForDirectAverages;
+ m_directTStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+
+ if (m_numberOfCounterTerroristEntriesForDirectAverages > 0)
+ {
+ m_directCTStatAverages /= m_numberOfCounterTerroristEntriesForDirectAverages;
+ m_directCTStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+
+ int numPlayers = m_numberOfCounterTerroristEntriesForDirectAverages + m_numberOfTerroristEntriesForDirectAverages;
+
+ if (numPlayers > 0)
+ {
+ m_directPlayerStatAverages /= numPlayers;
+ m_directPlayerStatAverages *= m_numberOfRoundsForDirectAverages;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Log accumulated weapon usage and performance data
+//-----------------------------------------------------------------------------
+void CCSGameStats::DumpMatchWeaponMetrics()
+{
+ //// generate a filename
+ //time_t t = time( NULL );
+ //struct tm *now = localtime( &t );
+ //if ( !now )
+ // return;
+
+ //int year = now->tm_year + 1900;
+ //int month = now->tm_mon + 1;
+ //int day = now->tm_mday;
+ //int hour = now->tm_hour;
+ //int minute = now->tm_min;
+ //int second = now->tm_sec;
+
+ //char filename[ 128 ];
+ //Q_snprintf( filename, sizeof(filename), "wm_%4d%02d%02d_%02d%02d%02d_%s.csv",
+ // year, month, day, hour, minute, second, gpGlobals->mapname.ToCStr());
+
+ //FileHandle_t hLogFile = filesystem->Open( filename, "wt" );
+
+ //if ( hLogFile == FILESYSTEM_INVALID_HANDLE )
+ // return;
+
+ //filesystem->FPrintf(hLogFile, "%s\n", "WeaponId, Mode, Cost, Bullets, CycleTime, TotalShots, TotalHits, TotalDamage, TotalKills");
+
+ //for (int iWeapon = 0; iWeapon < WEAPON_MAX; ++iWeapon)
+ //{
+ // CCSWeaponInfo* pInfo = GetWeaponInfo( (CSWeaponID)iWeapon );
+ // if ( !pInfo )
+ // continue;
+
+ // const char* pWeaponName = pInfo->szClassName;
+ // if ( !pWeaponName )
+ // continue;
+
+ // if ( V_strncmp(pWeaponName, "weapon_", 7) == 0 )
+ // pWeaponName += 7;
+
+ // for ( int iMode = 0; iMode < WeaponMode_MAX; ++iMode)
+ // {
+ // filesystem->FPrintf(hLogFile, "%s, %d, %d, %d, %f, %d, %d, %d, %d\n",
+ // pWeaponName,
+ // iMode,
+ // pInfo->GetWeaponPrice(),
+ // pInfo->m_iBullets,
+ // pInfo->m_flCycleTime,
+ // m_weaponStats[iWeapon][iMode].shots,
+ // m_weaponStats[iWeapon][iMode].hits,
+ // m_weaponStats[iWeapon][iMode].damage,
+ // m_weaponStats[iWeapon][iMode].kills);
+ // }
+ //}
+
+ //filesystem->FPrintf(hLogFile, "\n");
+ //filesystem->FPrintf(hLogFile, "weapon_accuracy_model, %d\n", weapon_accuracy_model.GetInt());
+ //filesystem->FPrintf(hLogFile, "bot_difficulty, %d\n", cv_bot_difficulty.GetInt());
+
+ //g_pFullFileSystem->Close(hLogFile);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerConnected( CBasePlayer *pPlayer )
+{
+ ResetPlayerStats( ToCSPlayer( pPlayer ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerDisconnected( CBasePlayer *pPlayer )
+{
+ CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer );
+ if ( !pCSPlayer )
+ return;
+
+ ResetPlayerStats( pCSPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
+{
+ // This also gets called when the victim is a building. That gets tracked separately as building destruction, don't count it here
+ if ( !pVictim->IsPlayer() )
+ return;
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ CCSPlayer *pPlayerAttacker = ToCSPlayer( pAttacker );
+ CCSPlayer *pPlayerVictim = ToCSPlayer( pVictim );
+
+ // keep track of how many times every player kills every other player
+ TrackKillStats( pPlayerAttacker, pPlayerVictim );
+
+ // Skip rest of stat reporting for friendly fire
+ if ( pPlayerAttacker->GetTeam() == pVictim->GetTeam() )
+ return;
+
+ CSWeaponID weaponId = WEAPON_NONE;
+
+ if ( pInflictor )
+ {
+ if ( pInflictor == pAttacker )
+ {
+ if ( pAttacker->GetActiveWeapon() )
+ {
+ CBaseCombatWeapon* weapon = pAttacker->GetActiveWeapon();
+ if (weapon)
+ {
+ CWeaponCSBase* pCSWeapon = static_cast<CWeaponCSBase*>(weapon);
+
+ weaponId = pCSWeapon->GetWeaponID();
+
+ CCSWeaponInfo *info = GetWeaponInfo(weaponId);
+
+ if (info && info->m_iTeam != TEAM_UNASSIGNED && pAttacker->GetTeamNumber() != info->m_iTeam )
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WEAPON, 1);
+ }
+ }
+ }
+ }
+ else
+ {
+ //In the case of grenades, the inflictor is the spawned grenade entity.
+ if ( V_strcmp(pInflictor->m_iClassname.ToCStr(), "hegrenade_projectile") == 0 )
+ weaponId = WEAPON_HEGRENADE;
+ }
+ }
+
+ // update weapon stats
+ ++m_weaponStats[weaponId][pPlayerAttacker->IsBot()].kills;
+
+ for (int i = 0; WeaponName_StatId_Table[i].killStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( WeaponName_StatId_Table[i].weaponId == weaponId )
+ {
+ IncrementStat( pPlayerAttacker, WeaponName_StatId_Table[i].killStatId, 1 );
+ break;
+ }
+ }
+
+ if (pPlayerVictim && pPlayerVictim->IsBlind())
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_ENEMY_BLINDED, 1 );
+ }
+
+ if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->IsBlindForAchievement())
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_WHILE_BLINDED, 1 );
+ }
+
+ // [sbodenbender] check for deaths near planted bomb for funfact
+ if (pPlayerVictim && pPlayerAttacker && pPlayerAttacker->GetTeamNumber() == TEAM_TERRORIST && CSGameRules()->m_bBombPlanted)
+ {
+ float bombCheckDistSq = AchievementConsts::KillEnemyNearBomb_MaxDistance * AchievementConsts::KillEnemyNearBomb_MaxDistance;
+ for ( int i=0; i < g_PlantedC4s.Count(); i++ )
+ {
+ CPlantedC4 *pC4 = g_PlantedC4s[i];
+
+ if ( pC4->IsBombActive() )
+ {
+ Vector bombPos = pC4->GetAbsOrigin();
+ Vector victimToBomb = pPlayerVictim->GetAbsOrigin() - bombPos;
+ Vector attackerToBomb = pPlayerAttacker->GetAbsOrigin() - bombPos;
+ if (victimToBomb.LengthSqr() < bombCheckDistSq || attackerToBomb.LengthSqr() < bombCheckDistSq)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_DEFENDING_BOMB, 1);
+ break; // you only get credit for one kill even if you happen to be by more than one bomb
+ }
+ }
+ }
+ }
+
+ //Increment stat if this is a headshot.
+ if (info.GetDamageType() & DMG_HEADSHOT)
+ {
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS_HEADSHOT, 1 );
+ }
+
+ IncrementStat( pPlayerAttacker, CSSTAT_KILLS, 1 );
+
+ // we don't have a simple way (yet) to check if the victim actually just achieved The Unstoppable Force, so we
+ // award this achievement simply if they've met the requirements and would have received it.
+ PlayerStats_t &victimStats = m_aPlayerStats[pVictim->entindex()];
+ if (victimStats.statsCurrentRound[CSSTAT_KILLS] >= AchievementConsts::UnstoppableForce_Kills)
+ {
+ pPlayerAttacker->AwardAchievement(CSImmovableObject);
+ }
+
+ CCSGameRules::TeamPlayerCounts playerCounts[TEAM_MAXCOUNT];
+
+ CSGameRules()->GetPlayerCounts(playerCounts);
+ int iAttackerTeamNumber = pPlayerAttacker->GetTeamNumber() ;
+ if (playerCounts[iAttackerTeamNumber].totalAlivePlayers == 1 && playerCounts[iAttackerTeamNumber].killedPlayers >= 2)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE, 1);
+ }
+
+ //if they were damaged by more than one person that must mean that someone else did damage before the killer finished them off.
+ if (pPlayerVictim->GetNumEnemyDamagers() > 1)
+ {
+ IncrementStat(pPlayerAttacker, CSSTAT_KILLS_ENEMY_WOUNDED, 1);
+ }
+
+ // Let's check for the "Happy Camper" achievement where we snipe two players while standing in the same spot.
+ if ( pPlayerAttacker && !pPlayerAttacker->IsBot() && weaponId != WEAPON_NONE )
+ {
+ // Were we using a sniper rifle?
+ bool bUsingSniper = ( weaponId == WEAPON_AWP ||
+ weaponId == WEAPON_SCOUT ||
+ weaponId == WEAPON_SG550 ||
+ weaponId == WEAPON_G3SG1 );
+
+ // If we're zoomed in
+ if ( bUsingSniper && pPlayerAttacker->GetFOV() != pPlayerAttacker->GetDefaultFOV() )
+ {
+ // Get our position
+ Vector position = pPlayerAttacker->GetAbsOrigin();
+ int userid = pPlayerAttacker->GetUserID();
+
+ bool bFoundPlayerEntry = false;
+ FOR_EACH_LL( m_PlayerSnipedPosition, iElement )
+ {
+ sHappyCamperSnipePosition *pSnipePosition = &m_PlayerSnipedPosition[iElement];
+
+ // We've found this player's entry. Next, check to see if they meet the achievement criteria
+ if ( userid == pSnipePosition->m_iUserID )
+ {
+ bFoundPlayerEntry = true;
+ Vector posDif = position - pSnipePosition->m_vPos;
+
+ // Give a small epsilon to account for floating point anomalies
+ if ( posDif.IsLengthLessThan( .01f) )
+ {
+ pPlayerAttacker->AwardAchievement( CSSnipeTwoFromSameSpot );
+ }
+
+ // Update their position
+ pSnipePosition->m_vPos = position;
+ break;
+ }
+ }
+
+ // No entry so add one
+ if ( !bFoundPlayerEntry )
+ {
+ m_PlayerSnipedPosition.AddToTail( sHappyCamperSnipePosition( userid, position ) );
+ }
+ }
+ }
+}
+
+
+void CCSGameStats::CalculateOverkill(CCSPlayer* pAttacker, CCSPlayer* pVictim)
+{
+ //Count domination overkills - Do this before determining domination
+ if (pAttacker->GetTeam() != pVictim->GetTeam())
+ {
+ if (pAttacker->IsPlayerDominated(pVictim->entindex()))
+ {
+ IncrementStat( pAttacker, CSSTAT_DOMINATION_OVERKILLS, 1 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Steamworks Gamestats death tracking
+//-----------------------------------------------------------------------------
+void CCSGameStats::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
+{
+ if ( !pVictim )
+ return;
+
+ m_DeathData.AddToTail( new SCSSDeathData( pVictim, info ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stats event for giving damage to player
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
+{
+ CCSPlayer *pAttacker = ToCSPlayer( info.GetAttacker() );
+ if ( pAttacker && pAttacker->GetTeam() != pBasePlayer->GetTeam() )
+ {
+ CSWeaponMode weaponMode = Primary_Mode;
+ IncrementStat( pAttacker, CSSTAT_DAMAGE, info.GetDamage() );
+
+ if (pAttacker->m_bNightVisionOn)
+ {
+ IncrementStat( pAttacker, CSSTAT_NIGHTVISION_DAMAGE, info.GetDamage() );
+ }
+
+ const char *pWeaponName = NULL;
+
+ if ( info.GetInflictor() == info.GetAttacker() )
+ {
+ if ( pAttacker->GetActiveWeapon() )
+ {
+ CWeaponCSBase* pCSWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon());
+ if (pCSWeapon)
+ {
+ pWeaponName = pCSWeapon->GetClassname();
+ weaponMode = pCSWeapon->m_weaponMode;
+ }
+ }
+ }
+ // Need to determine the weapon name
+ else
+ {
+ pWeaponName = info.GetInflictor()->GetClassname();
+ }
+
+ // Unknown weapon?!?
+ if ( !pWeaponName )
+ return;
+
+ // Now update the damage this weapon has done
+ CSWeaponID weaponId = AliasToWeaponID( GetTranslatedWeaponAlias( pWeaponName ) );
+ for (int i = 0; WeaponName_StatId_Table[i].shotStatId != CSSTAT_UNDEFINED; ++i)
+ {
+ if ( weaponId == WeaponName_StatId_Table[i].weaponId )
+ {
+ int weaponId = WeaponName_StatId_Table[i].weaponId;
+ int iType = pAttacker->IsBot();
+ ++m_weaponStats[weaponId][iType].hits;
+ m_weaponStats[weaponId][iType].damage += info.GetDamage();
+ break;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stats event for giving money to player
+//-----------------------------------------------------------------------------
+void CCSGameStats::Event_MoneyEarned( CCSPlayer* pPlayer, int moneyEarned)
+{
+ if ( pPlayer && moneyEarned > 0)
+ {
+ IncrementStat(pPlayer, CSSTAT_MONEY_EARNED, moneyEarned);
+ }
+}
+
+void CCSGameStats::Event_MoneySpent( CCSPlayer* pPlayer, int moneySpent, const char *pItemName )
+{
+ if ( pPlayer && moneySpent > 0)
+ {
+ IncrementStat(pPlayer, CSSTAT_MONEY_SPENT, moneySpent);
+ if ( pItemName && !pPlayer->IsBot() )
+ {
+ CSteamID steamIDForBuyer;
+ pPlayer->GetSteamID( &steamIDForBuyer );
+ m_MarketPurchases.AddToTail( new SMarketPurchases( steamIDForBuyer.ConvertToUint64(), moneySpent, pItemName ) );
+ }
+ }
+}
+
+void CCSGameStats::Event_PlayerDonatedWeapon (CCSPlayer* pPlayer)
+{
+ if (pPlayer)
+ {
+ IncrementStat(pPlayer, CSSTAT_WEAPONS_DONATED, 1);
+ }
+}
+
+void CCSGameStats::Event_MVPEarned( CCSPlayer* pPlayer )
+{
+ if (pPlayer)
+ {
+ IncrementStat(pPlayer, CSSTAT_MVPS, 1);
+ }
+}
+
+void CCSGameStats::Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage )
+{
+ if (pPlayer)
+ {
+ int weaponId = WEAPON_KNIFE;
+ int iType = pPlayer->IsBot();
+
+ IncrementStat( pPlayer, CSSTAT_SHOTS_KNIFE, 1 );
+ ++m_weaponStats[weaponId][iType].shots;
+ if ( iDamage )
+ {
+ IncrementStat( pPlayer, CSSTAT_HITS_KNIFE, 1 );
+ ++m_weaponStats[weaponId][iType].hits;
+
+ IncrementStat( pPlayer, CSSTAT_DAMAGE_KNIFE, iDamage );
+ m_weaponStats[weaponId][iType].damage += iDamage;
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+void CCSGameStats::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+
+ if ( V_strcmp(pEventName, "round_start") == 0 )
+ {
+ m_PlayerSnipedPosition.Purge();
+ m_bInRound = true;
+ }
+ else if ( V_strcmp(pEventName, "round_end") == 0 )
+ {
+ const int reason = event->GetInt( "reason" );
+
+ if( reason == Game_Commencing )
+ {
+ ResetPlayerClassMatchStats();
+ }
+ else
+ {
+ UpdatePlayerRoundStats(event->GetInt("winner"));
+ ComputeDirectStatAverages();
+ SendDirectStatsAveragesToAllPlayers();
+
+ UploadRoundStats();
+ ResetWeaponStats();
+ }
+ return;
+ }
+ else if ( V_strcmp(pEventName, "break_prop") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ int entindex = event->GetInt("entindex", 0);
+ CBreakableProp* pProp = static_cast<CBreakableProp*>(CBaseEntity::Instance(entindex));
+ Event_BreakProp(ToCSPlayer(UTIL_PlayerByUserId(userid)), pProp);
+ }
+ else if ( V_strcmp(pEventName, "player_decal") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ Event_PlayerSprayedDecal(ToCSPlayer(UTIL_PlayerByUserId(userid)));
+ }
+ else if ( V_strcmp(pEventName, "hegrenade_detonate") == 0 )
+ {
+ int userid = event->GetInt("userid", 0);
+ IncrementStat( ToCSPlayer(UTIL_PlayerByUserId(userid)), CSSTAT_SHOTS_HEGRENADE, 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return stats for the given player
+//-----------------------------------------------------------------------------
+const PlayerStats_t& CCSGameStats::FindPlayerStats( CBasePlayer *pPlayer ) const
+{
+ return m_aPlayerStats[pPlayer->entindex()];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return stats for the given team
+//-----------------------------------------------------------------------------
+const StatsCollection_t& CCSGameStats::GetTeamStats( int iTeamIndex ) const
+{
+ int arrayIndex = iTeamIndex - FIRST_GAME_TEAM;
+ Assert( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - FIRST_GAME_TEAM );
+ return m_aTeamStats[arrayIndex];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the stats for each team
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetAllTeamStats()
+{
+ for ( int i = 0; i < ARRAYSIZE(m_aTeamStats); ++i )
+ {
+ m_aTeamStats[i].Reset();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets all stats (including round, match, accumulated and rolling averages
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetAllStats()
+{
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ m_aPlayerStats[i].statsDelta.Reset();
+ m_aPlayerStats[i].statsCurrentRound.Reset();
+ m_aPlayerStats[i].statsCurrentMatch.Reset();
+ m_rollingCTStatAverages.Reset();
+ m_rollingTStatAverages.Reset();
+ m_rollingPlayerStatAverages.Reset();
+ m_numberOfRoundsForDirectAverages = 0;
+ m_numberOfTerroristEntriesForDirectAverages = 0;
+ m_numberOfCounterTerroristEntriesForDirectAverages = 0;
+ }
+}
+
+
+void CCSGameStats::ResetWeaponStats()
+{
+ V_memset(m_weaponStats, 0, sizeof(m_weaponStats));
+}
+
+void CCSGameStats::IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount )
+{
+ int arrayIndex = iTeamIndex - TEAM_TERRORIST;
+ Assert( iStatIndex >= 0 && iStatIndex < CSSTAT_MAX );
+ if( arrayIndex >= 0 && arrayIndex < TEAM_MAXCOUNT - TEAM_TERRORIST )
+ {
+ m_aTeamStats[arrayIndex][iStatIndex] += iAmount;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets all stats for this player
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetPlayerStats( CBasePlayer* pPlayer )
+{
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+ // reset the stats on this player
+ stats.Reset();
+ // reset the matrix of who killed whom with respect to this player
+ ResetKillHistory( pPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the kill history for this player
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetKillHistory( CBasePlayer* pPlayer )
+{
+ int iPlayerIndex = pPlayer->entindex();
+
+ PlayerStats_t& statsPlayer = m_aPlayerStats[iPlayerIndex];
+
+ // for every other player, set all all the kills with respect to this player to 0
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ //reset their record of us.
+ PlayerStats_t &statsOther = m_aPlayerStats[i];
+ statsOther.statsKills.iNumKilled[iPlayerIndex] = 0;
+ statsOther.statsKills.iNumKilledBy[iPlayerIndex] = 0;
+ statsOther.statsKills.iNumKilledByUnanswered[iPlayerIndex] = 0;
+
+ //reset our record of them
+ statsPlayer.statsKills.iNumKilled[i] = 0;
+ statsPlayer.statsKills.iNumKilledBy[i] = 0;
+ statsPlayer.statsKills.iNumKilledByUnanswered[i] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets per-round stats for all players
+//-----------------------------------------------------------------------------
+void CCSGameStats::ResetRoundStats()
+{
+ for ( int i = 0; i < ARRAYSIZE( m_aPlayerStats ); i++ )
+ {
+ m_aPlayerStats[i].statsCurrentRound.Reset();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Increments specified stat for specified player by specified amount
+//-----------------------------------------------------------------------------
+void CCSGameStats::IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iDelta, bool bPlayerOnly /* = false */ )
+{
+ if ( pPlayer )
+ {
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+ stats.statsDelta[statId] += iDelta;
+ stats.statsCurrentRound[statId] += iDelta;
+ stats.statsCurrentMatch[statId] += iDelta;
+
+ // increment team stat
+ int teamIndex = pPlayer->GetTeamNumber() - FIRST_GAME_TEAM;
+ if ( !bPlayerOnly && teamIndex >= 0 && teamIndex < ARRAYSIZE(m_aTeamStats) )
+ {
+ m_aTeamStats[teamIndex][statId] += iDelta;
+ }
+
+ for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
+ {
+ if (ServerStatBasedAchievements[i].statId == statId)
+ {
+ // skip this if there is a map filter and it doesn't match
+ if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
+ continue;
+
+ bool bWasMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId] - iDelta, stats.statsCurrentMatch[statId] - iDelta);
+ bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
+ if (!bWasMet && bIsMet)
+ {
+ pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the specified stat for specified player to the specified amount
+//-----------------------------------------------------------------------------
+void CCSGameStats::SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue )
+{
+ if (pPlayer)
+ {
+ int oldRoundValue, oldMatchValue;
+ PlayerStats_t &stats = m_aPlayerStats[pPlayer->entindex()];
+
+ oldRoundValue = stats.statsCurrentRound[statId];
+ oldMatchValue = stats.statsCurrentMatch[statId];
+
+ stats.statsDelta[statId] = iValue;
+ stats.statsCurrentRound[statId] = iValue;
+ stats.statsCurrentMatch[statId] = iValue;
+
+ for (int i = 0; i < ARRAYSIZE(ServerStatBasedAchievements); ++i)
+ {
+ if (ServerStatBasedAchievements[i].statId == statId)
+ {
+ // skip this if there is a map filter and it doesn't match
+ if (ServerStatBasedAchievements[i].mapFilter != NULL && V_strcmp(gpGlobals->mapname.ToCStr(), ServerStatBasedAchievements[i].mapFilter) != 0)
+ continue;
+
+ bool bWasMet = ServerStatBasedAchievements[i].IsMet(oldRoundValue, oldMatchValue);
+ bool bIsMet = ServerStatBasedAchievements[i].IsMet(stats.statsCurrentRound[statId], stats.statsCurrentMatch[statId]);
+ if (!bWasMet && bIsMet)
+ {
+ pPlayer->AwardAchievement(ServerStatBasedAchievements[i].achievementId);
+ }
+ }
+ }
+ }
+}
+
+void CCSGameStats::SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority )
+{
+ ASSERT(CSSTAT_MAX < 255); // if we add more than 255 stats, we'll need to update this protocol
+ if ( pPlayer && pPlayer->IsConnected())
+ {
+ StatsCollection_t &deltaStats = m_aPlayerStats[pPlayer->entindex()].statsDelta;
+
+ // check to see if we have any stats to actually send
+ byte iStatsToSend = 0;
+ for ( int iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ ASSERT(CSStatProperty_Table[iStat].statId == iStat);
+ if ( CSStatProperty_Table[iStat].statId != iStat )
+ {
+ Warning( "CSStatProperty_Table[iStat].statId != iStat, (%d)", CSStatProperty_Table[iStat].statId );
+ }
+ int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
+ if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
+ {
+ ++iStatsToSend;
+ }
+ }
+
+ // nothing changed - bail out
+ if ( !iStatsToSend )
+ return;
+
+ CSingleUserRecipientFilter filter( pPlayer );
+ filter.MakeReliable();
+ UserMessageBegin( filter, "PlayerStatsUpdate" );
+
+ CRC32_t crc;
+ CRC32_Init( &crc );
+
+ // begin the CRC with a trivially hidden key value to discourage packet modification
+ const uint32 key = 0x82DA9F4C; // this key should match the key in cs_client_gamestats.cpp
+ CRC32_ProcessBuffer( &crc, &key, sizeof(key));
+
+ // if we make any change to the ordering of the stats or this message format, update this value
+ const byte version = 0x01;
+ CRC32_ProcessBuffer( &crc, &version, sizeof(version));
+ WRITE_BYTE(version);
+
+ CRC32_ProcessBuffer( &crc, &iStatsToSend, sizeof(iStatsToSend));
+ WRITE_BYTE(iStatsToSend);
+
+ for ( byte iStat = CSSTAT_FIRST; iStat < CSSTAT_MAX; ++iStat )
+ {
+ int iPriority = CSStatProperty_Table[iStat].flags & CSSTAT_PRIORITY_MASK;
+ if (deltaStats[iStat] != 0 && iPriority >= iMinStatPriority)
+ {
+ CRC32_ProcessBuffer( &crc, &iStat, sizeof(iStat));
+ WRITE_BYTE(iStat);
+ Assert(deltaStats[iStat] <= 0x7FFF && deltaStats[iStat] > 0); // make sure we aren't truncating bits
+ short delta = deltaStats[iStat];
+ CRC32_ProcessBuffer( &crc, &delta, sizeof(delta));
+ WRITE_SHORT( deltaStats[iStat]);
+ deltaStats[iStat] = 0;
+ --iStatsToSend;
+ }
+ }
+
+ Assert(iStatsToSend == 0);
+
+ CRC32_Final( &crc );
+ WRITE_LONG(crc);
+
+ MessageEnd();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sends intermittent stats updates for stats that need to be updated during a round and/or life
+//-----------------------------------------------------------------------------
+void CCSGameStats::PreClientUpdate()
+{
+ int iMinStatPriority = -1;
+ m_fDisseminationTimerHigh += gpGlobals->frametime;
+ m_fDisseminationTimerLow += gpGlobals->frametime;
+
+ if ( m_fDisseminationTimerHigh > cDisseminationTimeHigh)
+ {
+ iMinStatPriority = CSSTAT_PRIORITY_HIGH;
+ m_fDisseminationTimerHigh = 0.0f;
+
+ if ( m_fDisseminationTimerLow > cDisseminationTimeLow)
+ {
+ iMinStatPriority = CSSTAT_PRIORITY_LOW;
+ m_fDisseminationTimerLow = 0.0f;
+ }
+ }
+ else
+ return;
+
+ //The proper time has elapsed, now send the update to every player
+ for ( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(iPlayerIndex) );
+ SendStatsToPlayer(pPlayer, iMinStatPriority);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the stats of who has killed whom
+//-----------------------------------------------------------------------------
+void CCSGameStats::TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim )
+{
+ int iPlayerIndexAttacker = pAttacker->entindex();
+ int iPlayerIndexVictim = pVictim->entindex();
+
+ PlayerStats_t &statsAttacker = m_aPlayerStats[iPlayerIndexAttacker];
+ PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
+
+ statsVictim.statsKills.iNumKilledBy[iPlayerIndexAttacker]++;
+ statsVictim.statsKills.iNumKilledByUnanswered[iPlayerIndexAttacker]++;
+ statsAttacker.statsKills.iNumKilled[iPlayerIndexVictim]++;
+ statsAttacker.statsKills.iNumKilledByUnanswered[iPlayerIndexVictim] = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines if attacker and victim have gotten domination or revenge
+//-----------------------------------------------------------------------------
+void CCSGameStats::CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags )
+{
+ //=============================================================================
+ // HPE_BEGIN:
+ // [Forrest] Allow nemesis/revenge to be turned off for a server
+ //=============================================================================
+ if ( sv_nonemesis.GetBool() )
+ {
+ return;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ //If there is no attacker, there is no domination or revenge
+ if( !pAttacker || !pVictim )
+ {
+ return;
+ }
+ if (pAttacker->GetTeam() == pVictim->GetTeam())
+ {
+ return;
+ }
+ int iPlayerIndexVictim = pVictim->entindex();
+ PlayerStats_t &statsVictim = m_aPlayerStats[iPlayerIndexVictim];
+ // calculate # of unanswered kills between killer & victim
+ // This is plus 1 as this function gets called before the stat is updated. That is done so that the domination
+ // and revenge will be calculated prior to the death message being sent to the clients
+ int attackerEntityIndex = pAttacker->entindex();
+ int iKillsUnanswered = statsVictim.statsKills.iNumKilledByUnanswered[attackerEntityIndex] + 1;
+
+ if ( CS_KILLS_FOR_DOMINATION == iKillsUnanswered )
+ {
+ // this is the Nth unanswered kill between killer and victim, killer is now dominating victim
+ *piDeathFlags |= ( CS_DEATH_DOMINATION );
+ }
+ else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) )
+ {
+ // the killer killed someone who was dominating him, gains revenge
+ *piDeathFlags |= ( CS_DEATH_REVENGE );
+ }
+
+ //Check the overkill on 1 player achievement
+ if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION + AchievementConsts::ExtendedDomination_AdditionalKills)
+ {
+ pAttacker->AwardAchievement(CSExtendedDomination);
+ }
+
+ if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION)
+ {
+ //this is the Nth unanswered kill between killer and victim, killer is now dominating victim
+ //set victim to be dominated by killer
+ pAttacker->SetPlayerDominated( pVictim, true );
+
+ //Check concurrent dominations achievement
+ int numConcurrentDominations = 0;
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CCSPlayer *pPlayer= ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if (pPlayer && pAttacker->IsPlayerDominated(pPlayer->entindex()))
+ {
+ numConcurrentDominations++;
+ }
+ }
+ if (numConcurrentDominations >= AchievementConsts::ConcurrentDominations_MinDominations)
+ {
+ pAttacker->AwardAchievement(CSConcurrentDominations);
+ }
+
+
+ // record stats
+ Event_PlayerDominatedOther( pAttacker, pVictim );
+ }
+ else if ( pVictim->IsPlayerDominated( pAttacker->entindex() ) )
+ {
+ // the killer killed someone who was dominating him, gains revenge
+ // set victim to no longer be dominating the killer
+
+ pVictim->SetPlayerDominated( pAttacker, false );
+ // record stats
+ Event_PlayerRevenge( pAttacker );
+ }
+}
+
+void CCSGameStats::Event_PlayerDominatedOther( CCSPlayer *pAttacker, CCSPlayer* pVictim )
+{
+ IncrementStat( pAttacker, CSSTAT_DOMINATIONS, 1 );
+}
+
+void CCSGameStats::Event_PlayerRevenge( CCSPlayer *pAttacker )
+{
+ IncrementStat( pAttacker, CSSTAT_REVENGES, 1 );
+}
+
+void CCSGameStats::Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer )
+{
+ if (pAttacker && pAvengedPlayer)
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_avenged_teammate" );
+
+ if ( event )
+ {
+ event->SetInt( "avenger_id", pAttacker->GetUserID() );
+ event->SetInt( "avenged_player_id", pAvengedPlayer->GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+void CCSGameStats::Event_LevelInit()
+{
+ ResetAllTeamStats();
+ ResetWeaponStats();
+ CBaseGameStats::Event_LevelInit();
+ GetSteamWorksSGameStatsUploader().StartSession();
+}
+
+void CCSGameStats::Event_LevelShutdown( float fElapsed )
+{
+ DumpMatchWeaponMetrics();
+ CBaseGameStats::Event_LevelShutdown(fElapsed);
+}
+
+// Reset any per match info that resides in the player class
+void CCSGameStats::ResetPlayerClassMatchStats()
+{
+ for ( int i = 1; i <= MAX_PLAYERS; i++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer )
+ {
+ pPlayer->SetNumMVPs( 0 );
+ }
+ }
+}
diff --git a/game/server/cstrike/cs_gamestats.h b/game/server/cstrike/cs_gamestats.h
new file mode 100644
index 0000000..4a24efb
--- /dev/null
+++ b/game/server/cstrike/cs_gamestats.h
@@ -0,0 +1,340 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The CS game stats header
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CS_GAMESTATS_H
+#define CS_GAMESTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cs_blackmarket.h"
+#include "gamestats.h"
+#include "cs_gamestats_shared.h"
+#include "GameEventListener.h"
+#include "steamworks_gamestats.h"
+#include "weapon_csbase.h"
+
+// forward declares
+class CBreakableProp;
+
+#define CS_STATS_BLOB_VERSION 3
+
+const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
+const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
+
+int GetCSLevelIndex( const char *pLevelName );
+
+typedef struct
+{
+ char szGameName[8];
+ byte iVersion;
+ char szMapName[32];
+ char ipAddr[4];
+ short port;
+ int serverid;
+} gamestats_header_t;
+
+typedef struct
+{
+ gamestats_header_t header;
+ short iMinutesPlayed;
+
+ short iTerroristVictories[CS_NUM_LEVELS];
+ short iCounterTVictories[CS_NUM_LEVELS];
+ short iBlackMarketPurchases[WEAPON_MAX];
+
+ short iAutoBuyPurchases;
+ short iReBuyPurchases;
+ short iAutoBuyM4A1Purchases;
+ short iAutoBuyAK47Purchases;
+ short iAutoBuyFamasPurchases;
+ short iAutoBuyGalilPurchases;
+ short iAutoBuyVestHelmPurchases;
+ short iAutoBuyVestPurchases;
+
+} cs_gamestats_t;
+
+extern short g_iWeaponPurchases[WEAPON_MAX];
+extern float g_flGameStatsUpdateTime;
+extern short g_iTerroristVictories[CS_NUM_LEVELS];
+extern short g_iCounterTVictories[CS_NUM_LEVELS];
+extern short g_iWeaponPurchases[WEAPON_MAX];
+
+extern short g_iAutoBuyPurchases;
+extern short g_iReBuyPurchases;
+extern short g_iAutoBuyM4A1Purchases;
+extern short g_iAutoBuyAK47Purchases;
+extern short g_iAutoBuyFamasPurchases;
+extern short g_iAutoBuyGalilPurchases;
+extern short g_iAutoBuyVestHelmPurchases;
+extern short g_iAutoBuyVestPurchases;
+
+
+struct sHappyCamperSnipePosition
+{
+ sHappyCamperSnipePosition( int userid, Vector pos ) : m_iUserID(userid), m_vPos(pos) {}
+
+ int m_iUserID;
+ Vector m_vPos;
+};
+
+struct SMarketPurchases : public BaseStatData
+{
+ SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName ) : m_nPlayerID(ulPlayerID), ItemCost(iPrice)
+ {
+ if ( pName )
+ {
+ Q_strncpy( ItemID, pName, ARRAYSIZE(ItemID) );
+ }
+ else
+ {
+ Q_strncpy( ItemID, "unknown", ARRAYSIZE(ItemID) );
+ }
+ }
+ uint64 m_nPlayerID;
+ int ItemCost;
+ char ItemID[64];
+
+ BEGIN_STAT_TABLE( "CSSMarketPurchase" )
+ REGISTER_STAT( m_nPlayerID )
+ REGISTER_STAT( ItemCost )
+ REGISTER_STAT_STRING( ItemID )
+ END_STAT_TABLE()
+};
+typedef CUtlVector< SMarketPurchases* > VectorMarketPurchaseData;
+
+struct WeaponStats
+{
+ int shots;
+ int hits;
+ int kills;
+ int damage;
+};
+
+struct SCSSWeaponData : public BaseStatData
+{
+ SCSSWeaponData( const char *pWeaponName, const WeaponStats &wpnData )
+ {
+ if ( pWeaponName )
+ {
+ Q_strncpy( WeaponID, pWeaponName, ARRAYSIZE(WeaponID) );
+ }
+ else
+ {
+ Q_strncpy( WeaponID, "unknown", ARRAYSIZE(WeaponID) );
+ }
+
+ Shots = wpnData.shots;
+ Hits = wpnData.hits;
+ Kills = wpnData.kills;
+ Damage = wpnData.damage;
+ }
+
+ char WeaponID[64];
+ int Shots;
+ int Hits;
+ int Kills;
+ int Damage;
+
+ BEGIN_STAT_TABLE( "CSSWeaponData" )
+ REGISTER_STAT_STRING( WeaponID )
+ REGISTER_STAT( Shots )
+ REGISTER_STAT( Hits )
+ REGISTER_STAT( Kills )
+ REGISTER_STAT( Damage )
+ END_STAT_TABLE()
+};
+typedef CUtlVector< SCSSWeaponData* > CSSWeaponData;
+
+struct SCSSDeathData : public BaseStatData
+{
+ SCSSDeathData( CBasePlayer *pVictim, const CTakeDamageInfo &info )
+ {
+ m_bUseGlobalData = false;
+
+ m_DeathPos = info.GetDamagePosition();
+ m_iVictimTeam = pVictim->GetTeamNumber();
+
+ CCSPlayer *pCSPlayer = ToCSPlayer( info.GetAttacker() );
+ if ( pCSPlayer )
+ {
+ m_iKillerTeam = pCSPlayer->GetTeamNumber();
+ }
+ else
+ {
+ m_iKillerTeam = -1;
+ }
+
+ const char *pszWeaponName = info.GetInflictor() ? info.GetInflictor()->GetClassname() : "unknown";
+
+ if ( pszWeaponName )
+ {
+ if ( V_strcmp(pszWeaponName, "player") == 0 )
+ {
+ // get the player's weapon
+ if ( pCSPlayer && pCSPlayer->GetActiveCSWeapon() )
+ {
+ pszWeaponName = pCSPlayer->GetActiveCSWeapon()->GetClassname();
+ }
+ }
+ }
+
+
+ m_uiDeathParam = WEAPON_NONE;
+ if ( (m_uiDeathParam = AliasToWeaponID( pszWeaponName )) == WEAPON_NONE )
+ {
+ m_uiDeathParam = AliasToWeaponID( pszWeaponName );
+ }
+
+ const char *pszMapName = gpGlobals->mapname.ToCStr() ? gpGlobals->mapname.ToCStr() : "unknown";
+ Q_strncpy( m_szMapName, pszMapName, ARRAYSIZE(m_szMapName) );
+ }
+ Vector m_DeathPos;
+ int m_iVictimTeam;
+ int m_iKillerTeam;
+ int m_iDamageType;
+ uint64 m_uiDeathParam;
+ char m_szMapName[64];
+
+ BEGIN_STAT_TABLE( "Deaths" )
+ REGISTER_STAT_NAMED( m_DeathPos.x, "XCoord" )
+ REGISTER_STAT_NAMED( m_DeathPos.y, "YCoord" )
+ REGISTER_STAT_NAMED( m_DeathPos.z, "ZCoord" )
+ REGISTER_STAT_NAMED( m_iVictimTeam, "Team" )
+ REGISTER_STAT_NAMED( m_iKillerTeam, "DeathCause" )
+ REGISTER_STAT_NAMED( m_uiDeathParam, "DeathParam" )
+ REGISTER_STAT_NAMED( m_szMapName, "DeathMap" )
+ END_STAT_TABLE()
+};
+typedef CUtlVector< SCSSDeathData* > CSSDeathData;
+
+//=============================================================================
+//
+// CS Game Stats Class
+//
+class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame, public IGameStatTracker
+{
+public:
+
+ // Constructor/Destructor.
+ CCSGameStats( void );
+ ~CCSGameStats( void );
+
+ virtual void Clear( void );
+ virtual bool Init();
+ virtual void PreClientUpdate();
+ virtual void PostInit();
+ virtual void LevelShutdownPreClearSteamAPIContext();
+
+ void UploadRoundStats( void );
+ // Overridden events
+ virtual void Event_LevelInit( void );
+ virtual void Event_LevelShutdown( float flElapsed );
+ virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
+ virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
+ virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
+ virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
+ void UpdatePlayerRoundStats(int winner);
+ virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
+ virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
+ virtual void Event_WindowShattered( CBasePlayer *pPlayer );
+ virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
+
+
+ // CSS specific events
+ void Event_BombPlanted( CCSPlayer *pPlayer );
+ void Event_BombDefused( CCSPlayer *pPlayer );
+ void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
+ void Event_BombExploded( CCSPlayer *pPlayer );
+ void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
+ void Event_MoneySpent( CCSPlayer *pPlayer, int moneySpent, const char *pItemName );
+ void Event_HostageRescued( CCSPlayer *pPlayer );
+ void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
+ void Event_AllHostagesRescued();
+ void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
+ void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
+ void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
+ void Event_PlayerRevenge( CCSPlayer* pAttacker );
+ void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
+ void Event_MVPEarned( CCSPlayer* pPlayer );
+ void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void DumpMatchWeaponMetrics();
+
+ const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
+ void ResetPlayerStats( CBasePlayer *pPlayer );
+ void ResetKillHistory( CBasePlayer *pPlayer );
+ void ResetRoundStats();
+ void ResetPlayerClassMatchStats();
+
+ const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
+ void ResetAllTeamStats();
+ void ResetAllStats();
+ void ResetWeaponStats();
+ void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
+ void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
+ void CalculateOverkill( CCSPlayer* pAttacker, CCSPlayer* pVictim );
+ void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
+
+ void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false );
+ // Steamworks Gamestats
+ virtual void SubmitGameStats( KeyValues *pKV );
+
+ virtual StatContainerList_t* GetStatContainerList( void )
+ {
+ return s_StatLists;
+ }
+
+protected:
+ void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
+ void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
+ void ComputeRollingStatAverages();
+ void ComputeDirectStatAverages();
+ void SendRollingStatsAveragesToAllPlayers();
+ void SendDirectStatsAveragesToAllPlayers();
+ void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
+
+private:
+ PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
+ StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
+
+ RoundStatsRollingAverage_t m_rollingCTStatAverages;
+ RoundStatsRollingAverage_t m_rollingTStatAverages;
+ RoundStatsRollingAverage_t m_rollingPlayerStatAverages;
+
+ RoundStatsDirectAverage_t m_directCTStatAverages;
+ RoundStatsDirectAverage_t m_directTStatAverages;
+ RoundStatsDirectAverage_t m_directPlayerStatAverages;
+
+ float m_fDisseminationTimerLow; // how long since last medium priority stat update
+ float m_fDisseminationTimerHigh; // how long since last high priority stat update
+
+ int m_numberOfRoundsForDirectAverages;
+ int m_numberOfTerroristEntriesForDirectAverages;
+ int m_numberOfCounterTerroristEntriesForDirectAverages;
+
+ CUtlDict< CSStatType_t, short > m_PropStatTable;
+
+ CUtlLinkedList<sHappyCamperSnipePosition, int> m_PlayerSnipedPosition;
+ WeaponStats m_weaponStats[WEAPON_MAX][2];
+
+ // Steamworks Gamestats
+ VectorMarketPurchaseData m_MarketPurchases;
+ CSSWeaponData m_WeaponData;
+ CSSDeathData m_DeathData;
+
+ // A static list of all the stat containers, one for each data structure being tracked
+ static StatContainerList_t * s_StatLists;
+
+ bool m_bInRound;
+};
+
+extern CCSGameStats CCS_GameStats;
+
+#endif // CS_GAMESTATS_H
diff --git a/game/server/cstrike/cs_hltvdirector.cpp b/game/server/cstrike/cs_hltvdirector.cpp
new file mode 100644
index 0000000..30f451a
--- /dev/null
+++ b/game/server/cstrike/cs_hltvdirector.cpp
@@ -0,0 +1,149 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "hltvdirector.h"
+#include "igameevents.h"
+
+class CCSHLTVDirector : public CHLTVDirector
+{
+public:
+ DECLARE_CLASS( CCSHLTVDirector, CHLTVDirector );
+
+ const char** GetModEvents();
+ void SetHLTVServer( IHLTVServer *hltv );
+ void CreateShotFromEvent( CHLTVGameEvent *event );
+
+};
+
+void CCSHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
+{
+ BaseClass::SetHLTVServer( hltv );
+
+ if ( m_pHLTVServer )
+ {
+ // mod specific events the director uses to find interesting shots
+ ListenForGameEvent( "hostage_rescued" );
+ ListenForGameEvent( "hostage_killed" );
+ ListenForGameEvent( "hostage_hurt" );
+ ListenForGameEvent( "hostage_follows" );
+ ListenForGameEvent( "bomb_pickup" );
+ ListenForGameEvent( "bomb_dropped" );
+ ListenForGameEvent( "bomb_exploded" );
+ ListenForGameEvent( "bomb_defused" );
+ ListenForGameEvent( "bomb_planted" );
+ ListenForGameEvent( "vip_escaped" );
+ ListenForGameEvent( "vip_killed" );
+ }
+}
+
+void CCSHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
+{
+ // show event at least for 2 more seconds after it occured
+ const char *name = event->m_Event->GetName();
+ IGameEvent *shot = NULL;
+
+ if ( !Q_strcmp( "hostage_rescued", name ) ||
+ !Q_strcmp( "hostage_hurt", name ) ||
+ !Q_strcmp( "hostage_follows", name ) ||
+ !Q_strcmp( "hostage_killed", name ) )
+ {
+ CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
+
+ if ( !player )
+ return;
+
+ // shot player as primary, hostage as secondary target
+ shot = gameeventmanager->CreateEvent( "hltv_chase", true );
+ shot->SetInt( "target1", player->entindex() );
+ shot->SetInt( "target2", event->m_Event->GetInt("hostage") );
+ shot->SetFloat( "distance", 96.0f );
+ shot->SetInt( "theta", 40 );
+ shot->SetInt( "phi", 20 );
+
+ // shot 2 seconds after event
+ m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
+ m_iPVSEntity = player->entindex();
+ }
+
+ else if ( !Q_strcmp( "bomb_pickup", name ) ||
+ !Q_strcmp( "bomb_dropped", name ) ||
+ !Q_strcmp( "bomb_planted", name ) ||
+ !Q_strcmp( "bomb_defused", name ) )
+ {
+ CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
+
+ if ( !player )
+ return;
+
+ shot = gameeventmanager->CreateEvent( "hltv_chase", true );
+ shot->SetInt( "target1", player->entindex() );
+ shot->SetInt( "target2", 0 );
+ shot->SetFloat( "distance", 64.0f );
+ shot->SetInt( "theta", 200 );
+ shot->SetInt( "phi", 10 );
+
+ // shot 2 seconds after pickup
+ m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
+ m_iPVSEntity = player->entindex();
+ }
+ else
+ {
+ // let baseclass create a shot
+ BaseClass::CreateShotFromEvent( event );
+
+ return;
+ }
+
+ if ( shot )
+ {
+ m_pHLTVServer->BroadcastEvent( shot );
+ gameeventmanager->FreeEvent( shot );
+ DevMsg("DrcCmd: %s\n", name );
+ }
+}
+
+const char** CCSHLTVDirector::GetModEvents()
+{
+ // game events relayed to spectator clients
+ static const char *s_modevents[] =
+ {
+ "hltv_status",
+ "hltv_chat",
+ "player_connect",
+ "player_disconnect",
+ "player_team",
+ "player_info",
+ "server_cvar",
+ "player_death",
+ "player_chat",
+ "round_start",
+ "round_end",
+ // additional CS:S events:
+ "bomb_planted",
+ "bomb_defused",
+ "hostage_killed",
+ "hostage_hurt",
+ NULL
+ };
+
+ return s_modevents;
+}
+
+static CCSHLTVDirector s_HLTVDirector; // singleton
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
+
+CHLTVDirector* HLTVDirector()
+{
+ return &s_HLTVDirector;
+}
+
+IGameSystem* HLTVDirectorSystem()
+{
+ return &s_HLTVDirector;
+} \ No newline at end of file
diff --git a/game/server/cstrike/cs_nav.h b/game/server/cstrike/cs_nav.h
new file mode 100644
index 0000000..f37b90d
--- /dev/null
+++ b/game/server/cstrike/cs_nav.h
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav.h
+// Data structures and constants for the Navigation Mesh system
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#ifndef _CS_NAV_H_
+#define _CS_NAV_H_
+
+#include "nav.h"
+
+/**
+ * Below are several constants used by the navigation system.
+ * @todo Move these into TheNavMesh singleton.
+ */
+const float BotRadius = 10.0f; ///< circular extent that contains bot
+
+class CNavArea;
+class CSNavNode;
+
+#if 0
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if given entity can be ignored when moving
+ */
+#define WALK_THRU_DOORS 0x01
+#define WALK_THRU_BREAKABLES 0x02
+#define WALK_THRU_TOGGLE_BRUSHES 0x04
+#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES | WALK_THRU_TOGGLE_BRUSHES)
+inline bool IsEntityWalkable( CBaseEntity *entity, unsigned int flags )
+{
+ if (FClassnameIs( entity, "worldspawn" ))
+ return false;
+
+ if (FClassnameIs( entity, "player" ))
+ return false;
+
+ // if we hit a door, assume its walkable because it will open when we touch it
+ if (FClassnameIs( entity, "prop_door*" ) || FClassnameIs( entity, "func_door*" ))
+ return (flags & WALK_THRU_DOORS) ? true : false;
+
+ // if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
+ if (FClassnameIs( entity, "func_brush" ))
+ {
+ CFuncBrush *brush = (CFuncBrush *)entity;
+ switch ( brush->m_iSolidity )
+ {
+ case CFuncBrush::BRUSHSOLID_ALWAYS:
+ return false;
+ case CFuncBrush::BRUSHSOLID_NEVER:
+ return true;
+ case CFuncBrush::BRUSHSOLID_TOGGLE:
+ return (flags & WALK_THRU_TOGGLE_BRUSHES) ? true : false;
+ }
+ }
+
+ // if we hit a breakable object, assume its walkable because we will shoot it when we touch it
+ if (FClassnameIs( entity, "func_breakable" ) && entity->GetHealth() && entity->m_takedamage == DAMAGE_YES)
+ return (flags & WALK_THRU_BREAKABLES) ? true : false;
+
+ if (FClassnameIs( entity, "func_breakable_surf" ) && entity->m_takedamage == DAMAGE_YES)
+ return (flags & WALK_THRU_BREAKABLES) ? true : false;
+
+ return false;
+}
+#endif
+
+#endif // _CS_NAV_H_
diff --git a/game/server/cstrike/cs_nav_area.cpp b/game/server/cstrike/cs_nav_area.cpp
new file mode 100644
index 0000000..b1a2464
--- /dev/null
+++ b/game/server/cstrike/cs_nav_area.cpp
@@ -0,0 +1,473 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_area.cpp
+// AI Navigation areas
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#include "cbase.h"
+#include "cs_nav_mesh.h"
+#include "cs_nav_area.h"
+#include "nav_pathfind.h"
+#include "nav_colors.h"
+#include "fmtstr.h"
+#include "props_shared.h"
+#include "func_breakablesurf.h"
+#include "Color.h"
+#include "collisionutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Constructor used during normal runtime.
+ */
+CCSNavArea::CCSNavArea( void )
+{
+ m_approachCount = 0;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Destructor
+ */
+CCSNavArea::~CCSNavArea()
+{
+}
+
+
+void CCSNavArea::OnServerActivate( void )
+{
+ CNavArea::OnServerActivate();
+
+}
+
+void CCSNavArea::OnRoundRestart( void )
+{
+ CNavArea::OnRoundRestart();
+}
+
+
+void CCSNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
+{
+ CNavArea::Save( fileBuffer, version );
+
+ //
+ // Save the approach areas for this area
+ //
+
+ // save number of approach areas
+ fileBuffer.PutUnsignedChar(m_approachCount);
+
+ // save approach area info
+ for( int a=0; a<m_approachCount; ++a )
+ {
+ if (m_approach[a].here.area)
+ fileBuffer.PutUnsignedInt(m_approach[a].here.area->GetID());
+ else
+ fileBuffer.PutUnsignedInt(0);
+
+ if (m_approach[a].prev.area)
+ fileBuffer.PutUnsignedInt(m_approach[a].prev.area->GetID());
+ else
+ fileBuffer.PutUnsignedInt(0);
+ fileBuffer.PutUnsignedChar(m_approach[a].prevToHereHow);
+
+ if (m_approach[a].next.area)
+ fileBuffer.PutUnsignedInt(m_approach[a].next.area->GetID());
+ else
+ fileBuffer.PutUnsignedInt(0);
+ fileBuffer.PutUnsignedChar(m_approach[a].hereToNextHow);
+ }
+}
+
+NavErrorType CCSNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
+{
+ if ( version < 15 )
+ return LoadLegacy(fileBuffer, version, subVersion);
+
+ // load base class data
+ NavErrorType error = CNavArea::Load( fileBuffer, version, subVersion );
+
+ switch ( subVersion )
+ {
+ case 1:
+ //
+ // Load number of approach areas
+ //
+ m_approachCount = fileBuffer.GetUnsignedChar();
+
+ // load approach area info (IDs)
+ for( int a = 0; a < m_approachCount; ++a )
+ {
+ m_approach[a].here.id = fileBuffer.GetUnsignedInt();
+
+ m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
+ m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
+
+ m_approach[a].next.id = fileBuffer.GetUnsignedInt();
+ m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
+ }
+
+ if ( !fileBuffer.IsValid() )
+ error = NAV_INVALID_FILE;
+
+ // fall through
+
+ case 0:
+ // legacy version
+ break;
+
+ default:
+ Warning( "Unknown NavArea sub-version number\n" );
+ error = NAV_INVALID_FILE;
+ }
+
+ return error;
+}
+
+
+NavErrorType CCSNavArea::PostLoad( void )
+{
+ NavErrorType error = CNavArea::PostLoad();
+
+ // resolve approach area IDs
+ for ( int a = 0; a < m_approachCount; ++a )
+ {
+ m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
+ if (m_approach[a].here.id && m_approach[a].here.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
+ if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
+ if (m_approach[a].next.id && m_approach[a].next.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ return error;
+}
+
+
+void CCSNavArea::Draw( void ) const
+{
+ CNavArea::Draw();
+}
+
+void CCSNavArea::CustomAnalysis( bool isIncremental /*= false */ )
+{
+ ComputeApproachAreas();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load legacy navigation area from the file
+ */
+NavErrorType CCSNavArea::LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
+{
+ // load ID
+ m_id = fileBuffer.GetUnsignedInt();
+
+ // update nextID to avoid collisions
+ if (m_id >= m_nextID)
+ m_nextID = m_id+1;
+
+ // load attribute flags
+ if ( version <= 8 )
+ {
+ m_attributeFlags = fileBuffer.GetUnsignedChar();
+ }
+ else if ( version < 13 )
+ {
+ m_attributeFlags = fileBuffer.GetUnsignedShort();
+ }
+ else
+ {
+ m_attributeFlags = fileBuffer.GetInt();
+ }
+
+ // load extent of area
+ fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
+ fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
+
+ m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
+ m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
+ m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
+
+ if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
+ {
+ m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
+ m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
+ }
+ else
+ {
+ m_invDxCorners = m_invDyCorners = 0;
+
+ DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
+ m_id, m_center.x, m_center.y, m_center.z );
+ }
+
+ // load heights of implicit corners
+ m_neZ = fileBuffer.GetFloat();
+ m_swZ = fileBuffer.GetFloat();
+
+ CheckWaterLevel();
+
+ // load connections (IDs) to adjacent areas
+ // in the enum order NORTH, EAST, SOUTH, WEST
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ // load number of connections for this direction
+ unsigned int count = fileBuffer.GetUnsignedInt();
+ Assert( fileBuffer.IsValid() );
+
+ m_connect[d].EnsureCapacity( count );
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavConnect connect;
+ connect.id = fileBuffer.GetUnsignedInt();
+ Assert( fileBuffer.IsValid() );
+
+ // don't allow self-referential connections
+ if ( connect.id != m_id )
+ {
+ m_connect[d].AddToTail( connect );
+ }
+ }
+ }
+
+ //
+ // Load hiding spots
+ //
+
+ // load number of hiding spots
+ unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
+
+ if (version == 1)
+ {
+ // load simple vector array
+ Vector pos;
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ fileBuffer.Get( &pos, 3 * sizeof(float) );
+
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+ spot->SetPosition( pos );
+ spot->SetFlags( HidingSpot::IN_COVER );
+ m_hidingSpots.AddToTail( spot );
+ }
+ }
+ else
+ {
+ // load HidingSpot objects for this area
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+
+ spot->Load( fileBuffer, version );
+
+ m_hidingSpots.AddToTail( spot );
+ }
+ }
+
+ if ( version < 15 )
+ {
+ //
+ // Load number of approach areas
+ //
+ m_approachCount = fileBuffer.GetUnsignedChar();
+
+ // load approach area info (IDs)
+ for( int a = 0; a < m_approachCount; ++a )
+ {
+ m_approach[a].here.id = fileBuffer.GetUnsignedInt();
+
+ m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
+ m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
+
+ m_approach[a].next.id = fileBuffer.GetUnsignedInt();
+ m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
+ }
+ }
+
+
+ //
+ // Load encounter paths for this area
+ //
+ unsigned int count = fileBuffer.GetUnsignedInt();
+
+ if (version < 3)
+ {
+ // old data, read and discard
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter encounter;
+
+ encounter.from.id = fileBuffer.GetUnsignedInt();
+ encounter.to.id = fileBuffer.GetUnsignedInt();
+
+ fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
+ fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
+
+ // read list of spots along this path
+ unsigned char spotCount = fileBuffer.GetUnsignedChar();
+
+ for( int s=0; s<spotCount; ++s )
+ {
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ }
+ }
+ return NAV_OK;
+ }
+
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter *encounter = new SpotEncounter;
+
+ encounter->from.id = fileBuffer.GetUnsignedInt();
+
+ unsigned char dir = fileBuffer.GetUnsignedChar();
+ encounter->fromDir = static_cast<NavDirType>( dir );
+
+ encounter->to.id = fileBuffer.GetUnsignedInt();
+
+ dir = fileBuffer.GetUnsignedChar();
+ encounter->toDir = static_cast<NavDirType>( dir );
+
+ // read list of spots along this path
+ unsigned char spotCount = fileBuffer.GetUnsignedChar();
+
+ SpotOrder order;
+ for( int s=0; s<spotCount; ++s )
+ {
+ order.id = fileBuffer.GetUnsignedInt();
+
+ unsigned char t = fileBuffer.GetUnsignedChar();
+
+ order.t = (float)t/255.0f;
+
+ encounter->spots.AddToTail( order );
+ }
+
+ m_spotEncounters.AddToTail( encounter );
+ }
+
+ if (version < 5)
+ return NAV_OK;
+
+ //
+ // Load Place data
+ //
+ PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
+
+ // convert entry to actual Place
+ SetPlace( placeDirectory.IndexToPlace( entry ) );
+
+ if ( version < 7 )
+ return NAV_OK;
+
+ // load ladder data
+ for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
+ {
+ count = fileBuffer.GetUnsignedInt();
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavLadderConnect connect;
+ connect.id = fileBuffer.GetUnsignedInt();
+
+ bool alreadyConnected = false;
+ FOR_EACH_VEC( m_ladder[dir], j )
+ {
+ if ( m_ladder[dir][j].id == connect.id )
+ {
+ alreadyConnected = true;
+ break;
+ }
+ }
+
+ if ( !alreadyConnected )
+ {
+ m_ladder[dir].AddToTail( connect );
+ }
+ }
+ }
+
+ if ( version < 8 )
+ return NAV_OK;
+
+ // load earliest occupy times
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ m_earliestOccupyTime[i] = fileBuffer.GetFloat();
+ }
+
+ if ( version < 11 )
+ return NAV_OK;
+
+ // load light intensity
+ for ( int i=0; i<NUM_CORNERS; ++i )
+ {
+ m_lightIntensity[i] = fileBuffer.GetFloat();
+ }
+
+ if ( version < 16 )
+ return NAV_OK;
+
+ // load visibility information
+ unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
+ if ( !IsX360() )
+ {
+ m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
+ }
+ else
+ {
+/* TODO: Re-enable when latest 360 code gets integrated (MSB 5/5/09)
+ size_t nBytes = visibleAreaCount * sizeof( AreaBindInfo );
+ m_potentiallyVisibleAreas.~CAreaBindInfoArray();
+ new ( &m_potentiallyVisibleAreas ) CAreaBindInfoArray( (AreaBindInfo *)engine->AllocLevelStaticData( nBytes ), visibleAreaCount );
+*/
+ }
+
+ for( unsigned int j=0; j<visibleAreaCount; ++j )
+ {
+ AreaBindInfo info;
+ info.id = fileBuffer.GetUnsignedInt();
+ info.attributes = fileBuffer.GetUnsignedChar();
+
+ m_potentiallyVisibleAreas.AddToTail( info );
+ }
+
+ // read area from which we inherit visibility
+ m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
+
+ return NAV_OK;
+}
+
+
diff --git a/game/server/cstrike/cs_nav_area.h b/game/server/cstrike/cs_nav_area.h
new file mode 100644
index 0000000..e8d913c
--- /dev/null
+++ b/game/server/cstrike/cs_nav_area.h
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_area.h
+// Navigation areas
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#ifndef _CS_NAV_AREA_H_
+#define _CS_NAV_AREA_H_
+
+#include "nav_area.h"
+
+//-------------------------------------------------------------------------------------------------------------------
+/**
+ * A CNavArea is a rectangular region defining a walkable area in the environment
+ */
+class CCSNavArea : public CNavArea
+{
+public:
+ DECLARE_CLASS( CCSNavArea, CNavArea );
+
+ CCSNavArea( void );
+ ~CCSNavArea();
+
+ virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
+ virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
+
+ virtual void Draw( void ) const; // draw area for debugging & editing
+
+ virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
+ virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
+ virtual NavErrorType PostLoad( void ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
+
+ virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
+
+ //- approach areas ----------------------------------------------------------------------------------
+ struct ApproachInfo
+ {
+ NavConnect here; ///< the approach area
+ NavConnect prev; ///< the area just before the approach area on the path
+ NavTraverseType prevToHereHow;
+ NavConnect next; ///< the area just after the approach area on the path
+ NavTraverseType hereToNextHow;
+ };
+ const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; }
+ int GetApproachInfoCount( void ) const { return m_approachCount; }
+ void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning
+
+ //- player counting --------------------------------------------------------------------------------
+ void ClearPlayerCount( void ); ///< set the player count to zero
+
+protected:
+ NavErrorType LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion );
+
+
+private:
+ //- approach areas ----------------------------------------------------------------------------------
+ enum { MAX_APPROACH_AREAS = 16 };
+ ApproachInfo m_approach[ MAX_APPROACH_AREAS ];
+ unsigned char m_approachCount;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+//
+// Inlines
+//
+
+inline void CCSNavArea::ClearPlayerCount( void )
+{
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ m_playerCount[ i ] = 0;
+ }
+}
+
+#endif // _CS_NAV_AREA_H_
diff --git a/game/server/cstrike/cs_nav_edit.cpp b/game/server/cstrike/cs_nav_edit.cpp
new file mode 100644
index 0000000..b49bd23
--- /dev/null
+++ b/game/server/cstrike/cs_nav_edit.cpp
@@ -0,0 +1,69 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_edit.cpp
+// Implementation of Navigation Mesh edit mode
+// Author: Michael Booth, 2003-2004
+
+#include "cbase.h"
+#include "nav_mesh.h"
+#include "cs_nav_pathfind.h"
+#include "cs_nav_node.h"
+#include "nav_colors.h"
+#include "Color.h"
+#include "tier0/vprof.h"
+#include "collisionutils.h"
+
+ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_GAMEDLL, "Duration in seconds to show nav area ID and attributes while editing" );
+ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_GAMEDLL, "Snap to the nav generation grid when creating new nav areas" );
+ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_GAMEDLL, "If true, nav areas will be placed flush with the ground when created by hand." );
+
+#if DEBUG_NAV_NODES
+extern ConVar nav_show_nodes;
+#endif // DEBUG_NAV_NODES
+
+
+//--------------------------------------------------------------------------------------------------------------
+void EditNav_Precache(void *pUser)
+{
+ CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOn" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_TOGGLE_PLACE_MODE" );
+ CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOff" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_PLACE_PICK" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_DELETE" );
+ CBaseEntity::PrecacheScriptSound( "EDIT.ToggleAttribute" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Enable" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Disable" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Enable" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Disable" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.Enable" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.AllDirections" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.Added" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.MarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.Creating" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.NotCreating" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.Creating" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.NotCreating" );
+ CBaseEntity::PrecacheScriptSound( "EDIT_WARP_TO_MARK" );
+}
+
+#ifdef CSTRIKE_DLL
+PRECACHE_REGISTER_FN( EditNav_Precache );
+#endif
+
+
diff --git a/game/server/cstrike/cs_nav_file.cpp b/game/server/cstrike/cs_nav_file.cpp
new file mode 100644
index 0000000..3b7db5d
--- /dev/null
+++ b/game/server/cstrike/cs_nav_file.cpp
@@ -0,0 +1,1365 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_file.cpp
+// Reading and writing nav files
+// Author: Michael S. Booth ([email protected]), January-September 2003
+
+#include "cbase.h"
+#include "nav_mesh.h"
+
+#ifdef CSTRIKE_DLL
+#include "cs_shareddefs.h"
+#include "cs_nav_pathfind.h"
+#endif
+
+#if 0
+//--------------------------------------------------------------------------------------------------------------
+/// The current version of the nav file format
+const int NavCurrentVersion = 9;
+
+//--------------------------------------------------------------------------------------------------------------
+//
+// The 'place directory' is used to save and load places from
+// nav files in a size-efficient manner that also allows for the
+// order of the place ID's to change without invalidating the
+// nav files.
+//
+// The place directory is stored in the nav file as a list of
+// place name strings. Each nav area then contains an index
+// into that directory, or zero if no place has been assigned to
+// that area.
+//
+class PlaceDirectory
+{
+public:
+
+ typedef unsigned short IndexType;
+
+ void Reset( void )
+ {
+ m_directory.RemoveAll();
+ }
+
+ /// return true if this place is already in the directory
+ bool IsKnown( Place place ) const
+ {
+ return m_directory.HasElement( place );
+ }
+
+ /// return the directory index corresponding to this Place (0 = no entry)
+ IndexType GetIndex( Place place ) const
+ {
+ if (place == UNDEFINED_PLACE)
+ return 0;
+
+ int i = m_directory.Find( place );
+
+ if (i < 0)
+ {
+ Assert( false && "PlaceDirectory::GetIndex failure" );
+ return 0;
+ }
+
+ return (IndexType)(i+1);
+ }
+
+ /// add the place to the directory if not already known
+ void AddPlace( Place place )
+ {
+ if (place == UNDEFINED_PLACE)
+ return;
+
+ Assert( place < 1000 );
+
+ if (IsKnown( place ))
+ return;
+
+ m_directory.AddToTail( place );
+ }
+
+ /// given an index, return the Place
+ Place IndexToPlace( IndexType entry ) const
+ {
+ if (entry == 0)
+ return UNDEFINED_PLACE;
+
+ int i = entry-1;
+
+ if (i >= m_directory.Count())
+ {
+ Assert( false && "PlaceDirectory::IndexToPlace: Invalid entry" );
+ return UNDEFINED_PLACE;
+ }
+
+ return m_directory[ i ];
+ }
+
+ /// store the directory
+ void Save( FileHandle_t file )
+ {
+ // store number of entries in directory
+ IndexType count = (IndexType)m_directory.Count();
+ filesystem->Write( &count, sizeof(IndexType), file );
+
+ // store entries
+ for( int i=0; i<m_directory.Count(); ++i )
+ {
+ const char *placeName = TheNavMesh->PlaceToName( m_directory[i] );
+
+ // store string length followed by string itself
+ unsigned short len = (unsigned short)(strlen( placeName ) + 1);
+ filesystem->Write( &len, sizeof(unsigned short), file );
+ filesystem->Write( placeName, len, file );
+ }
+ }
+
+ /// load the directory
+ void Load( FileHandle_t file )
+ {
+ // read number of entries
+ IndexType count;
+ filesystem->Read( &count, sizeof(IndexType), file );
+
+ m_directory.RemoveAll();
+
+ // read each entry
+ char placeName[256];
+ unsigned short len;
+ for( int i=0; i<count; ++i )
+ {
+ filesystem->Read( &len, sizeof(unsigned short), file );
+ filesystem->Read( placeName, len, file );
+
+ AddPlace( TheNavMesh->NameToPlace( placeName ) );
+ }
+ }
+
+private:
+ CUtlVector< Place > m_directory;
+};
+
+static PlaceDirectory placeDirectory;
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Replace extension with "bsp"
+ */
+char *GetBspFilename( const char *navFilename )
+{
+ static char bspFilename[256];
+
+ Q_snprintf( bspFilename, sizeof( bspFilename ), "maps\\%s.bsp", STRING( gpGlobals->mapname ) );
+
+ int len = strlen( bspFilename );
+ if (len < 3)
+ return NULL;
+
+ bspFilename[ len-3 ] = 'b';
+ bspFilename[ len-2 ] = 's';
+ bspFilename[ len-1 ] = 'p';
+
+ return bspFilename;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/*
+void CNavArea::Save( FILE *fp ) const
+{
+ fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.lo.y, m_extent.lo.z );
+ fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.lo.y, m_neZ );
+ fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.hi.y, m_extent.hi.z );
+ fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.hi.y, m_swZ );
+
+ static int base = 1;
+ fprintf( fp, "\n\ng %04dArea%s%s%s%s\n", m_id,
+ (GetAttributes() & BOT_NAV_CROUCH) ? "CROUCH" : "",
+ (GetAttributes() & BOT_NAV_JUMP) ? "JUMP" : "",
+ (GetAttributes() & BOT_NAV_PRECISE) ? "PRECISE" : "",
+ (GetAttributes() & BOT_NAV_NO_JUMP) ? "NO_JUMP" : "" );
+ fprintf( fp, "f %d %d %d %d\n\n", base, base+1, base+2, base+3 );
+ base += 4;
+}
+*/
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Save a navigation area to the opened binary stream
+ */
+void CNavArea::Save( FileHandle_t file, unsigned int version ) const
+{
+ // save ID
+ filesystem->Write( &m_id, sizeof(unsigned int), file );
+
+ // save attribute flags
+ filesystem->Write( &m_attributeFlags, sizeof(unsigned short), file );
+
+ // save extent of area
+ filesystem->Write( &m_extent, 6*sizeof(float), file );
+
+ // save heights of implicit corners
+ filesystem->Write( &m_neZ, sizeof(float), file );
+ filesystem->Write( &m_swZ, sizeof(float), file );
+
+ // save connections to adjacent areas
+ // in the enum order NORTH, EAST, SOUTH, WEST
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ // save number of connections for this direction
+ unsigned int count = m_connect[d].Count();
+ filesystem->Write( &count, sizeof(unsigned int), file );
+
+ FOR_EACH_LL( m_connect[d], it )
+ {
+ NavConnect connect = m_connect[d][ it ];
+ filesystem->Write( &connect.area->m_id, sizeof(unsigned int), file );
+ }
+ }
+
+ //
+ // Store hiding spots for this area
+ //
+ unsigned char count;
+ if (m_hidingSpotList.Count() > 255)
+ {
+ count = 255;
+ Warning( "Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id );
+ }
+ else
+ {
+ count = (unsigned char)m_hidingSpotList.Count();
+ }
+ filesystem->Write( &count, sizeof(unsigned char), file );
+
+ // store HidingSpot objects
+ unsigned int saveCount = 0;
+ FOR_EACH_LL( m_hidingSpotList, hit )
+ {
+ HidingSpot *spot = m_hidingSpotList[ hit ];
+
+ spot->Save( file, version );
+
+ // overflow check
+ if (++saveCount == count)
+ break;
+ }
+
+ //
+ // Save the approach areas for this area
+ //
+
+ // save number of approach areas
+ filesystem->Write( &m_approachCount, sizeof(unsigned char), file );
+
+ // save approach area info
+ unsigned char type;
+ unsigned int zero = 0;
+ for( int a=0; a<m_approachCount; ++a )
+ {
+ if (m_approach[a].here.area)
+ filesystem->Write( &m_approach[a].here.area->m_id, sizeof(unsigned int), file );
+ else
+ filesystem->Write( &zero, sizeof(unsigned int), file );
+
+ if (m_approach[a].prev.area)
+ filesystem->Write( &m_approach[a].prev.area->m_id, sizeof(unsigned int), file );
+ else
+ filesystem->Write( &zero, sizeof(unsigned int), file );
+ type = (unsigned char)m_approach[a].prevToHereHow;
+ filesystem->Write( &type, sizeof(unsigned char), file );
+
+ if (m_approach[a].next.area)
+ filesystem->Write( &m_approach[a].next.area->m_id, sizeof(unsigned int), file );
+ else
+ filesystem->Write( &zero, sizeof(unsigned int), file );
+ type = (unsigned char)m_approach[a].hereToNextHow;
+ filesystem->Write( &type, sizeof(unsigned char), file );
+ }
+
+ //
+ // Save encounter spots for this area
+ //
+ {
+ // save number of encounter paths for this area
+ unsigned int count = m_spotEncounterList.Count();
+ filesystem->Write( &count, sizeof(unsigned int), file );
+
+ SpotEncounter *e;
+ FOR_EACH_LL( m_spotEncounterList, it )
+ {
+ e = m_spotEncounterList[ it ];
+
+ if (e->from.area)
+ filesystem->Write( &e->from.area->m_id, sizeof(unsigned int), file );
+ else
+ filesystem->Write( &zero, sizeof(unsigned int), file );
+
+ unsigned char dir = (unsigned char)e->fromDir;
+ filesystem->Write( &dir, sizeof(unsigned char), file );
+
+ if (e->to.area)
+ filesystem->Write( &e->to.area->m_id, sizeof(unsigned int), file );
+ else
+ filesystem->Write( &zero, sizeof(unsigned int), file );
+
+ dir = (unsigned char)e->toDir;
+ filesystem->Write( &dir, sizeof(unsigned char), file );
+
+ // write list of spots along this path
+ unsigned char spotCount;
+ if (e->spotList.Count() > 255)
+ {
+ spotCount = 255;
+ Warning( "Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id );
+ }
+ else
+ {
+ spotCount = (unsigned char)e->spotList.Count();
+ }
+ filesystem->Write( &spotCount, sizeof(unsigned char), file );
+
+ saveCount = 0;
+ FOR_EACH_LL( e->spotList, sit )
+ {
+ SpotOrder *order = &e->spotList[ sit ];
+
+ // order->spot may be NULL if we've loaded a nav mesh that has been edited but not re-analyzed
+ unsigned int id = (order->spot) ? order->spot->GetID() : 0;
+ filesystem->Write( &id, sizeof(unsigned int), file );
+
+ unsigned char t = (unsigned char)(255 * order->t);
+ filesystem->Write( &t, sizeof(unsigned char), file );
+
+ // overflow check
+ if (++saveCount == spotCount)
+ break;
+ }
+ }
+ }
+
+ // store place dictionary entry
+ PlaceDirectory::IndexType entry = placeDirectory.GetIndex( GetPlace() );
+ filesystem->Write( &entry, sizeof(entry), file );
+
+ // write out ladder info
+ int i;
+ for ( i=0; i<CSNavLadder::NUM_LADDER_DIRECTIONS; ++i )
+ {
+ // save number of encounter paths for this area
+ unsigned int count = m_ladder[i].Count();
+ filesystem->Write( &count, sizeof(unsigned int), file );
+
+ NavLadderConnect ladder;
+ FOR_EACH_LL( m_ladder[i], it )
+ {
+ ladder = m_ladder[i][it];
+
+ unsigned int id = ladder.ladder->GetID();
+ filesystem->Write( &id, sizeof( id ), file );
+ }
+ }
+
+ // save earliest occupy times
+ for( i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ filesystem->Write( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load a navigation area from the file
+ */
+void CNavArea::Load( FileHandle_t file, unsigned int version )
+{
+ // load ID
+ filesystem->Read( &m_id, sizeof(unsigned int), file );
+
+ // update nextID to avoid collisions
+ if (m_id >= m_nextID)
+ m_nextID = m_id+1;
+
+ // load attribute flags
+ if ( version <= 8 )
+ {
+ unsigned char flags = 0;
+ filesystem->Read( &flags, sizeof(unsigned char), file );
+ m_attributeFlags = flags;
+ }
+ else
+ {
+ filesystem->Read( &m_attributeFlags, sizeof(unsigned short), file );
+ }
+
+ // load extent of area
+ filesystem->Read( &m_extent, 6*sizeof(float), file );
+
+ m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f;
+ m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f;
+ m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f;
+
+ // load heights of implicit corners
+ filesystem->Read( &m_neZ, sizeof(float), file );
+ filesystem->Read( &m_swZ, sizeof(float), file );
+
+ CheckWaterLevel();
+
+ // load connections (IDs) to adjacent areas
+ // in the enum order NORTH, EAST, SOUTH, WEST
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ // load number of connections for this direction
+ unsigned int count;
+ int result = filesystem->Read( &count, sizeof(unsigned int), file );
+ Assert( result == sizeof(unsigned int) );
+
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavConnect connect;
+ result = filesystem->Read( &connect.id, sizeof(unsigned int), file );
+ Assert( result == sizeof(unsigned int) );
+
+ // don't allow self-referential connections
+ if ( connect.id != m_id )
+ {
+ m_connect[d].AddToTail( connect );
+ }
+ }
+ }
+
+ //
+ // Load hiding spots
+ //
+
+ // load number of hiding spots
+ unsigned char hidingSpotCount;
+ filesystem->Read( &hidingSpotCount, sizeof(unsigned char), file );
+
+ if (version == 1)
+ {
+ // load simple vector array
+ Vector pos;
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ filesystem->Read( &pos, 3 * sizeof(float), file );
+
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+ spot->SetPosition( pos );
+ spot->SetFlags( HidingSpot::IN_COVER );
+ m_hidingSpotList.AddToTail( spot );
+ }
+ }
+ else
+ {
+ // load HidingSpot objects for this area
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+
+ spot->Load( file, version );
+
+ m_hidingSpotList.AddToTail( spot );
+ }
+ }
+
+ //
+ // Load number of approach areas
+ //
+ filesystem->Read( &m_approachCount, sizeof(unsigned char), file );
+
+ // load approach area info (IDs)
+ unsigned char type;
+ for( int a=0; a<m_approachCount; ++a )
+ {
+ filesystem->Read( &m_approach[a].here.id, sizeof(unsigned int), file );
+
+ filesystem->Read( &m_approach[a].prev.id, sizeof(unsigned int), file );
+ filesystem->Read( &type, sizeof(unsigned char), file );
+ m_approach[a].prevToHereHow = (NavTraverseType)type;
+
+ filesystem->Read( &m_approach[a].next.id, sizeof(unsigned int), file );
+ filesystem->Read( &type, sizeof(unsigned char), file );
+ m_approach[a].hereToNextHow = (NavTraverseType)type;
+ }
+
+
+ //
+ // Load encounter paths for this area
+ //
+ unsigned int count;
+ filesystem->Read( &count, sizeof(unsigned int), file );
+
+ if (version < 3)
+ {
+ // old data, read and discard
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter encounter;
+
+ filesystem->Read( &encounter.from.id, sizeof(unsigned int), file );
+ filesystem->Read( &encounter.to.id, sizeof(unsigned int), file );
+
+ filesystem->Read( &encounter.path.from.x, 3 * sizeof(float), file );
+ filesystem->Read( &encounter.path.to.x, 3 * sizeof(float), file );
+
+ // read list of spots along this path
+ unsigned char spotCount;
+ filesystem->Read( &spotCount, sizeof(unsigned char), file );
+
+ for( int s=0; s<spotCount; ++s )
+ {
+ Vector pos;
+ filesystem->Read( &pos, 3*sizeof(float), file );
+ filesystem->Read( &pos, sizeof(float), file );
+ }
+ }
+ return;
+ }
+
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter *encounter = new SpotEncounter;
+
+ filesystem->Read( &encounter->from.id, sizeof(unsigned int), file );
+
+ unsigned char dir;
+ filesystem->Read( &dir, sizeof(unsigned char), file );
+ encounter->fromDir = static_cast<NavDirType>( dir );
+
+ filesystem->Read( &encounter->to.id, sizeof(unsigned int), file );
+
+ filesystem->Read( &dir, sizeof(unsigned char), file );
+ encounter->toDir = static_cast<NavDirType>( dir );
+
+ // read list of spots along this path
+ unsigned char spotCount;
+ filesystem->Read( &spotCount, sizeof(unsigned char), file );
+
+ SpotOrder order;
+ for( int s=0; s<spotCount; ++s )
+ {
+ filesystem->Read( &order.id, sizeof(unsigned int), file );
+
+ unsigned char t;
+ filesystem->Read( &t, sizeof(unsigned char), file );
+
+ order.t = (float)t/255.0f;
+
+ encounter->spotList.AddToTail( order );
+ }
+
+ m_spotEncounterList.AddToTail( encounter );
+ }
+
+ if (version < 5)
+ return;
+
+ //
+ // Load Place data
+ //
+ PlaceDirectory::IndexType entry;
+ filesystem->Read( &entry, sizeof(entry), file );
+
+ // convert entry to actual Place
+ SetPlace( placeDirectory.IndexToPlace( entry ) );
+
+ if ( version < 7 )
+ return;
+
+ // load ladder data
+ for ( int dir=0; dir<CSNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
+ {
+ filesystem->Read( &count, sizeof(unsigned int), file );
+ {
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavLadderConnect connect;
+ filesystem->Read( &connect.id, sizeof(unsigned int), file );
+
+ bool alreadyConnected = false;
+ FOR_EACH_LL( m_ladder[dir], j )
+ {
+ if ( m_ladder[dir][j].id == connect.id )
+ {
+ alreadyConnected = true;
+ break;
+ }
+ }
+
+ if ( !alreadyConnected )
+ {
+ m_ladder[dir].AddToTail( connect );
+ }
+ }
+ }
+ }
+
+ if ( version < 8 )
+ return;
+
+ // load earliest occupy times
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ filesystem->Read( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Convert loaded IDs to pointers
+ * Make sure all IDs are converted, even if corrupt data is encountered.
+ */
+NavErrorType CNavArea::PostLoad( void )
+{
+ NavErrorType error = NAV_OK;
+
+ for ( int dir=0; dir < CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
+ {
+ FOR_EACH_VEC( m_ladder[dir], it )
+ {
+ NavLadderConnect& connect = m_ladder[dir][it];
+
+ unsigned int id = connect.id;
+
+ if ( TheNavMesh->GetLadders().Find( connect.ladder ) == TheNavMesh->GetLadders().InvalidIndex() )
+ {
+ connect.ladder = TheNavMesh->GetLadderByID( id );
+ }
+
+ if (id && connect.ladder == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation ladder data. Cannot connect Navigation Areas.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ }
+
+ // connect areas together
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ FOR_EACH_VEC( m_connect[d], it )
+ {
+ NavConnect *connect = &m_connect[ d ][ it ];
+
+ unsigned int id = connect->id;
+ connect->area = TheNavMesh->GetNavAreaByID( id );
+ if (id && connect->area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Cannot connect Navigation Areas.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ }
+
+ // resolve approach area IDs
+ for( int a=0; a<m_approachCount; ++a )
+ {
+ m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
+ if (m_approach[a].here.id && m_approach[a].here.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
+ if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
+ if (m_approach[a].next.id && m_approach[a].next.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+
+ // resolve spot encounter IDs
+ SpotEncounter *e;
+ FOR_EACH_VEC( m_spotEncounters, it )
+ {
+ e = m_spotEncounters[ it ];
+
+ e->from.area = TheNavMesh->GetNavAreaByID( e->from.id );
+ if (e->from.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"from\" Navigation Area for Encounter Spot.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ e->to.area = TheNavMesh->GetNavAreaByID( e->to.id );
+ if (e->to.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"to\" Navigation Area for Encounter Spot.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ if (e->from.area && e->to.area)
+ {
+ // compute path
+ float halfWidth;
+ ComputePortal( e->to.area, e->toDir, &e->path.to, &halfWidth );
+ ComputePortal( e->from.area, e->fromDir, &e->path.from, &halfWidth );
+
+ const float eyeHeight = HalfHumanHeight;
+ e->path.from.z = e->from.area->GetZ( e->path.from ) + eyeHeight;
+ e->path.to.z = e->to.area->GetZ( e->path.to ) + eyeHeight;
+ }
+
+ // resolve HidingSpot IDs
+ FOR_EACH_VEC( e->spots, sit )
+ {
+ SpotOrder *order = &e->spots[ sit ];
+
+ order->spot = GetHidingSpotByID( order->id );
+ if (order->spot == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Hiding Spot\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ }
+
+ // build overlap list
+ /// @todo Optimize this
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CNavArea *area = TheNavAreas[ nit ];
+
+ if (area == this)
+ continue;
+
+ if (IsOverlapping( area ))
+ m_overlappingAreas.AddToTail( area );
+ }
+
+ return error;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine the earliest time this hiding spot can be reached by either team
+ */
+void CNavArea::ComputeEarliestOccupyTimes( void )
+{
+#ifdef CSTRIKE_DLL
+ /// @todo Derive cstrike-specific navigation classes
+
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ m_earliestOccupyTime[i] = 120.0f;
+ }
+
+ if (nav_quicksave.GetBool())
+ return;
+
+ // maximum player speed in units/second
+ const float playerSpeed = 240.0f;
+
+ ShortestPathCost cost;
+ CBaseEntity *spot;
+
+ // determine the shortest time it will take a Terrorist to reach this area
+ int team = TEAM_TERRORIST % MAX_NAV_TEAMS;
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
+ {
+ float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
+ if (travelDistance < 0.0f)
+ continue;
+
+ float travelTime = travelDistance / playerSpeed;
+ if (travelTime < m_earliestOccupyTime[ team ])
+ {
+ m_earliestOccupyTime[ team ] = travelTime;
+ }
+ }
+
+
+ // determine the shortest time it will take a CT to reach this area
+ team = TEAM_CT % MAX_NAV_TEAMS;
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
+ {
+ float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
+ if (travelDistance < 0.0f)
+ continue;
+
+ float travelTime = travelDistance / playerSpeed;
+ if (travelTime < m_earliestOccupyTime[ team ])
+ {
+ m_earliestOccupyTime[ team ] = travelTime;
+ }
+ }
+
+#else
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ m_earliestOccupyTime[i] = 0.0f;
+ }
+#endif
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine if this area is a "battlefront" area - where two rushing teams first meet.
+ */
+void CNavMesh::ComputeBattlefrontAreas( void )
+{
+#if 0
+#ifdef CSTRIKE_DLL
+ ShortestPathCost cost;
+ CBaseEntity *tSpawn, *ctSpawn;
+
+ for( tSpawn = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ tSpawn;
+ tSpawn = gEntList.FindEntityByClassname( tSpawn, "info_player_terrorist" ) )
+ {
+ CNavArea *tArea = TheNavMesh->GetNavArea( tSpawn->GetAbsOrigin() );
+ if (tArea == NULL)
+ continue;
+
+ for( ctSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ ctSpawn;
+ ctSpawn = gEntList.FindEntityByClassname( ctSpawn, "info_player_counterterrorist" ) )
+ {
+ CNavArea *ctArea = TheNavMesh->GetNavArea( ctSpawn->GetAbsOrigin() );
+
+ if (ctArea == NULL)
+ continue;
+
+ if (tArea == ctArea)
+ {
+ m_isBattlefront = true;
+ return;
+ }
+
+ // build path between these two spawn points - assume if path fails, it at least got close
+ // (ie: imagine spawn points that you jump down from - can't path to)
+ CNavArea *goalArea = NULL;
+ NavAreaBuildPath( tArea, ctArea, NULL, cost, &goalArea );
+
+ if (goalArea == NULL)
+ continue;
+
+
+/**
+ * @todo Need to enumerate ALL paths between all pairs of spawn points to find all battlefront areas
+ */
+
+ // find the area with the earliest overlapping occupy times
+ CNavArea *battlefront = NULL;
+ float earliestTime = 999999.9f;
+
+ const float epsilon = 1.0f;
+ CNavArea *area;
+ for( area = goalArea; area; area = area->GetParent() )
+ {
+ if (fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) < epsilon)
+ {
+ }
+
+ }
+ }
+ }
+#endif
+#endif
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the filename for this map's "nav map" file
+ */
+const char *CNavMesh::GetFilename( void ) const
+{
+ // filename is local to game dir for Steam, so we need to prepend game dir for regular file save
+ char gamePath[256];
+ engine->GetGameDir( gamePath, 256 );
+
+ static char filename[256];
+ Q_snprintf( filename, sizeof( filename ), "%s\\maps\\%s.nav", gamePath, STRING( gpGlobals->mapname ) );
+
+ return filename;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/*
+============
+COM_FixSlashes
+
+Changes all '/' characters into '\' characters, in place.
+============
+*/
+inline void COM_FixSlashes( char *pname )
+{
+#ifdef _WIN32
+ while ( *pname )
+ {
+ if ( *pname == '/' )
+ *pname = '\\';
+ pname++;
+ }
+#else
+ while ( *pname )
+ {
+ if ( *pname == '\\' )
+ *pname = '/';
+ pname++;
+ }
+#endif
+}
+
+static void WarnIfMeshNeedsAnalysis( void )
+{
+ // Quick check to warn about needing to analyze: nav_strip, nav_delete, etc set
+ // every CNavArea's m_approachCount to 0, and delete their m_spotEncounterList.
+ // So, if no area has either, odds are good we need an analyze.
+ {
+ bool hasApproachAreas = false;
+ bool hasSpotEncounters = false;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+ if ( area->GetApproachInfoCount() )
+ {
+ hasApproachAreas = true;
+ }
+
+ if ( area->GetSpotEncounterCount() )
+ {
+ hasSpotEncounters = true;
+ }
+ }
+
+ if ( !hasApproachAreas || !hasSpotEncounters )
+ {
+ Warning( "The nav mesh needs a full nav_analyze\n" );
+ }
+ }
+}
+
+/**
+ * Store Navigation Mesh to a file
+ */
+bool CNavMesh::Save( void ) const
+{
+ WarnIfMeshNeedsAnalysis();
+
+ const char *filename = GetFilename();
+ if (filename == NULL)
+ return false;
+
+ //
+ // Store the NAV file
+ //
+ COM_FixSlashes( const_cast<char *>(filename) );
+
+ // get size of source bsp file for later (before we open the nav file for writing, in
+ // case of failure)
+ char *bspFilename = GetBspFilename( filename );
+ if (bspFilename == NULL)
+ {
+ return false;
+ }
+
+ FileHandle_t file = filesystem->Open( filename, "wb" );
+
+ if (!file)
+ {
+ return false;
+ }
+
+ // store "magic number" to help identify this kind of file
+ unsigned int magic = NAV_MAGIC_NUMBER;
+ filesystem->Write( &magic, sizeof(unsigned int), file );
+
+ // store version number of file
+ // 1 = hiding spots as plain vector array
+ // 2 = hiding spots as HidingSpot objects
+ // 3 = Encounter spots use HidingSpot ID's instead of storing vector again
+ // 4 = Includes size of source bsp file to verify nav data correlation
+ // ---- Beta Release at V4 -----
+ // 5 = Added Place info
+ // ---- Conversion to Src ------
+ // 6 = Added Ladder info
+ // 7 = Areas store ladder ID's so ladders can have one-way connections
+ // 8 = Added earliest occupy times (2 floats) to each area
+ // 9 = Promoted CNavArea's attribute flags to a short
+ unsigned int version = NavCurrentVersion;
+ filesystem->Write( &version, sizeof(unsigned int), file );
+
+ // store the size of source bsp file in the nav file
+ // so we can test if the bsp changed since the nav file was made
+ unsigned int bspSize = filesystem->Size( bspFilename );
+ DevMsg( "Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize );
+
+ filesystem->Write( &bspSize, sizeof(unsigned int), file );
+
+
+ //
+ // Build a directory of the Places in this map
+ //
+ placeDirectory.Reset();
+
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CNavArea *area = TheNavAreas[ nit ];
+
+ Place place = area->GetPlace();
+
+ if (place)
+ {
+ placeDirectory.AddPlace( place );
+ }
+ }
+
+ placeDirectory.Save( file );
+
+
+ //
+ // Store navigation areas
+ //
+ {
+ // store number of areas
+ unsigned int count = TheNavAreas.Count();
+ filesystem->Write( &count, sizeof(unsigned int), file );
+
+ // store each area
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ area->Save( file, version );
+ }
+ }
+
+ //
+ // Store ladders
+ //
+ {
+ // store number of ladders
+ unsigned int count = m_ladders.Count();
+ filesystem->Write( &count, sizeof(unsigned int), file );
+
+ // store each ladder
+ FOR_EACH_VEC( m_ladders, it )
+ {
+ CNavLadder *ladder = m_ladders[ it ];
+
+ ladder->Save( file, version );
+ }
+ }
+
+ filesystem->Flush( file );
+ filesystem->Close( file );
+
+ unsigned int navSize = filesystem->Size( filename );
+ DevMsg( "Size of nav file '%s' is %u bytes.\n", filename, navSize );
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+static NavErrorType CheckNavFile( const char *bspFilename )
+{
+ if ( !bspFilename )
+ return NAV_CANT_ACCESS_FILE;
+
+ char bspPathname[256];
+ char filename[256];
+ Q_strncpy( bspPathname, "maps/", sizeof( bspPathname ) );
+ Q_strncat( bspPathname, bspFilename, sizeof( bspPathname ), COPY_ALL_CHARACTERS );
+ Q_strncpy( filename, bspPathname, sizeof( filename ) );
+ Q_SetExtension( filename, ".nav", sizeof( filename ) );
+
+ bool navIsInBsp = false;
+ FileHandle_t file = filesystem->Open( filename, "rb", "MOD" ); // this ignores .nav files embedded in the .bsp ...
+ if ( !file )
+ {
+ navIsInBsp = true;
+ file = filesystem->Open( filename, "rb", "GAME" ); // ... and this looks for one if it's the only one around.
+ }
+
+ if (!file)
+ {
+ return NAV_CANT_ACCESS_FILE;
+ }
+
+ // check magic number
+ int result;
+ unsigned int magic;
+ result = filesystem->Read( &magic, sizeof(unsigned int), file );
+ if (!result || magic != NAV_MAGIC_NUMBER)
+ {
+ filesystem->Close( file );
+ return NAV_INVALID_FILE;
+ }
+
+ // read file version number
+ unsigned int version;
+ result = filesystem->Read( &version, sizeof(unsigned int), file );
+ if (!result || version > NavCurrentVersion || version < 4)
+ {
+ filesystem->Close( file );
+ return NAV_BAD_FILE_VERSION;
+ }
+
+ // get size of source bsp file and verify that the bsp hasn't changed
+ unsigned int saveBspSize;
+ filesystem->Read( &saveBspSize, sizeof(unsigned int), file );
+
+ // verify size
+ unsigned int bspSize = filesystem->Size( bspPathname );
+
+ if (bspSize != saveBspSize && !navIsInBsp)
+ {
+ return NAV_FILE_OUT_OF_DATE;
+ }
+
+ return NAV_OK;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CommandNavCheckFileConsistency( void )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ FileFindHandle_t findHandle;
+ const char *bspFilename = filesystem->FindFirstEx( "maps/*.bsp", "MOD", &findHandle );
+ while ( bspFilename )
+ {
+ switch ( CheckNavFile( bspFilename ) )
+ {
+ case NAV_CANT_ACCESS_FILE:
+ Warning( "Missing nav file for %s\n", bspFilename );
+ break;
+ case NAV_INVALID_FILE:
+ Warning( "Invalid nav file for %s\n", bspFilename );
+ break;
+ case NAV_BAD_FILE_VERSION:
+ Warning( "Old nav file for %s\n", bspFilename );
+ break;
+ case NAV_FILE_OUT_OF_DATE:
+ Warning( "The nav file for %s is built from an old version of the map\n", bspFilename );
+ break;
+ case NAV_OK:
+ Msg( "The nav file for %s is up-to-date\n", bspFilename );
+ break;
+ }
+
+ bspFilename = filesystem->FindNext( findHandle );
+ }
+ filesystem->FindClose( findHandle );
+}
+static ConCommand nav_check_file_consistency( "nav_check_file_consistency", CommandNavCheckFileConsistency, "Scans the maps directory and reports any missing/out-of-date navigation files.", FCVAR_GAMEDLL | FCVAR_CHEAT );
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load AI navigation data from a file
+ */
+NavErrorType CNavMesh::Load( void )
+{
+ // free previous navigation mesh data
+ Reset();
+ placeDirectory.Reset();
+
+ CNavArea::m_nextID = 1;
+
+ // nav filename is derived from map filename
+ char filename[256];
+ Q_snprintf( filename, sizeof( filename ), "maps\\%s.nav", STRING( gpGlobals->mapname ) );
+
+ bool navIsInBsp = false;
+ FileHandle_t file = filesystem->Open( filename, "rb", "MOD" ); // this ignores .nav files embedded in the .bsp ...
+ if ( !file )
+ {
+ navIsInBsp = true;
+ file = filesystem->Open( filename, "rb", "GAME" ); // ... and this looks for one if it's the only one around.
+ }
+
+ if (!file)
+ {
+ return NAV_CANT_ACCESS_FILE;
+ }
+
+ // check magic number
+ int result;
+ unsigned int magic;
+ result = filesystem->Read( &magic, sizeof(unsigned int), file );
+ if (!result || magic != NAV_MAGIC_NUMBER)
+ {
+ Msg( "Invalid navigation file '%s'.\n", filename );
+ filesystem->Close( file );
+ return NAV_INVALID_FILE;
+ }
+
+ // read file version number
+ unsigned int version;
+ result = filesystem->Read( &version, sizeof(unsigned int), file );
+ if (!result || version > NavCurrentVersion)
+ {
+ Msg( "Unknown navigation file version.\n" );
+ filesystem->Close( file );
+ return NAV_BAD_FILE_VERSION;
+ }
+
+ if (version >= 4)
+ {
+ // get size of source bsp file and verify that the bsp hasn't changed
+ unsigned int saveBspSize;
+ filesystem->Read( &saveBspSize, sizeof(unsigned int), file );
+
+ // verify size
+ char *bspFilename = GetBspFilename( filename );
+ if ( bspFilename == NULL )
+ {
+ filesystem->Close( file );
+ return NAV_INVALID_FILE;
+ }
+
+ unsigned int bspSize = filesystem->Size( bspFilename );
+
+ if (bspSize != saveBspSize && !navIsInBsp)
+ {
+ if ( engine->IsDedicatedServer() )
+ {
+ // Warning doesn't print to the dedicated server console, so we'll use Msg instead
+ Msg( "The Navigation Mesh was built using a different version of this map.\n" );
+ }
+ else
+ {
+ Warning( "The Navigation Mesh was built using a different version of this map.\n" );
+ }
+ m_isFromCurrentMap = false;
+ }
+ }
+
+ // load Place directory
+ if (version >= 5)
+ {
+ placeDirectory.Load( file );
+ }
+
+ // get number of areas
+ unsigned int count;
+ unsigned int i;
+ result = filesystem->Read( &count, sizeof(unsigned int), file );
+
+ Extent extent;
+ extent.lo.x = 9999999999.9f;
+ extent.lo.y = 9999999999.9f;
+ extent.hi.x = -9999999999.9f;
+ extent.hi.y = -9999999999.9f;
+
+ // load the areas and compute total extent
+ for( i=0; i<count; ++i )
+ {
+ CNavArea *area = new CNavArea;
+ area->Load( file, version );
+ TheNavAreas.AddToTail( area );
+
+ Extent areaExtent;
+ area->GetExtent(&areaExtent);
+
+ // check validity of nav area
+ if (areaExtent.lo.x >= areaExtent.hi.x || areaExtent.lo.y >= areaExtent.hi.y)
+ Warning( "WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n",
+ area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z );
+
+ if (areaExtent.lo.x < extent.lo.x)
+ extent.lo.x = areaExtent.lo.x;
+ if (areaExtent.lo.y < extent.lo.y)
+ extent.lo.y = areaExtent.lo.y;
+ if (areaExtent.hi.x > extent.hi.x)
+ extent.hi.x = areaExtent.hi.x;
+ if (areaExtent.hi.y > extent.hi.y)
+ extent.hi.y = areaExtent.hi.y;
+ }
+
+ // add the areas to the grid
+ AllocateGrid( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y );
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ AddNavArea( TheNavAreas[ it ] );
+ }
+
+
+ //
+ // Set up all the ladders
+ //
+ if (version >= 6)
+ {
+ result = filesystem->Read( &count, sizeof(unsigned int), file );
+
+ // load the ladders
+ for( i=0; i<count; ++i )
+ {
+ CNavLadder *ladder = new CNavLadder;
+ ladder->Load( file, version );
+ m_ladders.AddToTail( ladder );
+ }
+ }
+ else
+ {
+ BuildLadders();
+ }
+
+ // allow areas to connect to each other, etc
+ FOR_EACH_VEC( TheNavAreas, pit )
+ {
+ CNavArea *area = TheNavAreas[ pit ];
+ area->PostLoad();
+ }
+
+ // allow hiding spots to compute information
+ FOR_EACH_VEC( TheHidingSpots, hit )
+ {
+ HidingSpot *spot = TheHidingSpots[ hit ];
+ spot->PostLoad();
+ }
+
+ if ( version < 8 )
+ {
+ // Old nav meshes need to compute earliest occupy times
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CNavArea *area = TheNavAreas[ nit ];
+ area->ComputeEarliestOccupyTimes();
+ }
+ }
+
+ ComputeBattlefrontAreas();
+
+ // the Navigation Mesh has been successfully loaded
+ m_isLoaded = true;
+
+ filesystem->Close( file );
+
+ WarnIfMeshNeedsAnalysis();
+
+ return NAV_OK;
+}
+#endif \ No newline at end of file
diff --git a/game/server/cstrike/cs_nav_generate.cpp b/game/server/cstrike/cs_nav_generate.cpp
new file mode 100644
index 0000000..18e9ffa
--- /dev/null
+++ b/game/server/cstrike/cs_nav_generate.cpp
@@ -0,0 +1,269 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_generate.cpp
+// Auto-generate a Navigation Mesh by sampling the current map
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "util_shared.h"
+#include "nav_mesh.h"
+#include "cs_nav_area.h"
+#include "cs_nav_node.h"
+#include "cs_nav_pathfind.h"
+#include "viewport_panel_names.h"
+
+enum { MAX_BLOCKED_AREAS = 256 };
+static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
+static int blockedIDCount = 0;
+static float lastMsgTime = 0.0f;
+
+
+//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
+ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Shortest path cost, paying attention to "blocked" areas
+ */
+class ApproachAreaCost
+{
+public:
+ // HPE_TODO[pmf]: check that these new parameters are okay to be ignored
+ float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
+ {
+ // check if this area is "blocked"
+ for( int i=0; i<blockedIDCount; ++i )
+ {
+ if (area->GetID() == blockedID[i])
+ {
+ return -1.0f;
+ }
+ }
+
+ if (fromArea == NULL)
+ {
+ // first area in path, no cost
+ return 0.0f;
+ }
+ else
+ {
+ // compute distance traveled along path so far
+ float dist;
+
+ if (ladder)
+ {
+ dist = ladder->m_length;
+ }
+ else
+ {
+ dist = (area->GetCenter() - fromArea->GetCenter()).Length();
+ }
+
+ float cost = dist + fromArea->GetCostSoFar();
+
+ return cost;
+ }
+ }
+};
+
+/*
+ * Determine the set of "approach areas".
+ * An approach area is an area representing a place where players
+ * move into/out of our local neighborhood of areas.
+ * @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
+ */
+void CCSNavArea::ComputeApproachAreas( void )
+{
+ m_approachCount = 0;
+
+ if (nav_quicksave.GetBool())
+ return;
+
+ // use the center of the nav area as the "view" point
+ Vector eye = m_center;
+ if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
+ return;
+
+ // approximate eye position
+ if (GetAttributes() & NAV_MESH_CROUCH)
+ eye.z += 0.9f * HalfHumanHeight;
+ else
+ eye.z += 0.9f * HumanHeight;
+
+ enum { MAX_PATH_LENGTH = 256 };
+ CNavArea *path[ MAX_PATH_LENGTH ];
+ ApproachAreaCost cost;
+
+ enum SearchType
+ {
+ FROM_EYE, ///< start search from our eyepoint outward to farArea
+ TO_EYE, ///< start search from farArea beack towards our eye
+ SEARCH_FINISHED
+ };
+
+ //
+ // In order to *completely* enumerate all of the approach areas, we
+ // need to search from our eyepoint outward, as well as from outwards
+ // towards our eyepoint
+ //
+ for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
+ {
+ //
+ // In order to enumerate all of the approach areas, we need to
+ // run the algorithm many times, once for each "far away" area
+ // and keep the union of the approach area sets
+ //
+ int it;
+ for( it = 0; it < TheNavAreas.Count(); ++it )
+ {
+ CNavArea *farArea = TheNavAreas[ it ];
+
+ blockedIDCount = 0;
+
+ // skip the small areas
+ const float minSize = 200.0f; // 150
+ Extent extent;
+ farArea->GetExtent(&extent);
+ if (extent.SizeX() < minSize || extent.SizeY() < minSize)
+ {
+ continue;
+ }
+
+ // if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
+ if (farArea->IsVisible( eye ))
+ {
+ continue;
+ }
+
+ //
+ // Keep building paths to farArea and blocking them off until we
+ // cant path there any more.
+ // As areas are blocked off, all exits will be enumerated.
+ //
+ while( m_approachCount < MAX_APPROACH_AREAS )
+ {
+ CNavArea *from, *to;
+
+ if (searchType == FROM_EYE)
+ {
+ // find another path *to* 'farArea'
+ // we must pathfind from us in order to pick up one-way paths OUT OF our area
+ from = this;
+ to = farArea;
+ }
+ else // TO_EYE
+ {
+ // find another path *from* 'farArea'
+ // we must pathfind to us in order to pick up one-way paths INTO our area
+ from = farArea;
+ to = this;
+ }
+
+ // build the actual path
+ if (NavAreaBuildPath( from, to, NULL, cost ) == false)
+ {
+ break;
+ }
+
+ // find number of areas on path
+ int count = 0;
+ CNavArea *area;
+ for( area = to; area; area = area->GetParent() )
+ {
+ ++count;
+ }
+
+ if (count > MAX_PATH_LENGTH)
+ {
+ count = MAX_PATH_LENGTH;
+ }
+
+ // if the path is only two areas long, there can be no approach points
+ if (count <= 2)
+ {
+ break;
+ }
+
+ // build path starting from eye
+ int i = 0;
+
+ if (searchType == FROM_EYE)
+ {
+ for( area = to; i < count && area; area = area->GetParent() )
+ {
+ path[ count-i-1 ] = area;
+ ++i;
+ }
+ }
+ else // TO_EYE
+ {
+ for( area = to; i < count && area; area = area->GetParent() )
+ {
+ path[ i++ ] = area;
+ }
+ }
+
+ // traverse path to find first area we cannot see (skip the first area)
+ for( i=1; i<count; ++i )
+ {
+ // if we see this area, continue on
+ if (path[i]->IsVisible( eye ))
+ {
+ continue;
+ }
+
+ // we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
+ if (blockedIDCount == MAX_BLOCKED_AREAS)
+ {
+ Msg( "Overflow computing approach areas for area #%d.\n", GetID());
+ return;
+ }
+
+ // if the area to be blocked is actually farArea, block the one just prior
+ // (blocking farArea will cause all subsequent pathfinds to fail)
+ int block = (path[i] == farArea) ? i-1 : i;
+
+ // dont block the start area, or all subsequence pathfinds will fail
+ if (block == 0)
+ {
+ continue;
+ }
+
+ blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
+
+ // store new approach area if not already in set
+ int a;
+ for( a=0; a<m_approachCount; ++a )
+ {
+ if (m_approach[a].here.area == path[block-1])
+ {
+ break;
+ }
+ }
+
+ if (a == m_approachCount)
+ {
+ m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
+
+ m_approach[ m_approachCount ].here.area = path[block-1];
+ m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
+
+ m_approach[ m_approachCount ].next.area = path[block];
+ m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
+
+ ++m_approachCount;
+ }
+
+ // we are done with this path
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/game/server/cstrike/cs_nav_mesh.cpp b/game/server/cstrike/cs_nav_mesh.cpp
new file mode 100644
index 0000000..0a46745
--- /dev/null
+++ b/game/server/cstrike/cs_nav_mesh.cpp
@@ -0,0 +1,127 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// NavMesh.cpp
+// Implementation of Navigation Mesh interface
+// Author: Michael S. Booth, 2003-2004
+
+#include "cbase.h"
+#include "filesystem.h"
+#include "cs_nav_mesh.h"
+#include "cs_nav_node.h"
+#include "cs_nav_area.h"
+#include "fmtstr.h"
+#include "utlbuffer.h"
+#include "tier0/vprof.h"
+
+//--------------------------------------------------------------------------------------------------------------
+CSNavMesh::CSNavMesh( void )
+{
+}
+
+//--------------------------------------------------------------------------------------------------------------
+CSNavMesh::~CSNavMesh()
+{
+}
+
+CNavArea * CSNavMesh::CreateArea( void ) const
+{
+ return new CCSNavArea;
+}
+
+//-------------------------------------------------------------------------
+void CSNavMesh::BeginCustomAnalysis( bool bIncremental )
+{
+
+}
+
+
+//-------------------------------------------------------------------------
+// invoked when custom analysis step is complete
+void CSNavMesh::PostCustomAnalysis( void )
+{
+
+}
+
+
+//-------------------------------------------------------------------------
+void CSNavMesh::EndCustomAnalysis()
+{
+
+}
+
+
+//-------------------------------------------------------------------------
+/**
+ * Returns sub-version number of data format used by derived classes
+ */
+unsigned int CSNavMesh::GetSubVersionNumber( void ) const
+{
+ // 1: initial implementation - added ApproachArea data
+ return 1;
+}
+
+//-------------------------------------------------------------------------
+/**
+ * Store custom mesh data for derived classes
+ */
+void CSNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const
+{
+
+}
+
+//-------------------------------------------------------------------------
+/**
+ * Load custom mesh data for derived classes
+ */
+void CSNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion )
+{
+
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset the Navigation Mesh to initial values
+ */
+void CSNavMesh::Reset( void )
+{
+ CNavMesh::Reset();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Zero player counts in all areas
+ */
+void CSNavMesh::ClearPlayerCounts( void )
+{
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CCSNavArea *area = (CCSNavArea*)TheNavAreas[ it ];
+ area->ClearPlayerCount();
+ }
+}
+
+void CSNavMesh::Update( void )
+{
+ CNavMesh::Update();
+}
+
+NavErrorType CSNavMesh::Load( void )
+{
+ return CNavMesh::Load();
+}
+
+bool CSNavMesh::Save( void ) const
+{
+ return CNavMesh::Save();
+}
+
+NavErrorType CSNavMesh::PostLoad( unsigned int version )
+{
+ return CNavMesh::PostLoad(version);
+} \ No newline at end of file
diff --git a/game/server/cstrike/cs_nav_mesh.h b/game/server/cstrike/cs_nav_mesh.h
new file mode 100644
index 0000000..a240a87
--- /dev/null
+++ b/game/server/cstrike/cs_nav_mesh.h
@@ -0,0 +1,68 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// cs_nav_mesh.h
+// The Navigation Mesh interface
+// Author: Michael S. Booth ([email protected]), January 2003
+
+//
+// Author: Michael S. Booth ([email protected]), 2003
+//
+// NOTE: The Navigation code uses Doxygen-style comments. If you run Doxygen over this code, it will
+// auto-generate documentation. Visit www.doxygen.org to download the system for free.
+//
+
+#ifndef _CS_NAV_MESH_H_
+#define _CS_NAV_MESH_H_
+
+#include "filesystem.h"
+
+#include "nav_mesh.h"
+
+#include "cs_nav.h"
+#include "nav_area.h"
+#include "nav_colors.h"
+
+class CNavArea;
+class CCSNavArea;
+class CBaseEntity;
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * The CSNavMesh is the global interface to the Navigation Mesh.
+ * @todo Make this an abstract base class interface, and derive mod-specific implementations.
+ */
+class CSNavMesh : public CNavMesh
+{
+public:
+ CSNavMesh( void );
+ virtual ~CSNavMesh();
+
+ virtual CNavArea *CreateArea( void ) const; // CNavArea factory
+
+ virtual unsigned int GetSubVersionNumber( void ) const; // returns sub-version number of data format used by derived classes
+ virtual void SaveCustomData( CUtlBuffer &fileBuffer ) const; // store custom mesh data for derived classes
+ virtual void LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion ); // load custom mesh data for derived classes
+
+ virtual void Reset( void ); ///< destroy Navigation Mesh data and revert to initial state
+ virtual void Update( void ); ///< invoked on each game frame
+
+ virtual NavErrorType Load( void ); // load navigation data from a file
+ virtual NavErrorType PostLoad( unsigned int version ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
+ virtual bool Save( void ) const; ///< store Navigation Mesh to a file
+
+ void ClearPlayerCounts( void ); ///< zero player counts in all areas
+
+protected:
+ virtual void BeginCustomAnalysis( bool bIncremental );
+ virtual void PostCustomAnalysis( void ); // invoked when custom analysis step is complete
+ virtual void EndCustomAnalysis();
+
+private:
+};
+
+#endif // _CS_NAV_MESH_H_
diff --git a/game/server/cstrike/cs_nav_node.cpp b/game/server/cstrike/cs_nav_node.cpp
new file mode 100644
index 0000000..93f7b1f
--- /dev/null
+++ b/game/server/cstrike/cs_nav_node.cpp
@@ -0,0 +1,383 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_node.cpp
+// AI Navigation Nodes
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#include "cbase.h"
+#include "cs_nav_node.h"
+#include "nav_colors.h"
+#include "nav_mesh.h"
+
+NavDirType Opposite[ NUM_DIRECTIONS ] = { SOUTH, WEST, NORTH, EAST };
+
+CSNavNode *CSNavNode::m_list = NULL;
+unsigned int CSNavNode::m_listLength = 0;
+unsigned int CSNavNode::m_nextID = 1;
+
+ConVar nav_show_nodes( "nav_show_nodes", "0" );
+
+
+//--------------------------------------------------------------------------------------------------------------
+class LookAtTarget
+{
+public:
+ LookAtTarget( const Vector &target )
+ {
+ m_target = target;
+ }
+
+ bool operator()( CBasePlayer *player )
+ {
+ QAngle angles;
+ Vector to = m_target - player->GetAbsOrigin();
+ VectorAngles( to, angles );
+
+ player->SetLocalAngles( angles );
+ player->SnapEyeAngles( angles );
+ return true;
+ }
+
+private:
+ Vector m_target;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Constructor
+ */
+CSNavNode::CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent )
+{
+ m_pos = pos;
+ m_normal = normal;
+
+ m_id = m_nextID++;
+
+ int i;
+ for( i=0; i<NUM_DIRECTIONS; ++i )
+ {
+ m_to[ i ] = NULL;
+ }
+
+ for ( i=0; i<NUM_CORNERS; ++i )
+ {
+ m_crouch[ i ] = false;
+ }
+
+ m_visited = 0;
+ m_parent = parent;
+
+ m_next = m_list;
+ m_list = this;
+ m_listLength++;
+
+ m_isCovered = false;
+ m_area = NULL;
+
+ m_attributeFlags = 0;
+
+ if ( nav_show_nodes.GetBool() )
+ {
+ NDebugOverlay::Cross3D( m_pos, 10.0f, 128, 128, 128, true, 10.0f );
+ NDebugOverlay::Cross3D( m_pos, 10.0f, 255, 255, 255, false, 10.0f );
+
+ LookAtTarget lookAt( m_pos );
+ ForEachPlayer( lookAt );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+#if DEBUG_NAV_NODES
+ConVar nav_show_node_id( "nav_show_node_id", "0" );
+ConVar nav_test_node( "nav_test_node", "0" );
+ConVar nav_test_node_crouch( "nav_test_node_crouch", "0" );
+ConVar nav_test_node_crouch_dir( "nav_test_node_crouch_dir", "4" );
+#endif // DEBUG_NAV_NODES
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CSNavNode::Draw( void )
+{
+#if DEBUG_NAV_NODES
+ if ( !nav_show_nodes.GetBool() )
+ return;
+
+ int r = 0, g = 0, b = 0;
+
+ if ( m_isCovered )
+ {
+ if ( GetAttributes() & NAV_MESH_CROUCH )
+ {
+ b = 255;
+ }
+ else
+ {
+ r = 255;
+ }
+ }
+ else
+ {
+ if ( GetAttributes() & NAV_MESH_CROUCH )
+ {
+ b = 255;
+ }
+ g = 255;
+ }
+
+ NDebugOverlay::Cross3D( m_pos, 2, r, g, b, true, 0.1f );
+
+ if ( (!m_isCovered && nav_show_node_id.GetBool()) || (m_isCovered && nav_show_node_id.GetInt() < 0) )
+ {
+ char text[16];
+ Q_snprintf( text, sizeof( text ), "%d", m_id );
+ NDebugOverlay::Text( m_pos, text, true, 0.1f );
+ }
+
+ if ( (unsigned int)(nav_test_node.GetInt()) == m_id )
+ {
+ TheNavMesh->TestArea( this, 1, 1 );
+ nav_test_node.SetValue( 0 );
+ }
+
+ if ( (unsigned int)(nav_test_node_crouch.GetInt()) == m_id )
+ {
+ CheckCrouch();
+ nav_test_node_crouch.SetValue( 0 );
+ }
+
+ if ( GetAttributes() & NAV_MESH_CROUCH )
+ {
+ int i;
+ for( i=0; i<NUM_CORNERS; i++ )
+ {
+ if ( m_crouch[i] )
+ {
+ Vector2D dir;
+ CornerToVector2D( (NavCornerType)i, &dir );
+
+ const float scale = 3.0f;
+ Vector scaled( dir.x * scale, dir.y * scale, 0 );
+
+ NDebugOverlay::HorzArrow( m_pos, m_pos + scaled, 0.5, 0, 0, 255, 255, true, 0.1f );
+ }
+ }
+ }
+
+#endif // DEBUG_NAV_NODES
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CSNavNode::CheckCrouch( void )
+{
+ CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
+ trace_t tr;
+
+ // Trace downward from duck height to find the max floor height for the node's surroundings
+ Vector mins( -HalfHumanWidth, -HalfHumanWidth, 0 );
+ Vector maxs( HalfHumanWidth, HalfHumanWidth, 0 );
+ Vector start( m_pos.x, m_pos.y, m_pos.z + VEC_DUCK_HULL_MAX.z - 0.1f );
+ UTIL_TraceHull(
+ start,
+ m_pos,
+ mins,
+ maxs,
+ MASK_PLAYERSOLID_BRUSHONLY,
+ &filter,
+ &tr );
+
+ Vector groundPos = tr.endpos;
+
+ if ( tr.startsolid && !tr.allsolid )
+ {
+ // Try going down out of the solid and re-check for the floor height
+ start.z -= tr.endpos.z - 0.1f;
+
+ UTIL_TraceHull(
+ start,
+ m_pos,
+ mins,
+ maxs,
+ MASK_PLAYERSOLID_BRUSHONLY,
+ &filter,
+ &tr );
+
+ groundPos = tr.endpos;
+ }
+
+ if ( tr.startsolid )
+ {
+ // we don't even have duck height clear. try a simple check to find floor height.
+ float x, y;
+
+ // Find the highest floor z - for a player to stand in this area, we need a full
+ // VEC_HULL_MAX.z of clearance above this height at all points.
+ float maxFloorZ = m_pos.z;
+ for( y = -HalfHumanWidth; y <= HalfHumanWidth + 0.1f; y += HalfHumanWidth )
+ {
+ for( x = -HalfHumanWidth; x <= HalfHumanWidth + 0.1f; x += HalfHumanWidth )
+ {
+ float floorZ;
+ if ( TheNavMesh->GetGroundHeight( m_pos, &floorZ ) )
+ {
+ maxFloorZ = MAX( maxFloorZ, floorZ + 0.1f );
+ }
+ }
+ }
+
+ groundPos.Init( m_pos.x, m_pos.y, maxFloorZ );
+ }
+
+ // For each direction, trace upwards from our best ground height to VEC_HULL_MAX.z to see if we have standing room.
+ for ( int i=0; i<NUM_CORNERS; ++i )
+ {
+#if DEBUG_NAV_NODES
+ if ( nav_test_node_crouch_dir.GetInt() != NUM_CORNERS && i != nav_test_node_crouch_dir.GetInt() )
+ continue;
+#endif // DEBUG_NAV_NODES
+
+ NavCornerType corner = (NavCornerType)i;
+ Vector2D cornerVec;
+ CornerToVector2D( corner, &cornerVec );
+
+ Vector actualGroundPos = groundPos; // we might need to adjust this if the tracehull failed above and we fell back to m_pos.z
+
+ // Build a mins/maxs pair for the HumanWidth x HalfHumanWidth box facing the appropriate direction
+ mins.Init();
+ maxs.Init( cornerVec.x * HalfHumanWidth, cornerVec.y * HalfHumanWidth, 0 );
+
+ // now make sure that mins is smaller than maxs
+ for ( int j=0; j<3; ++j )
+ {
+ if ( mins[j] > maxs[j] )
+ {
+ float tmp = mins[j];
+ mins[j] = maxs[j];
+ maxs[j] = tmp;
+ }
+ }
+
+ UTIL_TraceHull(
+ actualGroundPos + Vector( 0, 0, 0.1f ),
+ actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
+ mins,
+ maxs,
+ MASK_PLAYERSOLID_BRUSHONLY,
+ &filter,
+ &tr );
+ actualGroundPos.z += tr.fractionleftsolid * VEC_HULL_MAX.z;
+ float maxHeight = actualGroundPos.z + VEC_DUCK_HULL_MAX.z;
+ for ( ; tr.startsolid && actualGroundPos.z <= maxHeight; actualGroundPos.z += 1.0f )
+ {
+ // In case we didn't find a good ground pos above, we could start in the ground. Move us up some.
+ UTIL_TraceHull(
+ actualGroundPos + Vector( 0, 0, 0.1f ),
+ actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
+ mins,
+ maxs,
+ MASK_PLAYERSOLID_BRUSHONLY,
+ &filter,
+ &tr );
+ }
+ if (tr.startsolid || tr.fraction != 1.0f)
+ {
+ SetAttributes( NAV_MESH_CROUCH );
+ m_crouch[corner] = true;
+ }
+
+#if DEBUG_NAV_NODES
+ if ( nav_show_nodes.GetBool() )
+ {
+ if ( nav_test_node_crouch_dir.GetInt() == i || nav_test_node_crouch_dir.GetInt() == NUM_CORNERS )
+ {
+ if ( tr.startsolid )
+ {
+ NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 255, 0, 0, 10, 20.0f );
+ }
+ else if ( m_crouch[corner] )
+ {
+ NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 0, 255, 10, 20.0f );
+ }
+ else
+ {
+ NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 255, 0, 10, 10.0f );
+ }
+ }
+ }
+#endif // DEBUG_NAV_NODES
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Create a connection FROM this node TO the given node, in the given direction
+ */
+void CSNavNode::ConnectTo( CSNavNode *node, NavDirType dir )
+{
+ m_to[ dir ] = node;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return node at given position.
+ * @todo Need a hash table to make this lookup fast
+ */
+CSNavNode *CSNavNode::GetNode( const Vector &pos )
+{
+ const float tolerance = 0.45f * GenerationStepSize; // 1.0f
+
+ for( CSNavNode *node = m_list; node; node = node->m_next )
+ {
+ float dx = fabs( node->m_pos.x - pos.x );
+ float dy = fabs( node->m_pos.y - pos.y );
+ float dz = fabs( node->m_pos.z - pos.z );
+
+ if (dx < tolerance && dy < tolerance && dz < tolerance)
+ return node;
+ }
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this node is bidirectionally linked to
+ * another node in the given direction
+ */
+BOOL CSNavNode::IsBiLinked( NavDirType dir ) const
+{
+ if (m_to[ dir ] && m_to[ dir ]->m_to[ Opposite[dir] ] == this)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this node is the NW corner of a quad of nodes
+ * that are all bidirectionally linked.
+ */
+BOOL CSNavNode::IsClosedCell( void ) const
+{
+ if (IsBiLinked( SOUTH ) &&
+ IsBiLinked( EAST ) &&
+ m_to[ EAST ]->IsBiLinked( SOUTH ) &&
+ m_to[ SOUTH ]->IsBiLinked( EAST ) &&
+ m_to[ EAST ]->m_to[ SOUTH ] == m_to[ SOUTH ]->m_to[ EAST ])
+ {
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/game/server/cstrike/cs_nav_node.h b/game/server/cstrike/cs_nav_node.h
new file mode 100644
index 0000000..449f97e
--- /dev/null
+++ b/game/server/cstrike/cs_nav_node.h
@@ -0,0 +1,135 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_node.h
+// Navigation Nodes are used when generating a Navigation Mesh by point sampling the map
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#ifndef _CS_NAV_NODE_H_
+#define _CS_NAV_NODE_H_
+
+#include "cs_nav.h"
+
+// If DEBUG_NAV_NODES is true, nav_show_nodes controls drawing node positions, and
+// nav_show_node_id allows you to show the IDs of nodes that didn't get used to create areas.
+#ifdef _DEBUG
+#define DEBUG_NAV_NODES 1
+#else
+#define DEBUG_NAV_NODES 0
+#endif
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Navigation Nodes.
+ * These Nodes encapsulate world locations, and ways to get from one location to an adjacent one.
+ * Note that these links are not necessarily commutative (falling off of a ledge, for example).
+ */
+class CSNavNode
+{
+public:
+ CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent = NULL );
+
+ static CSNavNode *GetNode( const Vector &pos ); ///< return navigation node at the position, or NULL if none exists
+
+ CSNavNode *GetConnectedNode( NavDirType dir ) const; ///< get navigation node connected in given direction, or NULL if cant go that way
+ const Vector *GetPosition( void ) const;
+ const Vector *GetNormal( void ) const { return &m_normal; }
+ unsigned int GetID( void ) const { return m_id; }
+
+ static CSNavNode *GetFirst( void ) { return m_list; }
+ static unsigned int GetListLength( void ) { return m_listLength; }
+ CSNavNode *GetNext( void ) { return m_next; }
+
+ void Draw( void );
+
+ void ConnectTo( CSNavNode *node, NavDirType dir ); ///< create a connection FROM this node TO the given node, in the given direction
+ CSNavNode *GetParent( void ) const;
+
+ void MarkAsVisited( NavDirType dir ); ///< mark the given direction as having been visited
+ BOOL HasVisited( NavDirType dir ); ///< return TRUE if the given direction has already been searched
+ BOOL IsBiLinked( NavDirType dir ) const; ///< node is bidirectionally linked to another node in the given direction
+ BOOL IsClosedCell( void ) const; ///< node is the NW corner of a bi-linked quad of nodes
+
+ void Cover( void ) { m_isCovered = true; } ///< @todo Should pass in area that is covering
+ BOOL IsCovered( void ) const { return m_isCovered; } ///< return true if this node has been covered by an area
+
+ void AssignArea( CNavArea *area ); ///< assign the given area to this node
+ CNavArea *GetArea( void ) const; ///< return associated area
+
+ void SetAttributes( unsigned char bits ) { m_attributeFlags = bits; }
+ unsigned char GetAttributes( void ) const { return m_attributeFlags; }
+
+private:
+ friend class CNavMesh;
+
+ void CheckCrouch( void );
+
+ Vector m_pos; ///< position of this node in the world
+ Vector m_normal; ///< surface normal at this location
+ CSNavNode *m_to[ NUM_DIRECTIONS ]; ///< links to north, south, east, and west. NULL if no link
+ unsigned int m_id; ///< unique ID of this node
+ unsigned char m_attributeFlags; ///< set of attribute bit flags (see NavAttributeType)
+
+ static CSNavNode *m_list; ///< the master list of all nodes for this map
+ static unsigned int m_listLength;
+ static unsigned int m_nextID;
+ CSNavNode *m_next; ///< next link in master list
+
+ // below are only needed when generating
+ unsigned char m_visited; ///< flags for automatic node generation. If direction bit is clear, that direction hasn't been explored yet.
+ CSNavNode *m_parent; ///< the node prior to this in the search, which we pop back to when this node's search is done (a stack)
+ bool m_isCovered; ///< true when this node is "covered" by a CNavArea
+ CNavArea *m_area; ///< the area this node is contained within
+
+ bool m_crouch[ NUM_CORNERS ];
+};
+
+//--------------------------------------------------------------------------------------------------------------
+//
+// Inlines
+//
+
+inline CSNavNode *CSNavNode::GetConnectedNode( NavDirType dir ) const
+{
+ return m_to[ dir ];
+}
+
+inline const Vector *CSNavNode::GetPosition( void ) const
+{
+ return &m_pos;
+}
+
+inline CSNavNode *CSNavNode::GetParent( void ) const
+{
+ return m_parent;
+}
+
+inline void CSNavNode::MarkAsVisited( NavDirType dir )
+{
+ m_visited |= (1 << dir);
+}
+
+inline BOOL CSNavNode::HasVisited( NavDirType dir )
+{
+ if (m_visited & (1 << dir))
+ return true;
+
+ return false;
+}
+
+inline void CSNavNode::AssignArea( CNavArea *area )
+{
+ m_area = area;
+}
+
+inline CNavArea *CSNavNode::GetArea( void ) const
+{
+ return m_area;
+}
+
+
+#endif // _CS_NAV_NODE_H_
diff --git a/game/server/cstrike/cs_nav_path.cpp b/game/server/cstrike/cs_nav_path.cpp
new file mode 100644
index 0000000..5a1a9ce
--- /dev/null
+++ b/game/server/cstrike/cs_nav_path.cpp
@@ -0,0 +1,1208 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_path.cpp
+// Encapsulation of a path through space
+// Author: Michael S. Booth ([email protected]), November 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "cs_player.h"
+
+#include "nav_mesh.h"
+#include "cs_nav_path.h"
+#include "bot_util.h"
+#include "improv_locomotor.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+
+#define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f )
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine actual path positions
+ */
+bool CCSNavPath::ComputePathPositions( void )
+{
+ if (m_segmentCount == 0)
+ return false;
+
+ // start in first area's center
+ m_path[0].pos = m_path[0].area->GetCenter();
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ for( int i=1; i<m_segmentCount; ++i )
+ {
+ const PathSegment *from = &m_path[ i-1 ];
+ PathSegment *to = &m_path[ i ];
+
+ if (to->how <= GO_WEST) // walk along the floor to the next area
+ {
+ to->ladder = NULL;
+
+ // compute next point, keeping path as straight as possible
+ from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos );
+
+ // move goal position into the goal area a bit
+ const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
+ AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist );
+
+ // we need to walk out of "from" area, so keep Z where we can reach it
+ to->pos.z = from->area->GetZ( to->pos );
+
+ // if this is a "jump down" connection, we must insert an additional point on the path
+ if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false)
+ {
+ // this is a "jump down" link
+
+ // compute direction of path just prior to "jump down"
+ Vector2D dir;
+ DirectionToVector2D( (NavDirType)to->how, &dir );
+
+ // shift top of "jump down" out a bit to "get over the ledge"
+ const float pushDist = 25.0f;
+ to->pos.x += pushDist * dir.x;
+ to->pos.y += pushDist * dir.y;
+
+ // insert a duplicate node to represent the bottom of the fall
+ if (m_segmentCount < MAX_PATH_SEGMENTS-1)
+ {
+ // copy nodes down
+ for( int j=m_segmentCount; j>i; --j )
+ m_path[j] = m_path[j-1];
+
+ // path is one node longer
+ ++m_segmentCount;
+
+ // move index ahead into the new node we just duplicated
+ ++i;
+
+ m_path[i].pos.x = to->pos.x + pushDist * dir.x;
+ m_path[i].pos.y = to->pos.y + pushDist * dir.y;
+
+ // put this one at the bottom of the fall
+ m_path[i].pos.z = to->area->GetZ( m_path[i].pos );
+ }
+ }
+ }
+ else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder
+ {
+ // find our ladder
+ const NavLadderConnectVector *ladders = from->area->GetLadders( CNavLadder::LADDER_UP );
+ int it;
+ for( it=0; it<ladders->Count(); ++it )
+ {
+ CNavLadder *ladder = (*ladders)[ it ].ladder;
+
+ // can't use "behind" area when ascending...
+ if (ladder->m_topForwardArea == to->area ||
+ ladder->m_topLeftArea == to->area ||
+ ladder->m_topRightArea == to->area)
+ {
+ to->ladder = ladder;
+ to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth;
+ break;
+ }
+ }
+
+ if (it == ladders->Count())
+ {
+ //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder
+ {
+ // find our ladder
+ const NavLadderConnectVector *ladders = from->area->GetLadders( CNavLadder::LADDER_DOWN );
+ int it;
+ for( it=0; it<ladders->Count(); ++it )
+ {
+ CNavLadder *ladder = (*ladders)[ it ].ladder;
+
+ if (ladder->m_bottomArea == to->area)
+ {
+ to->ladder = ladder;
+ to->pos = ladder->m_top;
+ to->pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth;
+ break;
+ }
+ }
+
+ if (it == ladders->Count())
+ {
+ //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if position is at the end of the path
+ */
+bool CCSNavPath::IsAtEnd( const Vector &pos ) const
+{
+ if (!IsValid())
+ return false;
+
+ const float epsilon = 20.0f;
+ return (pos - GetEndpoint()).IsLengthLessThan( epsilon );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return length of path from start to finish
+ */
+float CCSNavPath::GetLength( void ) const
+{
+ float length = 0.0f;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ length += (m_path[i].pos - m_path[i-1].pos).Length();
+ }
+
+ return length;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
+ * @todo Be careful of returning "positions" along one-way drops, ladders, etc.
+ */
+bool CCSNavPath::GetPointAlongPath( float distAlong, Vector *pointOnPath ) const
+{
+ if (!IsValid() || pointOnPath == NULL)
+ return false;
+
+ if (distAlong <= 0.0f)
+ {
+ *pointOnPath = m_path[0].pos;
+ return true;
+ }
+
+ float lengthSoFar = 0.0f;
+ float segmentLength;
+ Vector dir;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ dir = m_path[i].pos - m_path[i-1].pos;
+ segmentLength = dir.Length();
+
+ if (segmentLength + lengthSoFar >= distAlong)
+ {
+ // desired point is on this segment of the path
+ float delta = distAlong - lengthSoFar;
+ float t = delta / segmentLength;
+
+ *pointOnPath = m_path[i].pos + t * dir;
+
+ return true;
+ }
+
+ lengthSoFar += segmentLength;
+ }
+
+ *pointOnPath = m_path[ GetSegmentCount()-1 ].pos;
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the node index closest to the given distance along the path without going over - returns (-1) if error
+ */
+int CCSNavPath::GetSegmentIndexAlongPath( float distAlong ) const
+{
+ if (!IsValid())
+ return -1;
+
+ if (distAlong <= 0.0f)
+ {
+ return 0;
+ }
+
+ float lengthSoFar = 0.0f;
+ Vector dir;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ lengthSoFar += (m_path[i].pos - m_path[i-1].pos).Length();
+
+ if (lengthSoFar > distAlong)
+ {
+ return i-1;
+ }
+ }
+
+ return GetSegmentCount()-1;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute closest point on path to given point
+ * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
+ */
+bool CCSNavPath::FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const
+{
+ if (!IsValid() || close == NULL)
+ return false;
+
+ Vector along, toWorldPos;
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ float distSq;
+
+ for( int i=startIndex; i<=endIndex; ++i )
+ {
+ from = &m_path[i-1].pos;
+ to = &m_path[i].pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toWorldPos = *worldPos - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toWorldPos, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - *worldPos).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ closeDistSq = distSq;
+ *close = pos;
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Build trivial path when start and goal are in the same nav area
+ */
+bool CCSNavPath::BuildTrivialPath( const Vector &start, const Vector &goal )
+{
+ m_segmentCount = 0;
+
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
+ if (startArea == NULL)
+ return false;
+
+ CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
+ if (goalArea == NULL)
+ return false;
+
+ m_segmentCount = 2;
+
+ m_path[0].area = startArea;
+ m_path[0].pos.x = start.x;
+ m_path[0].pos.y = start.y;
+ m_path[0].pos.z = startArea->GetZ( start );
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ m_path[1].area = goalArea;
+ m_path[1].pos.x = goal.x;
+ m_path[1].pos.y = goal.y;
+ m_path[1].pos.z = goalArea->GetZ( goal );
+ m_path[1].ladder = NULL;
+ m_path[1].how = NUM_TRAVERSE_TYPES;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Draw the path for debugging.
+ */
+void CCSNavPath::Draw( const Vector &color )
+{
+ if (!IsValid())
+ return;
+
+ for( int i=1; i<m_segmentCount; ++i )
+ {
+ DrawLine( m_path[i-1].pos + Vector( 0, 0, HalfHumanHeight ),
+ m_path[i].pos + Vector( 0, 0, HalfHumanHeight ), 2, 255 * color.x, 255 * color.y, 255 * color.z );
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check line of sight from 'anchor' node on path to subsequent nodes until
+ * we find a node that can't been seen from 'anchor'.
+ */
+int CCSNavPath::FindNextOccludedNode( int anchor )
+{
+ for( int i=anchor+1; i<m_segmentCount; ++i )
+ {
+ // don't remove ladder nodes
+ if (m_path[i].ladder)
+ return i;
+
+ if (!IsWalkableTraceLineClear( m_path[ anchor ].pos, m_path[ i ].pos ))
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+
+ Vector anchorPlusHalf = m_path[ anchor ].pos + Vector( 0, 0, HalfHumanHeight );
+ Vector iPlusHalf = m_path[ i ].pos +Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( anchorPlusHalf, iPlusHalf) )
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+
+ Vector anchorPlusFull = m_path[ anchor ].pos + Vector( 0, 0, HumanHeight );
+ Vector iPlusFull = m_path[ i ].pos + Vector( 0, 0, HumanHeight );
+ if (!IsWalkableTraceLineClear( anchorPlusFull, iPlusFull ))
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+ }
+
+ return m_segmentCount;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Smooth out path, removing redundant nodes
+ */
+void CCSNavPath::Optimize( void )
+{
+// DONT USE THIS: Optimizing the path results in cutting thru obstacles
+return;
+
+ if (m_segmentCount < 3)
+ return;
+
+ int anchor = 0;
+
+ while( anchor < m_segmentCount )
+ {
+ int occluded = FindNextOccludedNode( anchor );
+ int nextAnchor = occluded-1;
+
+ if (nextAnchor > anchor)
+ {
+ // remove redundant nodes between anchor and nextAnchor
+ int removeCount = nextAnchor - anchor - 1;
+ if (removeCount > 0)
+ {
+ for( int i=nextAnchor; i<m_segmentCount; ++i )
+ {
+ m_path[i-removeCount] = m_path[i];
+ }
+ m_segmentCount -= removeCount;
+ }
+ }
+
+ ++anchor;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+
+/**
+ * Constructor
+ */
+CNavPathFollower::CNavPathFollower( void )
+{
+ m_improv = NULL;
+ m_path = NULL;
+
+ m_segmentIndex = 0;
+ m_isLadderStarted = false;
+
+ m_isDebug = false;
+}
+
+void CNavPathFollower::Reset( void )
+{
+ m_segmentIndex = 1;
+ m_isLadderStarted = false;
+
+ m_stuckMonitor.Reset();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move improv along path
+ */
+void CNavPathFollower::Update( float deltaT, bool avoidObstacles )
+{
+ if (m_path == NULL || m_path->IsValid() == false)
+ return;
+
+ const CCSNavPath::PathSegment *node = (*m_path)[ m_segmentIndex ];
+
+ if (node == NULL)
+ {
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_INVALID_PATH );
+ m_path->Invalidate();
+ return;
+ }
+
+ // handle ladders
+ /*
+ if (node->ladder)
+ {
+ const Vector *approachPos = NULL;
+ const Vector *departPos = NULL;
+
+ if (m_segmentIndex)
+ approachPos = &(*m_path)[ m_segmentIndex-1 ]->pos;
+
+ if (m_segmentIndex < m_path->GetSegmentCount()-1)
+ departPos = &(*m_path)[ m_segmentIndex+1 ]->pos;
+
+ if (!m_isLadderStarted)
+ {
+ // set up ladder movement
+ m_improv->StartLadder( node->ladder, node->how, approachPos, departPos );
+ m_isLadderStarted = true;
+ }
+
+ // move improv along ladder
+ if (m_improv->TraverseLadder( node->ladder, node->how, approachPos, departPos, deltaT ))
+ {
+ // completed ladder
+ ++m_segmentIndex;
+ }
+ return;
+ }
+ */
+
+ // reset ladder init flag
+ m_isLadderStarted = false;
+
+ //
+ // Check if we reached the end of the path
+ //
+ const float closeRange = 20.0f;
+ if ((m_improv->GetFeet() - node->pos).IsLengthLessThan( closeRange ))
+ {
+ ++m_segmentIndex;
+
+ if (m_segmentIndex >= m_path->GetSegmentCount())
+ {
+ m_improv->OnMoveToSuccess( m_path->GetEndpoint() );
+ m_path->Invalidate();
+ return;
+ }
+ }
+
+
+ m_goal = node->pos;
+
+ const float aheadRange = 300.0f;
+ m_segmentIndex = FindPathPoint( aheadRange, &m_goal, &m_behindIndex );
+ if (m_segmentIndex >= m_path->GetSegmentCount())
+ m_segmentIndex = m_path->GetSegmentCount()-1;
+
+
+ bool isApproachingJumpArea = false;
+
+ //
+ // Crouching
+ //
+ if (!m_improv->IsUsingLadder())
+ {
+ // because hostage crouching is not really supported by the engine,
+ // if we are standing in a crouch area, we must crouch to avoid collisions
+ if (m_improv->GetLastKnownArea() &&
+ m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_CROUCH &&
+ !(m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_JUMP))
+ {
+ m_improv->Crouch();
+ }
+
+ // if we are approaching a crouch area, crouch
+ // if there are no crouch areas coming up, stand
+ const float crouchRange = 50.0f;
+ bool didCrouch = false;
+ for( int i=m_segmentIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ const CNavArea *to = (*m_path)[i]->area;
+
+ // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
+ if (to->GetAttributes() & NAV_MESH_JUMP)
+ {
+ isApproachingJumpArea = true;
+ break;
+ }
+
+ Vector close;
+ to->GetClosestPointOnArea( m_improv->GetCentroid(), &close );
+
+ if ((close - m_improv->GetFeet()).AsVector2D().IsLengthGreaterThan( crouchRange ))
+ break;
+
+ if (to->GetAttributes() & NAV_MESH_CROUCH)
+ {
+ m_improv->Crouch();
+ didCrouch = true;
+ break;
+ }
+
+ }
+
+ if (!didCrouch && !m_improv->IsJumping())
+ {
+ // no crouch areas coming up
+ m_improv->StandUp();
+ }
+
+ } // end crouching logic
+
+
+ if (m_isDebug)
+ {
+ m_path->Draw();
+ UTIL_DrawBeamPoints( m_improv->GetCentroid(), m_goal + Vector( 0, 0, StepHeight ), 1, 255, 0, 255 );
+ UTIL_DrawBeamPoints( m_goal + Vector( 0, 0, StepHeight ), m_improv->GetCentroid(), 1, 255, 0, 255 );
+ }
+
+ // check if improv becomes stuck
+ m_stuckMonitor.Update( m_improv );
+
+
+ // if improv has been stuck for too long, give up
+ const float giveUpTime = 2.0f;
+ if (m_stuckMonitor.GetDuration() > giveUpTime)
+ {
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_STUCK );
+ m_path->Invalidate();
+ return;
+ }
+
+
+ // if our goal is high above us, we must have fallen
+ if (m_goal.z - m_improv->GetFeet().z > JumpCrouchHeight)
+ {
+ const float closeRange = 75.0f;
+ Vector2D to( m_improv->GetFeet().x - m_goal.x, m_improv->GetFeet().y - m_goal.y );
+ if (to.IsLengthLessThan( closeRange ))
+ {
+ // we can't reach the goal position
+ // check if we can reach the next node, in case this was a "jump down" situation
+ const CCSNavPath::PathSegment *nextNode = (*m_path)[ m_behindIndex+1 ];
+ if (m_behindIndex >=0 && nextNode)
+ {
+ if (nextNode->pos.z - m_improv->GetFeet().z > JumpCrouchHeight)
+ {
+ // the next node is too high, too - we really did fall of the path
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF );
+ m_path->Invalidate();
+ return;
+ }
+ }
+ else
+ {
+ // fell trying to get to the last node in the path
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF );
+ m_path->Invalidate();
+ return;
+ }
+ }
+ }
+
+
+ // avoid small obstacles
+ if (avoidObstacles && !isApproachingJumpArea && !m_improv->IsJumping() && m_segmentIndex < m_path->GetSegmentCount()-1)
+ {
+ FeelerReflexAdjustment( &m_goal );
+
+ // currently, this is only used for hostages, and their collision physics stinks
+ // do more feeler checks to avoid short obstacles
+ /*
+ const float inc = 0.25f;
+ for( float t = 0.5f; t < 1.0f; t += inc )
+ {
+ FeelerReflexAdjustment( &m_goal, t * StepHeight );
+ }
+ */
+
+ }
+
+ // move improv along path
+ m_improv->TrackPath( m_goal, deltaT );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest point to our current position on our current path
+ * If "local" is true, only check the portion of the path surrounding m_pathIndex.
+ */
+int CNavPathFollower::FindOurPositionOnPath( Vector *close, bool local ) const
+{
+ if (!m_path->IsValid())
+ return -1;
+
+ Vector along, toFeet;
+ Vector feet = m_improv->GetFeet();
+ Vector eyes = m_improv->GetEyes();
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ int closeIndex = -1;
+ float distSq;
+
+ int start, end;
+
+ if (local)
+ {
+ start = m_segmentIndex - 3;
+ if (start < 1)
+ start = 1;
+
+ end = m_segmentIndex + 3;
+ if (end > m_path->GetSegmentCount())
+ end = m_path->GetSegmentCount();
+ }
+ else
+ {
+ start = 1;
+ end = m_path->GetSegmentCount();
+ }
+
+ for( int i=start; i<end; ++i )
+ {
+ from = &(*m_path)[i-1]->pos;
+ to = &(*m_path)[i]->pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toFeet = feet - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toFeet, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - feet).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ))
+ continue;
+
+ // don't use points we cant reach
+ //if (!IsStraightLinePathWalkable( &pos ))
+ // continue;
+
+ closeDistSq = distSq;
+ if (close)
+ *close = pos;
+ closeIndex = i-1;
+ }
+ }
+
+ return closeIndex;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute a point a fixed distance ahead along our path.
+ * Returns path index just after point.
+ */
+int CNavPathFollower::FindPathPoint( float aheadRange, Vector *point, int *prevIndex )
+{
+ // find path index just past aheadRange
+ int afterIndex;
+
+ // finds the closest point on local area of path, and returns the path index just prior to it
+ Vector close;
+ int startIndex = FindOurPositionOnPath( &close, true );
+
+ if (prevIndex)
+ *prevIndex = startIndex;
+
+ if (startIndex <= 0)
+ {
+ // went off the end of the path
+ // or next point in path is unwalkable (ie: jump-down)
+ // keep same point
+ return m_segmentIndex;
+ }
+
+ // if we are crouching, just follow the path exactly
+ if (m_improv->IsCrouching())
+ {
+ // we want to move to the immediately next point along the path from where we are now
+ int index = startIndex+1;
+ if (index >= m_path->GetSegmentCount())
+ index = m_path->GetSegmentCount()-1;
+
+ *point = (*m_path)[ index ]->pos;
+
+ // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f; // 10
+ while ((*point - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++index;
+
+ if (index >= m_path->GetSegmentCount())
+ {
+ index = m_path->GetSegmentCount()-1;
+ break;
+ }
+
+ *point = (*m_path)[ index ]->pos;
+ }
+
+ return index;
+ }
+
+ // make sure we use a node a minimum distance ahead of us, to avoid wiggling
+ while (startIndex < m_path->GetSegmentCount()-1)
+ {
+ Vector pos = (*m_path)[ startIndex+1 ]->pos;
+
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f;
+ if ((pos - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++startIndex;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // if we hit a ladder or jump area, must stop (dont use ladder behind us)
+ if (startIndex > m_segmentIndex && startIndex < m_path->GetSegmentCount() &&
+ ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ *point = (*m_path)[ startIndex ]->pos;
+ return startIndex;
+ }
+
+ // we need the point just *ahead* of us
+ ++startIndex;
+ if (startIndex >= m_path->GetSegmentCount())
+ startIndex = m_path->GetSegmentCount()-1;
+
+ // if we hit a ladder or jump area, must stop
+ if (startIndex < m_path->GetSegmentCount() &&
+ ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ *point = (*m_path)[ startIndex ]->pos;
+ return startIndex;
+ }
+
+ // note direction of path segment we are standing on
+ Vector initDir = (*m_path)[ startIndex ]->pos - (*m_path)[ startIndex-1 ]->pos;
+ initDir.NormalizeInPlace();
+
+ Vector feet = m_improv->GetFeet();
+ Vector eyes = m_improv->GetEyes();
+ float rangeSoFar = 0;
+
+ // this flag is true if our ahead point is visible
+ bool visible = true;
+
+ Vector prevDir = initDir;
+
+ // step along the path until we pass aheadRange
+ bool isCorner = false;
+ int i;
+ for( i=startIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ Vector pos = (*m_path)[i]->pos;
+ Vector to = pos - (*m_path)[i-1]->pos;
+ Vector dir = to;
+ dir.NormalizeInPlace();
+
+ // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
+ if (DotProduct( dir, initDir ) < 0.0f) // -0.25f
+ {
+ --i;
+ break;
+ }
+
+ // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
+ if (DotProduct( dir, prevDir ) < 0.5f)
+ {
+ isCorner = true;
+ --i;
+ break;
+ }
+ prevDir = dir;
+
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ))
+ {
+ // presumably, the previous point is visible, so we will interpolate
+ visible = false;
+ break;
+ }
+
+ // if we encounter a ladder or jump area, we must stop
+ if (i < m_path->GetSegmentCount() &&
+ ((*m_path)[ i ]->ladder || (*m_path)[ i ]->area->GetAttributes() & NAV_MESH_JUMP))
+ break;
+
+ // Check straight-line path from our current position to this position
+ // Test for un-jumpable height change, or unrecoverable fall
+ //if (!IsStraightLinePathWalkable( &pos ))
+ //{
+ // --i;
+ // break;
+ //}
+
+ Vector along = (i == startIndex) ? (pos - feet) : (pos - (*m_path)[i-1]->pos);
+ rangeSoFar += along.Length2D();
+
+ // stop if we have gone farther than aheadRange
+ if (rangeSoFar >= aheadRange)
+ break;
+ }
+
+ if (i < startIndex)
+ afterIndex = startIndex;
+ else if (i < m_path->GetSegmentCount())
+ afterIndex = i;
+ else
+ afterIndex = m_path->GetSegmentCount()-1;
+
+
+ // compute point on the path at aheadRange
+ if (afterIndex == 0)
+ {
+ *point = (*m_path)[0]->pos;
+ }
+ else
+ {
+ // interpolate point along path segment
+ const Vector *afterPoint = &(*m_path)[ afterIndex ]->pos;
+ const Vector *beforePoint = &(*m_path)[ afterIndex-1 ]->pos;
+
+ Vector to = *afterPoint - *beforePoint;
+ float length = to.Length2D();
+
+ float t = 1.0f - ((rangeSoFar - aheadRange) / length);
+
+ if (t < 0.0f)
+ t = 0.0f;
+ else if (t > 1.0f)
+ t = 1.0f;
+
+ *point = *beforePoint + t * to;
+
+ // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
+ if (!visible)
+ {
+ const float sightStepSize = 25.0f;
+ float dt = sightStepSize / length;
+
+ Vector probe = *point + Vector( 0, 0, HalfHumanHeight );
+ while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) )
+ {
+ t -= dt;
+ *point = *beforePoint + t * to;
+ }
+
+ if (t <= 0.0f)
+ *point = *beforePoint;
+ }
+ }
+
+ // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
+ if (!isCorner)
+ {
+ const float epsilon = 50.0f;
+ Vector2D toPoint;
+ Vector2D centroid( m_improv->GetCentroid().x, m_improv->GetCentroid().y );
+
+ toPoint.x = point->x - centroid.x;
+ toPoint.y = point->y - centroid.y;
+
+ if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon ))
+ {
+ int i;
+ for( i=startIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ toPoint.x = (*m_path)[i]->pos.x - centroid.x;
+ toPoint.y = (*m_path)[i]->pos.y - centroid.y;
+ if ((*m_path)[i]->ladder || (*m_path)[i]->area->GetAttributes() & NAV_MESH_JUMP || toPoint.IsLengthGreaterThan( epsilon ))
+ {
+ *point = (*m_path)[i]->pos;
+ startIndex = i;
+ break;
+ }
+ }
+
+ if (i == m_path->GetSegmentCount())
+ {
+ *point = m_path->GetEndpoint();
+ startIndex = m_path->GetSegmentCount()-1;
+ }
+ }
+ }
+
+ // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
+ if (startIndex < m_path->GetSegmentCount())
+ return startIndex;
+
+ return m_path->GetSegmentCount()-1;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Do reflex avoidance movements if our "feelers" are touched
+ * @todo Parameterize feeler spacing
+ */
+void CNavPathFollower::FeelerReflexAdjustment( Vector *goalPosition, float height )
+{
+ // if we are in a "precise" area, do not do feeler adjustments
+ if (m_improv->GetLastKnownArea() && m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_PRECISE)
+ return;
+
+ // use the direction towards the goal
+ Vector dir = *goalPosition - m_improv->GetFeet();
+ dir.z = 0.0f;
+ dir.NormalizeInPlace();
+
+ Vector lat( -dir.y, dir.x, 0.0f );
+
+ const float feelerOffset = (m_improv->IsCrouching()) ? 15.0f : 20.0f; // 15, 20
+ const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
+ const float feelerLengthWalk = 30.0f;
+
+ const float feelerHeight = (height > 0.0f) ? height : StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
+
+ float feelerLength = (m_improv->IsRunning()) ? feelerLengthRun : feelerLengthWalk;
+
+ feelerLength = (m_improv->IsCrouching()) ? 20.0f : feelerLength;
+
+ //
+ // Feelers must follow floor slope
+ //
+ float ground;
+ Vector normal;
+ if (m_improv->GetSimpleGroundHeightWithFloor( m_improv->GetEyes(), &ground, &normal ) == false)
+ return;
+
+ // get forward vector along floor
+ dir = CrossProduct( lat, normal );
+
+ // correct the sideways vector
+ lat = CrossProduct( dir, normal );
+
+
+ Vector feet = m_improv->GetFeet();
+ feet.z += feelerHeight;
+
+ Vector from = feet + feelerOffset * lat;
+ Vector to = from + feelerLength * dir;
+
+ bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+ // draw debug beams
+ if (m_isDebug)
+ {
+ if (leftClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+ from = feet - feelerOffset * lat;
+ to = from + feelerLength * dir;
+
+ bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+ // draw debug beams
+ if (m_isDebug)
+ {
+ if (rightClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+
+
+ const float avoidRange = (m_improv->IsCrouching()) ? 150.0f : 300.0f;
+
+ if (!rightClear)
+ {
+ if (leftClear)
+ {
+ // right hit, left clear - veer left
+ *goalPosition = *goalPosition + avoidRange * lat;
+ //*goalPosition = m_improv->GetFeet() + avoidRange * lat;
+
+ //m_improv->StrafeLeft();
+ }
+ }
+ else if (!leftClear)
+ {
+ // right clear, left hit - veer right
+ *goalPosition = *goalPosition - avoidRange * lat;
+ //*goalPosition = m_improv->GetFeet() - avoidRange * lat;
+
+ //m_improv->StrafeRight();
+ }
+
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset the stuck-checker.
+ */
+CStuckMonitor::CStuckMonitor( void )
+{
+ m_isStuck = false;
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+}
+
+/**
+ * Reset the stuck-checker.
+ */
+void CStuckMonitor::Reset( void )
+{
+ m_isStuck = false;
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Test if the improv has become stuck
+ */
+void CStuckMonitor::Update( CImprovLocomotor *improv )
+{
+ if (m_isStuck)
+ {
+ // improv is stuck - see if it has moved far enough to be considered unstuck
+ const float unstuckRange = 75.0f;
+ if ((improv->GetCentroid() - m_stuckSpot).IsLengthGreaterThan( unstuckRange ))
+ {
+ // no longer stuck
+ Reset();
+ //PrintIfWatched( "UN-STUCK\n" );
+ }
+ }
+ else
+ {
+ // check if improv has become stuck
+
+ // compute average velocity over a short period (for stuck check)
+ Vector vel = improv->GetCentroid() - m_lastCentroid;
+
+ // if we are jumping, ignore Z
+ //if (improv->IsJumping())
+ // vel.z = 0.0f;
+
+ // ignore Z unless we are on a ladder (which is only Z)
+ if (!improv->IsUsingLadder())
+ vel.z = 0.0f;
+
+ // cannot be Length2D, or will break ladder movement (they are only Z)
+ float moveDist = vel.Length();
+
+ float deltaT = gpGlobals->curtime - m_lastTime;
+ if (deltaT <= 0.0f)
+ return;
+
+ m_lastTime = gpGlobals->curtime;
+
+ // compute current velocity
+ m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
+
+ if (m_avgVelIndex == MAX_VEL_SAMPLES)
+ m_avgVelIndex = 0;
+
+ if (m_avgVelCount < MAX_VEL_SAMPLES)
+ {
+ ++m_avgVelCount;
+ }
+ else
+ {
+ // we have enough samples to know if we're stuck
+
+ float avgVel = 0.0f;
+ for( int t=0; t<m_avgVelCount; ++t )
+ avgVel += m_avgVel[t];
+
+ avgVel /= m_avgVelCount;
+
+ // cannot make this velocity too high, or actors will get "stuck" when going down ladders
+ float stuckVel = (improv->IsUsingLadder()) ? 10.0f : 20.0f;
+
+ if (avgVel < stuckVel)
+ {
+ // note when and where we initially become stuck
+ m_stuckTimer.Start();
+ m_stuckSpot = improv->GetCentroid();
+ m_isStuck = true;
+ }
+ }
+ }
+
+ // always need to track this
+ m_lastCentroid = improv->GetCentroid();
+}
+
diff --git a/game/server/cstrike/cs_nav_path.h b/game/server/cstrike/cs_nav_path.h
new file mode 100644
index 0000000..001bc04
--- /dev/null
+++ b/game/server/cstrike/cs_nav_path.h
@@ -0,0 +1,246 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_path.h
+// Navigation Path encapsulation
+// Author: Michael S. Booth ([email protected]), November 2003
+
+#ifndef _CS_NAV_PATH_H_
+#define _CS_NAV_PATH_H_
+
+#include "nav_area.h"
+#include "bot_util.h"
+
+class CImprovLocomotor;
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * The CCSNavPath class encapsulates a path through space
+ */
+class CCSNavPath
+{
+public:
+ CCSNavPath( void )
+ {
+ m_segmentCount = 0;
+ }
+
+ struct PathSegment
+ {
+ CNavArea *area; ///< the area along the path
+ NavTraverseType how; ///< how to enter this area from the previous one
+ Vector pos; ///< our movement goal position at this point in the path
+ const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it
+ };
+
+ const PathSegment * operator[] ( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
+ const PathSegment *GetSegment( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
+ int GetSegmentCount( void ) const { return m_segmentCount; }
+ const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; }
+ bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path
+
+ float GetLength( void ) const; ///< return length of path from start to finish
+ bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
+
+ /// return the node index closest to the given distance along the path without going over - returns (-1) if error
+ int GetSegmentIndexAlongPath( float distAlong ) const;
+
+ bool IsValid( void ) const { return (m_segmentCount > 0); }
+ void Invalidate( void ) { m_segmentCount = 0; }
+
+ void Draw( const Vector &color = Vector( 1.0f, 0.3f, 0 ) ); ///< draw the path for debugging
+
+ /// compute closest point on path to given point
+ bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const;
+
+ void Optimize( void );
+
+ /**
+ * Compute shortest path from 'start' to 'goal' via A* algorithm.
+ * If returns true, path was build to the goal position.
+ * If returns false, path may either be invalid (use IsValid() to check), or valid but
+ * doesn't reach all the way to the goal.
+ */
+ template< typename CostFunctor >
+ bool Compute( const Vector &start, const Vector &goal, CostFunctor &costFunc )
+ {
+ Invalidate();
+
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( start + Vector( 0.0f, 0.0f, 1.0f ) );
+ if (startArea == NULL)
+ {
+ return false;
+ }
+
+ CNavArea *goalArea = TheNavMesh->GetNavArea( goal );
+
+ // if we are already in the goal area, build trivial path
+ if (startArea == goalArea)
+ {
+ BuildTrivialPath( start, goal );
+ return true;
+ }
+
+ // make sure path end position is on the ground
+ Vector pathEndPosition = goal;
+ if (goalArea)
+ {
+ pathEndPosition.z = goalArea->GetZ( pathEndPosition );
+ }
+ else
+ {
+ TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
+ }
+
+ //
+ // Compute shortest path to goal
+ //
+ CNavArea *closestArea;
+ bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea );
+
+ //
+ // Build path by following parent links
+ //
+
+ // get count
+ int count = 0;
+ CNavArea *area;
+ for( area = closestArea; area; area = area->GetParent() )
+ {
+ ++count;
+ }
+
+ // save room for endpoint
+ if (count > MAX_PATH_SEGMENTS-1)
+ {
+ count = MAX_PATH_SEGMENTS-1;
+ }
+
+ if (count == 0)
+ {
+ return false;
+ }
+
+ if (count == 1)
+ {
+ BuildTrivialPath( start, goal );
+ return true;
+ }
+
+ // build path
+ m_segmentCount = count;
+ for( area = closestArea; count && area; area = area->GetParent() )
+ {
+ --count;
+ m_path[ count ].area = area;
+ m_path[ count ].how = area->GetParentHow();
+ }
+
+ // compute path positions
+ if (ComputePathPositions() == false)
+ {
+ //PrintIfWatched( "CNavPath::Compute: Error building path\n" );
+ Invalidate();
+ return false;
+ }
+
+ // append path end position
+ m_path[ m_segmentCount ].area = closestArea;
+ m_path[ m_segmentCount ].pos = pathEndPosition;
+ m_path[ m_segmentCount ].ladder = NULL;
+ m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
+ ++m_segmentCount;
+
+ return pathResult;
+ }
+
+private:
+ enum { MAX_PATH_SEGMENTS = 256 };
+ PathSegment m_path[ MAX_PATH_SEGMENTS ];
+ int m_segmentCount;
+
+ bool ComputePathPositions( void ); ///< determine actual path positions
+ bool BuildTrivialPath( const Vector &start, const Vector &goal ); ///< utility function for when start and goal are in the same area
+
+ int FindNextOccludedNode( int anchor ); ///< used by Optimize()
+};
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Monitor improv movement and determine if it becomes stuck
+ */
+class CStuckMonitor
+{
+public:
+ CStuckMonitor( void );
+
+ void Reset( void );
+ void Update( CImprovLocomotor *improv );
+ bool IsStuck( void ) const { return m_isStuck; }
+
+ float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; }
+
+private:
+ bool m_isStuck; ///< if true, we are stuck
+ Vector m_stuckSpot; ///< the location where we became stuck
+ IntervalTimer m_stuckTimer; ///< how long we have been stuck
+
+ enum { MAX_VEL_SAMPLES = 5 };
+ float m_avgVel[ MAX_VEL_SAMPLES ];
+ int m_avgVelIndex;
+ int m_avgVelCount;
+ Vector m_lastCentroid;
+ float m_lastTime;
+};
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * The CNavPathFollower class implements path following behavior
+ */
+class CNavPathFollower
+{
+public:
+ CNavPathFollower( void );
+
+ void SetImprov( CImprovLocomotor *improv ) { m_improv = improv; }
+ void SetPath( CCSNavPath *path ) { m_path = path; }
+
+ void Reset( void );
+
+ #define DONT_AVOID_OBSTACLES false
+ void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path
+ void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off
+
+ bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck
+ void ResetStuck( void ) { m_stuckMonitor.Reset(); }
+ float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck
+
+ void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched
+
+private:
+ CImprovLocomotor *m_improv; ///< who is doing the path following
+
+ CCSNavPath *m_path; ///< the path being followed
+
+ int m_segmentIndex; ///< the point on the path the improv is moving towards
+ int m_behindIndex; ///< index of the node on the path just behind us
+ Vector m_goal; ///< last computed follow goal
+
+ bool m_isLadderStarted;
+
+ bool m_isDebug;
+
+ int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path
+ int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path.
+
+ CStuckMonitor m_stuckMonitor;
+};
+
+
+
+#endif // _CS_NAV_PATH_H_
+
diff --git a/game/server/cstrike/cs_nav_pathfind.h b/game/server/cstrike/cs_nav_pathfind.h
new file mode 100644
index 0000000..042179b
--- /dev/null
+++ b/game/server/cstrike/cs_nav_pathfind.h
@@ -0,0 +1,65 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_pathfind.h
+// Path-finding mechanisms using the Navigation Mesh
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#ifndef _CS_NAV_PATHFIND_H_
+#define _CS_NAV_PATHFIND_H_
+
+#include "nav_pathfind.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute travel distance along shortest path from startPos to goalPos.
+ * Return -1 if can't reach endPos from goalPos.
+ */
+template< typename CostFunctor >
+float NavAreaTravelDistance( const Vector &startPos, const Vector &goalPos, CostFunctor &costFunc )
+{
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( startPos );
+ if (startArea == NULL)
+ {
+ return -1.0f;
+ }
+
+ // compute path between areas using given cost heuristic
+ CNavArea *goalArea = NULL;
+ if (NavAreaBuildPath( startArea, NULL, &goalPos, costFunc, &goalArea ) == false)
+ {
+ return -1.0f;
+ }
+
+ // compute distance along path
+ if (goalArea->GetParent() == NULL)
+ {
+ // both points are in the same area - return euclidean distance
+ return (goalPos - startPos).Length();
+ }
+ else
+ {
+ CNavArea *area;
+ float distance;
+
+ // goalPos is assumed to be inside goalArea (or very close to it) - skip to next area
+ area = goalArea->GetParent();
+ distance = (goalPos - area->GetCenter()).Length();
+
+ for( ; area->GetParent(); area = area->GetParent() )
+ {
+ distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
+ }
+
+ // add in distance to startPos
+ distance += (startPos - area->GetCenter()).Length();
+
+ return distance;
+ }
+}
+
+#endif // _CS_NAV_PATHFIND_H_
diff --git a/game/server/cstrike/cs_player.cpp b/game/server/cstrike/cs_player.cpp
new file mode 100644
index 0000000..79ed9c1
--- /dev/null
+++ b/game/server/cstrike/cs_player.cpp
@@ -0,0 +1,8267 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "cs_player.h"
+#include "cs_gamerules.h"
+#include "trains.h"
+#include "vcollide_parse.h"
+#include "in_buttons.h"
+#include "igamemovement.h"
+#include "ai_hull.h"
+#include "ndebugoverlay.h"
+#include "weapon_csbase.h"
+#include "decals.h"
+#include "cs_ammodef.h"
+#include "IEffects.h"
+#include "cs_client.h"
+#include "client.h"
+#include "cs_shareddefs.h"
+#include "shake.h"
+#include "team.h"
+#include "weapon_c4.h"
+#include "weapon_parse.h"
+#include "weapon_knife.h"
+#include "movehelper_server.h"
+#include "tier0/vprof.h"
+#include "te_effect_dispatch.h"
+#include "vphysics/player_controller.h"
+#include "weapon_hegrenade.h"
+#include "weapon_flashbang.h"
+#include "weapon_csbasegun.h"
+#include "weapon_smokegrenade.h"
+#include <KeyValues.h>
+#include "engine/IEngineSound.h"
+#include "bot.h"
+#include "studio.h"
+#include <coordsize.h>
+#include "predicted_viewmodel.h"
+#include "props_shared.h"
+#include "tier0/icommandline.h"
+#include "info_camera_link.h"
+#include "hintmessage.h"
+#include "obstacle_pushaway.h"
+#include "movevars_shared.h"
+#include "death_pose.h"
+#include "basecsgrenade_projectile.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "CRagdollMagnet.h"
+#include "datacache/imdlcache.h"
+#include "npcevent.h"
+#include "cs_gamestats.h"
+#include "gamestats.h"
+#include "holiday_gift.h"
+#include "../../shared/cstrike/cs_achievement_constants.h"
+
+//=============================================================================
+// HPE_BEGIN
+//=============================================================================
+
+// [dwenger] Needed for global hostage list
+#include "cs_simple_hostage.h"
+
+// [dwenger] Needed for weapon type used tracking
+#include "../../shared/cstrike/cs_weapon_parse.h"
+
+#define REPORT_PLAYER_DAMAGE 0
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#pragma optimize( "", off )
+
+#pragma warning( disable : 4355 )
+
+// Minimum interval between rate-limited commands that players can run.
+#define CS_COMMAND_MAX_RATE 0.3
+
+const float CycleLatchInterval = 0.2f;
+
+#define CS_PUSHAWAY_THINK_CONTEXT "CSPushawayThink"
+
+ConVar cs_ShowStateTransitions( "cs_ShowStateTransitions", "-2", FCVAR_CHEAT, "cs_ShowStateTransitions <ent index or -1 for all>. Show player state transitions." );
+ConVar sv_max_usercmd_future_ticks( "sv_max_usercmd_future_ticks", "8", 0, "Prevents clients from running usercmds too far in the future. Prevents speed hacks." );
+ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." );
+//=============================================================================
+// HPE_BEGIN:
+// [Forrest] Allow MVP to be turned off for a server
+// [Forrest] Allow freezecam to be turned off for a server
+// [Forrest] Allow win panel to be turned off for a server
+//=============================================================================
+static void SvNoMVPChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ if ( var.IsValid() && var.GetBool() )
+ {
+ // Clear the MVPs of all players when MVP is turned off.
+ for ( int i = 1; i <= MAX_PLAYERS; i++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer )
+ {
+ pPlayer->SetNumMVPs( 0 );
+ }
+ }
+ }
+}
+ConVar sv_nomvp( "sv_nomvp", "0", 0, "Disable MVP awards.", SvNoMVPChangeCallback );
+ConVar sv_disablefreezecam( "sv_disablefreezecam", "0", FCVAR_REPLICATED, "Turn on/off freezecam on server" );
+ConVar sv_nowinpanel( "sv_nowinpanel", "0", FCVAR_REPLICATED, "Turn on/off win panel on server" );
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+
+// ConVar bot_mimic( "bot_mimic", "0", FCVAR_CHEAT );
+ConVar bot_freeze( "bot_freeze", "0", FCVAR_CHEAT );
+ConVar bot_crouch( "bot_crouch", "0", FCVAR_CHEAT );
+ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "180", FCVAR_CHEAT );
+
+ConVar sv_legacy_grenade_damage( "sv_legacy_grenade_damage", "0", FCVAR_REPLICATED, "Enable to replicate grenade damage behavior of the original Counter-Strike Source game." );
+
+extern ConVar mp_autokick;
+extern ConVar mp_holiday_nogifts;
+extern ConVar sv_turbophysics;
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Added in convars for freeze cam time length
+//=============================================================================
+
+extern ConVar spec_freeze_time;
+extern ConVar spec_freeze_traveltime;
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+extern ConVar ammo_hegrenade_max;
+extern ConVar ammo_flashbang_max;
+extern ConVar ammo_smokegrenade_max;
+
+
+#define THROWGRENADE_COUNTER_BITS 3
+
+
+EHANDLE g_pLastCTSpawn;
+EHANDLE g_pLastTerroristSpawn;
+
+void TE_RadioIcon( IRecipientFilter& filter, float delay, CBaseEntity *pPlayer );
+
+
+// -------------------------------------------------------------------------------- //
+// Classes
+// -------------------------------------------------------------------------------- //
+
+class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
+{
+public:
+ int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer *)pObject->GetGameData();
+ if ( pPlayer )
+ {
+ if ( pPlayer->TouchedPhysics() )
+ {
+ return 0;
+ }
+ }
+ return 1;
+ }
+};
+
+static CPhysicsPlayerCallback playerCallback;
+
+
+// -------------------------------------------------------------------------------- //
+// Ragdoll entities.
+// -------------------------------------------------------------------------------- //
+
+class CCSRagdoll : public CBaseAnimatingOverlay
+{
+public:
+ DECLARE_CLASS( CCSRagdoll, CBaseAnimatingOverlay );
+ DECLARE_SERVERCLASS();
+
+ // Transmit ragdolls to everyone.
+ virtual int UpdateTransmitState()
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+ void Init( void )
+ {
+ SetSolid( SOLID_BBOX );
+ SetMoveType( MOVETYPE_STEP );
+ SetFriction( 1.0f );
+ SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX );
+ m_takedamage = DAMAGE_NO;
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ SetAbsOrigin( m_hPlayer->GetAbsOrigin() );
+ SetAbsVelocity( m_hPlayer->GetAbsVelocity() );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ ChangeTeam( m_hPlayer->GetTeamNumber() );
+ UseClientSideAnimation();
+ }
+
+public:
+ // In case the client has the player entity, we transmit the player index.
+ // In case the client doesn't have it, we transmit the player's model index, origin, and angles
+ // so they can create a ragdoll in the right place.
+ CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
+ CNetworkVector( m_vecRagdollVelocity );
+ CNetworkVector( m_vecRagdollOrigin );
+ CNetworkVar(int, m_iDeathPose );
+ CNetworkVar(int, m_iDeathFrame );
+};
+
+LINK_ENTITY_TO_CLASS( cs_ragdoll, CCSRagdoll );
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CCSRagdoll, DT_CSRagdoll )
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+ SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ),
+ SendPropEHandle( SENDINFO( m_hPlayer ) ),
+ SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
+ SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
+ SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
+ SendPropVector( SENDINFO( m_vecRagdollVelocity ) ),
+ SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDeathFrame ), 5 ),
+ SendPropInt( SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0),
+ SendPropInt( SENDINFO( m_bClientSideAnimation ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+
+// -------------------------------------------------------------------------------- //
+// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
+// -------------------------------------------------------------------------------- //
+
+class CTEPlayerAnimEvent : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
+ {
+ }
+
+ CNetworkHandle( CBasePlayer, m_hPlayer );
+ CNetworkVar( int, m_iEvent );
+ CNetworkVar( int, m_nData );
+};
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
+ SendPropEHandle( SENDINFO( m_hPlayer ) ),
+ SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nData ), 32 )
+END_SEND_TABLE()
+
+static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
+
+void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
+{
+ CPVSFilter filter( (const Vector&)pPlayer->EyePosition() );
+
+ g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
+ g_TEPlayerAnimEvent.m_iEvent = event;
+ g_TEPlayerAnimEvent.m_nData = nData;
+ g_TEPlayerAnimEvent.Create( filter, 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Filters updates to a variable so that only non-local players see
+// the changes. This is so we can send a low-res origin to non-local players
+// while sending a hi-res one to the local player.
+// Input : *pVarData -
+// *pOut -
+// objectID -
+//-----------------------------------------------------------------------------
+
+void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
+{
+ pRecipients->SetAllRecipients();
+ pRecipients->ClearRecipient( objectID - 1 );
+ return ( void * )pVarData;
+}
+REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendNonLocalDataTable );
+
+
+// -------------------------------------------------------------------------------- //
+// Tables.
+// -------------------------------------------------------------------------------- //
+
+LINK_ENTITY_TO_CLASS( player, CCSPlayer );
+PRECACHE_REGISTER(player);
+
+BEGIN_SEND_TABLE_NOBASE( CCSPlayer, DT_CSLocalPlayerExclusive )
+ SendPropFloat( SENDINFO( m_flStamina ), 14, 0, 0, 1400 ),
+ SendPropInt( SENDINFO( m_iDirection ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iShotsFired ), 8, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flVelocityModifier ), 8, 0, 0, 1 ),
+
+ // send a hi-res origin to the local player for use in prediction
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj]Set up the send table for per-client domination data
+ //=============================================================================
+
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ),
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ),
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+END_SEND_TABLE()
+
+
+BEGIN_SEND_TABLE_NOBASE( CCSPlayer, DT_CSNonLocalPlayerExclusive )
+ // send a lo-res origin to other players
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+END_SEND_TABLE()
+
+
+IMPLEMENT_SERVERCLASS_ST( CCSPlayer, DT_CSPlayer )
+ SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
+ SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nMuzzleFlashParity" ),
+ SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
+ SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
+
+ // cs_playeranimstate and clientside animation takes care of these on the client
+ SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
+ SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
+
+ // We need to send a hi-res origin to the local player to avoid prediction errors sliding along walls
+ SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
+
+ // Data that only gets sent to the local player.
+ SendPropDataTable( "cslocaldata", 0, &REFERENCE_SEND_TABLE(DT_CSLocalPlayerExclusive), SendProxy_SendLocalDataTable ),
+ SendPropDataTable( "csnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_CSNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ),
+
+ SendPropInt( SENDINFO( m_iThrowGrenadeCounter ), THROWGRENADE_COUNTER_BITS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iAddonBits ), NUM_ADDON_BITS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iPrimaryAddon ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iSecondaryAddon ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iPlayerState ), Q_log2( NUM_PLAYER_STATES )+1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iAccount ), 16, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bInBombZone ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bInBuyZone ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iClass ), Q_log2( CS_NUM_CLASSES )+1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_ArmorValue ), 8 ),
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ),
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ),
+ SendPropBool( SENDINFO( m_bHasDefuser ) ),
+ SendPropBool( SENDINFO( m_bNightVisionOn ) ), //send as int so we can use a RecvProxy on the client
+ SendPropBool( SENDINFO( m_bHasNightVision ) ),
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ //SendPropBool( SENDINFO( m_bPickedUpDefuser ) ),
+ //SendPropBool( SENDINFO( m_bDefusedWithPickedUpKit) ),
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ SendPropBool( SENDINFO( m_bInHostageRescueZone ) ),
+ SendPropBool( SENDINFO( m_bIsDefusing ) ),
+
+ SendPropBool( SENDINFO( m_bResumeZoom ) ),
+ SendPropInt( SENDINFO( m_iLastZoom ), 8, SPROP_UNSIGNED ),
+
+#ifdef CS_SHIELD_ENABLED
+ SendPropBool( SENDINFO( m_bHasShield ) ),
+ SendPropBool( SENDINFO( m_bShieldDrawn ) ),
+#endif
+ SendPropBool( SENDINFO( m_bHasHelmet ) ),
+ SendPropFloat (SENDINFO(m_flFlashDuration), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flFlashMaxAlpha), 0, SPROP_NOSCALE ),
+ SendPropInt( SENDINFO( m_iProgressBarDuration ), 4, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flProgressBarStartTime ), 0, SPROP_NOSCALE ),
+ SendPropEHandle( SENDINFO( m_hRagdoll ) ),
+ SendPropInt( SENDINFO( m_cycleLatch ), 4, SPROP_UNSIGNED ),
+
+
+END_SEND_TABLE()
+
+
+BEGIN_DATADESC( CCSPlayer )
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "OnRescueZoneTouch", RescueZoneTouch ),
+ DEFINE_THINKFUNC( PushawayThink )
+
+END_DATADESC()
+
+
+// has to be included after above macros
+#include "cs_bot.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+
+// -------------------------------------------------------------------------------- //
+
+void cc_CreatePredictionError_f( const CCommand &args )
+{
+ float distance = 32;
+
+ if ( args.ArgC() >= 2 )
+ {
+ distance = atof(args[1]);
+ }
+
+ CBaseEntity *pEnt = CBaseEntity::Instance( 1 );
+ pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( distance, 0, 0 ) );
+}
+
+ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT );
+
+
+// -------------------------------------------------------------------------------- //
+// CCSPlayer implementation.
+// -------------------------------------------------------------------------------- //
+
+CCSPlayer::CCSPlayer()
+{
+ m_PlayerAnimState = CreatePlayerAnimState( this, this, LEGANIM_9WAY, true );
+
+ UseClientSideAnimation();
+
+ m_iLastWeaponFireUsercmd = 0;
+ m_iAddonBits = 0;
+ m_bEscaped = false;
+ m_iAccount = 0;
+
+ m_bIsVIP = false;
+ m_iClass = (int)CS_CLASS_NONE;
+ m_angEyeAngles.Init();
+
+ SetViewOffset( VEC_VIEW_SCALED( this ) );
+
+ m_pCurStateInfo = NULL; // no state yet
+ m_iThrowGrenadeCounter = 0;
+
+ m_lifeState = LIFE_DEAD; // Start "dead".
+ m_bInBombZone = false;
+ m_bInBuyZone = false;
+ m_bInHostageRescueZone = false;
+ m_flDeathTime = 0.0f;
+ m_iHostagesKilled = 0;
+ iRadioMenu = -1;
+ m_bTeamChanged = false;
+ m_iShotsFired = 0;
+ m_iDirection = 0;
+ m_receivesMoneyNextRound = true;
+ m_bIsBeingGivenItem = false;
+ m_isVIP = false;
+
+ m_bJustKilledTeammate = false;
+ m_bPunishedForTK = false;
+ m_iTeamKills = 0;
+ m_flLastMovement = gpGlobals->curtime;
+ m_iNextTimeCheck = 0;
+
+ m_szNewName[0] = 0;
+ m_szClanTag[0] = 0;
+
+ for ( int i=0; i<NAME_CHANGE_HISTORY_SIZE; i++ )
+ {
+ m_flNameChangeHistory[i] = -NAME_CHANGE_HISTORY_INTERVAL;
+ }
+
+ m_iIgnoreGlobalChat = 0;
+ m_bIgnoreRadio = false;
+
+ m_pHintMessageQueue = new CHintMessageQueue(this);
+ m_iDisplayHistoryBits = 0;
+ m_bShowHints = true;
+ m_flNextMouseoverUpdate = gpGlobals->curtime;
+
+ m_lastDamageHealth = 0;
+ m_lastDamageArmor = 0;
+
+ m_applyDeafnessTime = 0.0f;
+
+ m_cycleLatch = 0;
+ m_cycleLatchTimer.Invalidate();
+
+ m_iShouldHaveCash = 0;
+
+ m_lastNavArea = NULL;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Init achievement variables
+ // [menglish] Init bullet collision variables
+ //=============================================================================
+
+ m_NumEnemiesKilledThisRound = 0;
+ m_NumEnemiesAtRoundStart = 0;
+ m_KillingSpreeStartTime = -1;
+ m_firstKillBlindStartTime = -1;
+ m_killsWhileBlind = 0;
+ m_bSurvivedHeadshotDueToHelmet = false;
+ m_pGooseChaseDistractingPlayer = NULL;
+ m_gooseChaseStep = GC_NONE;
+ m_defuseDefenseStep = DD_NONE;
+ m_lastRoundResult = Invalid_Round_End_Reason;
+ m_bMadeFootstepNoise = false;
+ m_bombPickupTime = -1;
+ m_bMadePurchseThisRound = false;
+ m_roundsWonWithoutPurchase = 0;
+ m_iDeathFlags = 0;
+ m_lastFlashBangAttacker = NULL;
+ m_iMVPs = 0;
+ m_bKilledDefuser = false;
+ m_bKilledRescuer = false;
+ m_maxGrenadeKills = 0;
+ m_grenadeDamageTakenThisRound = 0;
+
+ m_vLastHitLocationObjectSpace = Vector(0,0,0);
+
+ m_wasNotKilledNaturally = false;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+}
+
+
+CCSPlayer::~CCSPlayer()
+{
+ delete m_pHintMessageQueue;
+ m_pHintMessageQueue = NULL;
+
+ // delete the records of damage taken and given
+ ResetDamageCounters();
+ m_PlayerAnimState->Release();
+}
+
+
+CCSPlayer *CCSPlayer::CreatePlayer( const char *className, edict_t *ed )
+{
+ CCSPlayer::s_PlayerEdict = ed;
+ return (CCSPlayer*)CreateEntityByName( className );
+}
+
+
+void CCSPlayer::Precache()
+{
+ Vector mins( -13, -13, -10 );
+ Vector maxs( 13, 13, 75 );
+
+ int i;
+ for ( i=0; i<CTPlayerModels.Count(); ++i )
+ {
+ PrecacheModel( CTPlayerModels[i] );
+ engine->ForceModelBounds( CTPlayerModels[i], mins, maxs );
+ }
+ for ( i=0; i<TerroristPlayerModels.Count(); ++i )
+ {
+ PrecacheModel( TerroristPlayerModels[i] );
+ engine->ForceModelBounds( TerroristPlayerModels[i], mins, maxs );
+ }
+
+ // Sigh - have to force identical VMTs for the player models. I'm just going to hard-code these
+ // strings here, rather than have char***'s or the CUtlVector<CUtlVector<>> equivalent.
+ engine->ForceSimpleMaterial( "materials/models/player/ct_urban/ct_urban.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_urban/ct_urban_glass.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_sas/ct_sas.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_sas/ct_sas_glass.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_gsg9/ct_gsg9.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_gign/ct_gign.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/ct_gign/ct_gign_glass.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/t_phoenix/t_phoenix.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/t_guerilla/t_guerilla.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/t_leet/t_leet.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/t_leet/t_leet_glass.vmt" );
+ engine->ForceSimpleMaterial( "materials/models/player/t_arctic/t_arctic.vmt" );
+
+#ifdef CS_SHIELD_ENABLED
+ PrecacheModel( SHIELD_VIEW_MODEL );
+#endif
+
+ PrecacheScriptSound( "Player.DeathHeadShot" );
+ PrecacheScriptSound( "Player.Death" );
+ PrecacheScriptSound( "Player.DamageHelmet" );
+ PrecacheScriptSound( "Player.DamageHeadShot" );
+ PrecacheScriptSound( "Flesh.BulletImpact" );
+ PrecacheScriptSound( "Player.DamageKevlar" );
+ PrecacheScriptSound( "Player.PickupWeapon" );
+ PrecacheScriptSound( "Player.NightVisionOff" );
+ PrecacheScriptSound( "Player.NightVisionOn" );
+ PrecacheScriptSound( "Player.FlashlightOn" );
+ PrecacheScriptSound( "Player.FlashlightOff" );
+
+ // CS Bot sounds
+ PrecacheScriptSound( "Bot.StuckSound" );
+ PrecacheScriptSound( "Bot.StuckStart" );
+ PrecacheScriptSound( "Bot.FellOff" );
+
+ UTIL_PrecacheOther( "item_kevlar" );
+ UTIL_PrecacheOther( "item_assaultsuit" );
+ UTIL_PrecacheOther( "item_defuser" );
+
+ PrecacheModel ( "sprites/glow01.vmt" );
+ PrecacheModel ( "models/items/cs_gift.mdl" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow pre-frame adjustments on the player
+//-----------------------------------------------------------------------------
+ConVar sv_runcmds( "sv_runcmds", "1" );
+void CCSPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper )
+{
+ VPROF( "CCSPlayer::PlayerRunCommand" );
+
+ if ( !sv_runcmds.GetInt() )
+ return;
+
+ // don't run commands in the future
+ if ( !IsEngineThreaded() &&
+ ( ucmd->tick_count > (gpGlobals->tickcount + sv_max_usercmd_future_ticks.GetInt()) ) )
+ {
+ DevMsg( "Client cmd out of sync (delta %i).\n", ucmd->tick_count - gpGlobals->tickcount );
+ return;
+ }
+
+ // If they use a negative bot_mimic value, then don't process their usercmds, but have
+ // bots process them instead (so they can stay still and have the bot move around).
+ CUserCmd tempCmd;
+ if ( -bot_mimic.GetInt() == entindex() )
+ {
+ tempCmd = *ucmd;
+ ucmd = &tempCmd;
+
+ ucmd->forwardmove = ucmd->sidemove = ucmd->upmove = 0;
+ ucmd->buttons = 0;
+ ucmd->impulse = 0;
+ }
+
+ if ( IsBot() && bot_crouch.GetInt() )
+ ucmd->buttons |= IN_DUCK;
+
+ BaseClass::PlayerRunCommand( ucmd, moveHelper );
+}
+
+
+bool CCSPlayer::RunMimicCommand( CUserCmd& cmd )
+{
+ if ( !IsBot() )
+ return false;
+
+ int iMimic = abs( bot_mimic.GetInt() );
+ if ( iMimic > gpGlobals->maxClients )
+ return false;
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( iMimic );
+ if ( !pPlayer )
+ return false;
+
+ if ( !pPlayer->GetLastUserCommand() )
+ return false;
+
+ cmd = *pPlayer->GetLastUserCommand();
+ cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
+
+ pl.fixangle = FIXANGLE_NONE;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Simulates a single frame of movement for a player
+//-----------------------------------------------------------------------------
+void CCSPlayer::RunPlayerMove( const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
+{
+ CUserCmd cmd;
+
+ // Store off the globals.. they're gonna get whacked
+ float flOldFrametime = gpGlobals->frametime;
+ float flOldCurtime = gpGlobals->curtime;
+
+ float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
+ this->SetTimeBase( flTimeBase );
+
+ CUserCmd lastUserCmd = *GetLastUserCommand();
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+
+ if ( !RunMimicCommand( cmd ) )
+ {
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+
+ MoveHelperServer()->SetHost( this );
+ PlayerRunCommand( &cmd, MoveHelperServer() );
+
+ // save off the last good usercmd
+ if ( -bot_mimic.GetInt() == entindex() )
+ {
+ CUserCmd lastCmd = *GetLastUserCommand();
+ lastCmd.command_number = cmd.command_number;
+ lastCmd.tick_count = cmd.tick_count;
+ SetLastUserCommand( lastCmd );
+ }
+ else
+ {
+ SetLastUserCommand( cmd );
+ }
+
+ // Clear out any fixangle that has been set
+ pl.fixangle = FIXANGLE_NONE;
+
+ // Restore the globals..
+ gpGlobals->frametime = flOldFrametime;
+ gpGlobals->curtime = flOldCurtime;
+
+ MoveHelperServer()->SetHost( NULL );
+}
+
+
+void CCSPlayer::InitialSpawn( void )
+{
+ BaseClass::InitialSpawn();
+
+ // we're going to give the bots money here instead of FinishClientPutInServer()
+ // because of the bots' timing for purchasing weapons/items.
+ if ( IsBot() )
+ {
+ m_iAccount = CSGameRules()->GetStartMoney();
+ }
+
+ if ( !engine->IsDedicatedServer() && TheNavMesh->IsOutOfDate() && this == UTIL_GetListenServerHost() )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "The Navigation Mesh was built using a different version of this map." );
+ }
+
+ State_Enter( STATE_WELCOME );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] We reset the stats at the beginning of the map (including domination tracking)
+ //=============================================================================
+
+ CCS_GameStats.ResetPlayerStats(this);
+ RemoveNemesisRelationships();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+}
+
+void CCSPlayer::SetModelFromClass( void )
+{
+ if ( GetTeamNumber() == TEAM_TERRORIST )
+ {
+ int index = m_iClass - FIRST_T_CLASS;
+ if ( index < 0 || index >= TerroristPlayerModels.Count() )
+ {
+ index = RandomInt( 0, TerroristPlayerModels.Count() - 1 );
+ m_iClass = index + FIRST_T_CLASS; // clean up players who selected a higher class than we support yet
+ }
+ SetModel( TerroristPlayerModels[index] );
+ }
+ else if ( GetTeamNumber() == TEAM_CT )
+ {
+ int index = m_iClass - FIRST_CT_CLASS;
+ if ( index < 0 || index >= CTPlayerModels.Count() )
+ {
+ index = RandomInt( 0, CTPlayerModels.Count() - 1 );
+ m_iClass = index + FIRST_CT_CLASS; // clean up players who selected a higher class than we support yet
+ }
+ SetModel( CTPlayerModels[index] );
+ }
+ else
+ {
+ SetModel( CTPlayerModels[0] );
+ }
+}
+
+void CCSPlayer::Spawn()
+{
+ m_RateLimitLastCommandTimes.Purge();
+
+ // Get rid of the progress bar...
+ SetProgressBarTime( 0 );
+
+ CreateViewModel( 1 );
+
+ // Set their player model.
+ SetModelFromClass();
+
+ BaseClass::Spawn();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [pfreese] Clear the last known nav area (used to be done by CBasePlayer)
+ //=============================================================================
+
+ m_lastNavArea = NULL;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round.
+
+ // Override what CBasePlayer set for the view offset.
+ SetViewOffset( VEC_VIEW_SCALED( this ) );
+
+ //
+ // Our player movement speed is set once here. This will override the cl_xxxx
+ // cvars unless they are set to be lower than this.
+ //
+ SetMaxSpeed( CS_PLAYER_SPEED_RUN );
+
+ SetFOV( this, 0 );
+
+ m_bIsDefusing = false;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Reset hostage-related variables
+ //=============================================================================
+
+ m_bIsRescuing = false;
+ m_bInjuredAHostage = false;
+ m_iNumFollowers = 0;
+
+ // [tj] Reset this flag if the player is not in observer mode (as happens when a player spawns late)
+ if (m_iPlayerState != STATE_OBSERVER_MODE)
+ {
+ m_wasNotKilledNaturally = false;
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ m_iShotsFired = 0;
+ m_iDirection = 0;
+
+ if ( m_pHintMessageQueue )
+ {
+ m_pHintMessageQueue->Reset();
+ }
+ m_iDisplayHistoryBits &= ~DHM_ROUND_CLEAR;
+
+ // Special-case here. A bunch of things happen in CBasePlayer::Spawn(), and we really want the
+ // player states to control these things, so give whatever player state we're in a chance
+ // to reinitialize itself.
+ State_Transition( m_iPlayerState );
+
+ ClearFlashbangScreenFade();
+
+ m_flVelocityModifier = 1.0f;
+
+ ResetStamina();
+
+ m_flLastRadarUpdateTime = 0.0f;
+
+ m_iNumSpawns++;
+
+ if ( !engine->IsDedicatedServer() && CSGameRules()->m_iTotalRoundsPlayed < 2 && TheNavMesh->IsOutOfDate() && this == UTIL_GetListenServerHost() )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "The Navigation Mesh was built using a different version of this map." );
+ }
+
+ m_bTeamChanged = false;
+ m_iOldTeam = TEAM_UNASSIGNED;
+
+ m_iRadioMessages = 60;
+ m_flRadioTime = gpGlobals->curtime;
+
+ if ( m_hRagdoll )
+ {
+ UTIL_Remove( m_hRagdoll );
+ }
+
+ m_hRagdoll = NULL;
+
+ // did we change our name while we were dead?
+ if ( m_szNewName[0] != 0 )
+ {
+ ChangeName( m_szNewName );
+ m_szNewName[0] = 0;
+ }
+
+ if ( m_bIsVIP )
+ {
+ HintMessage( "#Hint_you_are_the_vip", true, true );
+ }
+
+ m_bIsInAutoBuy = false;
+ m_bIsInRebuy = false;
+ m_bAutoReload = false;
+
+ SetContextThink( &CCSPlayer::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, CS_PUSHAWAY_THINK_CONTEXT );
+
+ if ( GetActiveWeapon() && !IsObserver() )
+ {
+ GetActiveWeapon()->Deploy();
+ m_flNextAttack = gpGlobals->curtime; // Allow reloads to finish, since we're playing the deploy anim instead. This mimics goldsrc behavior, anyway.
+ }
+
+ m_applyDeafnessTime = 0.0f;
+
+ m_cycleLatch = 0;
+ m_cycleLatchTimer.Start( RandomFloat( 0.0f, CycleLatchInterval ) );
+
+ StockPlayerAmmo();
+ }
+
+void CCSPlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data )
+{
+ if ( CSGameRules()->IsLogoMap() )
+ return;
+
+ if ( CommandLine()->FindParm("-makedevshots") )
+ return;
+
+ BaseClass::ShowViewPortPanel( name, bShow, data );
+}
+
+void CCSPlayer::ClearFlashbangScreenFade( void )
+{
+ if( IsBlind() )
+ {
+ color32 clr = { 0, 0, 0, 0 };
+ UTIL_ScreenFade( this, clr, 0.01, 0.0, FFADE_OUT | FFADE_PURGE );
+
+ m_flFlashDuration = 0.0f;
+ m_flFlashMaxAlpha = 255.0f;
+ }
+
+ // clear blind time (after screen fades are canceled)
+ m_blindUntilTime = 0.0f;
+ m_blindStartTime = 0.0f;
+}
+
+void CCSPlayer::GiveDefaultItems()
+{
+ // Always give the player the knife.
+ CBaseCombatWeapon *pistol = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ if ( pistol )
+ {
+ return;
+ }
+
+ m_bUsingDefaultPistol = true;
+
+ if ( GetTeamNumber() == TEAM_CT )
+ {
+ GiveNamedItem( "weapon_knife" );
+ GiveNamedItem( "weapon_usp" );
+ GiveAmmo( 24, BULLET_PLAYER_45ACP );
+ }
+ else if ( GetTeamNumber() == TEAM_TERRORIST )
+ {
+ GiveNamedItem( "weapon_knife" );
+ GiveNamedItem( "weapon_glock" );
+ GiveAmmo( 40, BULLET_PLAYER_9MM );
+ }
+}
+
+void CCSPlayer::SetClanTag( const char *pTag )
+{
+ if ( pTag )
+ {
+ Q_strncpy( m_szClanTag, pTag, sizeof( m_szClanTag ) );
+ }
+}
+void CCSPlayer::CreateRagdollEntity()
+{
+ // If we already have a ragdoll, don't make another one.
+ CCSRagdoll *pRagdoll = dynamic_cast< CCSRagdoll* >( m_hRagdoll.Get() );
+
+ if ( !pRagdoll )
+ {
+ // create a new one
+ pRagdoll = dynamic_cast< CCSRagdoll* >( CreateEntityByName( "cs_ragdoll" ) );
+ }
+
+ if ( pRagdoll )
+ {
+ pRagdoll->m_hPlayer = this;
+ pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
+ pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
+ pRagdoll->m_nModelIndex = m_nModelIndex;
+ pRagdoll->m_nForceBone = m_nForceBone;
+ pRagdoll->m_vecForce = m_vecTotalBulletForce;
+ pRagdoll->m_iDeathPose = m_iDeathPose;
+ pRagdoll->m_iDeathFrame = m_iDeathFrame;
+ pRagdoll->Init();
+ }
+
+ // ragdolls will be removed on round restart automatically
+ m_hRagdoll = pRagdoll;
+}
+
+int CCSPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // set damage type sustained
+ m_bitsDamageType |= info.GetDamageType();
+
+ if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) )
+ return 0;
+
+ // don't apply damage forces in CS
+
+ // fire global game event
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
+
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("health", MAX(0, m_iHealth) );
+ event->SetInt("armor", MAX(0, ArmorValue()) );
+
+ event->SetInt( "dmg_health", m_lastDamageHealth );
+ event->SetInt( "dmg_armor", m_lastDamageArmor );
+
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ event->SetInt( "hitgroup", HITGROUP_GENERIC );
+ }
+ else
+ {
+ event->SetInt( "hitgroup", m_LastHitGroup );
+ }
+
+ CBaseEntity * attacker = info.GetAttacker();
+ const char *weaponName = "";
+
+ if ( attacker->IsPlayer() )
+ {
+ CBasePlayer *player = ToBasePlayer( attacker );
+ event->SetInt("attacker", player->GetUserID() ); // hurt by other player
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+ if ( pInflictor )
+ {
+ if ( pInflictor == player )
+ {
+ // If the inflictor is the killer, then it must be their current weapon doing the damage
+ if ( player->GetActiveWeapon() )
+ {
+ weaponName = player->GetActiveWeapon()->GetClassname();
+ }
+ }
+ else
+ {
+ weaponName = STRING( pInflictor->m_iClassname ); // it's just that easy
+ }
+ }
+ }
+ else
+ {
+ event->SetInt("attacker", 0 ); // hurt by "world"
+ }
+
+ if ( strncmp( weaponName, "weapon_", 7 ) == 0 )
+ {
+ weaponName += 7;
+ }
+ else if( strncmp( weaponName, "hegrenade", 9 ) == 0 ) //"hegrenade_projectile"
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Handle grenade-surviving achievement
+ //=============================================================================
+ if (info.GetAttacker()->GetTeamNumber() != GetTeamNumber())
+ {
+ m_grenadeDamageTakenThisRound += info.GetDamage();
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ weaponName = "hegrenade";
+ }
+ else if( strncmp( weaponName, "flashbang", 9 ) == 0 ) //"flashbang_projectile"
+ {
+ weaponName = "flashbang";
+ }
+ else if( strncmp( weaponName, "smokegrenade", 12 ) == 0 ) //"smokegrenade_projectile"
+ {
+ weaponName = "smokegrenade";
+ }
+
+ event->SetString( "weapon", weaponName );
+ event->SetInt( "priority", 5 );
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ return 1;
+}
+
+//=============================================================================
+// HPE_BEGIN:
+// [dwenger] Supports fun-fact
+//=============================================================================
+
+// Returns the % of the enemies this player killed in the round
+int CCSPlayer::GetPercentageOfEnemyTeamKilled()
+{
+ if ( m_NumEnemiesAtRoundStart > 0 )
+ {
+ return (int)( ( (float)m_NumEnemiesKilledThisRound / (float)m_NumEnemiesAtRoundStart ) * 100.0f );
+ }
+
+ return 0;
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+void CCSPlayer::Event_Killed( const CTakeDamageInfo &info )
+{
+ //=============================================================================
+ // HPE_BEGIN:
+ // [pfreese] Process on-death achievements
+ //=============================================================================
+
+ ProcessPlayerDeathAchievements(ToCSPlayer(info.GetAttacker()), this, info);
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ SetArmorValue( 0 );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Added a parameter so we know if it was death that caused the drop
+ // [menglish] Keep track of what the player has dropped for the freeze panel callouts
+ //=============================================================================
+
+ CBaseEntity* pAttacker = info.GetAttacker();
+ bool friendlyFire = pAttacker && pAttacker->GetTeamNumber() == GetTeamNumber();
+
+ //Only count the drop if it was not friendly fire
+ DropWeapons(true, !friendlyFire);
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ // Just in case the progress bar is on screen, kill it.
+ SetProgressBarTime( 0 );
+
+ m_bIsDefusing = false;
+
+ m_bHasNightVision = false;
+ m_bNightVisionOn = false;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ m_bPickedUpDefuser = false;
+ m_bDefusedWithPickedUpKit = false;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ m_bHasHelmet = false;
+
+ m_flFlashDuration = 0.0f;
+
+ FlashlightTurnOff();
+
+ // show killer in death cam mode
+ if( IsValidObserverTarget( info.GetAttacker() ) )
+ {
+ SetObserverTarget( info.GetAttacker() );
+ }
+ else
+ {
+ ResetObserverMode();
+ }
+
+ //update damage info with our accumulated physics force
+ CTakeDamageInfo subinfo = info;
+
+ // HACK[pfreese]: scale impulse up for visual effect
+ const float kImpulseBonusScale = 2.0f;
+ subinfo.SetDamageForce( m_vecTotalBulletForce * kImpulseBonusScale);
+
+ //Adrian: Select a death pose to extrapolate the ragdoll's velocity.
+ SelectDeathPose( info );
+
+ // See if there's a ragdoll magnet that should influence our force.
+ CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this );
+ if( pMagnet )
+ {
+ m_vecTotalBulletForce += pMagnet->GetForceVector( this );
+ }
+
+ // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
+ // because we still want to transmit to the clients in our PVS.
+ CreateRagdollEntity();
+
+ // Special code to drop holiday gifts for the holiday achievement
+ if ( ( mp_holiday_nogifts.GetBool() == false ) && UTIL_IsHolidayActive( 3 /*kHoliday_Christmas*/ ) )
+ {
+ if ( RandomInt( 0, 100 ) < 20 )
+ {
+ CHolidayGift::Create( WorldSpaceCenter(), GetAbsAngles(), EyeAngles(), GetAbsVelocity(), this );
+ }
+ }
+
+ State_Transition( STATE_DEATH_ANIM ); // Transition into the dying state.
+ BaseClass::Event_Killed( subinfo );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [pfreese] If this kill ended the round, award the MVP to someone on the
+ // winning team.
+ // TODO - move this code somewhere else more MVP related
+ //=============================================================================
+
+ bool roundWasAlreadyWon = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE);
+ bool roundIsWonNow = CSGameRules()->CheckWinConditions();
+
+ if ( !roundWasAlreadyWon && roundIsWonNow )
+ {
+ CCSPlayer* pMVP = NULL;
+ int maxKills = 0;
+ int maxDamage = 0;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if ( pPlayer )
+ {
+ // only consider players on the winning team
+ if ( pPlayer->GetTeamNumber() != CSGameRules()->m_iRoundWinStatus )
+ continue;
+
+ int nKills = CCS_GameStats.FindPlayerStats( pPlayer ).statsCurrentRound[CSSTAT_KILLS];
+ int nDamage = CCS_GameStats.FindPlayerStats( pPlayer ).statsCurrentRound[CSSTAT_DAMAGE];
+
+ if ( nKills > maxKills || ( nKills == maxKills && nDamage > maxDamage ) )
+ {
+ pMVP = pPlayer;
+ maxKills = nKills;
+ maxDamage = nDamage;
+ }
+ }
+ }
+
+ if ( pMVP )
+ {
+ pMVP->IncrementNumMVPs( CSMVP_ELIMINATION );
+ }
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ OutputDamageGiven();
+ OutputDamageTaken();
+ ResetDamageCounters();
+
+ if ( m_bPunishedForTK )
+ {
+ m_bPunishedForTK = false;
+ HintMessage( "#Hint_cannot_play_because_tk", true, true );
+ }
+
+ if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) )
+ {
+ m_iDisplayHistoryBits |= DHF_SPEC_DUCK;
+ HintMessage( "#Spec_Duck", true, true );
+ }
+}
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish, tj] Update and check any one-off achievements based on the kill
+//=============================================================================
+
+// Notify that I've killed some other entity. (called from Victim's Event_Killed).
+void CCSPlayer::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
+{
+ BaseClass::Event_KilledOther(pVictim, info);
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+void CCSPlayer::DeathSound( const CTakeDamageInfo &info )
+{
+ if( m_LastHitGroup == HITGROUP_HEAD )
+ {
+ EmitSound( "Player.DeathHeadShot" );
+ }
+ else
+ {
+ EmitSound( "Player.Death" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSPlayer::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity )
+{
+ BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity );
+
+ if ( sv_turbophysics.GetBool() )
+ return;
+
+ // Setup the HL2 specific callback.
+ GetPhysicsController()->SetEventHandler( &playerCallback );
+}
+
+void CCSPlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
+{
+ if ( !CanMove() )
+ return;
+
+ BaseClass::VPhysicsShadowUpdate( pPhysics );
+}
+
+bool CCSPlayer::HasShield() const
+{
+#ifdef CS_SHIELD_ENABLED
+ return m_bHasShield;
+#else
+ return false;
+#endif
+}
+
+
+bool CCSPlayer::IsShieldDrawn() const
+{
+#ifdef CS_SHIELD_ENABLED
+ return m_bShieldDrawn;
+#else
+ return false;
+#endif
+}
+
+
+void CCSPlayer::CheatImpulseCommands( int iImpulse )
+{
+ switch( iImpulse )
+ {
+ case 101:
+ {
+ if( sv_cheats->GetBool() )
+ {
+ extern int gEvilImpulse101;
+ gEvilImpulse101 = true;
+
+ AddAccount( 16000 );
+
+ GiveAmmo( 250, BULLET_PLAYER_50AE );
+ GiveAmmo( 250, BULLET_PLAYER_762MM );
+ GiveAmmo( 250, BULLET_PLAYER_338MAG );
+ GiveAmmo( 250, BULLET_PLAYER_556MM );
+ GiveAmmo( 250, BULLET_PLAYER_556MM_BOX );
+ GiveAmmo( 250, BULLET_PLAYER_9MM );
+ GiveAmmo( 250, BULLET_PLAYER_BUCKSHOT );
+ GiveAmmo( 250, BULLET_PLAYER_45ACP );
+ GiveAmmo( 250, BULLET_PLAYER_357SIG );
+ GiveAmmo( 250, BULLET_PLAYER_57MM );
+
+ gEvilImpulse101 = false;
+ }
+ }
+ break;
+
+ default:
+ {
+ BaseClass::CheatImpulseCommands( iImpulse );
+ }
+ }
+}
+
+void CCSPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
+{
+ BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
+
+ int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
+ PointCameraSetupVisibility( this, area, pvs, pvssize );
+}
+
+void CCSPlayer::UpdateAddonBits()
+{
+ int iNewBits = 0;
+
+ int nFlashbang = GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_FLASHBANG ) );
+ if ( dynamic_cast< CFlashbang* >( GetActiveWeapon() ) )
+ {
+ --nFlashbang;
+ }
+
+ if ( nFlashbang >= 1 )
+ iNewBits |= ADDON_FLASHBANG_1;
+
+ if ( nFlashbang >= 2 )
+ iNewBits |= ADDON_FLASHBANG_2;
+
+ if ( GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_HEGRENADE ) ) &&
+ !dynamic_cast< CHEGrenade* >( GetActiveWeapon() ) )
+ {
+ iNewBits |= ADDON_HE_GRENADE;
+ }
+
+ if ( GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_SMOKEGRENADE ) ) &&
+ !dynamic_cast< CSmokeGrenade* >( GetActiveWeapon() ) )
+ {
+ iNewBits |= ADDON_SMOKE_GRENADE;
+ }
+
+ if ( HasC4() && !dynamic_cast< CC4* >( GetActiveWeapon() ) )
+ iNewBits |= ADDON_C4;
+
+ if ( HasDefuser() )
+ iNewBits |= ADDON_DEFUSEKIT;
+
+ CWeaponCSBase *weapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE ));
+ if ( weapon && weapon != GetActiveWeapon() )
+ {
+ iNewBits |= ADDON_PRIMARY;
+ m_iPrimaryAddon = weapon->GetWeaponID();
+ }
+ else
+ {
+ m_iPrimaryAddon = WEAPON_NONE;
+ }
+
+ weapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL ));
+ if ( weapon && weapon != GetActiveWeapon() )
+ {
+ iNewBits |= ADDON_PISTOL;
+ if ( weapon->GetWeaponID() == WEAPON_ELITE )
+ {
+ iNewBits |= ADDON_PISTOL2;
+ }
+ m_iSecondaryAddon = weapon->GetWeaponID();
+ }
+ else if ( weapon && weapon->GetWeaponID() == WEAPON_ELITE )
+ {
+ // The active weapon is weapon_elite. Set ADDON_PISTOL2 without ADDON_PISTOL, so we know
+ // to display the empty holster.
+ iNewBits |= ADDON_PISTOL2;
+ m_iSecondaryAddon = weapon->GetWeaponID();
+ }
+ else
+ {
+ m_iSecondaryAddon = WEAPON_NONE;
+ }
+
+ m_iAddonBits = iNewBits;
+}
+
+void CCSPlayer::UpdateRadar()
+{
+ // update once a second
+ if ( (m_flLastRadarUpdateTime + 1.0) > gpGlobals->curtime )
+ return;
+
+ m_flLastRadarUpdateTime = gpGlobals->curtime;
+
+ // update positions of all players outside of my PVS
+ CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits;
+ engine->Message_DetermineMulticastRecipients( false, EyePosition(), playerbits );
+
+ CSingleUserRecipientFilter user( this );
+ UserMessageBegin( user, "UpdateRadar" );
+
+ for ( int i=0; i < MAX_PLAYERS; i++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i+1 ) );
+
+ if ( !pPlayer )
+ continue; // nothing there
+
+ bool bSameTeam = pPlayer->GetTeamNumber() == GetTeamNumber();
+
+ if ( playerbits.Get(i) && bSameTeam == true )
+ continue; // this player is in my PVS and not in my team, don't update radar pos
+
+ if ( pPlayer == this )
+ continue;
+
+ if ( !pPlayer->IsAlive() || pPlayer->IsObserver() || !pPlayer->IsConnected() )
+ continue; // don't update specattors or dead players
+
+ WRITE_BYTE( i+1 ); // player index as entity
+ WRITE_SBITLONG( pPlayer->GetAbsOrigin().x/4, COORD_INTEGER_BITS-1 );
+ WRITE_SBITLONG( pPlayer->GetAbsOrigin().y/4, COORD_INTEGER_BITS-1 );
+ WRITE_SBITLONG( pPlayer->GetAbsOrigin().z/4, COORD_INTEGER_BITS-1 );
+ WRITE_SBITLONG( AngleNormalize( pPlayer->GetAbsAngles().y ), 9 );
+ }
+
+ WRITE_BYTE( 0 ); // end marker
+
+ MessageEnd();
+}
+
+void CCSPlayer::UpdateMouseoverHints()
+{
+ if ( IsBlind() || IsObserver() )
+ return;
+
+ Vector forward, up;
+ EyeVectors( &forward, NULL, &up );
+
+ trace_t tr;
+ // Search for objects in a sphere (tests for entities that are not solid, yet still useable)
+ Vector searchStart = EyePosition();
+ Vector searchEnd = searchStart + forward * 2048;
+
+ int useableContents = MASK_NPCSOLID_BRUSHONLY | MASK_VISIBLE_AND_NPCS;
+
+ UTIL_TraceLine( searchStart, searchEnd, useableContents, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction != 1.0f )
+ {
+ if (tr.DidHitNonWorldEntity() && tr.m_pEnt)
+ {
+ CBaseEntity *pObject = tr.m_pEnt;
+ switch ( pObject->Classify() )
+ {
+ case CLASS_PLAYER:
+ {
+ const float grenadeBloat = 1.2f; // Be conservative in estimating what a player can distinguish
+ if ( !TheBots->IsLineBlockedBySmoke( EyePosition(), pObject->EyePosition(), grenadeBloat ) )
+ {
+ if ( g_pGameRules->PlayerRelationship( this, pObject ) == GR_TEAMMATE )
+ {
+ if ( !(m_iDisplayHistoryBits & DHF_FRIEND_SEEN) )
+ {
+ m_iDisplayHistoryBits |= DHF_FRIEND_SEEN;
+ HintMessage( "#Hint_spotted_a_friend", true );
+ }
+ }
+ else
+ {
+ if ( !(m_iDisplayHistoryBits & DHF_ENEMY_SEEN) )
+ {
+ m_iDisplayHistoryBits |= DHF_ENEMY_SEEN;
+ HintMessage( "#Hint_spotted_an_enemy", true );
+ }
+ }
+ }
+ }
+ break;
+ case CLASS_PLAYER_ALLY:
+ switch ( GetTeamNumber() )
+ {
+ case TEAM_CT:
+ if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_FAR) && tr.fraction > 0.1f )
+ {
+ m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR;
+ HintMessage( "#Hint_rescue_the_hostages", true );
+ }
+ else if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_NEAR) && tr.fraction <= 0.1f )
+ {
+ m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR;
+ m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_NEAR;
+ HintMessage( "#Hint_press_use_so_hostage_will_follow", false );
+ }
+ break;
+ case TEAM_TERRORIST:
+ if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_FAR) )
+ {
+ m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR;
+ HintMessage( "#Hint_prevent_hostage_rescue", true );
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+}
+
+void CCSPlayer::PostThink()
+{
+ BaseClass::PostThink();
+
+ UpdateAddonBits();
+
+ UpdateRadar();
+
+ if ( !(m_iDisplayHistoryBits & DHF_ROUND_STARTED) && CanPlayerBuy(false) )
+ {
+ HintMessage( "#Hint_press_buy_to_purchase", false );
+ m_iDisplayHistoryBits |= DHF_ROUND_STARTED;
+ }
+ if ( m_flNextMouseoverUpdate < gpGlobals->curtime )
+ {
+ m_flNextMouseoverUpdate = gpGlobals->curtime + 0.2f;
+ if ( m_bShowHints )
+ {
+ UpdateMouseoverHints();
+ }
+ }
+ if ( GetActiveWeapon() && !(m_iDisplayHistoryBits & DHF_AMMO_EXHAUSTED) )
+ {
+ CBaseCombatWeapon *pWeapon = GetActiveWeapon();
+ if ( !pWeapon->HasAnyAmmo() && !(pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE) )
+ {
+ m_iDisplayHistoryBits |= DHF_AMMO_EXHAUSTED;
+ HintMessage( "#Hint_out_of_ammo", false );
+ }
+ }
+
+ QAngle angles = GetLocalAngles();
+ angles[PITCH] = 0;
+ SetLocalAngles( angles );
+
+ // Store the eye angles pitch so the client can compute its animation state correctly.
+ m_angEyeAngles = EyeAngles();
+
+ m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
+
+ // check if we need to apply a deafness DSP effect.
+ if ((m_applyDeafnessTime != 0.0f) && (m_applyDeafnessTime <= gpGlobals->curtime))
+ {
+ ApplyDeafnessEffect();
+ }
+
+ if ( IsPlayerUnderwater() && GetWaterLevel() < 3 )
+ {
+ StopSound( "Player.AmbientUnderWater" );
+ SetPlayerUnderwater( false );
+ }
+
+ if( IsAlive() && m_cycleLatchTimer.IsElapsed() )
+ {
+ m_cycleLatchTimer.Start( CycleLatchInterval );
+
+ // Cycle is a float from 0 to 1. We don't need to transmit a whole float for that. Compress it in to a small fixed point
+ m_cycleLatch.GetForModify() = 16 * GetCycle();// 4 point fixed
+ }
+}
+
+
+void CCSPlayer::PushawayThink()
+{
+ // Push physics props out of our way.
+ PerformObstaclePushaway( this );
+ SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, CS_PUSHAWAY_THINK_CONTEXT );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether or not we can switch to the given weapon.
+// Input : pWeapon -
+//-----------------------------------------------------------------------------
+bool CCSPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
+{
+ if ( !pWeapon->CanDeploy() )
+ return false;
+
+ if ( GetActiveWeapon() )
+ {
+ if ( !GetActiveWeapon()->CanHolster() )
+ return false;
+ }
+
+ return true;
+}
+
+bool CCSPlayer::ShouldDoLargeFlinch( int nHitGroup, CBaseEntity *pAttacker )
+{
+ if ( FBitSet( GetFlags(), FL_DUCKING ) )
+ return FALSE;
+
+ if ( nHitGroup == HITGROUP_LEFTLEG )
+ return FALSE;
+
+ if ( nHitGroup == HITGROUP_RIGHTLEG )
+ return FALSE;
+
+ CCSPlayer *pPlayer = ToCSPlayer( pAttacker );
+
+ if ( pPlayer == NULL || !pPlayer->IsPlayer() )
+ pPlayer = NULL;
+
+ if ( pPlayer )
+ {
+ CWeaponCSBase *pWeapon = pPlayer->GetActiveCSWeapon();
+
+ if ( pWeapon )
+ {
+ if ( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_RIFLE ||
+ pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SHOTGUN ||
+ pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SNIPER_RIFLE )
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return false;
+}
+
+bool CCSPlayer::IsArmored( int nHitGroup )
+{
+ bool bApplyArmor = false;
+
+ if ( ArmorValue() > 0 )
+ {
+ switch ( nHitGroup )
+ {
+ case HITGROUP_GENERIC:
+ case HITGROUP_CHEST:
+ case HITGROUP_STOMACH:
+ case HITGROUP_LEFTARM:
+ case HITGROUP_RIGHTARM:
+ bApplyArmor = true;
+ break;
+ case HITGROUP_HEAD:
+ if ( m_bHasHelmet )
+ {
+ bApplyArmor = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return bApplyArmor;
+}
+
+void CCSPlayer::Pain( bool bHasArmour )
+{
+ switch (m_LastHitGroup)
+ {
+ case HITGROUP_HEAD:
+ if (m_bHasHelmet) // He's wearing a helmet
+ {
+ EmitSound( "Player.DamageHelmet" );
+ }
+ else // He's not wearing a helmet
+ {
+ EmitSound( "Player.DamageHeadShot" );
+ }
+ break;
+ default:
+ if ( bHasArmour == false )
+ {
+ EmitSound( "Flesh.BulletImpact" );
+ }
+ else
+ {
+ EmitSound( "Player.DamageKevlar" );
+ }
+ break;
+ }
+}
+
+int CCSPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ if ( !pInflictor )
+ return 0;
+
+ if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER )
+ return 0;
+
+ const float flArmorBonus = 0.5f;
+ float flArmorRatio = 0.5f;
+ float flDamage = info.GetDamage();
+
+ bool bFriendlyFire = CSGameRules()->IsFriendlyFireOn();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Added properties for goose chase achievement
+ //=============================================================================
+
+ CSGameRules()->PlayerTookDamage(this, inputInfo);
+
+ //Check "Goose Chase" achievement
+ CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker());
+ if (m_bIsDefusing && m_gooseChaseStep == GC_NONE && pAttacker && pAttacker->GetTeamNumber() != GetTeamNumber() )
+ {
+
+ //Count enemies
+ int livingEnemies = 0;
+ CTeam *pAttackerTeam = GetGlobalTeam( pAttacker->GetTeamNumber() );
+ for ( int iPlayer=0; iPlayer < pAttackerTeam->GetNumPlayers(); iPlayer++ )
+ {
+ CCSPlayer *pPlayer = ToCSPlayer( pAttackerTeam->GetPlayer( iPlayer ) );
+ Assert( pPlayer );
+ if ( !pPlayer )
+ continue;
+
+ Assert( pPlayer->GetTeamNumber() == pAttackerTeam->GetTeamNumber() );
+
+ if ( pPlayer->m_lifeState == LIFE_ALIVE )
+ {
+ livingEnemies++;
+ }
+ }
+
+ //Must be last enemy alive;
+ if (livingEnemies == 1)
+ {
+ m_gooseChaseStep = GC_SHOT_DURING_DEFUSE;
+ m_pGooseChaseDistractingPlayer = pAttacker;
+ }
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ // warn about team attacks
+ if ( bFriendlyFire && pInflictor->GetTeamNumber() == GetTeamNumber() && pInflictor != this && info.GetAttacker() != this )
+ {
+ CCSPlayer *pCSAttacker = ToCSPlayer( pInflictor );
+ if ( !pCSAttacker )
+ pCSAttacker = ToCSPlayer( info.GetAttacker() );
+
+ if ( pCSAttacker )
+ {
+ if ( !(pCSAttacker->m_iDisplayHistoryBits & DHF_FRIEND_INJURED) )
+ {
+ pCSAttacker->HintMessage( "#Hint_try_not_to_injure_teammates", false );
+ pCSAttacker->m_iDisplayHistoryBits |= DHF_FRIEND_INJURED;
+ }
+
+ if ( (pCSAttacker->m_flLastAttackedTeammate + 0.6f) < gpGlobals->curtime )
+ {
+ pCSAttacker->m_flLastAttackedTeammate = gpGlobals->curtime;
+
+ // tell the rest of this player's team
+ Msg( "%s attacked a teammate\n", pCSAttacker->GetPlayerName() );
+ for ( int i=1; i<=gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer && pPlayer->GetTeamNumber() == GetTeamNumber() )
+ {
+ ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_teammate_attack", pCSAttacker->GetPlayerName() );
+ }
+ }
+ }
+ }
+ }
+
+ if ( bFriendlyFire ||
+ pInflictor->GetTeamNumber() != GetTeamNumber() ||
+ pInflictor == this ||
+ info.GetAttacker() == this )
+ {
+ if ( bFriendlyFire && (info.GetDamageType() & DMG_BLAST) == 0 )
+ {
+ if ( pInflictor->GetTeamNumber() == GetTeamNumber() )
+ {
+ flDamage *= 0.35; // bullets hurt teammates less
+ }
+ }
+
+ if ( ShouldDoLargeFlinch( m_LastHitGroup, info.GetAttacker() ) )
+ {
+ if ( GetAbsVelocity().Length() < 300 )
+ {
+ m_flVelocityModifier = 0.65;
+ }
+ }
+ else
+ {
+ m_flVelocityModifier = 0.5;
+ }
+
+//=============================================================================
+// HPE_BEGIN:
+//=============================================================================
+ // [menglish] Store whether or not the knife did this damage as knives do bullet damage,
+ // so we need to specifically check the weapon here
+ bool bKnifeDamage = false;
+ CCSPlayer *pPlayer = ToCSPlayer( info.GetAttacker() );
+
+ if ( pPlayer )
+ {
+
+ // [paquin. forest] if this is blast damage, and we haven't opted out with a cvar,
+ // we need to get the armor ratio out of the inflictor
+
+ if ( (info.GetDamageType() & DMG_BLAST) && !sv_legacy_grenade_damage.GetBool() )
+ {
+
+ // [paquin] if we know this is a grenade, use it's armor ratio, otherwise
+ // use the he grenade armor ratio
+
+ CBaseCSGrenadeProjectile *pGrenade = dynamic_cast< CBaseCSGrenadeProjectile * >( pInflictor );
+ CCSWeaponInfo* pWeaponInfo;
+
+ if ( pGrenade && pGrenade->m_pWeaponInfo )
+ {
+ pWeaponInfo = pGrenade->m_pWeaponInfo;
+ }
+ else
+ {
+ pWeaponInfo = GetWeaponInfo( WEAPON_HEGRENADE );
+ }
+
+ if ( pWeaponInfo )
+ {
+ flArmorRatio *= pWeaponInfo->m_flArmorRatio;
+ }
+ }
+ else
+ {
+ CWeaponCSBase *pWeapon = pPlayer->GetActiveCSWeapon();
+
+ if ( pWeapon )
+ {
+ flArmorRatio *= pWeapon->GetCSWpnData().m_flArmorRatio;
+ //Knives do bullet damage, so we need to specifically check the weapon here
+ bKnifeDamage = pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_KNIFE;
+
+ if ( info.GetDamageType() & DMG_BULLET && !bKnifeDamage && pPlayer->GetTeam() != GetTeam() )
+ {
+ CCS_GameStats.Event_ShotHit( pPlayer, info );
+ }
+ }
+ }
+ }
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+ // keep track of amount of damage last sustained
+ m_lastDamageAmount = flDamage;
+
+ // Deal with Armour
+ if ( ArmorValue() && !( info.GetDamageType() & (DMG_FALL | DMG_DROWN)) && IsArmored( m_LastHitGroup ) )
+ {
+ float fDamageToHealth = flDamage * flArmorRatio;
+ float fDamageToArmor = (flDamage - fDamageToHealth) * flArmorBonus;
+
+ int armorValue = ArmorValue();
+
+ // Does this use more armor than we have?
+ if (fDamageToArmor > armorValue )
+ {
+ fDamageToHealth = flDamage - armorValue / flArmorBonus;
+ fDamageToArmor = armorValue;
+ armorValue = 0;
+ }
+ else
+ {
+ if ( fDamageToArmor < 0 )
+ fDamageToArmor = 1;
+
+ armorValue -= fDamageToArmor;
+ }
+ m_lastDamageArmor = (int)fDamageToArmor;
+ SetArmorValue(armorValue);
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Handle headshot-surviving achievement
+ //=============================================================================
+ if (m_LastHitGroup == HITGROUP_HEAD && flDamage > m_iHealth && fDamageToHealth < m_iHealth)
+ {
+ m_bSurvivedHeadshotDueToHelmet = true;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ flDamage = fDamageToHealth;
+
+ info.SetDamage( flDamage );
+
+ if ( ArmorValue() <= 0.0)
+ m_bHasHelmet = false;
+
+ if( !(info.GetDamageType() & DMG_FALL) )
+ Pain( true /*has armor*/ );
+ }
+ else
+ {
+ m_lastDamageArmor = 0;
+ if( !(info.GetDamageType() & DMG_FALL) )
+ Pain( false /*no armor*/ );
+ }
+
+ // round damage to integer
+ m_lastDamageHealth = (int)flDamage;
+ info.SetDamage( m_lastDamageHealth );
+
+ if ( info.GetDamage() <= 0 )
+ return 0;
+
+ CSingleUserRecipientFilter user( this );
+ user.MakeReliable();
+ UserMessageBegin( user, "Damage" );
+ WRITE_BYTE( (int)info.GetDamage() );
+ WRITE_VEC3COORD( info.GetInflictor()->WorldSpaceCenter() );
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Send the info about where the player was hit
+//=============================================================================
+ if ( !( info.GetDamageType() & DMG_BULLET ) || bKnifeDamage )
+ {
+ WRITE_LONG( -1 );
+ }
+ else
+ {
+ WRITE_LONG( m_LastHitBox );
+ }
+ WRITE_VEC3COORD( m_vLastHitLocationObjectSpace );
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+ MessageEnd();
+
+ // Do special explosion damage effect
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ OnDamagedByExplosion( info );
+ }
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Achievement award for kill stealing i.e. killing an enemy who was very damaged from other players
+// [Forrest] Moved this check before RecordDamageTaken so that the damage currently being dealt by this player
+// won't disqualify them from getting the achievement.
+//=============================================================================
+
+ if(m_iHealth - info.GetDamage() <= 0 && m_iHealth <= AchievementConsts::KillLowDamage_MaxHealthLeft)
+ {
+ bool onlyDamage = true;
+ CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker());
+ if(pAttacker && pAttacker->GetTeamNumber() != GetTeamNumber())
+ {
+ //Verify that the killer has not done damage to this player beforehand
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if( Q_strncmp( pAttacker->GetPlayerName(), m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 )
+ {
+ onlyDamage = false;
+ break;
+ }
+ }
+ if(onlyDamage)
+ {
+ pAttacker->AwardAchievement(CSKillLowDamage);
+ }
+ }
+ }
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+#if REPORT_PLAYER_DAMAGE
+ // damage output spew
+ char dmgtype[64];
+ CTakeDamageInfo::DebugGetDamageTypeString( info.GetDamageType(), dmgtype, sizeof(dmgtype) );
+
+ if ( info.GetDamageType() & DMG_HEADSHOT )
+ Q_strncat(dmgtype, "HEADSHOT", sizeof(dmgtype));
+
+ char outputString[256];
+ Q_snprintf( outputString, sizeof(outputString), "%f: Player %s incoming %f damage from %s, type %s; applied %d health and %d armor\n",
+ gpGlobals->curtime, GetPlayerName(),
+ inputInfo.GetDamage(), info.GetInflictor()->GetDebugName(), dmgtype,
+ m_lastDamageHealth, m_lastDamageArmor);
+
+ Msg(outputString);
+#endif
+
+
+ if ( pPlayer )
+ {
+ // Record for the shooter
+ pPlayer->RecordDamageGiven( GetPlayerName(), info.GetDamage() );
+
+ // And for the victim
+ RecordDamageTaken( pPlayer->GetPlayerName(), info.GetDamage() );
+ }
+ else
+ {
+ RecordDamageTaken( "world", info.GetDamage() );
+ }
+
+ m_vecTotalBulletForce += info.GetDamageForce();
+
+ gamestats->Event_PlayerDamage( this, info );
+
+ return CBaseCombatCharacter::OnTakeDamage( info );
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+//MIKETODO: this probably should let the shield model catch the trace attacks.
+bool CCSPlayer::IsHittingShield( const Vector &vecDirection, trace_t *ptr )
+{
+ if ( HasShield() == false )
+ return false;
+
+ if ( IsShieldDrawn() == false )
+ return false;
+
+ float flDot;
+ Vector vForward;
+ Vector2D vec2LOS = vecDirection.AsVector2D();
+ AngleVectors( GetLocalAngles(), &vForward );
+
+ Vector2DNormalize( vForward.AsVector2D() );
+ Vector2DNormalize( vec2LOS );
+
+ flDot = DotProduct2D ( vec2LOS , vForward.AsVector2D() );
+
+ if ( flDot < -0.87f )
+ return true;
+
+ return false;
+}
+
+
+void CCSPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ bool bShouldBleed = true;
+ bool bShouldSpark = false;
+ bool bHitShield = IsHittingShield( vecDir, ptr );
+
+ CBasePlayer *pAttacker = (CBasePlayer*)ToBasePlayer( info.GetAttacker() );
+
+ // show blood for firendly fire only if FF is on
+ if ( pAttacker && ( GetTeamNumber() == pAttacker->GetTeamNumber() ) )
+ bShouldBleed = CSGameRules()->IsFriendlyFireOn();
+
+ if ( m_takedamage != DAMAGE_YES )
+ return;
+
+ m_LastHitGroup = ptr->hitgroup;
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Used when calculating the position this player was hit at in the bone space
+//=============================================================================
+ m_LastHitBox = ptr->hitbox;
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+ m_nForceBone = ptr->physicsbone; //Save this bone for ragdoll
+
+ float flDamage = info.GetDamage();
+
+ bool bHeadShot = false;
+
+ if ( bHitShield )
+ {
+ flDamage = 0;
+ bShouldBleed = false;
+ bShouldSpark = true;
+ }
+ else if( info.GetDamageType() & DMG_BLAST )
+ {
+ if ( ArmorValue() > 0 )
+ bShouldBleed = false;
+
+ if ( bShouldBleed == true )
+ {
+ // punch view if we have no armor
+ QAngle punchAngle = GetPunchAngle();
+ punchAngle.x = flDamage * -0.1;
+
+ if ( punchAngle.x < -4 )
+ punchAngle.x = -4;
+
+ SetPunchAngle( punchAngle );
+ }
+ }
+ else
+ {
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Calculate the position this player was hit at in the bone space
+//=============================================================================
+
+ matrix3x4_t boneTransformToWorld, boneTransformToObject;
+ GetBoneTransform(GetHitboxBone(ptr->hitbox), boneTransformToWorld);
+ MatrixInvert(boneTransformToWorld, boneTransformToObject);
+ VectorTransform(ptr->endpos, boneTransformToObject, m_vLastHitLocationObjectSpace);
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+ switch ( ptr->hitgroup )
+ {
+ case HITGROUP_GENERIC:
+ break;
+
+ case HITGROUP_HEAD:
+
+ if ( m_bHasHelmet )
+ {
+// bShouldBleed = false;
+ bShouldSpark = true;
+ }
+
+ flDamage *= 4;
+
+ if ( !m_bHasHelmet )
+ {
+ QAngle punchAngle = GetPunchAngle();
+ punchAngle.x = flDamage * -0.5;
+
+ if ( punchAngle.x < -12 )
+ punchAngle.x = -12;
+
+ punchAngle.z = flDamage * random->RandomFloat(-1,1);
+
+ if ( punchAngle.z < -9 )
+ punchAngle.z = -9;
+
+ else if ( punchAngle.z > 9 )
+ punchAngle.z = 9;
+
+ SetPunchAngle( punchAngle );
+ }
+
+ bHeadShot = true;
+
+ break;
+
+ case HITGROUP_CHEST:
+
+ flDamage *= 1.0;
+
+ if ( ArmorValue() <= 0 )
+ {
+ QAngle punchAngle = GetPunchAngle();
+ punchAngle.x = flDamage * -0.1;
+
+ if ( punchAngle.x < -4 )
+ punchAngle.x = -4;
+
+ SetPunchAngle( punchAngle );
+ }
+ break;
+
+ case HITGROUP_STOMACH:
+
+ flDamage *= 1.25;
+
+ if ( ArmorValue() <= 0 )
+ {
+ QAngle punchAngle = GetPunchAngle();
+ punchAngle.x = flDamage * -0.1;
+
+ if ( punchAngle.x < -4 )
+ punchAngle.x = -4;
+
+ SetPunchAngle( punchAngle );
+ }
+
+ break;
+
+ case HITGROUP_LEFTARM:
+ case HITGROUP_RIGHTARM:
+ flDamage *= 1.0;
+ break;
+
+ case HITGROUP_LEFTLEG:
+ case HITGROUP_RIGHTLEG:
+ flDamage *= 0.75;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ // Since this code only runs on the server, make sure it shows the tempents it creates.
+ CDisablePredictionFiltering disabler;
+
+ if ( bShouldBleed )
+ {
+ // This does smaller splotches on the guy and splats blood on the world.
+ TraceBleed( flDamage, vecDir, ptr, info.GetDamageType() );
+
+ CEffectData data;
+ data.m_vOrigin = ptr->endpos;
+ data.m_vNormal = vecDir * -1;
+ data.m_nEntIndex = ptr->m_pEnt ? ptr->m_pEnt->entindex() : 0;
+ data.m_flMagnitude = flDamage;
+
+ // reduce blood effect if target has armor
+ if ( ArmorValue() > 0 )
+ data.m_flMagnitude *= 0.5f;
+
+ // reduce blood effect if target is hit in the helmet
+ if ( ptr->hitgroup == HITGROUP_HEAD && bShouldSpark )
+ data.m_flMagnitude *= 0.5;
+
+ DispatchEffect( "csblood", data );
+ }
+ if ( ( ptr->hitgroup == HITGROUP_HEAD || bHitShield ) && bShouldSpark ) // they hit a helmet
+ {
+ // show metal spark effect
+ g_pEffects->Sparks( ptr->endpos, 1, 1, &ptr->plane.normal );
+ }
+
+ if ( !bHitShield )
+ {
+ CTakeDamageInfo subInfo = info;
+
+ subInfo.SetDamage( flDamage );
+
+ if( bHeadShot )
+ subInfo.AddDamageType( DMG_HEADSHOT );
+
+ AddMultiDamage( subInfo, this );
+ }
+}
+
+
+void CCSPlayer::Reset()
+{
+ ResetFragCount();
+ ResetDeathCount();
+ m_iAccount = 0;
+ AddAccount( -16000, false );
+
+ //remove any weapons they bought before the round started
+ RemoveAllItems( true );
+
+ //RemoveShield();
+
+ AddAccount( CSGameRules()->GetStartMoney(), true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Displays a hint message to the player
+// Input : *pMessage -
+// bDisplayIfDead -
+// bOverrideClientSettings -
+//-----------------------------------------------------------------------------
+void CCSPlayer::HintMessage( const char *pMessage, bool bDisplayIfDead, bool bOverrideClientSettings )
+{
+ if ( ( !bDisplayIfDead && !IsAlive() ) || !IsNetClient() || !m_pHintMessageQueue )
+ return;
+
+ if ( bOverrideClientSettings || m_bShowHints )
+ m_pHintMessageQueue->AddMessage( pMessage );
+}
+
+void CCSPlayer::AddAccount( int amount, bool bTrackChange, bool bItemBought, const char *pItemName )
+{
+ m_iAccount += amount;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Description of reason for change
+ //=============================================================================
+
+ if(amount > 0)
+ {
+ CCS_GameStats.Event_MoneyEarned( this, amount );
+ }
+ else if( amount < 0 && bItemBought)
+ {
+ CCS_GameStats.Event_MoneySpent( this, ABS(amount), pItemName );
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if ( m_iAccount < 0 )
+ m_iAccount = 0;
+ else if ( m_iAccount > 16000 )
+ m_iAccount = 16000;
+}
+
+void CCSPlayer::MarkAsNotReceivingMoneyNextRound()
+{
+ m_receivesMoneyNextRound = false;
+}
+
+bool CCSPlayer::DoesPlayerGetRoundStartMoney()
+{
+ return m_receivesMoneyNextRound;
+}
+
+CCSPlayer* CCSPlayer::Instance( int iEnt )
+{
+ return dynamic_cast< CCSPlayer* >( CBaseEntity::Instance( INDEXENT( iEnt ) ) );
+}
+
+
+void CCSPlayer::DropC4()
+{
+}
+
+
+bool CCSPlayer::HasDefuser()
+{
+ return m_bHasDefuser;
+}
+
+void CCSPlayer::RemoveDefuser()
+{
+ m_bHasDefuser = false;
+}
+
+void CCSPlayer::GiveDefuser(bool bPickedUp /* = false */)
+{
+ if ( !m_bHasDefuser )
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
+ if( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "item", "defuser" );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ m_bHasDefuser = true;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ m_bPickedUpDefuser = bPickedUp;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+}
+
+// player blinded by a flashbang
+void CCSPlayer::Blind( float holdTime, float fadeTime, float startingAlpha )
+{
+ // Don't flash a spectator.
+ color32 clr = {255, 255, 255, 255};
+
+ clr.a = startingAlpha;
+
+ // estimate when we can see again
+ float oldBlindUntilTime = m_blindUntilTime;
+ float oldBlindStartTime = m_blindStartTime;
+ m_blindUntilTime = MAX( m_blindUntilTime, gpGlobals->curtime + holdTime + 0.5f * fadeTime );
+ m_blindStartTime = gpGlobals->curtime;
+
+ // Spectators get a lessened flash.
+ if ( (GetObserverMode() != OBS_MODE_NONE) && (GetObserverMode() != OBS_MODE_IN_EYE) )
+ {
+ if ( !mp_fadetoblack.GetBool() )
+ {
+ clr.a = 150;
+
+ fadeTime = MIN(fadeTime, 0.5f); // make sure the spectator flashbang time is 1/2 second or less.
+ holdTime = MIN(holdTime, fadeTime * 0.5f); // adjust the hold time to match the fade time.
+ UTIL_ScreenFade( this, clr, fadeTime, holdTime, FFADE_IN );
+ }
+ }
+ else
+ {
+ fadeTime /= 1.4;
+
+ if ( gpGlobals->curtime > oldBlindUntilTime )
+ {
+ // The previous flashbang is wearing off, or completely gone
+ m_flFlashDuration = fadeTime;
+ m_flFlashMaxAlpha = startingAlpha;
+ }
+ else
+ {
+ // The previous flashbang is still going strong - only extend the duration
+ float remainingDuration = oldBlindStartTime + m_flFlashDuration - gpGlobals->curtime;
+
+ m_flFlashDuration = MAX( remainingDuration, fadeTime );
+ m_flFlashMaxAlpha = MAX( m_flFlashMaxAlpha, startingAlpha );
+ }
+
+ // allow bots to react
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_blind" );
+ if ( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+void CCSPlayer::Deafen( float flDistance )
+{
+ // Spectators don't get deafened
+ if ( (GetObserverMode() == OBS_MODE_NONE) || (GetObserverMode() == OBS_MODE_IN_EYE) )
+ {
+ // dsp presets are defined in hl2/scripts/dsp_presets.txt
+
+ int effect;
+
+ if( flDistance < 600 )
+ {
+ effect = 134;
+ }
+ else if( flDistance < 800 )
+ {
+ effect = 135;
+ }
+ else if( flDistance < 1000 )
+ {
+ effect = 136;
+ }
+ else
+ {
+ // too far for us to get an effect
+ return;
+ }
+
+ CSingleUserRecipientFilter user( this );
+ enginesound->SetPlayerDSP( user, effect, false );
+
+ //TODO: bots can't hear sound for a while?
+ }
+}
+
+void CCSPlayer::GiveShield( void )
+{
+#ifdef CS_SHIELD_ENABLED
+ m_bHasShield = true;
+ m_bShieldDrawn = false;
+
+ if ( HasSecondaryWeapon() )
+ {
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ pWeapon->SetModel( pWeapon->GetViewModel() );
+ pWeapon->Deploy();
+ }
+
+ CBaseViewModel *pVM = GetViewModel( 1 );
+
+ if ( pVM )
+ {
+ ShowViewModel( true );
+ pVM->RemoveEffects( EF_NODRAW );
+ pVM->SetWeaponModel( SHIELD_VIEW_MODEL, GetActiveWeapon() );
+ pVM->SendViewModelMatchingSequence( 1 );
+ }
+#endif
+}
+
+void CCSPlayer::RemoveShield( void )
+{
+#ifdef CS_SHIELD_ENABLED
+ m_bHasShield = false;
+
+ CBaseViewModel *pVM = GetViewModel( 1 );
+
+ if ( pVM )
+ {
+ pVM->AddEffects( EF_NODRAW );
+ }
+#endif
+}
+
+void CCSPlayer::RemoveAllItems( bool removeSuit )
+{
+ if( HasDefuser() )
+ {
+ RemoveDefuser();
+ }
+
+ if ( HasShield() )
+ {
+ RemoveShield();
+ }
+
+ m_bHasNightVision = false;
+ m_bNightVisionOn = false;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ m_bPickedUpDefuser = false;
+ m_bDefusedWithPickedUpKit = false;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if ( removeSuit )
+ {
+ m_bHasHelmet = false;
+ SetArmorValue( 0 );
+ }
+
+ BaseClass::RemoveAllItems( removeSuit );
+}
+
+void CCSPlayer::ObserverRoundRespawn()
+{
+ ClearFlashbangScreenFade();
+
+ // did we change our name last round?
+ if ( m_szNewName[0] != 0 )
+ {
+ // ... and force the name change now. After this happens, the gamerules will get
+ // a ClientSettingsChanged callback from the above ClientCommand, but the name
+ // matches what we're setting here, so it will do nothing.
+ ChangeName( m_szNewName );
+ m_szNewName[0] = 0;
+ }
+}
+
+void CCSPlayer::RoundRespawn()
+{
+ //MIKETODO: menus
+ //if ( m_iMenu != Menu_ChooseAppearance )
+ {
+ // Put them back into the game.
+ StopObserverMode();
+ State_Transition( STATE_ACTIVE );
+ respawn( this, false );
+ m_nButtons = 0;
+ SetNextThink( TICK_NEVER_THINK );
+ }
+
+ m_receivesMoneyNextRound = true; // reset this variable so they can receive their cash next round.
+
+ //If they didn't die, this will print out their damage info
+ OutputDamageGiven();
+ OutputDamageTaken();
+ ResetDamageCounters();
+}
+
+void CCSPlayer::CheckTKPunishment( void )
+{
+ // teamkill punishment..
+ if ( (m_bJustKilledTeammate == true) && mp_tkpunish.GetInt() )
+ {
+ m_bJustKilledTeammate = false;
+ m_bPunishedForTK = true;
+ CommitSuicide();
+ }
+}
+
+CWeaponCSBase* CCSPlayer::GetActiveCSWeapon() const
+{
+ return dynamic_cast< CWeaponCSBase* >( GetActiveWeapon() );
+}
+
+void CCSPlayer::PreThink()
+{
+ BaseClass::PreThink();
+ if ( m_bAutoReload )
+ {
+ m_bAutoReload = false;
+ m_nButtons |= IN_RELOAD;
+ }
+
+ if ( m_afButtonLast != m_nButtons )
+ m_flLastMovement = gpGlobals->curtime;
+
+ if ( g_fGameOver )
+ return;
+
+ State_PreThink();
+
+ if ( m_pHintMessageQueue )
+ m_pHintMessageQueue->Update();
+
+ //Reset bullet force accumulator, only lasts one frame
+ m_vecTotalBulletForce = vec3_origin;
+
+ if ( mp_autokick.GetBool() && !IsBot() && !IsHLTV() && !IsAutoKickDisabled() )
+ {
+ if ( m_flLastMovement + CSGameRules()->GetRoundLength()*2 < gpGlobals->curtime )
+ {
+ UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_idle_kick", GetPlayerName() );
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", GetUserID() ) );
+ m_flLastMovement = gpGlobals->curtime;
+ }
+ }
+#ifndef _XBOX
+ // CS would like their players to continue to update their LastArea since it is displayed in the hud voice chat UI
+ // But we won't do the population tracking while dead.
+ CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin(), 1000 );
+ if (area && area != m_lastNavArea)
+ {
+ m_lastNavArea = area;
+ if ( area->GetPlace() != UNDEFINED_PLACE )
+ {
+ const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() );
+ if ( placeName && *placeName )
+ {
+ Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH );
+ }
+ }
+ }
+#endif
+}
+
+void CCSPlayer::MoveToNextIntroCamera()
+{
+ m_pIntroCamera = gEntList.FindEntityByClassname( m_pIntroCamera, "point_viewcontrol" );
+
+ // if m_pIntroCamera is NULL we just were at end of list, start searching from start again
+ if(!m_pIntroCamera)
+ m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "point_viewcontrol");
+
+ // find the target
+ CBaseEntity *Target = NULL;
+
+ if( m_pIntroCamera )
+ {
+ Target = gEntList.FindEntityByName( NULL, STRING(m_pIntroCamera->m_target) );
+ }
+
+ // if we still couldn't find a camera, goto T spawn
+ if(!m_pIntroCamera)
+ m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "info_player_terrorist");
+
+ SetViewOffset( vec3_origin ); // no view offset
+ UTIL_SetSize( this, vec3_origin, vec3_origin ); // no bbox
+
+ if( !Target ) //if there are no cameras(or the camera has no target, find a spawn point and black out the screen
+ {
+ if ( m_pIntroCamera.IsValid() )
+ SetAbsOrigin( m_pIntroCamera->GetAbsOrigin() + VEC_VIEW );
+
+ SetAbsAngles( QAngle( 0, 0, 0 ) );
+
+ m_pIntroCamera = NULL; // never update again
+ return;
+ }
+
+
+ Vector vCamera = Target->GetAbsOrigin() - m_pIntroCamera->GetAbsOrigin();
+ Vector vIntroCamera = m_pIntroCamera->GetAbsOrigin();
+
+ VectorNormalize( vCamera );
+
+ QAngle CamAngles;
+ VectorAngles( vCamera, CamAngles );
+
+ SetAbsOrigin( vIntroCamera );
+ SetAbsAngles( CamAngles );
+ SnapEyeAngles( CamAngles );
+ m_fIntroCamTime = gpGlobals->curtime + 6;
+}
+
+class NotVIP
+{
+public:
+ bool operator()( CBasePlayer *player )
+ {
+ CCSPlayer *csPlayer = static_cast< CCSPlayer * >(player);
+ csPlayer->MakeVIP( false );
+
+ return true;
+ }
+};
+
+// Expose the VIP selection to plugins, since we don't have an official VIP mode. This
+// allows plugins to access the (limited) VIP functionality already present (scoreboard
+// identification and radar color).
+CON_COMMAND( cs_make_vip, "Marks a player as the VIP" )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ if ( args.ArgC() != 2 )
+ {
+ return;
+ }
+
+ CCSPlayer *player = static_cast< CCSPlayer * >(UTIL_PlayerByIndex( atoi( args[1] ) ));
+ if ( !player )
+ {
+ // Invalid value clears out VIP
+ NotVIP notVIP;
+ ForEachPlayer( notVIP );
+ return;
+ }
+
+ player->MakeVIP( true );
+}
+
+void CCSPlayer::MakeVIP( bool isVIP )
+{
+ if ( isVIP )
+ {
+ NotVIP notVIP;
+ ForEachPlayer( notVIP );
+ }
+ m_isVIP = isVIP;
+}
+
+bool CCSPlayer::IsVIP() const
+{
+ return m_isVIP;
+}
+
+void CCSPlayer::DropShield( void )
+{
+#ifdef CS_SHIELD_ENABLED
+ //Drop an item_defuser
+ Vector vForward, vRight;
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL );
+
+ RemoveShield();
+
+ CBaseAnimating *pShield = (CBaseAnimating *)CBaseEntity::Create( "item_shield", WorldSpaceCenter(), GetLocalAngles() );
+ pShield->ApplyAbsVelocityImpulse( vForward * 200 + vRight * random->RandomFloat( -50, 50 ) );
+
+ CBaseCombatWeapon *pActive = GetActiveWeapon();
+
+ if ( pActive )
+ {
+ pActive->Deploy();
+ }
+#endif
+}
+
+void CCSPlayer::SetShieldDrawnState( bool bState )
+{
+#ifdef CS_SHIELD_ENABLED
+ m_bShieldDrawn = bState;
+#endif
+}
+
+bool CCSPlayer::CSWeaponDrop( CBaseCombatWeapon *pWeapon, bool bDropShield, bool bThrowForward )
+{
+ bool bSuccess = false;
+
+ if ( HasShield() && bDropShield == true )
+ {
+ DropShield();
+ return true;
+ }
+
+ if ( pWeapon )
+ {
+ Vector vForward;
+
+ AngleVectors( EyeAngles(), &vForward, NULL, NULL );
+ //GetVectors( &vForward, NULL, NULL );
+ Vector vTossPos = WorldSpaceCenter();
+
+ if( bThrowForward )
+ vTossPos = vTossPos + vForward * 64;
+
+ Weapon_Drop( pWeapon, &vTossPos, NULL );
+
+ pWeapon->SetSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_TRIGGER | FSOLID_USE_TRIGGER_BOUNDS );
+ pWeapon->SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE );
+
+ CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon );
+
+ if( pCSWeapon )
+ {
+ pCSWeapon->SetWeaponModelIndex( pCSWeapon->GetCSWpnData().szWorldModel );
+
+ //Find out the index of the ammo type
+ int iAmmoIndex = pCSWeapon->GetPrimaryAmmoType();
+
+ //If it has an ammo type, find out how much the player has
+ if( iAmmoIndex != -1 )
+ {
+ // Check to make sure we don't have other weapons using this ammo type
+ bool bAmmoTypeInUse = false;
+ if ( IsAlive() && GetHealth() > 0 )
+ {
+ for ( int i=0; i<MAX_WEAPONS; ++i )
+ {
+ CBaseCombatWeapon *pOtherWeapon = GetWeapon(i);
+ if ( pOtherWeapon && pOtherWeapon != pWeapon && pOtherWeapon->GetPrimaryAmmoType() == iAmmoIndex )
+ {
+ bAmmoTypeInUse = true;
+ break;
+ }
+ }
+ }
+
+ if ( !bAmmoTypeInUse )
+ {
+ int iAmmoToDrop = GetAmmoCount( iAmmoIndex );
+
+ //Add this much to the dropped weapon
+ pCSWeapon->SetExtraAmmoCount( iAmmoToDrop );
+
+ //Remove all ammo of this type from the player
+ SetAmmoCount( 0, iAmmoIndex );
+ }
+ }
+ }
+
+ //=========================================
+ // Teleport the weapon to the player's hand
+ //=========================================
+ int iBIndex = -1;
+ int iWeaponBoneIndex = -1;
+
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr *hdr = pWeapon->GetModelPtr();
+ // If I have a hand, set the weapon position to my hand bone position.
+ if ( hdr && hdr->numbones() > 0 )
+ {
+ // Assume bone zero is the root
+ for ( iWeaponBoneIndex = 0; iWeaponBoneIndex < hdr->numbones(); ++iWeaponBoneIndex )
+ {
+ iBIndex = LookupBone( hdr->pBone( iWeaponBoneIndex )->pszName() );
+ // Found one!
+ if ( iBIndex != -1 )
+ {
+ break;
+ }
+ }
+
+ if ( iWeaponBoneIndex == hdr->numbones() )
+ return true;
+
+ if ( iBIndex == -1 )
+ {
+ iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
+ }
+ }
+ else
+ {
+ iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
+ }
+
+ if ( iBIndex != -1)
+ {
+ Vector origin;
+ QAngle angles;
+ matrix3x4_t transform;
+
+ // Get the transform for the weapon bonetoworldspace in the NPC
+ GetBoneTransform( iBIndex, transform );
+
+ // find offset of root bone from origin in local space
+ // Make sure we're detached from hierarchy before doing this!!!
+ pWeapon->StopFollowingEntity();
+ pWeapon->SetAbsOrigin( Vector( 0, 0, 0 ) );
+ pWeapon->SetAbsAngles( QAngle( 0, 0, 0 ) );
+ pWeapon->InvalidateBoneCache();
+ matrix3x4_t rootLocal;
+ pWeapon->GetBoneTransform( iWeaponBoneIndex, rootLocal );
+
+ // invert it
+ matrix3x4_t rootInvLocal;
+ MatrixInvert( rootLocal, rootInvLocal );
+
+ matrix3x4_t weaponMatrix;
+ ConcatTransforms( transform, rootInvLocal, weaponMatrix );
+ MatrixAngles( weaponMatrix, angles, origin );
+
+ pWeapon->Teleport( &origin, &angles, NULL );
+
+ //Have to teleport the physics object as well
+
+ IPhysicsObject *pWeaponPhys = pWeapon->VPhysicsGetObject();
+
+ if( pWeaponPhys )
+ {
+ Vector vPos;
+ QAngle vAngles;
+ pWeaponPhys->GetPosition( &vPos, &vAngles );
+ pWeaponPhys->SetPosition( vPos, angles, true );
+
+ AngularImpulse angImp(0,0,0);
+ Vector vecAdd = GetAbsVelocity();
+ pWeaponPhys->AddVelocity( &vecAdd, &angImp );
+ }
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+
+bool CCSPlayer::DropRifle( bool fromDeath )
+{
+ bool bSuccess = false;
+
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+ if ( pWeapon )
+ {
+ bSuccess = CSWeaponDrop( pWeapon, false );
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Add the dropped weapon to the dropped equipment list
+ //=============================================================================
+ if( fromDeath && bSuccess )
+ {
+ m_hDroppedEquipment[DROPPED_WEAPON] = static_cast<CBaseEntity *>(pWeapon);
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ return bSuccess;
+}
+
+
+bool CCSPlayer::DropPistol( bool fromDeath )
+{
+ bool bSuccess = false;
+
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ if ( pWeapon )
+ {
+ bSuccess = CSWeaponDrop( pWeapon, false );
+ m_bUsingDefaultPistol = false;
+ }
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Add the dropped weapon to the dropped equipment list
+ //=============================================================================
+ if( fromDeath && bSuccess )
+ {
+ m_hDroppedEquipment[DROPPED_WEAPON] = static_cast<CBaseEntity *>(pWeapon);
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ return bSuccess;
+}
+
+bool CCSPlayer::HasPrimaryWeapon( void )
+{
+ bool bSuccess = false;
+
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+
+ if ( pWeapon )
+ {
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+
+bool CCSPlayer::HasSecondaryWeapon( void )
+{
+ bool bSuccess = false;
+
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ if ( pWeapon )
+ {
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+bool CCSPlayer::IsInBuyZone()
+{
+ return m_bInBuyZone && !IsVIP();
+}
+
+bool CCSPlayer::CanPlayerBuy( bool display )
+{
+ // is the player in a buy zone?
+ if ( !IsInBuyZone() )
+ {
+ return false;
+ }
+
+ CCSGameRules* mp = CSGameRules();
+
+ // is the player alive?
+ if ( m_lifeState != LIFE_ALIVE )
+ {
+ return false;
+ }
+
+ int buyTime = (int)(mp_buytime.GetFloat() * 60);
+
+ if ( mp->IsBuyTimeElapsed() )
+ {
+ if ( display == true )
+ {
+ char strBuyTime[16];
+ Q_snprintf( strBuyTime, sizeof( strBuyTime ), "%d", buyTime );
+ ClientPrint( this, HUD_PRINTCENTER, "#Cant_buy", strBuyTime );
+ }
+
+ return false;
+ }
+
+ if ( m_bIsVIP )
+ {
+ if ( display == true )
+ ClientPrint( this, HUD_PRINTCENTER, "#VIP_cant_buy" );
+
+ return false;
+ }
+
+ if ( mp->m_bCTCantBuy && ( GetTeamNumber() == TEAM_CT ) )
+ {
+ if ( display == true )
+ ClientPrint( this, HUD_PRINTCENTER, "#CT_cant_buy" );
+
+ return false;
+ }
+
+ if ( mp->m_bTCantBuy && ( GetTeamNumber() == TEAM_TERRORIST ) )
+ {
+ if ( display == true )
+ ClientPrint( this, HUD_PRINTCENTER, "#Terrorist_cant_buy" );
+
+ return false;
+ }
+
+ return true;
+}
+
+
+BuyResult_e CCSPlayer::AttemptToBuyVest( void )
+{
+ int iKevlarPrice = KEVLAR_PRICE;
+
+ if ( CSGameRules()->IsBlackMarket() )
+ {
+ iKevlarPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_KEVLAR );
+ }
+
+ if ( ArmorValue() >= 100 )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar" );
+ return BUY_ALREADY_HAVE;
+ }
+ else if ( m_iAccount < iKevlarPrice )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ return BUY_CANT_AFFORD;
+ }
+ else
+ {
+ if ( m_bHasHelmet )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Helmet_Bought_Kevlar" );
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
+ if( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "item", "vest" );
+ gameeventmanager->FireEvent( event );
+ }
+
+ GiveNamedItem( "item_kevlar" );
+ AddAccount( -iKevlarPrice, true, true, "item_kevlar" );
+ BlackMarketAddWeapon( "item_kevlar", this );
+ return BUY_BOUGHT;
+ }
+}
+
+
+BuyResult_e CCSPlayer::AttemptToBuyAssaultSuit( void )
+{
+ // WARNING: This price logic also exists in C_CSPlayer::GetCurrentAssaultSuitPrice
+ // and must be kept in sync if changes are made.
+
+ int fullArmor = ArmorValue() >= 100 ? 1 : 0;
+
+ int price = 0, enoughMoney = 0;
+
+ int iHelmetPrice = HELMET_PRICE;
+ int iKevlarPrice = KEVLAR_PRICE;
+ int iAssaultSuitPrice = ASSAULTSUIT_PRICE;
+
+ if ( CSGameRules()->IsBlackMarket() )
+ {
+ iKevlarPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_KEVLAR );
+ iAssaultSuitPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_ASSAULTSUIT );
+
+ iHelmetPrice = iAssaultSuitPrice - iKevlarPrice;
+ }
+
+ if ( fullArmor && m_bHasHelmet )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar_Helmet" );
+ return BUY_ALREADY_HAVE;
+ }
+ else if ( fullArmor && !m_bHasHelmet && m_iAccount >= iHelmetPrice )
+ {
+ enoughMoney = 1;
+ price = iHelmetPrice;
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar_Bought_Helmet" );
+ }
+ else if ( !fullArmor && m_bHasHelmet && m_iAccount >= iKevlarPrice )
+ {
+ enoughMoney = 1;
+ price = iKevlarPrice;
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Helmet_Bought_Kevlar" );
+ }
+ else if ( m_iAccount >= iAssaultSuitPrice )
+ {
+ enoughMoney = 1;
+ price = iAssaultSuitPrice;
+ }
+
+ // process the result
+ if ( !enoughMoney )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ return BUY_CANT_AFFORD;
+ }
+ else
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
+ if( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "item", "vesthelm" );
+ gameeventmanager->FireEvent( event );
+ }
+
+ GiveNamedItem( "item_assaultsuit" );
+ AddAccount( -price, true, true, "item_assaultsuit" );
+ BlackMarketAddWeapon( "item_assaultsuit", this );
+ return BUY_BOUGHT;
+ }
+}
+
+BuyResult_e CCSPlayer::AttemptToBuyShield( void )
+{
+#ifdef CS_SHIELD_ENABLED
+ if ( HasShield() ) // prevent this guy from buying more than 1 Defuse Kit
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" );
+ return BUY_ALREADY_HAVE;
+ }
+ else if ( m_iAccount < SHIELD_PRICE )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ return BUY_CANT_AFFORD;
+ }
+ else
+ {
+ if ( HasSecondaryWeapon() )
+ {
+ CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon );
+
+ if ( pCSWeapon && pCSWeapon->GetCSWpnData().m_bCanUseWithShield == false )
+ return;
+ }
+
+ if ( HasPrimaryWeapon() )
+ DropRifle();
+
+ GiveShield();
+
+ CPASAttenuationFilter filter( this, "Player.PickupWeapon" );
+ EmitSound( filter, entindex(), "Player.PickupWeapon" );
+
+ m_bAnythingBought = true;
+ AddAccount( -SHIELD_PRICE, true, true, "item_shield" );
+ return BUY_BOUGHT;
+ }
+#else
+ ClientPrint( this, HUD_PRINTCENTER, "Tactical shield disabled" );
+ return BUY_NOT_ALLOWED;
+#endif
+}
+
+BuyResult_e CCSPlayer::AttemptToBuyDefuser( void )
+{
+ CCSGameRules *MPRules = CSGameRules();
+
+ if( ( GetTeamNumber() == TEAM_CT ) && MPRules->IsBombDefuseMap() )
+ {
+ if ( HasDefuser() ) // prevent this guy from buying more than 1 Defuse Kit
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" );
+ return BUY_ALREADY_HAVE;
+ }
+ else if ( m_iAccount < DEFUSEKIT_PRICE )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ return BUY_CANT_AFFORD;
+ }
+ else
+ {
+ GiveDefuser();
+
+ CPASAttenuationFilter filter( this, "Player.PickupWeapon" );
+ EmitSound( filter, entindex(), "Player.PickupWeapon" );
+
+ AddAccount( -DEFUSEKIT_PRICE, true, true, "item_defuser" );
+ return BUY_BOUGHT;
+ }
+ }
+
+ return BUY_NOT_ALLOWED;
+}
+
+BuyResult_e CCSPlayer::AttemptToBuyNightVision( void )
+{
+ int iNVGPrice = NVG_PRICE;
+
+ if ( CSGameRules()->IsBlackMarket() )
+ {
+ iNVGPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_NVG );
+ }
+
+ if ( m_bHasNightVision == TRUE )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" );
+ return BUY_ALREADY_HAVE;
+ }
+ else if ( m_iAccount < iNVGPrice )
+ {
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ return BUY_CANT_AFFORD;
+ }
+ else
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
+ if( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "item", "nvgs" );
+ gameeventmanager->FireEvent( event );
+ }
+
+ GiveNamedItem( "item_nvgs" );
+ AddAccount( -iNVGPrice, true, true );
+ BlackMarketAddWeapon( "nightvision", this );
+
+ if ( !(m_iDisplayHistoryBits & DHF_NIGHTVISION) )
+ {
+ HintMessage( "#Hint_use_nightvision", false );
+ m_iDisplayHistoryBits |= DHF_NIGHTVISION;
+ }
+ return BUY_BOUGHT;
+ }
+}
+
+
+// Handles the special "buy" alias commands we're creating to accommodate the buy
+// scripts players use (now that we've rearranged the buy menus and broken the scripts)
+//=============================================================================
+// HPE_BEGIN:
+//[tj] This is essentially a shim so I can easily check the return
+// value without adding new code to all the return points.
+//=============================================================================
+
+BuyResult_e CCSPlayer::HandleCommand_Buy( const char *item )
+{
+ BuyResult_e result = HandleCommand_Buy_Internal(item);
+ if (result == BUY_BOUGHT)
+ {
+ m_bMadePurchseThisRound = true;
+ CCS_GameStats.IncrementStat(this, CSSTAT_ITEMS_PURCHASED, 1);
+ }
+ return result;
+}
+
+BuyResult_e CCSPlayer::HandleCommand_Buy_Internal( const char* wpnName )
+//=============================================================================
+// HPE_END
+//=============================================================================
+{
+ BuyResult_e result = CanPlayerBuy( false ) ? BUY_PLAYER_CANT_BUY : BUY_INVALID_ITEM; // set some defaults
+
+ // translate the new weapon names to the old ones that are actually being used.
+ wpnName = GetTranslatedWeaponAlias(wpnName);
+
+ CCSWeaponInfo *pWeaponInfo = GetWeaponInfo( AliasToWeaponID( wpnName ) );
+ if ( pWeaponInfo == NULL )
+ {
+ if ( Q_stricmp( wpnName, "primammo" ) == 0 )
+ {
+ result = AttemptToBuyAmmo( 0 );
+ }
+ else if ( Q_stricmp( wpnName, "secammo" ) == 0 )
+ {
+ result = AttemptToBuyAmmo( 1 );
+ }
+ else if ( Q_stristr( wpnName, "defuser" ) )
+ {
+ if( CanPlayerBuy( true ) )
+ {
+ result = AttemptToBuyDefuser();
+ }
+ }
+ }
+ else
+ {
+
+ if( !CanPlayerBuy( true ) )
+ {
+ return BUY_PLAYER_CANT_BUY;
+ }
+
+ BuyResult_e equipResult = BUY_INVALID_ITEM;
+
+ if ( Q_stristr( wpnName, "kevlar" ) )
+ {
+ equipResult = AttemptToBuyVest();
+ }
+ else if ( Q_stristr( wpnName, "assaultsuit" ) )
+ {
+ equipResult = AttemptToBuyAssaultSuit();
+ }
+ else if ( Q_stristr( wpnName, "shield" ) )
+ {
+ equipResult = AttemptToBuyShield();
+ }
+ else if ( Q_stristr( wpnName, "nightvision" ) )
+ {
+ equipResult = AttemptToBuyNightVision();
+ }
+
+ if ( equipResult != BUY_INVALID_ITEM )
+ {
+ if ( equipResult == BUY_BOUGHT )
+ {
+ BuildRebuyStruct();
+ }
+ return equipResult; // intentional early return here
+ }
+
+ bool bPurchase = false;
+
+ // MIKETODO: assasination maps have a specific set of weapons that can be used in them.
+ if ( pWeaponInfo->m_iTeam != TEAM_UNASSIGNED && GetTeamNumber() != pWeaponInfo->m_iTeam )
+ {
+ result = BUY_NOT_ALLOWED;
+ if ( pWeaponInfo->m_WrongTeamMsg[0] != 0 )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Alias_Not_Avail", pWeaponInfo->m_WrongTeamMsg );
+ }
+ }
+ else if ( pWeaponInfo->GetWeaponPrice() <= 0 )
+ {
+ // ClientPrint( this, HUD_PRINTCENTER, "#Cant_buy_this_item", pWeaponInfo->m_WrongTeamMsg );
+ }
+ else if( pWeaponInfo->m_WeaponType == WEAPONTYPE_GRENADE )
+ {
+ // make sure the player can afford this item.
+ if ( m_iAccount >= pWeaponInfo->GetWeaponPrice() )
+ {
+ bPurchase = true;
+
+ const char *szWeaponName = NULL;
+ int ammoMax = 0;
+ if ( Q_strstr( pWeaponInfo->szClassName, "flashbang" ) )
+ {
+ szWeaponName = "weapon_flashbang";
+ ammoMax = ammo_flashbang_max.GetInt();
+ }
+ else if ( Q_strstr( pWeaponInfo->szClassName, "hegrenade" ) )
+ {
+ szWeaponName = "weapon_hegrenade";
+ ammoMax = ammo_hegrenade_max.GetInt();
+ }
+ else if ( Q_strstr( pWeaponInfo->szClassName, "smokegrenade" ) )
+ {
+ szWeaponName = "weapon_smokegrenade";
+ ammoMax = ammo_smokegrenade_max.GetInt();
+ }
+
+ if ( szWeaponName != NULL )
+ {
+ CBaseCombatWeapon* pGrenadeWeapon = Weapon_OwnsThisType( szWeaponName );
+ {
+ if ( pGrenadeWeapon != NULL )
+ {
+ int nAmmoType = pGrenadeWeapon->GetPrimaryAmmoType();
+
+ if( nAmmoType != -1 )
+ {
+ if( GetAmmoCount(nAmmoType) >= ammoMax )
+ {
+ result = BUY_ALREADY_HAVE;
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Carry_Anymore" );
+ bPurchase = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else if ( !Weapon_OwnsThisType( pWeaponInfo->szClassName ) ) //don't buy duplicate weapons
+ {
+ // do they have enough money?
+ if ( m_iAccount >= pWeaponInfo->GetWeaponPrice() )
+ {
+ if ( m_lifeState != LIFE_DEAD )
+ {
+ if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
+ {
+ DropPistol();
+ }
+ else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
+ {
+ DropRifle();
+ }
+ }
+
+ bPurchase = true;
+ }
+ else
+ {
+ result = BUY_CANT_AFFORD;
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ }
+ }
+ else
+ {
+ result = BUY_ALREADY_HAVE;
+ }
+
+ if ( HasShield() )
+ {
+ if ( pWeaponInfo->m_bCanUseWithShield == false )
+ {
+ result = BUY_NOT_ALLOWED;
+ bPurchase = false;
+ }
+ }
+
+ if( bPurchase )
+ {
+ result = BUY_BOUGHT;
+
+ if ( bPurchase && pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
+ m_bUsingDefaultPistol = false;
+
+ GiveNamedItem( pWeaponInfo->szClassName );
+ AddAccount( -pWeaponInfo->GetWeaponPrice(), true, true, pWeaponInfo->szClassName );
+ BlackMarketAddWeapon( wpnName, this );
+ }
+ }
+
+ if ( result == BUY_BOUGHT )
+ {
+ BuildRebuyStruct();
+ }
+
+ return result;
+}
+
+
+BuyResult_e CCSPlayer::BuyGunAmmo( CBaseCombatWeapon *pWeapon, bool bBlinkMoney )
+{
+ if ( !CanPlayerBuy( false ) )
+ {
+ return BUY_PLAYER_CANT_BUY;
+ }
+
+ // Ensure that the weapon uses ammo
+ int nAmmo = pWeapon->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ // Can only buy if the player does not already have full ammo
+ if ( GetAmmoCount( nAmmo ) >= GetAmmoDef()->MaxCarry( nAmmo ) )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ // Purchase the ammo if the player has enough money
+ if ( m_iAccount >= GetCSAmmoDef()->GetCost( nAmmo ) )
+ {
+ GiveAmmo( GetCSAmmoDef()->GetBuySize( nAmmo ), nAmmo, true );
+ AddAccount( -GetCSAmmoDef()->GetCost( nAmmo ), true, true, GetCSAmmoDef()->GetAmmoOfIndex( nAmmo )->pName );
+ return BUY_BOUGHT;
+ }
+
+ if ( bBlinkMoney )
+ {
+ // Not enough money.. let the player know
+ if( !m_bIsInAutoBuy && !m_bIsInRebuy )
+ ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" );
+ }
+
+ return BUY_CANT_AFFORD;
+}
+
+
+BuyResult_e CCSPlayer::BuyAmmo( int nSlot, bool bBlinkMoney )
+{
+ if ( !CanPlayerBuy( false ) )
+ {
+ return BUY_PLAYER_CANT_BUY;
+ }
+
+ if ( nSlot < 0 || nSlot > 1 )
+ {
+ return BUY_INVALID_ITEM;
+ }
+
+ // Buy one ammo clip for all weapons in the given slot
+ //
+ // nSlot == 1 : Primary weapons
+ // nSlot == 2 : Secondary weapons
+
+ CBaseCombatWeapon *pSlot = Weapon_GetSlot( nSlot );
+ if ( !pSlot )
+ return BUY_INVALID_ITEM;
+
+ //MIKETODO: shield.
+ //if ( player->HasShield() && player->m_rgpPlayerItems[2] )
+ // pItem = player->m_rgpPlayerItems[2];
+
+ return BuyGunAmmo( pSlot, bBlinkMoney );
+}
+
+
+BuyResult_e CCSPlayer::AttemptToBuyAmmo( int iAmmoType )
+{
+ Assert( iAmmoType == 0 || iAmmoType == 1 );
+
+ BuyResult_e result = BuyAmmo( iAmmoType, true );
+
+ if ( result == BUY_BOUGHT )
+ {
+ while ( BuyAmmo( iAmmoType, false ) == BUY_BOUGHT )
+ {
+ // empty loop - keep buying
+ }
+
+ return BUY_BOUGHT;
+ }
+
+ return result;
+}
+
+BuyResult_e CCSPlayer::AttemptToBuyAmmoSingle( int iAmmoType )
+{
+ Assert( iAmmoType == 0 || iAmmoType == 1 );
+
+ BuyResult_e result = BuyAmmo( iAmmoType, true );
+
+ if ( result == BUY_BOUGHT )
+ {
+ BuildRebuyStruct();
+ }
+
+ return result;
+}
+
+const char *RadioEventName[ RADIO_NUM_EVENTS+1 ] =
+{
+ "RADIO_INVALID",
+
+ "EVENT_START_RADIO_1",
+
+ "EVENT_RADIO_COVER_ME",
+ "EVENT_RADIO_YOU_TAKE_THE_POINT",
+ "EVENT_RADIO_HOLD_THIS_POSITION",
+ "EVENT_RADIO_REGROUP_TEAM",
+ "EVENT_RADIO_FOLLOW_ME",
+ "EVENT_RADIO_TAKING_FIRE",
+
+ "EVENT_START_RADIO_2",
+
+ "EVENT_RADIO_GO_GO_GO",
+ "EVENT_RADIO_TEAM_FALL_BACK",
+ "EVENT_RADIO_STICK_TOGETHER_TEAM",
+ "EVENT_RADIO_GET_IN_POSITION_AND_WAIT",
+ "EVENT_RADIO_STORM_THE_FRONT",
+ "EVENT_RADIO_REPORT_IN_TEAM",
+
+ "EVENT_START_RADIO_3",
+
+ "EVENT_RADIO_AFFIRMATIVE",
+ "EVENT_RADIO_ENEMY_SPOTTED",
+ "EVENT_RADIO_NEED_BACKUP",
+ "EVENT_RADIO_SECTOR_CLEAR",
+ "EVENT_RADIO_IN_POSITION",
+ "EVENT_RADIO_REPORTING_IN",
+ "EVENT_RADIO_GET_OUT_OF_THERE",
+ "EVENT_RADIO_NEGATIVE",
+ "EVENT_RADIO_ENEMY_DOWN",
+
+ "EVENT_RADIO_END",
+
+ NULL // must be NULL-terminated
+};
+
+
+/**
+ * Convert name to RadioType
+ */
+RadioType NameToRadioEvent( const char *name )
+{
+ for( int i=0; RadioEventName[i]; ++i )
+ if (!stricmp( RadioEventName[i], name ))
+ return static_cast<RadioType>( i );
+
+ return RADIO_INVALID;
+}
+
+
+void CCSPlayer::HandleMenu_Radio1( int slot )
+{
+ if( m_iRadioMessages < 0 )
+ return;
+
+ if( m_flRadioTime > gpGlobals->curtime )
+ return;
+
+ m_iRadioMessages--;
+ m_flRadioTime = gpGlobals->curtime + 1.5;
+
+ switch ( slot )
+ {
+ case 1 :
+ Radio( "Radio.CoverMe", "#Cstrike_TitlesTXT_Cover_me" );
+ break;
+
+ case 2 :
+ Radio( "Radio.YouTakeThePoint", "#Cstrike_TitlesTXT_You_take_the_point" );
+ break;
+
+ case 3 :
+ Radio( "Radio.HoldPosition", "#Cstrike_TitlesTXT_Hold_this_position" );
+ break;
+
+ case 4 :
+ Radio( "Radio.Regroup", "#Cstrike_TitlesTXT_Regroup_team" );
+ break;
+
+ case 5 :
+ Radio( "Radio.FollowMe", "#Cstrike_TitlesTXT_Follow_me" );
+ break;
+
+ case 6 :
+ Radio( "Radio.TakingFire", "#Cstrike_TitlesTXT_Taking_fire" );
+ break;
+ }
+
+ // tell bots about radio message
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" );
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("slot", RADIO_START_1 + slot );
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+void CCSPlayer::HandleMenu_Radio2( int slot )
+{
+ if( m_iRadioMessages < 0 )
+ return;
+
+ if( m_flRadioTime > gpGlobals->curtime )
+ return;
+
+ m_iRadioMessages--;
+ m_flRadioTime = gpGlobals->curtime + 1.5;
+
+ switch ( slot )
+ {
+ case 1 :
+ Radio( "Radio.GoGoGo", "#Cstrike_TitlesTXT_Go_go_go" );
+ break;
+
+ case 2 :
+ Radio( "Radio.TeamFallBack", "#Cstrike_TitlesTXT_Team_fall_back" );
+ break;
+
+ case 3 :
+ Radio( "Radio.StickTogether", "#Cstrike_TitlesTXT_Stick_together_team" );
+ break;
+
+ case 4 :
+ Radio( "Radio.GetInPosition", "#Cstrike_TitlesTXT_Get_in_position_and_wait" );
+ break;
+
+ case 5 :
+ Radio( "Radio.StormFront", "#Cstrike_TitlesTXT_Storm_the_front" );
+ break;
+
+ case 6 :
+ Radio( "Radio.ReportInTeam", "#Cstrike_TitlesTXT_Report_in_team" );
+ break;
+ }
+
+ // tell bots about radio message
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" );
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("slot", RADIO_START_2 + slot );
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+void CCSPlayer::HandleMenu_Radio3( int slot )
+{
+ if( m_iRadioMessages < 0 )
+ return;
+
+ if( m_flRadioTime > gpGlobals->curtime )
+ return;
+
+ m_iRadioMessages--;
+ m_flRadioTime = gpGlobals->curtime + 1.5;
+
+ switch ( slot )
+ {
+ case 1 :
+ if ( random->RandomInt( 0,1 ) )
+ Radio( "Radio.Affirmitive", "#Cstrike_TitlesTXT_Affirmative" );
+ else
+ Radio( "Radio.Roger", "#Cstrike_TitlesTXT_Roger_that" );
+
+ break;
+
+ case 2 :
+ Radio( "Radio.EnemySpotted", "#Cstrike_TitlesTXT_Enemy_spotted" );
+ break;
+
+ case 3 :
+ Radio( "Radio.NeedBackup", "#Cstrike_TitlesTXT_Need_backup" );
+ break;
+
+ case 4 :
+ Radio( "Radio.SectorClear", "#Cstrike_TitlesTXT_Sector_clear" );
+ break;
+
+ case 5 :
+ Radio( "Radio.InPosition", "#Cstrike_TitlesTXT_In_position" );
+ break;
+
+ case 6 :
+ Radio( "Radio.ReportingIn", "#Cstrike_TitlesTXT_Reporting_in" );
+ break;
+
+ case 7 :
+ Radio( "Radio.GetOutOfThere", "#Cstrike_TitlesTXT_Get_out_of_there" );
+ break;
+
+ case 8 :
+ Radio( "Radio.Negative", "#Cstrike_TitlesTXT_Negative" );
+ break;
+
+ case 9 :
+ Radio( "Radio.EnemyDown", "#Cstrike_TitlesTXT_Enemy_down" );
+ break;
+ }
+
+ // tell bots about radio message
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" );
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("slot", RADIO_START_3 + slot );
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+void UTIL_CSRadioMessage( IRecipientFilter& filter, int iClient, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL )
+{
+ UserMessageBegin( filter, "RadioText" );
+ WRITE_BYTE( msg_dest );
+ WRITE_BYTE( iClient );
+ WRITE_STRING( msg_name );
+
+ if ( param1 )
+ WRITE_STRING( param1 );
+ else
+ WRITE_STRING( "" );
+
+ if ( param2 )
+ WRITE_STRING( param2 );
+ else
+ WRITE_STRING( "" );
+
+ if ( param3 )
+ WRITE_STRING( param3 );
+ else
+ WRITE_STRING( "" );
+
+ if ( param4 )
+ WRITE_STRING( param4 );
+ else
+ WRITE_STRING( "" );
+
+ MessageEnd();
+}
+
+void CCSPlayer::ConstructRadioFilter( CRecipientFilter& filter )
+{
+ filter.MakeReliable();
+
+ int localTeam = GetTeamNumber();
+
+ int i;
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CCSPlayer *player = static_cast<CCSPlayer *>( UTIL_PlayerByIndex( i ) );
+ if ( !player )
+ continue;
+
+ // Skip players ignoring the radio
+ if ( player->m_bIgnoreRadio )
+ continue;
+
+ if( player->GetTeamNumber() == TEAM_SPECTATOR )
+ {
+ // add spectators
+ if( player->m_iObserverMode == OBS_MODE_IN_EYE || player->m_iObserverMode == OBS_MODE_CHASE )
+ {
+ filter.AddRecipient( player );
+ }
+ }
+ else if( player->GetTeamNumber() == localTeam )
+ {
+ // add teammates
+ filter.AddRecipient( player );
+ }
+ }
+}
+
+void CCSPlayer::Radio( const char *pszRadioSound, const char *pszRadioText )
+{
+ if( !IsAlive() )
+ return;
+
+ if ( IsObserver() )
+ return;
+
+ CRecipientFilter filter;
+ ConstructRadioFilter( filter );
+
+ if( pszRadioText )
+ {
+ const char *pszLocationText = CSGameRules()->GetChatLocation( true, this );
+ if ( pszLocationText && *pszLocationText )
+ {
+ UTIL_CSRadioMessage( filter, entindex(), HUD_PRINTTALK, "#Game_radio_location", GetPlayerName(), pszLocationText, pszRadioText );
+ }
+ else
+ {
+ UTIL_CSRadioMessage( filter, entindex(), HUD_PRINTTALK, "#Game_radio", GetPlayerName(), pszRadioText );
+ }
+ }
+
+ UserMessageBegin ( filter, "SendAudio" );
+ WRITE_STRING( pszRadioSound );
+ MessageEnd();
+
+ //icon over the head for teammates
+ TE_RadioIcon( filter, 0.0, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Outputs currently connected players to the console
+//-----------------------------------------------------------------------------
+void CCSPlayer::ListPlayers()
+{
+ char buf[64];
+ for ( int i=1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) );
+ if ( pPlayer && !pPlayer->IsDormant() )
+ {
+ if ( pPlayer->IsBot() )
+ {
+ Q_snprintf( buf, sizeof(buf), "B %d : %s", pPlayer->GetUserID(), pPlayer->GetPlayerName() );
+ }
+ else
+ {
+ Q_snprintf( buf, sizeof(buf), " %d : %s", pPlayer->GetUserID(), pPlayer->GetPlayerName() );
+ }
+ ClientPrint( this, HUD_PRINTCONSOLE, buf );
+ }
+ }
+ ClientPrint( this, HUD_PRINTCONSOLE, "\n" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+void CCSPlayer::OnDamagedByExplosion( const CTakeDamageInfo &info )
+{
+ float lastDamage = info.GetDamage();
+
+ //Adrian - This is hacky since we might have been damaged by something else
+ //but since the round is ending, who cares.
+ if ( CSGameRules()->m_bTargetBombed == true )
+ return;
+
+ float distanceFromPlayer = 9999.0f;
+
+ CBaseEntity *inflictor = info.GetInflictor();
+ if ( inflictor )
+ {
+ Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin();
+ distanceFromPlayer = delta.Length();
+ }
+
+ bool shock = lastDamage >= 30.0f;
+
+ if ( !shock )
+ return;
+
+ m_applyDeafnessTime = gpGlobals->curtime + 0.3;
+ m_currentDeafnessFilter = 0;
+}
+
+void CCSPlayer::ApplyDeafnessEffect()
+{
+ // what's happening here is that the low-pass filter and the oscillator frequency effects need
+ // to fade in and out slowly. So we have several filters that we switch between to achieve this
+ // effect. The first 3rd of the total effect will be the "fade in" of the effect. Which means going
+ // from filter to filter from the first to the last. Then we keep on the "last" filter for another
+ // third of the total effect time. Then the last third of the time we go back from the last filter
+ // to the first. Clear as mud?
+
+ // glossary:
+ // filter: an individual filter as defined in dsp_presets.txt
+ // section: one of the sections for the total effect, fade in, full, fade out are the possible sections
+ // effect: the total effect of combining all the sections, the whole of what the player hears from start to finish.
+
+ const int firstGrenadeFilterIndex = 137;
+ const int lastGrenadeFilterIndex = 139;
+ const float grenadeEffectLengthInSecs = 4.5f; // time of the total effect
+ const float fadeInSectionTime = 0.1f;
+ const float fadeOutSectionTime = 1.5f;
+
+ const float timeForEachFilterInFadeIn = fadeInSectionTime / (lastGrenadeFilterIndex - firstGrenadeFilterIndex);
+ const float timeForEachFilterInFadeOut = fadeOutSectionTime / (lastGrenadeFilterIndex - firstGrenadeFilterIndex);
+
+ float timeIntoEffect = gpGlobals->curtime - m_applyDeafnessTime;
+
+ if (timeIntoEffect >= grenadeEffectLengthInSecs)
+ {
+ // the effect is done, so reset the deafness variables.
+ m_applyDeafnessTime = 0.0f;
+ m_currentDeafnessFilter = 0;
+ return;
+ }
+
+ int section = 0;
+
+ if (timeIntoEffect < fadeInSectionTime)
+ {
+ section = 0;
+ }
+ else if (timeIntoEffect < (grenadeEffectLengthInSecs - fadeOutSectionTime))
+ {
+ section = 1;
+ }
+ else
+ {
+ section = 2;
+ }
+
+ int filterToUse = 0;
+
+ if (section == 0)
+ {
+ // fade into the effect.
+ int filterIndex = (int)(timeIntoEffect / timeForEachFilterInFadeIn);
+ filterToUse = filterIndex += firstGrenadeFilterIndex;
+ }
+ else if (section == 1)
+ {
+ // in full effect.
+ filterToUse = lastGrenadeFilterIndex;
+ }
+ else if (section == 2)
+ {
+ // fade out of the effect
+ float timeIntoSection = timeIntoEffect - (grenadeEffectLengthInSecs - fadeOutSectionTime);
+ int filterIndex = (int)(timeIntoSection / timeForEachFilterInFadeOut);
+ filterToUse = lastGrenadeFilterIndex - filterIndex - 1;
+ }
+
+ if (filterToUse != m_currentDeafnessFilter)
+ {
+ m_currentDeafnessFilter = filterToUse;
+
+ CSingleUserRecipientFilter user( this );
+ enginesound->SetPlayerDSP( user, m_currentDeafnessFilter, false );
+ }
+}
+
+
+void CCSPlayer::NoteWeaponFired()
+{
+ Assert( m_pCurrentCommand );
+ if( m_pCurrentCommand )
+ {
+ m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number;
+ }
+}
+
+
+bool CCSPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
+{
+ // No need to lag compensate at all if we're not attacking in this command and
+ // we haven't attacked recently.
+ if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
+ {
+ if ( ( pCmd->buttons & IN_ATTACK2 ) == 0 )
+ return false;
+
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+ if ( !weapon )
+ return false;
+
+ if ( weapon->GetWeaponID() != WEAPON_KNIFE )
+ return false; // IN_ATTACK2 with WEAPON_KNIFE should do lag compensation
+ }
+
+ return BaseClass::WantsLagCompensationOnEntity( pPlayer, pCmd, pEntityTransmitBits );
+}
+
+// Handles the special "radio" alias commands we're creating to accommodate the scripts players use
+// ** Returns true if we've handled the command **
+bool HandleRadioAliasCommands( CCSPlayer *pPlayer, const char *pszCommand )
+{
+ bool bRetVal = false;
+
+ // don't execute them if we are not alive or are an observer
+ if( !pPlayer->IsAlive() || pPlayer->IsObserver() )
+ return false;
+
+ // Radio1 commands
+ if ( FStrEq( pszCommand, "coverme" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 1 );
+ }
+ else if ( FStrEq( pszCommand, "takepoint" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 2 );
+ }
+ else if ( FStrEq( pszCommand, "holdpos" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 3 );
+ }
+ else if ( FStrEq( pszCommand, "regroup" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 4 );
+ }
+ else if ( FStrEq( pszCommand, "followme" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 5 );
+ }
+ else if ( FStrEq( pszCommand, "takingfire" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio1( 6 );
+ }
+ // Radio2 commands
+ else if ( FStrEq( pszCommand, "go" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 1 );
+ }
+ else if ( FStrEq( pszCommand, "fallback" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 2 );
+ }
+ else if ( FStrEq( pszCommand, "sticktog" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 3 );
+ }
+ else if ( FStrEq( pszCommand, "getinpos" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 4 );
+ }
+ else if ( FStrEq( pszCommand, "stormfront" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 5 );
+ }
+ else if ( FStrEq( pszCommand, "report" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio2( 6 );
+ }
+ // Radio3 commands
+ else if ( FStrEq( pszCommand, "roger" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 1 );
+ }
+ else if ( FStrEq( pszCommand, "enemyspot" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 2 );
+ }
+ else if ( FStrEq( pszCommand, "needbackup" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 3 );
+ }
+ else if ( FStrEq( pszCommand, "sectorclear" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 4 );
+ }
+ else if ( FStrEq( pszCommand, "inposition" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 5 );
+ }
+ else if ( FStrEq( pszCommand, "reportingin" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 6 );
+ }
+ else if ( FStrEq( pszCommand, "getout" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 7 );
+ }
+ else if ( FStrEq( pszCommand, "negative" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 8 );
+ }
+ else if ( FStrEq( pszCommand, "enemydown" ) )
+ {
+ bRetVal = true;
+ pPlayer->HandleMenu_Radio3( 9 );
+ }
+
+ return bRetVal;
+}
+
+bool CCSPlayer::ShouldRunRateLimitedCommand( const CCommand &args )
+{
+ const char *pcmd = args[0];
+
+ int i = m_RateLimitLastCommandTimes.Find( pcmd );
+ if ( i == m_RateLimitLastCommandTimes.InvalidIndex() )
+ {
+ m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime );
+ return true;
+ }
+ else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < CS_COMMAND_MAX_RATE )
+ {
+ // Too fast.
+ return false;
+ }
+ else
+ {
+ m_RateLimitLastCommandTimes[i] = gpGlobals->curtime;
+ return true;
+ }
+}
+
+bool CCSPlayer::ClientCommand( const CCommand &args )
+{
+ const char *pcmd = args[0];
+
+ // Bots mimic our client commands.
+/*
+ if ( bot_mimic.GetInt() && !( GetFlags() & FL_FAKECLIENT ) )
+ {
+ for ( int i=1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) );
+ if ( pPlayer && pPlayer != this && ( pPlayer->GetFlags() & FL_FAKECLIENT ) )
+ {
+ pPlayer->ClientCommand( pcmd );
+ }
+ }
+ }
+*/
+
+#if defined ( DEBUG )
+
+ if ( FStrEq( pcmd, "bot_cmd" ) )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( atoi( args[1] ) ) );
+ if ( pPlayer && pPlayer != this && ( pPlayer->GetFlags() & FL_FAKECLIENT ) )
+ {
+ CCommand botArgs( args.ArgC() - 2, &args.ArgV()[2] );
+ pPlayer->ClientCommand( botArgs );
+ pPlayer->RemoveEffects( EF_NODRAW );
+ }
+ return true;
+ }
+
+ if ( FStrEq( pcmd, "blind" ) )
+ {
+ if ( ShouldRunRateLimitedCommand( args ) )
+ {
+ if ( args.ArgC() == 3 )
+ {
+ Blind( atof( args[1] ), atof( args[2] ) );
+ }
+ else
+ {
+ ClientPrint( this, HUD_PRINTCONSOLE, "usage: blind holdtime fadetime\n" );
+ }
+ }
+ return true;
+ }
+
+ if ( FStrEq( pcmd, "deafen" ) )
+ {
+ Deafen( 0.0f );
+ return true;
+ }
+
+ if ( FStrEq( pcmd, "he_deafen" ) )
+ {
+ m_applyDeafnessTime = gpGlobals->curtime + 0.3;
+ m_currentDeafnessFilter = 0;
+ return true;
+ }
+
+ if ( FStrEq( pcmd, "hint_reset" ) )
+ {
+ m_iDisplayHistoryBits = 0;
+ return true;
+ }
+
+ if ( FStrEq( pcmd, "punch" ) )
+ {
+ float flDamage = 100;
+
+ QAngle punchAngle = GetPunchAngle();
+
+ punchAngle.x = flDamage * random->RandomFloat ( -0.15, 0.15 );
+ punchAngle.y = flDamage * random->RandomFloat ( -0.15, 0.15 );
+ punchAngle.z = flDamage * random->RandomFloat ( -0.15, 0.15 );
+
+ clamp( punchAngle.x, -4, punchAngle.x );
+ clamp( punchAngle.y, -5, 5 );
+ clamp( punchAngle.z, -5, 5 );
+
+ // +y == down
+ // +x == left
+ // +z == roll clockwise
+ if ( args.ArgC() == 4 )
+ {
+ punchAngle.x = atof(args[1]);
+ punchAngle.y = atof(args[2]);
+ punchAngle.z = atof(args[3]);
+ }
+
+ SetPunchAngle( punchAngle );
+
+ return true;
+ }
+
+#endif //DEBUG
+
+ if ( FStrEq( pcmd, "jointeam" ) )
+ {
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "Player sent bad jointeam syntax\n" );
+ }
+
+ if ( ShouldRunRateLimitedCommand( args ) )
+ {
+ int iTeam = atoi( args[1] );
+ HandleCommand_JoinTeam( iTeam );
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "spectate" ) )
+ {
+ if ( ShouldRunRateLimitedCommand( args ) )
+ {
+ // instantly join spectators
+ HandleCommand_JoinTeam( TEAM_SPECTATOR );
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "joingame" ) )
+ {
+ // player just closed MOTD dialog
+ if ( m_iPlayerState == STATE_WELCOME )
+ {
+ State_Transition( STATE_PICKINGTEAM );
+ }
+
+ return true;
+ }
+ else if ( FStrEq( pcmd, "joinclass" ) )
+ {
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "Player sent bad joinclass syntax\n" );
+ }
+
+ if ( ShouldRunRateLimitedCommand( args ) )
+ {
+ int iClass = atoi( args[1] );
+ HandleCommand_JoinClass( iClass );
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "drop" ) )
+ {
+ CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( GetActiveWeapon() );
+
+ if( pWeapon )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Determine value of dropped item.
+ //=============================================================================
+
+ if ( !pWeapon->IsAPriorOwner( this ) )
+ {
+ pWeapon->AddToPriorOwnerList( this );
+
+ CCS_GameStats.IncrementStat(this, CSTAT_ITEMS_DROPPED_VALUE, pWeapon->GetCSWpnData().GetWeaponPrice());
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ CSWeaponType type = pWeapon->GetCSWpnData().m_WeaponType;
+
+ if( type != WEAPONTYPE_KNIFE && type != WEAPONTYPE_GRENADE )
+ {
+ if (CSGameRules()->GetCanDonateWeapon() && !pWeapon->GetDonated())
+ {
+ pWeapon->SetDonated(true);
+ pWeapon->SetDonor(this);
+ }
+ CSWeaponDrop( pWeapon, true, true );
+
+ }
+ }
+
+ return true;
+ }
+ else if ( FStrEq( pcmd, "buy" ) )
+ {
+ BuyResult_e result = BUY_INVALID_ITEM;
+ if ( args.ArgC() == 2 )
+ {
+ result = HandleCommand_Buy( args[1] );
+ }
+ if ( result == BUY_INVALID_ITEM )
+ {
+ // Print out a message on the console
+ int msg_dest = HUD_PRINTCONSOLE;
+
+ ClientPrint( this, msg_dest, "usage: buy <item>\n" );
+ ClientPrint( this, msg_dest, " primammo\n" );
+ ClientPrint( this, msg_dest, " secammo\n" );
+ ClientPrint( this, msg_dest, " vest\n" );
+ ClientPrint( this, msg_dest, " vesthelm\n" );
+ ClientPrint( this, msg_dest, " defuser\n" );
+ //ClientPrint( this, msg_dest, " shield\n" );
+ ClientPrint( this, msg_dest, " nvgs\n" );
+ ClientPrint( this, msg_dest, " flashbang\n" );
+ ClientPrint( this, msg_dest, " hegrenade\n" );
+ ClientPrint( this, msg_dest, " smokegrenade\n" );
+ ClientPrint( this, msg_dest, " galil\n" );
+ ClientPrint( this, msg_dest, " ak47\n" );
+ ClientPrint( this, msg_dest, " scout\n" );
+ ClientPrint( this, msg_dest, " sg552\n" );
+ ClientPrint( this, msg_dest, " awp\n" );
+ ClientPrint( this, msg_dest, " g3sg1\n" );
+ ClientPrint( this, msg_dest, " famas\n" );
+ ClientPrint( this, msg_dest, " m4a1\n" );
+ ClientPrint( this, msg_dest, " aug\n" );
+ ClientPrint( this, msg_dest, " sg550\n" );
+ ClientPrint( this, msg_dest, " glock\n" );
+ ClientPrint( this, msg_dest, " usp\n" );
+ ClientPrint( this, msg_dest, " p228\n" );
+ ClientPrint( this, msg_dest, " deagle\n" );
+ ClientPrint( this, msg_dest, " elite\n" );
+ ClientPrint( this, msg_dest, " fiveseven\n" );
+ ClientPrint( this, msg_dest, " m3\n" );
+ ClientPrint( this, msg_dest, " xm1014\n" );
+ ClientPrint( this, msg_dest, " mac10\n" );
+ ClientPrint( this, msg_dest, " tmp\n" );
+ ClientPrint( this, msg_dest, " mp5navy\n" );
+ ClientPrint( this, msg_dest, " ump45\n" );
+ ClientPrint( this, msg_dest, " p90\n" );
+ ClientPrint( this, msg_dest, " m249\n" );
+ }
+
+ return true;
+ }
+ else if ( FStrEq( pcmd, "buyammo1" ) )
+ {
+ AttemptToBuyAmmoSingle(0);
+ return true;
+ }
+ else if ( FStrEq( pcmd, "buyammo2" ) )
+ {
+ AttemptToBuyAmmoSingle(1);
+ return true;
+ }
+ else if ( FStrEq( pcmd, "nightvision" ) )
+ {
+ if ( ShouldRunRateLimitedCommand( args ) )
+ {
+ if( m_bHasNightVision )
+ {
+ if( m_bNightVisionOn )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Player.NightVisionOff" );
+ }
+ else
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Player.NightVisionOn" );
+ }
+
+ m_bNightVisionOn = !m_bNightVisionOn;
+ }
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "menuselect" ) )
+ {
+ return true;
+ }
+ else if ( HandleRadioAliasCommands( this, pcmd ) )
+ {
+ return true;
+ }
+ else if ( FStrEq( pcmd, "listplayers" ) )
+ {
+ ListPlayers();
+ return true;
+ }
+
+ else if ( FStrEq( pcmd, "ignorerad" ) )
+ {
+ m_bIgnoreRadio = !m_bIgnoreRadio;
+ if ( m_bIgnoreRadio )
+ {
+ ClientPrint( this, HUD_PRINTTALK, "#Ignore_Radio" );
+ }
+ else
+ {
+ ClientPrint( this, HUD_PRINTTALK, "#Accept_Radio" );
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "become_vip" ) )
+ {
+ //MIKETODO: VIP mode
+ /*
+ if ( ( CSGameRules()->m_iMapHasVIPSafetyZone == 1 ) && ( m_iTeam == TEAM_CT ) )
+ {
+ mp->AddToVIPQueue( this );
+ }
+ */
+ return true;
+ }
+
+ return BaseClass::ClientCommand( args );
+}
+
+
+// returns true if the selection has been handled and the player's menu
+// can be closed...false if the menu should be displayed again
+bool CCSPlayer::HandleCommand_JoinTeam( int team )
+{
+ CCSGameRules *mp = CSGameRules();
+
+ if ( !GetGlobalTeam( team ) )
+ {
+ DevWarning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team );
+ return false;
+ }
+
+ // If this player is a VIP, don't allow him to switch teams/appearances unless the following conditions are met :
+ // a) There is another TEAM_CT player who is in the queue to be a VIP
+ // b) This player is dead
+
+ //MIKETODO: handle this when doing VIP mode
+ /*
+ if ( m_bIsVIP == true )
+ {
+ if ( !IsDead() )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Switch_From_VIP" );
+ MenuReset();
+ return true;
+ }
+ else if ( mp->IsVIPQueueEmpty() == true )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Switch_From_VIP" );
+ MenuReset();
+ return true;
+ }
+ }
+
+ //MIKETODO: VIP mode
+
+ case 3:
+ if ( ( mp->m_iMapHasVIPSafetyZone == 1 ) && ( m_iTeam == TEAM_CT ) )
+ {
+ mp->AddToVIPQueue( player );
+ MenuReset();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ */
+
+ // If we already died and changed teams once, deny
+ if( m_bTeamChanged && team != m_iOldTeam && team != TEAM_SPECTATOR )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Only_1_Team_Change" );
+ return true;
+ }
+
+ // check if we're limited in our team selection
+ if ( team == TEAM_UNASSIGNED && !IsBot() )
+ {
+ team = mp->GetHumanTeam(); // returns TEAM_UNASSIGNED if we're unrestricted
+ }
+
+ if ( team == TEAM_UNASSIGNED )
+ {
+ // Attempt to auto-select a team, may set team to T, CT or SPEC
+ team = mp->SelectDefaultTeam( !IsBot() );
+
+ if ( team == TEAM_UNASSIGNED )
+ {
+ // still team unassigned, try to kick a bot if possible
+
+ // kick a bot to allow human to join
+ if (cv_bot_auto_vacate.GetBool() && !IsBot())
+ {
+ team = (random->RandomInt( 0, 1 ) == 0) ? TEAM_TERRORIST : TEAM_CT;
+ if (UTIL_KickBotFromTeam( team ) == false)
+ {
+ // no bots on that team, try the other
+ team = (team == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT;
+ if (UTIL_KickBotFromTeam( team ) == false)
+ {
+ // couldn't kick any bots, fail
+ team = TEAM_UNASSIGNED;
+ }
+ }
+ }
+
+ if (team == TEAM_UNASSIGNED)
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#All_Teams_Full" );
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+ }
+ }
+
+ if ( team == GetTeamNumber() )
+ {
+ // Let people change class (skin) by re-joining the same team
+ if ( GetTeamNumber() == TEAM_TERRORIST && TerroristPlayerModels.Count() > 1 )
+ {
+ ShowViewPortPanel( PANEL_CLASS_TER );
+ }
+ else if ( GetTeamNumber() == TEAM_CT && CTPlayerModels.Count() > 1 )
+ {
+ ShowViewPortPanel( PANEL_CLASS_CT );
+ }
+ return true; // we wouldn't change the team
+ }
+
+ if ( mp->TeamFull( team ) )
+ {
+ // attempt to kick a bot to make room for this player
+ bool madeRoom = false;
+ if (cv_bot_auto_vacate.GetBool() && !IsBot())
+ {
+ if (UTIL_KickBotFromTeam( team ))
+ madeRoom = true;
+ }
+
+ if (!madeRoom)
+ {
+ if ( team == TEAM_TERRORIST )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Terrorists_Full" );
+ }
+ else if ( team == TEAM_CT )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#CTs_Full" );
+ }
+
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+ }
+
+ // check if humans are restricted to a single team (Tour of Duty, etc)
+ if ( !IsBot() && team != TEAM_SPECTATOR)
+ {
+ int humanTeam = mp->GetHumanTeam();
+ if ( humanTeam != TEAM_UNASSIGNED && humanTeam != team )
+ {
+ if ( humanTeam == TEAM_TERRORIST )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Humans_Join_Team_T" );
+ }
+ else if ( humanTeam == TEAM_CT )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Humans_Join_Team_CT" );
+ }
+
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+ }
+
+ if ( team == TEAM_SPECTATOR )
+ {
+ // Prevent this is the cvar is set
+ if ( !mp_allowspectators.GetInt() && !IsHLTV() )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" );
+ return false;
+ }
+
+ if ( GetTeamNumber() != TEAM_UNASSIGNED && State_Get() == STATE_ACTIVE )
+ {
+ m_fNextSuicideTime = gpGlobals->curtime; // allow the suicide to work
+
+ CommitSuicide();
+
+ // add 1 to frags to balance out the 1 subtracted for killing yourself
+ IncrementFragCount( 1 );
+ }
+
+ ChangeTeam( TEAM_SPECTATOR );
+ m_iClass = (int)CS_CLASS_NONE;
+
+ if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) )
+ {
+ m_iDisplayHistoryBits |= DHF_SPEC_DUCK;
+ HintMessage( "#Spec_Duck", true, true );
+ }
+
+ // do we have fadetoblack on? (need to fade their screen back in)
+ if ( mp_fadetoblack.GetBool() )
+ {
+ color32_s clr = { 0,0,0,255 };
+ UTIL_ScreenFade( this, clr, 0, 0, FFADE_IN | FFADE_PURGE );
+ }
+
+ return true;
+ }
+
+ // If the code gets this far, the team is not TEAM_UNASSIGNED
+
+
+ if (mp->TeamStacked( team, GetTeamNumber() ))//players are allowed to change to their own team so they can just change their model
+ {
+ // attempt to kick a bot to make room for this player
+ bool madeRoom = false;
+ if (cv_bot_auto_vacate.GetBool() && !IsBot())
+ {
+ if (UTIL_KickBotFromTeam( team ))
+ madeRoom = true;
+ }
+
+ if (!madeRoom)
+ {
+ // The specified team is full
+ ClientPrint(
+ this,
+ HUD_PRINTCENTER,
+ ( team == TEAM_TERRORIST ) ? "#Too_Many_Terrorists" : "#Too_Many_CTs" );
+
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+ }
+
+ // Show the appropriate Choose Appearance menu
+ // This must come before ClientKill() for CheckWinConditions() to function properly
+
+ // Switch their actual team...
+ ChangeTeam( team );
+
+ return true;
+}
+
+
+bool CCSPlayer::HandleCommand_JoinClass( int iClass )
+{
+ if( iClass == CS_CLASS_NONE )
+ {
+ // User choosed random class
+ switch ( GetTeamNumber() )
+ {
+ case TEAM_TERRORIST : iClass = RandomInt(FIRST_T_CLASS, LAST_T_CLASS);
+ break;
+
+ case TEAM_CT : iClass = RandomInt(FIRST_CT_CLASS, LAST_CT_CLASS);
+ break;
+
+ default : iClass = CS_CLASS_NONE;
+ break;
+ }
+ }
+
+ // clamp to valid classes
+ switch ( GetTeamNumber() )
+ {
+ case TEAM_TERRORIST:
+ iClass = clamp( iClass, FIRST_T_CLASS, LAST_T_CLASS );
+ break;
+ case TEAM_CT:
+ iClass = clamp( iClass, FIRST_CT_CLASS, LAST_CT_CLASS );
+ break;
+ default:
+ iClass = CS_CLASS_NONE;
+ }
+
+ // Reset the player's state
+ if ( State_Get() == STATE_ACTIVE )
+ {
+ CSGameRules()->CheckWinConditions();
+ }
+
+ if ( !IsBot() && State_Get() == STATE_ACTIVE ) // Bots are responsible about only switching classes when they join.
+ {
+ // Kill player if switching classes while alive.
+ // This mimics goldsrc CS 1.6, and prevents a player from hiding, and switching classes to
+ // make the opposing team think there are more enemies than there really are.
+ CommitSuicide();
+ }
+
+ m_iClass = iClass;
+
+ if (State_Get() == STATE_PICKINGCLASS)
+ {
+// SetModelFromClass();
+ GetIntoGame();
+ }
+
+ return true;
+}
+
+
+/*
+void CheckStartMoney( void )
+{
+ if ( mp_startmoney.GetInt() > 16000 )
+ {
+ mp_startmoney.SetInt( 16000 );
+ }
+ else if ( mp_startmoney.GetInt() < 800 )
+ {
+ mp_startmoney.SetInt( 800 );
+ }
+}
+*/
+
+void CCSPlayer::GetIntoGame()
+{
+ // Set their model and if they're allowed to spawn right now, put them into the world.
+ //SetPlayerModel( iClass );
+
+ SetFOV( this, 0 );
+ m_flLastMovement = gpGlobals->curtime;
+
+ CCSGameRules *MPRules = CSGameRules();
+
+/* //MIKETODO: Escape gameplay ?
+ if ( ( MPRules->m_bMapHasEscapeZone == true ) && ( m_iTeam == TEAM_CT ) )
+ {
+ m_iAccount = 0;
+
+ CheckStartMoney();
+ AddAccount( (int)startmoney.value, true );
+ }
+ */
+
+
+ //****************New Code by SupraFiend************
+ if ( !MPRules->FPlayerCanRespawn( this ) )
+ {
+ // This player is joining in the middle of a round or is an observer. Put them directly into observer mode.
+ //pev->deadflag = DEAD_RESPAWNABLE;
+ //pev->classname = MAKE_STRING("player");
+ //pev->flags &= ( FL_PROXY | FL_FAKECLIENT ); // clear flags, but keep proxy and bot flags that might already be set
+ //pev->flags |= FL_CLIENT | FL_SPECTATOR;
+ //SetThink(PlayerDeathThink);
+ if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) )
+ {
+ m_iDisplayHistoryBits |= DHF_SPEC_DUCK;
+ HintMessage( "#Spec_Duck", true, true );
+ }
+
+ State_Transition( STATE_OBSERVER_MODE );
+
+ m_wasNotKilledNaturally = true;
+
+ MPRules->CheckWinConditions();
+ }
+ else// else spawn them right in
+ {
+ State_Transition( STATE_ACTIVE );
+
+ Spawn();
+
+ MPRules->CheckWinConditions();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Have the rules update anything related to a player spawning in late
+ //=============================================================================
+
+ MPRules->SpawningLatePlayer(this);
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if( MPRules->m_flRestartRoundTime == 0.0f )
+ {
+ //Bomb target, no bomber and no bomb lying around.
+ if( MPRules->IsBombDefuseMap() && !MPRules->IsThereABomber() && !MPRules->IsThereABomb() )
+ MPRules->GiveC4(); //Checks for terrorists.
+ }
+
+ // If a new terrorist is entering the fray, then up the # of potential escapers.
+ if ( GetTeamNumber() == TEAM_TERRORIST )
+ MPRules->m_iNumEscapers++;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Reset Round Based Achievement Variables
+ //=============================================================================
+
+ ResetRoundBasedAchievementVariables();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ }
+}
+
+
+int CCSPlayer::PlayerClass() const
+{
+ return m_iClass;
+}
+
+
+
+bool CCSPlayer::SelectSpawnSpot( const char *pEntClassName, CBaseEntity* &pSpot )
+{
+ // Find the next spawn spot.
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ if ( pSpot == NULL ) // skip over the null point
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+
+ CBaseEntity *pFirstSpot = pSpot;
+ do
+ {
+ if ( pSpot )
+ {
+ // check if pSpot is valid
+ if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) )
+ {
+ if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
+ {
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ continue;
+ }
+
+ // if so, go to pSpot
+ return true;
+ }
+ }
+ // increment pSpot
+ pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
+ } while ( pSpot != pFirstSpot ); // loop if we're not back to the start
+
+ DevMsg("CCSPlayer::SelectSpawnSpot: couldn't find valid spawn point.\n");
+
+ return true;
+}
+
+
+CBaseEntity* CCSPlayer::EntSelectSpawnPoint()
+{
+ CBaseEntity *pSpot;
+
+ /* MIKETODO: VIP
+ // VIP spawn point *************
+ if ( ( g_pGameRules->IsDeathmatch() ) && ( ((CBasePlayer*)pPlayer)->m_bIsVIP == TRUE) )
+ {
+ //ALERT (at_console,"Looking for a VIP spawn point\n");
+ // Randomize the start spot
+ //for ( int i = RANDOM_LONG(1,5); i > 0; i-- )
+ pSpot = UTIL_FindEntityByClassname( NULL, "info_vip_start" );
+ if ( !FNullEnt( pSpot ) ) // skip over the null point
+ goto ReturnSpot;
+ else
+ goto CTSpawn;
+ }
+
+ //
+ // the counter-terrorist spawns at "info_player_start"
+ else
+ */
+
+ pSpot = NULL;
+ if ( CSGameRules()->IsLogoMap() )
+ {
+ // This is a logo map. Don't allow movement or logos or menus.
+ SelectSpawnSpot( "info_player_logo", pSpot );
+ LockPlayerInPlace();
+ goto ReturnSpot;
+ }
+ else
+ {
+ if ( GetTeamNumber() == TEAM_CT )
+ {
+ pSpot = g_pLastCTSpawn;
+ if ( SelectSpawnSpot( "info_player_counterterrorist", pSpot ))
+ {
+
+ g_pLastCTSpawn = pSpot;
+ goto ReturnSpot;
+ }
+ }
+
+ /*********************************************************/
+ // The terrorist spawn points
+ else if ( GetTeamNumber() == TEAM_TERRORIST )
+ {
+ pSpot = g_pLastTerroristSpawn;
+
+ if ( SelectSpawnSpot( "info_player_terrorist", pSpot ) )
+ {
+ g_pLastTerroristSpawn = pSpot;
+ goto ReturnSpot;
+ }
+ }
+ }
+
+
+ // If startspot is set, (re)spawn there.
+ if ( !gpGlobals->startspot || !strlen(STRING(gpGlobals->startspot)))
+ {
+ pSpot = gEntList.FindEntityByClassname(NULL, "info_player_terrorist");
+ if ( pSpot )
+ goto ReturnSpot;
+ }
+ else
+ {
+ pSpot = gEntList.FindEntityByTarget( NULL, STRING(gpGlobals->startspot) );
+ if ( pSpot )
+ goto ReturnSpot;
+ }
+
+ReturnSpot:
+ if ( !pSpot )
+ {
+ if( CSGameRules()->IsLogoMap() )
+ Warning( "PutClientInServer: no info_player_logo on level\n" );
+ else
+ Warning( "PutClientInServer: no info_player_start on level\n" );
+
+ return CBaseEntity::Instance( INDEXENT(0) );
+ }
+
+ return pSpot;
+}
+
+
+void CCSPlayer::SetProgressBarTime( int barTime )
+{
+ m_iProgressBarDuration = barTime;
+ m_flProgressBarStartTime = this->m_flSimulationTime;
+}
+
+
+void CCSPlayer::PlayerDeathThink()
+{
+}
+
+
+void CCSPlayer::State_Transition( CSPlayerState newState )
+{
+ State_Leave();
+ State_Enter( newState );
+}
+
+
+void CCSPlayer::State_Enter( CSPlayerState newState )
+{
+ m_iPlayerState = newState;
+ m_pCurStateInfo = State_LookupInfo( newState );
+
+ if ( cs_ShowStateTransitions.GetInt() == -1 || cs_ShowStateTransitions.GetInt() == entindex() )
+ {
+ if ( m_pCurStateInfo )
+ Msg( "ShowStateTransitions: entering '%s'\n", m_pCurStateInfo->m_pStateName );
+ else
+ Msg( "ShowStateTransitions: entering #%d\n", newState );
+ }
+
+ // Initialize the new state.
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
+ (this->*m_pCurStateInfo->pfnEnterState)();
+}
+
+
+void CCSPlayer::State_Leave()
+{
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
+ {
+ (this->*m_pCurStateInfo->pfnLeaveState)();
+ }
+}
+
+
+void CCSPlayer::State_PreThink()
+{
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink )
+ {
+ (this->*m_pCurStateInfo->pfnPreThink)();
+ }
+}
+
+
+CCSPlayerStateInfo* CCSPlayer::State_LookupInfo( CSPlayerState state )
+{
+ // This table MUST match the
+ static CCSPlayerStateInfo playerStateInfos[] =
+ {
+ { STATE_ACTIVE, "STATE_ACTIVE", &CCSPlayer::State_Enter_ACTIVE, NULL, &CCSPlayer::State_PreThink_ACTIVE },
+ { STATE_WELCOME, "STATE_WELCOME", &CCSPlayer::State_Enter_WELCOME, NULL, &CCSPlayer::State_PreThink_WELCOME },
+ { STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CCSPlayer::State_Enter_PICKINGTEAM, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE },
+ { STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CCSPlayer::State_Enter_PICKINGCLASS, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE },
+ { STATE_DEATH_ANIM, "STATE_DEATH_ANIM", &CCSPlayer::State_Enter_DEATH_ANIM, NULL, &CCSPlayer::State_PreThink_DEATH_ANIM },
+ { STATE_DEATH_WAIT_FOR_KEY, "STATE_DEATH_WAIT_FOR_KEY", &CCSPlayer::State_Enter_DEATH_WAIT_FOR_KEY, NULL, &CCSPlayer::State_PreThink_DEATH_WAIT_FOR_KEY },
+ { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CCSPlayer::State_Enter_OBSERVER_MODE, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE }
+ };
+
+ for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
+ {
+ if ( playerStateInfos[i].m_iPlayerState == state )
+ return &playerStateInfos[i];
+ }
+
+ return NULL;
+}
+
+
+void CCSPlayer::PhysObjectSleep()
+{
+ IPhysicsObject *pObj = VPhysicsGetObject();
+ if ( pObj )
+ pObj->Sleep();
+}
+
+
+void CCSPlayer::PhysObjectWake()
+{
+ IPhysicsObject *pObj = VPhysicsGetObject();
+ if ( pObj )
+ pObj->Wake();
+}
+
+
+void CCSPlayer::State_Enter_WELCOME()
+{
+ StartObserverMode( OBS_MODE_ROAMING );
+
+ // Important to set MOVETYPE_NONE or our physics object will fall while we're sitting at one of the intro cameras.
+ SetMoveType( MOVETYPE_NONE );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ PhysObjectSleep();
+
+ const ConVar *hostname = cvar->FindVar( "hostname" );
+ const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
+
+ // Show info panel (if it's not a simple demo map).
+ if ( !CSGameRules()->IsLogoMap() )
+ {
+ if ( CommandLine()->FindParm( "-makereslists" ) ) // don't show the MOTD when making reslists
+ {
+ engine->ClientCommand( edict(), "jointeam 3\n" );
+ }
+ else
+ {
+ KeyValues *data = new KeyValues("data");
+ data->SetString( "title", title ); // info panel title
+ data->SetString( "type", "1" ); // show userdata from stringtable entry
+ data->SetString( "msg", "motd" ); // use this stringtable entry
+ data->SetInt( "cmd", TEXTWINDOW_CMD_JOINGAME ); // exec this command if panel closed
+ data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() );
+
+ ShowViewPortPanel( PANEL_INFO, true, data );
+
+ data->deleteThis();
+ }
+ }
+}
+
+
+void CCSPlayer::State_PreThink_WELCOME()
+{
+ // Verify some state.
+ Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
+ Assert( GetAbsVelocity().Length() == 0 );
+
+ // Update whatever intro camera it's at.
+ if( m_pIntroCamera && (gpGlobals->curtime >= m_fIntroCamTime) )
+ {
+ MoveToNextIntroCamera();
+ }
+}
+
+
+void CCSPlayer::State_Enter_PICKINGTEAM()
+{
+ ShowViewPortPanel( "team" ); // show the team menu
+}
+
+
+void CCSPlayer::State_Enter_DEATH_ANIM()
+{
+ if ( HasWeapons() )
+ {
+ // we drop the guns here because weapons that have an area effect and can kill their user
+ // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
+ // player class sometimes is freed. It's safer to manipulate the weapons once we know
+ // we aren't calling into any of their code anymore through the player pointer.
+ PackDeadPlayerItems();
+ }
+
+ // Used for a timer.
+ m_flDeathTime = gpGlobals->curtime;
+
+ m_bAbortFreezeCam = false;
+
+ StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode
+ RemoveEffects( EF_NODRAW ); // still draw player body
+
+ if ( mp_fadetoblack.GetBool() )
+ {
+ color32_s clr = {0,0,0,255};
+ UTIL_ScreenFade( this, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT );
+ //Don't perform any freezecam stuff if we are fading to black
+ State_Transition( STATE_DEATH_WAIT_FOR_KEY );
+ }
+}
+
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish, pfreese] Added freeze cam logic
+//=============================================================================
+
+void CCSPlayer::State_PreThink_DEATH_ANIM()
+{
+ // If the anim is done playing, go to the next state (waiting for a keypress to
+ // either respawn the guy or put him into observer mode).
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ float flForward = GetAbsVelocity().Length() - 20;
+ if (flForward <= 0)
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+ else
+ {
+ Vector vAbsVel = GetAbsVelocity();
+ VectorNormalize( vAbsVel );
+ vAbsVel *= flForward;
+ SetAbsVelocity( vAbsVel );
+ }
+ }
+
+ float fDeathEnd = m_flDeathTime + CS_DEATH_ANIMATION_TIME;
+ float fFreezeEnd = fDeathEnd + spec_freeze_traveltime.GetFloat() + spec_freeze_time.GetFloat();
+
+ // transition to Freezecam mode once the death animation is complete
+ if ( gpGlobals->curtime >= fDeathEnd )
+ {
+ if ( GetObserverTarget() && GetObserverTarget() != this &&
+ !m_bAbortFreezeCam && gpGlobals->curtime < fFreezeEnd && GetObserverMode() != OBS_MODE_FREEZECAM)
+ {
+ StartObserverMode( OBS_MODE_FREEZECAM );
+ }
+ else if(GetObserverMode() == OBS_MODE_FREEZECAM)
+ {
+ if ( m_bAbortFreezeCam && !mp_fadetoblack.GetBool() )
+ {
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ }
+ }
+
+ // Don't transfer to observer state until the freeze cam is done
+ if ( gpGlobals->curtime < fFreezeEnd )
+ return;
+
+ State_Transition( STATE_OBSERVER_MODE );
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+
+void CCSPlayer::State_Enter_DEATH_WAIT_FOR_KEY()
+{
+ // Remember when we died, so we can automatically put them into observer mode
+ // if they don't hit a key soon enough.
+
+ m_lifeState = LIFE_DEAD;
+
+ StopAnimation();
+
+ // Don't do this. The ragdoll system expects to be able to read from this player on
+ // the next update and will read it at the new origin if this is set.
+ // Since it is more complicated to redesign the ragdoll system to not need that data
+ // it is easier to cause a less obvious bug than popping ragdolls
+ //AddEffects( EF_NOINTERP );
+}
+
+
+void CCSPlayer::State_PreThink_DEATH_WAIT_FOR_KEY()
+{
+ // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore
+ // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn
+ if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
+ SetMoveType( MOVETYPE_NONE );
+
+ // if the player has been dead for one second longer than allowed by forcerespawn,
+ // forcerespawn isn't on. Send the player off to an intermission camera until they
+ // choose to respawn.
+
+ bool fAnyButtonDown = (m_nButtons & ~IN_SCORE) != 0;
+ if ( mp_fadetoblack.GetBool() )
+ fAnyButtonDown = false;
+
+ // after a certain amount of time switch to observer mode even if they don't press a key.
+ if (gpGlobals->curtime >= (m_flDeathTime + DEATH_ANIMATION_TIME + 3.0))
+ {
+ fAnyButtonDown = true;
+ }
+
+ if ( fAnyButtonDown )
+ {
+ if ( GetObserverTarget() )
+ {
+ StartReplayMode( 8, 8, GetObserverTarget()->entindex() );
+ }
+
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+}
+
+void CCSPlayer::State_Enter_OBSERVER_MODE()
+{
+ // do we have fadetoblack on? (need to fade their screen back in)
+ if ( mp_fadetoblack.GetBool() && mp_forcecamera.GetInt() != OBS_ALLOW_NONE)
+ {
+ color32_s clr = { 0,0,0,255 };
+ UTIL_ScreenFade( this, clr, 0, 0, FFADE_IN | FFADE_PURGE );
+ }
+
+ int observerMode = m_iObserverLastMode;
+ if ( IsNetClient() )
+ {
+ const char *pIdealMode = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_spec_mode" );
+ if ( pIdealMode )
+ {
+ int nIdealMode = atoi( pIdealMode );
+
+ if ( nIdealMode < OBS_MODE_IN_EYE )
+ {
+ nIdealMode = OBS_MODE_IN_EYE;
+ }
+ else if ( nIdealMode > OBS_MODE_ROAMING )
+ {
+ nIdealMode = OBS_MODE_ROAMING;
+ }
+
+ observerMode = nIdealMode;
+ }
+ }
+
+ StartObserverMode( observerMode );
+
+ PhysObjectSleep();
+}
+
+void CCSPlayer::State_PreThink_OBSERVER_MODE()
+{
+ // Make sure nobody has changed any of our state.
+// Assert( GetMoveType() == MOVETYPE_FLY );
+ Assert( m_takedamage == DAMAGE_NO );
+ Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
+// Assert( IsEffectActive( EF_NODRAW ) );
+
+ // Must be dead.
+ Assert( m_lifeState == LIFE_DEAD );
+ Assert( pl.deadflag );
+}
+
+
+void CCSPlayer::State_Enter_PICKINGCLASS()
+{
+ if ( CommandLine()->FindParm( "-makereslists" ) ) // don't show the menu when making reslists
+ {
+ engine->ClientCommand( edict(), "joinclass 0\n" );
+ return;
+ }
+
+ // go to spec mode, if dying keep deathcam
+ if ( GetObserverMode() == OBS_MODE_DEATHCAM )
+ {
+ StartObserverMode( OBS_MODE_DEATHCAM );
+ }
+ else
+ {
+ StartObserverMode( OBS_MODE_FIXED );
+ }
+
+ m_iClass = (int)CS_CLASS_NONE;
+
+ PhysObjectSleep();
+
+ // show the class menu:
+ if ( GetTeamNumber() == TEAM_TERRORIST && TerroristPlayerModels.Count() > 1 )
+ {
+ ShowViewPortPanel( PANEL_CLASS_TER );
+ }
+ else if ( GetTeamNumber() == TEAM_CT && CTPlayerModels.Count() > 1 )
+ {
+ ShowViewPortPanel( PANEL_CLASS_CT );
+ }
+ else
+ {
+ HandleCommand_JoinClass( 0 );
+ }
+}
+
+void CCSPlayer::State_Enter_ACTIVE()
+{
+ SetMoveType( MOVETYPE_WALK );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_Local.m_iHideHUD = 0;
+ PhysObjectWake();
+}
+
+
+void CCSPlayer::State_PreThink_ACTIVE()
+{
+ // We only allow noclip here only because noclip is useful for debugging.
+ // It would be nice if the noclip command set some flag so we could tell that they
+ // did it intentionally.
+ if ( IsEFlagSet( EFL_NOCLIP_ACTIVE ) )
+ {
+// Assert( GetMoveType() == MOVETYPE_NOCLIP );
+ }
+ else
+ {
+// Assert( GetMoveType() == MOVETYPE_WALK );
+ }
+
+ Assert( !IsSolidFlagSet( FSOLID_NOT_SOLID ) );
+}
+
+
+void CCSPlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon )
+{
+ CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon );
+ if ( pCSWeapon )
+ {
+ // For rifles, pistols, or the knife, drop our old weapon in this slot.
+ if ( pCSWeapon->GetSlot() == WEAPON_SLOT_RIFLE ||
+ pCSWeapon->GetSlot() == WEAPON_SLOT_PISTOL ||
+ pCSWeapon->GetSlot() == WEAPON_SLOT_KNIFE )
+ {
+ CBaseCombatWeapon *pDropWeapon = Weapon_GetSlot( pCSWeapon->GetSlot() );
+ if ( pDropWeapon )
+ {
+ CSWeaponDrop( pDropWeapon, false, true );
+ }
+ }
+ else if( pCSWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_GRENADE )
+ {
+ //if we already have this weapon, just add the ammo and destroy it
+ if( Weapon_OwnsThisType( pCSWeapon->GetClassname() ) )
+ {
+ Weapon_EquipAmmoOnly( pWeapon );
+ UTIL_Remove( pCSWeapon );
+ return;
+ }
+ }
+
+ pCSWeapon->SetSolidFlags( FSOLID_NOT_SOLID );
+ pCSWeapon->SetOwnerEntity( this );
+ }
+
+ BaseClass::Weapon_Equip( pWeapon );
+}
+
+bool CCSPlayer::Weapon_CanUse( CBaseCombatWeapon *pBaseWeapon )
+{
+ CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon );
+
+ if ( pWeapon )
+ {
+ // Don't give weapon_c4 to non-terrorists
+ if( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_C4 && GetTeamNumber() != TEAM_TERRORIST )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool CCSPlayer::BumpWeapon( CBaseCombatWeapon *pBaseWeapon )
+{
+ CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon );
+ if ( !pWeapon )
+ {
+ Assert( !pWeapon );
+ pBaseWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
+ pBaseWeapon->AddEffects( EF_NODRAW );
+ Weapon_Equip( pBaseWeapon );
+ return true;
+ }
+
+ CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
+
+ // Can I have this weapon type?
+ if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
+ {
+ extern int gEvilImpulse101;
+ if ( gEvilImpulse101 )
+ {
+ UTIL_Remove( pWeapon );
+ }
+ return false;
+ }
+
+ // Even if we already have a grenade in this slot, we can pickup another one if we don't already
+ // own this type of grenade.
+ bool bPickupGrenade = ( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_GRENADE );
+
+
+ /*
+ // ----------------------------------------
+ // If I already have it just take the ammo
+ // ----------------------------------------
+ if ( !bPickupGrenade && Weapon_SlotOccupied( pWeapon ) )
+ {
+ Weapon_EquipAmmoOnly( pWeapon );
+ // Only remove me if I have no ammo left
+ // Can't just check HasAnyAmmo because if I don't use clips, I want to be removed,
+ if ( pWeapon->UsesClipsForAmmo1() && pWeapon->HasPrimaryAmmo() )
+ return false;
+
+ UTIL_Remove( pWeapon );
+ return false;
+ }
+ */
+
+ if ( HasShield() && pWeapon->GetCSWpnData().m_bCanUseWithShield == false )
+ return false;
+
+ // Check ammo counts for grenades, and don't try to pick up more grenades than we can carry
+ if ( bPickupGrenade )
+ {
+ CBaseCombatWeapon *pOwnedGrenade = Weapon_OwnsThisType( pWeapon->GetClassname() );
+
+ if( pOwnedGrenade )
+ {
+ int numGrenades = 0;
+ int maxGrenades = 0;
+
+ int ammoIndex = pOwnedGrenade->GetPrimaryAmmoType();
+ if( ammoIndex != -1 )
+ {
+ numGrenades = GetAmmoCount( ammoIndex );
+ }
+ maxGrenades = GetAmmoDef()->MaxCarry(ammoIndex);
+
+ if( numGrenades >= maxGrenades )
+ {
+ return false;
+ }
+ }
+ }
+
+ if( bPickupGrenade || !Weapon_SlotOccupied( pWeapon ) )
+ {
+ pWeapon->CheckRespawn();
+
+ pWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
+ pWeapon->AddEffects( EF_NODRAW );
+
+ CCSPlayer* pDonor = pWeapon->GetDonor();
+ if ( pDonor && pDonor != this && pWeapon->GetCSWpnData().GetWeaponPrice() > m_iAccount )
+ {
+ CCS_GameStats.Event_PlayerDonatedWeapon( pDonor );
+ }
+ pWeapon->SetDonor(NULL);
+
+ Weapon_Equip( pWeapon );
+
+ int iExtraAmmo = pWeapon->GetExtraAmmoCount();
+
+ if( iExtraAmmo && !bPickupGrenade )
+ {
+ //Find out the index of the ammo
+ int iAmmoIndex = pWeapon->GetPrimaryAmmoType();
+
+ if( iAmmoIndex != -1 )
+ {
+ //Remove the extra ammo from the weapon
+ pWeapon->SetExtraAmmoCount(0);
+
+ //Give it to the player
+ SetAmmoCount( iExtraAmmo, iAmmoIndex );
+ }
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" );
+ if( event )
+ {
+ const char *weaponName = pWeapon->GetClassname();
+ if ( strncmp( weaponName, "weapon_", 7 ) == 0 )
+ {
+ weaponName += 7;
+ }
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "item", weaponName );
+ gameeventmanager->FireEvent( event );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+void CCSPlayer::ResetStamina( void )
+{
+ m_flStamina = 0.0f;
+}
+
+void CCSPlayer::RescueZoneTouch( inputdata_t &inputdata )
+{
+ m_bInHostageRescueZone = true;
+ if ( GetTeamNumber() == TEAM_CT && !(m_iDisplayHistoryBits & DHF_IN_RESCUE_ZONE) )
+ {
+ HintMessage( "#Hint_hostage_rescue_zone", false );
+ m_iDisplayHistoryBits |= DHF_IN_RESCUE_ZONE;
+ }
+}
+
+//------------------------------------------------------------------------------------------
+CON_COMMAND( timeleft, "prints the time remaining in the match" )
+{
+ CCSPlayer *pPlayer = ToCSPlayer( UTIL_GetCommandClient() );
+ if ( pPlayer && pPlayer->m_iNextTimeCheck >= gpGlobals->curtime )
+ {
+ return; // rate limiting
+ }
+
+ int iTimeRemaining = (int)CSGameRules()->GetMapRemainingTime();
+
+ if ( iTimeRemaining < 0 )
+ {
+ if ( pPlayer )
+ {
+ ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_no_timelimit" );
+ }
+ else
+ {
+ Msg( "* No Time Limit *\n" );
+ }
+ }
+ else if ( iTimeRemaining == 0 )
+ {
+ if ( pPlayer )
+ {
+ ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_last_round" );
+ }
+ else
+ {
+ Msg( "* Last Round *\n" );
+ }
+ }
+ else
+ {
+ int iMinutes, iSeconds;
+ iMinutes = iTimeRemaining / 60;
+ iSeconds = iTimeRemaining % 60;
+
+ char minutes[8];
+ char seconds[8];
+
+ Q_snprintf( minutes, sizeof(minutes), "%d", iMinutes );
+ Q_snprintf( seconds, sizeof(seconds), "%2.2d", iSeconds );
+
+ if ( pPlayer )
+ {
+ ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_timelimit", minutes, seconds );
+ }
+ else
+ {
+ Msg( "Time Remaining: %s:%s\n", minutes, seconds );
+ }
+ }
+
+ if ( pPlayer )
+ {
+ pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1;
+ }
+}
+
+//------------------------------------------------------------------------------------------
+/**
+ * Emit given sound that only we can hear
+ */
+void CCSPlayer::EmitPrivateSound( const char *soundName )
+{
+ CSoundParameters params;
+ if (!GetParametersForSound( soundName, params, NULL ))
+ return;
+
+ CSingleUserRecipientFilter filter( this );
+ EmitSound( filter, entindex(), soundName );
+}
+
+
+//=====================
+//Autobuy
+//=====================
+static void AutoBuy( void )
+{
+ CCSPlayer *player = ToCSPlayer( UTIL_GetCommandClient() );
+
+ if ( player )
+ player->AutoBuy();
+}
+static ConCommand autobuy( "autobuy", AutoBuy, "Attempt to purchase items with the order listed in cl_autobuy" );
+
+//==============================================
+//AutoBuy - do the work of deciding what to buy
+//==============================================
+void CCSPlayer::AutoBuy()
+{
+ if ( !IsInBuyZone() )
+ {
+ EmitPrivateSound( "BuyPreset.CantBuy" );
+ return;
+ }
+
+ const char *autobuyString = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_autobuy" );
+ if ( !autobuyString || !*autobuyString )
+ {
+ EmitPrivateSound( "BuyPreset.AlreadyBought" );
+ return;
+ }
+
+ bool boughtPrimary = false, boughtSecondary = false;
+
+ m_bIsInAutoBuy = true;
+ ParseAutoBuyString(autobuyString, boughtPrimary, boughtSecondary);
+ m_bIsInAutoBuy = false;
+
+ m_bAutoReload = true;
+
+ //TODO ?: stripped out all the attempts to buy career weapons.
+ // as we're not porting cs:cz, these were skipped
+}
+
+void CCSPlayer::ParseAutoBuyString(const char *string, bool &boughtPrimary, bool &boughtSecondary)
+{
+ char command[32];
+ int nBuffSize = sizeof(command) - 1; // -1 to leave space for the NULL at the end of the string
+ const char *c = string;
+
+ if (c == NULL)
+ {
+ EmitPrivateSound( "BuyPreset.AlreadyBought" );
+ return;
+ }
+
+ BuyResult_e overallResult = BUY_ALREADY_HAVE;
+
+ // loop through the string of commands, trying each one in turn.
+ while (*c != 0)
+ {
+ int i = 0;
+ // copy the next word into the command buffer.
+ while ((*c != 0) && (*c != ' ') && (i < nBuffSize))
+ {
+ command[i] = *(c);
+ ++c;
+ ++i;
+ }
+ if (*c == ' ')
+ {
+ ++c; // skip the space.
+ }
+
+ command[i] = 0; // terminate the string.
+
+ // clear out any spaces.
+ i = 0;
+ while (command[i] != 0)
+ {
+ if (command[i] == ' ')
+ {
+ command[i] = 0;
+ break;
+ }
+ ++i;
+ }
+
+ // make sure we actually have a command.
+ if (strlen(command) == 0)
+ {
+ continue;
+ }
+
+ AutoBuyInfoStruct * commandInfo = GetAutoBuyCommandInfo(command);
+
+ if (ShouldExecuteAutoBuyCommand(commandInfo, boughtPrimary, boughtSecondary))
+ {
+ BuyResult_e result = HandleCommand_Buy( command );
+
+ overallResult = CombineBuyResults( overallResult, result );
+
+ // check to see if we actually bought a primary or secondary weapon this time.
+ PostAutoBuyCommandProcessing(commandInfo, boughtPrimary, boughtSecondary);
+ }
+ }
+
+ if ( overallResult == BUY_CANT_AFFORD )
+ {
+ EmitPrivateSound( "BuyPreset.CantBuy" );
+ }
+ else if ( overallResult == BUY_ALREADY_HAVE )
+ {
+ EmitPrivateSound( "BuyPreset.AlreadyBought" );
+ }
+ else if ( overallResult == BUY_BOUGHT )
+ {
+ g_iAutoBuyPurchases++;
+ }
+}
+
+BuyResult_e CCSPlayer::CombineBuyResults( BuyResult_e prevResult, BuyResult_e newResult )
+{
+ if ( newResult == BUY_BOUGHT )
+ {
+ prevResult = BUY_BOUGHT;
+ }
+ else if ( prevResult != BUY_BOUGHT &&
+ (newResult == BUY_CANT_AFFORD || newResult == BUY_INVALID_ITEM || newResult == BUY_PLAYER_CANT_BUY ) )
+ {
+ prevResult = BUY_CANT_AFFORD;
+ }
+
+ return prevResult;
+}
+
+//==============================================
+//PostAutoBuyCommandProcessing
+//==============================================
+void CCSPlayer::PostAutoBuyCommandProcessing(const AutoBuyInfoStruct *commandInfo, bool &boughtPrimary, bool &boughtSecondary)
+{
+ if (commandInfo == NULL)
+ {
+ return;
+ }
+
+ CBaseCombatWeapon *pPrimary = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+ CBaseCombatWeapon *pSecondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+
+ if ((pPrimary != NULL) && (stricmp(pPrimary->GetClassname(), commandInfo->m_classname) == 0))
+ {
+ // I just bought the gun I was trying to buy.
+ boughtPrimary = true;
+ }
+ else if ((pPrimary == NULL) && ((commandInfo->m_class & AUTOBUYCLASS_SHIELD) == AUTOBUYCLASS_SHIELD) && HasShield())
+ {
+ // the shield is a primary weapon even though it isn't a "real" weapon.
+ boughtPrimary = true;
+ }
+ else if ((pSecondary != NULL) && (stricmp(pSecondary->GetClassname(), commandInfo->m_classname) == 0))
+ {
+ // I just bought the pistol I was trying to buy.
+ boughtSecondary = true;
+ }
+}
+
+bool CCSPlayer::ShouldExecuteAutoBuyCommand(const AutoBuyInfoStruct *commandInfo, bool boughtPrimary, bool boughtSecondary)
+{
+ if (commandInfo == NULL)
+ {
+ return false;
+ }
+
+ if ((boughtPrimary) && ((commandInfo->m_class & AUTOBUYCLASS_PRIMARY) != 0) && ((commandInfo->m_class & AUTOBUYCLASS_AMMO) == 0))
+ {
+ // this is a primary weapon and we already have one.
+ return false;
+ }
+
+ if ((boughtSecondary) && ((commandInfo->m_class & AUTOBUYCLASS_SECONDARY) != 0) && ((commandInfo->m_class & AUTOBUYCLASS_AMMO) == 0))
+ {
+ // this is a secondary weapon and we already have one.
+ return false;
+ }
+
+ if( commandInfo->m_class & AUTOBUYCLASS_ARMOR && ArmorValue() >= 100 )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+AutoBuyInfoStruct *CCSPlayer::GetAutoBuyCommandInfo(const char *command)
+{
+ int i = 0;
+ AutoBuyInfoStruct *ret = NULL;
+ AutoBuyInfoStruct *temp = &(g_autoBuyInfo[i]);
+
+ // loop through all the commands till we find the one that matches.
+ while ((ret == NULL) && (temp->m_class != (AutoBuyClassType)0))
+ {
+ temp = &(g_autoBuyInfo[i]);
+ ++i;
+
+ if (stricmp(temp->m_command, command) == 0)
+ {
+ ret = temp;
+ }
+ }
+
+ return ret;
+}
+
+//==============================================
+//PostAutoBuyCommandProcessing
+//- reorders the tokens in autobuyString based on the order of tokens in the priorityString.
+//==============================================
+void CCSPlayer::PrioritizeAutoBuyString(char *autobuyString, const char *priorityString)
+{
+ char newString[256];
+ int newStringPos = 0;
+ char priorityToken[32];
+
+ if ((priorityString == NULL) || (autobuyString == NULL))
+ {
+ return;
+ }
+
+ const char *priorityChar = priorityString;
+
+ while (*priorityChar != 0)
+ {
+ int i = 0;
+
+ // get the next token from the priority string.
+ while ((*priorityChar != 0) && (*priorityChar != ' '))
+ {
+ priorityToken[i] = *priorityChar;
+ ++i;
+ ++priorityChar;
+ }
+ priorityToken[i] = 0;
+
+ // skip spaces
+ while (*priorityChar == ' ')
+ {
+ ++priorityChar;
+ }
+
+ if (strlen(priorityToken) == 0)
+ {
+ continue;
+ }
+
+ // see if the priority token is in the autobuy string.
+ // if it is, copy that token to the new string and blank out
+ // that token in the autobuy string.
+ char *autoBuyPosition = strstr(autobuyString, priorityToken);
+ if (autoBuyPosition != NULL)
+ {
+ while ((*autoBuyPosition != 0) && (*autoBuyPosition != ' '))
+ {
+ newString[newStringPos] = *autoBuyPosition;
+ *autoBuyPosition = ' ';
+ ++newStringPos;
+ ++autoBuyPosition;
+ }
+
+ newString[newStringPos++] = ' ';
+ }
+ }
+
+ // now just copy anything left in the autobuyString to the new string in the order it's in already.
+ char *autobuyPosition = autobuyString;
+ while (*autobuyPosition != 0)
+ {
+ // skip spaces
+ while (*autobuyPosition == ' ')
+ {
+ ++autobuyPosition;
+ }
+
+ // copy the token over to the new string.
+ while ((*autobuyPosition != 0) && (*autobuyPosition != ' '))
+ {
+ newString[newStringPos] = *autobuyPosition;
+ ++newStringPos;
+ ++autobuyPosition;
+ }
+
+ // add a space at the end.
+ newString[newStringPos++] = ' ';
+ }
+
+ // terminate the string. Trailing spaces shouldn't matter.
+ newString[newStringPos] = 0;
+
+ Q_snprintf(autobuyString, sizeof(autobuyString), "%s", newString);
+}
+
+
+//==============================================================
+// ReBuy
+// system for attempting to buy the weapons you had last round
+//==============================================================
+static void Rebuy( void )
+{
+ CCSPlayer *player = ToCSPlayer( UTIL_GetCommandClient() );
+
+ if ( player )
+ player->Rebuy();
+}
+static ConCommand rebuy( "rebuy", Rebuy, "Attempt to repurchase items with the order listed in cl_rebuy" );
+
+void CCSPlayer::BuildRebuyStruct()
+{
+ if (m_bIsInRebuy)
+ {
+ // if we are in the middle of a rebuy, we don't want to update the buy struct.
+ return;
+ }
+
+ CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+ CBaseCombatWeapon *secondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+
+ // do the primary weapon/ammo stuff.
+ if (primary == NULL)
+ {
+ // count a shieldgun as a primary.
+ if (HasShield())
+ {
+ //m_rebuyStruct.m_primaryWeapon = WEAPON_SHIELDGUN;
+ Q_strncpy( m_rebuyStruct.m_szPrimaryWeapon, "shield", sizeof(m_rebuyStruct.m_szPrimaryWeapon) );
+ m_rebuyStruct.m_primaryAmmo = 0; // shields don't have ammo.
+ }
+ else
+ {
+
+ m_rebuyStruct.m_szPrimaryWeapon[0] = 0; // if we don't have a shield and we don't have a primary weapon, we got nuthin.
+ m_rebuyStruct.m_primaryAmmo = 0; // can't have ammo if we don't have a gun right?
+ }
+ }
+ else
+ {
+ //strip off the "weapon_"
+
+ const char *wpnName = primary->GetClassname();
+
+ Q_strncpy( m_rebuyStruct.m_szPrimaryWeapon, wpnName + 7, sizeof(m_rebuyStruct.m_szPrimaryWeapon) );
+
+ if( primary->GetPrimaryAmmoType() != -1 )
+ {
+ m_rebuyStruct.m_primaryAmmo = GetAmmoCount( primary->GetPrimaryAmmoType() );
+ }
+ }
+
+ // do the secondary weapon/ammo stuff.
+ if (secondary == NULL)
+ {
+ m_rebuyStruct.m_szSecondaryWeapon[0] = 0;
+ m_rebuyStruct.m_secondaryAmmo = 0; // can't have ammo if we don't have a gun right?
+ }
+ else
+ {
+ const char *wpnName = secondary->GetClassname();
+
+ Q_strncpy( m_rebuyStruct.m_szSecondaryWeapon, wpnName + 7, sizeof(m_rebuyStruct.m_szSecondaryWeapon) );
+
+ if( secondary->GetPrimaryAmmoType() != -1 )
+ {
+ m_rebuyStruct.m_secondaryAmmo = GetAmmoCount( secondary->GetPrimaryAmmoType() );
+ }
+ }
+
+ CBaseCombatWeapon *pGrenade;
+
+ //MATTTODO: right now you can't buy more than one grenade. make it so you can
+ //buy more and query the number you have.
+ // HE Grenade
+ pGrenade = Weapon_OwnsThisType( "weapon_hegrenade" );
+ if ( pGrenade && pGrenade->GetPrimaryAmmoType() != -1 )
+ {
+ m_rebuyStruct.m_heGrenade = GetAmmoCount(pGrenade->GetPrimaryAmmoType());
+ }
+ else
+ m_rebuyStruct.m_heGrenade = 0;
+
+
+ // flashbang
+ pGrenade = Weapon_OwnsThisType( "weapon_flashbang" );
+ if ( pGrenade && pGrenade->GetPrimaryAmmoType() != -1 )
+ {
+ m_rebuyStruct.m_flashbang = GetAmmoCount(pGrenade->GetPrimaryAmmoType());
+ }
+ else
+ m_rebuyStruct.m_flashbang = 0;
+
+ // smokegrenade
+ pGrenade = Weapon_OwnsThisType( "weapon_smokegrenade" );
+ if ( pGrenade /*&& pGrenade->GetPrimaryAmmoType() != -1*/ )
+ {
+ m_rebuyStruct.m_smokeGrenade = 1; //GetAmmoCount(pGrenade->GetPrimaryAmmoType());
+ }
+ else
+ m_rebuyStruct.m_smokeGrenade = 0;
+
+ // defuser
+ m_rebuyStruct.m_defuser = HasDefuser();
+
+ // night vision
+ m_rebuyStruct.m_nightVision = m_bHasNightVision.Get(); //cast to avoid strange compiler warning
+
+ // check for armor.
+ m_rebuyStruct.m_armor = ( m_bHasHelmet ? 2 : ( ArmorValue() > 0 ? 1 : 0 ) );
+}
+
+void CCSPlayer::Rebuy( void )
+{
+ if ( !IsInBuyZone() )
+ {
+ EmitPrivateSound( "BuyPreset.CantBuy" );
+ return;
+ }
+
+ const char *rebuyString = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_rebuy" );
+ if ( !rebuyString || !*rebuyString )
+ {
+ EmitPrivateSound( "BuyPreset.AlreadyBought" );
+ return;
+ }
+
+ m_bIsInRebuy = true;
+ BuyResult_e overallResult = BUY_ALREADY_HAVE;
+
+ char token[256];
+ rebuyString = engine->ParseFile( rebuyString, token, sizeof( token ) );
+
+ while (rebuyString != NULL)
+ {
+ BuyResult_e result = BUY_ALREADY_HAVE;
+
+ if (!Q_strncmp(token, "PrimaryWeapon", 14))
+ {
+ result = RebuyPrimaryWeapon();
+ }
+ else if (!Q_strncmp(token, "PrimaryAmmo", 12))
+ {
+ result = RebuyPrimaryAmmo();
+ }
+ else if (!Q_strncmp(token, "SecondaryWeapon", 16))
+ {
+ result = RebuySecondaryWeapon();
+ }
+ else if (!Q_strncmp(token, "SecondaryAmmo", 14))
+ {
+ result = RebuySecondaryAmmo();
+ }
+ else if (!Q_strncmp(token, "HEGrenade", 10))
+ {
+ result = RebuyHEGrenade();
+ }
+ else if (!Q_strncmp(token, "Flashbang", 10))
+ {
+ result = RebuyFlashbang();
+ }
+ else if (!Q_strncmp(token, "SmokeGrenade", 13))
+ {
+ result = RebuySmokeGrenade();
+ }
+ else if (!Q_strncmp(token, "Defuser", 8))
+ {
+ result = RebuyDefuser();
+ }
+ else if (!Q_strncmp(token, "NightVision", 12))
+ {
+ result = RebuyNightVision();
+ }
+ else if (!Q_strncmp(token, "Armor", 6))
+ {
+ result = RebuyArmor();
+ }
+
+ overallResult = CombineBuyResults( overallResult, result );
+
+ rebuyString = engine->ParseFile( rebuyString, token, sizeof( token ) );
+ }
+
+ m_bIsInRebuy = false;
+
+ // after we're done buying, the user is done with their equipment purchasing experience.
+ // so we are effectively out of the buy zone.
+// if (TheTutor != NULL)
+// {
+// TheTutor->OnEvent(EVENT_PLAYER_LEFT_BUY_ZONE);
+// }
+
+ m_bAutoReload = true;
+
+ if ( overallResult == BUY_CANT_AFFORD )
+ {
+ EmitPrivateSound( "BuyPreset.CantBuy" );
+ }
+ else if ( overallResult == BUY_ALREADY_HAVE )
+ {
+ EmitPrivateSound( "BuyPreset.AlreadyBought" );
+ }
+ else if ( overallResult == BUY_BOUGHT )
+ {
+ g_iReBuyPurchases++;
+ }
+}
+
+BuyResult_e CCSPlayer::RebuyPrimaryWeapon()
+{
+ CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+ if (primary != NULL)
+ {
+ return BUY_ALREADY_HAVE; // don't drop primary weapons via rebuy - if the player picked up a different weapon, he wants to keep it.
+ }
+
+ if( strlen( m_rebuyStruct.m_szPrimaryWeapon ) > 0 )
+ return HandleCommand_Buy(m_rebuyStruct.m_szPrimaryWeapon);
+
+ return BUY_ALREADY_HAVE;
+}
+
+BuyResult_e CCSPlayer::RebuySecondaryWeapon()
+{
+ CBaseCombatWeapon *pistol = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+ if (pistol != NULL && !m_bUsingDefaultPistol)
+ {
+ return BUY_ALREADY_HAVE; // don't drop pistols via rebuy if we've bought one other than the default pistol
+ }
+
+ if( strlen( m_rebuyStruct.m_szSecondaryWeapon ) > 0 )
+ return HandleCommand_Buy(m_rebuyStruct.m_szSecondaryWeapon);
+
+ return BUY_ALREADY_HAVE;
+}
+
+BuyResult_e CCSPlayer::RebuyPrimaryAmmo()
+{
+ CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE );
+
+ if (primary == NULL)
+ {
+ return BUY_ALREADY_HAVE; // can't buy ammo when we don't even have a gun.
+ }
+
+ // Ensure that the weapon uses ammo
+ int nAmmo = primary->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ // if we had more ammo before than we have now, buy more.
+ if (m_rebuyStruct.m_primaryAmmo > GetAmmoCount( nAmmo ))
+ {
+ return HandleCommand_Buy("primammo");
+ }
+
+ return BUY_ALREADY_HAVE;
+}
+
+
+BuyResult_e CCSPlayer::RebuySecondaryAmmo()
+{
+ CBaseCombatWeapon *secondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL );
+
+ if (secondary == NULL)
+ {
+ return BUY_ALREADY_HAVE; // can't buy ammo when we don't even have a gun.
+ }
+
+ // Ensure that the weapon uses ammo
+ int nAmmo = secondary->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ if (m_rebuyStruct.m_secondaryAmmo > GetAmmoCount( nAmmo ))
+ {
+ return HandleCommand_Buy("secammo");
+ }
+
+ return BUY_ALREADY_HAVE;
+}
+
+BuyResult_e CCSPlayer::RebuyHEGrenade()
+{
+ CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_hegrenade" );
+
+ int numGrenades = 0;
+
+ if( pGrenade )
+ {
+ int nAmmo = pGrenade->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ numGrenades = GetAmmoCount( nAmmo );
+ }
+
+ BuyResult_e overallResult = BUY_ALREADY_HAVE;
+ int numToBuy = MAX( 0, m_rebuyStruct.m_heGrenade - numGrenades );
+ for (int i = 0; i < numToBuy; ++i)
+ {
+ BuyResult_e result = HandleCommand_Buy("hegrenade");
+ overallResult = CombineBuyResults( overallResult, result );
+ }
+
+ return overallResult;
+}
+
+BuyResult_e CCSPlayer::RebuyFlashbang()
+{
+ CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_flashbang" );
+
+ int numGrenades = 0;
+
+ if( pGrenade )
+ {
+ int nAmmo = pGrenade->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+ numGrenades = GetAmmoCount( nAmmo );
+
+ }
+
+ BuyResult_e overallResult = BUY_ALREADY_HAVE;
+ int numToBuy = MAX( 0, m_rebuyStruct.m_flashbang - numGrenades );
+ for (int i = 0; i < numToBuy; ++i)
+ {
+ BuyResult_e result = HandleCommand_Buy("flashbang");
+ overallResult = CombineBuyResults( overallResult, result );
+ }
+
+ return overallResult;
+}
+
+BuyResult_e CCSPlayer::RebuySmokeGrenade()
+{
+ CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_smokegrenade" );
+
+ int numGrenades = 0;
+
+ if( pGrenade )
+ {
+ int nAmmo = pGrenade->GetPrimaryAmmoType();
+ if ( nAmmo == -1 )
+ {
+ return BUY_ALREADY_HAVE;
+ }
+
+ numGrenades = GetAmmoCount( nAmmo );
+ }
+
+ BuyResult_e overallResult = BUY_ALREADY_HAVE;
+ int numToBuy = MAX( 0, m_rebuyStruct.m_smokeGrenade - numGrenades );
+ for (int i = 0; i < numToBuy; ++i)
+ {
+ BuyResult_e result = HandleCommand_Buy("smokegrenade");
+ overallResult = CombineBuyResults( overallResult, result );
+ }
+
+ return overallResult;
+}
+
+BuyResult_e CCSPlayer::RebuyDefuser()
+{
+ //If we don't have a defuser, and we want one, buy it!
+ if( !HasDefuser() && m_rebuyStruct.m_defuser )
+ {
+ return HandleCommand_Buy("defuser");
+ }
+
+ return BUY_ALREADY_HAVE;
+}
+
+BuyResult_e CCSPlayer::RebuyNightVision()
+{
+ //if we don't have night vision and we want one, buy it!
+ if( !m_bHasNightVision && m_rebuyStruct.m_nightVision )
+ {
+ return HandleCommand_Buy("nvgs");
+ }
+
+ return BUY_ALREADY_HAVE;
+}
+
+BuyResult_e CCSPlayer::RebuyArmor()
+{
+ if (m_rebuyStruct.m_armor > 0 )
+ {
+ int armor = 0;
+
+ if( m_bHasHelmet )
+ armor = 2;
+ else if( ArmorValue() > 0 )
+ armor = 1;
+
+ if( armor < m_rebuyStruct.m_armor )
+ {
+ if (m_rebuyStruct.m_armor == 1)
+ {
+ return HandleCommand_Buy("vest");
+ }
+ else
+ {
+ return HandleCommand_Buy("vesthelm");
+ }
+ }
+
+ }
+
+ return BUY_ALREADY_HAVE;
+}
+
+bool CCSPlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps )
+{
+ CWeaponCSBase *pCSWepaon = dynamic_cast<CWeaponCSBase*>(pEntity);
+
+ if( pCSWepaon )
+ {
+ // we can't USE dropped weapons
+ return true;
+ }
+
+ CBaseCSGrenadeProjectile *pGrenade = dynamic_cast<CBaseCSGrenadeProjectile*>(pEntity);
+ if ( pGrenade )
+ {
+ // we can't USE thrown grenades
+ }
+
+ return BaseClass::IsUseableEntity( pEntity, requiredCaps );
+}
+
+CBaseEntity *CCSPlayer::FindUseEntity()
+{
+ CBaseEntity *entity = NULL;
+
+ // Check to see if the bomb is close enough to use before attempting to use anything else.
+
+ if ( CSGameRules()->IsBombDefuseMap() && GetTeamNumber() == TEAM_CT )
+ {
+ // This is done separately since there might be something blocking our LOS to it
+ // but we might want to use it anyway if it's close enough. This should eliminate
+ // the vast majority of bomb placement exploits (places where the bomb can be planted
+ // but can't be "used". This also mimics goldsrc cstrike behavior.
+ CBaseEntity *bomb = gEntList.FindEntityByClassname( NULL, PLANTED_C4_CLASSNAME );
+ if (bomb != NULL)
+ {
+ Vector bombPos = bomb->GetAbsOrigin();
+ Vector vecLOS = EyePosition() - bombPos;
+
+ if (vecLOS.LengthSqr() < (96*96)) // 64 is the distance in Goldsrc. However since Goldsrc did distance from the player's origin and we're doing distance from the player's eye, make the radius a bit bigger.
+ {
+ // bomb is close enough, now make sure the player is facing the bomb.
+ Vector forward;
+ AngleVectors(EyeAngles(), &forward, NULL, NULL);
+
+ vecLOS.NormalizeInPlace();
+
+ float flDot = DotProduct(forward, vecLOS);
+ if (flDot < -0.7) // 0.7 taken from Goldsrc, +/- ~45 degrees
+ {
+ entity = bomb;
+ }
+ }
+ }
+ }
+
+ if ( entity == NULL )
+ {
+ entity = BaseClass::FindUseEntity();
+ }
+
+ return entity;
+}
+
+void CCSPlayer::StockPlayerAmmo( CBaseCombatWeapon *pNewWeapon )
+{
+ CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase * >( pNewWeapon );
+
+ if ( pWeapon )
+ {
+ if ( pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE )
+ return;
+
+ int nAmmo = pWeapon->GetPrimaryAmmoType();
+
+ if ( nAmmo != -1 )
+ {
+ GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName );
+ pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
+ }
+
+ return;
+ }
+
+ pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE ));
+
+ if ( pWeapon )
+ {
+ int nAmmo = pWeapon->GetPrimaryAmmoType();
+
+ if ( nAmmo != -1 )
+ {
+ GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName );
+ pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
+ }
+ }
+
+ pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL ));
+
+ if ( pWeapon )
+ {
+ int nAmmo = pWeapon->GetPrimaryAmmoType();
+
+ if ( nAmmo != -1 )
+ {
+ GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName );
+ pWeapon->m_iClip1 = pWeapon->GetMaxClip1();
+ }
+ }
+}
+
+CBaseEntity *CCSPlayer::GiveNamedItem( const char *pszName, int iSubType )
+{
+ EHANDLE pent;
+
+ if ( !pszName || !pszName[0] )
+ return NULL;
+
+#ifndef CS_SHIELD_ENABLED
+ if ( !Q_stricmp( pszName, "weapon_shield" ) )
+ return NULL;
+#endif
+
+ pent = CreateEntityByName(pszName);
+ if ( pent == NULL )
+ {
+ Msg( "NULL Ent in GiveNamedItem!\n" );
+ return NULL;
+ }
+
+ pent->SetLocalOrigin( GetLocalOrigin() );
+ pent->AddSpawnFlags( SF_NORESPAWN );
+
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent );
+ if ( pWeapon )
+ {
+ if ( iSubType )
+ {
+ pWeapon->SetSubType( iSubType );
+ }
+ }
+
+ DispatchSpawn( pent );
+
+ m_bIsBeingGivenItem = true;
+ if ( pent != NULL && !(pent->IsMarkedForDeletion()) )
+ {
+ pent->Touch( this );
+ }
+ m_bIsBeingGivenItem = false;
+
+ StockPlayerAmmo( pWeapon );
+ return pent;
+}
+
+
+void CCSPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
+{
+ if ( event == PLAYERANIMEVENT_THROW_GRENADE )
+ {
+ // Grenade throwing has to synchronize exactly with the player's grenade weapon going away,
+ // and events get delayed a bit, so we let CCSPlayerAnimState pickup the change to this
+ // variable.
+ m_iThrowGrenadeCounter = (m_iThrowGrenadeCounter+1) % (1<<THROWGRENADE_COUNTER_BITS);
+ }
+ else
+ {
+ m_PlayerAnimState->DoAnimationEvent( event, nData );
+ TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CCSPlayer::FlashlightIsOn( void )
+{
+ return IsEffectActive( EF_DIMLIGHT );
+}
+
+extern ConVar flashlight;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CCSPlayer::FlashlightTurnOn( void )
+{
+ if( flashlight.GetInt() > 0 && IsAlive() )
+ {
+ AddEffects( EF_DIMLIGHT );
+ EmitSound( "Player.FlashlightOn" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CCSPlayer::FlashlightTurnOff( void )
+{
+ RemoveEffects( EF_DIMLIGHT );
+
+ if( IsAlive() )
+ {
+ EmitSound( "Player.FlashlightOff" );
+ }
+}
+
+
+//Drop the appropriate weapons:
+// Defuser if we have one
+// C4 if we have one
+// The best weapon we have, first check primary,
+// then secondary and drop the best one
+
+//=============================================================================
+// HPE_BEGIN:
+// [tj] Added a parameter so we know if it was death that caused the drop
+// [menglish] Clear all previously dropped equipment and add the c4 to the dropped equipment
+//=============================================================================
+
+void CCSPlayer::DropWeapons( bool fromDeath, bool friendlyFire )
+{
+ for ( int i = 0; i < DROPPED_COUNT; ++i )
+ {
+ m_hDroppedEquipment[i] = NULL;
+ }
+
+ CBaseCombatWeapon *pC4 = Weapon_OwnsThisType( "weapon_c4" );
+ if ( pC4 )
+ {
+ CSWeaponDrop( pC4, false, true );
+ if( fromDeath )
+ {
+ if( friendlyFire )
+ {
+ (static_cast<CC4*> (pC4))->SetDroppedFromDeath(true);
+ }
+ m_hDroppedEquipment[DROPPED_C4] = static_cast<CBaseEntity *>(pC4);
+ }
+ }
+
+ //NOTE: Function continues beyond comment block. This is just the part I touched.
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+
+ if( HasDefuser() )
+ {
+ //Drop an item_defuser
+ Vector vForward, vRight;
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL );
+
+ CBaseAnimating *pDefuser = (CBaseAnimating *)CBaseEntity::Create( "item_defuser", WorldSpaceCenter(), GetLocalAngles(), this );
+ pDefuser->ApplyAbsVelocityImpulse( vForward * 200 + vRight * random->RandomFloat( -50, 50 ) );
+
+ RemoveDefuser();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Add the newly created defuser to the dropped equipment list
+ //=============================================================================
+
+ if(fromDeath)
+ {
+ m_hDroppedEquipment[DROPPED_DEFUSE] = static_cast<CBaseEntity *>(pDefuser);
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+
+ if( HasShield() )
+ {
+ DropShield();
+ }
+ else
+ {
+ //drop the best weapon we have
+ if( !DropRifle( true ) )
+ DropPistol( true );
+ }
+
+ // drop any live grenades so they explode
+ CBaseCSGrenade *pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_hegrenade"));
+ if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) )
+ {
+ pGrenade->DropGrenade();
+ pGrenade->DecrementAmmo( this );
+ }
+ else
+ {
+ pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_flashbang"));
+ if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) )
+ {
+ pGrenade->DropGrenade();
+ pGrenade->DecrementAmmo( this );
+ }
+ else
+ {
+ pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_smokegrenade"));
+ if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) )
+ {
+ pGrenade->DropGrenade();
+ pGrenade->DecrementAmmo( this );
+ }
+ }
+ }
+
+ // drop the "best" grenade remaining
+ CBaseCombatWeapon *pWeapon = Weapon_OwnsThisType("weapon_hegrenade");
+ bool grenadeDrop = false;
+ if ( pWeapon && pWeapon->HasAmmo() )
+ {
+ grenadeDrop = CSWeaponDrop(pWeapon, false);
+ }
+ else
+ {
+ pWeapon = Weapon_OwnsThisType("weapon_flashbang");
+ if ( pWeapon && pWeapon->HasAmmo() )
+ {
+ grenadeDrop = CSWeaponDrop(pWeapon, false);
+ }
+ else
+ {
+ pWeapon = Weapon_OwnsThisType("weapon_smokegrenade");
+ if ( pWeapon && pWeapon->HasAmmo() )
+ {
+ grenadeDrop = CSWeaponDrop(pWeapon, false);
+ }
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Add whichever, if any, grenade was dropped
+ //=============================================================================
+
+ if( pWeapon && grenadeDrop )
+ {
+ m_hDroppedEquipment[DROPPED_GRENADE] = static_cast<CBaseEntity *>(pWeapon);
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put the player in the specified team
+//-----------------------------------------------------------------------------
+void CCSPlayer::ChangeTeam( int iTeamNum )
+{
+ if ( !GetGlobalTeam( iTeamNum ) )
+ {
+ Warning( "CCSPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum );
+ return;
+ }
+
+ int iOldTeam = GetTeamNumber();
+
+ // if this is our current team, just abort
+ if ( iTeamNum == iOldTeam )
+ return;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // [tj] Added a parameter so we know if it was death that caused the drop
+ // Drop Our best weapon
+ DropWeapons(false, false);
+
+ // [tj] Clear out dominations
+ RemoveNemesisRelationships();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ // Always allow a change to spectator, and don't count it as one of our team changes.
+ // We now store the old team, so if a player changes once to one team, then to spectator,
+ // they won't be able to change back to their old old team, but will still be able to join
+ // the team they initially changed to.
+ if( iTeamNum != TEAM_SPECTATOR )
+ {
+ m_bTeamChanged = true;
+ }
+ else
+ {
+ m_iOldTeam = iOldTeam;
+ }
+
+ // do the team change:
+ BaseClass::ChangeTeam( iTeamNum );
+
+ //reset class
+ m_iClass = (int)CS_CLASS_NONE;
+
+ // update client state
+
+ if ( iTeamNum == TEAM_UNASSIGNED )
+ {
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ else if ( iTeamNum == TEAM_SPECTATOR )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Removed these lines so players keep their money when switching to spectator.
+ //=============================================================================
+ //Reset money
+ //m_iAccount = 0;
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ RemoveAllItems( true );
+
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ else // active player
+ {
+ if ( iOldTeam == TEAM_SPECTATOR )
+ {
+ // If they're switching from being a spectator to ingame player
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Changed this so players either retain their existing money or,
+ // if they have less than the default, give them the default.
+ //=============================================================================
+ int startMoney = CSGameRules()->GetStartMoney();
+ if (startMoney > m_iAccount)
+ {
+ m_iAccount = startMoney;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+
+ // bots get to this state on TEAM_UNASSIGNED, yet they are marked alive. Don't kill them.
+ else if ( iOldTeam != TEAM_UNASSIGNED && !IsDead() )
+ {
+ // Kill player if switching teams while alive
+ CommitSuicide();
+ }
+
+ // Put up the class selection menu.
+ State_Transition( STATE_PICKINGCLASS );
+ }
+
+ // Initialize the player counts now that a player has switched teams
+ int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
+ CSGameRules()->InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put the player in the specified team without penalty
+//-----------------------------------------------------------------------------
+void CCSPlayer::SwitchTeam( int iTeamNum )
+{
+ if ( !GetGlobalTeam( iTeamNum ) || (iTeamNum != TEAM_CT && iTeamNum != TEAM_TERRORIST) )
+ {
+ Warning( "CCSPlayer::SwitchTeam( %d ) - invalid team index.\n", iTeamNum );
+ return;
+ }
+
+ int iOldTeam = GetTeamNumber();
+
+ // if this is our current team, just abort
+ if ( iTeamNum == iOldTeam )
+ return;
+
+ // Always allow a change to spectator, and don't count it as one of our team changes.
+ // We now store the old team, so if a player changes once to one team, then to spectator,
+ // they won't be able to change back to their old old team, but will still be able to join
+ // the team they initially changed to.
+ m_bTeamChanged = true;
+
+ // do the team change:
+ BaseClass::ChangeTeam( iTeamNum );
+
+ if( HasDefuser() )
+ {
+ RemoveDefuser();
+ }
+
+ //reset class
+ switch ( m_iClass )
+ {
+ // Terrorist -> CT
+ case CS_CLASS_PHOENIX_CONNNECTION:
+ m_iClass = (int)CS_CLASS_SEAL_TEAM_6;
+ break;
+ case CS_CLASS_L337_KREW:
+ m_iClass = (int)CS_CLASS_GSG_9;
+ break;
+ case CS_CLASS_ARCTIC_AVENGERS:
+ m_iClass = (int)CS_CLASS_SAS;
+ break;
+ case CS_CLASS_GUERILLA_WARFARE:
+ m_iClass = (int)CS_CLASS_GIGN;
+ break;
+
+ // CT -> Terrorist
+ case CS_CLASS_SEAL_TEAM_6:
+ m_iClass = (int)CS_CLASS_PHOENIX_CONNNECTION;
+ break;
+ case CS_CLASS_GSG_9:
+ m_iClass = (int)CS_CLASS_L337_KREW;
+ break;
+ case CS_CLASS_SAS:
+ m_iClass = (int)CS_CLASS_ARCTIC_AVENGERS;
+ break;
+ case CS_CLASS_GIGN:
+ m_iClass = (int)CS_CLASS_GUERILLA_WARFARE;
+ break;
+
+ case CS_CLASS_NONE:
+ default:
+ break;
+ }
+
+ // Initialize the player counts now that a player has switched teams
+ int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
+ CSGameRules()->InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT );
+}
+
+void CCSPlayer::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set )
+{
+ // this is for giving player info to the hostage response system
+ // and is as yet unused.
+ // Eventually we could give the hostage a few tidbits about this player,
+ // eg their health, what weapons they have, and the hostage could
+ // comment accordingly.
+
+ //do not append any player data to the Criteria!
+
+ //we don't know which player we should be caring about
+}
+
+static unsigned int s_BulletGroupCounter = 0;
+
+void CCSPlayer::StartNewBulletGroup()
+{
+ s_BulletGroupCounter++;
+}
+
+//=======================================================
+// Remember this amount of damage that we dealt for stats
+//=======================================================
+void CCSPlayer::RecordDamageGiven( const char *szDamageTaker, int iDamageGiven )
+{
+ FOR_EACH_LL( m_DamageGivenList, i )
+ {
+ if( Q_strncmp( szDamageTaker, m_DamageGivenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 )
+ {
+ m_DamageGivenList[i]->AddDamage( iDamageGiven, s_BulletGroupCounter );
+ return;
+ }
+ }
+
+ CDamageRecord *record = new CDamageRecord( szDamageTaker, iDamageGiven, s_BulletGroupCounter );
+ int k = m_DamageGivenList.AddToTail();
+ m_DamageGivenList[k] = record;
+}
+
+//=======================================================
+// Remember this amount of damage that we took for stats
+//=======================================================
+void CCSPlayer::RecordDamageTaken( const char *szDamageDealer, int iDamageTaken )
+{
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if( Q_strncmp( szDamageDealer, m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 )
+ {
+ m_DamageTakenList[i]->AddDamage( iDamageTaken, s_BulletGroupCounter );
+ return;
+ }
+ }
+
+ CDamageRecord *record = new CDamageRecord( szDamageDealer, iDamageTaken, s_BulletGroupCounter );
+ int k = m_DamageTakenList.AddToTail();
+ m_DamageTakenList[k] = record;
+}
+
+//=======================================================
+// Reset our damage given and taken counters
+//=======================================================
+void CCSPlayer::ResetDamageCounters()
+{
+ m_DamageGivenList.PurgeAndDeleteElements();
+ m_DamageTakenList.PurgeAndDeleteElements();
+}
+
+//=======================================================
+// Output the damage that we dealt to other players
+//=======================================================
+void CCSPlayer::OutputDamageTaken( void )
+{
+ bool bPrintHeader = true;
+ CDamageRecord *pRecord;
+ char buf[64];
+ int msg_dest = HUD_PRINTCONSOLE;
+
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if( bPrintHeader )
+ {
+ ClientPrint( this, msg_dest, "Player: %s1 - Damage Taken\n", GetPlayerName() );
+ ClientPrint( this, msg_dest, "-------------------------\n" );
+ bPrintHeader = false;
+ }
+ pRecord = m_DamageTakenList[i];
+
+ if( pRecord )
+ {
+ if (pRecord->GetNumHits() == 1)
+ {
+ Q_snprintf( buf, sizeof(buf), "%d in %d hit", pRecord->GetDamage(), pRecord->GetNumHits() );
+ }
+ else
+ {
+ Q_snprintf( buf, sizeof(buf), "%d in %d hits", pRecord->GetDamage(), pRecord->GetNumHits() );
+ }
+ ClientPrint( this, msg_dest, "Damage Taken from \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
+ }
+ }
+}
+
+//=======================================================
+// Output the damage that we took from other players
+//=======================================================
+void CCSPlayer::OutputDamageGiven( void )
+{
+ bool bPrintHeader = true;
+ CDamageRecord *pRecord;
+ char buf[64];
+ int msg_dest = HUD_PRINTCONSOLE;
+
+ FOR_EACH_LL( m_DamageGivenList, i )
+ {
+ if( bPrintHeader )
+ {
+ ClientPrint( this, msg_dest, "Player: %s1 - Damage Given\n", GetPlayerName() );
+ ClientPrint( this, msg_dest, "-------------------------\n" );
+ bPrintHeader = false;
+ }
+
+ pRecord = m_DamageGivenList[i];
+
+ if( pRecord )
+ {
+ if (pRecord->GetNumHits() == 1)
+ {
+ Q_snprintf( buf, sizeof(buf), "%d in %d hit", pRecord->GetDamage(), pRecord->GetNumHits() );
+ }
+ else
+ {
+ Q_snprintf( buf, sizeof(buf), "%d in %d hits", pRecord->GetDamage(), pRecord->GetNumHits() );
+ }
+ ClientPrint( this, msg_dest, "Damage Given to \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
+ }
+ }
+}
+
+void CCSPlayer::CreateViewModel( int index /*=0*/ )
+{
+ Assert( index >= 0 && index < MAX_VIEWMODELS );
+
+ if ( GetViewModel( index ) )
+ return;
+
+ CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" );
+ if ( vm )
+ {
+ vm->SetAbsOrigin( GetAbsOrigin() );
+ vm->SetOwner( this );
+ vm->SetIndex( index );
+ DispatchSpawn( vm );
+ vm->FollowEntity( this, false );
+ m_hViewModel.Set( index, vm );
+ }
+}
+
+bool CCSPlayer::HasC4() const
+{
+ return ( Weapon_OwnsThisType( "weapon_c4" ) != NULL );
+}
+
+int CCSPlayer::GetNextObserverSearchStartPoint( bool bReverse )
+{
+ // If we are currently watching someone who is dead, they must have died while we were watching (since
+ // a dead guy is not a valid pick to start watching). He was given his killer as an observer target
+ // when he died, so let's start by trying to observe his killer. If we fail, we'll use the normal way.
+ // And this is just the start point anyway, but we want to start the search here in case it is okay.
+ if( m_hObserverTarget && !m_hObserverTarget->IsAlive() )
+ {
+ CCSPlayer *targetPlayer = ToCSPlayer(m_hObserverTarget);
+ if( targetPlayer && targetPlayer->GetObserverTarget() )
+ return targetPlayer->GetObserverTarget()->entindex();
+ }
+
+ return BaseClass::GetNextObserverSearchStartPoint( bReverse );
+}
+
+void CCSPlayer::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force )
+{
+ BaseClass::PlayStepSound( vecOrigin, psurface, fvol, force );
+
+ if ( !sv_footsteps.GetFloat() )
+ return;
+
+ if ( !psurface )
+ return;
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_footstep" );
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ m_bMadeFootstepNoise = true;
+}
+
+
+void CCSPlayer::SelectDeathPose( const CTakeDamageInfo &info )
+{
+ MDLCACHE_CRITICAL_SECTION();
+ if ( !GetModelPtr() )
+ return;
+
+ Activity aActivity = ACT_INVALID;
+ int iDeathFrame = 0;
+
+ SelectDeathPoseActivityAndFrame( this, info, m_LastHitGroup, aActivity, iDeathFrame );
+ if ( aActivity == ACT_INVALID )
+ {
+ SetDeathPose( ACT_INVALID );
+ SetDeathPoseFrame( 0 );
+ return;
+ }
+
+ SetDeathPose( SelectWeightedSequence( aActivity ) );
+ SetDeathPoseFrame( iDeathFrame );
+}
+
+
+void CCSPlayer::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == 4001 || pEvent->event == 4002 )
+ {
+ // Ignore these for now - soon we will be playing footstep sounds based on these events
+ // that mark footfalls in the anims.
+ }
+ else
+ {
+ BaseClass::HandleAnimEvent( pEvent );
+ }
+}
+
+
+bool CCSPlayer::CanChangeName( void )
+{
+ if ( IsBot() )
+ return true;
+
+ // enforce the minimum interval
+ if ( (m_flNameChangeHistory[0] + MIN_NAME_CHANGE_INTERVAL) >= gpGlobals->curtime )
+ {
+ return false;
+ }
+
+ // enforce that we dont do more than NAME_CHANGE_HISTORY_SIZE
+ // changes within NAME_CHANGE_HISTORY_INTERVAL
+ if ( (m_flNameChangeHistory[NAME_CHANGE_HISTORY_SIZE-1] + NAME_CHANGE_HISTORY_INTERVAL) >= gpGlobals->curtime )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void CCSPlayer::ChangeName( const char *pszNewName )
+{
+ // make sure name is not too long
+ char trimmedName[MAX_PLAYER_NAME_LENGTH];
+ Q_strncpy( trimmedName, pszNewName, sizeof( trimmedName ) );
+
+ const char *pszOldName = GetPlayerName();
+
+ // send colored message to everyone
+ CReliableBroadcastRecipientFilter filter;
+ UTIL_SayText2Filter( filter, this, false, "#Cstrike_Name_Change", pszOldName, trimmedName );
+
+ // broadcast event
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" );
+ if ( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetString( "oldname", pszOldName );
+ event->SetString( "newname", trimmedName );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // change shared player name
+ SetPlayerName( trimmedName );
+
+ // tell engine to use new name
+ engine->ClientCommand( edict(), "name \"%s\"", trimmedName );
+
+ // remember time of name change
+ for ( int i=NAME_CHANGE_HISTORY_SIZE-1; i>0; i-- )
+ {
+ m_flNameChangeHistory[i] = m_flNameChangeHistory[i-1];
+ }
+
+ m_flNameChangeHistory[0] = gpGlobals->curtime; // last change
+}
+
+bool CCSPlayer::StartReplayMode( float fDelay, float fDuration, int iEntity )
+{
+ if ( !BaseClass::StartReplayMode( fDelay, fDuration, iEntity ) )
+ return false;
+
+ CSingleUserRecipientFilter filter( this );
+ filter.MakeReliable();
+
+ UserMessageBegin( filter, "KillCam" );
+ WRITE_BYTE( OBS_MODE_IN_EYE );
+
+ if ( m_hObserverTarget.Get() )
+ {
+ WRITE_BYTE( m_hObserverTarget.Get()->entindex() ); // first target
+ WRITE_BYTE( entindex() ); //second target
+ }
+ else
+ {
+ WRITE_BYTE( entindex() ); // first target
+ WRITE_BYTE( 0 ); //second target
+ }
+ MessageEnd();
+
+ ClientPrint( this, HUD_PRINTCENTER, "Kill Cam Replay" );
+
+ return true;
+}
+
+void CCSPlayer::StopReplayMode()
+{
+ BaseClass::StopReplayMode();
+
+ CSingleUserRecipientFilter filter( this );
+ filter.MakeReliable();
+
+ UserMessageBegin( filter, "KillCam" );
+ WRITE_BYTE( OBS_MODE_NONE );
+ WRITE_BYTE( 0 );
+ WRITE_BYTE( 0 );
+ MessageEnd();
+}
+
+void CCSPlayer::PlayUseDenySound()
+{
+ // Don't do a sound here because it can mute your footsteps giving you an advantage.
+ // The CS:S content for this sound is silent anyways.
+ //EmitSound( "Player.UseDeny" );
+}
+
+//=============================================================================
+// HPE_BEGIN:
+//=============================================================================
+
+// [menglish, tj] This is where we reset all the per-round information for achievements for this player
+void CCSPlayer::ResetRoundBasedAchievementVariables()
+{
+ m_KillingSpreeStartTime = -1;
+
+ int numCTPlayers = 0, numTPlayers = 0;
+ for (int i = 0; i < g_Teams.Count(); i++ )
+ {
+ if(g_Teams[i])
+ {
+ if ( g_Teams[i]->GetTeamNumber() == TEAM_CT )
+ numCTPlayers = g_Teams[i]->GetNumPlayers();
+ else if(g_Teams[i]->GetTeamNumber() == TEAM_TERRORIST)
+ numTPlayers = g_Teams[i]->GetNumPlayers();
+ }
+ }
+ m_NumEnemiesKilledThisRound = 0;
+ if(GetTeamNumber() == TEAM_CT)
+ m_NumEnemiesAtRoundStart = numTPlayers;
+ else if(GetTeamNumber() == TEAM_TERRORIST)
+ m_NumEnemiesAtRoundStart = numCTPlayers;
+
+
+ //Clear the previous owner field for currently held weapons
+ CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE ));
+ if ( pWeapon )
+ {
+ pWeapon->SetPreviousOwner(NULL);
+ }
+ pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL));
+ if ( pWeapon )
+ {
+ pWeapon->SetPreviousOwner(NULL);
+ }
+
+ //Clear list of weapons used to get kills
+ m_killWeapons.RemoveAll();
+
+ //Clear sliding window of kill times
+ m_killTimes.RemoveAll();
+
+ //clear round kills
+ m_enemyPlayersKilledThisRound.RemoveAll();
+
+ m_killsWhileBlind = 0;
+
+ m_bSurvivedHeadshotDueToHelmet = false;
+
+ m_gooseChaseStep = GC_NONE;
+ m_defuseDefenseStep = DD_NONE;
+ m_pGooseChaseDistractingPlayer = NULL;
+
+ m_bMadeFootstepNoise = false;
+
+ m_bombPickupTime = -1;
+
+ m_bMadePurchseThisRound = false;
+
+ m_bKilledDefuser = false;
+ m_bKilledRescuer = false;
+ m_maxGrenadeKills = 0;
+ m_grenadeDamageTakenThisRound = 0;
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Needed for fun-fact implementation
+ //=============================================================================
+
+ WieldingKnifeAndKilledByGun(false);
+
+ m_WeaponTypesUsed.RemoveAll();
+
+ m_bPickedUpDefuser = false;
+ m_bDefusedWithPickedUpKit = false;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+}
+
+
+/**
+ * static public CCSPlayer::GetCSWeaponIDCausingDamage()
+ *
+ * Helper function to get the ID of the weapon used to kill a player.
+ * This is slightly non-trivial because the grenade because a separate
+ * entity when thrown.
+ *
+ * Parameters:
+ * info -
+ *
+ * Returns:
+ * int -
+ */
+CSWeaponID CCSPlayer::GetWeaponIdCausingDamange( const CTakeDamageInfo &info )
+{
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker());
+ if (pAttacker == pInflictor)
+ {
+ CWeaponCSBase* pAttackerWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon());
+ if (!pAttackerWeapon)
+ return WEAPON_NONE;
+
+ return pAttackerWeapon->GetWeaponID();
+ }
+ else if (pInflictor && V_strcmp(pInflictor->GetClassname(), "hegrenade_projectile") == 0)
+ {
+ return WEAPON_HEGRENADE;
+ }
+ return WEAPON_NONE;
+}
+
+
+//=============================================================================
+// HPE_BEGIN:
+// [dwenger] adding tracking for weapon used fun fact
+//=============================================================================
+void CCSPlayer::PlayerUsedFirearm( CBaseCombatWeapon* pBaseWeapon )
+{
+ if ( pBaseWeapon )
+ {
+ CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon );
+
+ if ( pWeapon )
+ {
+ CSWeaponType weaponType = pWeapon->GetCSWpnData().m_WeaponType;
+ CSWeaponID weaponID = pWeapon->GetWeaponID();
+
+ if ( weaponType != WEAPONTYPE_KNIFE && weaponType != WEAPONTYPE_C4 && weaponType != WEAPONTYPE_GRENADE )
+ {
+ if ( m_WeaponTypesUsed.Find( weaponID ) == -1 )
+ {
+ // Add this weapon to the list of weapons used by the player
+ m_WeaponTypesUsed.AddToTail( weaponID );
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * public CCSPlayer::ProcessPlayerDeathAchievements()
+ *
+ * Do Achievement processing whenever a player is killed
+ *
+ * Parameters:
+ * pAttacker -
+ * pVictim -
+ * info -
+ */
+void CCSPlayer::ProcessPlayerDeathAchievements( CCSPlayer *pAttacker, CCSPlayer *pVictim, const CTakeDamageInfo &info )
+{
+ Assert(pVictim != NULL);
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ // all these achievements require a valid attacker on a different team
+ if ( pAttacker != NULL && pVictim != NULL && pVictim->GetTeamNumber() != pAttacker->GetTeamNumber() )
+ {
+ // get the weapon used - some of the achievements will need this data
+ CWeaponCSBase* pAttackerWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon());
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Fun-fact processing
+ //=============================================================================
+
+ CWeaponCSBase* pVictimWeapon = dynamic_cast< CWeaponCSBase* >(pVictim->GetActiveWeapon());
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ CSWeaponID attackerWeaponId = GetWeaponIdCausingDamange(info);
+
+ if (pVictim->m_bIsDefusing)
+ {
+ pAttacker->AwardAchievement(CSKilledDefuser);
+ pAttacker->m_bKilledDefuser = true;
+
+ if (attackerWeaponId == WEAPON_HEGRENADE)
+ {
+ pAttacker->AwardAchievement(CSKilledDefuserWithGrenade);
+ }
+ }
+
+ // [pfreese] Achievement check for attacker killing player while reloading
+ if (pVictim->IsReloading())
+ {
+ pAttacker->AwardAchievement(CSKillEnemyReloading);
+ }
+
+ if (pVictim->IsRescuing())
+ {
+ // Ensure the killer did not injure any hostages
+ if ( !pAttacker->InjuredAHostage() && pVictim->GetNumFollowers() == g_Hostages.Count() )
+ {
+ pAttacker->AwardAchievement(CSKilledRescuer);
+ pAttacker->m_bKilledRescuer = true;
+ }
+ }
+
+ // [menglish] Achievement check for doing 95% or more damage to a player and having another player kill them
+ FOR_EACH_LL( pVictim->m_DamageTakenList, i )
+ {
+ if( pVictim->m_DamageTakenList[i]->GetDamage() >= pVictim->GetMaxHealth() - AchievementConsts::DamageNoKill_MaxHealthLeftOnKill &&
+ Q_strncmp( pAttacker->GetPlayerName(), pVictim->m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) != 0 )
+ {
+ //Now find the player who did that amount of damage
+ for ( int j = 1; j <= MAX_PLAYERS; j++ )
+ {
+ CBasePlayer *pPlayerIter = UTIL_PlayerByIndex( j );
+
+ if ( pPlayerIter && Q_strncmp( pPlayerIter->GetPlayerName(), pVictim->m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 &&
+ pPlayerIter->GetTeamNumber() != pVictim->GetTeamNumber() )
+ {
+ ToCSPlayer(pPlayerIter)->AwardAchievement(CSDamageNoKill);
+ break;
+ }
+ }
+ }
+ }
+
+ pAttacker->m_NumEnemiesKilledThisRound++;
+
+ //store a list of kill times for spree tracking
+ pAttacker->m_killTimes.AddToTail(gpGlobals->curtime);
+
+ //Add the victim to the list of players killed this round
+ pAttacker->m_enemyPlayersKilledThisRound.AddToTail(pVictim);
+
+ //Calculate Avenging for all players the victim has killed
+ for ( int avengedIndex = 0; avengedIndex < pVictim->m_enemyPlayersKilledThisRound.Count(); avengedIndex++ )
+ {
+ CCSPlayer* avengedPlayer = pVictim->m_enemyPlayersKilledThisRound[avengedIndex];
+
+ if (avengedPlayer)
+ {
+ //Make sure you are avenging someone on your own team (This is the expected flow. Just here to avoid edge cases like team-switching).
+ if (pAttacker->GetTeamNumber() == avengedPlayer->GetTeamNumber())
+ {
+ CCS_GameStats.Event_PlayerAvengedTeammate(pAttacker, pVictim->m_enemyPlayersKilledThisRound[avengedIndex]);
+ }
+ }
+ }
+
+
+
+ //remove elements older than a certain time
+ while (pAttacker->m_killTimes.Count() > 0 && pAttacker->m_killTimes[0] + AchievementConsts::KillingSpree_WindowTime < gpGlobals->curtime)
+ {
+ pAttacker->m_killTimes.Remove(0);
+ }
+
+ //If we killed enough players in the time window, award the achievement
+ if (pAttacker->m_killTimes.Count() >= AchievementConsts::KillingSpree_Kills)
+ {
+ pAttacker->m_KillingSpreeStartTime = gpGlobals->curtime;
+ pAttacker->AwardAchievement(CSKillingSpree);
+ }
+
+ // Did the attacker just kill someone on a killing spree?
+ if (pVictim->m_KillingSpreeStartTime >= 0 && pVictim->m_KillingSpreeStartTime - gpGlobals->curtime <= AchievementConsts::KillingSpreeEnder_TimeWindow)
+ {
+ pAttacker->AwardAchievement(CSKillingSpreeEnder);
+ }
+
+ //Check the "killed someone with their own weapon" achievement
+ if (pAttackerWeapon && pAttackerWeapon->GetPreviousOwner() == pVictim)
+ {
+ pAttacker->AwardAchievement(CSKillEnemyWithFormerGun);
+ }
+
+ //If this player has killed the entire team award him the achievement
+ if (pAttacker->m_NumEnemiesKilledThisRound == pAttacker->m_NumEnemiesAtRoundStart && pAttacker->m_NumEnemiesKilledThisRound >= AchievementConsts::KillEnemyTeam_MinKills)
+ {
+ pAttacker->AwardAchievement(CSKillEnemyTeam);
+ }
+
+ //If this is a posthumous kill award the achievement
+ if (!pAttacker->IsAlive() && attackerWeaponId == WEAPON_HEGRENADE)
+ {
+ CCS_GameStats.IncrementStat(pAttacker, CSSTAT_GRENADE_POSTHUMOUSKILLS, 1);
+ ToCSPlayer(pAttacker)->AwardAchievement(CSPosthumousGrenadeKill);
+ }
+
+ if (pAttacker->GetActiveWeapon() && pAttacker->GetActiveWeapon()->Clip1() == 0 && pAttackerWeapon && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_SNIPER_RIFLE)
+ {
+ if (pInflictor == pAttacker)
+ {
+ pAttacker->AwardAchievement(CSKillEnemyLastBullet);
+ CCS_GameStats.IncrementStat(pAttacker, CSSTAT_KILLS_WITH_LAST_ROUND, 1);
+ }
+ }
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Fun-fact processing
+ //=============================================================================
+
+ if (pVictimWeapon && pVictimWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_KNIFE && pAttackerWeapon &&
+ pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_KNIFE && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_C4 && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_GRENADE)
+ {
+ // Victim was wielding knife when killed by a gun
+ pVictim->WieldingKnifeAndKilledByGun(true);
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ //see if this is a unique weapon
+ if (attackerWeaponId != WEAPON_NONE)
+ {
+ if (pAttacker->m_killWeapons.Find(attackerWeaponId) == -1)
+ {
+ pAttacker->m_killWeapons.AddToTail(attackerWeaponId);
+ if (pAttacker->m_killWeapons.Count() >= AchievementConsts::KillsWithMultipleGuns_MinWeapons)
+ {
+ pAttacker->AwardAchievement(CSKillsWithMultipleGuns);
+ }
+ }
+ }
+
+ //Check for kills while blind
+ if (pAttacker->IsBlindForAchievement())
+ {
+ //if this is from a different blinding, restart the kill counter and set the time
+ if (pAttacker->m_blindStartTime != pAttacker->m_firstKillBlindStartTime)
+ {
+ pAttacker->m_killsWhileBlind = 0;
+ pAttacker->m_firstKillBlindStartTime = pAttacker->m_blindStartTime;
+ }
+
+ ++pAttacker->m_killsWhileBlind;
+ if (pAttacker->m_killsWhileBlind >= AchievementConsts::KillEnemiesWhileBlind_Kills)
+ {
+ pAttacker->AwardAchievement(CSKillEnemiesWhileBlind);
+ }
+
+ if (pAttacker->m_killsWhileBlind >= AchievementConsts::KillEnemiesWhileBlindHard_Kills)
+ {
+ pAttacker->AwardAchievement(CSKillEnemiesWhileBlindHard);
+ }
+ }
+
+ //Check sniper killing achievements
+ bool victimZoomed = ( pVictim->GetFOV() != pVictim->GetDefaultFOV() );
+ bool attackerZoomed = ( pAttacker->GetFOV() != pAttacker->GetDefaultFOV() );
+ bool attackerUsedSniperRifle = pAttackerWeapon && pAttackerWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SNIPER_RIFLE && pInflictor == pAttacker;
+ if (victimZoomed && attackerUsedSniperRifle)
+ {
+ pAttacker->AwardAchievement(CSKillSniperWithSniper);
+ }
+
+ if (attackerWeaponId == WEAPON_KNIFE && victimZoomed)
+ {
+ pAttacker->AwardAchievement(CSKillSniperWithKnife);
+ }
+ if (attackerUsedSniperRifle && !attackerZoomed)
+ {
+ pAttacker->AwardAchievement(CSHipShot);
+ }
+
+ //Kill a player at low health
+ if (pAttacker->IsAlive() && pAttacker->GetHealth() <= AchievementConsts::KillWhenAtLowHealth_MaxHealth)
+ {
+ pAttacker->AwardAchievement(CSKillWhenAtLowHealth);
+ }
+
+ //Kill a player with a knife during the pistol round
+ if (CSGameRules()->IsPistolRound())
+ {
+ if (attackerWeaponId == WEAPON_KNIFE)
+ {
+ pAttacker->AwardAchievement(CSPistolRoundKnifeKill);
+ }
+ }
+
+ //[tj] Check for dual elites fight
+ CWeaponCSBase* victimWeapon = pVictim->GetActiveCSWeapon();
+
+ if (victimWeapon)
+ {
+ CSWeaponID victimWeaponID = victimWeapon->GetWeaponID();
+
+ if (attackerWeaponId == WEAPON_ELITE && victimWeaponID == WEAPON_ELITE)
+ {
+ pAttacker->AwardAchievement(CSWinDualDuel);
+ }
+ }
+
+ //[tj] See if the attacker or defender are in the air [sbodenbender] dont include ladders
+ bool attackerInAir = pAttacker->GetMoveType() != MOVETYPE_LADDER && pAttacker->GetNearestSurfaceBelow(AchievementConsts::KillInAir_MinimumHeight) == NULL;
+ bool victimInAir = pVictim->GetMoveType() != MOVETYPE_LADDER && pVictim->GetNearestSurfaceBelow(AchievementConsts::KillInAir_MinimumHeight) == NULL;
+
+ if (attackerInAir)
+ {
+ pAttacker->AwardAchievement(CSKillWhileInAir);
+ }
+ if (victimInAir)
+ {
+ pAttacker->AwardAchievement(CSKillEnemyInAir);
+ }
+ if (attackerInAir && victimInAir)
+ {
+ pAttacker->AwardAchievement(CSKillerAndEnemyInAir);
+ }
+
+ //[tj] advance to the next stage of the defuse defense achievement
+ if (pAttacker->m_defuseDefenseStep == DD_STARTED_DEFUSE)
+ {
+ pAttacker->m_defuseDefenseStep = DD_KILLED_TERRORIST;
+ }
+
+ if (pVictim->HasC4() && pVictim->GetBombPickuptime() + AchievementConsts::KillBombPickup_MaxTime > gpGlobals->curtime)
+ {
+ pAttacker->AwardAchievement(CSKillBombPickup);
+ }
+
+ }
+
+
+ //If you kill a friendly player while blind (from an enemy player), give the guy that blinded you an achievement
+ if ( pAttacker != NULL && pVictim != NULL && pVictim->GetTeamNumber() == pAttacker->GetTeamNumber() && pAttacker->IsBlind())
+ {
+ CCSPlayer* flashbangAttacker = pAttacker->GetLastFlashbangAttacker();
+ if (flashbangAttacker && pAttacker->GetTeamNumber() != flashbangAttacker->GetTeamNumber())
+ {
+ flashbangAttacker->AwardAchievement(CSCauseFriendlyFireWithFlashbang);
+ }
+ }
+
+ // do a scan to determine count of players still alive
+ int livePlayerCount = 0;
+ int teamCount[TEAM_MAXCOUNT];
+ int teamIgnoreCount[TEAM_MAXCOUNT];
+ memset(teamCount, 0, sizeof(teamCount));
+ memset(teamIgnoreCount, 0, sizeof(teamIgnoreCount));
+ CCSPlayer *pAlivePlayer = NULL;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
+ if (pPlayer)
+ {
+ int teamNum = pPlayer->GetTeamNumber();
+ if ( teamNum >= 0 )
+ {
+ ++teamCount[teamNum];
+ if (pPlayer->WasNotKilledNaturally())
+ {
+ teamIgnoreCount[teamNum]++;
+ }
+ }
+ if (pPlayer->IsAlive() && pPlayer != pVictim)
+ {
+ ++livePlayerCount;
+ pAlivePlayer = pPlayer;
+ }
+ }
+ }
+
+ // Achievement check for being the last player alive in a match
+ if (pAlivePlayer)
+ {
+ int alivePlayerTeam = pAlivePlayer->GetTeamNumber();
+ int alivePlayerOpposingTeam = alivePlayerTeam == TEAM_CT ? TEAM_TERRORIST : TEAM_CT;
+ if (livePlayerCount == 1
+ && CSGameRules()->m_iRoundWinStatus == WINNER_NONE
+ && teamCount[alivePlayerTeam] - teamIgnoreCount[alivePlayerTeam] >= AchievementConsts::LastPlayerAlive_MinPlayersOnTeam
+ && teamCount[alivePlayerOpposingTeam] - teamIgnoreCount[alivePlayerOpposingTeam] >= AchievementConsts::DefaultMinOpponentsForAchievement
+ && ( !(pAlivePlayer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) ))
+ {
+ pAlivePlayer->AwardAchievement(CSLastPlayerAlive);
+ }
+ }
+
+ // [tj] Added hook into player killed stat that happens before weapon drop
+ CCS_GameStats.Event_PlayerKilled_PreWeaponDrop(pVictim, info);
+}
+
+//[tj] traces up to maxTrace units down and returns any standable object it hits
+// (doesn't check slope for standability)
+CBaseEntity* CCSPlayer::GetNearestSurfaceBelow(float maxTrace)
+{
+ trace_t trace;
+ Ray_t ray;
+
+ Vector traceStart = this->GetAbsOrigin();
+ Vector traceEnd = traceStart;
+ traceEnd.z -= maxTrace;
+
+ Vector minExtent = this->m_Local.m_bDucked ? VEC_DUCK_HULL_MIN_SCALED( this ) : VEC_HULL_MIN_SCALED( this );
+ Vector maxExtent = this->m_Local.m_bDucked ? VEC_DUCK_HULL_MAX_SCALED( this ) : VEC_HULL_MAX_SCALED( this );
+
+ ray.Init( traceStart, traceEnd, minExtent, maxExtent );
+ UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
+
+ return trace.m_pEnt;
+}
+
+// [tj] Added a way to react to the round ending before we reset.
+// It is important to note that this happens before the bomb explodes, so a player may die
+// after this from a bomb explosion or a late kill after a defuse/detonation/rescue.
+void CCSPlayer::OnRoundEnd(int winningTeam, int reason)
+{
+ if (winningTeam == WINNER_CT || winningTeam == WINNER_TER)
+ {
+ int losingTeamId = (winningTeam == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT;
+
+ CTeam* losingTeam = GetGlobalTeam(losingTeamId);
+
+ int losingTeamPlayers = 0;
+
+ if (losingTeam)
+ {
+ losingTeamPlayers = losingTeam->GetNumPlayers();
+
+ int ignoreCount = 0;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
+ if (pPlayer)
+ {
+ int teamNum = pPlayer->GetTeamNumber();
+ if ( teamNum == losingTeamId )
+ {
+ if (pPlayer->WasNotKilledNaturally())
+ {
+ ignoreCount++;
+ }
+ }
+
+ }
+ }
+
+ losingTeamPlayers -= ignoreCount;
+ }
+
+ //Check fast round win achievement
+ if ( IsAlive() &&
+ gpGlobals->curtime - CSGameRules()->GetRoundStartTime() < AchievementConsts::FastRoundWin_Time &&
+ GetTeamNumber() == winningTeam &&
+ losingTeamPlayers >= AchievementConsts::DefaultMinOpponentsForAchievement)
+ {
+ AwardAchievement(CSFastRoundWin);
+ }
+
+ //Check goosechase achievement
+ if (IsAlive() && reason == Target_Bombed && m_gooseChaseStep == GC_STOPPED_AFTER_GETTING_SHOT && m_pGooseChaseDistractingPlayer)
+ {
+ m_pGooseChaseDistractingPlayer->AwardAchievement(CSGooseChase);
+ }
+
+ //Check Defuse Defense achievement
+ if (IsAlive() && reason == Bomb_Defused && m_defuseDefenseStep == DD_KILLED_TERRORIST)
+ {
+ AwardAchievement(CSDefuseDefense);
+ }
+
+ //Check silent win
+ if (m_NumEnemiesKilledThisRound > 0 && GetTeamNumber() == winningTeam && !m_bMadeFootstepNoise)
+ {
+ AwardAchievement(CSSilentWin);
+ }
+
+ //Process && Check "win rounds without buying" achievement
+ if (GetTeamNumber() == winningTeam && !m_bMadePurchseThisRound)
+ {
+ m_roundsWonWithoutPurchase++;
+ if (m_roundsWonWithoutPurchase > AchievementConsts::WinRoundsWithoutBuying_Rounds)
+ {
+ AwardAchievement(CSWinRoundsWithoutBuying);
+ }
+ }
+ else
+ {
+ m_roundsWonWithoutPurchase = 0;
+ }
+ }
+
+ m_lastRoundResult = reason;
+}
+
+void CCSPlayer::OnPreResetRound()
+{
+ //Check headshot survival achievement
+ if (IsAlive() && m_bSurvivedHeadshotDueToHelmet)
+ {
+ AwardAchievement(CSSurvivedHeadshotDueToHelmet);
+ }
+
+ if (IsAlive() && m_grenadeDamageTakenThisRound > AchievementConsts::SurviveGrenade_MinDamage)
+ {
+ AwardAchievement(CSSurviveGrenade);
+ }
+
+
+ //Check achievement for surviving attacks from multiple players.
+ if (IsAlive())
+ {
+ int numberOfEnemyDamagers = GetNumEnemyDamagers();
+
+ if (numberOfEnemyDamagers >= AchievementConsts::SurviveManyAttacks_NumberDamagingPlayers)
+ {
+ AwardAchievement(CSSurviveManyAttacks);
+ }
+ }
+}
+
+void CCSPlayer::OnCanceledDefuse()
+{
+ if (m_gooseChaseStep == GC_SHOT_DURING_DEFUSE)
+ {
+ m_gooseChaseStep = GC_STOPPED_AFTER_GETTING_SHOT;
+ }
+}
+
+
+void CCSPlayer::OnStartedDefuse()
+{
+ if (m_defuseDefenseStep == DD_NONE)
+ {
+ m_defuseDefenseStep = DD_STARTED_DEFUSE;
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSPlayer::AttemptToExitFreezeCam( void )
+{
+ float fEndFreezeTravel = m_flDeathTime + CS_DEATH_ANIMATION_TIME + spec_freeze_traveltime.GetFloat();
+ if ( gpGlobals->curtime < fEndFreezeTravel )
+ return;
+
+ m_bAbortFreezeCam = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether this player is dominating the specified other player
+//-----------------------------------------------------------------------------
+void CCSPlayer::SetPlayerDominated( CCSPlayer *pPlayer, bool bDominated )
+{
+ int iPlayerIndex = pPlayer->entindex();
+ m_bPlayerDominated.Set( iPlayerIndex, bDominated );
+ pPlayer->SetPlayerDominatingMe( this, bDominated );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether this player is being dominated by the other player
+//-----------------------------------------------------------------------------
+void CCSPlayer::SetPlayerDominatingMe( CCSPlayer *pPlayer, bool bDominated )
+{
+ int iPlayerIndex = pPlayer->entindex();
+ m_bPlayerDominatingMe.Set( iPlayerIndex, bDominated );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether this player is dominating the specified other player
+//-----------------------------------------------------------------------------
+bool CCSPlayer::IsPlayerDominated( int iPlayerIndex )
+{
+ return m_bPlayerDominated.Get( iPlayerIndex );
+}
+
+bool CCSPlayer::IsPlayerDominatingMe( int iPlayerIndex )
+{
+ return m_bPlayerDominatingMe.Get( iPlayerIndex );
+}
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] MVP functions
+//=============================================================================
+
+void CCSPlayer::IncrementNumMVPs( CSMvpReason_t mvpReason )
+{
+ //=============================================================================
+ // HPE_BEGIN:
+ // [Forrest] Allow MVP to be turned off for a server
+ //=============================================================================
+ if ( sv_nomvp.GetBool() )
+ {
+ Msg( "Round MVP disabled: sv_nomvp is set.\n" );
+ return;
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ m_iMVPs++;
+ CCS_GameStats.Event_MVPEarned( this );
+ IGameEvent *mvpEvent = gameeventmanager->CreateEvent( "round_mvp" );
+
+ if ( mvpEvent )
+ {
+ mvpEvent->SetInt( "userid", GetUserID() );
+ mvpEvent->SetInt( "reason", mvpReason );
+ gameeventmanager->FireEvent( mvpEvent );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the number of rounds this player has caused to be won for their team
+//-----------------------------------------------------------------------------
+void CCSPlayer::SetNumMVPs( int iNumMVP )
+{
+ m_iMVPs = iNumMVP;
+}
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of rounds this player has caused to be won for their team
+//-----------------------------------------------------------------------------
+int CCSPlayer::GetNumMVPs()
+{
+ return m_iMVPs;
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes all nemesis relationships between this player and others
+//-----------------------------------------------------------------------------
+void CCSPlayer::RemoveNemesisRelationships()
+{
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CCSPlayer *pTemp = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+ if ( pTemp && pTemp != this )
+ {
+ // set this player to be not dominating anyone else
+ SetPlayerDominated( pTemp, false );
+
+ // set no one else to be dominating this player
+ pTemp->SetPlayerDominated( this, false );
+ }
+ }
+}
+
+void CCSPlayer::CheckMaxGrenadeKills(int grenadeKills)
+{
+ if (grenadeKills > m_maxGrenadeKills)
+ {
+ m_maxGrenadeKills = grenadeKills;
+ }
+}
+
+void CCSPlayer::CommitSuicide( bool bExplode /*= false*/, bool bForce /*= false*/ )
+{
+ m_wasNotKilledNaturally = true;
+ BaseClass::CommitSuicide(bExplode, bForce);
+}
+
+void CCSPlayer::CommitSuicide( const Vector &vecForce, bool bExplode /*= false*/, bool bForce /*= false*/ )
+{
+ m_wasNotKilledNaturally = true;
+ BaseClass::CommitSuicide(vecForce, bExplode, bForce);
+}
+
+int CCSPlayer::GetNumEnemyDamagers()
+{
+ int numberOfEnemyDamagers = 0;
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ for ( int j = 1; j <= MAX_PLAYERS; j++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( j );
+
+ if ( pPlayer && V_strncmp( pPlayer->GetPlayerName(), m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 &&
+ pPlayer->GetTeamNumber() != GetTeamNumber() )
+ {
+ numberOfEnemyDamagers++;
+ }
+ }
+ }
+ return numberOfEnemyDamagers;
+}
+
+
+int CCSPlayer::GetNumEnemiesDamaged()
+{
+ int numberOfEnemiesDamaged = 0;
+ FOR_EACH_LL( m_DamageGivenList, i )
+ {
+ for ( int j = 1; j <= MAX_PLAYERS; j++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( j );
+
+ if ( pPlayer && V_strncmp( pPlayer->GetPlayerName(), m_DamageGivenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 &&
+ pPlayer->GetTeamNumber() != GetTeamNumber() )
+ {
+ numberOfEnemiesDamaged++;
+ }
+ }
+ }
+ return numberOfEnemiesDamaged;
+}
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+void UTIL_AwardMoneyToTeam( int iAmount, int iTeam, CBaseEntity *pIgnore )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i );
+
+ if ( !pPlayer )
+ continue;
+
+ if ( pPlayer->GetTeamNumber() != iTeam )
+ continue;
+
+ if ( pPlayer == pIgnore )
+ continue;
+
+ pPlayer->AddAccount( iAmount );
+ }
+}
+
diff --git a/game/server/cstrike/cs_player.h b/game/server/cstrike/cs_player.h
new file mode 100644
index 0000000..9abf96a
--- /dev/null
+++ b/game/server/cstrike/cs_player.h
@@ -0,0 +1,1116 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CS_PLAYER_H
+#define CS_PLAYER_H
+#pragma once
+
+
+#include "basemultiplayerplayer.h"
+#include "server_class.h"
+#include "cs_playeranimstate.h"
+#include "cs_shareddefs.h"
+#include "cs_autobuy.h"
+#include "utldict.h"
+
+
+
+class CWeaponCSBase;
+class CMenu;
+class CHintMessageQueue;
+class CNavArea;
+
+#include "cs_weapon_parse.h"
+
+
+void UTIL_AwardMoneyToTeam( int iAmount, int iTeam, CBaseEntity *pIgnore );
+
+#define MENU_STRING_BUFFER_SIZE 1024
+#define MENU_MSG_TEXTCHUNK_SIZE 50
+
+enum
+{
+ MIN_NAME_CHANGE_INTERVAL = 10, // minimum number of seconds between name changes
+ NAME_CHANGE_HISTORY_SIZE = 5, // number of times a player can change names in NAME_CHANGE_HISTORY_INTERVAL
+ NAME_CHANGE_HISTORY_INTERVAL = 600, // no more than NAME_CHANGE_HISTORY_SIZE name changes can be made in this many seconds
+};
+
+extern ConVar bot_mimic;
+
+
+// Function table for each player state.
+class CCSPlayerStateInfo
+{
+public:
+ CSPlayerState m_iPlayerState;
+ const char *m_pStateName;
+
+ void (CCSPlayer::*pfnEnterState)(); // Init and deinit the state.
+ void (CCSPlayer::*pfnLeaveState)();
+
+ void (CCSPlayer::*pfnPreThink)(); // Do a PreThink() in this state.
+};
+
+
+//=======================================
+//Record of either damage taken or given.
+//Contains the player name that we hurt or that hurt us,
+//and the total damage
+//=======================================
+class CDamageRecord
+{
+public:
+ CDamageRecord( const char *name, int iDamage, int iCounter )
+ {
+ Q_strncpy( m_szPlayerName, name, sizeof(m_szPlayerName) );
+ m_iDamage = iDamage;
+ m_iNumHits = 1;
+ m_iLastBulletUpdate = iCounter;
+ }
+
+ void AddDamage( int iDamage, int iCounter )
+ {
+ m_iDamage += iDamage;
+
+ if ( m_iLastBulletUpdate != iCounter )
+ m_iNumHits++;
+
+ m_iLastBulletUpdate = iCounter;
+ }
+
+ char *GetPlayerName( void ) { return m_szPlayerName; }
+ int GetDamage( void ) { return m_iDamage; }
+ int GetNumHits( void ) { return m_iNumHits; }
+
+private:
+ char m_szPlayerName[MAX_PLAYER_NAME_LENGTH];
+ int m_iDamage; //how much damage was done
+ int m_iNumHits; //how many hits
+ int m_iLastBulletUpdate; // update counter
+};
+
+// Message display history (CCSPlayer::m_iDisplayHistoryBits)
+// These bits are set when hint messages are displayed, and cleared at
+// different times, according to the DHM_xxx bitmasks that follow
+
+#define DHF_ROUND_STARTED ( 1 << 1 )
+#define DHF_HOSTAGE_SEEN_FAR ( 1 << 2 )
+#define DHF_HOSTAGE_SEEN_NEAR ( 1 << 3 )
+#define DHF_HOSTAGE_USED ( 1 << 4 )
+#define DHF_HOSTAGE_INJURED ( 1 << 5 )
+#define DHF_HOSTAGE_KILLED ( 1 << 6 )
+#define DHF_FRIEND_SEEN ( 1 << 7 )
+#define DHF_ENEMY_SEEN ( 1 << 8 )
+#define DHF_FRIEND_INJURED ( 1 << 9 )
+#define DHF_FRIEND_KILLED ( 1 << 10 )
+#define DHF_ENEMY_KILLED ( 1 << 11 )
+#define DHF_BOMB_RETRIEVED ( 1 << 12 )
+#define DHF_AMMO_EXHAUSTED ( 1 << 15 )
+#define DHF_IN_TARGET_ZONE ( 1 << 16 )
+#define DHF_IN_RESCUE_ZONE ( 1 << 17 )
+#define DHF_IN_ESCAPE_ZONE ( 1 << 18 ) // unimplemented
+#define DHF_IN_VIPSAFETY_ZONE ( 1 << 19 ) // unimplemented
+#define DHF_NIGHTVISION ( 1 << 20 )
+#define DHF_HOSTAGE_CTMOVE ( 1 << 21 )
+#define DHF_SPEC_DUCK ( 1 << 22 )
+
+// DHF_xxx bits to clear when the round restarts
+
+#define DHM_ROUND_CLEAR ( \
+ DHF_ROUND_STARTED | \
+ DHF_HOSTAGE_KILLED | \
+ DHF_FRIEND_KILLED | \
+ DHF_BOMB_RETRIEVED )
+
+
+// DHF_xxx bits to clear when the player is restored
+
+#define DHM_CONNECT_CLEAR ( \
+ DHF_HOSTAGE_SEEN_FAR | \
+ DHF_HOSTAGE_SEEN_NEAR | \
+ DHF_HOSTAGE_USED | \
+ DHF_HOSTAGE_INJURED | \
+ DHF_FRIEND_SEEN | \
+ DHF_ENEMY_SEEN | \
+ DHF_FRIEND_INJURED | \
+ DHF_ENEMY_KILLED | \
+ DHF_AMMO_EXHAUSTED | \
+ DHF_IN_TARGET_ZONE | \
+ DHF_IN_RESCUE_ZONE | \
+ DHF_IN_ESCAPE_ZONE | \
+ DHF_IN_VIPSAFETY_ZONE | \
+ DHF_HOSTAGE_CTMOVE | \
+ DHF_SPEC_DUCK )
+
+// radio messages (these must be kept in sync with actual radio) -------------------------------------
+enum RadioType
+{
+ RADIO_INVALID = 0,
+
+ RADIO_START_1, ///< radio messages between this and RADIO_START_2 and part of Radio1()
+
+ RADIO_COVER_ME,
+ RADIO_YOU_TAKE_THE_POINT,
+ RADIO_HOLD_THIS_POSITION,
+ RADIO_REGROUP_TEAM,
+ RADIO_FOLLOW_ME,
+ RADIO_TAKING_FIRE,
+
+ RADIO_START_2, ///< radio messages between this and RADIO_START_3 are part of Radio2()
+
+ RADIO_GO_GO_GO,
+ RADIO_TEAM_FALL_BACK,
+ RADIO_STICK_TOGETHER_TEAM,
+ RADIO_GET_IN_POSITION_AND_WAIT,
+ RADIO_STORM_THE_FRONT,
+ RADIO_REPORT_IN_TEAM,
+
+ RADIO_START_3, ///< radio messages above this are part of Radio3()
+
+ RADIO_AFFIRMATIVE,
+ RADIO_ENEMY_SPOTTED,
+ RADIO_NEED_BACKUP,
+ RADIO_SECTOR_CLEAR,
+ RADIO_IN_POSITION,
+ RADIO_REPORTING_IN,
+ RADIO_GET_OUT_OF_THERE,
+ RADIO_NEGATIVE,
+ RADIO_ENEMY_DOWN,
+
+ RADIO_END,
+
+ RADIO_NUM_EVENTS
+};
+
+extern const char *RadioEventName[ RADIO_NUM_EVENTS+1 ];
+
+/**
+ * Convert name to RadioType
+ */
+extern RadioType NameToRadioEvent( const char *name );
+
+enum BuyResult_e
+{
+ BUY_BOUGHT,
+ BUY_ALREADY_HAVE,
+ BUY_CANT_AFFORD,
+ BUY_PLAYER_CANT_BUY, // not in the buy zone, is the VIP, is past the timelimit, etc
+ BUY_NOT_ALLOWED, // weapon is restricted by VIP mode, team, etc
+ BUY_INVALID_ITEM,
+};
+
+//=============================================================================
+// HPE_BEGIN:
+//=============================================================================
+
+// [tj] The phases for the "Goose Chase" achievement
+enum GooseChaseAchievementStep
+{
+ GC_NONE,
+ GC_SHOT_DURING_DEFUSE,
+ GC_STOPPED_AFTER_GETTING_SHOT
+};
+
+// [tj] The phases for the "Defuse Defense" achievement
+enum DefuseDefenseAchivementStep
+{
+ DD_NONE,
+ DD_STARTED_DEFUSE,
+ DD_KILLED_TERRORIST
+};
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+
+//=============================================================================
+// >> CounterStrike player
+//=============================================================================
+class CCSPlayer : public CBaseMultiplayerPlayer, public ICSPlayerAnimStateHelpers
+{
+public:
+ DECLARE_CLASS( CCSPlayer, CBaseMultiplayerPlayer );
+ DECLARE_SERVERCLASS();
+ DECLARE_PREDICTABLE();
+ DECLARE_DATADESC();
+
+ CCSPlayer();
+ ~CCSPlayer();
+
+ static CCSPlayer *CreatePlayer( const char *className, edict_t *ed );
+ static CCSPlayer* Instance( int iEnt );
+
+ virtual void Precache();
+ virtual void Spawn();
+ virtual void InitialSpawn( void );
+
+ virtual void CheatImpulseCommands( int iImpulse );
+ virtual void PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper );
+ virtual void PostThink();
+
+ virtual int OnTakeDamage( const CTakeDamageInfo &inputInfo );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] We have a custom implementation so we can check for achievements.
+ //=============================================================================
+
+ virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ virtual void TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ virtual CBaseEntity *GiveNamedItem( const char *pszName, int iSubType = 0 );
+ virtual bool IsBeingGivenItem() const { return m_bIsBeingGivenItem; }
+
+ virtual CBaseEntity *FindUseEntity( void );
+ virtual bool IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps );
+
+ virtual void CreateViewModel( int viewmodelindex = 0 );
+ virtual void ShowViewPortPanel( const char * name, bool bShow = true, KeyValues *data = NULL );
+
+ // This passes the event to the client's and server's CPlayerAnimState.
+ void DoAnimationEvent( PlayerAnimEvent_t event, int nData = 0 );
+
+ // from CBasePlayer
+ virtual void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize );
+
+ virtual int GetNextObserverSearchStartPoint( bool bReverse );
+// In shared code.
+public:
+
+ // ICSPlayerAnimState overrides.
+ virtual CWeaponCSBase* CSAnim_GetActiveWeapon();
+ virtual bool CSAnim_CanMove();
+
+ virtual float GetPlayerMaxSpeed();
+
+ void FireBullet(
+ Vector vecSrc,
+ const QAngle &shootAngles,
+ float flDistance,
+ int iPenetration,
+ int iBulletType,
+ int iDamage,
+ float flRangeModifier,
+ CBaseEntity *pevAttacker,
+ bool bDoEffects,
+ float xSpread, float ySpread );
+
+ void KickBack(
+ float up_base,
+ float lateral_base,
+ float up_modifier,
+ float lateral_modifier,
+ float up_max,
+ float lateral_max,
+ int direction_change );
+
+ void GetBulletTypeParameters(
+ int iBulletType,
+ float &fPenetrationPower,
+ float &flPenetrationDistance );
+
+ // Returns true if the player is allowed to move.
+ bool CanMove() const;
+
+ void OnJump( float fImpulse );
+ void OnLand( float fVelocity );
+
+ bool HasC4() const; // Is this player carrying a C4 bomb?
+ bool IsVIP() const;
+
+ int GetClass( void ) const;
+
+ void MakeVIP( bool isVIP );
+
+ virtual void SetAnimation( PLAYER_ANIM playerAnim );
+ ICSPlayerAnimState *GetPlayerAnimState() { return m_PlayerAnimState; }
+
+ virtual bool StartReplayMode( float fDelay, float fDuration, int iEntity );
+ virtual void StopReplayMode();
+ virtual void PlayUseDenySound();
+
+
+public:
+
+ // Simulates a single frame of movement for a player
+ void RunPlayerMove( const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime );
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+
+ virtual void UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity );
+ virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force );
+
+ // from cbasecombatcharacter
+ void InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity );
+ void VPhysicsShadowUpdate( IPhysicsObject *pPhysics );
+
+ bool HasShield() const;
+ bool IsShieldDrawn() const;
+ void GiveShield( void );
+ void RemoveShield( void );
+ bool IsProtectedByShield( void ) const; // returns true if player has a shield and is currently hidden behind it
+
+ bool HasPrimaryWeapon( void );
+ bool HasSecondaryWeapon( void );
+
+ bool IsReloading( void ) const; // returns true if current weapon is reloading
+
+ void GiveDefaultItems();
+ void RemoveAllItems( bool removeSuit ); //overridden to remove the defuser
+
+ // Reset account, get rid of shield, etc..
+ void Reset();
+
+ void RoundRespawn( void );
+ void ObserverRoundRespawn( void );
+ void CheckTKPunishment( void );
+
+ // Add money to this player's account.
+ void AddAccount( int amount, bool bTrackChange=true, bool bItemBought=false, const char *pItemName = NULL );
+
+ void HintMessage( const char *pMessage, bool bDisplayIfDead, bool bOverrideClientSettings = false ); // Displays a hint message to the player
+ CHintMessageQueue *m_pHintMessageQueue;
+ unsigned int m_iDisplayHistoryBits;
+ bool m_bShowHints;
+ float m_flLastAttackedTeammate;
+ float m_flNextMouseoverUpdate;
+ void UpdateMouseoverHints();
+
+ // mark this player as not receiving money at the start of the next round.
+ void MarkAsNotReceivingMoneyNextRound();
+ bool DoesPlayerGetRoundStartMoney(); // self-explanitory :)
+
+ void DropC4(); // Get rid of the C4 bomb.
+
+ bool HasDefuser(); // Is this player carrying a bomb defuser?
+ void GiveDefuser(bool bPickedUp = false); // give the player a defuser
+ void RemoveDefuser(); // remove defuser from the player and remove the model attachment
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ bool PickedUpDefuser() { return m_bPickedUpDefuser; }
+ void SetDefusedWithPickedUpKit(bool bDefusedWithPickedUpKit) { m_bDefusedWithPickedUpKit = bDefusedWithPickedUpKit; }
+ bool GetDefusedWithPickedUpKit() { return m_bDefusedWithPickedUpKit; }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [sbodenbender] Need a different test for player blindness for the achievements
+ //=============================================================================
+
+ bool IsBlindForAchievement(); // more stringent than IsBlind; more accurately represents when the player can see again
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ bool IsBlind( void ) const; // return true if this player is blind (from a flashbang)
+ virtual void Blind( float holdTime, float fadeTime, float startingAlpha = 255 ); // player blinded by a flashbang
+ float m_blindUntilTime;
+ float m_blindStartTime;
+
+ void Deafen( float flDistance ); //make the player deaf / apply dsp preset to muffle sound
+
+ void ApplyDeafnessEffect(); // apply the deafness effect for a nearby explosion.
+
+ bool IsAutoFollowAllowed( void ) const; // return true if this player will allow bots to auto follow
+ void InhibitAutoFollow( float duration ); // prevent bots from auto-following for given duration
+ void AllowAutoFollow( void ); // allow bots to auto-follow immediately
+ float m_allowAutoFollowTime; // bots can auto-follow after this time
+
+ // Have this guy speak a message into his radio.
+ void Radio( const char *szRadioSound, const char *szRadioText = NULL );
+ void ConstructRadioFilter( CRecipientFilter& filter );
+
+ void EmitPrivateSound( const char *soundName ); ///< emit given sound that only we can hear
+
+ CWeaponCSBase* GetActiveCSWeapon() const;
+
+ void PreThink();
+
+ // This is the think function for the player when they first join the server and have to select a team
+ void JoiningThink();
+
+ virtual bool ClientCommand( const CCommand &args );
+
+ bool HandleCommand_JoinClass( int iClass );
+ bool HandleCommand_JoinTeam( int iTeam );
+
+ BuyResult_e HandleCommand_Buy( const char *item );
+
+ BuyResult_e HandleCommand_Buy_Internal( const char * item );
+
+ void HandleMenu_Radio1( int slot );
+ void HandleMenu_Radio2( int slot );
+ void HandleMenu_Radio3( int slot );
+
+ float m_flRadioTime;
+ int m_iRadioMessages;
+ int iRadioMenu;
+
+ void ListPlayers();
+
+ bool m_bIgnoreRadio;
+
+ // Returns one of the CS_CLASS_ enums.
+ int PlayerClass() const;
+
+ void MoveToNextIntroCamera();
+
+ // Used to be GETINTOGAME state.
+ void GetIntoGame();
+
+ CBaseEntity* EntSelectSpawnPoint();
+
+ void SetProgressBarTime( int barTime );
+ virtual void PlayerDeathThink();
+
+ void Weapon_Equip( CBaseCombatWeapon *pWeapon );
+ virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon );
+ virtual bool Weapon_CanUse( CBaseCombatWeapon *pWeapon );
+
+ void ClearFlashbangScreenFade ( void );
+ bool ShouldDoLargeFlinch( int nHitGroup, CBaseEntity *pAttacker );
+
+ void ResetStamina( void );
+ bool IsArmored( int nHitGroup );
+ void Pain( bool HasArmour );
+
+ void DeathSound( const CTakeDamageInfo &info );
+
+ bool Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon );
+
+ void ChangeTeam( int iTeamNum );
+ void SwitchTeam( int iTeamNum ); // Changes teams without penalty - used for auto team balancing
+
+ void ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set );
+
+ virtual void OnDamagedByExplosion( const CTakeDamageInfo &info );
+
+ // Called whenever this player fires a shot.
+ void NoteWeaponFired();
+ virtual bool WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const;
+
+// ------------------------------------------------------------------------------------------------ //
+// Player state management.
+// ------------------------------------------------------------------------------------------------ //
+public:
+
+ void State_Transition( CSPlayerState newState ); // Cleanup the previous state and enter a new state.
+ CSPlayerState State_Get() const; // Get the current state.
+
+
+private:
+ void State_Enter( CSPlayerState newState ); // Initialize the new state.
+ void State_Leave(); // Cleanup the previous state.
+ void State_PreThink(); // Update the current state.
+
+ // Find the state info for the specified state.
+ static CCSPlayerStateInfo* State_LookupInfo( CSPlayerState state );
+
+ // This tells us which state the player is currently in (joining, observer, dying, etc).
+ // Each state has a well-defined set of parameters that go with it (ie: observer is movetype_noclip, nonsolid,
+ // invisible, etc).
+ CNetworkVar( CSPlayerState, m_iPlayerState );
+
+ CCSPlayerStateInfo *m_pCurStateInfo; // This can be NULL if no state info is defined for m_iPlayerState.
+
+ // tells us whether or not this player gets money at the start of the next round.
+ bool m_receivesMoneyNextRound;
+
+
+ // Specific state handler functions.
+ void State_Enter_WELCOME();
+ void State_PreThink_WELCOME();
+
+ void State_Enter_PICKINGTEAM();
+ void State_Enter_PICKINGCLASS();
+
+ void State_Enter_ACTIVE();
+ void State_PreThink_ACTIVE();
+
+ void State_Enter_OBSERVER_MODE();
+ void State_PreThink_OBSERVER_MODE();
+
+ void State_Enter_DEATH_WAIT_FOR_KEY();
+ void State_PreThink_DEATH_WAIT_FOR_KEY();
+
+ void State_Enter_DEATH_ANIM();
+ void State_PreThink_DEATH_ANIM();
+
+ int FlashlightIsOn( void );
+ void FlashlightTurnOn( void );
+ void FlashlightTurnOff( void );
+
+ void UpdateAddonBits();
+ void UpdateRadar();
+
+public:
+
+ void SetDeathPose( const int &iDeathPose ) { m_iDeathPose = iDeathPose; }
+ void SetDeathPoseFrame( const int &iDeathPoseFrame ) { m_iDeathFrame = iDeathPoseFrame; }
+
+ void SelectDeathPose( const CTakeDamageInfo &info );
+
+private:
+ int m_iDeathPose;
+ int m_iDeathFrame;
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish] Freeze cam function and variable declarations
+//=============================================================================
+
+ bool m_bAbortFreezeCam;
+
+protected:
+ void AttemptToExitFreezeCam( void );
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+public:
+
+ // Predicted variables.
+ CNetworkVar( bool, m_bResumeZoom );
+ CNetworkVar( int , m_iLastZoom ); // after firing a shot, set the FOV to 90, and after showing the animation, bring the FOV back to last zoom level.
+ CNetworkVar( bool, m_bIsDefusing ); // tracks whether this player is currently defusing a bomb
+ int m_LastHitGroup; // the last body region that took damage
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] Adding two variables, keeping track of damage to the player
+ //=============================================================================
+
+ int m_LastHitBox; // the last body hitbox that took damage
+ Vector m_vLastHitLocationObjectSpace; //position where last hit occured in space of the bone associated with the hitbox
+ EHANDLE m_hDroppedEquipment[DROPPED_COUNT];
+
+ // [tj] overriding the base suicides to trash CS specific stuff
+ virtual void CommitSuicide( bool bExplode = false, bool bForce = false );
+ virtual void CommitSuicide( const Vector &vecForce, bool bExplode = false, bool bForce = false );
+
+ void WieldingKnifeAndKilledByGun( bool bState ) { m_bWieldingKnifeAndKilledByGun = bState; }
+ bool WasWieldingKnifeAndKilledByGun() { return m_bWieldingKnifeAndKilledByGun; }
+
+ // [dwenger] adding tracking for weapon used fun fact
+ void PlayerUsedFirearm( CBaseCombatWeapon* pBaseWeapon );
+ int GetNumFirearmsUsed() { return m_WeaponTypesUsed.Count(); }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ CNetworkVar( bool, m_bHasHelmet ); // Does the player have helmet armor
+ bool m_bEscaped; // Has this terrorist escaped yet?
+
+ // Other variables.
+ bool m_bIsVIP; // Are we the VIP?
+ int m_iNumSpawns; // Number of times player has spawned this round
+ int m_iOldTeam; // Keep what team they were last on so we can allow joining spec and switching back to their real team
+ bool m_bTeamChanged; // Just allow one team change per round
+ CNetworkVar( int, m_iAccount ); // How much cash this player has.
+ int m_iShouldHaveCash;
+
+ bool m_bJustKilledTeammate;
+ bool m_bPunishedForTK;
+ int m_iTeamKills;
+ float m_flLastMovement;
+ int m_iNextTimeCheck; // Next time the player can execute a "timeleft" command
+
+ float m_flNameChangeHistory[NAME_CHANGE_HISTORY_SIZE]; // index 0 = most recent change
+
+ bool CanChangeName( void ); // Checks if the player can change his name
+ void ChangeName( const char *pszNewName );
+
+ void SetClanTag( const char *pTag );
+ const char *GetClanTag( void ) const;
+
+
+ CNetworkVar( bool, m_bHasDefuser ); // Does this player have a defuser kit?
+ CNetworkVar( bool, m_bHasNightVision ); // Does this player have night vision?
+ CNetworkVar( bool, m_bNightVisionOn ); // Is the NightVision turned on ?
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ //CNetworkVar( bool, m_bPickedUpDefuser ); // Did player pick up the defuser kit as opposed to buying it?
+ //CNetworkVar( bool, m_bDefusedWithPickedUpKit); // Did player defuse the bomb with a picked-up defuse kit?
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ float m_flLastRadarUpdateTime;
+
+ // last known navigation area of player - NULL if unknown
+ CNavArea *m_lastNavArea;
+
+ // Backup copy of the menu text so the player can change this and the menu knows when to update.
+ char m_MenuStringBuffer[MENU_STRING_BUFFER_SIZE];
+
+ // When the player joins, it cycles their view between trigger_camera entities.
+ // This is the current camera, and the time that we'll switch to the next one.
+ EHANDLE m_pIntroCamera;
+ float m_fIntroCamTime;
+
+ // Set to true each frame while in a bomb zone.
+ // Reset after prediction (in PostThink).
+ CNetworkVar( bool, m_bInBombZone );
+ CNetworkVar( bool, m_bInBuyZone );
+ int m_iBombSiteIndex;
+
+ bool IsInBuyZone();
+ bool CanPlayerBuy( bool display );
+
+ CNetworkVar( bool, m_bInHostageRescueZone );
+ void RescueZoneTouch( inputdata_t &inputdata );
+
+ CNetworkVar( float, m_flStamina );
+ CNetworkVar( int, m_iDirection ); // The current lateral kicking direction; 1 = right, 0 = left
+ CNetworkVar( int, m_iShotsFired ); // number of shots fired recently
+
+ // Make sure to register changes for armor.
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_ArmorValue );
+
+ CNetworkVar( float, m_flVelocityModifier );
+
+ int m_iHostagesKilled;
+
+ void SetShieldDrawnState( bool bState );
+ void DropShield( void );
+
+ char m_szNewName [MAX_PLAYER_NAME_LENGTH]; // not empty if player requested a namechange
+ char m_szClanTag[MAX_CLAN_TAG_LENGTH];
+
+ Vector m_vecTotalBulletForce; //Accumulator for bullet force in a single frame
+
+ CNetworkVar( float, m_flFlashDuration );
+ CNetworkVar( float, m_flFlashMaxAlpha );
+
+ CNetworkVar( float, m_flProgressBarStartTime );
+ CNetworkVar( int, m_iProgressBarDuration );
+ CNetworkVar( int, m_iThrowGrenadeCounter ); // used to trigger grenade throw animations.
+
+ // Tracks our ragdoll entity.
+ CNetworkHandle( CBaseEntity, m_hRagdoll ); // networked entity handle
+
+ // Bots and hostages auto-duck during jumps
+ bool m_duckUntilOnGround;
+
+ Vector m_lastStandingPos; // used by the gamemovement code for finding ladders
+
+ void SurpressLadderChecks( const Vector& pos, const Vector& normal );
+ bool CanGrabLadder( const Vector& pos, const Vector& normal );
+
+ CNetworkVar( bool, m_bDetected );
+
+private:
+ CountdownTimer m_ladderSurpressionTimer;
+ Vector m_lastLadderNormal;
+ Vector m_lastLadderPos;
+
+protected:
+
+ void CreateRagdollEntity();
+
+ bool IsHittingShield( const Vector &vecDirection, trace_t *ptr );
+
+ void PhysObjectSleep();
+ void PhysObjectWake();
+
+ bool RunMimicCommand( CUserCmd& cmd );
+
+ bool SelectSpawnSpot( const char *pEntClassName, CBaseEntity* &pSpot );
+
+ void SetModelFromClass( void );
+ CNetworkVar( int, m_iClass ); // One of the CS_CLASS_ enums.
+
+ bool CSWeaponDrop( CBaseCombatWeapon *pWeapon, bool bDropShield = true, bool bThrow = false );
+ bool DropRifle( bool fromDeath = false );
+ bool DropPistol( bool fromDeath = false );
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Added a parameter so we know if it was death that caused the drop
+ // [menglish] New parameter to always know if this is from death and not just an enemy death
+ //=============================================================================
+
+ void DropWeapons( bool fromDeath, bool friendlyFire );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ virtual int SpawnArmorValue( void ) const { return ArmorValue(); }
+
+ BuyResult_e AttemptToBuyAmmo( int iAmmoType );
+ BuyResult_e AttemptToBuyAmmoSingle( int iAmmoType );
+ BuyResult_e AttemptToBuyVest( void );
+ BuyResult_e AttemptToBuyAssaultSuit( void );
+ BuyResult_e AttemptToBuyDefuser( void );
+ BuyResult_e AttemptToBuyNightVision( void );
+ BuyResult_e AttemptToBuyShield( void );
+
+ BuyResult_e BuyAmmo( int nSlot, bool bBlinkMoney );
+ BuyResult_e BuyGunAmmo( CBaseCombatWeapon *pWeapon, bool bBlinkMoney );
+
+ void PushawayThink();
+
+private:
+
+ ICSPlayerAnimState *m_PlayerAnimState;
+
+ // Aiming heuristics code
+ float m_flIdleTime; //Amount of time we've been motionless
+ float m_flMoveTime; //Amount of time we've been in motion
+ float m_flLastDamageTime; //Last time we took damage
+ float m_flTargetFindTime;
+
+ int m_lastDamageHealth; // Last damage given to our health
+ int m_lastDamageArmor; // Last damage given to our armor
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ bool m_bPickedUpDefuser; // Did player pick up the defuser kit as opposed to buying it?
+ bool m_bDefusedWithPickedUpKit; // Did player defuse the bomb with a picked-up defuse kit?
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ // Last usercmd we shot a bullet on.
+ int m_iLastWeaponFireUsercmd;
+
+ // Copyed from EyeAngles() so we can send it to the client.
+ CNetworkQAngle( m_angEyeAngles );
+
+ bool m_bVCollisionInitted;
+
+// AutoBuy functions.
+public:
+ void AutoBuy(); // this should take into account what the player can afford and should buy the best equipment for them.
+
+ bool IsInAutoBuy( void ) { return m_bIsInAutoBuy; }
+ bool IsInReBuy( void ) { return m_bIsInRebuy; }
+
+private:
+ bool ShouldExecuteAutoBuyCommand(const AutoBuyInfoStruct *commandInfo, bool boughtPrimary, bool boughtSecondary);
+ void PostAutoBuyCommandProcessing(const AutoBuyInfoStruct *commandInfo, bool &boughtPrimary, bool &boughtSecondary);
+ void ParseAutoBuyString(const char *string, bool &boughtPrimary, bool &boughtSecondary);
+ AutoBuyInfoStruct *GetAutoBuyCommandInfo(const char *command);
+ void PrioritizeAutoBuyString(char *autobuyString, const char *priorityString); // reorders the tokens in autobuyString based on the order of tokens in the priorityString.
+ BuyResult_e CombineBuyResults( BuyResult_e prevResult, BuyResult_e newResult );
+
+ bool m_bIsInAutoBuy;
+ bool m_bAutoReload;
+
+//ReBuy functions
+
+public:
+ void Rebuy();
+private:
+ void BuildRebuyStruct();
+
+ BuyResult_e RebuyPrimaryWeapon();
+ BuyResult_e RebuyPrimaryAmmo();
+ BuyResult_e RebuySecondaryWeapon();
+ BuyResult_e RebuySecondaryAmmo();
+ BuyResult_e RebuyHEGrenade();
+ BuyResult_e RebuyFlashbang();
+ BuyResult_e RebuySmokeGrenade();
+ BuyResult_e RebuyDefuser();
+ BuyResult_e RebuyNightVision();
+ BuyResult_e RebuyArmor();
+
+ bool m_bIsInRebuy;
+ RebuyStruct m_rebuyStruct;
+ bool m_bUsingDefaultPistol;
+
+ bool m_bIsBeingGivenItem;
+
+#ifdef CS_SHIELD_ENABLED
+ CNetworkVar( bool, m_bHasShield );
+ CNetworkVar( bool, m_bShieldDrawn );
+#endif
+
+ // This is a combination of the ADDON_ flags in cs_shareddefs.h.
+ CNetworkVar( int, m_iAddonBits );
+
+ // Clients don't know about holstered weapons, so we need to tell them the weapon type here
+ CNetworkVar( int, m_iPrimaryAddon );
+ CNetworkVar( int, m_iSecondaryAddon );
+
+//Damage record functions
+public:
+
+ static void StartNewBulletGroup(); // global function
+
+ void RecordDamageTaken( const char *szDamageDealer, int iDamageTaken );
+ void RecordDamageGiven( const char *szDamageTaker, int iDamageGiven );
+
+ void ResetDamageCounters(); //Reset all lists
+
+ void OutputDamageTaken( void );
+ void OutputDamageGiven( void );
+
+ void StockPlayerAmmo( CBaseCombatWeapon *pNewWeapon = NULL );
+
+ CUtlLinkedList< CDamageRecord *, int >& GetDamageGivenList() {return m_DamageGivenList;}
+ CUtlLinkedList< CDamageRecord *, int >& GetDamageTakenList() {return m_DamageTakenList;}
+
+private:
+ //A list of damage given
+ CUtlLinkedList< CDamageRecord *, int > m_DamageGivenList;
+
+ //A list of damage taken
+ CUtlLinkedList< CDamageRecord *, int > m_DamageTakenList;
+
+protected:
+ float m_applyDeafnessTime;
+ int m_currentDeafnessFilter;
+
+ bool m_isVIP;
+
+// Command rate limiting.
+private:
+
+ bool ShouldRunRateLimitedCommand( const CCommand &args );
+
+ // This lets us rate limit the commands the players can execute so they don't overflow things like reliable buffers.
+ CUtlDict<float,int> m_RateLimitLastCommandTimes;
+
+ CNetworkVar(int, m_cycleLatch); // Every so often, we are going to transmit our cycle to the client to correct divergence caused by PVS changes
+ CountdownTimer m_cycleLatchTimer;
+
+//=============================================================================
+// HPE_BEGIN:
+// [menglish, tj] Achievement-based addition to CS player class.
+//=============================================================================
+
+public:
+ void ResetRoundBasedAchievementVariables();
+ void OnRoundEnd(int winningTeam, int reason);
+ void OnPreResetRound();
+
+ int GetNumEnemyDamagers();
+ int GetNumEnemiesDamaged();
+ CBaseEntity* GetNearestSurfaceBelow(float maxTrace);
+
+ // Returns the % of the enemies this player killed in the round
+ int GetPercentageOfEnemyTeamKilled();
+
+ //List of times of recent kills to check for sprees
+ CUtlVector<float> m_killTimes;
+
+ //List of all players killed this round
+ CUtlVector<CHandle<CCSPlayer> > m_enemyPlayersKilledThisRound;
+
+ //List of weapons we have used to kill players with this round
+ CUtlVector<int> m_killWeapons;
+
+ int m_NumEnemiesKilledThisRound;
+ int m_NumEnemiesAtRoundStart;
+ int m_KillingSpreeStartTime;
+
+ float m_firstKillBlindStartTime; //This is the start time of the blind effect during which we got our most recent kill.
+ int m_killsWhileBlind;
+
+ bool m_bIsRescuing; // tracks whether this player is currently rescuing a hostage
+ bool m_bInjuredAHostage; // tracks whether this player injured a hostage
+ int m_iNumFollowers; // Number of hostages following this player
+ bool m_bSurvivedHeadshotDueToHelmet;
+
+ void IncrementNumFollowers() { m_iNumFollowers++; }
+ void DecrementNumFollowers() { m_iNumFollowers--; if (m_iNumFollowers < 0) m_iNumFollowers = 0; }
+ int GetNumFollowers() { return m_iNumFollowers; }
+ void SetIsRescuing(bool in_bRescuing) { m_bIsRescuing = in_bRescuing; }
+ bool IsRescuing() { return m_bIsRescuing; }
+ void SetInjuredAHostage(bool in_bInjured) { m_bInjuredAHostage = in_bInjured; }
+ bool InjuredAHostage() { return m_bInjuredAHostage; }
+ float GetBombPickuptime() { return m_bombPickupTime; }
+ void SetBombPickupTime(float time) { m_bombPickupTime = time; }
+ CCSPlayer* GetLastFlashbangAttacker() { return m_lastFlashBangAttacker; }
+ void SetLastFlashbangAttacker(CCSPlayer* attacker) { m_lastFlashBangAttacker = attacker; }
+
+ static CSWeaponID GetWeaponIdCausingDamange( const CTakeDamageInfo &info );
+ static void ProcessPlayerDeathAchievements( CCSPlayer *pAttacker, CCSPlayer *pVictim, const CTakeDamageInfo &info );
+
+ void OnCanceledDefuse();
+ void OnStartedDefuse();
+ GooseChaseAchievementStep m_gooseChaseStep;
+ DefuseDefenseAchivementStep m_defuseDefenseStep;
+ CHandle<CCSPlayer> m_pGooseChaseDistractingPlayer;
+
+ int m_lastRoundResult; //save the reason for the last round ending.
+
+ bool m_bMadeFootstepNoise;
+
+ float m_bombPickupTime;
+
+ bool m_bMadePurchseThisRound;
+
+ int m_roundsWonWithoutPurchase;
+
+ bool m_bKilledDefuser;
+ bool m_bKilledRescuer;
+ int m_maxGrenadeKills;
+
+ int m_grenadeDamageTakenThisRound;
+
+ bool GetKilledDefuser() { return m_bKilledDefuser; }
+ bool GetKilledRescuer() { return m_bKilledRescuer; }
+ int GetMaxGrenadeKills() { return m_maxGrenadeKills; }
+
+ void CheckMaxGrenadeKills(int grenadeKills);
+
+ CHandle<CCSPlayer> m_lastFlashBangAttacker;
+
+ void SetPlayerDominated( CCSPlayer *pPlayer, bool bDominated );
+ void SetPlayerDominatingMe( CCSPlayer *pPlayer, bool bDominated );
+ bool IsPlayerDominated( int iPlayerIndex );
+ bool IsPlayerDominatingMe( int iPlayerIndex );
+
+ bool m_wasNotKilledNaturally; //Set if the player is dead from a kill command or late login
+
+ bool WasNotKilledNaturally() { return m_wasNotKilledNaturally; }
+
+ //=============================================================================
+ // [menglish] MVP functions
+ //=============================================================================
+
+ void SetNumMVPs( int iNumMVP );
+ void IncrementNumMVPs( CSMvpReason_t mvpReason );
+ int GetNumMVPs();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ void RemoveNemesisRelationships();
+ void SetDeathFlags( int iDeathFlags ) { m_iDeathFlags = iDeathFlags; }
+ int GetDeathFlags() { return m_iDeathFlags; }
+
+private:
+ CNetworkArray( bool, m_bPlayerDominated, MAX_PLAYERS+1 ); // array of state per other player whether player is dominating other players
+ CNetworkArray( bool, m_bPlayerDominatingMe, MAX_PLAYERS+1 ); // array of state per other player whether other players are dominating this player
+
+ //=============================================================================
+ // HPE_BEGIN:
+ //=============================================================================
+
+ // [menglish] number of rounds this player has caused to be won for their team
+ int m_iMVPs;
+
+ // [dwenger] adding tracking for fun fact
+ bool m_bWieldingKnifeAndKilledByGun;
+
+ // [dwenger] adding tracking for which weapons this player has used in a round
+ CUtlVector<CSWeaponID> m_WeaponTypesUsed;
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ int m_iDeathFlags; // Flags holding revenge and domination info about a death
+
+//=============================================================================
+// HPE_END
+//=============================================================================
+};
+
+
+inline CSPlayerState CCSPlayer::State_Get() const
+{
+ return m_iPlayerState;
+}
+
+inline CCSPlayer *ToCSPlayer( CBaseEntity *pEntity )
+{
+ if ( !pEntity || !pEntity->IsPlayer() )
+ return NULL;
+
+ return dynamic_cast<CCSPlayer*>( pEntity );
+}
+
+inline bool CCSPlayer::IsReloading( void ) const
+{
+ CBaseCombatWeapon *gun = GetActiveWeapon();
+ if (gun == NULL)
+ return false;
+
+ return gun->m_bInReload;
+}
+
+inline bool CCSPlayer::IsProtectedByShield( void ) const
+{
+ return HasShield() && IsShieldDrawn();
+}
+
+inline bool CCSPlayer::IsBlind( void ) const
+{
+ return gpGlobals->curtime < m_blindUntilTime;
+}
+
+//=============================================================================
+// HPE_BEGIN
+// [sbodenbender] Need a different test for player blindness for the achievements
+//=============================================================================
+inline bool CCSPlayer::IsBlindForAchievement()
+{
+ return (m_blindStartTime + m_flFlashDuration) > gpGlobals->curtime;
+}
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+inline bool CCSPlayer::IsAutoFollowAllowed( void ) const
+{
+ return (gpGlobals->curtime > m_allowAutoFollowTime);
+}
+
+inline void CCSPlayer::InhibitAutoFollow( float duration )
+{
+ m_allowAutoFollowTime = gpGlobals->curtime + duration;
+}
+
+inline void CCSPlayer::AllowAutoFollow( void )
+{
+ m_allowAutoFollowTime = 0.0f;
+}
+
+inline int CCSPlayer::GetClass( void ) const
+{
+ return m_iClass;
+}
+
+inline const char *CCSPlayer::GetClanTag( void ) const
+{
+ return m_szClanTag;
+}
+
+#endif //CS_PLAYER_H
diff --git a/game/server/cstrike/cs_player_resource.cpp b/game/server/cstrike/cs_player_resource.cpp
new file mode 100644
index 0000000..ec13230
--- /dev/null
+++ b/game/server/cstrike/cs_player_resource.cpp
@@ -0,0 +1,343 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: CS's custom CPlayerResource
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "cs_player.h"
+#include "player_resource.h"
+#include "cs_simple_hostage.h"
+#include "cs_player_resource.h"
+#include "weapon_c4.h"
+#include <coordsize.h>
+#include "cs_bot_manager.h"
+#include "cs_gamerules.h"
+
+// Datatable
+IMPLEMENT_SERVERCLASS_ST(CCSPlayerResource, DT_CSPlayerResource)
+ SendPropInt( SENDINFO( m_iPlayerC4 ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iPlayerVIP ), 8, SPROP_UNSIGNED ),
+ SendPropVector( SENDINFO(m_vecC4), -1, SPROP_COORD),
+ SendPropArray3( SENDINFO_ARRAY3(m_bHostageAlive), SendPropInt( SENDINFO_ARRAY(m_bHostageAlive), 1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_isHostageFollowingSomeone), SendPropInt( SENDINFO_ARRAY(m_isHostageFollowingSomeone), 1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_iHostageEntityIDs), SendPropInt( SENDINFO_ARRAY(m_iHostageEntityIDs), -1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_iHostageY), SendPropInt( SENDINFO_ARRAY(m_iHostageY), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_iHostageX), SendPropInt( SENDINFO_ARRAY(m_iHostageX), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_iHostageZ), SendPropInt( SENDINFO_ARRAY(m_iHostageZ), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropVector( SENDINFO(m_bombsiteCenterA), -1, SPROP_COORD),
+ SendPropVector( SENDINFO(m_bombsiteCenterB), -1, SPROP_COORD),
+ SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueX), SendPropInt( SENDINFO_ARRAY(m_hostageRescueX), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueY), SendPropInt( SENDINFO_ARRAY(m_hostageRescueY), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueZ), SendPropInt( SENDINFO_ARRAY(m_hostageRescueZ), COORD_INTEGER_BITS+1, 0 ) ),
+ SendPropBool( SENDINFO( m_bBombSpotted ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_bPlayerSpotted), SendPropInt( SENDINFO_ARRAY(m_bPlayerSpotted), 1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_iMVPs), SendPropInt( SENDINFO_ARRAY(m_iMVPs), COORD_INTEGER_BITS+1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_bHasDefuser), SendPropInt( SENDINFO_ARRAY(m_bHasDefuser), 1, SPROP_UNSIGNED ) ),
+ SendPropArray3( SENDINFO_ARRAY3(m_szClan), SendPropStringT( SENDINFO_ARRAY(m_szClan) ) ),
+END_SEND_TABLE()
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+BEGIN_DATADESC( CCSPlayerResource )
+ // DEFINE_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1 ),
+ // DEFINE_ARRAY( m_iPacketloss, FIELD_INTEGER, MAX_PLAYERS+1 ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( cs_player_manager, CCSPlayerResource );
+
+CCSPlayerResource::CCSPlayerResource( void )
+{
+
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+class Spotter
+{
+public:
+ Spotter( CBaseEntity *entity, const Vector &target, int spottingTeam )
+ {
+ m_targetEntity = entity;
+ m_target = target;
+ m_team = spottingTeam;
+ m_spotted = false;
+ }
+
+ bool operator()( CBasePlayer *player )
+ {
+ if ( !player->IsAlive() || player->GetTeamNumber() != m_team )
+ return true;
+
+ CCSPlayer *csPlayer = ToCSPlayer( player );
+ if ( !csPlayer )
+ return true;
+
+ if ( csPlayer->IsBlind() )
+ return true;
+
+ Vector eye, forward;
+ player->EyePositionAndVectors( &eye, &forward, NULL, NULL );
+ Vector path( m_target - eye );
+ float distance = path.Length();
+ path.NormalizeInPlace();
+ float dot = DotProduct( forward, path );
+ if( (dot > 0.995f )
+ || (dot > 0.98f && distance < 900)
+ || (dot > 0.8f && distance < 250)
+ )
+ {
+ trace_t tr;
+ CTraceFilterSkipTwoEntities filter( player, m_targetEntity, COLLISION_GROUP_DEBRIS );
+ UTIL_TraceLine( eye, m_target,
+ (CONTENTS_OPAQUE|CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_DEBRIS), &filter, &tr );
+
+ if( tr.fraction == 1.0f )
+ {
+ if ( TheCSBots()->IsLineBlockedBySmoke( eye, m_target ) )
+ {
+ return true;
+ }
+
+ m_spotted = true;
+ return false; // spotted already, so no reason to check for other players spotting the same thing.
+ }
+ }
+
+ return true;
+ }
+
+ bool Spotted( void ) const
+ {
+ return m_spotted;
+ }
+
+private:
+ CBaseEntity *m_targetEntity;
+ Vector m_target;
+ int m_team;
+ bool m_spotted;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSPlayerResource::UpdatePlayerData( void )
+{
+ int i;
+
+ m_iPlayerC4 = 0;
+ m_iPlayerVIP = 0;
+
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
+
+ if ( pPlayer && pPlayer->IsConnected() )
+ {
+ if ( pPlayer->IsVIP() )
+ {
+ // we should only have one VIP
+ Assert( m_iPlayerVIP == 0 );
+ m_iPlayerVIP = i;
+ }
+
+ if ( pPlayer->HasC4() )
+ {
+ // we should only have one bomb
+ m_iPlayerC4 = i;
+ }
+
+ m_szClan.Set(i, AllocPooledString( pPlayer->GetClanTag() ) );
+
+ m_iMVPs.Set(i, pPlayer->GetNumMVPs());
+
+ m_bHasDefuser.Set(i, pPlayer->HasDefuser());
+
+ }
+ else
+ {
+ m_szClan.Set( i, MAKE_STRING( "" ) );
+ m_iMVPs.Set( i, 0 );
+ }
+ }
+
+ CBaseEntity *c4 = NULL;
+ if ( m_iPlayerC4 == 0 )
+ {
+ // no player has C4, update C4 position
+ if ( g_C4s.Count() > 0 )
+ {
+ c4 = g_C4s[0];
+ m_vecC4 = c4->GetAbsOrigin();
+ }
+ else
+ {
+ m_vecC4.Init();
+ }
+ }
+
+ int numHostages = g_Hostages.Count();
+
+ for ( i = 0; i < MAX_HOSTAGES; i++ )
+ {
+ if ( i >= numHostages )
+ {
+// engine->Con_NPrintf( i, "Dead" );
+ m_bHostageAlive.Set( i, false );
+ m_isHostageFollowingSomeone.Set( i, false );
+ continue;
+ }
+
+ CHostage* pHostage = g_Hostages[i];
+
+ m_bHostageAlive.Set( i, pHostage->IsRescuable() );
+
+ if ( pHostage->IsValid() )
+ {
+ m_iHostageX.Set( i, (int) pHostage->GetAbsOrigin().x );
+ m_iHostageY.Set( i, (int) pHostage->GetAbsOrigin().y );
+ m_iHostageZ.Set( i, (int) pHostage->GetAbsOrigin().z );
+ m_iHostageEntityIDs.Set( i, pHostage->entindex() );
+ m_isHostageFollowingSomeone.Set( i, pHostage->IsFollowingSomeone() );
+// engine->Con_NPrintf( i, "ID:%d Pos:(%.0f,%.0f,%.0f)", pHostage->entindex(), pHostage->GetAbsOrigin().x, pHostage->GetAbsOrigin().y, pHostage->GetAbsOrigin().z );
+ }
+ else
+ {
+// engine->Con_NPrintf( i, "Invalid" );
+ }
+ }
+
+ if( !m_foundGoalPositions )
+ {
+ // We only need to update these once a map, but we need the client to know about them.
+ CBaseEntity* ent = NULL;
+ while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL )
+ {
+ const Vector &pos = ent->WorldSpaceCenter();
+ CNavArea *area = TheNavMesh->GetNearestNavArea( pos, true, 10000.0f, false, false );
+ const char *placeName = (area) ? TheNavMesh->PlaceToName( area->GetPlace() ) : NULL;
+ if ( placeName == NULL )
+ {
+ // The bomb site has no area or place name, so just choose A then B
+ if ( m_bombsiteCenterA.Get().IsZero() )
+ {
+ m_bombsiteCenterA = pos;
+ }
+ else
+ {
+ m_bombsiteCenterB = pos;
+ }
+ }
+ else
+ {
+ // The bomb site has a place name, so choose accordingly
+ if( FStrEq( placeName, "BombsiteA" ) )
+ {
+ m_bombsiteCenterA = pos;
+ }
+ else
+ {
+ m_bombsiteCenterB = pos;
+ }
+ }
+ m_foundGoalPositions = true;
+ }
+
+ int hostageRescue = 0;
+ while ( (( ent = gEntList.FindEntityByClassname( ent, "func_hostage_rescue" ) ) != NULL) && (hostageRescue < MAX_HOSTAGE_RESCUES) )
+ {
+ const Vector &pos = ent->WorldSpaceCenter();
+ m_hostageRescueX.Set( hostageRescue, (int) pos.x );
+ m_hostageRescueY.Set( hostageRescue, (int) pos.y );
+ m_hostageRescueZ.Set( hostageRescue, (int) pos.z );
+
+ hostageRescue++;
+ m_foundGoalPositions = true;
+ }
+ }
+
+ bool bombSpotted = false;
+ if ( c4 )
+ {
+ Spotter spotter( c4, m_vecC4, TEAM_CT );
+ ForEachPlayer( spotter );
+ if ( spotter.Spotted() )
+ {
+ bombSpotted = true;
+ }
+ }
+
+ for ( int i=0; i < MAX_PLAYERS+1; i++ )
+ {
+ CCSPlayer *target = ToCSPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( !target || !target->IsAlive() )
+ {
+ m_bPlayerSpotted.Set( i, 0 );
+ continue;
+ }
+
+ Spotter spotter( target, target->EyePosition(), (target->GetTeamNumber()==TEAM_CT) ? TEAM_TERRORIST : TEAM_CT );
+ ForEachPlayer( spotter );
+ if ( spotter.Spotted() )
+ {
+ if ( target->HasC4() )
+ {
+ bombSpotted = true;
+ }
+ m_bPlayerSpotted.Set( i, 1 );
+ }
+ else
+ {
+ m_bPlayerSpotted.Set( i, 0 );
+ }
+ }
+
+ if ( bombSpotted )
+ {
+ m_bBombSpotted = true;
+ }
+ else
+ {
+ m_bBombSpotted = false;
+ }
+
+ BaseClass::UpdatePlayerData();
+}
+
+void CCSPlayerResource::Spawn( void )
+{
+ m_vecC4.Init();
+ m_iPlayerC4 = 0;
+ m_iPlayerVIP = 0;
+ m_bombsiteCenterA.Init();
+ m_bombsiteCenterB.Init();
+ m_foundGoalPositions = false;
+
+ for ( int i=0; i < MAX_HOSTAGES; i++ )
+ {
+ m_bHostageAlive.Set( i, 0 );
+ m_isHostageFollowingSomeone.Set( i, 0 );
+ m_iHostageEntityIDs.Set(i, 0);
+ }
+
+ for ( int i=0; i < MAX_HOSTAGE_RESCUES; i++ )
+ {
+ m_hostageRescueX.Set( i, 0 );
+ m_hostageRescueY.Set( i, 0 );
+ m_hostageRescueZ.Set( i, 0 );
+ }
+
+ m_bBombSpotted = false;
+ for ( int i=0; i < MAX_PLAYERS+1; i++ )
+ {
+ m_bPlayerSpotted.Set( i, 0 );
+ m_szClan.Set( i, MAKE_STRING( "" ) );
+ m_iMVPs.Set( i, 0 );
+ m_bHasDefuser.Set(i, false);
+ }
+
+ BaseClass::Spawn();
+}
diff --git a/game/server/cstrike/cs_player_resource.h b/game/server/cstrike/cs_player_resource.h
new file mode 100644
index 0000000..d59cabc
--- /dev/null
+++ b/game/server/cstrike/cs_player_resource.h
@@ -0,0 +1,55 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: CS's custom CPlayerResource
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CS_PLAYER_RESOURCE_H
+#define CS_PLAYER_RESOURCE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CCSPlayerResource : public CPlayerResource
+{
+ DECLARE_CLASS( CCSPlayerResource, CPlayerResource );
+
+public:
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CCSPlayerResource();
+
+ virtual void UpdatePlayerData( void );
+ virtual void Spawn( void );
+protected:
+
+ CNetworkVar( int, m_iPlayerC4 ); // entity index of C4 carrier or 0
+ CNetworkVar( int, m_iPlayerVIP ); // entity index of VIP player or 0
+ CNetworkVector( m_vecC4 ); // position of C4
+ CNetworkArray( bool, m_bHostageAlive, MAX_HOSTAGES );
+ CNetworkArray( bool, m_isHostageFollowingSomeone, MAX_HOSTAGES );
+ CNetworkArray( int, m_iHostageEntityIDs, MAX_HOSTAGES );
+ CNetworkArray( int, m_iHostageX, MAX_HOSTAGES );
+ CNetworkArray( int, m_iHostageY, MAX_HOSTAGES );
+ CNetworkArray( int, m_iHostageZ, MAX_HOSTAGES );
+ CNetworkVector( m_bombsiteCenterA );// Location of bombsite A
+ CNetworkVector( m_bombsiteCenterB );// Location of bombsite B
+ CNetworkArray( int, m_hostageRescueX, MAX_HOSTAGE_RESCUES );// Locations of all hostage rescue spots
+ CNetworkArray( int, m_hostageRescueY, MAX_HOSTAGE_RESCUES );
+ CNetworkArray( int, m_hostageRescueZ, MAX_HOSTAGE_RESCUES );
+
+ CNetworkVar( bool, m_bBombSpotted );
+ CNetworkArray( bool, m_bPlayerSpotted, MAX_PLAYERS+1 );
+
+ CNetworkArray( string_t, m_szClan, MAX_PLAYERS+1 );
+
+ CNetworkArray( int, m_iMVPs, MAX_PLAYERS + 1 );
+ CNetworkArray( bool, m_bHasDefuser, MAX_PLAYERS + 1);
+
+private:
+ bool m_foundGoalPositions;
+};
+
+#endif // CS_PLAYER_RESOURCE_H
diff --git a/game/server/cstrike/cs_playermove.cpp b/game/server/cstrike/cs_playermove.cpp
new file mode 100644
index 0000000..a8f4eee
--- /dev/null
+++ b/game/server/cstrike/cs_playermove.cpp
@@ -0,0 +1,98 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "player_command.h"
+#include "igamemovement.h"
+#include "in_buttons.h"
+#include "ipredictionsystem.h"
+#include "iservervehicle.h"
+#include "cs_player.h"
+
+
+static CMoveData g_MoveData;
+CMoveData *g_pMoveData = &g_MoveData;
+
+IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL;
+
+
+//-----------------------------------------------------------------------------
+// Sets up the move data for TF2
+//-----------------------------------------------------------------------------
+class CCSPlayerMove : public CPlayerMove
+{
+DECLARE_CLASS( CCSPlayerMove, CPlayerMove );
+
+public:
+ virtual void StartCommand( CBasePlayer *player, CUserCmd *cmd );
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+ virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move );
+};
+
+// PlayerMove Interface
+static CCSPlayerMove g_PlayerMove;
+
+//-----------------------------------------------------------------------------
+// Singleton accessor
+//-----------------------------------------------------------------------------
+CPlayerMove *PlayerMove()
+{
+ return &g_PlayerMove;
+}
+
+//-----------------------------------------------------------------------------
+// Main setup, finish
+//-----------------------------------------------------------------------------
+
+void CCSPlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd )
+{
+ CCSPlayer *pPlayer = ToCSPlayer( player );
+
+ // Reset this.. it gets reset each frame that we're in a bomb zone.
+ pPlayer->m_bInBombZone = false;
+ pPlayer->m_bInBuyZone = false;
+ pPlayer->m_bInHostageRescueZone = false;
+
+ BaseClass::StartCommand( player, cmd );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called pre player movement and copies all the data necessary
+// from the player for movement. (Server-side, the client-side version
+// of this code can be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CCSPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ player->AvoidPhysicsProps( ucmd );
+
+ BaseClass::SetupMove( player, ucmd, pHelper, move );
+
+ IServerVehicle *pVehicle = player->GetVehicle();
+ if (pVehicle && gpGlobals->frametime != 0)
+ {
+ pVehicle->SetupMove( player, ucmd, pHelper, move );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called post player movement to copy back all data that
+// movement could have modified and that is necessary for future
+// movement. (Server-side, the client-side version of this code can
+// be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CCSPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move )
+{
+ // Call the default FinishMove code.
+ BaseClass::FinishMove( player, ucmd, move );
+
+ IServerVehicle *pVehicle = player->GetVehicle();
+ if (pVehicle && gpGlobals->frametime != 0)
+ {
+ pVehicle->FinishMove( player, ucmd, move );
+ }
+}
diff --git a/game/server/cstrike/cs_team.cpp b/game/server/cstrike/cs_team.cpp
new file mode 100644
index 0000000..61360ea
--- /dev/null
+++ b/game/server/cstrike/cs_team.cpp
@@ -0,0 +1,101 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Team management class. Contains all the details for a specific team
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "cs_team.h"
+#include "entitylist.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+// Datatable
+IMPLEMENT_SERVERCLASS_ST(CCSTeam, DT_CSTeam)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( cs_team_manager, CCSTeam );
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a pointer to the specified TF team manager
+//-----------------------------------------------------------------------------
+CCSTeam *GetGlobalTFTeam( int iIndex )
+{
+ return (CCSTeam*)GetGlobalTeam( iIndex );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Needed because this is an entity, but should never be used
+//-----------------------------------------------------------------------------
+void CCSTeam::Init( const char *pName, int iNumber )
+{
+ BaseClass::Init( pName, iNumber );
+
+ // Only detect changes every half-second.
+ NetworkProp()->SetUpdateInterval( 0.75f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCSTeam::~CCSTeam( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCSTeam::Precache( void )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called every frame
+//-----------------------------------------------------------------------------
+void CCSTeam::Think( void )
+{
+}
+
+
+//------------------------------------------------------------------------------------------------------------------
+// PLAYERS
+//-----------------------------------------------------------------------------
+// Purpose: Add the specified player to this team. Remove them from their current team, if any.
+//-----------------------------------------------------------------------------
+void CCSTeam::AddPlayer( CBasePlayer *pPlayer )
+{
+ BaseClass::AddPlayer( pPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clean up the player's objects when they leave
+//-----------------------------------------------------------------------------
+void CCSTeam::RemovePlayer( CBasePlayer *pPlayer )
+{
+ BaseClass::RemovePlayer( pPlayer );
+}
+
+//------------------------------------------------------------------------------------------------------------------
+// UTILITY FUNCS
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCSTeam* CCSTeam::GetEnemyTeam()
+{
+ // Look for nearby enemy objects we can capture.
+ int iMyTeam = GetTeamNumber();
+ if( iMyTeam == 0 )
+ return NULL;
+
+ int iEnemyTeam = !(iMyTeam - 1) + 1;
+ return (CCSTeam*)GetGlobalTeam( iEnemyTeam );
+}
+
+
diff --git a/game/server/cstrike/cs_team.h b/game/server/cstrike/cs_team.h
new file mode 100644
index 0000000..683ee9b
--- /dev/null
+++ b/game/server/cstrike/cs_team.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Team management class. Contains all the details for a specific team
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CS_TEAM_H
+#define CS_TEAM_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "utlvector.h"
+#include "team.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Team Manager
+//-----------------------------------------------------------------------------
+class CCSTeam : public CTeam
+{
+ DECLARE_CLASS( CCSTeam, CTeam );
+public:
+ virtual ~CCSTeam( void );
+
+ DECLARE_SERVERCLASS();
+
+ // Initialization
+ virtual void Init( const char *pName, int iNumber );
+
+ virtual void Precache( void );
+ virtual void Think( void );
+
+ //-----------------------------------------------------------------------------
+ // Players
+ //-----------------------------------------------------------------------------
+ virtual void AddPlayer( CBasePlayer *pPlayer );
+ virtual void RemovePlayer( CBasePlayer *pPlayer );
+
+ //-----------------------------------------------------------------------------
+ // Utility funcs
+ //-----------------------------------------------------------------------------
+ CCSTeam* GetEnemyTeam();
+
+private:
+
+ // Used to distribute resources to a team
+ float m_flNextResourceTime;
+
+ int m_iLastUpdateSentAt;
+};
+
+
+extern CCSTeam *GetGlobalTFTeam( int iIndex );
+
+
+#endif // TF_TEAM_H
diff --git a/game/server/cstrike/cs_vehicle_jeep.cpp b/game/server/cstrike/cs_vehicle_jeep.cpp
new file mode 100644
index 0000000..11a378f
--- /dev/null
+++ b/game/server/cstrike/cs_vehicle_jeep.cpp
@@ -0,0 +1,1566 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "vehicle_base.h"
+#include "engine/IEngineSound.h"
+#include "in_buttons.h"
+#include "ammodef.h"
+#include "IEffects.h"
+#include "beam_shared.h"
+#include "soundenvelope.h"
+#include "decals.h"
+#include "soundent.h"
+#include "te_effect_dispatch.h"
+#include "ndebugoverlay.h"
+#include "movevars_shared.h"
+#include "bone_setup.h"
+#include "ai_basenpc.h"
+#include "ai_hint.h"
+#include "globalstate.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define VEHICLE_HITBOX_DRIVER 1
+#define LOCK_SPEED 10
+#define JEEP_GUN_YAW "vehicle_weapon_yaw"
+#define JEEP_GUN_PITCH "vehicle_weapon_pitch"
+#define JEEP_GUN_SPIN "gun_spin"
+#define JEEP_GUN_SPIN_RATE 20
+
+#define CANNON_MAX_UP_PITCH 20
+#define CANNON_MAX_DOWN_PITCH 20
+#define CANNON_MAX_LEFT_YAW 90
+#define CANNON_MAX_RIGHT_YAW 90
+
+#define OVERTURNED_EXIT_WAITTIME 2.0f
+
+#define JEEP_AMMOCRATE_HITGROUP 5
+#define JEEP_WHEEL_COUNT 4
+
+#define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f
+
+#define JEEP_DELTA_LENGTH_MAX 12.0f // 1 foot
+#define JEEP_FRAMETIME_MIN 1e-6
+
+ConVar hud_jeephint_numentries( "hud_jeephint_numentries", "10", FCVAR_NONE );
+ConVar g_jeepexitspeed( "g_jeepexitspeed", "100", FCVAR_CHEAT );
+
+//=============================================================================
+//
+// Jeep water data.
+//
+struct JeepWaterData_t
+{
+ bool m_bWheelInWater[JEEP_WHEEL_COUNT];
+ bool m_bWheelWasInWater[JEEP_WHEEL_COUNT];
+ Vector m_vecWheelContactPoints[JEEP_WHEEL_COUNT];
+ float m_flNextRippleTime[JEEP_WHEEL_COUNT];
+ bool m_bBodyInWater;
+ bool m_bBodyWasInWater;
+
+ DECLARE_SIMPLE_DATADESC();
+};
+
+BEGIN_SIMPLE_DATADESC( JeepWaterData_t )
+ DEFINE_ARRAY( m_bWheelInWater, FIELD_BOOLEAN, JEEP_WHEEL_COUNT ),
+ DEFINE_ARRAY( m_bWheelWasInWater, FIELD_BOOLEAN, JEEP_WHEEL_COUNT ),
+ DEFINE_ARRAY( m_vecWheelContactPoints, FIELD_VECTOR, JEEP_WHEEL_COUNT ),
+ DEFINE_ARRAY( m_flNextRippleTime, FIELD_TIME, JEEP_WHEEL_COUNT ),
+ DEFINE_FIELD( m_bBodyInWater, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bBodyWasInWater, FIELD_BOOLEAN ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose: Four wheel physics vehicle server vehicle with weaponry
+//-----------------------------------------------------------------------------
+class CJeepFourWheelServerVehicle : public CFourWheelServerVehicle
+{
+ typedef CFourWheelServerVehicle BaseClass;
+// IServerVehicle
+public:
+ bool NPC_HasPrimaryWeapon( void ) { return true; }
+ void NPC_AimPrimaryWeapon( Vector vecTarget );
+ int GetExitAnimToUse( Vector &vecEyeExitEndpoint, bool &bAllPointsBlocked );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CPropJeep : public CPropVehicleDriveable
+{
+ DECLARE_CLASS( CPropJeep, CPropVehicleDriveable );
+
+public:
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CPropJeep( void );
+
+ // CPropVehicle
+ virtual void OnRestore( void );
+ virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData );
+ virtual void DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased );
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles );
+ virtual bool AllowBlockedExit( CBasePlayer *pPlayer, int nRole ) { return false; }
+ virtual bool CanExitVehicle( CBaseEntity *pEntity );
+ virtual bool IsVehicleBodyInWater() { return m_WaterData.m_bBodyInWater; }
+
+ // CBaseEntity
+ void Think(void);
+ void Precache( void );
+ void Spawn( void );
+
+ virtual void CreateServerVehicle( void );
+ virtual Vector BodyTarget( const Vector &posSrc, bool bNoisy = true );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void EnterVehicle( CBasePlayer *pPlayer );
+ virtual void ExitVehicle( int nRole );
+
+ void AimGunAt( Vector *endPos, float flInterval );
+ bool TauCannonHasBeenCutOff( void ) { return m_bGunHasBeenCutOff; }
+
+ // NPC Driving
+ bool NPC_HasPrimaryWeapon( void ) { return true; }
+ void NPC_AimPrimaryWeapon( Vector vecTarget );
+
+ const char *GetTracerType( void ) { return "AR2Tracer"; }
+ void DoImpactEffect( trace_t &tr, int nDamageType );
+
+ bool HeadlightIsOn( void ) { return m_bHeadlightIsOn; }
+ void HeadlightTurnOn( void ) { m_bHeadlightIsOn = true; }
+ void HeadlightTurnOff( void ) { m_bHeadlightIsOn = false; }
+
+private:
+
+ void FireCannon( void );
+ void ChargeCannon( void );
+ void FireChargedCannon( void );
+
+ void DrawBeam( const Vector &startPos, const Vector &endPos, float width );
+ void StopChargeSound( void );
+ void GetCannonAim( Vector *resultDir );
+
+ void InitWaterData( void );
+ void HandleWater( void );
+ bool CheckWater( void );
+ void CheckWaterLevel( void );
+ void CreateSplash( const Vector &vecPosition );
+ void CreateRipple( const Vector &vecPosition );
+
+ void UpdateSteeringAngle( void );
+ void CreateDangerSounds( void );
+
+ void ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime );
+ void DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime );
+ void DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime );
+
+ void InputStartRemoveTauCannon( inputdata_t &inputdata );
+ void InputFinishRemoveTauCannon( inputdata_t &inputdata );
+
+private:
+
+ bool m_bGunHasBeenCutOff;
+ float m_flDangerSoundTime;
+ int m_nBulletType;
+ bool m_bCannonCharging;
+ float m_flCannonTime;
+ float m_flCannonChargeStartTime;
+ Vector m_vecGunOrigin;
+ CSoundPatch *m_sndCannonCharge;
+ int m_nSpinPos;
+ float m_aimYaw;
+ float m_aimPitch;
+ float m_throttleDisableTime;
+ float m_flAmmoCrateCloseTime;
+
+ // handbrake after the fact to keep vehicles from rolling
+ float m_flHandbrakeTime;
+ bool m_bInitialHandbrake;
+
+ float m_flOverturnedTime;
+
+ Vector m_vecLastEyePos;
+ Vector m_vecLastEyeTarget;
+ Vector m_vecEyeSpeed;
+ Vector m_vecTargetSpeed;
+
+ JeepWaterData_t m_WaterData;
+
+ int m_iNumberOfEntries;
+ int m_nAmmoType;
+
+ // Seagull perching
+ float m_flPlayerExitedTime; // Time at which the player last left this vehicle
+ float m_flLastSawPlayerAt; // Time at which we last saw the player
+ EHANDLE m_hLastPlayerInVehicle;
+ bool m_bHasPoop;
+
+ CNetworkVar( bool, m_bHeadlightIsOn );
+};
+
+BEGIN_DATADESC( CPropJeep )
+ DEFINE_FIELD( m_bGunHasBeenCutOff, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flDangerSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nBulletType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bCannonCharging, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flCannonTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flCannonChargeStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecGunOrigin, FIELD_POSITION_VECTOR ),
+ DEFINE_SOUNDPATCH( m_sndCannonCharge ),
+ DEFINE_FIELD( m_nSpinPos, FIELD_INTEGER ),
+ DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
+ DEFINE_FIELD( m_throttleDisableTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flHandbrakeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bInitialHandbrake, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flOverturnedTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flAmmoCrateCloseTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecLastEyePos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecLastEyeTarget, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecEyeSpeed, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecTargetSpeed, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_bHeadlightIsOn, FIELD_BOOLEAN ),
+ DEFINE_EMBEDDED( m_WaterData ),
+
+ DEFINE_FIELD( m_iNumberOfEntries, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nAmmoType, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_flPlayerExitedTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flLastSawPlayerAt, FIELD_TIME ),
+ DEFINE_FIELD( m_hLastPlayerInVehicle, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bHasPoop, FIELD_BOOLEAN ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartRemoveTauCannon", InputStartRemoveTauCannon ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FinishRemoveTauCannon", InputFinishRemoveTauCannon ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CPropJeep, DT_PropJeep )
+ SendPropBool( SENDINFO( m_bHeadlightIsOn ) ),
+END_SEND_TABLE();
+
+//LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeep );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPropJeep::CPropJeep( void )
+{
+ m_bHasGun = true;
+ m_bGunHasBeenCutOff = false;
+ m_bCannonCharging = false;
+ m_flCannonChargeStartTime = 0;
+ m_flCannonTime = 0;
+ m_nBulletType = -1;
+ m_flOverturnedTime = 0.0f;
+ m_iNumberOfEntries = 0;
+
+ m_vecEyeSpeed.Init();
+
+ InitWaterData();
+
+ m_bUnableToFire = true;
+ m_flAmmoCrateCloseTime = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::CreateServerVehicle( void )
+{
+ // Create our armed server vehicle
+ m_pServerVehicle = new CJeepFourWheelServerVehicle();
+ m_pServerVehicle->SetVehicle( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::Precache( void )
+{
+ UTIL_PrecacheOther( "npc_seagull" );
+
+ PrecacheScriptSound( "PropJeep.AmmoClose" );
+ PrecacheScriptSound( "PropJeep.FireCannon" );
+ PrecacheScriptSound( "PropJeep.FireChargedCannon" );
+ PrecacheScriptSound( "PropJeep.AmmoOpen" );
+
+ PrecacheScriptSound( "Jeep.GaussCharge" );
+
+// PrecacheModel( GAUSS_BEAM_SPRITE );
+
+ BaseClass::Precache();
+}
+
+//------------------------------------------------
+// Spawn
+//------------------------------------------------
+void CPropJeep::Spawn( void )
+{
+ // Setup vehicle as a real-wheels car.
+ SetVehicleType( VEHICLE_TYPE_CAR_WHEELS );
+
+ BaseClass::Spawn();
+ m_flHandbrakeTime = gpGlobals->curtime + 0.1;
+ m_bInitialHandbrake = false;
+
+ m_VehiclePhysics.SetHasBrakePedal( false );
+
+ m_flMinimumSpeedToEnterExit = LOCK_SPEED;
+
+ m_nBulletType = GetAmmoDef()->Index("GaussEnergy");
+
+ if ( m_bHasGun )
+ {
+ SetBodygroup( 1, true );
+ }
+ else
+ {
+ SetBodygroup( 1, false );
+ }
+
+ // Initialize pose parameters
+ SetPoseParameter( JEEP_GUN_YAW, 0 );
+ SetPoseParameter( JEEP_GUN_PITCH, 0 );
+ m_nSpinPos = 0;
+ SetPoseParameter( JEEP_GUN_SPIN, m_nSpinPos );
+ m_aimYaw = 0;
+ m_aimPitch = 0;
+
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ CAmmoDef *pAmmoDef = GetAmmoDef();
+ m_nAmmoType = pAmmoDef->Index("GaussEnergy");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &tr -
+// nDamageType -
+//-----------------------------------------------------------------------------
+void CPropJeep::DoImpactEffect( trace_t &tr, int nDamageType )
+{
+ //Draw our beam
+ DrawBeam( tr.startpos, tr.endpos, 2.4 );
+
+ if ( (tr.surface.flags & SURF_SKY) == false )
+ {
+ CPVSFilter filter( tr.endpos );
+ te->GaussExplosion( filter, 0.0f, tr.endpos, tr.plane.normal, 0 );
+
+ UTIL_ImpactTrace( &tr, m_nBulletType );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+ if ( ptr->hitbox != VEHICLE_HITBOX_DRIVER )
+ {
+ if ( inputInfo.GetDamageType() & DMG_BULLET )
+ {
+ info.ScaleDamage( 0.0001 );
+ }
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CPropJeep::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ //Do scaled up physics damage to the car
+ CTakeDamageInfo info = inputInfo;
+ info.ScaleDamage( 25 );
+
+ // HACKHACK: Scale up grenades until we get a better explosion/pressure damage system
+ if ( inputInfo.GetDamageType() & DMG_BLAST )
+ {
+ info.SetDamageForce( inputInfo.GetDamageForce() * 10 );
+ }
+ VPhysicsTakeDamage( info );
+
+ // reset the damage
+ info.SetDamage( inputInfo.GetDamage() );
+
+ // small amounts of shock damage disrupt the car, but aren't transferred to the player
+ if ( info.GetDamageType() == DMG_SHOCK )
+ {
+ if ( info.GetDamage() <= 10 )
+ {
+ // take 10% damage and make the engine stall
+ info.ScaleDamage( 0.1 );
+ m_throttleDisableTime = gpGlobals->curtime + 2;
+ }
+ }
+
+ //Check to do damage to driver
+ if ( GetDriver() )
+ {
+ //Take no damage from physics damages
+ if ( info.GetDamageType() & DMG_CRUSH )
+ return 0;
+
+ // Take the damage (strip out the DMG_BLAST)
+ info.SetDamageType( info.GetDamageType() & (~DMG_BLAST) );
+ GetDriver()->TakeDamage( info );
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CPropJeep::BodyTarget( const Vector &posSrc, bool bNoisy )
+{
+ Vector shotPos;
+ matrix3x4_t matrix;
+
+ int eyeAttachmentIndex = LookupAttachment("vehicle_driver_eyes");
+ GetAttachment( eyeAttachmentIndex, matrix );
+ MatrixGetColumn( matrix, 3, shotPos );
+
+ if ( bNoisy )
+ {
+ shotPos[0] += random->RandomFloat( -8.0f, 8.0f );
+ shotPos[1] += random->RandomFloat( -8.0f, 8.0f );
+ shotPos[2] += random->RandomFloat( -8.0f, 8.0f );
+ }
+
+ return shotPos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Aim Gun at a target
+//-----------------------------------------------------------------------------
+void CPropJeep::AimGunAt( Vector *endPos, float flInterval )
+{
+ Vector aimPos = *endPos;
+
+ // See if the gun should be allowed to aim
+ if ( IsOverturned() || m_bEngineLocked )
+ {
+ SetPoseParameter( JEEP_GUN_YAW, 0 );
+ SetPoseParameter( JEEP_GUN_PITCH, 0 );
+ SetPoseParameter( JEEP_GUN_SPIN, 0 );
+ return;
+
+ // Make the gun go limp and look "down"
+ Vector v_forward, v_up;
+ AngleVectors( GetLocalAngles(), NULL, &v_forward, &v_up );
+ aimPos = WorldSpaceCenter() + ( v_forward * -32.0f ) - Vector( 0, 0, 128.0f );
+ }
+
+ matrix3x4_t gunMatrix;
+ GetAttachment( LookupAttachment("gun_ref"), gunMatrix );
+
+ // transform the enemy into gun space
+ Vector localEnemyPosition;
+ VectorITransform( aimPos, gunMatrix, localEnemyPosition );
+
+ // do a look at in gun space (essentially a delta-lookat)
+ QAngle localEnemyAngles;
+ VectorAngles( localEnemyPosition, localEnemyAngles );
+
+ // convert to +/- 180 degrees
+ localEnemyAngles.x = UTIL_AngleDiff( localEnemyAngles.x, 0 );
+ localEnemyAngles.y = UTIL_AngleDiff( localEnemyAngles.y, 0 );
+
+ float targetYaw = m_aimYaw + localEnemyAngles.y;
+ float targetPitch = m_aimPitch + localEnemyAngles.x;
+
+ // Constrain our angles
+ float newTargetYaw = clamp( targetYaw, -CANNON_MAX_LEFT_YAW, CANNON_MAX_RIGHT_YAW );
+ float newTargetPitch = clamp( targetPitch, -CANNON_MAX_DOWN_PITCH, CANNON_MAX_UP_PITCH );
+
+ // If the angles have been clamped, we're looking outside of our valid range
+ if ( fabs(newTargetYaw-targetYaw) > 1e-4 || fabs(newTargetPitch-targetPitch) > 1e-4 )
+ {
+ m_bUnableToFire = true;
+ }
+
+ targetYaw = newTargetYaw;
+ targetPitch = newTargetPitch;
+
+ // Exponentially approach the target
+ float yawSpeed = 8;
+ float pitchSpeed = 8;
+
+ m_aimYaw = UTIL_Approach( targetYaw, m_aimYaw, yawSpeed );
+ m_aimPitch = UTIL_Approach( targetPitch, m_aimPitch, pitchSpeed );
+
+ SetPoseParameter( JEEP_GUN_YAW, -m_aimYaw);
+ SetPoseParameter( JEEP_GUN_PITCH, -m_aimPitch );
+
+ InvalidateBoneCache();
+
+ // read back to avoid drift when hitting limits
+ // as long as the velocity is less than the delta between the limit and 180, this is fine.
+ m_aimPitch = -GetPoseParameter( JEEP_GUN_PITCH );
+ m_aimYaw = -GetPoseParameter( JEEP_GUN_YAW );
+
+ // Now draw crosshair for actual aiming point
+ Vector vecMuzzle, vecMuzzleDir;
+ QAngle vecMuzzleAng;
+
+ GetAttachment( "Muzzle", vecMuzzle, vecMuzzleAng );
+ AngleVectors( vecMuzzleAng, &vecMuzzleDir );
+
+ trace_t tr;
+ UTIL_TraceLine( vecMuzzle, vecMuzzle + (vecMuzzleDir * MAX_TRACE_LENGTH), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ // see if we hit something, if so, adjust endPos to hit location
+ if ( tr.fraction < 1.0 )
+ {
+ m_vecGunCrosshair = vecMuzzle + ( vecMuzzleDir * MAX_TRACE_LENGTH * tr.fraction );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::InitWaterData( void )
+{
+ m_WaterData.m_bBodyInWater = false;
+ m_WaterData.m_bBodyWasInWater = false;
+
+ for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
+ {
+ m_WaterData.m_bWheelInWater[iWheel] = false;
+ m_WaterData.m_bWheelWasInWater[iWheel] = false;
+ m_WaterData.m_vecWheelContactPoints[iWheel].Init();
+ m_WaterData.m_flNextRippleTime[iWheel] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::HandleWater( void )
+{
+ // Only check the wheels and engine in water if we have a driver (player).
+ if ( !GetDriver() )
+ return;
+
+ // Check to see if we are in water.
+ if ( CheckWater() )
+ {
+ for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
+ {
+ // Create an entry/exit splash!
+ if ( m_WaterData.m_bWheelInWater[iWheel] != m_WaterData.m_bWheelWasInWater[iWheel] )
+ {
+ CreateSplash( m_WaterData.m_vecWheelContactPoints[iWheel] );
+ CreateRipple( m_WaterData.m_vecWheelContactPoints[iWheel] );
+ }
+
+ // Create ripples.
+ if ( m_WaterData.m_bWheelInWater[iWheel] && m_WaterData.m_bWheelWasInWater[iWheel] )
+ {
+ if ( m_WaterData.m_flNextRippleTime[iWheel] < gpGlobals->curtime )
+ {
+ // Stagger ripple times
+ m_WaterData.m_flNextRippleTime[iWheel] = gpGlobals->curtime + RandomFloat( 0.1, 0.3 );
+ CreateRipple( m_WaterData.m_vecWheelContactPoints[iWheel] );
+ }
+ }
+ }
+ }
+
+ // Save of data from last think.
+ for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
+ {
+ m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel];
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPropJeep::CheckWater( void )
+{
+ bool bInWater = false;
+
+ // Check all four wheels.
+ for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
+ {
+ // Get the current wheel and get its contact point.
+ IPhysicsObject *pWheel = m_VehiclePhysics.GetWheel( iWheel );
+ if ( !pWheel )
+ continue;
+
+ // Check to see if we hit water.
+ if ( pWheel->GetContactPoint( &m_WaterData.m_vecWheelContactPoints[iWheel], NULL ) )
+ {
+ m_WaterData.m_bWheelInWater[iWheel] = ( UTIL_PointContents( m_WaterData.m_vecWheelContactPoints[iWheel] ) & MASK_WATER ) ? true : false;
+ if ( m_WaterData.m_bWheelInWater[iWheel] )
+ {
+ bInWater = true;
+ }
+ }
+ }
+
+ // Check the body and the BONNET.
+ int iEngine = LookupAttachment( "vehicle_engine" );
+ Vector vecEnginePoint;
+ QAngle vecEngineAngles;
+ GetAttachment( iEngine, vecEnginePoint, vecEngineAngles );
+
+ m_WaterData.m_bBodyInWater = ( UTIL_PointContents( vecEnginePoint ) & MASK_WATER ) ? true : false;
+ if ( m_WaterData.m_bBodyInWater )
+ {
+ if ( m_bHasPoop )
+ {
+ RemoveAllDecals();
+ m_bHasPoop = false;
+ }
+
+ if ( !m_VehiclePhysics.IsEngineDisabled() )
+ {
+ m_VehiclePhysics.SetDisableEngine( true );
+ }
+ }
+ else
+ {
+ if ( m_VehiclePhysics.IsEngineDisabled() )
+ {
+ m_VehiclePhysics.SetDisableEngine( false );
+ }
+ }
+
+ if ( bInWater )
+ {
+ // Check the player's water level.
+ CheckWaterLevel();
+ }
+
+ return bInWater;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::CheckWaterLevel( void )
+{
+ CBaseEntity *pEntity = GetDriver();
+ if ( pEntity && pEntity->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( pEntity );
+
+ Vector vecAttachPoint;
+ QAngle vecAttachAngles;
+
+ // Check eyes. (vehicle_driver_eyes point)
+ int iAttachment = LookupAttachment( "vehicle_driver_eyes" );
+ GetAttachment( iAttachment, vecAttachPoint, vecAttachAngles );
+
+ // Add the jeep's Z view offset
+ Vector vecUp;
+ AngleVectors( vecAttachAngles, NULL, NULL, &vecUp );
+ vecUp.z = clamp( vecUp.z, 0.0f, vecUp.z );
+ vecAttachPoint.z += r_JeepViewZHeight.GetFloat() * vecUp.z;
+
+ bool bEyes = ( UTIL_PointContents( vecAttachPoint ) & MASK_WATER ) ? true : false;
+ if ( bEyes )
+ {
+ pPlayer->SetWaterLevel( WL_Eyes );
+ return;
+ }
+
+ // Check waist. (vehicle_engine point -- see parent function).
+ if ( m_WaterData.m_bBodyInWater )
+ {
+ pPlayer->SetWaterLevel( WL_Waist );
+ return;
+ }
+
+ // Check feet. (vehicle_feet_passenger0 point)
+ iAttachment = LookupAttachment( "vehicle_feet_passenger0" );
+ GetAttachment( iAttachment, vecAttachPoint, vecAttachAngles );
+ bool bFeet = ( UTIL_PointContents( vecAttachPoint ) & MASK_WATER ) ? true : false;
+ if ( bFeet )
+ {
+ pPlayer->SetWaterLevel( WL_Feet );
+ return;
+ }
+
+ // Not in water.
+ pPlayer->SetWaterLevel( WL_NotInWater );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::CreateSplash( const Vector &vecPosition )
+{
+ // Splash data.
+ CEffectData data;
+ data.m_fFlags = 0;
+ data.m_vOrigin = vecPosition;
+ data.m_vNormal.Init( 0.0f, 0.0f, 1.0f );
+ VectorAngles( data.m_vNormal, data.m_vAngles );
+ data.m_flScale = 10.0f + random->RandomFloat( 0, 2 );
+
+ // Create the splash..
+ DispatchEffect( "watersplash", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::CreateRipple( const Vector &vecPosition )
+{
+ // Ripple data.
+ CEffectData data;
+ data.m_fFlags = 0;
+ data.m_vOrigin = vecPosition;
+ data.m_vNormal.Init( 0.0f, 0.0f, 1.0f );
+ VectorAngles( data.m_vNormal, data.m_vAngles );
+ data.m_flScale = 10.0f + random->RandomFloat( 0, 2 );
+ if ( GetWaterType() & CONTENTS_SLIME )
+ {
+ data.m_fFlags |= FX_WATER_IN_SLIME;
+ }
+
+ // Create the ripple.
+ DispatchEffect( "waterripple", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::Think(void)
+{
+ BaseClass::Think();
+
+/*
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ if ( m_bEngineLocked )
+ {
+ m_bUnableToFire = true;
+
+ if ( pPlayer != NULL )
+ {
+ pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
+ }
+ }
+ else
+ {
+ // Start this as false and update it again each frame
+ m_bUnableToFire = false;
+
+ if ( pPlayer != NULL )
+ {
+ pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_VEHICLE_CROSSHAIR;
+ }
+ }
+*/
+
+ // Water!?
+ HandleWater();
+
+ SetSimulationTime( gpGlobals->curtime );
+
+ SetNextThink( gpGlobals->curtime );
+ SetAnimatedEveryTick( true );
+
+ if ( !m_bInitialHandbrake ) // after initial timer expires, set the handbrake
+ {
+ m_bInitialHandbrake = true;
+ m_VehiclePhysics.SetHandbrake( true );
+ m_VehiclePhysics.Think();
+ }
+
+ // Check overturned status.
+ if ( !IsOverturned() )
+ {
+ m_flOverturnedTime = 0.0f;
+ }
+ else
+ {
+ m_flOverturnedTime += gpGlobals->frametime;
+ }
+
+ // spin gun if charging cannon
+ //FIXME: Don't bother for E3
+ if ( m_bCannonCharging )
+ {
+ m_nSpinPos += JEEP_GUN_SPIN_RATE;
+ SetPoseParameter( JEEP_GUN_SPIN, m_nSpinPos );
+ }
+
+ // Aim gun based on the player view direction.
+ if ( m_hPlayer && !m_bExitAnimOn && !m_bEnterAnimOn )
+ {
+ Vector vecEyeDir, vecEyePos;
+ m_hPlayer->EyePositionAndVectors( &vecEyePos, &vecEyeDir, NULL, NULL );
+
+ // Trace out from the player's eye point.
+ Vector vecEndPos = vecEyePos + ( vecEyeDir * MAX_TRACE_LENGTH );
+ trace_t trace;
+ UTIL_TraceLine( vecEyePos, vecEndPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace );
+
+ // See if we hit something, if so, adjust end position to hit location.
+ if ( trace.fraction < 1.0 )
+ {
+ vecEndPos = vecEyePos + ( vecEyeDir * MAX_TRACE_LENGTH * trace.fraction );
+ }
+
+ //m_vecLookCrosshair = vecEndPos;
+ AimGunAt( &vecEndPos, 0.1f );
+ }
+
+ StudioFrameAdvance();
+
+ // If the enter or exit animation has finished, tell the server vehicle
+ if ( IsSequenceFinished() && (m_bExitAnimOn || m_bEnterAnimOn) )
+ {
+ if ( m_bEnterAnimOn )
+ {
+ m_VehiclePhysics.ReleaseHandbrake();
+ StartEngine();
+
+ // HACKHACK: This forces the jeep to play a sound when it gets entered underwater
+ if ( m_VehiclePhysics.IsEngineDisabled() )
+ {
+ CBaseServerVehicle *pServerVehicle = dynamic_cast<CBaseServerVehicle *>(GetServerVehicle());
+ if ( pServerVehicle )
+ {
+ pServerVehicle->SoundStartDisabled();
+ }
+ }
+
+ // The first few time we get into the jeep, print the jeep help
+ if ( m_iNumberOfEntries < hud_jeephint_numentries.GetInt() )
+ {
+ UTIL_HudHintText( m_hPlayer, "#Valve_Hint_JeepKeys" );
+ m_iNumberOfEntries++;
+ }
+ }
+
+ // If we're exiting and have had the tau cannon removed, we don't want to reset the animation
+ GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, !(m_bExitAnimOn && TauCannonHasBeenCutOff()) );
+ }
+
+ // See if the ammo crate needs to close
+ if ( ( m_flAmmoCrateCloseTime < gpGlobals->curtime ) && ( GetSequence() == LookupSequence( "ammo_open" ) ) )
+ {
+ m_flAnimTime = gpGlobals->curtime;
+ m_flPlaybackRate = 0.0;
+ SetCycle( 0 );
+ ResetSequence( LookupSequence( "ammo_close" ) );
+ }
+ else if ( ( GetSequence() == LookupSequence( "ammo_close" ) ) && IsSequenceFinished() )
+ {
+ m_flAnimTime = gpGlobals->curtime;
+ m_flPlaybackRate = 0.0;
+ SetCycle( 0 );
+ ResetSequence( LookupSequence( "idle" ) );
+
+ CPASAttenuationFilter sndFilter( this, "PropJeep.AmmoClose" );
+ EmitSound( sndFilter, entindex(), "PropJeep.AmmoClose" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &startPos -
+// &endPos -
+// width -
+// useMuzzle -
+//-----------------------------------------------------------------------------
+void CPropJeep::DrawBeam( const Vector &startPos, const Vector &endPos, float width )
+{
+ /*
+ //Tracer down the middle
+ UTIL_Tracer( startPos, endPos, 0, TRACER_DONT_USE_ATTACHMENT, 6500, false, "GaussTracer" );
+
+ //Draw the main beam shaft
+ CBeam *pBeam = CBeam::BeamCreate( GAUSS_BEAM_SPRITE, 0.5 );
+
+ pBeam->SetStartPos( startPos );
+ pBeam->PointEntInit( endPos, this );
+ pBeam->SetEndAttachment( LookupAttachment("Muzzle") );
+ pBeam->SetWidth( width );
+ pBeam->SetEndWidth( 0.05f );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetColor( 255, 185+random->RandomInt( -16, 16 ), 40 );
+ pBeam->RelinkBeam();
+ pBeam->LiveForTime( 0.1f );
+
+ //Draw electric bolts along shaft
+ pBeam = CBeam::BeamCreate( GAUSS_BEAM_SPRITE, 3.0f );
+
+ pBeam->SetStartPos( startPos );
+ pBeam->PointEntInit( endPos, this );
+ pBeam->SetEndAttachment( LookupAttachment("Muzzle") );
+
+ pBeam->SetBrightness( random->RandomInt( 64, 255 ) );
+ pBeam->SetColor( 255, 255, 150+random->RandomInt( 0, 64 ) );
+ pBeam->RelinkBeam();
+ pBeam->LiveForTime( 0.1f );
+ pBeam->SetNoise( 1.6f );
+ pBeam->SetEndWidth( 0.1f ); */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::FireCannon( void )
+{
+ //Don't fire again if it's been too soon
+ if ( m_flCannonTime > gpGlobals->curtime )
+ return;
+
+ if ( m_bUnableToFire )
+ return;
+
+ m_flCannonTime = gpGlobals->curtime + 0.2f;
+ m_bCannonCharging = false;
+
+ //Find the direction the gun is pointing in
+ Vector aimDir;
+ GetCannonAim( &aimDir );
+
+ FireBulletsInfo_t info( 1, m_vecGunOrigin, aimDir, VECTOR_CONE_1DEGREES, MAX_TRACE_LENGTH, m_nAmmoType );
+
+ info.m_nFlags = FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS;
+ info.m_pAttacker = m_hPlayer;
+
+ FireBullets( info );
+
+ // Register a muzzleflash for the AI
+ if ( m_hPlayer )
+ {
+ m_hPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 );
+ }
+
+ CPASAttenuationFilter sndFilter( this, "PropJeep.FireCannon" );
+ EmitSound( sndFilter, entindex(), "PropJeep.FireCannon" );
+
+ // make cylinders of gun spin a bit
+ m_nSpinPos += JEEP_GUN_SPIN_RATE;
+ //SetPoseParameter( JEEP_GUN_SPIN, m_nSpinPos ); //FIXME: Don't bother with this for E3, won't look right
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::FireChargedCannon( void )
+{
+/* bool penetrated = false;
+
+ m_bCannonCharging = false;
+ m_flCannonTime = gpGlobals->curtime + 0.5f;
+
+ StopChargeSound();
+
+ CPASAttenuationFilter sndFilter( this, "PropJeep.FireChargedCannon" );
+ EmitSound( sndFilter, entindex(), "PropJeep.FireChargedCannon" );
+
+ //Find the direction the gun is pointing in
+ Vector aimDir;
+ GetCannonAim( &aimDir );
+
+ Vector endPos = m_vecGunOrigin + ( aimDir * MAX_TRACE_LENGTH );
+
+ //Shoot a shot straight out
+ trace_t tr;
+ UTIL_TraceLine( m_vecGunOrigin, endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ ClearMultiDamage();
+
+ //Find how much damage to do
+ float flChargeAmount = ( gpGlobals->curtime - m_flCannonChargeStartTime ) / MAX_GAUSS_CHARGE_TIME;
+
+ //Clamp this
+ if ( flChargeAmount > 1.0f )
+ {
+ flChargeAmount = 1.0f;
+ }
+
+ //Determine the damage amount
+ //FIXME: Use ConVars!
+ float flDamage = 15 + ( ( 250 - 15 ) * flChargeAmount );
+
+ CBaseEntity *pHit = tr.m_pEnt;
+
+ //Look for wall penetration
+ if ( tr.DidHitWorld() && !(tr.surface.flags & SURF_SKY) )
+ {
+ //Try wall penetration
+ UTIL_ImpactTrace( &tr, m_nBulletType, "ImpactJeep" );
+ UTIL_DecalTrace( &tr, "RedGlowFade" );
+
+ CPVSFilter filter( tr.endpos );
+ te->GaussExplosion( filter, 0.0f, tr.endpos, tr.plane.normal, 0 );
+
+ Vector testPos = tr.endpos + ( aimDir * 48.0f );
+
+ UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, GetDriver(), COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.allsolid == false )
+ {
+ UTIL_DecalTrace( &tr, "RedGlowFade" );
+
+ penetrated = true;
+ }
+ }
+ else if ( pHit != NULL )
+ {
+ CTakeDamageInfo dmgInfo( this, GetDriver(), flDamage, DMG_SHOCK );
+ CalculateBulletDamageForce( &dmgInfo, GetAmmoDef()->Index("GaussEnergy"), aimDir, tr.endpos, 1.0f + flChargeAmount * 4.0f );
+
+ //Do direct damage to anything in our path
+ pHit->DispatchTraceAttack( dmgInfo, aimDir, &tr );
+ }
+
+ ApplyMultiDamage();
+
+ //Kick up an effect
+ if ( !(tr.surface.flags & SURF_SKY) )
+ {
+ UTIL_ImpactTrace( &tr, m_nBulletType, "ImpactJeep" );
+
+ //Do a gauss explosion
+ CPVSFilter filter( tr.endpos );
+ te->GaussExplosion( filter, 0.0f, tr.endpos, tr.plane.normal, 0 );
+ }
+
+ //Show the effect
+ DrawBeam( m_vecGunOrigin, tr.endpos, 9.6 );
+
+ // Register a muzzleflash for the AI
+ if ( m_hPlayer )
+ {
+ m_hPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5f );
+ }
+
+ //Rock the car
+ IPhysicsObject *pObj = VPhysicsGetObject();
+
+ if ( pObj != NULL )
+ {
+ Vector shoveDir = aimDir * -( flDamage * 500.0f );
+
+ pObj->ApplyForceOffset( shoveDir, m_vecGunOrigin );
+ }
+
+ //Do radius damage if we didn't penetrate the wall
+ if ( penetrated == true )
+ {
+ RadiusDamage( CTakeDamageInfo( this, this, flDamage, DMG_SHOCK ), tr.endpos, 200.0f, CLASS_NONE, NULL );
+ } */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::ChargeCannon( void )
+{
+ //Don't fire again if it's been too soon
+ if ( m_flCannonTime > gpGlobals->curtime )
+ return;
+
+ //See if we're starting a charge
+ if ( m_bCannonCharging == false )
+ {
+ m_flCannonChargeStartTime = gpGlobals->curtime;
+ m_bCannonCharging = true;
+
+ //Start charging sound
+ CPASAttenuationFilter filter( this );
+ m_sndCannonCharge = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Jeep.GaussCharge", ATTN_NORM );
+
+ assert(m_sndCannonCharge!=NULL);
+ if ( m_sndCannonCharge != NULL )
+ {
+ (CSoundEnvelopeController::GetController()).Play( m_sndCannonCharge, 1.0f, 50 );
+ (CSoundEnvelopeController::GetController()).SoundChangePitch( m_sndCannonCharge, 250, 3.0f );
+ }
+
+ return;
+ }
+
+ //TODO: Add muzzle effect?
+
+ //TODO: Check for overcharge and have the weapon simply fire or instead "decharge"?
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::StopChargeSound( void )
+{
+ if ( m_sndCannonCharge != NULL )
+ {
+ (CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndCannonCharge, 0.1f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the true aiming position of the gun (looks at what player
+// is looking at and adjusts)
+// Input : &resultDir - direction to be calculated
+//-----------------------------------------------------------------------------
+void CPropJeep::GetCannonAim( Vector *resultDir )
+{
+ Vector muzzleOrigin;
+ QAngle muzzleAngles;
+
+ GetAttachment( LookupAttachment("gun_ref"), muzzleOrigin, muzzleAngles );
+
+ AngleVectors( muzzleAngles, resultDir );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead
+//-----------------------------------------------------------------------------
+void CPropJeep::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( pActivator );
+
+ if ( pPlayer == NULL)
+ return;
+
+ // Find out if the player's looking at our ammocrate hitbox
+ Vector vecForward;
+ pPlayer->EyeVectors( &vecForward, NULL, NULL );
+
+ trace_t tr;
+ Vector vecStart = pPlayer->EyePosition();
+ UTIL_TraceLine( vecStart, vecStart + vecForward * 1024, MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_HITBOX, pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt == this && tr.hitgroup == JEEP_AMMOCRATE_HITGROUP )
+ {
+ // Player's using the crate.
+ // Fill up his SMG ammo.
+ pPlayer->GiveAmmo( 300, "SMG1");
+
+ if ( ( GetSequence() != LookupSequence( "ammo_open" ) ) && ( GetSequence() != LookupSequence( "ammo_close" ) ) )
+ {
+ // Open the crate
+ m_flAnimTime = gpGlobals->curtime;
+ m_flPlaybackRate = 0.0;
+ SetCycle( 0 );
+ ResetSequence( LookupSequence( "ammo_open" ) );
+
+ CPASAttenuationFilter sndFilter( this, "PropJeep.AmmoOpen" );
+ EmitSound( sndFilter, entindex(), "PropJeep.AmmoOpen" );
+ }
+
+ m_flAmmoCrateCloseTime = gpGlobals->curtime + JEEP_AMMO_CRATE_CLOSE_DELAY;
+ return;
+ }
+
+ // Fall back and get in the vehicle instead
+ BaseClass::Use( pActivator, pCaller, useType, value );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPropJeep::CanExitVehicle( CBaseEntity *pEntity )
+{
+ return ( !m_bEnterAnimOn && !m_bExitAnimOn && !m_bLocked && (m_nSpeed <= g_jeepexitspeed.GetFloat() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles )
+{
+ // Get the frametime. (Check to see if enough time has passed to warrent dampening).
+ float flFrameTime = gpGlobals->frametime;
+ if ( flFrameTime < JEEP_FRAMETIME_MIN )
+ {
+ vecVehicleEyePos = m_vecLastEyePos;
+ DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, 0.0f );
+ return;
+ }
+
+ // Keep static the sideways motion.
+
+ // Dampen forward/backward motion.
+ DampenForwardMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime );
+
+ // Blend up/down motion.
+ DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime );
+}
+
+//-----------------------------------------------------------------------------
+// Use the controller as follows:
+// speed += ( pCoefficientsOut[0] * ( targetPos - currentPos ) + pCoefficientsOut[1] * ( targetSpeed - currentSpeed ) ) * flDeltaTime;
+//-----------------------------------------------------------------------------
+void CPropJeep::ComputePDControllerCoefficients( float *pCoefficientsOut,
+ float flFrequency, float flDampening,
+ float flDeltaTime )
+{
+ float flKs = 9.0f * flFrequency * flFrequency;
+ float flKd = 4.5f * flFrequency * flDampening;
+
+ float flScale = 1.0f / ( 1.0f + flKd * flDeltaTime + flKs * flDeltaTime * flDeltaTime );
+
+ pCoefficientsOut[0] = flKs * flScale;
+ pCoefficientsOut[1] = ( flKd + flKs * flDeltaTime ) * flScale;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime )
+{
+ // Get forward vector.
+ Vector vecForward;
+ AngleVectors( vecVehicleEyeAngles, &vecForward);
+
+ // Simulate the eye position forward based on the data from last frame
+ // (assumes no acceleration - it will get that from the "spring").
+ Vector vecCurrentEyePos = m_vecLastEyePos + m_vecEyeSpeed * flFrameTime;
+
+ // Calculate target speed based on the current vehicle eye position and the last vehicle eye position and frametime.
+ Vector vecVehicleEyeSpeed = ( vecVehicleEyePos - m_vecLastEyeTarget ) / flFrameTime;
+ m_vecLastEyeTarget = vecVehicleEyePos;
+
+ // Calculate the speed and position deltas.
+ Vector vecDeltaSpeed = vecVehicleEyeSpeed - m_vecEyeSpeed;
+ Vector vecDeltaPos = vecVehicleEyePos - vecCurrentEyePos;
+
+ // Clamp.
+ if ( vecDeltaPos.Length() > JEEP_DELTA_LENGTH_MAX )
+ {
+ float flSign = vecForward.Dot( vecVehicleEyeSpeed ) >= 0.0f ? -1.0f : 1.0f;
+ vecVehicleEyePos += flSign * ( vecForward * JEEP_DELTA_LENGTH_MAX );
+ m_vecLastEyePos = vecVehicleEyePos;
+ m_vecEyeSpeed = vecVehicleEyeSpeed;
+ return;
+ }
+
+ // Generate an updated (dampening) speed for use in next frames position extrapolation.
+ float flCoefficients[2];
+ ComputePDControllerCoefficients( flCoefficients, r_JeepViewDampenFreq.GetFloat(), r_JeepViewDampenDamp.GetFloat(), flFrameTime );
+ m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime );
+
+ // Save off data for next frame.
+ m_vecLastEyePos = vecCurrentEyePos;
+
+ // Move eye forward/backward.
+ Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) );
+ vecVehicleEyePos -= vecForwardOffset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime )
+{
+ // Get up vector.
+ Vector vecUp;
+ AngleVectors( vecVehicleEyeAngles, NULL, NULL, &vecUp );
+ vecUp.z = clamp( vecUp.z, 0.0f, vecUp.z );
+ vecVehicleEyePos.z += r_JeepViewZHeight.GetFloat() * vecUp.z;
+
+ // NOTE: Should probably use some damped equation here.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ // If we are overturned and hit any key - leave the vehicle (IN_USE is already handled!).
+ if ( m_flOverturnedTime > OVERTURNED_EXIT_WAITTIME )
+ {
+ if ( (ucmd->buttons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT|IN_SPEED|IN_JUMP|IN_ATTACK|IN_ATTACK2) ) && !m_bExitAnimOn )
+ {
+ // Can't exit yet? We're probably still moving. Swallow the keys.
+ if ( !CanExitVehicle(player) )
+ return;
+
+ if ( !GetServerVehicle()->HandlePassengerExit( m_hPlayer ) && ( m_hPlayer != NULL ) )
+ {
+ m_hPlayer->PlayUseDenySound();
+ }
+ return;
+ }
+ }
+
+ // If the throttle is disabled or we're upside-down, don't allow throttling (including turbo)
+ CUserCmd tmp;
+ if ( ( m_throttleDisableTime > gpGlobals->curtime ) || ( IsOverturned() ) )
+ {
+ m_bUnableToFire = true;
+
+ tmp = (*ucmd);
+ tmp.buttons &= ~(IN_FORWARD|IN_BACK|IN_SPEED);
+ ucmd = &tmp;
+ }
+
+ BaseClass::SetupMove( player, ucmd, pHelper, move );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
+{
+ int iButtons = ucmd->buttons;
+
+ //Adrian: No headlights on Superfly.
+/* if ( ucmd->impulse == 100 )
+ {
+ if (HeadlightIsOn())
+ {
+ HeadlightTurnOff();
+ }
+ else
+ {
+ HeadlightTurnOn();
+ }
+ }*/
+
+ // If we're holding down an attack button, update our state
+ if ( IsOverturned() == false )
+ {
+ if ( iButtons & IN_ATTACK )
+ {
+ if ( m_bCannonCharging )
+ {
+ FireChargedCannon();
+ }
+ else
+ {
+ FireCannon();
+ }
+ }
+ else if ( iButtons & IN_ATTACK2 )
+ {
+ ChargeCannon();
+ }
+ }
+
+ // If we've released our secondary button, fire off our cannon
+ if ( ( iButtonsReleased & IN_ATTACK2 ) && ( m_bCannonCharging ) )
+ {
+ FireChargedCannon();
+ }
+
+ BaseClass::DriveVehicle( flFrameTime, ucmd, iButtonsDown, iButtonsReleased );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+// *pMoveData -
+//-----------------------------------------------------------------------------
+void CPropJeep::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData )
+{
+ BaseClass::ProcessMovement( pPlayer, pMoveData );
+
+ // Create dangers sounds in front of the vehicle.
+ CreateDangerSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create danger sounds in front of the vehicle.
+//-----------------------------------------------------------------------------
+void CPropJeep::CreateDangerSounds( void )
+{
+ QAngle dummy;
+ GetAttachment( "Muzzle", m_vecGunOrigin, dummy );
+
+ if ( m_flDangerSoundTime > gpGlobals->curtime )
+ return;
+
+ QAngle vehicleAngles = GetLocalAngles();
+ Vector vecStart = GetAbsOrigin();
+ Vector vecDir, vecRight;
+
+ GetVectors( &vecDir, &vecRight, NULL );
+
+ const float soundDuration = 0.25;
+ float speed = m_VehiclePhysics.GetHLSpeed();
+ // Make danger sounds ahead of the jeep
+ if ( fabs(speed) > 120 )
+ {
+ Vector vecSpot;
+
+ float steering = m_VehiclePhysics.GetSteering();
+ if ( steering != 0 )
+ {
+ if ( speed > 0 )
+ {
+ vecDir += vecRight * steering * 0.5;
+ }
+ else
+ {
+ vecDir -= vecRight * steering * 0.5;
+ }
+ VectorNormalize(vecDir);
+ }
+ const float radius = speed * 0.4;
+ // 0.3 seconds ahead of the jeep
+ vecSpot = vecStart + vecDir * (speed * 0.3f);
+ CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, radius, soundDuration, this, 0 );
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, radius, soundDuration, this, 1 );
+ //NDebugOverlay::Box(vecSpot, Vector(-radius,-radius,-radius),Vector(radius,radius,radius), 255, 0, 255, 0, soundDuration);
+
+#if 0
+ trace_t tr;
+ // put sounds a bit to left and right but slightly closer to Jeep to make a "cone" of sound
+ // in front of it
+ vecSpot = vecStart + vecDir * (speed * 0.5f) - vecRight * speed * 0.5;
+ UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, soundDuration, this, 1 );
+
+ vecSpot = vecStart + vecDir * (speed * 0.5f) + vecRight * speed * 0.5;
+ UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, soundDuration, this, 2);
+#endif
+ }
+
+ m_flDangerSoundTime = gpGlobals->curtime + 0.1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::EnterVehicle( CBasePlayer *pPlayer )
+{
+ if ( !pPlayer )
+ return;
+
+ CheckWater();
+ BaseClass::EnterVehicle( pPlayer );
+
+ // Start looking for seagulls to land
+ m_hLastPlayerInVehicle = m_hPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::ExitVehicle( int nRole )
+{
+ HeadlightTurnOff();
+
+ BaseClass::ExitVehicle( nRole );
+
+ //If the player has exited, stop charging
+ StopChargeSound();
+ m_bCannonCharging = false;
+
+ // Remember when we last saw the player
+ m_flPlayerExitedTime = gpGlobals->curtime;
+ m_flLastSawPlayerAt = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::InputStartRemoveTauCannon( inputdata_t &inputdata )
+{
+ // Start the gun removal animation
+ m_flAnimTime = gpGlobals->curtime;
+ m_flPlaybackRate = 0.0;
+ SetCycle( 0 );
+ ResetSequence( LookupSequence( "tau_levitate" ) );
+
+ m_bGunHasBeenCutOff = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::InputFinishRemoveTauCannon( inputdata_t &inputdata )
+{
+ // Remove & hide the gun
+ SetBodygroup( 1, false );
+ m_bHasGun = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeep::OnRestore( void )
+{
+ IServerVehicle *pServerVehicle = GetServerVehicle();
+ if ( pServerVehicle != NULL )
+ {
+ // Restore the passenger information we're holding on to
+ pServerVehicle->RestorePassengerInfo();
+ }
+}
+
+//========================================================================================================================================
+// JEEP FOUR WHEEL PHYSICS VEHICLE SERVER VEHICLE
+//========================================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CJeepFourWheelServerVehicle::NPC_AimPrimaryWeapon( Vector vecTarget )
+{
+ ((CPropJeep*)m_pVehicle)->AimGunAt( &vecTarget, 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &vecEyeExitEndpoint -
+// Output : int
+//-----------------------------------------------------------------------------
+int CJeepFourWheelServerVehicle::GetExitAnimToUse( Vector &vecEyeExitEndpoint, bool &bAllPointsBlocked )
+{
+ bAllPointsBlocked = false;
+
+ if ( !m_bParsedAnimations )
+ {
+ // Load the entry/exit animations from the vehicle
+ ParseEntryExitAnims();
+ m_bParsedAnimations = true;
+ }
+
+ CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle);
+ // If we don't have the gun anymore, we want to get out using the "gun-less" animation
+ if ( pAnimating && ((CPropJeep*)m_pVehicle)->TauCannonHasBeenCutOff() )
+ {
+ // HACK: We know the tau-cannon removed exit anim uses the first upright anim's exit details
+ trace_t tr;
+
+ // Convert our offset points to worldspace ones
+ Vector vehicleExitOrigin = m_ExitAnimations[0].vecExitPointLocal;
+ QAngle vehicleExitAngles = m_ExitAnimations[0].vecExitAnglesLocal;
+ UTIL_ParentToWorldSpace( pAnimating, vehicleExitOrigin, vehicleExitAngles );
+
+ // Ensure the endpoint is clear by dropping a point down from above
+ vehicleExitOrigin -= VEC_VIEW;
+ Vector vecMove = Vector(0,0,64);
+ Vector vecStart = vehicleExitOrigin + vecMove;
+ Vector vecEnd = vehicleExitOrigin - vecMove;
+ UTIL_TraceHull( vecStart, vecEnd, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
+
+ Assert( !tr.startsolid && tr.fraction < 1.0 );
+ m_vecCurrentExitEndPoint = vecStart + ((vecEnd - vecStart) * tr.fraction);
+ vecEyeExitEndpoint = m_vecCurrentExitEndPoint + VEC_VIEW;
+ m_iCurrentExitAnim = 0;
+ return pAnimating->LookupSequence( "exit_tauremoved" );
+ }
+
+ return BaseClass::GetExitAnimToUse( vecEyeExitEndpoint, bAllPointsBlocked );
+}
diff --git a/game/server/cstrike/func_bomb_target.cpp b/game/server/cstrike/func_bomb_target.cpp
new file mode 100644
index 0000000..a85d646
--- /dev/null
+++ b/game/server/cstrike/func_bomb_target.cpp
@@ -0,0 +1,84 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bomb target area
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "cs_player.h"
+#include "func_bomb_target.h"
+#include "cs_gamerules.h"
+
+LINK_ENTITY_TO_CLASS( func_bomb_target, CBombTarget );
+
+BEGIN_DATADESC( CBombTarget )
+ DEFINE_FUNCTION( BombTargetTouch ),
+ DEFINE_FUNCTION( BombTargetUse ), //needed?
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "BombExplode", OnBombExplode ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BombPlanted", OnBombPlanted ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BombDefused", OnBombDefused ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnBombExplode, "BombExplode" ),
+ DEFINE_OUTPUT( m_OnBombPlanted, "BombPlanted" ),
+ DEFINE_OUTPUT( m_OnBombDefused, "BombDefused" ),
+ DEFINE_KEYFIELD( m_bIsHeistBombTarget, FIELD_BOOLEAN, "heistbomb" ),
+ DEFINE_KEYFIELD( m_szMountTarget, FIELD_STRING, "bomb_mount_target" ),
+
+END_DATADESC()
+
+CBombTarget::CBombTarget( void )
+{
+ m_bIsHeistBombTarget = false;
+ m_szMountTarget = NULL_STRING;
+}
+
+void CBombTarget::Spawn()
+{
+ InitTrigger();
+ SetTouch( &CBombTarget::BombTargetTouch );
+ SetUse( &CBombTarget::BombTargetUse );
+}
+
+void CBombTarget::BombTargetTouch( CBaseEntity* pOther )
+{
+ CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
+ if ( p )
+ {
+ if ( p->HasC4() && CSGameRules()->m_bBombPlanted == false )
+ {
+ p->m_bInBombZone = true;
+ p->m_iBombSiteIndex = entindex();
+ if ( !(p->m_iDisplayHistoryBits & DHF_IN_TARGET_ZONE) )
+ {
+ p->HintMessage( "#Hint_you_are_in_targetzone", false );
+ p->m_iDisplayHistoryBits |= DHF_IN_TARGET_ZONE;
+ }
+ }
+ }
+}
+
+void CBombTarget::BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ //SUB_UseTargets( NULL, USE_TOGGLE, 0 );
+ DevMsg( 2, "BombTargetUse does nothing\n" );
+}
+
+// Relay to our outputs
+void CBombTarget::OnBombExplode( inputdata_t &inputdata )
+{
+ m_OnBombExplode.FireOutput(this, this);
+}
+
+// Relay to our outputs
+void CBombTarget::OnBombPlanted( inputdata_t &inputdata )
+{
+ m_OnBombPlanted.FireOutput(this, this);
+}
+// Relay to our outputs
+void CBombTarget::OnBombDefused( inputdata_t &inputdata )
+{
+ m_OnBombDefused.FireOutput(this, this);
+} \ No newline at end of file
diff --git a/game/server/cstrike/func_bomb_target.h b/game/server/cstrike/func_bomb_target.h
new file mode 100644
index 0000000..090b81d
--- /dev/null
+++ b/game/server/cstrike/func_bomb_target.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bomb Target Area ent
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "triggers.h"
+
+class CBombTarget : public CBaseTrigger
+{
+public:
+ DECLARE_CLASS( CBombTarget, CBaseTrigger );
+ DECLARE_DATADESC();
+
+ CBombTarget();
+
+ void Spawn();
+ void EXPORT BombTargetTouch( CBaseEntity* pOther );
+ void EXPORT BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ void OnBombExplode( inputdata_t &inputdata );
+ void OnBombPlanted( inputdata_t &inputdata );
+ void OnBombDefused( inputdata_t &inputdata );
+
+ bool IsHeistBombTarget( void ) { return m_bIsHeistBombTarget; }
+ const char *GetBombMountTarget( void ){ return STRING( m_szMountTarget ); }
+
+private:
+ COutputEvent m_OnBombExplode; //Fired when the bomb explodes
+ COutputEvent m_OnBombPlanted; //Fired when the bomb is planted
+ COutputEvent m_OnBombDefused; //Fired when the bomb is defused
+
+ bool m_bIsHeistBombTarget;
+ string_t m_szMountTarget;
+}; \ No newline at end of file
diff --git a/game/server/cstrike/func_buy_zone.cpp b/game/server/cstrike/func_buy_zone.cpp
new file mode 100644
index 0000000..a982fe4
--- /dev/null
+++ b/game/server/cstrike/func_buy_zone.cpp
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "triggers.h"
+#include "cs_player.h"
+
+
+//======================================
+// Bomb target area
+//
+//
+
+class CBuyZone : public CBaseTrigger
+{
+public:
+ DECLARE_CLASS( CBuyZone, CBaseTrigger );
+ DECLARE_DATADESC();
+
+ CBuyZone();
+ void Spawn();
+ void EXPORT BuyZoneTouch( CBaseEntity* pOther );
+
+public:
+ int m_LegacyTeamNum;
+};
+
+
+LINK_ENTITY_TO_CLASS( func_buyzone, CBuyZone );
+
+BEGIN_DATADESC( CBuyZone )
+ DEFINE_FUNCTION( BuyZoneTouch ),
+
+ // This is here to support maps that haven't updated to using "teamnum" yet.
+ DEFINE_INPUT( m_LegacyTeamNum, FIELD_INTEGER, "team" )
+END_DATADESC()
+
+
+CBuyZone::CBuyZone()
+{
+ m_LegacyTeamNum = -1;
+}
+
+
+void CBuyZone::Spawn()
+{
+ InitTrigger();
+ SetTouch( &CBuyZone::BuyZoneTouch );
+
+ // Support for legacy-style teamnums.
+ if ( m_LegacyTeamNum == 1 )
+ {
+ ChangeTeam( TEAM_TERRORIST );
+ }
+ else if ( m_LegacyTeamNum == 2 )
+ {
+ ChangeTeam( TEAM_CT );
+ }
+}
+
+
+void CBuyZone::BuyZoneTouch( CBaseEntity* pOther )
+{
+ CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
+ if ( p )
+ {
+ // compare player team with buy zone team number
+ if ( p->GetTeamNumber() == GetTeamNumber() )
+ {
+ p->m_bInBuyZone = true;
+ }
+ }
+}
+
diff --git a/game/server/cstrike/func_hostage_rescue.cpp b/game/server/cstrike/func_hostage_rescue.cpp
new file mode 100644
index 0000000..5a5166f
--- /dev/null
+++ b/game/server/cstrike/func_hostage_rescue.cpp
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "triggers.h"
+
+class CHostageRescueZone : public CBaseTrigger
+{
+public:
+ DECLARE_CLASS( CHostageRescueZone, CBaseTrigger );
+ DECLARE_DATADESC();
+
+ void CHostageRescue();
+ void Spawn();
+
+ void HostageRescueTouch( CBaseEntity* pOther );
+};
+
+
+LINK_ENTITY_TO_CLASS( func_hostage_rescue, CHostageRescueZone );
+
+
+BEGIN_DATADESC( CHostageRescueZone )
+
+ //Functions
+ DEFINE_FUNCTION( HostageRescueTouch ),
+
+END_DATADESC()
+
+
+void CHostageRescueZone::Spawn()
+{
+ InitTrigger();
+ SetTouch( &CHostageRescueZone::HostageRescueTouch );
+}
+
+void CHostageRescueZone::HostageRescueTouch( CBaseEntity *pOther )
+{
+ variant_t emptyVariant;
+ pOther->AcceptInput( "OnRescueZoneTouch", NULL, NULL, emptyVariant, 0 );
+}
+
diff --git a/game/server/cstrike/funfact_cs.cpp b/game/server/cstrike/funfact_cs.cpp
new file mode 100644
index 0000000..e880359
--- /dev/null
+++ b/game/server/cstrike/funfact_cs.cpp
@@ -0,0 +1,793 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "cs_gamestats.h"
+#include "funfactmgr_cs.h"
+#include "funfact_cs.h"
+#include "../../game/shared/cstrike/weapon_csbase.h"
+#include "cs_achievement_constants.h"
+
+#define FIRST_BLOOD_TIME 45.0f
+#define FIRST_KILL_TIME 45.0f
+#define SHORT_ROUND_TIME 30.0f
+#define MIN_SHOTS_FOR_ACCURACY 10
+
+enum FunFactId
+{
+ FUNFACT_CT_WIN_NO_KILLS,
+ FUNFACT_T_WIN_NO_KILLS,
+ FUNFACT_KILL_DEFUSER,
+ FUNFACT_KILL_RESCUER,
+ FUNFACT_T_WIN_NO_CASUALTIES,
+ FUNFACT_CT_WIN_NO_CASUALTIES,
+ FUNFACT_DAMAGE_WITH_GRENADES,
+ FUNFACT_KILLS_WITH_GRENADES,
+ FUNFACT_KILLS_WITH_SINGLE_GRENADE,
+ FUNFACT_DAMAGE_NO_KILLS,
+ FUNFACT_KILLED_ENEMIES,
+ FUNFACT_FIRST_KILL,
+ FUNFACT_FIRST_BLOOD,
+ FUNFACT_SHORT_ROUND,
+ FUNFACT_BEST_ACCURACY,
+ FUNFACT_KNIFE_KILLS,
+ FUNFACT_BLIND_KILLS,
+ FUNFACT_KILLS_WITH_LAST_ROUND,
+ FUNFACT_DONATED_WEAPONS,
+ FUNFACT_POSTHUMOUS_KILLS_WITH_GRENADE,
+ FUNFACT_KNIFE_IN_GUNFIGHT,
+ FUNFACT_NUM_TIMES_JUMPED,
+ FUNFACT_FALL_DAMAGE,
+ FUNFACT_ITEMS_PURCHASED,
+ FUNFACT_WON_AS_LAST_MEMBER,
+ FUNFACT_NUMBER_OF_OVERKILLS,
+ FUNFACT_SHOTS_FIRED,
+ FUNFACT_MONEY_SPENT,
+ FUNFACT_SURVIVED_MULTIPLE_ATTACKERS,
+ FUNFACT_DIED_FROM_MULTIPLE_ATTACKERS,
+ FUNFACT_DAMAGE_MULTIPLE_ENEMIES,
+ FUNFACT_GRENADES_THROWN,
+ FUNFACT_USED_ALL_AMMO,
+ FUNFACT_DEFENDED_BOMB,
+ FUNFACT_ITEMS_DROPPED_VALUE,
+ FUNFACT_KILL_WOUNDED_ENEMIES,
+ FUNFACT_USED_MULTIPLE_WEAPONS,
+ FUNFACT_TERRORIST_ACCURACY,
+ FUNFACT_CT_ACCURACY,
+ FUNFACT_SAME_UNIFORM_TERRORIST,
+ FUNFACT_SAME_UNIFORM_CT,
+ FUNFACT_BEST_TERRORIST_ACCURACY,
+ FUNFACT_BEST_COUNTERTERRORIST_ACCURACY,
+ FUNFACT_FALLBACK1,
+ FUNFACT_FALLBACK2,
+ FUNFACT_KILLS_HEADSHOTS,
+ FUNFACT_BROKE_WINDOWS,
+ FUNFACT_NIGHTVISION_DAMAGE,
+ FUNFACT_DEFUSED_WITH_DROPPED_KIT,
+ FUNFACT_KILLED_HALF_OF_ENEMIES,
+};
+
+
+CFunFactHelper *CFunFactHelper::s_pFirst = NULL;
+
+
+//=============================================================================
+// Generic evaluation Fun Fact
+// This fun fact will evaluate the specified function to determine when it is
+// valid. This is basically just a glue class for simple evaluation functions.
+//=============================================================================
+
+// Function type that we use to evaluate our fun facts. The data is returned as ints then floats that are passed in as reference parameters
+typedef bool (*fFunFactEval)( int &iPlayer, int &data1, int &data2, int &data3 );
+
+class CFunFact_GenericEvalFunction : public FunFactEvaluator
+{
+public:
+ CFunFact_GenericEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, fFunFactEval pfnEval ) :
+ FunFactEvaluator(id, szLocalizationToken, fCoolness),
+ m_pfnEval(pfnEval)
+ {}
+
+ virtual bool Evaluate( FunFactVector& results ) const
+ {
+ FunFact funfact;
+ if (m_pfnEval(funfact.iPlayer, funfact.iData1, funfact.iData2, funfact.iData3))
+ {
+ funfact.id = GetId();
+ funfact.szLocalizationToken = GetLocalizationToken();
+ funfact.fMagnitude = 0.0f;
+ results.AddToTail(funfact);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ private:
+ fFunFactEval m_pfnEval;
+};
+#define DECLARE_FUNFACT_EVALFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval) \
+static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
+{ \
+ return new CFunFact_GenericEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval);\
+}; \
+static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
+
+
+//=============================================================================
+// Per-player evaluation Fun Fact
+// Evaluate the function per player and generate a fun fact for each valid or
+// highest valid player
+//=============================================================================
+
+namespace EvalFlags
+{
+ enum Type
+ {
+ All = 0x00,
+ TeamCT = 0x01,
+ TeamTerrorist = 0x02,
+ HighestOnly = 0x04, // when not set, generates fun facts for all valid testees
+ Alive = 0x08,
+ Dead = 0x10,
+ WinningTeam = 0x20,
+ LosingTeam = 0x40,
+ };
+};
+
+
+bool PlayerQualifies( const CBasePlayer* pPlayer, int flags )
+{
+ if ( (flags & EvalFlags::TeamCT) && pPlayer->GetTeamNumber() != TEAM_CT )
+ return false;
+ if ( (flags & EvalFlags::TeamTerrorist) && pPlayer->GetTeamNumber() != TEAM_TERRORIST )
+ return false;
+ if ( (flags & EvalFlags::Dead) && const_cast<CBasePlayer*>(pPlayer)->IsAlive() ) // IsAlive() really isn't const correct
+ return false;
+ if ( (flags & EvalFlags::Alive) && !const_cast<CBasePlayer*>(pPlayer)->IsAlive() )
+ return false;
+ if ( (flags & EvalFlags::WinningTeam) && pPlayer->GetTeamNumber() != CSGameRules()->m_iRoundWinStatus )
+ return false;
+ if ( (flags & EvalFlags::LosingTeam) && pPlayer->GetTeamNumber() == CSGameRules()->m_iRoundWinStatus )
+ return false;
+ return true;
+}
+
+
+typedef int (*PlayerEvalFunction)(CCSPlayer* pPlayer);
+
+class CFunFact_PlayerEvalFunction : public FunFactEvaluator
+{
+public:
+ CFunFact_PlayerEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, PlayerEvalFunction pfnEval,
+ int iMin, int flags ) :
+ FunFactEvaluator(id, szLocalizationToken, fCoolness),
+ m_pfnEval(pfnEval),
+ m_min(iMin),
+ m_flags(flags)
+ {}
+
+ virtual bool Evaluate( FunFactVector& results ) const
+ {
+ int iBestValue = 0;
+ int iBestPlayer = 0;
+ bool bResult = false;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer* pPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
+ if ( pPlayer )
+ {
+ if (!PlayerQualifies(pPlayer, m_flags))
+ continue;
+
+ int iValue = m_pfnEval(pPlayer);
+
+ if (m_flags & EvalFlags::HighestOnly)
+ {
+ if ( iValue > iBestValue )
+ {
+ iBestValue = iValue;
+ iBestPlayer = i;
+ }
+ }
+ else
+ {
+ // generate fun facts for any player who meets the validation requirement
+ if ( iValue >= m_min )
+ {
+ FunFact funfact;
+ funfact.id = GetId();
+ funfact.szLocalizationToken = GetLocalizationToken();
+ funfact.iPlayer = i;
+ funfact.iData1 = iValue;
+ funfact.fMagnitude = 1.0f - ((float)m_min / iValue);
+ results.AddToTail(funfact);
+ bResult = true;
+ }
+ }
+ }
+ }
+ if ( (m_flags & EvalFlags::HighestOnly) && iBestValue >= m_min )
+ {
+ FunFact funfact;
+ funfact.id = GetId();
+ funfact.szLocalizationToken = GetLocalizationToken();
+ funfact.iPlayer = iBestPlayer;
+ funfact.iData1 = iBestValue;
+ funfact.fMagnitude = 1.0f - ((float)m_min / iBestValue);
+
+ results.AddToTail(funfact);
+ bResult = true;
+ }
+ return bResult;
+ }
+
+private:
+ PlayerEvalFunction m_pfnEval;
+ int m_min;
+ int m_flags;
+};
+#define DECLARE_FUNFACT_PLAYERFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval, iMin, iFlags) \
+static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
+{ \
+ return new CFunFact_PlayerEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval, iMin, iFlags); \
+}; \
+static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
+
+
+//=============================================================================
+// Per-team evaluation Fun Fact
+//=============================================================================
+
+typedef bool (*TeamEvalFunction)(int iTeam, int &data1, int &data2, int &data3);
+
+class CFunFact_TeamEvalFunction : public FunFactEvaluator
+{
+public:
+ CFunFact_TeamEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, TeamEvalFunction pfnEval, int iTeam ) :
+ FunFactEvaluator(id, szLocalizationToken, fCoolness),
+ m_pfnEval(pfnEval),
+ m_team(iTeam)
+ {}
+
+ virtual bool Evaluate( FunFactVector& results ) const
+ {
+ int iData1, iData2, iData3;
+ if ( m_pfnEval(m_team, iData1, iData2, iData3) )
+ {
+ FunFact funfact;
+ funfact.id = GetId();
+ funfact.szLocalizationToken = GetLocalizationToken();
+ funfact.fMagnitude = 0.0f;
+ results.AddToTail(funfact);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ TeamEvalFunction m_pfnEval;
+ int m_team;
+};
+#define DECLARE_FUNFACT_TEAMFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval, iTeam) \
+static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
+{ \
+ return new CFunFact_TeamEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval, iTeam);\
+}; \
+static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
+
+
+//=============================================================================
+// High Stat-based Fun Fact
+// This fun fact will find the player with the highest value for a particular
+// stat, and validate when that stat exceeds a specified minimum
+//=============================================================================
+class CFunFact_StatBest : public FunFactEvaluator
+{
+public:
+ CFunFact_StatBest(FunFactId id, const char* szLocalizationToken, float fCoolness, CSStatType_t statId, int iMin, int flags ) :
+ FunFactEvaluator(id, szLocalizationToken, fCoolness),
+ m_statId(statId),
+ m_min(iMin),
+ m_flags(flags)
+ {
+ V_strncpy(m_singularLocalizationToken, szLocalizationToken, sizeof(m_singularLocalizationToken));
+ if (m_min == 1)
+ {
+ V_strncat(m_singularLocalizationToken, "_singular", sizeof(m_singularLocalizationToken));
+ }
+ }
+
+ virtual bool Evaluate( FunFactVector& results ) const
+ {
+ int iBestValue = 0;
+ int iBestPlayer = 0;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ if (!PlayerQualifies(pPlayer, m_flags))
+ continue;
+
+ int iValue = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[m_statId];
+ if ( iValue > iBestValue )
+ {
+ iBestValue = iValue;
+ iBestPlayer = i;
+ }
+ }
+ }
+ if ( iBestValue >= m_min )
+ {
+ FunFact funfact;
+ funfact.id = GetId();
+ funfact.szLocalizationToken = iBestValue == 1 ? m_singularLocalizationToken : GetLocalizationToken();
+ funfact.iPlayer = iBestPlayer;
+ funfact.iData1 = iBestValue;
+ funfact.fMagnitude = 1.0f - ((float)m_min / iBestValue);
+
+ results.AddToTail(funfact);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ CSStatType_t m_statId;
+ int m_min;
+ char m_singularLocalizationToken[128];
+ int m_flags;
+
+};
+#define DECLARE_FUNFACT_STATBEST(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags) \
+static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
+{ \
+ return new CFunFact_StatBest(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags); \
+}; \
+static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
+
+
+//=============================================================================
+// Sum-based Fun Fact
+// This fun fact will add up a stat for all players, and is valid when the
+// sum exceeds a threshold
+//=============================================================================
+class CFunFact_StatSum : public FunFactEvaluator
+{
+public:
+ CFunFact_StatSum(FunFactId id, const char* szLocalizationToken, float fCoolness, CSStatType_t statId, int iMin, EvalFlags::Type flags ) :
+ FunFactEvaluator(id, szLocalizationToken, fCoolness),
+ m_statId(statId),
+ m_min(iMin),
+ m_flags(flags)
+ {}
+
+ virtual bool Evaluate( FunFactVector& results ) const
+ {
+ int iSum = 0;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ if (!PlayerQualifies(pPlayer, m_flags))
+ continue;
+
+ iSum += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[m_statId];
+ }
+ }
+ if ( iSum >= m_min )
+ {
+ FunFact funfact;
+ funfact.id = GetId();
+ funfact.szLocalizationToken = GetLocalizationToken();
+ funfact.iPlayer = 0;
+ funfact.iData1 = iSum;
+ funfact.fMagnitude = 1.0f - ((float)m_min / iSum);
+
+ results.AddToTail(funfact);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ CSStatType_t m_statId;
+ int m_min;
+ int m_flags;
+
+};
+#define DECLARE_FUNFACT_STATSUM(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags) \
+static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
+{ \
+ return new CFunFact_StatSum(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags); \
+}; \
+static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
+
+
+
+//=============================================================================
+// Helper function to calculate team accuracy
+//=============================================================================
+
+float GetTeamAccuracy( int teamNumber )
+{
+ int teamShots = 0;
+ int teamHits = 0;
+
+ //Add up hits and shots
+ CBasePlayer *pPlayer = NULL;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ pPlayer = UTIL_PlayerByIndex( i );
+ if (pPlayer)
+ {
+ if (pPlayer->GetTeamNumber() == teamNumber)
+ {
+ teamShots += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];;
+ teamHits += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];;
+ }
+ }
+ }
+
+ if (teamShots > MIN_SHOTS_FOR_ACCURACY)
+ return (float)teamHits / teamShots;
+
+ return 0.0f;
+}
+
+
+
+//=============================================================================
+// fun fact explicit evaluation functions
+//=============================================================================
+
+bool FFEVAL_ALWAYS_TRUE( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ return true;
+}
+
+bool FFEVAL_CT_WIN_NO_KILLS( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ return ( CSGameRules()->m_iRoundWinStatus == WINNER_CT && CSGameRules()->m_bNoTerroristsKilled );
+}
+
+bool FFEVAL_T_WIN_NO_KILLS( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ return ( CSGameRules()->m_iRoundWinStatus == WINNER_TER && CSGameRules()->m_bNoCTsKilled );
+}
+
+bool FFEVAL_T_WIN_NO_CASUALTIES( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ return ( CSGameRules()->m_iRoundWinStatus == WINNER_TER && CSGameRules()->m_bNoTerroristsKilled );
+}
+
+bool FFEVAL_CT_WIN_NO_CASUALTIES( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ return ( CSGameRules()->m_iRoundWinStatus == WINNER_CT && CSGameRules()->m_bNoCTsKilled );
+}
+
+int FFEVAL_KILLED_DEFUSER( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetKilledDefuser() ? 1 : 0;
+}
+
+int FFEVAL_KILLED_RESCUER( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetKilledRescuer() ? 1 : 0;
+}
+
+int FFEVAL_KILLS_WITH_GRENADE( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetMaxGrenadeKills();
+}
+
+int FFEVAL_DAMAGE_NO_KILLS( CCSPlayer* pPlayer )
+{
+ if (CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_KILLS] == 0)
+ return CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_DAMAGE];
+ else
+ return 0;
+}
+
+int FFEVAL_FIRST_KILL( CCSPlayer* pPlayer )
+{
+ if ( pPlayer == CSGameRules()->m_pFirstKill && CSGameRules()->m_firstKillTime < FIRST_KILL_TIME )
+ return CSGameRules()->m_firstKillTime;
+ else
+ return 0;
+}
+
+int FFEVAL_FIRST_BLOOD( CCSPlayer* pPlayer )
+{
+ if ( pPlayer == CSGameRules()->m_pFirstBlood && CSGameRules()->m_firstBloodTime < FIRST_BLOOD_TIME )
+ return CSGameRules()->m_firstBloodTime;
+ else
+ return 0;
+}
+
+bool FFEVAL_SHORT_ROUND( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ if ( CSGameRules()->GetRoundLength() - CSGameRules()->GetRoundRemainingTime() < SHORT_ROUND_TIME )
+ {
+ data1 = CSGameRules()->GetRoundLength() - CSGameRules()->GetRoundRemainingTime();
+ return true;
+ }
+ return false;
+}
+
+int FFEVAL_ACCURACY( CCSPlayer* pPlayer )
+{
+ float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
+ float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
+ if (shots >= MIN_SHOTS_FOR_ACCURACY)
+ return RoundFloatToInt(100.0f * hits / shots);
+ return 0;
+}
+
+int FFEVAL_KILLED_HALF_OF_ENEMIES( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetPercentageOfEnemyTeamKilled();
+}
+
+bool FFEVAL_WON_AS_LAST_MEMBER( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ CCSPlayer *pCSPlayer = NULL;
+ int winningTeam = CSGameRules()->m_iRoundWinStatus;
+
+ if (winningTeam != TEAM_TERRORIST && winningTeam != TEAM_CT)
+ {
+ return false;
+ }
+
+ int losingTeam = (winningTeam == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
+
+ CCSGameRules::TeamPlayerCounts playerCounts[TEAM_MAXCOUNT];
+ CSGameRules()->GetPlayerCounts(playerCounts);
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ pCSPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
+ if( pCSPlayer && pCSPlayer->GetTeamNumber() == winningTeam && pCSPlayer->IsAlive())
+ {
+ //Check if the player is still the only living member of his team ( on the off chance that a player joins late)
+ //This check is a little hacky. We make sure that there are no enemies alive. Since the bomb causes the round to end before exploding,
+ //the only way for only 1 person to be alive at round win time is extermination or defuse (in both cases, the last living player caused the win)
+ if (playerCounts[winningTeam].totalAlivePlayers == 1 && playerCounts[losingTeam].totalAlivePlayers == 0)
+ {
+ const PlayerStats_t& playerStats = CCS_GameStats.FindPlayerStats( pCSPlayer );
+ iPlayer = i;
+ data1 = playerStats.statsCurrentRound[CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE];
+ if (data1 >= 2)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+int FFEVAL_KNIFE_IN_GUNFIGHT( CCSPlayer* pPlayer )
+{
+ return pPlayer->WasWieldingKnifeAndKilledByGun() ? 1 : 0;
+}
+
+int FFEVAL_MULTIPLE_ATTACKER_COUNT( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetNumEnemyDamagers();
+}
+
+int FFEVAL_USED_ALL_AMMO( CCSPlayer* pPlayer )
+{
+ CWeaponCSBase *pRifleWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->Weapon_GetSlot( WEAPON_SLOT_RIFLE ));
+ CWeaponCSBase *pHandgunWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->Weapon_GetSlot( WEAPON_SLOT_PISTOL ));
+ if ( pRifleWeapon && !pRifleWeapon->HasAmmo() && pHandgunWeapon && !pHandgunWeapon->HasAmmo() )
+ return 1;
+ else
+ return 0;
+}
+
+int FFEVAL_DAMAGE_MULTIPLE_ENEMIES( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetNumEnemiesDamaged();
+}
+
+int FFEVAL_USED_MULTIPLE_WEAPONS( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetNumFirearmsUsed();
+}
+
+int FFEVAL_DEFUSED_WITH_DROPPED_KIT( CCSPlayer* pPlayer )
+{
+ return pPlayer->GetDefusedWithPickedUpKit() ? 1 : 0;
+}
+
+bool FFEVAL_TERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ float terroristAccuracy = GetTeamAccuracy(TEAM_TERRORIST);
+ float ctAccuracy = GetTeamAccuracy(TEAM_CT);
+
+ if (terroristAccuracy > 0.2f && terroristAccuracy > ctAccuracy)
+ {
+ data1 = RoundFloatToInt(terroristAccuracy * 100.0f);
+ return true;
+ }
+ return false;
+}
+
+bool FFEVAL_CT_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ float terroristAccuracy = GetTeamAccuracy(TEAM_TERRORIST);
+ float ctAccuracy = GetTeamAccuracy(TEAM_CT);
+
+ if (ctAccuracy > 0.2f && ctAccuracy > terroristAccuracy)
+ {
+ data1 = RoundFloatToInt(ctAccuracy * 100.0f);
+ return true;
+ }
+ return false;
+}
+
+bool FFEVAL_SAME_UNIFORM( int iTeam, int &iData1, int &iData2, int &iData3 )
+{
+ int numberInUniform = 0;
+ int iUniform = -1;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CCSPlayer *pCSPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
+ if ( pCSPlayer && pCSPlayer->GetTeamNumber() == iTeam && pCSPlayer->State_Get() != STATE_PICKINGCLASS)
+ {
+ if (iUniform == -1)
+ {
+ iUniform = pCSPlayer->PlayerClass();
+ }
+ else if (pCSPlayer->PlayerClass() != iUniform)
+ {
+ return false;
+ }
+ ++numberInUniform;
+ }
+ }
+
+ return numberInUniform >= 3;
+}
+
+bool FFEVAL_BEST_TERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ float fAccuracy = 0.0f, fBestAccuracy = 0.0f;
+ CBasePlayer *pPlayer = NULL;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ pPlayer = UTIL_PlayerByIndex( i );
+
+ // Look only at terrorist players
+ if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
+ {
+ // Calculate accuracy the terrorist
+ float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
+ float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
+ if (shots > MIN_SHOTS_FOR_ACCURACY)
+ {
+ fAccuracy = (float)hits / shots;
+ }
+
+ // Track the most accurate terrorist
+ if ( fAccuracy > fBestAccuracy )
+ {
+ fBestAccuracy = fAccuracy;
+ iPlayer = i;
+ }
+ }
+ }
+
+ if ( fBestAccuracy - GetTeamAccuracy( TEAM_TERRORIST ) >= 0.10f )
+ {
+ data1 = RoundFloatToInt(fBestAccuracy * 100.0f);
+ data2 = RoundFloatToInt(GetTeamAccuracy( TEAM_TERRORIST ) * 100.0f);
+ return true;
+ }
+
+ return false;
+}
+
+bool FFEVAL_BEST_COUNTERTERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
+{
+ float fAccuracy = 0.0f, fBestAccuracy = 0.0f;
+ CBasePlayer *pPlayer = NULL;
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ pPlayer = UTIL_PlayerByIndex( i );
+
+ // Look only at counter-terrorist players
+ if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT )
+ {
+ // Calculate accuracy the counter-terrorist
+ float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
+ float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
+ if (shots > MIN_SHOTS_FOR_ACCURACY)
+ {
+ fAccuracy = (float)hits / shots;
+ }
+
+ // Track the most accurate counter-terrorist
+ if ( fAccuracy > fBestAccuracy )
+ {
+ fBestAccuracy = fAccuracy;
+ iPlayer = i;
+ }
+ }
+ }
+
+ if ( fBestAccuracy - GetTeamAccuracy( TEAM_CT ) >= 0.10f )
+ {
+ data1 = RoundFloatToInt(fBestAccuracy * 100.0f);
+ data2 = RoundFloatToInt(GetTeamAccuracy( TEAM_CT ) * 100.0f);
+ return true;
+ }
+
+ return false;
+}
+
+
+//=============================================================================
+// Fun Fact Declarations
+//=============================================================================
+
+DECLARE_FUNFACT_STATBEST( FUNFACT_DAMAGE_WITH_GRENADES, "#funfact_damage_with_grenade", 0.5f, CSSTAT_GRENADE_DAMAGE, 200, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KNIFE_KILLS, "#funfact_knife_kills", 0.5f, CSSTAT_KILLS_KNIFE, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_WITH_GRENADES, "#funfact_kills_grenades", 0.7f, CSSTAT_KILLS_HEGRENADE, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_BLIND_KILLS, "#funfact_blind_kills", 0.9f, CSSTAT_KILLS_WHILE_BLINDED, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KILLED_ENEMIES, "#funfact_killed_enemies", 0.6f, CSSTAT_KILLS, 3, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_WITH_LAST_ROUND, "#funfact_kills_with_last_round", 0.6f, CSSTAT_KILLS_WITH_LAST_ROUND, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_DONATED_WEAPONS, "#funfact_donated_weapons", 0.3f, CSSTAT_WEAPONS_DONATED, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_NUM_TIMES_JUMPED, "#funfact_num_times_jumped", 0.2f, CSSTAT_TOTAL_JUMPS, 10, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_FALL_DAMAGE, "#funfact_fall_damage", 0.2f, CSSTAT_FALL_DAMAGE, 50, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_POSTHUMOUS_KILLS_WITH_GRENADE, "#funfact_posthumous_kills_with_grenade", 1.0f, CSSTAT_GRENADE_POSTHUMOUSKILLS, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_ITEMS_PURCHASED, "#funfact_items_purchased", 0.2f, CSSTAT_ITEMS_PURCHASED, 5, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_NUMBER_OF_OVERKILLS, "#funfact_number_of_overkills", 0.5f, CSSTAT_DOMINATION_OVERKILLS, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_MONEY_SPENT, "#funfact_money_spent", 0.2f, CSSTAT_MONEY_SPENT, 5000, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_GRENADES_THROWN, "#funfact_grenades_thrown", 0.3f, CSSTAT_GRENADES_THROWN, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_DEFENDED_BOMB, "#funfact_defended_bomb", 0.5f, CSSTAT_KILLS_WHILE_DEFENDING_BOMB, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_ITEMS_DROPPED_VALUE, "#funfact_items_dropped_value", 0.5f, CSTAT_ITEMS_DROPPED_VALUE, 10000, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KILL_WOUNDED_ENEMIES, "#funfact_kill_wounded_enemies", 0.4f, CSSTAT_KILLS_ENEMY_WOUNDED, 3, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_HEADSHOTS, "#funfact_kills_headshots", 0.7f, CSSTAT_KILLS_HEADSHOT, 3, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_BROKE_WINDOWS, "#funfact_broke_windows", 0.3f, CSSTAT_NUM_BROKEN_WINDOWS, 5, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_STATBEST( FUNFACT_NIGHTVISION_DAMAGE, "#funfact_nightvision_damage", 0.5f, CSSTAT_NIGHTVISION_DAMAGE, 100, EvalFlags::HighestOnly);
+
+DECLARE_FUNFACT_STATSUM( FUNFACT_SHOTS_FIRED, "#funfact_shots_fired", 0.1f, CSSTAT_SHOTS_FIRED, 200, EvalFlags::All);
+
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILL_DEFUSER, "#funfact_kill_defuser", 0.6f, FFEVAL_KILLED_DEFUSER, 1, EvalFlags::TeamTerrorist | EvalFlags::WinningTeam);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILL_RESCUER, "#funfact_kill_rescuer", 0.6f, FFEVAL_KILLED_RESCUER, 1, EvalFlags::TeamTerrorist);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILLS_WITH_SINGLE_GRENADE, "#funfact_kills_with_single_grenade", 0.8f, FFEVAL_KILLS_WITH_GRENADE, 2, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DAMAGE_NO_KILLS, "#funfact_damage_no_kills", 0.4f, FFEVAL_DAMAGE_NO_KILLS, 200, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_FIRST_KILL, "#funfact_first_kill", 0.2f, FFEVAL_FIRST_KILL, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_FIRST_BLOOD, "#funfact_first_blood", 0.2f, FFEVAL_FIRST_BLOOD, 1, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_BEST_ACCURACY, "#funfact_best_accuracy", 0.4f, FFEVAL_ACCURACY, 20, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KNIFE_IN_GUNFIGHT, "#funfact_knife_in_gunfight", 0.6f, FFEVAL_KNIFE_IN_GUNFIGHT , 1, EvalFlags::All);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_SURVIVED_MULTIPLE_ATTACKERS, "#funfact_survived_multiple_attackers", 0.3f, FFEVAL_MULTIPLE_ATTACKER_COUNT, 3, EvalFlags::HighestOnly | EvalFlags::Alive);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DIED_FROM_MULTIPLE_ATTACKERS, "#funfact_died_from_multiple_attackers", 0.5f, FFEVAL_MULTIPLE_ATTACKER_COUNT, 3, EvalFlags::HighestOnly | EvalFlags::Dead);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_USED_ALL_AMMO, "#funfact_used_all_ammo", 0.5f, FFEVAL_USED_ALL_AMMO, 1, EvalFlags::All );
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DAMAGE_MULTIPLE_ENEMIES, "#funfact_damage_multiple_enemies", 0.5f, FFEVAL_DAMAGE_MULTIPLE_ENEMIES, 3, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_USED_MULTIPLE_WEAPONS, "#funfact_used_multiple_weapons", 0.5f, FFEVAL_USED_MULTIPLE_WEAPONS, 4, EvalFlags::HighestOnly);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DEFUSED_WITH_DROPPED_KIT, "#funfact_defused_with_dropped_kit", 0.4f, FFEVAL_DEFUSED_WITH_DROPPED_KIT, 1, EvalFlags::TeamCT);
+DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILLED_HALF_OF_ENEMIES, "#funfact_killed_half_of_enemies", 0.5f, FFEVAL_KILLED_HALF_OF_ENEMIES, 50, EvalFlags::WinningTeam | EvalFlags::HighestOnly);
+
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_WIN_NO_KILLS, "#funfact_ct_win_no_kills", 0.4f, FFEVAL_CT_WIN_NO_KILLS);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_T_WIN_NO_KILLS, "#funfact_t_win_no_kills", 0.4f, FFEVAL_T_WIN_NO_KILLS );
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_T_WIN_NO_CASUALTIES, "#funfact_t_win_no_casualties", 0.2f, FFEVAL_T_WIN_NO_CASUALTIES );
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_WIN_NO_CASUALTIES, "#funfact_ct_win_no_casualties", 0.2f, FFEVAL_CT_WIN_NO_CASUALTIES );
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_SHORT_ROUND, "#funfact_short_round", 0.3f, FFEVAL_SHORT_ROUND );
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_WON_AS_LAST_MEMBER, "#funfact_won_as_last_member", 0.6f, FFEVAL_WON_AS_LAST_MEMBER );
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_TERRORIST_ACCURACY, "#funfact_terrorist_accuracy", 0.2f, FFEVAL_TERRORIST_ACCURACY);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_ACCURACY, "#funfact_ct_accuracy", 0.2f, FFEVAL_CT_ACCURACY);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_BEST_TERRORIST_ACCURACY, "#funfact_best_terrorist_accuracy", 0.3f, FFEVAL_BEST_TERRORIST_ACCURACY);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_BEST_COUNTERTERRORIST_ACCURACY, "#funfact_best_counterterrorist_accuracy", 0.3f, FFEVAL_BEST_COUNTERTERRORIST_ACCURACY);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_FALLBACK1, "#funfact_fallback1", 0.0f, FFEVAL_ALWAYS_TRUE);
+DECLARE_FUNFACT_EVALFUNC( FUNFACT_FALLBACK2, "#funfact_fallback2", 0.0f, FFEVAL_ALWAYS_TRUE);
+
+DECLARE_FUNFACT_TEAMFUNC( FUNFACT_SAME_UNIFORM_TERRORIST, "#funfact_same_uniform_terrorist", 0.5f, FFEVAL_SAME_UNIFORM, TEAM_TERRORIST);
+DECLARE_FUNFACT_TEAMFUNC( FUNFACT_SAME_UNIFORM_CT, "#funfact_same_uniform_ct", 0.5f, FFEVAL_SAME_UNIFORM, TEAM_CT);
diff --git a/game/server/cstrike/funfact_cs.h b/game/server/cstrike/funfact_cs.h
new file mode 100644
index 0000000..bed7bec
--- /dev/null
+++ b/game/server/cstrike/funfact_cs.h
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#ifndef INCLUDED_funfact_cs
+#define INCLUDED_funfact_cs
+#pragma once
+
+#include "cs_player.h"
+
+struct FunFact
+{
+ FunFact() :
+ id(-1),
+ szLocalizationToken(NULL),
+ iPlayer(0),
+ iData1(0),
+ iData2(0),
+ iData3(0),
+ fMagnitude(0.0f)
+ {}
+ int id;
+ const char* szLocalizationToken;
+ int iPlayer;
+ int iData1;
+ int iData2;
+ int iData3;
+ float fMagnitude;
+};
+
+typedef CUtlVector<FunFact> FunFactVector;
+
+class FunFactEvaluator
+{
+ DECLARE_CLASS_NOBASE( FunFactEvaluator );
+public:
+ FunFactEvaluator( int id, const char* szLocalizationToken, float fCoolness ) :
+ m_id(id),
+ m_pLocalizationToken(szLocalizationToken),
+ m_fCoolness(fCoolness)
+ {}
+
+ virtual ~FunFactEvaluator() {}
+
+ int GetId() const { return m_id; }
+ const char* GetLocalizationToken() const { return m_pLocalizationToken; }
+ float GetCoolness() const { return m_fCoolness; }
+
+ virtual bool Evaluate( FunFactVector& results ) const = 0;
+
+private:
+ int m_id;
+ const char* m_pLocalizationToken;
+ float m_fCoolness;
+};
+
+
+typedef FunFactEvaluator* (*funfactCreateFunc) (void);
+class CFunFactHelper
+{
+public:
+ CFunFactHelper ( funfactCreateFunc createFunc )
+ {
+ m_pfnCreate = createFunc;
+ m_pNext = s_pFirst;
+ s_pFirst = this;
+ }
+ funfactCreateFunc m_pfnCreate;
+ CFunFactHelper *m_pNext;
+ static CFunFactHelper *s_pFirst;
+};
+
+#endif // INCLUDED_funfact_cs
+
diff --git a/game/server/cstrike/funfactmgr_cs.cpp b/game/server/cstrike/funfactmgr_cs.cpp
new file mode 100644
index 0000000..4454e08
--- /dev/null
+++ b/game/server/cstrike/funfactmgr_cs.cpp
@@ -0,0 +1,204 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+#include "usermessages.h"
+#include "funfactmgr_cs.h"
+
+const float kCooldownRatePlayer = 0.4f;
+const float kCooldownRateFunFact = 0.2f;
+
+const float kWeightPlayerCooldown = 0.8f;
+const float kWeightFunFactCooldown = 1.0f;
+const float kWeightCoolness = 2.0f;
+const float kWeightRarity = 1.0f;
+
+#define DEBUG_FUNFACT_SCORING 0
+
+//-----------------------------------------------------------------------------
+// Purpose: constructor
+//-----------------------------------------------------------------------------
+CCSFunFactMgr::CCSFunFactMgr() :
+ CAutoGameSystemPerFrame( "CCSFunFactMgr" ),
+ m_funFactDatabase(0, 100, DefLessFunc(int) )
+{
+ for ( int i = 0; i < MAX_PLAYERS; ++i )
+ {
+ m_playerCooldown[i] = 0.0f;
+ }
+}
+
+CCSFunFactMgr::~CCSFunFactMgr()
+{
+ Shutdown();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the fun fact manager
+//-----------------------------------------------------------------------------
+bool CCSFunFactMgr::Init()
+{
+ ListenForGameEvent( "player_connect" );
+
+ CFunFactHelper *pFunFactHelper = CFunFactHelper::s_pFirst;
+
+ // create database of all fun fact evaluators (and initial usage metrics)
+ while ( pFunFactHelper )
+ {
+ FunFactDatabaseEntry entry;
+ entry.fCooldown = 0.0f;
+ entry.iOccurrences = 0;
+ entry.pEvaluator = pFunFactHelper->m_pfnCreate();
+ m_funFactDatabase.Insert(entry.pEvaluator->GetId(), entry);
+
+ pFunFactHelper = pFunFactHelper->m_pNext;
+ }
+
+ for (int i = 0; i < ARRAYSIZE(m_playerCooldown); ++i)
+ {
+ m_playerCooldown[i] = 0.0f;
+ }
+
+ m_numRounds = 0;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shuts down the fun fact manager
+//-----------------------------------------------------------------------------
+void CCSFunFactMgr::Shutdown()
+{
+ FOR_EACH_MAP( m_funFactDatabase, iter )
+ {
+ delete m_funFactDatabase[iter].pEvaluator;
+ }
+ m_funFactDatabase.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Per frame processing
+//-----------------------------------------------------------------------------
+void CCSFunFactMgr::Update( float frametime )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Listens for game events. Clears out map based stats and player based stats when necessary
+//-----------------------------------------------------------------------------
+void CCSFunFactMgr::FireGameEvent( IGameEvent *event )
+{
+ const char *eventname = event->GetName();
+
+ if ( Q_strcmp( "player_connect", eventname ) == 0 )
+ {
+ int index = event->GetInt("index");// player slot (entity index-1)
+ ASSERT( index >= 0 && index < MAX_PLAYERS );
+ if( index >= 0 && index < MAX_PLAYERS )
+ {
+ m_playerCooldown[index] = 0.0f;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the best fun fact to display and returns all necessary information through the parameters
+//-----------------------------------------------------------------------------
+bool CCSFunFactMgr::GetRoundEndFunFact( int iWinningTeam, int iRoundResult, FunFact& funfact )
+{
+ FunFactVector validFunFacts;
+
+ // Generate a vector of all valid fun facts for this round
+ FOR_EACH_MAP( m_funFactDatabase, i )
+ {
+ FunFact funFact;
+ if ( m_funFactDatabase[i].pEvaluator->Evaluate(validFunFacts) )
+ {
+ m_funFactDatabase[i].iOccurrences++;
+ }
+ }
+
+ m_numRounds++;
+
+ if (validFunFacts.Size() == 0)
+ return false;
+
+ // pick the fun fact with the highest score
+ float fBestScore = -FLT_MAX;
+ int iFunFactIndex = -1;
+
+#if DEBUG_FUNFACT_SCORING
+ Msg("Scoring fun facts:\n");
+#endif
+
+ FOR_EACH_VEC(validFunFacts, i)
+ {
+ float fScore = ScoreFunFact(validFunFacts[i]);
+
+#if DEBUG_FUNFACT_SCORING
+ char szPlayerName[64];
+ const FunFact& funfact = validFunFacts[i];
+ if (funfact.iPlayer > 0)
+ V_strncpy(szPlayerName, ToCSPlayer(UTIL_PlayerByIndex(funfact.iPlayer))->GetPlayerName(), sizeof(szPlayerName));
+ else
+ V_strcpy(szPlayerName, "");
+
+ Msg("(%5.4f) %s, %s, %i, %i, %i\n", fScore, funfact.szLocalizationToken, szPlayerName, funfact.iData1, funfact.iData2, funfact.iData3);
+#endif
+
+ if (fScore > fBestScore)
+ {
+ fBestScore = fScore;
+ iFunFactIndex = i;
+ }
+ }
+
+ if (iFunFactIndex < 0)
+ return false;
+
+ funfact = validFunFacts[iFunFactIndex];
+
+ // decay player cooldowns
+ for (int i = 0; i < MAX_PLAYERS; ++i )
+ {
+ m_playerCooldown[i] *= (1.0f - kCooldownRatePlayer);
+ }
+
+ // decay funfact cooldowns
+ FOR_EACH_MAP(m_funFactDatabase, i)
+ {
+ m_funFactDatabase[i].fCooldown *= (1.0f - kCooldownRateFunFact);
+ }
+
+ // set player cooldown for player in funfact
+ if ( funfact.iPlayer )
+ {
+ m_playerCooldown[funfact.iPlayer - 1] = 1.0f;
+ }
+
+ // set funfact cooldown for current funfact
+ m_funFactDatabase[m_funFactDatabase.Find(funfact.id)].fCooldown = 1.0f;
+
+ return true;
+}
+
+float CCSFunFactMgr::ScoreFunFact( const FunFact& funfact )
+{
+ float fScore = 0.0f;
+ const FunFactDatabaseEntry& dbEntry = m_funFactDatabase[m_funFactDatabase.Find(funfact.id)];
+
+ // add the coolness score for the funfact
+ fScore += kWeightCoolness * dbEntry.pEvaluator->GetCoolness() * (1.0f + funfact.fMagnitude);
+
+ // subtract the cooldown for the funfact
+ fScore -= kWeightFunFactCooldown * dbEntry.fCooldown;
+
+ // subtract the cooldown for the player
+ if ( funfact.iPlayer ) {
+ fScore -= kWeightPlayerCooldown * m_playerCooldown[funfact.iPlayer - 1];
+ }
+
+ // add the rarity bonus
+ fScore += kWeightRarity * powf((1.0f - (float)dbEntry.iOccurrences / m_numRounds), 2.0f);
+
+ return fScore;
+}
diff --git a/game/server/cstrike/funfactmgr_cs.h b/game/server/cstrike/funfactmgr_cs.h
new file mode 100644
index 0000000..aac715c
--- /dev/null
+++ b/game/server/cstrike/funfactmgr_cs.h
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#ifndef FUNFACTMGR_H
+#define FUNFACTMGR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "GameEventListener.h"
+#include "funfact_cs.h"
+#include "utlmap.h"
+
+class FunFactEvaluator;
+
+class CCSFunFactMgr : public CAutoGameSystemPerFrame, public CGameEventListener
+{
+public:
+ CCSFunFactMgr();
+ ~CCSFunFactMgr();
+
+ virtual bool Init();
+ virtual void Shutdown();
+ virtual void Update( float frametime );
+
+ bool GetRoundEndFunFact( int iWinningTeam, int iRoundResult, FunFact& funfact );
+
+protected:
+ float ScoreFunFact( const FunFact& funfact );
+ void FireGameEvent( IGameEvent *event );
+
+private:
+
+ float m_playerCooldown[MAX_PLAYERS]; // Weights for all players. Updated every round
+
+ struct FunFactDatabaseEntry
+ {
+ const FunFactEvaluator* pEvaluator;
+ int iOccurrences;
+ float fCooldown;
+ };
+ CUtlMap<int, FunFactDatabaseEntry> m_funFactDatabase;
+ int m_numRounds;
+};
+
+#endif
+
diff --git a/game/server/cstrike/holiday_gift.cpp b/game/server/cstrike/holiday_gift.cpp
new file mode 100644
index 0000000..afb2f0a
--- /dev/null
+++ b/game/server/cstrike/holiday_gift.cpp
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "smokegrenade_projectile.h"
+#include "sendproxy.h"
+#include "holiday_gift.h"
+#include "cs_player.h"
+#include "KeyValues.h"
+#include "bot_manager.h"
+#include "weapon_csbase.h"
+
+#define CHRISTMAS_MODEL "models/items/cs_gift.mdl"
+
+LINK_ENTITY_TO_CLASS( holiday_gift, CHolidayGift );
+PRECACHE_WEAPON_REGISTER( holiday_gift );
+
+//-----------------------------------------------------------------------------
+CHolidayGift* CHolidayGift::Create( const Vector &position, const QAngle &angles, const QAngle &eyeAngles, const Vector &velocity, CBaseCombatCharacter *pOwner )
+{
+ CHolidayGift *pGift = (CHolidayGift*)CBaseEntity::Create( "holiday_gift", position, angles, pOwner );
+
+ if ( pGift )
+ {
+ pGift->AddSpawnFlags( SF_NORESPAWN );
+
+ Vector vecRight, vecUp;
+ AngleVectors( eyeAngles, NULL, &vecRight, &vecUp );
+
+ // Calculate the initial impulse on the gift.
+ Vector vecImpulse( 0.0f, 0.0f, 0.0f );
+ vecImpulse += vecUp * random->RandomFloat( 0, 0.25 );
+ vecImpulse += vecRight * random->RandomFloat( -0.25, 0.25 );
+ VectorNormalize( vecImpulse );
+ vecImpulse *= random->RandomFloat( 100.0, 150.0 );
+ vecImpulse += velocity;
+
+ // Cap the impulse.
+ float flSpeed = vecImpulse.Length();
+ if ( flSpeed > 300.0 )
+ {
+ VectorScale( vecImpulse, 300.0 / flSpeed, vecImpulse );
+ }
+
+ pGift->SetMoveType( MOVETYPE_FLYGRAVITY );
+ pGift->SetAbsVelocity( vecImpulse * 2.f + Vector(0,0,200) );
+ pGift->SetAbsAngles( QAngle(0,0,0) );
+ pGift->UseClientSideAnimation();
+ pGift->ResetSequence( pGift->LookupSequence("idle") );
+
+ pGift->EmitSound( "Christmas.GiftDrop" );
+
+ pGift->ActivateWhenAtRest();
+ }
+
+ return pGift;
+}
+
+//-----------------------------------------------------------------------------
+void CHolidayGift::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel( CHRISTMAS_MODEL );
+ PrecacheScriptSound( "Christmas.GiftDrop" );
+ PrecacheScriptSound( "Christmas.GiftPickup" );
+}
+
+//-----------------------------------------------------------------------------
+void CHolidayGift::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ SetModel( CHRISTMAS_MODEL );
+
+ // Die in 30 seconds
+ SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 30, "DIE_THINK" );
+ SetContextThink( &CHolidayGift::DropSoundThink, gpGlobals->curtime + 0.2f, "SOUND_THINK" );
+}
+
+//-----------------------------------------------------------------------------
+void CHolidayGift::DropSoundThink( void )
+{
+ EmitSound( "Christmas.GiftDrop" );
+}
+
+//-----------------------------------------------------------------------------
+bool CHolidayGift::MyTouch( CBasePlayer *pPlayer )
+{
+ if( !pPlayer )
+ return false;
+
+ if( !pPlayer->IsAlive() )
+ return false;
+
+ if ( pPlayer->IsBot() )
+ return false;
+
+ if ( ( pPlayer->GetTeamNumber() != TEAM_CT ) && ( pPlayer->GetTeamNumber() != TEAM_TERRORIST ) )
+ return false;
+
+ // Send a message for the achievement tracking.
+ IGameEvent *event = gameeventmanager->CreateEvent( "christmas_gift_grab" );
+ if ( event )
+ {
+ event->SetInt( "userid", pPlayer->GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+ pPlayer->EmitSound( "Christmas.GiftPickup" );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void CHolidayGift::ItemTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsWorld() )
+ {
+ Vector absVel = GetAbsVelocity();
+ SetAbsVelocity( Vector( 0,0,absVel.z ) );
+ return;
+ }
+
+ BaseClass::ItemTouch( pOther );
+}
diff --git a/game/server/cstrike/holiday_gift.h b/game/server/cstrike/holiday_gift.h
new file mode 100644
index 0000000..f5987bd
--- /dev/null
+++ b/game/server/cstrike/holiday_gift.h
@@ -0,0 +1,36 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef HOLIDAY_GIFT_H
+#define HOLIDAY_GIFT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "items.h"
+
+
+class CHolidayGift: public CItem
+{
+public:
+ DECLARE_CLASS( CHolidayGift, CItem );
+
+public:
+
+ virtual void Precache();
+ virtual void Spawn( void );
+ virtual bool MyTouch( CBasePlayer *pBasePlayer );
+ virtual void ItemTouch( CBaseEntity *pOther );
+ void DropSoundThink( void );
+
+public:
+
+ static CHolidayGift* Create( const Vector &position, const QAngle &angles, const QAngle &eyeAngles, const Vector &velocity, CBaseCombatCharacter *pOwner );
+};
+
+
+#endif // HOLIDAY_GIFT_H
diff --git a/game/server/cstrike/hostage/cs_simple_hostage.cpp b/game/server/cstrike/hostage/cs_simple_hostage.cpp
new file mode 100644
index 0000000..aacab39
--- /dev/null
+++ b/game/server/cstrike/hostage/cs_simple_hostage.cpp
@@ -0,0 +1,1345 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+// cs_simple_hostage.cpp
+// Simple CS1.6 level hostage
+// Author: Michael S. Booth and Matt Boone, July 2004
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_player.h"
+#include "cs_gamerules.h"
+#include "game.h"
+#include "bot.h"
+#include <KeyValues.h>
+#include "obstacle_pushaway.h"
+#include "props_shared.h"
+
+//=============================================================================
+// HPE_BEGIN
+//=============================================================================
+// [dwenger] Necessary for stats tracking
+#include "cs_gamestats.h"
+
+//[tj] Necessary for fast rescue achievement
+#include "cs_achievement_constants.h"
+//=============================================================================
+// HPE_END
+//=============================================================================
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define HOSTAGE_THINK_INTERVAL 0.1f
+
+#define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f )
+#define HOSTAGE_PUSHAWAY_THINK_CONTEXT "HostagePushawayThink"
+
+#define HOSTAGE_BBOX_VEC_MIN Vector( -13, -13, 0 )
+#define HOSTAGE_BBOX_VEC_MAX Vector( 13, 13, 72 )
+
+
+ConVar mp_hostagepenalty( "mp_hostagepenalty", "13", FCVAR_NOTIFY, "Terrorist are kicked for killing too much hostages" );
+ConVar hostage_debug( "hostage_debug", "0", FCVAR_CHEAT, "Show hostage AI debug information" );
+
+extern ConVar sv_pushaway_force;
+extern ConVar sv_pushaway_min_player_speed;
+extern ConVar sv_pushaway_max_force;
+
+// We need hostage-specific pushaway cvars because the hostage doesn't have the same friction etc as players
+ConVar sv_pushaway_hostage_force( "sv_pushaway_hostage_force", "20000", FCVAR_REPLICATED | FCVAR_CHEAT, "How hard the hostage is pushed away from physics objects (falls off with inverse square of distance)." );
+ConVar sv_pushaway_max_hostage_force( "sv_pushaway_max_hostage_force", "1000", FCVAR_REPLICATED | FCVAR_CHEAT, "Maximum of how hard the hostage is pushed away from physics objects." );
+
+const int NumHostageModels = 4;
+static const char *HostageModel[NumHostageModels] =
+{
+ "models/Characters/Hostage_01.mdl",
+ "models/Characters/Hostage_02.mdl",
+ "models/Characters/hostage_03.mdl",
+ "models/Characters/hostage_04.mdl",
+};
+
+Vector DropToGround( CBaseEntity *pMainEnt, const Vector &vPos, const Vector &vMins, const Vector &vMaxs );
+
+//-----------------------------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( hostage_entity, CHostage );
+
+
+//-----------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( CHostage )
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "OnRescueZoneTouch", HostageRescueZoneTouch ),
+
+ DEFINE_USEFUNC( HostageUse ),
+ DEFINE_THINKFUNC( HostageThink ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------------------------------
+IMPLEMENT_SERVERCLASS_ST( CHostage, DT_CHostage )
+ SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
+ SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
+ SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
+
+ // cs_playeranimstate and clientside animation takes care of these on the client
+ SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
+ SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
+
+ SendPropBool( SENDINFO(m_isRescued) ),
+ SendPropInt( SENDINFO(m_iHealth), 10 ),
+ SendPropInt( SENDINFO(m_iMaxHealth), 10 ),
+ SendPropInt( SENDINFO(m_lifeState), 3, SPROP_UNSIGNED ),
+
+ SendPropEHandle( SENDINFO(m_leader) ),
+
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------------------------------
+CUtlVector< CHostage * > g_Hostages;
+static CountdownTimer announceTimer; // used to stop "hostage rescued" announcements from stepping on each other
+
+
+//-----------------------------------------------------------------------------------------------------
+CHostage::CHostage()
+{
+ g_Hostages.AddToTail( this );
+ m_PlayerAnimState = CreateHostageAnimState( this, this, LEGANIM_8WAY, false );
+ UseClientSideAnimation();
+ SetBloodColor( BLOOD_COLOR_RED );
+}
+
+//-----------------------------------------------------------------------------------------------------
+CHostage::~CHostage()
+{
+ g_Hostages.FindAndRemove( this );
+ m_PlayerAnimState->Release();
+}
+
+CWeaponCSBase* CHostage::CSAnim_GetActiveWeapon()
+{
+ return NULL;
+}
+
+bool CHostage::CSAnim_CanMove()
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CHostage::Spawn( void )
+{
+ Precache();
+
+ // round-robin through the hostage models
+ static int index = 0;
+ int whichModel = index % NumHostageModels;
+ ++index;
+
+ SetModel( HostageModel[ whichModel ] );
+
+
+ SetHullType( HULL_HUMAN );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetCollisionGroup( COLLISION_GROUP_PLAYER );
+
+ SetGravity( 1.0 );
+
+ m_iHealth = 100;
+ m_iMaxHealth = m_iHealth;
+ m_takedamage = DAMAGE_YES;
+
+ InitBoneControllers( );
+
+ // we must set this, because its zero by default thus putting their eyes in their feet
+ SetViewOffset( Vector( 0, 0, 60 ) );
+
+
+ // set up think callback
+ SetNextThink( gpGlobals->curtime + HOSTAGE_THINK_INTERVAL );
+ SetThink( &CHostage::HostageThink );
+
+ SetContextThink( &CHostage::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, HOSTAGE_PUSHAWAY_THINK_CONTEXT );
+
+ SetUse( &CHostage::HostageUse );
+
+ m_leader = NULL;
+ m_reuseTimer.Invalidate();
+ m_hasBeenUsed = false;
+
+ m_isRescued = false;
+
+ m_vel = Vector( 0, 0, 0 );
+ m_accel = Vector( 0, 0, 0 );
+
+ m_path.Invalidate();
+ m_repathTimer.Invalidate();
+
+ m_pathFollower.Reset();
+ m_pathFollower.SetPath( &m_path );
+ m_pathFollower.SetImprov( this );
+
+ m_lastKnownArea = NULL;
+
+ // Need to make sure the hostages are on the ground when they spawn
+ Vector GroundPos = DropToGround( this, GetAbsOrigin(), HOSTAGE_BBOX_VEC_MIN, HOSTAGE_BBOX_VEC_MAX );
+ SetAbsOrigin( GroundPos );
+
+ if (TheNavMesh)
+ {
+ Vector pos = GetAbsOrigin();
+ m_lastKnownArea = TheNavMesh->GetNearestNavArea( pos );
+ }
+
+ m_isCrouching = false;
+ m_isRunning = true;
+ m_jumpTimer.Invalidate();
+ m_inhibitObstacleAvoidanceTimer.Invalidate();
+
+ m_isWaitingForLeader = false;
+
+ m_isAdjusted = false;
+
+ m_lastLeaderID = 0;
+
+ announceTimer.Invalidate();
+ m_disappearTime = 0.0f;
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CHostage::Precache()
+{
+ for ( int i=0; i<NumHostageModels; ++i )
+ {
+ PrecacheModel( HostageModel[i] );
+ }
+
+ PrecacheScriptSound( "Hostage.StartFollowCT" );
+ PrecacheScriptSound( "Hostage.StopFollowCT" );
+ PrecacheScriptSound( "Hostage.Pain" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------------------------------
+int CHostage::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ float actualDamage = info.GetDamage();
+
+ // say something
+ EmitSound( "Hostage.Pain" );
+
+ CCSPlayer *player = ToCSPlayer( info.GetAttacker() );
+
+ if (player)
+ {
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Track which player injured the hostage
+ //=============================================================================
+
+ player->SetInjuredAHostage(true);
+ CSGameRules()->HostageInjured();
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ if ( !( player->m_iDisplayHistoryBits & DHF_HOSTAGE_INJURED ) )
+ {
+ player->HintMessage( "#Hint_careful_around_hostages", FALSE );
+ player->m_iDisplayHistoryBits |= DHF_HOSTAGE_INJURED;
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "hostage_hurt" );
+ if ( event )
+ {
+ event->SetInt( "userid", player->GetUserID() );
+ event->SetInt( "hostage", entindex() );
+ event->SetInt( "priority", 5 );
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ player->AddAccount( -((int)actualDamage * 20) );
+ }
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Modify damage the hostage takes by hitgroup
+ */
+float CHostage::GetModifiedDamage( float flDamage, int nHitGroup )
+{
+ switch ( nHitGroup )
+ {
+ case HITGROUP_GENERIC: flDamage *= 1.75; break;
+ case HITGROUP_HEAD: flDamage *= 2.5; break;
+ case HITGROUP_CHEST: flDamage *= 1.5; break;
+ case HITGROUP_STOMACH: flDamage *= 1.75; break;
+ case HITGROUP_LEFTARM: flDamage *= 0.75; break;
+ case HITGROUP_RIGHTARM: flDamage *= 0.75; break;
+ case HITGROUP_LEFTLEG: flDamage *= 0.6; break;
+ case HITGROUP_RIGHTLEG: flDamage *= 0.6; break;
+ default: flDamage *= 1.5; break;
+ }
+
+ return flDamage;
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CHostage::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo scaledInfo = info;
+ scaledInfo.SetDamage( GetModifiedDamage( info.GetDamage(), ptr->hitgroup ) );
+ BaseClass::TraceAttack( scaledInfo, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Check for hostage-killer abuse
+ */
+void CHostage::CheckForHostageAbuse( CCSPlayer *player )
+{
+ int hostageKillLimit = mp_hostagepenalty.GetInt();
+
+ if (hostageKillLimit > 0)
+ {
+ player->m_iHostagesKilled++;
+
+ if ( player->m_iHostagesKilled == hostageKillLimit - 1 )
+ {
+ player->HintMessage( "#Hint_removed_for_next_hostage_killed", TRUE );
+ }
+ else if ( player->m_iHostagesKilled >= hostageKillLimit )
+ {
+ Msg( "Kicking client \"%s\" for killing too many hostages\n", player->GetPlayerName() );
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d \"For killing too many hostages\"\n", player->GetUserID() ) );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Hostage was killed
+ */
+void CHostage::Event_Killed( const CTakeDamageInfo &info )
+{
+ // tell the game logic that we've died
+ CSGameRules()->CheckWinConditions();
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [tj] Let the game know that a hostage has been killed
+ //=============================================================================
+ CSGameRules()->HostageKilled();
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ CCSPlayer *attacker = ToCSPlayer( info.GetAttacker() );
+
+ if (attacker)
+ {
+ if ( !( attacker->m_iDisplayHistoryBits & DHF_HOSTAGE_KILLED ) )
+ {
+ attacker->HintMessage( "#Hint_lost_money", FALSE );
+ attacker->m_iDisplayHistoryBits |= DHF_HOSTAGE_KILLED;
+ }
+
+ // monetary penalty for killing the hostage
+ attacker->AddAccount( -( 500 + ((int)info.GetDamage() * 20) ) );
+
+ // check for hostage-killer abuse
+ if (attacker->GetTeamNumber() == TEAM_TERRORIST)
+ {
+ CheckForHostageAbuse( attacker );
+ }
+ }
+
+ m_lastLeaderID = 0;
+
+ SetUse( NULL );
+ BaseClass::Event_Killed( info );
+
+ IGameEvent *event = gameeventmanager->CreateEvent("hostage_killed");
+ if ( event )
+ {
+ event->SetInt( "userid", (attacker)?attacker->GetUserID():0 );
+ event->SetInt( "hostage", entindex() );
+ event->SetInt( "priority", 6 );
+ gameeventmanager->FireEvent( event );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a Hostage touches a Rescue Zone
+ */
+void CHostage::HostageRescueZoneTouch( inputdata_t &inputdata )
+{
+ if (!m_isRescued)
+ {
+ m_isRescued = true;
+ m_lastLeaderID = 0;
+
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( 0 );
+
+ // start fading out
+ m_disappearTime = gpGlobals->curtime + 3.0f;
+
+ SetUse( NULL );
+ m_takedamage = DAMAGE_NO;
+
+ // give rescuer a cash bonus
+ CCSPlayer *player = GetLeader();
+ if (player)
+ {
+ const int rescuerCashBonus = 1000;
+ player->AddAccount( rescuerCashBonus );
+ }
+
+ Idle();
+
+ // tell the bots someone has rescued a hostage
+ IGameEvent *event = gameeventmanager->CreateEvent( "hostage_rescued" );
+ if ( event )
+ {
+ event->SetInt( "userid", player ? player->GetUserID() : (-1) );
+ event->SetInt( "hostage", entindex() );
+ event->SetInt( "site", 0 ); // TODO add site index
+ event->SetInt( "priority", 9 );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // update game rules
+ CSGameRules()->m_iHostagesRescued++;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Hostage rescue achievement processing
+ //=============================================================================
+
+ // Track last rescuer
+ if ( CSGameRules()->m_pLastRescuer == NULL )
+ {
+ // No rescuer yet, so assign one & set rescuer count to 1
+ CSGameRules()->m_pLastRescuer = player;
+ CSGameRules()->m_iNumRescuers = 1;
+ }
+ else
+ {
+ if ( CSGameRules()->m_pLastRescuer != player )
+ {
+ // Rescuer changed
+ CSGameRules()->m_pLastRescuer = player;
+ CSGameRules()->m_iNumRescuers++;
+ }
+ }
+
+ bool roundIsAlreadyOver = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE);
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if (CSGameRules()->CheckWinConditions() == false)
+ {
+ // this hostage didn't win the round, so announce its rescue to everyone
+ if (announceTimer.IsElapsed())
+ {
+ CSGameRules()->BroadcastSound( "Event.HostageRescued" );
+ }
+
+ // avoid having the announcer talk over himself
+ announceTimer.Start( 2.0f );
+ }
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Awarding of hostage rescue achievement
+ //=============================================================================
+
+ else
+ {
+ //Check hostage rescue achievements
+ if ( CSGameRules()->m_iNumRescuers == 1 && !CSGameRules()->WasHostageKilled())
+ {
+ //check for unrescued hostages
+ bool allHostagesRescued = true;
+ CHostage* hostage = NULL;
+ int iNumHostages = g_Hostages.Count();
+
+ for ( int i = 0 ; i < iNumHostages; i++ )
+ {
+ hostage = g_Hostages[i];
+
+ if ( hostage->m_iHealth > 0 && !hostage->IsRescued() )
+ {
+ allHostagesRescued = false;
+ break;
+ }
+ }
+
+ if (allHostagesRescued)
+ {
+ player->AwardAchievement(CSRescueAllHostagesInARound);
+
+ //[tj] fast version
+ if (gpGlobals->curtime - CSGameRules()->GetRoundStartTime() < AchievementConsts::FastHostageRescue_Time)
+ {
+ if ( player )
+ {
+ player->AwardAchievement(CSFastHostageRescue);
+ }
+ }
+ }
+ }
+ //=============================================================================
+ // HPE_BEGIN:
+ // [menglish] If this rescue ended the round give an mvp to the rescuer
+ //=============================================================================
+
+ if ( player && !roundIsAlreadyOver)
+ {
+ player->IncrementNumMVPs( CSMVP_HOSTAGERESCUE );
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+
+ if ( player )
+ {
+ CCS_GameStats.Event_HostageRescued( player );
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * In contact with "other"
+ */
+void CHostage::Touch( CBaseEntity *other )
+{
+ BaseClass::Touch( other );
+
+ // allow players and other hostages to push me around
+ if ( ( other->IsPlayer() && other->GetTeamNumber() == TEAM_CT ) || FClassnameIs( other, "hostage_entity" ) )
+ {
+ // only push in 2D
+ Vector to = GetAbsOrigin() - other->GetAbsOrigin();
+ to.z = 0.0f;
+ to.NormalizeInPlace();
+
+ const float pushForce = 500.0f;
+ ApplyForce( pushForce * to );
+ }
+ else if ( m_inhibitDoorTimer.IsElapsed() &&
+ ( other->ClassMatches( "func_door*" ) || other->ClassMatches( "prop_door*" ) ) )
+ {
+ m_inhibitDoorTimer.Start( 3.0f );
+ other->Use( this, this, USE_TOGGLE, 0.0f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Hostage is stuck - attempt to wiggle out
+ */
+void CHostage::Wiggle( void )
+{
+ if (m_wiggleTimer.IsElapsed())
+ {
+ m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
+ m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) );
+ }
+
+ Vector dir, lat;
+ AngleVectors( GetAbsAngles(), &dir, &lat, NULL );
+
+ const float speed = 500.0f;
+
+ switch( m_wiggleDirection )
+ {
+ case LEFT:
+ ApplyForce( speed * lat );
+ break;
+
+ case RIGHT:
+ ApplyForce( -speed * lat );
+ break;
+
+ case FORWARD:
+ ApplyForce( speed * dir );
+ break;
+
+ case BACKWARD:
+ ApplyForce( -speed * dir );
+ break;
+ }
+
+ const float minStuckJumpTime = 0.25f;
+ if (m_pathFollower.GetStuckDuration() > minStuckJumpTime)
+ {
+ Jump();
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Do following behavior
+ */
+void CHostage::UpdateFollowing( float deltaT )
+{
+ if ( !IsFollowingSomeone() && m_lastLeaderID != 0 )
+ {
+ // emit hostage_stops_following event
+ IGameEvent *event = gameeventmanager->CreateEvent( "hostage_stops_following" );
+ if ( event )
+ {
+ event->SetInt( "userid", m_lastLeaderID );
+ event->SetInt( "hostage", entindex() );
+ event->SetInt( "priority", 6 );
+ gameeventmanager->FireEvent( event );
+ }
+
+ m_lastLeaderID = 0;
+ }
+
+ // if we have a leader, follow him
+ CCSPlayer *leader = GetLeader();
+ if (leader)
+ {
+ // if leader is dead, stop following him
+ if (!leader->IsAlive())
+ {
+ Idle();
+ return;
+ }
+
+ // if leader has moved, repath
+ if (m_path.IsValid())
+ {
+ Vector pathError = leader->GetAbsOrigin() - m_path.GetEndpoint();
+
+ const float repathRange = 100.0f;
+ if (pathError.IsLengthGreaterThan( repathRange ))
+ {
+ m_path.Invalidate();
+ }
+ }
+
+
+ // build a path to our leader
+ if (!m_path.IsValid() && m_repathTimer.IsElapsed())
+ {
+ const float repathInterval = 0.5f;
+ m_repathTimer.Start( repathInterval );
+
+ Vector from = GetAbsOrigin();
+ Vector to = leader->GetAbsOrigin();
+ HostagePathCost pathCost;
+
+ m_path.Compute( from, to, pathCost );
+ m_pathFollower.Reset();
+ }
+
+
+ // if our rescuer is too far away, give up
+ const float giveUpRange = 2000.0f;
+ const float maxPathLength = 4000.0f;
+ Vector toLeader = leader->GetAbsOrigin() - GetAbsOrigin();
+ if (toLeader.IsLengthGreaterThan( giveUpRange ) || (m_path.IsValid() && m_path.GetLength() > maxPathLength))
+ {
+ if ( hostage_debug.GetInt() < 2 )
+ {
+ Idle();
+ }
+ return;
+ }
+
+
+ // don't crowd the leader
+ if (m_isWaitingForLeader)
+ {
+ // we are close to our leader and waiting for him to move
+ const float waitRange = 150.0f;
+ if (toLeader.IsLengthGreaterThan( waitRange ))
+ {
+ // leader has moved away - follow him
+ m_isWaitingForLeader = false;
+ }
+
+ // face the leader
+ //FaceTowards( leader->GetAbsOrigin(), deltaT );
+ }
+ else
+ {
+ // we are far from our leader, and need to check if we're close enough to wait
+ const float nearRange = 125.0f;
+
+ if (toLeader.IsLengthLessThan( nearRange ))
+ {
+ // we are close to the leader - wait for him to move
+ m_isWaitingForLeader = true;
+ }
+ }
+
+ if (!m_isWaitingForLeader)
+ {
+ // move along path towards the leader
+ m_pathFollower.Update( deltaT, m_inhibitObstacleAvoidanceTimer.IsElapsed() );
+
+ if (hostage_debug.GetBool())
+ {
+ m_pathFollower.Debug( true );
+ }
+
+ if (m_pathFollower.IsStuck())
+ {
+ Wiggle();
+ }
+
+ if (hostage_debug.GetBool())
+ {
+ m_path.Draw();
+ }
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------------------------------
+void CHostage::AvoidPhysicsProps( void )
+{
+ if ( m_lifeState == LIFE_DEAD )
+ return;
+
+ CBaseEntity *props[512];
+ int nEnts = GetPushawayEnts( this, props, ARRAYSIZE( props ), 0.0f, PARTITION_ENGINE_SOLID_EDICTS );
+
+ for ( int i=0; i < nEnts; i++ )
+ {
+ // Don't respond to this entity on the client unless it has PHYSICS_MULTIPLAYER_FULL set.
+ IMultiplayerPhysics *pInterface = dynamic_cast<IMultiplayerPhysics*>( props[i] );
+ if ( pInterface && pInterface->GetMultiplayerPhysicsMode() != PHYSICS_MULTIPLAYER_SOLID )
+ continue;
+
+ const float minMass = 10.0f; // minimum mass that can push a player back
+ const float maxMass = 30.0f; // cap at a decently large value
+ float mass = maxMass;
+ if ( pInterface )
+ {
+ mass = pInterface->GetMass();
+ }
+ mass = MIN( mass, maxMass );
+ mass -= minMass;
+ mass = MAX( mass, 0 );
+ mass /= (maxMass-minMass); // bring into a 0..1 range
+
+ // Push away from the collision point. The closer our center is to the collision point,
+ // the harder we push away.
+ Vector vPushAway = (WorldSpaceCenter() - props[i]->WorldSpaceCenter());
+ float flDist = VectorNormalize( vPushAway );
+ flDist = MAX( flDist, 1 );
+
+ float flForce = sv_pushaway_hostage_force.GetFloat() / flDist * mass;
+ flForce = MIN( flForce, sv_pushaway_max_hostage_force.GetFloat() );
+ vPushAway *= flForce;
+
+ ApplyForce( vPushAway );
+ }
+
+ //
+ // Handle step and ledge "step-up" movement here, before m_accel is zero'd
+ //
+ if ( !m_accel.IsZero() )
+ {
+ trace_t trace;
+ Vector start = GetAbsOrigin();
+ Vector forward = m_accel;
+ forward.NormalizeInPlace();
+ UTIL_TraceEntity( this, start, start + forward, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &trace );
+ if ( !trace.startsolid && trace.fraction < 1.0f && trace.plane.normal.z < 0.7f )
+ {
+ float groundFraction = trace.fraction;
+ start.z += StepHeight;
+ UTIL_TraceEntity( this, start, start + forward, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &trace );
+ if ( !trace.startsolid && trace.fraction > groundFraction )
+ {
+ SetAbsOrigin( start );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Push physics objects away from the hostage
+ */
+void CHostage::PushawayThink( void )
+{
+ PerformObstaclePushaway( this );
+ SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, HOSTAGE_PUSHAWAY_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * @TODO imitate player movement:
+ * MoveHelperServer()->SetHost( this );
+ * this->PlayerRunCommand( &cmd, MoveHelperServer() );
+ */
+void CHostage::PhysicsSimulate( void )
+{
+ BaseClass::PhysicsSimulate();
+
+ SetAbsVelocity( m_vel );
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Update Hostage behaviors
+ */
+void CHostage::HostageThink( void )
+{
+ if (!m_isAdjusted)
+ {
+ m_isAdjusted = true;
+
+ // HACK - figure out why the default bbox is 6 units too low
+ SetCollisionBounds( HOSTAGE_BBOX_VEC_MIN, HOSTAGE_BBOX_VEC_MAX );
+ }
+
+ const float deltaT = HOSTAGE_THINK_INTERVAL;
+ SetNextThink( gpGlobals->curtime + deltaT );
+
+ // keep track of which Navigation Area we are in (or were in, if we're "off the mesh" right now)
+ CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin() );
+ if (area != NULL && area != m_lastKnownArea)
+ {
+ // entered a new nav area
+ m_lastKnownArea = area;
+ }
+
+ // do leader-following behavior, if necessary
+ UpdateFollowing( deltaT );
+
+ AvoidPhysicsProps();
+
+ // update hostage velocity in the XY plane
+ const float damping = 2.0f;
+ m_vel += deltaT * (m_accel - damping * m_vel);
+
+ // leave Z component untouched
+ m_vel.z = GetAbsVelocity().z;
+
+ if ( m_accel.IsZero() && m_vel.AsVector2D().IsZero( 1.0f ) )
+ {
+ m_vel.x = 0.0f;
+ m_vel.y = 0.0f;
+ }
+
+ m_accel = Vector( 0, 0, 0 );
+
+ // set animation to idle for now
+ StudioFrameAdvance();
+
+ int sequence = SelectWeightedSequence( ACT_IDLE );
+ if (GetSequence() != sequence)
+ {
+ SetSequence( sequence );
+ }
+
+ m_PlayerAnimState->Update( GetAbsAngles()[YAW], GetAbsAngles()[PITCH] );
+
+
+ if ( m_disappearTime && m_disappearTime < gpGlobals->curtime )
+ {
+ // finished fading - remove us completely
+ AddEffects( EF_NODRAW );
+
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( 0 );
+ m_disappearTime = 0.0f;
+ }
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsFollowingSomeone( void )
+{
+ return (m_leader.m_Value != NULL);
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsFollowing( const CBaseEntity *entity )
+{
+ return (m_leader.m_Value == entity);
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsValid( void ) const
+{
+ return (m_iHealth > 0 && !IsRescued());
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsRescuable( void ) const
+{
+ return (m_iHealth > 0 && !IsRescued());
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsRescued( void ) const
+{
+ return m_isRescued;
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsOnGround( void ) const
+{
+ return (GetFlags() & FL_ONGROUND);
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Return true if hostage can see position
+ */
+bool CHostage::IsVisible( const Vector &pos, bool testFOV ) const
+{
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), pos, CONTENTS_SOLID, this, COLLISION_GROUP_NONE, &result );
+ return (result.fraction >= 1.0f);
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Give bonus to CT's for talking to a hostage
+ */
+void CHostage::GiveCTUseBonus( CCSPlayer *rescuer )
+{
+ // money to team
+ const int teamBonus = 100;
+ CSGameRules()->m_iAccountCT += teamBonus;
+
+ // money to rescuer
+ const int rescuerBonus = 150;
+ rescuer->AddAccount( rescuerBonus );
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Stand idle
+ */
+void CHostage::Idle( void )
+{
+ m_leader = NULL;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Begin following "leader"
+ */
+void CHostage::Follow( CCSPlayer *leader )
+{
+ //=============================================================================
+ // HPE_BEGIN
+ // [dwenger] Set variable to track whether player is currently rescuing hostages
+ //=============================================================================
+
+ if ( leader )
+ {
+ leader->IncrementNumFollowers();
+ leader->SetIsRescuing(true);
+ }
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ m_leader = leader;
+ m_isWaitingForLeader = false;
+ m_lastLeaderID = (leader) ? leader->GetUserID() : 0;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Return our leader, or NULL
+ */
+CCSPlayer *CHostage::GetLeader( void ) const
+{
+ return ToCSPlayer( m_leader.m_Value );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a Hostage is "used" by a player
+ */
+void CHostage::HostageUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ Vector to = pActivator->GetAbsOrigin() - GetAbsOrigin();
+
+ // limit use range
+ float useRange = 1000.0f;
+ if (to.IsLengthGreaterThan( useRange ))
+ {
+ return;
+ }
+
+ // TODO: check line of sight to hostage
+
+
+ CCSPlayer *user = ToCSPlayer( pActivator );
+ if (user == NULL)
+ {
+ return;
+ }
+
+ // only members of the CT team can use hostages (no T's or spectators)
+ if (!hostage_debug.GetBool() && user->GetTeamNumber() != TEAM_CT)
+ {
+ if ( user->GetTeamNumber() == TEAM_TERRORIST )
+ {
+ if ( !(user->m_iDisplayHistoryBits & DHF_HOSTAGE_CTMOVE) )
+ {
+ user->m_iDisplayHistoryBits |= DHF_HOSTAGE_CTMOVE;
+ user->HintMessage( "#Only_CT_Can_Move_Hostages", false, true );
+ }
+ }
+
+ return;
+ }
+
+ CCSPlayer *leader = GetLeader();
+ if( leader && !leader->IsAlive() )
+ {
+ Idle();
+ leader = NULL;
+ }
+
+ // throttle how often leader can change
+ if (!m_reuseTimer.IsElapsed())
+ {
+ return;
+ }
+
+ // give money bonus to first CT touching this hostage
+ if (!m_hasBeenUsed)
+ {
+ m_hasBeenUsed = true;
+
+ GiveCTUseBonus( user );
+
+ CSGameRules()->HostageTouched();
+ }
+
+ // if we are already following the player who used us, stop following
+ if (IsFollowing( user ))
+ {
+ Idle();
+
+ // say something
+ EmitSound( "Hostage.StopFollowCT" );
+ }
+ else
+ {
+ // if we're already following a CT, ignore new uses
+ if (IsFollowingSomeone())
+ {
+ return;
+ }
+
+ // start following
+ Follow( user );
+
+ // say something
+ EmitSound( "Hostage.StartFollowCT" );
+
+ // emit hostage_follows event
+ IGameEvent *event = gameeventmanager->CreateEvent( "hostage_follows" );
+ if ( event )
+ {
+ event->SetInt( "userid", user->GetUserID() );
+ event->SetInt( "hostage", entindex() );
+ event->SetInt( "priority", 6 );
+ gameeventmanager->FireEvent( event );
+ }
+
+ if ( !(user->m_iDisplayHistoryBits & DHF_HOSTAGE_USED) )
+ {
+ user->m_iDisplayHistoryBits |= DHF_HOSTAGE_USED;
+ user->HintMessage( "#Hint_lead_hostage_to_rescue_point", false );
+ }
+ }
+
+ m_reuseTimer.Start( 1.0f );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Rotate body to face towards "target"
+ */
+void CHostage::FaceTowards( const Vector &target, float deltaT )
+{
+ Vector to = target - GetFeet();
+ to.z = 0.0f;
+
+ QAngle desiredAngles;
+ VectorAngles( to, desiredAngles );
+
+ QAngle angles = GetAbsAngles();
+
+ const float turnSpeed = 250.0f;
+ angles.y = ApproachAngle( desiredAngles.y, angles.y, turnSpeed * deltaT );
+
+ SetAbsAngles( angles );
+}
+
+//-----------------------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------------------
+
+const Vector &CHostage::GetCentroid( void ) const
+{
+ static Vector centroid;
+
+ centroid = GetFeet();
+ centroid.z += HalfHumanHeight;
+
+ return centroid;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Return position of "feet" - point below centroid of improv at feet level
+ */
+const Vector &CHostage::GetFeet( void ) const
+{
+ static Vector feet;
+
+ feet = GetAbsOrigin();
+
+ return feet;
+}
+
+//-----------------------------------------------------------------------------------------------------
+const Vector &CHostage::GetEyes( void ) const
+{
+ static Vector eyes;
+
+ eyes = EyePosition();
+
+ return eyes;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Return direction of movement
+ */
+float CHostage::GetMoveAngle( void ) const
+{
+ return GetAbsAngles().y;
+}
+
+//-----------------------------------------------------------------------------------------------------
+CNavArea *CHostage::GetLastKnownArea( void ) const
+{
+ return m_lastKnownArea;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Find "simple" ground height, treating current nav area as part of the floo
+ */
+bool CHostage::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
+{
+ if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
+ {
+ // our current nav area also serves as a ground polygon
+ if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
+ {
+ *height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CHostage::Crouch( void )
+{
+ m_isCrouching = true;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * un-crouch
+ */
+void CHostage::StandUp( void )
+{
+ m_isCrouching = false;
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsCrouching( void ) const
+{
+ return m_isCrouching;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Initiate a jump
+ */
+void CHostage::Jump( void )
+{
+ if (m_jumpTimer.IsElapsed() && IsOnGround())
+ {
+ const float minJumpInterval = 0.5f;
+ m_jumpTimer.Start( minJumpInterval );
+
+ Vector vel = GetAbsVelocity();
+ vel.z += 200.0f;
+ SetAbsVelocity( vel );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsJumping( void ) const
+{
+ return !m_jumpTimer.IsElapsed();
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Set movement speed to running
+ */
+void CHostage::Run( void )
+{
+ m_isRunning = true;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Set movement speed to walking
+ */
+void CHostage::Walk( void )
+{
+ m_isRunning = false;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsRunning( void ) const
+{
+ return m_isRunning;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Invoked when a ladder is encountered while following a path
+ */
+void CHostage::StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos )
+{
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Traverse given ladder
+ */
+bool CHostage::TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT )
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------------------------------
+bool CHostage::IsUsingLadder( void ) const
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Move Hostage directly toward "pathGoal", causing Hostage to track the current path.
+ */
+void CHostage::TrackPath( const Vector &pathGoal, float deltaT )
+{
+ // face in the direction of our motion
+ FaceTowards( GetAbsOrigin() + 10.0f * m_vel, deltaT );
+
+ if (GetFlags() & FL_ONGROUND)
+ {
+ // on the ground - move towards pathGoal
+ Vector to = pathGoal - GetFeet();
+ to.z = 0.0f;
+ to.NormalizeInPlace();
+
+ const float speed = 1000.0f;
+ ApplyForce( speed * to );
+ }
+ else
+ {
+ // in the air - continue forward motion
+ Vector to;
+ QAngle angles = GetAbsAngles();
+ AngleVectors( angles, &to );
+
+ const float airSpeed = 350.0f;
+ ApplyForce( airSpeed * to );
+ }
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Invoked when an improv reaches its MoveTo goal
+ */
+void CHostage::OnMoveToSuccess( const Vector &goal )
+{
+}
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Invoked when an improv fails to reach a MoveTo goal
+ */
+void CHostage::OnMoveToFailure( const Vector &goal, MoveToFailureType reason )
+{
+}
+
+
+unsigned int CHostage::PhysicsSolidMaskForEntity() const
+{
+ return MASK_PLAYERSOLID;
+}
+
diff --git a/game/server/cstrike/hostage/cs_simple_hostage.h b/game/server/cstrike/hostage/cs_simple_hostage.h
new file mode 100644
index 0000000..cdc6544
--- /dev/null
+++ b/game/server/cstrike/hostage/cs_simple_hostage.h
@@ -0,0 +1,256 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// cs_simple_hostage.h
+// Simple CS1.6 level hostage
+// Author: Michael S. Booth, July 2004
+
+#ifndef _CS_SIMPLE_HOSTAGE_H_
+#define _CS_SIMPLE_HOSTAGE_H_
+
+#include "nav_mesh.h"
+#include "cs_nav_path.h"
+#include "cs_nav_pathfind.h"
+#include "improv_locomotor.h"
+#include "cs_playeranimstate.h"
+
+class CCSPlayer;
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * A Counter-Strike Hostage
+ */
+class CHostage : public CBaseCombatCharacter, public CImprovLocomotor, public ICSPlayerAnimStateHelpers
+{
+public:
+ DECLARE_CLASS( CHostage, CBaseCombatCharacter );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CHostage( void );
+ virtual ~CHostage();
+
+
+public:
+ virtual void Spawn( void );
+ virtual void Precache();
+ int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE); } // make hostage "useable"
+
+ virtual void PhysicsSimulate( void );
+
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual void Touch( CBaseEntity *other ); // in contact with "other"
+
+ void HostageRescueZoneTouch( inputdata_t &inputdata ); // invoked when hostage touches a rescue zone
+
+ void HostageThink( void ); // periodic update to initiate behaviors
+
+ void GiveCTUseBonus( CCSPlayer *rescuer ); // give bonus to CT's for talking to a hostage
+ void CheckForHostageAbuse( CCSPlayer *player ); // check for hostage-killer abuse
+
+ // queries
+ bool IsFollowingSomeone( void );
+ bool IsFollowing( const CBaseEntity *entity );
+ bool IsValid( void ) const;
+ bool IsRescuable( void ) const;
+ bool IsRescued( void ) const;
+ bool IsOnGround( void ) const;
+
+ bool IsVisible( const Vector &pos, bool testFOV = false ) const; // return true if hostage can see position
+
+ // hostage states
+ void Idle( void ); // stand idle
+ void Follow( CCSPlayer *leader ); // begin following "leader"
+ CCSPlayer *GetLeader( void ) const; // return our leader, or NULL
+
+ void FaceTowards( const Vector &target, float deltaT ); // rotate body to face towards "target"
+ void ApplyForce( const Vector &force ) { m_accel += force; } // apply a force to the hostage
+
+ unsigned int PhysicsSolidMaskForEntity() const;
+ Class_T Classify ( void ) { return CLASS_PLAYER_ALLY; }
+
+public:
+ // begin CImprovLocomotor -----------------------------------------------------------------------------------------------------------------
+ virtual const Vector &GetCentroid( void ) const;
+ virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of improv at feet level
+ virtual const Vector &GetEyes( void ) const;
+ virtual float GetMoveAngle( void ) const; // return direction of movement
+
+ virtual CNavArea *GetLastKnownArea( void ) const;
+ virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ); // find "simple" ground height, treating current nav area as part of the floor
+
+ virtual void Crouch( void );
+ virtual void StandUp( void ); // "un-crouch"
+ virtual bool IsCrouching( void ) const;
+
+ virtual void Jump( void ); // initiate a jump
+ virtual bool IsJumping( void ) const;
+
+ virtual void Run( void ); // set movement speed to running
+ virtual void Walk( void ); // set movement speed to walking
+ virtual bool IsRunning( void ) const;
+
+ virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ); // invoked when a ladder is encountered while following a path
+ virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ); // traverse given ladder
+ virtual bool IsUsingLadder( void ) const;
+
+ virtual void TrackPath( const Vector &pathGoal, float deltaT ); // move along path by following "pathGoal"
+ virtual void OnMoveToSuccess( const Vector &goal ); // invoked when an improv reaches its MoveTo goal
+ virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ); // invoked when an improv fails to reach a MoveTo goal
+ // end CImprovLocomotor -------------------------------------------------------------------------------------------------------------------
+
+// ICSPlayerAnimState overrides.
+public:
+ virtual CWeaponCSBase* CSAnim_GetActiveWeapon();
+ virtual bool CSAnim_CanMove();
+
+
+
+protected:
+ virtual void HostageUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+private:
+ float GetModifiedDamage( float flDamage, int nHitGroup );
+
+ ICSPlayerAnimState *m_PlayerAnimState;
+
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iMaxHealth );
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth );
+ IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_lifeState );
+ CNetworkVar( bool, m_isRescued ); // true if the hostage has been rescued
+
+ CNetworkVar( EHANDLE, m_leader ); // the player we are following
+ void UpdateFollowing( float deltaT ); // do following behavior
+
+ int m_lastLeaderID;
+
+ CountdownTimer m_reuseTimer; // to throttle how often hostage can be used
+ bool m_hasBeenUsed; // flag to give first rescuer bonus money
+
+ Vector m_vel;
+ Vector m_accel;
+
+ bool m_isRunning; // true if hostage move speed is to run (walk if false)
+ bool m_isCrouching; // true if hostage is crouching
+ CountdownTimer m_jumpTimer; // if zero, we can jump
+
+ bool m_isWaitingForLeader; // true if we are waiting for our rescuer to move
+
+ CCSNavPath m_path; // current path to follow
+ CountdownTimer m_repathTimer; // throttle pathfinder
+
+ CountdownTimer m_inhibitDoorTimer;
+
+ CNavPathFollower m_pathFollower; // path tracking mechanism
+ CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
+
+ CNavArea *m_lastKnownArea; // last area we were in
+
+ void Wiggle( void ); // attempt to wiggle-out of begin stuck
+ CountdownTimer m_wiggleTimer; // for wiggling
+ NavRelativeDirType m_wiggleDirection;
+
+ bool m_isAdjusted; // hack for adjusting bounding box
+ float m_disappearTime; // has finished fading, remove me
+
+ void PushawayThink( void ); // pushes physics objects away from the hostage
+ void AvoidPhysicsProps( void ); // guides the hostage away from physics props
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Functor used with NavAreaBuildPath() for building Hostage paths.
+ * Once we hook up crouching and ladders, this can be removed and ShortestPathCost() can be used instead.
+ */
+class HostagePathCost
+{
+public:
+
+ // HPE_TODO[pmf]: check that these new parameters are okay to be ignored
+ float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
+ {
+ if (fromArea == NULL)
+ {
+ // first area in path, no cost
+ return 0.0f;
+ }
+ else
+ {
+ // if this area isn't available to hostages, skip it
+ if ( area->GetAttributes() & NAV_MESH_NO_HOSTAGES )
+ {
+ return -1.0f;
+ }
+
+ // compute distance travelled along path so far
+ float dist;
+
+ if (ladder)
+ {
+ // can't traverse ladders
+ return -1.0f;
+ }
+ else
+ {
+ dist = (area->GetCenter() - fromArea->GetCenter()).Length();
+ }
+
+ float cost = dist + fromArea->GetCostSoFar();
+
+ if (area->GetAttributes() & NAV_MESH_CROUCH) // && !(area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ // can't traverse areas that require crouching
+ return -1.0f;
+ }
+
+ // if this is a "jump" area, add penalty
+ if (area->GetAttributes() & NAV_MESH_JUMP)
+ {
+ const float jumpPenalty = 5.0f;
+ cost += jumpPenalty * dist;
+ }
+
+ return cost;
+ }
+ }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+// All the hostage entities.
+extern CUtlVector< CHostage * > g_Hostages;
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Iterate over all active hostages in the game, invoking functor on each.
+ * If functor returns false, stop iteration and return false.
+ */
+template < typename Functor >
+bool ForEachHostage( Functor &func )
+{
+ for( int i=0; i<g_Hostages.Count(); ++i )
+ {
+ CHostage *hostage = g_Hostages[i];
+
+ if ( hostage == NULL || !hostage->IsValid() )
+ continue;
+
+ if ( func( hostage ) == false )
+ return false;
+ }
+
+ return true;
+}
+
+
+#endif // _CS_SIMPLE_HOSTAGE_H_
diff --git a/game/server/cstrike/info_view_parameters.cpp b/game/server/cstrike/info_view_parameters.cpp
new file mode 100644
index 0000000..d4579fe
--- /dev/null
+++ b/game/server/cstrike/info_view_parameters.cpp
@@ -0,0 +1,18 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "info_view_parameters.h"
+
+
+BEGIN_DATADESC( CInfoViewParameters )
+ DEFINE_KEYFIELD( m_nViewMode, FIELD_INTEGER, "ViewMode" )
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( info_view_parameters, CInfoViewParameters );
+
+
diff --git a/game/server/cstrike/info_view_parameters.h b/game/server/cstrike/info_view_parameters.h
new file mode 100644
index 0000000..0e1d38f
--- /dev/null
+++ b/game/server/cstrike/info_view_parameters.h
@@ -0,0 +1,24 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef INFO_VIEW_PARAMETERS_H
+#define INFO_VIEW_PARAMETERS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+class CInfoViewParameters : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CInfoViewParameters, CBaseEntity );
+ DECLARE_DATADESC();
+
+ int m_nViewMode;
+};
+
+
+#endif // INFO_VIEW_PARAMETERS_H
diff --git a/game/server/cstrike/item_ammo.cpp b/game/server/cstrike/item_ammo.cpp
new file mode 100644
index 0000000..7b09cc5
--- /dev/null
+++ b/game/server/cstrike/item_ammo.cpp
@@ -0,0 +1,147 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "items.h"
+#include "cs_player.h"
+#include "weapon_csbase.h"
+#include "cs_ammodef.h"
+
+
+//-----------------------------------------------------------------------------
+class CItemAmmo : public CItem
+{
+public:
+ DECLARE_CLASS( CItemAmmo, CItem );
+
+ bool MyTouch( CBasePlayer *pBasePlayer )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
+ if ( !pPlayer )
+ {
+ Assert( false );
+ return false;
+ }
+
+ int ammoIndex = GetCSAmmoDef()->Index( GetAmmoName() );
+ if ( ammoIndex < 0 )
+ {
+ Assert( false );
+ return false;
+ }
+
+ pPlayer->GiveAmmo( GetCSAmmoDef()->GetBuySize( ammoIndex ), ammoIndex );
+
+ return true;
+ }
+
+ virtual const char * GetAmmoName( void ) const { return NULL; }
+};
+
+//-----------------------------------------------------------------------------
+class CItemAmmo50AE : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo50AE, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_50AE; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_50ae, CItemAmmo50AE );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo762MM : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo762MM, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_762MM; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_762mm, CItemAmmo762MM );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo556MM : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo556MM, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_556mm, CItemAmmo556MM );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo556MM_BOX : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo556MM_BOX, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM_BOX; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_556mm_box, CItemAmmo556MM_BOX );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo338MAG : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo338MAG, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_338MAG; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_338mag, CItemAmmo338MAG );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo9MM : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo9MM, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_9MM; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_9mm, CItemAmmo9MM );
+
+//-----------------------------------------------------------------------------
+class CItemAmmoBuckshot : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmoBuckshot, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_BUCKSHOT; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_buckshot, CItemAmmoBuckshot );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo45ACP : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo45ACP, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_45ACP; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_45acp, CItemAmmo45ACP );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo357SIG : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo357SIG, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_357sig, CItemAmmo357SIG );
+
+//-----------------------------------------------------------------------------
+class CItemAmmo57MM : public CItemAmmo
+{
+public:
+ DECLARE_CLASS( CItemAmmo57MM, CItemAmmo );
+ virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_57MM; }
+};
+
+LINK_ENTITY_TO_CLASS( ammo_57mm, CItemAmmo57MM );
+
+//-----------------------------------------------------------------------------
+
diff --git a/game/server/cstrike/item_assaultsuit.cpp b/game/server/cstrike/item_assaultsuit.cpp
new file mode 100644
index 0000000..da36de5
--- /dev/null
+++ b/game/server/cstrike/item_assaultsuit.cpp
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "items.h"
+#include "cs_player.h"
+
+
+class CItemAssaultSuit : public CItem
+{
+ void Spawn( void )
+ {
+ Precache( );
+ CItem::Spawn( );
+ }
+
+ void Precache( void )
+ {
+ PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
+ }
+
+ bool MyTouch( CBasePlayer *pBasePlayer )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
+ if ( !pPlayer )
+ {
+ Assert( false );
+ return false;
+ }
+
+ pPlayer->m_bHasHelmet = true;
+ pPlayer->SetArmorValue( 100 );
+
+ if ( pPlayer->IsDead() == false )
+ {
+ CPASAttenuationFilter filter( pBasePlayer );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
+
+ CSingleUserRecipientFilter user( pPlayer );
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( "item_assaultsuit" );
+ MessageEnd();
+ }
+
+ return true;
+ }
+};
+
+LINK_ENTITY_TO_CLASS( item_assaultsuit, CItemAssaultSuit );
+
+
diff --git a/game/server/cstrike/item_defuser.cpp b/game/server/cstrike/item_defuser.cpp
new file mode 100644
index 0000000..b6f3343
--- /dev/null
+++ b/game/server/cstrike/item_defuser.cpp
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Defuser kit that drops from counter-strike CTS
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "items.h"
+#include "cs_player.h"
+
+class CItemDefuser : public CItem
+{
+public:
+ DECLARE_CLASS( CItemDefuser, CItem );
+
+ void Spawn( void );
+ void Precache( void );
+ void DefuserTouch( CBaseEntity *pOther );
+ void ActivateThink( void );
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( item_defuser, CItemDefuser );
+PRECACHE_REGISTER(item_defuser);
+
+
+BEGIN_DATADESC( CItemDefuser )
+
+ //Functions
+ DEFINE_THINKFUNC( ActivateThink ),
+ DEFINE_ENTITYFUNC( DefuserTouch ),
+
+END_DATADESC()
+
+
+void CItemDefuser::Spawn( void )
+{
+ Precache( );
+ SetModel( "models/weapons/w_defuser.mdl" );
+ BaseClass::Spawn();
+
+ SetNextThink( gpGlobals->curtime + 0.5f );
+ SetThink( &CItemDefuser::ActivateThink );
+
+ SetTouch( NULL );
+}
+
+void CItemDefuser::Precache( void )
+{
+ PrecacheModel( "models/weapons/w_defuser.mdl" );
+
+ PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
+}
+
+void CItemDefuser::ActivateThink( void )
+{
+ //since we can't stop the item from being touched while its in the air,
+ //activate 1 second after being dropped
+
+ SetTouch( &CItemDefuser::DefuserTouch );
+ SetThink( NULL );
+}
+
+void CItemDefuser::DefuserTouch( CBaseEntity *pOther )
+{
+ if ( !pOther->IsPlayer() )
+ {
+ return;
+ }
+
+ //if( GetFlags() & FL_ONGROUND )
+ {
+ CCSPlayer *pPlayer = (CCSPlayer *)pOther;
+
+ if ( !pPlayer )
+ {
+ Assert( false );
+ return;
+ }
+
+ if( pPlayer->GetTeamNumber() == TEAM_CT && !pPlayer->HasDefuser() )
+ {
+ //=============================================================================
+ // HPE_BEGIN:
+ // [dwenger] Added for fun-fact support
+ //=============================================================================
+
+ pPlayer->GiveDefuser( true );
+
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if ( pPlayer->IsDead() == false )
+ {
+ CPASAttenuationFilter filter( pPlayer );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
+ }
+
+ UTIL_Remove( this );
+ return;
+ }
+ }
+}
+
+
diff --git a/game/server/cstrike/item_kevlar.cpp b/game/server/cstrike/item_kevlar.cpp
new file mode 100644
index 0000000..bbb12e6
--- /dev/null
+++ b/game/server/cstrike/item_kevlar.cpp
@@ -0,0 +1,59 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "items.h"
+#include "cs_player.h"
+
+
+class CItemKevlar : public CItem
+{
+public:
+
+ DECLARE_CLASS( CItemKevlar, CItem );
+
+ void Spawn( void )
+ {
+ Precache( );
+ BaseClass::Spawn( );
+ }
+
+ void Precache( void )
+ {
+ PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
+ }
+
+ bool MyTouch( CBasePlayer *pBasePlayer )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
+ if ( !pPlayer )
+ {
+ Assert( false );
+ return false;
+ }
+
+ pPlayer->SetArmorValue( 100 );
+
+ if ( pPlayer->IsDead() == false )
+ {
+ CPASAttenuationFilter filter( pBasePlayer );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
+
+ CSingleUserRecipientFilter user( pPlayer );
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( "item_kevlar" );
+ MessageEnd();
+ }
+
+ return true;
+ }
+};
+
+LINK_ENTITY_TO_CLASS( item_kevlar, CItemKevlar );
+
+
diff --git a/game/server/cstrike/item_nvgs.cpp b/game/server/cstrike/item_nvgs.cpp
new file mode 100644
index 0000000..b013280
--- /dev/null
+++ b/game/server/cstrike/item_nvgs.cpp
@@ -0,0 +1,59 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "items.h"
+#include "cs_player.h"
+
+
+class CItemNvgs : public CItem
+{
+public:
+
+ DECLARE_CLASS( CItemNvgs, CItem );
+
+ void Spawn( void )
+ {
+ Precache( );
+ BaseClass::Spawn( );
+ }
+
+ void Precache( void )
+ {
+ PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
+ }
+
+ bool MyTouch( CBasePlayer *pBasePlayer )
+ {
+ CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
+ if ( !pPlayer )
+ {
+ Assert( false );
+ return false;
+ }
+
+ pPlayer->m_bHasNightVision = true;
+
+ if ( pPlayer->IsDead() == false )
+ {
+ CPASAttenuationFilter filter( pPlayer );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
+
+ CSingleUserRecipientFilter user( pPlayer );
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( "item_nvgs" );
+ MessageEnd();
+ }
+
+ return true;
+ }
+};
+
+LINK_ENTITY_TO_CLASS( item_nvgs, CItemNvgs );
+
+
diff --git a/game/server/cstrike/mapinfo.cpp b/game/server/cstrike/mapinfo.cpp
new file mode 100644
index 0000000..bb2a20a
--- /dev/null
+++ b/game/server/cstrike/mapinfo.cpp
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "mapinfo.h"
+#include "cs_gamerules.h"
+
+LINK_ENTITY_TO_CLASS( info_map_parameters, CMapInfo );
+
+BEGIN_DATADESC( CMapInfo )
+
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "FireWinCondition", InputFireWinCondition ),
+
+END_DATADESC()
+
+CMapInfo *g_pMapInfo = NULL;
+
+
+CMapInfo::CMapInfo()
+{
+ m_flBombRadius = 500.0f;
+ m_iBuyingStatus = 0;
+
+ if ( g_pMapInfo )
+ {
+ // Should only be one of these.
+ Warning( "Warning: Multiple info_map_parameters entities in map!\n" );
+ }
+ else
+ {
+ g_pMapInfo = this;
+ }
+}
+
+
+CMapInfo::~CMapInfo()
+{
+ if ( g_pMapInfo == this )
+ g_pMapInfo = NULL;
+}
+
+
+bool CMapInfo::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq(szKeyName, "buying"))
+ {
+ m_iBuyingStatus = atoi(szValue);
+ return true;
+ }
+ else if (FStrEq(szKeyName, "bombradius"))
+ {
+ m_flBombRadius = (float)(atoi(szValue));
+ if (m_flBombRadius > 2048)
+ m_flBombRadius = 2048;
+
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+
+void CMapInfo::Spawn( void )
+{
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_NONE );
+ AddEffects( EF_NODRAW );
+}
+
+void CMapInfo::InputFireWinCondition(inputdata_t &inputdata )
+{
+ CSGameRules()->TerminateRound( 5, inputdata.value.Int() );
+}
diff --git a/game/server/cstrike/mapinfo.h b/game/server/cstrike/mapinfo.h
new file mode 100644
index 0000000..bd3cb5d
--- /dev/null
+++ b/game/server/cstrike/mapinfo.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef MAPINFO_H
+#define MAPINFO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "baseentity.h"
+
+
+class CMapInfo : public CPointEntity
+{
+public :
+
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CMapInfo, CPointEntity );
+
+ CMapInfo();
+ virtual ~CMapInfo();
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Spawn();
+
+ void InputFireWinCondition( inputdata_t &inputdata );
+
+public:
+ int m_iBuyingStatus;
+ float m_flBombRadius;
+};
+
+
+// The info_map_parameters entity in this map (only one is allowed for).
+extern CMapInfo *g_pMapInfo;
+
+
+#endif // MAPINFO_H
diff --git a/game/server/cstrike/point_surroundtest.cpp b/game/server/cstrike/point_surroundtest.cpp
new file mode 100644
index 0000000..8703827
--- /dev/null
+++ b/game/server/cstrike/point_surroundtest.cpp
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "mapinfo.h"
+
+
+class CSurroundTest : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CSurroundTest, CPointEntity );
+
+ void FireCorrectOutput( inputdata_t &inputdata );
+ void Spawn( void );
+
+private:
+
+ COutputEvent m_On2Speakers;
+ COutputEvent m_On4Speakers;
+ COutputEvent m_On51Speakers;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( point_surroundtest, CSurroundTest );
+
+BEGIN_DATADESC( CSurroundTest )
+ DEFINE_INPUTFUNC( FIELD_VOID, "FireCorrectOutput", FireCorrectOutput ),
+ DEFINE_OUTPUT( m_On2Speakers, "On2Speakers" ),
+ DEFINE_OUTPUT( m_On4Speakers, "On4Speakers" ),
+ DEFINE_OUTPUT( m_On51Speakers, "On51Speakers" ),
+END_DATADESC()
+
+enum
+{
+ SND_SURROUND_HEADPHONES = 0,
+ SND_SURROUND_2SPEAKERS = 2,
+ SND_SURROUND_4SPEAKERS = 4,
+ SND_SURROUND_51SPEAKERS,
+};
+
+void CSurroundTest::FireCorrectOutput( inputdata_t &inputdata )
+{
+ ConVar const *pSurroundCVar = cvar->FindVar( "snd_surround_speakers" );
+
+ if ( pSurroundCVar )
+ {
+ int iSetting = pSurroundCVar->GetInt();
+
+ if ( iSetting == SND_SURROUND_HEADPHONES || iSetting == SND_SURROUND_2SPEAKERS )
+ {
+ m_On2Speakers.FireOutput( this, this );
+ }
+ else if ( iSetting == SND_SURROUND_4SPEAKERS )
+ {
+ m_On4Speakers.FireOutput( this, this );
+ }
+ else if ( iSetting == SND_SURROUND_51SPEAKERS )
+ {
+ m_On51Speakers.FireOutput( this, this );
+ }
+ }
+}
+
+void CSurroundTest::Spawn( void )
+{
+ BaseClass::Spawn();
+} \ No newline at end of file
diff --git a/game/server/cstrike/smokegrenade_projectile.cpp b/game/server/cstrike/smokegrenade_projectile.cpp
new file mode 100644
index 0000000..82d8d7c
--- /dev/null
+++ b/game/server/cstrike/smokegrenade_projectile.cpp
@@ -0,0 +1,189 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "smokegrenade_projectile.h"
+#include "sendproxy.h"
+#include "particle_smokegrenade.h"
+#include "cs_player.h"
+#include "KeyValues.h"
+#include "bot_manager.h"
+#include "weapon_csbase.h"
+
+#define GRENADE_MODEL "models/Weapons/w_eq_smokegrenade_thrown.mdl"
+
+
+LINK_ENTITY_TO_CLASS( smokegrenade_projectile, CSmokeGrenadeProjectile );
+PRECACHE_WEAPON_REGISTER( smokegrenade_projectile );
+
+BEGIN_DATADESC( CSmokeGrenadeProjectile )
+ DEFINE_THINKFUNC( Think_Detonate ),
+ DEFINE_THINKFUNC( Think_Fade ),
+ DEFINE_THINKFUNC( Think_Remove )
+END_DATADESC()
+
+
+CSmokeGrenadeProjectile* CSmokeGrenadeProjectile::Create(
+ const Vector &position,
+ const QAngle &angles,
+ const Vector &velocity,
+ const AngularImpulse &angVelocity,
+ CBaseCombatCharacter *pOwner )
+{
+ CSmokeGrenadeProjectile *pGrenade = (CSmokeGrenadeProjectile*)CBaseEntity::Create( "smokegrenade_projectile", position, angles, pOwner );
+
+ // Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER
+ // one second before detonation.
+ pGrenade->SetTimer( 1.5 );
+ pGrenade->SetAbsVelocity( velocity );
+ pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity );
+ pGrenade->SetThrower( pOwner );
+ pGrenade->SetGravity( 0.55 );
+ pGrenade->SetFriction( 0.7 );
+ pGrenade->m_flDamage = 100;
+ pGrenade->ChangeTeam( pOwner->GetTeamNumber() );
+ pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
+ pGrenade->SetTouch( &CBaseGrenade::BounceTouch );
+
+ pGrenade->SetGravity( BaseClass::GetGrenadeGravity() );
+ pGrenade->SetFriction( BaseClass::GetGrenadeFriction() );
+ pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() );
+ pGrenade->m_bDidSmokeEffect = false;
+
+ pGrenade->m_pWeaponInfo = GetWeaponInfo( WEAPON_SMOKEGRENADE );
+
+ return pGrenade;
+}
+
+
+void CSmokeGrenadeProjectile::SetTimer( float timer )
+{
+ SetThink( &CSmokeGrenadeProjectile::Think_Detonate );
+ SetNextThink( gpGlobals->curtime + timer );
+
+ TheBots->SetGrenadeRadius( this, 0.0f );
+}
+
+void CSmokeGrenadeProjectile::Think_Detonate()
+{
+ if ( GetAbsVelocity().Length() > 0.1 )
+ {
+ // Still moving. Don't detonate yet.
+ SetNextThink( gpGlobals->curtime + 0.2 );
+ return;
+ }
+
+ TheBots->SetGrenadeRadius( this, SmokeGrenadeRadius );
+
+ // Ok, we've stopped rolling or whatever. Now detonate.
+ ParticleSmokeGrenade *pGren = (ParticleSmokeGrenade*)CBaseEntity::Create( PARTICLESMOKEGRENADE_ENTITYNAME, GetAbsOrigin(), QAngle(0,0,0), NULL );
+ if ( pGren )
+ {
+ pGren->FillVolume();
+ pGren->SetFadeTime( 15, 20 );
+ pGren->SetAbsOrigin( GetAbsOrigin() );
+
+ //tell the hostages about the smoke!
+ CBaseEntity *pEntity = NULL;
+ variant_t var; //send the location of the smoke?
+ var.SetVector3D( GetAbsOrigin() );
+ while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "hostage_entity" ) ) != NULL)
+ {
+ //send to hostages that have a resonable chance of being in it while its still smoking
+ if( (GetAbsOrigin() - pEntity->GetAbsOrigin()).Length() < 1000 )
+ pEntity->AcceptInput( "smokegrenade", this, this, var, 0 );
+ }
+
+ // tell the bots a smoke grenade has exploded
+ CCSPlayer *player = ToCSPlayer(GetThrower());
+ if ( player )
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "smokegrenade_detonate" );
+ if ( event )
+ {
+ event->SetInt( "userid", player->GetUserID() );
+ event->SetFloat( "x", GetAbsOrigin().x );
+ event->SetFloat( "y", GetAbsOrigin().y );
+ event->SetFloat( "z", GetAbsOrigin().z );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ }
+
+ m_hSmokeEffect = pGren;
+ m_bDidSmokeEffect = true;
+
+ EmitSound( "BaseSmokeEffect.Sound" );
+
+ m_nRenderMode = kRenderTransColor;
+ SetNextThink( gpGlobals->curtime + 5 );
+ SetThink( &CSmokeGrenadeProjectile::Think_Fade );
+}
+
+
+// Fade the projectile out over time before making it disappear
+void CSmokeGrenadeProjectile::Think_Fade()
+{
+ SetNextThink( gpGlobals->curtime );
+
+ color32 c = GetRenderColor();
+ c.a -= 1;
+ SetRenderColor( c.r, c.b, c.g, c.a );
+
+ if ( !c.a )
+ {
+ TheBots->RemoveGrenade( this );
+
+ SetModelName( NULL_STRING );//invisible
+ SetNextThink( gpGlobals->curtime + 20 );
+ SetThink( &CSmokeGrenadeProjectile::Think_Remove ); // Spit out smoke for 10 seconds.
+ SetSolid( SOLID_NONE );
+ }
+}
+
+
+void CSmokeGrenadeProjectile::Think_Remove()
+{
+ if ( m_hSmokeEffect.Get() )
+ UTIL_Remove( m_hSmokeEffect );
+
+ TheBots->RemoveGrenade( this );
+
+ SetModelName( NULL_STRING );//invisible
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+}
+
+//Implement this so we never call the base class,
+//but this should never be called either.
+void CSmokeGrenadeProjectile::Detonate( void )
+{
+ Assert(!"Smoke grenade handles its own detonation");
+}
+
+
+void CSmokeGrenadeProjectile::Spawn()
+{
+ SetModel( GRENADE_MODEL );
+ BaseClass::Spawn();
+}
+
+
+void CSmokeGrenadeProjectile::Precache()
+{
+ PrecacheModel( GRENADE_MODEL );
+ PrecacheScriptSound( "BaseSmokeEffect.Sound" );
+ PrecacheScriptSound( "SmokeGrenade.Bounce" );
+ BaseClass::Precache();
+}
+
+void CSmokeGrenadeProjectile::BounceSound( void )
+{
+ if ( !m_bDidSmokeEffect )
+ {
+ EmitSound( "SmokeGrenade.Bounce" );
+ }
+}
diff --git a/game/server/cstrike/smokegrenade_projectile.h b/game/server/cstrike/smokegrenade_projectile.h
new file mode 100644
index 0000000..ea6ab1c
--- /dev/null
+++ b/game/server/cstrike/smokegrenade_projectile.h
@@ -0,0 +1,53 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef HEGRENADE_PROJECTILE_H
+#define HEGRENADE_PROJECTILE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "basecsgrenade_projectile.h"
+
+
+class CSmokeGrenadeProjectile : public CBaseCSGrenadeProjectile
+{
+public:
+ DECLARE_CLASS( CSmokeGrenadeProjectile, CBaseCSGrenadeProjectile );
+ DECLARE_DATADESC();
+
+// Overrides.
+public:
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void Detonate();
+ virtual void BounceSound( void );
+
+ void Think_Detonate();
+ void Think_Fade();
+ void Think_Remove();
+
+
+// Grenade stuff.
+public:
+
+ static CSmokeGrenadeProjectile* Create(
+ const Vector &position,
+ const QAngle &angles,
+ const Vector &velocity,
+ const AngularImpulse &angVelocity,
+ CBaseCombatCharacter *pOwner );
+
+ void SetTimer( float timer );
+
+ EHANDLE m_hSmokeEffect;
+ bool m_bDidSmokeEffect;
+};
+
+
+#endif // HEGRENADE_PROJECTILE_H
diff --git a/game/server/cstrike/te_radioicon.cpp b/game/server/cstrike/te_radioicon.cpp
new file mode 100644
index 0000000..771ce9f
--- /dev/null
+++ b/game/server/cstrike/te_radioicon.cpp
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "basetempentity.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Dispatches blood stream tempentity
+//-----------------------------------------------------------------------------
+class CTERadioIcon : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTERadioIcon, CBaseTempEntity );
+
+ CTERadioIcon( const char *name );
+ virtual ~CTERadioIcon( void );
+
+ void Precache( void );
+
+ DECLARE_SERVERCLASS();
+
+public:
+
+ CNetworkVar( int, m_iAttachToClient );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+//-----------------------------------------------------------------------------
+CTERadioIcon::CTERadioIcon( const char *name ) :
+ CBaseTempEntity( name )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTERadioIcon::~CTERadioIcon( void )
+{
+}
+
+void CTERadioIcon::Precache( void )
+{
+ CBaseEntity::PrecacheModel("sprites/radio.vmt");
+}
+
+IMPLEMENT_SERVERCLASS_ST(CTERadioIcon, DT_TERadioIcon)
+ SendPropInt( SENDINFO(m_iAttachToClient), 8, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+
+// Singleton to fire StickyBolt objects
+static CTERadioIcon g_TERadioIcon( "RadioIcon" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : filter -
+// delay -
+// pPlayer -
+//-----------------------------------------------------------------------------
+void TE_RadioIcon( IRecipientFilter& filter, float delay, CBaseEntity *pPlayer )
+{
+ g_TERadioIcon.m_iAttachToClient = pPlayer->entindex();
+
+ // Send it over the wire
+ g_TERadioIcon.Create( filter, delay );
+}
diff --git a/game/server/cstrike/te_shotgun_shot.cpp b/game/server/cstrike/te_shotgun_shot.cpp
new file mode 100644
index 0000000..d730f18
--- /dev/null
+++ b/game/server/cstrike/te_shotgun_shot.cpp
@@ -0,0 +1,161 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "basetempentity.h"
+#include "fx_cs_shared.h"
+
+
+#define NUM_BULLET_SEED_BITS 8
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Display's a blood sprite
+//-----------------------------------------------------------------------------
+class CTEFireBullets : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEFireBullets, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEFireBullets( const char *name );
+ virtual ~CTEFireBullets( void );
+
+public:
+ CNetworkVar( int, m_iPlayer );
+ CNetworkVector( m_vecOrigin );
+ CNetworkQAngle( m_vecAngles );
+ CNetworkVar( int, m_iWeaponID );
+ CNetworkVar( int, m_iMode );
+ CNetworkVar( int, m_iSeed );
+ CNetworkVar( float, m_fInaccuracy );
+ CNetworkVar( float, m_fSpread );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+//-----------------------------------------------------------------------------
+CTEFireBullets::CTEFireBullets( const char *name ) :
+ CBaseTempEntity( name )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTEFireBullets::~CTEFireBullets( void )
+{
+}
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEFireBullets, DT_TEFireBullets)
+ SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ),
+ SendPropAngle( SENDINFO_VECTORELEM( m_vecAngles, 0 ), 13, 0 ),
+ SendPropAngle( SENDINFO_VECTORELEM( m_vecAngles, 1 ), 13, 0 ),
+ SendPropInt( SENDINFO( m_iWeaponID ), 5, SPROP_UNSIGNED ), // max 31 weapons
+ SendPropInt( SENDINFO( m_iMode ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iSeed ), NUM_BULLET_SEED_BITS, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iPlayer ), 6, SPROP_UNSIGNED ), // max 64 players, see MAX_PLAYERS
+ SendPropFloat( SENDINFO( m_fInaccuracy ), 10, 0, 0, 1 ),
+ SendPropFloat( SENDINFO( m_fSpread ), 8, 0, 0, 0.1f ),
+END_SEND_TABLE()
+
+
+// Singleton
+static CTEFireBullets g_TEFireBullets( "Shotgun Shot" );
+
+
+void TE_FireBullets(
+ int iPlayerIndex,
+ const Vector &vOrigin,
+ const QAngle &vAngles,
+ int iWeaponID,
+ int iMode,
+ int iSeed,
+ float fInaccuracy,
+ float fSpread
+ )
+{
+ CPASFilter filter( vOrigin );
+ filter.UsePredictionRules();
+
+ g_TEFireBullets.m_iPlayer = iPlayerIndex-1;
+ g_TEFireBullets.m_vecOrigin = vOrigin;
+ g_TEFireBullets.m_vecAngles = vAngles;
+ g_TEFireBullets.m_iSeed = iSeed;
+ g_TEFireBullets.m_fInaccuracy = fInaccuracy;
+ g_TEFireBullets.m_fSpread = fSpread;
+ g_TEFireBullets.m_iMode = iMode;
+ g_TEFireBullets.m_iWeaponID = iWeaponID;
+
+ Assert( iSeed < (1 << NUM_BULLET_SEED_BITS) );
+
+ g_TEFireBullets.Create( filter, 0 );
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Displays a bomb plant animation
+//-----------------------------------------------------------------------------
+class CTEPlantBomb : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEPlantBomb, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEPlantBomb( const char *name );
+ virtual ~CTEPlantBomb( void );
+
+public:
+ CNetworkVar( int, m_iPlayer );
+ CNetworkVector( m_vecOrigin );
+ CNetworkVar( PlantBombOption_t, m_option );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+//-----------------------------------------------------------------------------
+CTEPlantBomb::CTEPlantBomb( const char *name ) :
+ CBaseTempEntity( name )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTEPlantBomb::~CTEPlantBomb( void )
+{
+}
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEPlantBomb, DT_TEPlantBomb)
+ SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ),
+ SendPropInt( SENDINFO( m_iPlayer ), 6, SPROP_UNSIGNED ), // max 64 players, see MAX_PLAYERS
+ SendPropInt( SENDINFO( m_option ), 1, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+
+// Singleton
+static CTEPlantBomb g_TEPlantBomb( "Bomb Plant" );
+
+
+void TE_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option )
+{
+ CPASFilter filter( vOrigin );
+ filter.UsePredictionRules();
+
+ g_TEPlantBomb.m_iPlayer = iPlayerIndex-1;
+ g_TEPlantBomb.m_option = option;
+ g_TEPlantBomb.Create( filter, 0 );
+}
diff --git a/game/server/cstrike/te_shotgun_shot.h b/game/server/cstrike/te_shotgun_shot.h
new file mode 100644
index 0000000..8d72e9c
--- /dev/null
+++ b/game/server/cstrike/te_shotgun_shot.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TE_SHOTGUN_SHOT_H
+#define TE_SHOTGUN_SHOT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+void TE_FireBullets(
+ int iPlayerIndex,
+ const Vector &vOrigin,
+ const QAngle &vAngles,
+ int iWeaponID,
+ int iMode,
+ int iSeed,
+ float fInaccuracy,
+ float fSpread
+ );
+
+void TE_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option );
+
+
+#endif // TE_SHOTGUN_SHOT_H