This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

View File

@@ -0,0 +1,116 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), Leon Hartwig, 2003
#include "cbase.h"
#include "cs_shareddefs.h"
#include "basegrenade_shared.h"
#include "bot.h"
#include "bot_util.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
/// @todo Remove this nasty hack - CreateFakeClient() calls CBot::Spawn, which needs the profile and team
const BotProfile *g_botInitProfile = NULL;
int g_botInitTeam = 0;
//
// NOTE: Because CBot had to be templatized, the code was moved into bot.h
//
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
ActiveGrenade::ActiveGrenade( CBaseGrenade *grenadeEntity )
{
m_entity = grenadeEntity;
m_detonationPosition = grenadeEntity->GetAbsOrigin();
m_dieTimestamp = 0.0f;
m_isSmoke = false;
m_isFlashbang = false;
m_isMolotov = false;
m_isDecoy = false;
switch ( grenadeEntity->GetGrenadeType() )
{
case GRENADE_TYPE_EXPLOSIVE: m_radius = HEGrenadeRadius; break;
case GRENADE_TYPE_FLASH: m_radius = FlashbangGrenadeRadius; m_isFlashbang = true; break;
case GRENADE_TYPE_FIRE: m_radius = MolotovGrenadeRadius; m_isMolotov = true; break;
case GRENADE_TYPE_DECOY: m_radius = DecoyGrenadeRadius; m_isDecoy = true; break;
case GRENADE_TYPE_SMOKE: m_radius = SmokeGrenadeRadius; m_isSmoke = true; break;
case GRENADE_TYPE_SENSOR: m_radius = MolotovGrenadeRadius; m_isSensor = true; break;
default:
AssertMsg( 0, "Invalid grenade type!\n" );
m_radius = HEGrenadeRadius;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Called when the grenade in the world goes away
*/
void ActiveGrenade::OnEntityGone( void )
{
if (m_isSmoke)
{
// smoke lingers after grenade is gone
const float smokeLingerTime = 4.0f;
m_dieTimestamp = gpGlobals->curtime + smokeLingerTime;
}
m_entity = NULL;
}
//--------------------------------------------------------------------------------------------------------------
void ActiveGrenade::Update( void )
{
if (m_entity != NULL)
{
m_detonationPosition = m_entity->GetAbsOrigin();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this grenade is valid
*/
bool ActiveGrenade::IsValid( void ) const
{
if ( m_isSmoke )
{
if ( m_entity == NULL && gpGlobals->curtime > m_dieTimestamp )
{
return false;
}
}
else
{
if ( m_entity == NULL )
{
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
const Vector &ActiveGrenade::GetPosition( void ) const
{
// smoke grenades can vanish before the smoke itself does - refer to the detonation position
if (m_entity == NULL)
return GetDetonationPosition();
return m_entity->GetAbsOrigin();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#ifndef BOT_CONSTANTS_H
#define BOT_CONSTANTS_H
/// version number is MAJOR.MINOR
#define BOT_VERSION_MAJOR 1
#define BOT_VERSION_MINOR 50
//--------------------------------------------------------------------------------------------------------
/**
* Difficulty levels
*/
enum BotDifficultyType
{
BOT_EASY = 0,
BOT_NORMAL = 1,
BOT_HARD = 2,
BOT_EXPERT = 3,
NUM_DIFFICULTY_LEVELS
};
#ifdef DEFINE_DIFFICULTY_NAMES
char *BotDifficultyName[] =
{
"EASY", "NORMAL", "HARD", "EXPERT", NULL
};
#else
extern char *BotDifficultyName[];
#endif
namespace BotProfileInputDevice
{
enum Device
{
GAMEPAD = 0,
KB_MOUSE = 1,
PS3_MOVE = 2,
HYDRA = 3,
SHARPSHOOTER = 4,
COUNT, // Auto list counter
FORCE_INT32 = 0x7FFFFFFF // Force the typedef to be int32
};
};
typedef BotProfileInputDevice::Device BotProfileDevice_t;
#endif // BOT_CONSTANTS_H

View File

@@ -0,0 +1,496 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// bot_hide.cpp
// Mechanisms for using Hiding Spots in the Navigation Mesh
// Author: Michael Booth, 2003-2004
#include "cbase.h"
#include "bot.h"
#include "cs_nav_pathfind.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* If a player is at the given spot, return true
*/
bool IsSpotOccupied( CBaseEntity *me, const Vector &pos )
{
const float closeRange = 75.0f; // 50
// is there a player in this spot
float range;
CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range );
if (player != me)
{
if (player && range < closeRange)
return true;
}
// is there is a hostage in this spot
// BOTPORT: Implement hostage manager
/*
if (g_pHostages)
{
CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range );
if (hostage && hostage != me && range < closeRange)
return true;
}
*/
return false;
}
//--------------------------------------------------------------------------------------------------------------
class CollectHidingSpotsFunctor
{
public:
CollectHidingSpotsFunctor( CBaseEntity *me, const Vector &origin, float range, int flags, Place place = UNDEFINED_PLACE ) : m_origin( origin )
{
m_me = me;
m_count = 0;
m_range = range;
m_flags = (unsigned char)flags;
m_place = place;
m_totalWeight = 0;
}
enum { MAX_SPOTS = 256 };
bool operator() ( CNavArea *area )
{
// if a place is specified, only consider hiding spots from areas in that place
if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place)
return true;
// collect all the hiding spots in this area
const HidingSpotVector *pSpots = area->GetHidingSpots();
FOR_EACH_VEC( (*pSpots), it )
{
const HidingSpot *spot = (*pSpots)[ it ];
// if we've filled up, stop searching
if (m_count == MAX_SPOTS)
{
return false;
}
// make sure hiding spot is in range
if (m_range > 0.0f)
{
if ((spot->GetPosition() - m_origin).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;
}
if (spot->GetArea() && (spot->GetArea()->GetAttributes() & NAV_MESH_DONT_HIDE))
{
// the area has been marked as DONT_HIDE since the last analysis, so let's ignore it
continue;
}
// only collect hiding spots with matching flags
if (m_flags & spot->GetFlags())
{
m_hidingSpot[ m_count ] = &spot->GetPosition();
m_hidingSpotWeight[ m_count ] = m_totalWeight;
// if it's an 'avoid' area, give it a low weight
if ( spot->GetArea() && ( spot->GetArea()->GetAttributes() & NAV_MESH_AVOID ) )
{
m_totalWeight += 1;
}
else
{
m_totalWeight += 2;
}
++m_count;
}
}
return (m_count < MAX_SPOTS);
}
/**
* Remove the spot at index "i"
*/
void RemoveSpot( int i )
{
if (m_count == 0)
return;
for( int j=i+1; j<m_count; ++j )
m_hidingSpot[j-1] = m_hidingSpot[j];
--m_count;
}
int GetRandomHidingSpot( void )
{
int weight = RandomInt( 0, m_totalWeight-1 );
for ( int i=0; i<m_count-1; ++i )
{
// if the next spot's starting weight is over the target weight, this spot is the one
if ( m_hidingSpotWeight[i+1] >= weight )
{
return i;
}
}
// if we didn't find any, it's the last one
return m_count - 1;
}
CBaseEntity *m_me;
const Vector &m_origin;
float m_range;
const Vector *m_hidingSpot[ MAX_SPOTS ];
int m_hidingSpotWeight[ MAX_SPOTS ];
int m_totalWeight;
int m_count;
unsigned char m_flags;
Place m_place;
};
/**
* Do a breadth-first search to find a nearby hiding spot and return it.
* Don't pick a hiding spot that a Player is currently occupying.
* @todo Clean up this mess
*/
const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange, bool isSniper, bool useNearest )
{
CNavArea *startArea = TheNavMesh->GetNearestNavArea( pos );
if (startArea == NULL)
return NULL;
// collect set of nearby hiding spots
if (isSniper)
{
CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
SearchSurroundingAreas( startArea, pos, collector, maxRange );
if (collector.m_count)
{
int which = collector.GetRandomHidingSpot();
return collector.m_hidingSpot[ which ];
}
else
{
// no ideal sniping spots, look for "good" sniping spots
CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
SearchSurroundingAreas( startArea, pos, collector, maxRange );
if (collector.m_count)
{
int which = collector.GetRandomHidingSpot();
return collector.m_hidingSpot[ which ];
}
// no sniping spots at all.. fall through and pick a normal hiding spot
}
}
// collect hiding spots with decent "cover"
CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IN_COVER );
SearchSurroundingAreas( startArea, pos, collector, maxRange );
if (collector.m_count == 0)
{
// no hiding spots at all - if we're not a sniper, try to find a sniper spot to use instead
if (!isSniper)
{
return FindNearbyHidingSpot( me, pos, maxRange, true, useNearest );
}
return NULL;
}
if (useNearest)
{
// return closest hiding spot
const Vector *closest = NULL;
float closeRangeSq = 9999999999.9f;
for( int i=0; i<collector.m_count; ++i )
{
float rangeSq = (*collector.m_hidingSpot[i] - pos).LengthSqr();
if (rangeSq < closeRangeSq)
{
closeRangeSq = rangeSq;
closest = collector.m_hidingSpot[i];
}
}
return closest;
}
// select a hiding spot at random
int which = collector.GetRandomHidingSpot();
return collector.m_hidingSpot[ which ];
}
//--------------------------------------------------------------------------------------------------------------
/**
* Select a random hiding spot among the nav areas that are tagged with the given place
*/
const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper )
{
// collect set of nearby hiding spots
if (isSniper)
{
CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IDEAL_SNIPER_SPOT, place );
TheNavMesh->ForAllAreas( collector );
if (collector.m_count)
{
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
else
{
// no ideal sniping spots, look for "good" sniping spots
CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::GOOD_SNIPER_SPOT, place );
TheNavMesh->ForAllAreas( collector );
if (collector.m_count)
{
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
// no sniping spots at all.. fall through and pick a normal hiding spot
}
}
// collect hiding spots with decent "cover"
CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IN_COVER, place );
TheNavMesh->ForAllAreas( collector );
if (collector.m_count == 0)
return NULL;
// select a hiding spot at random
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
//--------------------------------------------------------------------------------------------------------------------
/**
* Select a nearby retreat spot.
* Don't pick a hiding spot that a Player is currently occupying.
* If "avoidTeam" is nonzero, avoid getting close to members of that team.
*/
const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange, int avoidTeam )
{
CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
if (startArea == NULL)
return NULL;
// collect hiding spots with decent "cover"
CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER );
SearchSurroundingAreas( startArea, start, collector, maxRange );
if (collector.m_count == 0)
return NULL;
// find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover
for( int i=0; i<collector.m_count; ++i )
{
// check if we would have to cross a line of fire to reach this hiding spot
if (IsCrossingLineOfFire( start, *collector.m_hidingSpot[i], me ))
{
collector.RemoveSpot( i );
// back up a step, so iteration won't skip a spot
--i;
continue;
}
// check if there is someone on the avoidTeam near this hiding spot
if (avoidTeam)
{
float range;
if (UTIL_GetClosestPlayer( *collector.m_hidingSpot[i], avoidTeam, &range ))
{
const float dangerRange = 150.0f;
if (range < dangerRange)
{
// there is an avoidable player too near this spot - remove it
collector.RemoveSpot( i );
// back up a step, so iteration won't skip a spot
--i;
continue;
}
}
}
}
if (collector.m_count <= 0)
return NULL;
// all remaining spots are ok - pick one at random
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
//--------------------------------------------------------------------------------------------------------------------
/**
* Functor to collect all hiding spots in range that we can reach before the enemy arrives.
* NOTE: This only works for the initial rush.
*/
class CollectArriveFirstSpotsFunctor
{
public:
CollectArriveFirstSpotsFunctor( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float range, int flags ) : m_searchOrigin( searchOrigin )
{
m_me = me;
m_count = 0;
m_range = range;
m_flags = (unsigned char)flags;
m_enemyArriveTime = enemyArriveTime;
}
enum { MAX_SPOTS = 256 };
bool operator() ( CNavArea *area )
{
const HidingSpotVector *pSpots = area->GetHidingSpots();
FOR_EACH_VEC( (*pSpots), it )
{
const HidingSpot *spot = (*pSpots)[ it ];
// make sure hiding spot is in range
if (m_range > 0.0f)
{
if ((spot->GetPosition() - m_searchOrigin).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;
}
// only collect hiding spots with matching flags
if (!(m_flags & spot->GetFlags()))
{
continue;
}
// only collect this hiding spot if we can reach it before the enemy arrives
// NOTE: This assumes the area is fairly small and the difference of moving to the corner vs the center is small
const float settleTime = 1.0f;
if(!spot->GetArea())
{
AssertMsg(false, "Check console spew for Hiding Spot off the Nav Mesh errors.");
continue;
}
if (spot->GetArea()->GetEarliestOccupyTime( m_me->GetTeamNumber() ) + settleTime < m_enemyArriveTime)
{
m_hidingSpot[ m_count++ ] = spot;
}
}
// if we've filled up, stop searching
if (m_count == MAX_SPOTS)
return false;
return true;
}
CBaseEntity *m_me;
const Vector &m_searchOrigin;
float m_range;
float m_enemyArriveTime;
unsigned char m_flags;
const HidingSpot *m_hidingSpot[ MAX_SPOTS ];
int m_count;
};
/**
* Select a hiding spot that we can reach before the enemy arrives.
* NOTE: This only works for the initial rush.
*/
const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper )
{
CNavArea *startArea = TheNavMesh->GetNearestNavArea( searchOrigin );
if (startArea == NULL)
return NULL;
// collect set of nearby hiding spots
if (isSniper)
{
CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
if (collector.m_count)
{
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
else
{
// no ideal sniping spots, look for "good" sniping spots
CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
if (collector.m_count)
{
int which = RandomInt( 0, collector.m_count-1 );
return collector.m_hidingSpot[ which ];
}
// no sniping spots at all.. fall through and pick a normal hiding spot
}
}
// collect hiding spots with decent "cover"
CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IN_COVER | HidingSpot::EXPOSED );
SearchSurroundingAreas( startArea, searchOrigin, 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_hidingSpot[ which ];
}

View File

@@ -0,0 +1,352 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "bot.h"
#include "bot_manager.h"
#include "nav_area.h"
#include "bot_util.h"
#include "basegrenade_shared.h"
#include "cs_bot.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
float g_BotUpkeepInterval = 0.0f;
float g_BotUpdateInterval = 0.0f;
//--------------------------------------------------------------------------------------------------------------
CBotManager::CBotManager()
{
InitBotTrig();
}
//--------------------------------------------------------------------------------------------------------------
CBotManager::~CBotManager()
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when the round is restarting
*/
void CBotManager::RestartRound( void )
{
DestroyAllGrenades();
ClearDebugMessages();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked at the start of each frame
*/
void CBotManager::StartFrame( void )
{
VPROF_BUDGET( "CBotManager::StartFrame", VPROF_BUDGETGROUP_NPCS );
ValidateActiveGrenades();
// debug smoke grenade visualization
if (cv_bot_debug.GetInt() == 5)
{
Vector edge, lastEdge;
FOR_EACH_LL( m_activeGrenadeList, it )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
const Vector &pos = ag->GetDetonationPosition();
UTIL_DrawBeamPoints( pos, pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 );
lastEdge = Vector( ag->GetRadius() + pos.x, pos.y, pos.z );
float angle;
for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
{
edge.x = ag->GetRadius() * BotCOS( angle ) + pos.x;
edge.y = pos.y;
edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
lastEdge = edge;
}
lastEdge = Vector( pos.x, ag->GetRadius() + pos.y, pos.z );
for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
{
edge.x = pos.x;
edge.y = ag->GetRadius() * BotCOS( angle ) + pos.y;
edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
lastEdge = edge;
}
}
}
// set frame duration
g_BotUpkeepInterval = m_frameTimer.GetElapsedTime();
m_frameTimer.Start();
g_BotUpdateInterval = (g_BotUpdateSkipCount+1) * g_BotUpkeepInterval;
//
// Process each active bot
//
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (!player)
continue;
// Hack for now so the temp bot code works. The temp bots are very useful for debugging
// because they can be setup to mimic the player's usercmds.
if (player->IsBot() && IsEntityValid( player ) )
{
// EVIL: Messes up vtables
//CBot< CBasePlayer > *bot = static_cast< CBot< CBasePlayer > * >( player );
CCSBot *bot = dynamic_cast< CCSBot * >( player );
if ( bot )
{
{
SNPROF("Upkeep");
bot->Upkeep();
}
if (((gpGlobals->tickcount + bot->entindex()) % g_BotUpdateSkipCount) == 0)
{
{
SNPROF("ResetCommand");
bot->ResetCommand();
}
{
SNPROF("Bot Update");
bot->Update();
}
}
{
SNPROF("UpdatePlayer");
bot->UpdatePlayer();
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add an active grenade to the bot's awareness
*/
void CBotManager::AddGrenade( CBaseGrenade *grenade )
{
ActiveGrenade *ag = new ActiveGrenade( grenade );
m_activeGrenadeList.AddToTail( ag );
}
//--------------------------------------------------------------------------------------------------------------
/**
* The grenade entity in the world is going away
*/
void CBotManager::RemoveGrenade( CBaseGrenade *grenade )
{
FOR_EACH_LL( m_activeGrenadeList, it )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
if (ag->IsEntity( grenade ))
{
ag->OnEntityGone();
m_activeGrenadeList.Remove( it );
delete ag;
return;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* The grenade entity has changed its radius
*/
void CBotManager::SetGrenadeRadius( CBaseGrenade *grenade, float radius )
{
FOR_EACH_LL( m_activeGrenadeList, it )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
if (ag->IsEntity( grenade ))
{
ag->SetRadius( radius );
return;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destroy any invalid active grenades
*/
void CBotManager::ValidateActiveGrenades( void )
{
int it = m_activeGrenadeList.Head();
while( it != m_activeGrenadeList.InvalidIndex() )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
int current = it;
it = m_activeGrenadeList.Next( it );
// lazy validation
if (!ag->IsValid())
{
m_activeGrenadeList.Remove( current );
delete ag;
continue;
}
else
{
ag->Update();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CBotManager::DestroyAllGrenades( void )
{
int it = m_activeGrenadeList.Head();
while ( it != m_activeGrenadeList.InvalidIndex() )
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next( it );
m_activeGrenadeList.Remove( current );
delete ag;
}
m_activeGrenadeList.PurgeAndDeleteElements();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if position is inside a smoke cloud
*/
bool CBotManager::IsInsideSmokeCloud( const Vector *pos, float radius )
{
int it = m_activeGrenadeList.Head();
while( it != m_activeGrenadeList.InvalidIndex() )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
int current = it;
it = m_activeGrenadeList.Next( it );
// lazy validation
if (!ag->IsValid())
{
m_activeGrenadeList.Remove( current );
delete ag;
continue;
}
if (ag->IsSmoke())
{
const Vector &smokeOrigin = ag->GetDetonationPosition();
if ((smokeOrigin - *pos).IsLengthLessThan( ag->GetRadius() + radius ))
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if line intersects smoke volume
* Determine the length of the line of sight covered by each smoke cloud,
* and sum them (overlap is additive for obstruction).
* If the overlap exceeds the threshold, the bot can't see through.
*/
bool CBotManager::IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat )
{
VPROF_BUDGET( "CBotManager::IsLineBlockedBySmoke", VPROF_BUDGETGROUP_NPCS );
float totalSmokedLength = 0.0f; // distance along line of sight covered by smoke
// compute unit vector and length of line of sight segment
//Vector sightDir = to - from;
//float sightLength = sightDir.NormalizeInPlace();
FOR_EACH_LL( m_activeGrenadeList, it )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
const float smokeRadiusSq = ag->GetRadius() * ag->GetRadius() * grenadeBloat * grenadeBloat;
if ( ag->IsSmoke() && CSGameRules() )
{
float flLengthAdd = CSGameRules()->CheckTotalSmokedLength( smokeRadiusSq, ag->GetDetonationPosition(), from, to );
// get the totalSmokedLength and check to see if the line starts or stops in smoke. If it does this will return -1 and we should just bail early
if ( flLengthAdd == -1 )
return true;
totalSmokedLength += flLengthAdd;
}
}
// define how much smoke a bot can see thru
const float maxSmokedLength = 0.7f * SmokeGrenadeRadius;
// return true if the total length of smoke-covered line-of-sight is too much
return (totalSmokedLength > maxSmokedLength);
}
//--------------------------------------------------------------------------------------------------------------
void CBotManager::ClearDebugMessages( void )
{
m_debugMessageCount = 0;
m_currentDebugMessage = -1;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add a new debug message to the message history
*/
void CBotManager::AddDebugMessage( const char *msg )
{
if (++m_currentDebugMessage >= MAX_DBG_MSGS)
{
m_currentDebugMessage = 0;
}
if (m_debugMessageCount < MAX_DBG_MSGS)
{
++m_debugMessageCount;
}
Q_strncpy( m_debugMessage[ m_currentDebugMessage ].m_string, msg, MAX_DBG_MSG_SIZE );
m_debugMessage[ m_currentDebugMessage ].m_age.Start();
}

View File

@@ -0,0 +1,193 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef BASE_CONTROL_H
#define BASE_CONTROL_H
#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
extern float g_BotUpkeepInterval; ///< duration between bot upkeeps
extern float g_BotUpdateInterval; ///< duration between bot updates
const int g_BotUpdateSkipCount = 2; ///< number of upkeep periods to skip update
class CNavArea;
//--------------------------------------------------------------------------------------------------------------
class CBaseGrenade;
/**
* An ActiveGrenade is a representation of a grenade in the world
* NOTE: Currently only used for smoke grenade line-of-sight testing
* @todo Use system allow bots to avoid HE and Flashbangs
*/
class ActiveGrenade
{
public:
ActiveGrenade( CBaseGrenade *grenadeEntity );
void OnEntityGone( void ); ///< called when the grenade in the world goes away
void Update( void ); ///< called every frame
bool IsValid( void ) const ; ///< return true if this grenade is valid
bool IsEntity( CBaseGrenade *grenade ) const { return (grenade == m_entity) ? true : false; }
CBaseGrenade *GetEntity( void ) const { return m_entity; }
const Vector &GetDetonationPosition( void ) const { return m_detonationPosition; }
const Vector &GetPosition( void ) const;
bool IsSmoke( void ) const { return m_isSmoke; }
bool IsFlashbang( void ) const { return m_isFlashbang; }
bool IsMolotov( void ) const { return m_isMolotov; }
bool IsDecoy( void ) const { return m_isDecoy; }
bool IsSensor( void ) const { return m_isSensor; }
CBaseGrenade *GetGrenade( void ) { return m_entity; }
float GetRadius( void ) const { return m_radius; }
void SetRadius( float radius ) { m_radius = radius; }
private:
CBaseGrenade *m_entity; ///< the entity
Vector m_detonationPosition; ///< the location where the grenade detonated (smoke)
float m_dieTimestamp; ///< time this should go away after m_entity is NULL
bool m_isSmoke; ///< true if this is a smoke grenade
bool m_isFlashbang; ///< true if this is a flashbang grenade
bool m_isMolotov; ///< true if this is a molotov grenade
bool m_isDecoy; ///< true if this is a decoy grenade
bool m_isSensor; ///< true if this is a sensor grenade
float m_radius;
};
typedef CUtlLinkedList<ActiveGrenade *> ActiveGrenadeList;
//--------------------------------------------------------------------------------------------------------------
/**
* This class manages all active bots, propagating events to them and updating them.
*/
class CBotManager
{
public:
CBotManager();
virtual ~CBotManager();
CBasePlayer *AllocateAndBindBotEntity( edict_t *ed ); ///< allocate the appropriate entity for the bot and bind it to the given edict
virtual CBasePlayer *AllocateBotEntity( void ) = 0; ///< factory method to allocate the appropriate entity for the bot
virtual void ClientDisconnect( CBaseEntity *entity ) = 0;
virtual bool ClientCommand( CBasePlayer *player, const CCommand &args ) = 0;
virtual void ServerActivate( void ) = 0;
virtual void ServerDeactivate( void ) = 0;
virtual bool ServerCommand( const char * pcmd ) = 0;
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 = 0; ///< return priority of player (0 = max pri)
void AddGrenade( CBaseGrenade *grenade ); ///< add an active grenade to the bot's awareness
void RemoveGrenade( CBaseGrenade *grenade ); ///< the grenade entity in the world is going away
void SetGrenadeRadius( CBaseGrenade *grenade, float radius ); ///< the radius of the grenade entity (or associated smoke cloud)
void ValidateActiveGrenades( void ); ///< destroy any invalid active grenades
void DestroyAllGrenades( void );
bool IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat = 1.0f ); ///< return true if line intersects smoke volume, with grenade radius increased by the grenadeBloat factor
bool IsInsideSmokeCloud( const Vector *pos, float radius ); ///< return true if sphere at position overlaps a smoke cloud
//
// Invoke functor on all active grenades.
// If any functor call return false, return false. Otherwise, return true.
//
template < typename T >
bool ForEachGrenade( T &func )
{
int it = m_activeGrenadeList.Head();
while( it != m_activeGrenadeList.InvalidIndex() )
{
ActiveGrenade *ag = m_activeGrenadeList[ it ];
int current = it;
it = m_activeGrenadeList.Next( it );
// lazy validation
if (!ag->IsValid())
{
m_activeGrenadeList.Remove( current );
delete ag;
continue;
}
else
{
if (func( ag ) == false)
{
return false;
}
}
}
return true;
}
enum { MAX_DBG_MSG_SIZE = 1024 };
struct DebugMessage
{
char m_string[ MAX_DBG_MSG_SIZE ];
IntervalTimer m_age;
};
// debug message history -------------------------------------------------------------------------------
int GetDebugMessageCount( void ) const; ///< get number of debug messages in history
const DebugMessage *GetDebugMessage( int which = 0 ) const; ///< return the debug message emitted by the bot (0 = most recent)
void ClearDebugMessages( void );
void AddDebugMessage( const char *msg );
ActiveGrenadeList m_activeGrenadeList;///< the list of active grenades the bots are aware of
private:
enum { MAX_DBG_MSGS = 6 };
DebugMessage m_debugMessage[ MAX_DBG_MSGS ]; ///< debug message history
int m_debugMessageCount;
int m_currentDebugMessage;
IntervalTimer m_frameTimer; ///< for measuring each frame's duration
};
inline CBasePlayer *CBotManager::AllocateAndBindBotEntity( edict_t *ed )
{
CBasePlayer::s_PlayerEdict = ed;
return AllocateBotEntity();
}
inline int CBotManager::GetDebugMessageCount( void ) const
{
return m_debugMessageCount;
}
inline const CBotManager::DebugMessage *CBotManager::GetDebugMessage( int which ) const
{
if (which >= m_debugMessageCount)
return NULL;
int i = m_currentDebugMessage - which;
if (i < 0)
i += MAX_DBG_MSGS;
return &m_debugMessage[ i ];
}
// global singleton to create and control bots
extern CBotManager *TheBots;
#endif

View File

@@ -0,0 +1,852 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
#define DEFINE_DIFFICULTY_NAMES
#include "bot_profile.h"
#include "shared_util.h"
#include "bot.h"
#include "bot_util.h"
#include "cs_bot.h" // BOTPORT: Remove this CS dependency
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BotProfileManager *TheBotProfiles = NULL;
//--------------------------------------------------------------------------------------------------------
/**
* Generates a filename-decorated skin name
*/
static const char * GetDecoratedSkinName( const char *name, const char *filename )
{
const int BufLen = _MAX_PATH + 64;
static char buf[BufLen];
Q_snprintf( buf, sizeof( buf ), "%s/%s", filename, name );
return buf;
}
//--------------------------------------------------------------------------------------------------------------
const char* BotProfile::GetWeaponPreferenceAsString( int i ) const
{
if ( i < 0 || i >= m_weaponPreferenceCount )
return NULL;
return WeaponIDToAlias( m_weaponPreference[ i ] );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this profile has a primary weapon preference
*/
bool BotProfile::HasPrimaryPreference() const
{
for ( int i=0; i < m_weaponPreferenceCount; ++i )
{
if ( IsPrimaryWeapon( m_weaponPreference[i] ) )
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this profile has a pistol weapon preference
*/
bool BotProfile::HasPistolPreference() const
{
for ( int i=0; i < m_weaponPreferenceCount; ++i )
if ( IsSecondaryWeapon( m_weaponPreference[i] ) )
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this profile is valid for the specified team
*/
bool BotProfile::IsValidForTeam( int team ) const
{
return ( team == TEAM_UNASSIGNED || m_teams == TEAM_UNASSIGNED || team == m_teams );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this profile inherits from the specified template
*/
bool BotProfile::InheritsFrom( const char *name ) const
{
if ( WildcardMatch( name, GetName() ) )
return true;
for ( int i=0; i<m_templates.Count(); ++i )
{
const BotProfile *queryTemplate = m_templates[i];
if ( queryTemplate && queryTemplate->InheritsFrom( name ) )
{
return true;
}
}
return false;
}
void BotProfile::SetName( const char* name )
{
if ( name )
{
if ( m_name )
{
// Delete the current name if it exists
delete [] m_name;
}
m_name = CloneString( name );
}
}
const BotProfile* BotProfile::GetTemplate( int index ) const
{
if ( index > 0 && index < m_templates.Count() )
{
return m_templates[index];
}
return NULL;
}
void BotProfile::Clone( const BotProfile* source_profile )
{
if ( source_profile )
{
SetName( source_profile->GetName() );
for ( int device = 0; device < BotProfileInputDevice::COUNT; ++device )
{
m_aggression = source_profile->GetAggression();
m_skill = source_profile->GetSkill();
m_teamwork = source_profile->GetTeamwork();
m_aimFocusInitial = source_profile->GetAimFocusInitial();
m_aimFocusDecay = source_profile->GetAimFocusDecay();
m_aimFocusOffsetScale = source_profile->GetAimFocusOffsetScale();
m_aimFocusInterval = source_profile->GetAimFocusInterval();
m_weaponPreferenceCount = source_profile->GetWeaponPreferenceCount();
for( int i = 0; i < source_profile->GetWeaponPreferenceCount(); ++i )
{
m_weaponPreference[i] = source_profile->GetWeaponPreference(i);
}
m_cost = source_profile->GetCost();
m_skin = source_profile->GetSkin();
m_difficultyFlags = source_profile->GetDifficultyFlags();
m_voicePitch = source_profile->GetVoicePitch();
m_reactionTime = source_profile->GetReactionTime();
m_attackDelay = source_profile->GetAttackDelay();
m_lookAngleMaxAccelNormal = source_profile->GetLookAngleMaxAccelerationNormal();
m_lookAngleStiffnessNormal = source_profile->GetLookAngleStiffnessNormal();
m_lookAngleDampingNormal = source_profile->GetLookAngleDampingNormal();
m_lookAngleMaxAccelAttacking = source_profile->GetLookAngleMaxAccelerationAttacking();
m_lookAngleStiffnessAttacking = source_profile->GetLookAngleStiffnessAttacking();
m_lookAngleDampingAttacking = source_profile->GetLookAngleDampingAttacking();
}
m_teams = source_profile->GetTeams();
m_voiceBank = source_profile->GetVoiceBank();
m_prefersSilencer = source_profile->PrefersSilencer();
for ( int i = 0; i < source_profile->GetTemplatesCount(); ++i )
{
m_templates.AddToTail( source_profile->GetTemplate( i ) );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
BotProfileManager::BotProfileManager( void )
{
m_nextSkin = 0;
for (int i=0; i<NumCustomSkins; ++i)
{
m_skins[i] = NULL;
m_skinFilenames[i] = NULL;
m_skinModelnames[i] = NULL;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Load the bot profile database
*/
void BotProfileManager::Init( const char *filename, unsigned int *checksum )
{
FileHandle_t file = filesystem->Open( filename, "r" );
if (!file)
{
if ( true ) // UTIL_IsGame( "czero" ) )
{
CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename );
}
return;
}
int dataLength = filesystem->Size( filename );
char *dataPointer = new char[ dataLength ];
int dataReadLength = filesystem->Read( dataPointer, dataLength, 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.
dataPointer[ dataReadLength - 1 ] = 0;
}
const char *dataFile = dataPointer;
// compute simple checksum
if (checksum)
{
*checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength );
}
BotProfile defaultProfile;
//
// Parse the BotProfile.db into BotProfile instances
//
while( true )
{
dataFile = SharedParse( dataFile );
if (!dataFile)
break;
char *token = SharedGetToken();
bool isDefault = ( !stricmp( token, "Default" ));
bool isTemplate = ( !stricmp( token, "Template" ));
bool isCustomSkin = ( !stricmp( token, "Skin" ));
if ( isCustomSkin )
{
const int BufLen = 64;
char skinName[BufLen];
// get skin name
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
Q_snprintf( skinName, sizeof( skinName ), "%s", token );
// get attribute name
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
if (stricmp( "Model", token ))
{
CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
delete [] dataPointer;
return;
}
// eat '='
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
if (strcmp( "=", token ))
{
CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
delete [] dataPointer;
return;
}
// get attribute value
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
const char *decoratedName = GetDecoratedSkinName( skinName, filename );
bool skinExists = GetCustomSkinIndex( decoratedName ) > 0;
if ( m_nextSkin < NumCustomSkins && !skinExists )
{
// decorate the name
m_skins[ m_nextSkin ] = CloneString( decoratedName );
// construct the model filename
m_skinModelnames[ m_nextSkin ] = CloneString( token );
m_skinFilenames[ m_nextSkin ] = new char[ strlen( token )*2 + strlen("models/player//.mdl") + 1 ];
Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token );
++m_nextSkin;
}
// eat 'End'
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
if (strcmp( "End", token ))
{
CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
delete [] dataPointer;
return;
}
continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc.
}
// encountered a new profile
BotProfile *profile;
if (isDefault)
{
profile = &defaultProfile;
}
else
{
profile = new BotProfile;
// always inherit from Default
*profile = defaultProfile;
}
// do inheritance in order of appearance
if (!isTemplate && !isDefault)
{
const BotProfile *inherit = NULL;
// template names are separated by "+"
while(true)
{
char *c = strchr( token, '+' );
if (c)
*c = '\000';
// find the given template name
FOR_EACH_LL( m_templateList, it )
{
BotProfile *profile = m_templateList[ it ];
if ( !stricmp( profile->GetName(), token ))
{
inherit = profile;
break;
}
}
if (inherit == NULL)
{
CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token );
delete [] dataPointer;
return;
}
// inherit the data
profile->Inherit( inherit, &defaultProfile );
if (c == NULL)
break;
token = c+1;
}
}
// get name of this profile
if (!isDefault)
{
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename );
delete [] dataPointer;
return;
}
profile->m_name = CloneString( SharedGetToken() );
/**
* HACK HACK
* Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
* preference towards silencers based on his name.
*/
if ( profile->m_name[0] % 2 )
{
profile->m_prefersSilencer = true;
}
}
// read attributes for this profile
bool isFirstWeaponPref = true;
while( true )
{
// get next token
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
// check for End delimiter
if ( !stricmp( token, "End" ))
break;
// found attribute name - keep it
char attributeName[64];
strcpy( attributeName, token );
// eat '='
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
if (strcmp( "=", token ))
{
CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
delete [] dataPointer;
return;
}
// get attribute value
dataFile = SharedParse( dataFile );
if (!dataFile)
{
CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
delete [] dataPointer;
return;
}
token = SharedGetToken();
// store value in appropriate attribute
if ( !stricmp( "Aggression", attributeName ) )
{
profile->m_aggression = (float)atof( token ) / 100.0f;
}
else if ( !stricmp( "Skill", attributeName ) )
{
profile->m_skill = (float)atof( token ) / 100.0f;
}
else if ( !stricmp( "Skin", attributeName ) )
{
profile->m_skin = atoi( token );
if ( profile->m_skin == 0 )
{
// atoi() failed - try to look up a custom skin by name
profile->m_skin = GetCustomSkinIndex( token, filename );
}
}
else if ( !stricmp( "Teamwork", attributeName ))
{
profile->m_teamwork = (float)atof( token ) / 100.0f;
}
else if ( !V_stricmp( "AimFocusInitial", attributeName ) )
{
profile->m_aimFocusInitial = (float)atof( token );
}
else if ( !V_stricmp( "AimFocusDecay", attributeName ) )
{
profile->m_aimFocusDecay = (float)atof( token );
}
else if ( !V_stricmp( "AimFocusOffsetScale", attributeName ) )
{
profile->m_aimFocusOffsetScale = (float)atof( token );
}
else if ( !V_stricmp( "AimFocusInterval", attributeName ) )
{
profile->m_aimFocusInterval = (float)atof( token );
}
else if ( !stricmp( "Cost", attributeName ) )
{
profile->m_cost = atoi( token );
}
else if ( !stricmp( "VoicePitch", attributeName ) )
{
profile->m_voicePitch = atoi( token );
}
else if ( !stricmp( "VoiceBank", attributeName ) )
{
profile->m_voiceBank = FindVoiceBankIndex( token );
}
else if ( !stricmp( "WeaponPreference", attributeName ) )
{
ParseWeaponPreference( isFirstWeaponPref, profile->m_weaponPreferenceCount, profile->m_weaponPreference, token );
}
else if ( !stricmp( "ReactionTime", attributeName ) )
{
profile->m_reactionTime = (float)atof( token );
}
else if ( !stricmp( "AttackDelay", attributeName ))
{
profile->m_attackDelay = (float)atof( token );
}
else if ( !stricmp( "Difficulty", attributeName ))
{
// override inheritance
ParseDifficultySetting( profile->m_difficultyFlags, token );
}
else if ( !stricmp( "Team", attributeName ))
{
if ( !stricmp( token, "T" ) )
{
profile->m_teams = TEAM_TERRORIST;
}
else if ( !stricmp( token, "CT" ) )
{
profile->m_teams = TEAM_CT;
}
else
{
profile->m_teams = TEAM_UNASSIGNED;
}
}
else if ( !stricmp( "LookAngleMaxAccelNormal", attributeName ) )
{
profile->m_lookAngleMaxAccelNormal = atof( token );
}
else if ( !stricmp( "LookAngleStiffnessNormal", attributeName ) )
{
profile->m_lookAngleStiffnessNormal = atof( token );
}
else if ( !stricmp( "LookAngleDampingNormal", attributeName ) )
{
profile->m_lookAngleDampingNormal = atof( token );
}
else if ( !stricmp( "LookAngleMaxAccelAttacking", attributeName ) )
{
profile->m_lookAngleMaxAccelAttacking = atof( token );
}
else if ( !stricmp( "LookAngleStiffnessAttacking", attributeName ) )
{
profile->m_lookAngleStiffnessAttacking = atof( token );
}
else if ( !stricmp( "LookAngleDampingAttacking", attributeName ) )
{
profile->m_lookAngleDampingAttacking = atof( token );
}
else
{
CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName );
}
}
if (!isDefault)
{
if (isTemplate)
{
// add to template list
m_templateList.AddToTail( profile );
}
else
{
// add profile to the master list
m_profileList.AddToTail( profile );
}
}
}
delete [] dataPointer;
}
void BotProfileManager::ParseDifficultySetting( unsigned char &difficultyFlags, char* token)
{
// override inheritance
difficultyFlags = 0;
// parse bit flags
while(true)
{
char *c = strchr( token, '+' );
if (c)
{
*c = '\000';
}
for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i )
{
if ( !stricmp( BotDifficultyName[i], token ))
{
difficultyFlags |= (1 << i);
}
}
if (c == NULL)
{
break;
}
token = c+1;
}
}
void BotProfileManager::ParseWeaponPreference( bool &isFirstWeaponPref, int &weaponPreferenceCount, CSWeaponID* weaponPreference, char* token )
{
// weapon preferences override parent prefs
if (isFirstWeaponPref)
{
isFirstWeaponPref = false;
weaponPreferenceCount = 0;
}
if ( !stricmp( token, "none" ))
{
weaponPreferenceCount = 0;
}
else
{
if (weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
{
weaponPreference[ weaponPreferenceCount++ ] = AliasToWeaponID( token );
}
}
}
//--------------------------------------------------------------------------------------------------------------
BotProfileManager::~BotProfileManager( void )
{
Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Free all bot profiles
*/
void BotProfileManager::Reset( void )
{
m_profileList.PurgeAndDeleteElements();
m_templateList.PurgeAndDeleteElements();
int i;
for (i=0; i<NumCustomSkins; ++i)
{
if ( m_skins[i] )
{
delete[] m_skins[i];
m_skins[i] = NULL;
}
if ( m_skinFilenames[i] )
{
delete[] m_skinFilenames[i];
m_skinFilenames[i] = NULL;
}
if ( m_skinModelnames[i] )
{
delete[] m_skinModelnames[i];
m_skinModelnames[i] = NULL;
}
}
for ( i=0; i<m_voiceBanks.Count(); ++i )
{
delete[] m_voiceBanks[i];
}
m_voiceBanks.RemoveAll();
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns custom skin name at a particular index
*/
const char * BotProfileManager::GetCustomSkin( int index )
{
if ( index < FirstCustomSkin || index > LastCustomSkin )
{
return NULL;
}
return m_skins[ index - FirstCustomSkin ];
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns custom skin filename at a particular index
*/
const char * BotProfileManager::GetCustomSkinFname( int index )
{
if ( index < FirstCustomSkin || index > LastCustomSkin )
{
return NULL;
}
return m_skinFilenames[ index - FirstCustomSkin ];
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns custom skin modelname at a particular index
*/
const char * BotProfileManager::GetCustomSkinModelname( int index )
{
if ( index < FirstCustomSkin || index > LastCustomSkin )
{
return NULL;
}
return m_skinModelnames[ index - FirstCustomSkin ];
}
//--------------------------------------------------------------------------------------------------------
/**
* Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
*/
int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename )
{
const char * skinName = name;
if ( filename )
{
skinName = GetDecoratedSkinName( name, filename );
}
for (int i=0; i<NumCustomSkins; ++i)
{
if ( m_skins[i] )
{
if ( !stricmp( skinName, m_skins[i] ) )
{
return FirstCustomSkin + i;
}
}
}
return 0;
}
//--------------------------------------------------------------------------------------------------------
/**
* return index of the (custom) bot phrase db, inserting it if needed
*/
int BotProfileManager::FindVoiceBankIndex( const char *filename )
{
int index = 0;
for ( int i=0; i<m_voiceBanks.Count(); ++i )
{
if ( !stricmp( filename, m_voiceBanks[i] ) )
{
return index;
}
}
m_voiceBanks.AddToTail( CloneString( filename ) );
return index;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return random unused profile that matches the given difficulty level
*/
const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType, bool forceMatchHighestDifficulty ) const
{
// count up valid profiles
CUtlVector< const BotProfile * > profiles;
FOR_EACH_LL( m_profileList, it )
{
const BotProfile *profile = m_profileList[ it ];
// Match difficulty
if ( forceMatchHighestDifficulty )
{
if ( !profile->IsMaxDifficulty( difficulty ) )
{
continue;
}
}
else
{
if ( !profile->IsDifficulty( difficulty ) )
continue;
}
// Prevent duplicate names
if ( UTIL_IsNameTaken( profile->GetName() ) )
continue;
// Match team choice
if ( !profile->IsValidForTeam( team ) )
continue;
// Match desired weapon
if ( weaponType != WEAPONTYPE_UNKNOWN )
{
if ( !profile->GetWeaponPreferenceCount() )
continue;
if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) )
continue;
}
profiles.AddToTail( profile );
}
if ( !profiles.Count() )
return NULL;
// select one at random
int which = RandomInt( 0, profiles.Count()-1 );
return profiles[which];
}

View File

@@ -0,0 +1,424 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef _BOT_PROFILE_H_
#define _BOT_PROFILE_H_
#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
#include "bot_constants.h"
#include "bot_util.h"
#include "cs_weapon_parse.h"
enum
{
FirstCustomSkin = 100,
NumCustomSkins = 100,
LastCustomSkin = FirstCustomSkin + NumCustomSkins - 1,
};
//--------------------------------------------------------------------------------------------------------------
/**
* A BotProfile describes the "personality" of a given bot
*/
class BotProfile
{
public:
BotProfile( void )
{
m_name = NULL;
m_aggression = 0.0f;
m_skill = 0.0f;
m_teamwork = 0.0f;
m_weaponPreferenceCount = 0;
m_aimFocusInitial = 0.0f;
m_aimFocusDecay = 1.0f;
m_aimFocusOffsetScale = 0.0f;
m_aimFocusInterval = .5f;
m_cost = 0;
m_skin = 0;
m_difficultyFlags = 0;
m_voicePitch = 100;
m_reactionTime = 0.3f;
m_attackDelay = 0.0f;
m_lookAngleMaxAccelNormal = 0.0f;
m_lookAngleStiffnessNormal = 0.0f;
m_lookAngleDampingNormal = 0.0f;
m_lookAngleMaxAccelAttacking = 0.0f;
m_lookAngleStiffnessAttacking = 0.0f;
m_lookAngleDampingAttacking = 0.0f;
m_teams = TEAM_UNASSIGNED;
m_voiceBank = 0;
m_prefersSilencer = false;
}
~BotProfile( void )
{
if ( m_name )
delete [] m_name;
}
const char *GetName( void ) const { return m_name; } ///< return bot's name
float GetAggression() const;
float GetSkill() const;
float GetTeamwork() const;
float GetAimFocusInitial() const { return m_aimFocusInitial; }
float GetAimFocusDecay() const { return m_aimFocusDecay; }
float GetAimFocusOffsetScale() const { return m_aimFocusOffsetScale; }
float GetAimFocusInterval() const { return m_aimFocusInterval; }
CSWeaponID GetWeaponPreference(int i ) const;
const char *GetWeaponPreferenceAsString( int i ) const;
int GetWeaponPreferenceCount() const;
bool HasPrimaryPreference() const; ///< return true if this profile has a primary weapon preference
bool HasPistolPreference() const; ///< return true if this profile has a pistol weapon preference
int GetCost() const;
int GetSkin() const;
int GetMaxDifficulty() const; ///< return maximum difficulty flag value
bool IsDifficulty( BotDifficultyType diff ) const; ///< return true if this profile can be used for the given difficulty level
bool IsMaxDifficulty( BotDifficultyType diff ) const; ///< return true if this profile's highest difficulty matches the incoming difficulty level
int GetVoicePitch() const;
float GetReactionTime() const;
float GetAttackDelay() const;
int GetVoiceBank() const { return m_voiceBank; }
unsigned char GetDifficultyFlags() const;
int GetTeams( void ) const { return m_teams; }
float GetLookAngleMaxAccelerationNormal() const;
float GetLookAngleStiffnessNormal() const;
float GetLookAngleDampingNormal() const;
float GetLookAngleMaxAccelerationAttacking() const;
float GetLookAngleStiffnessAttacking() const;
float GetLookAngleDampingAttacking() const;
bool IsValidForTeam( int team ) const;
bool PrefersSilencer() const { return m_prefersSilencer; }
bool InheritsFrom( const char *name ) const;
void Clone( const BotProfile* source_profile );
void SetName( const char* name );
void SetVoicePitch( int pitch );
int GetTemplatesCount( void ) const { return m_templates.Count(); }
const BotProfile* GetTemplate( int index ) const;
private:
friend class BotProfileManager; ///< for loading profiles
void Inherit( const BotProfile *parent, const BotProfile *baseline ); ///< copy values from parent if they differ from baseline
char *m_name; // the bot's name
float m_aggression; // percentage: 0 = coward, 1 = berserker
float m_skill; // percentage: 0 = terrible, 1 = expert
float m_teamwork; // percentage: 0 = rogue, 1 = complete obeyance to team, lots of comm
float m_aimFocusInitial; // initial minimum aim error on first attack
float m_aimFocusDecay; // how quickly our focus error decays (scale/sec)
float m_aimFocusOffsetScale; // how much aim focus error we get based on maximum angle distance from our view angle
float m_aimFocusInterval; // how frequently we update our focus
enum { MAX_WEAPON_PREFS = 16 };
CSWeaponID m_weaponPreference[ MAX_WEAPON_PREFS ]; ///< which weapons this bot likes to use, in order of priority
int m_weaponPreferenceCount;
int m_cost; ///< reputation point cost for career mode
int m_skin; ///< "skin" index
unsigned char m_difficultyFlags; ///< bits set correspond to difficulty levels this is valid for
int m_voicePitch; ///< the pitch shift for bot chatter (100 = normal)
float m_reactionTime; ///< our reaction time in seconds
float m_attackDelay; ///< time in seconds from when we notice an enemy to when we open fire
int m_teams; ///< teams for which this profile is valid
bool m_prefersSilencer; ///< does the bot prefer to use silencers?
int m_voiceBank; ///< Index of the BotChatter.db voice bank this profile uses (0 is the default)
float m_lookAngleMaxAccelNormal; // Acceleration of look angle spring under normal conditions
float m_lookAngleStiffnessNormal; // Stiffness of look angle spring under normal conditions
float m_lookAngleDampingNormal; // Damping of look angle spring under normal conditions
float m_lookAngleMaxAccelAttacking; // Acceleration of look angle spring under attack conditions
float m_lookAngleStiffnessAttacking; // Stiffness of look angle spring under attack conditions
float m_lookAngleDampingAttacking; // Damping of look angle spring under attack conditions
CUtlVector< const BotProfile * > m_templates; ///< List of templates we inherit from
};
typedef CUtlLinkedList<BotProfile *> BotProfileList;
inline int BotProfile::GetMaxDifficulty() const
{
for ( int i = NUM_DIFFICULTY_LEVELS - 1; i >= BOT_EASY; --i )
{
if ( m_difficultyFlags & ( 1 << i ) )
{
return i;
}
}
return BOT_EASY;
}
inline bool BotProfile::IsDifficulty( BotDifficultyType diff ) const
{
return (m_difficultyFlags & (1 << diff)) ? true : false;
}
inline bool BotProfile::IsMaxDifficulty( BotDifficultyType diff ) const
{
return ( ( m_difficultyFlags >> diff ) == 1 );
}
inline float BotProfile::GetAggression() const
{
return m_aggression;
}
inline float BotProfile::GetSkill() const
{
return m_skill;
}
inline float BotProfile::GetTeamwork() const
{
return m_teamwork;
}
inline CSWeaponID BotProfile::GetWeaponPreference( int i ) const
{
return m_weaponPreference[ i ];
}
inline int BotProfile::GetWeaponPreferenceCount() const
{
return m_weaponPreferenceCount;
}
inline int BotProfile::GetCost() const
{
return m_cost;
}
inline int BotProfile::GetSkin() const
{
return m_skin;
}
inline int BotProfile::GetVoicePitch() const
{
return m_voicePitch;
}
inline float BotProfile::GetReactionTime() const
{
return m_reactionTime;
}
inline float BotProfile::GetAttackDelay() const
{
return m_attackDelay;
}
inline unsigned char BotProfile::GetDifficultyFlags() const
{
return m_difficultyFlags;
}
inline float BotProfile::GetLookAngleMaxAccelerationNormal() const
{
return m_lookAngleMaxAccelNormal;
}
inline float BotProfile::GetLookAngleStiffnessNormal( ) const
{
return m_lookAngleStiffnessNormal;
}
inline float BotProfile::GetLookAngleDampingNormal() const
{
return m_lookAngleDampingNormal;
}
inline float BotProfile::GetLookAngleMaxAccelerationAttacking() const
{
return m_lookAngleMaxAccelAttacking;
}
inline float BotProfile::GetLookAngleStiffnessAttacking() const
{
return m_lookAngleStiffnessAttacking;
}
inline float BotProfile::GetLookAngleDampingAttacking() const
{
return m_lookAngleDampingAttacking;
}
inline void BotProfile::SetVoicePitch( int pitch )
{
m_voicePitch = pitch;
}
/**
* Copy in data from parent if it differs from the baseline
*/
inline void BotProfile::Inherit( const BotProfile *parent, const BotProfile *baseline )
{
if ( parent->m_aggression != baseline->m_aggression )
m_aggression = parent->m_aggression;
if ( parent->m_skill != baseline->m_skill )
m_skill = parent->m_skill;
if ( parent->m_teamwork != baseline->m_teamwork )
m_teamwork = parent->m_teamwork;
if ( parent->m_aimFocusInitial != baseline->m_aimFocusInitial )
m_aimFocusInitial = parent->m_aimFocusInitial;
if ( parent->m_aimFocusDecay != baseline->m_aimFocusDecay )
m_aimFocusDecay = parent->m_aimFocusDecay;
if ( parent->m_aimFocusOffsetScale != baseline->m_aimFocusOffsetScale )
m_aimFocusOffsetScale = parent->m_aimFocusOffsetScale;
if ( parent->m_aimFocusInterval != baseline->m_aimFocusInterval )
m_aimFocusInterval = parent->m_aimFocusInterval;
if (parent->m_weaponPreferenceCount != baseline->m_weaponPreferenceCount)
{
m_weaponPreferenceCount = parent->m_weaponPreferenceCount;
for( int i=0; i<parent->m_weaponPreferenceCount; ++i )
m_weaponPreference[i] = parent->m_weaponPreference[i];
}
if (parent->m_cost != baseline->m_cost)
m_cost = parent->m_cost;
if (parent->m_skin != baseline->m_skin)
m_skin = parent->m_skin;
if (parent->m_difficultyFlags != baseline->m_difficultyFlags)
m_difficultyFlags = parent->m_difficultyFlags;
if (parent->m_voicePitch != baseline->m_voicePitch)
m_voicePitch = parent->m_voicePitch;
if (parent->m_reactionTime != baseline->m_reactionTime)
m_reactionTime = parent->m_reactionTime;
if (parent->m_attackDelay != baseline->m_attackDelay)
m_attackDelay = parent->m_attackDelay;
if (parent->m_teams != baseline->m_teams)
m_teams = parent->m_teams;
if (parent->m_voiceBank != baseline->m_voiceBank)
m_voiceBank = parent->m_voiceBank;
m_templates.AddToTail( parent );
}
//--------------------------------------------------------------------------------------------------------------
/**
* The BotProfileManager defines the interface to accessing BotProfiles
*/
class BotProfileManager
{
public:
BotProfileManager( void );
~BotProfileManager( void );
void Init( const char *filename, unsigned int *checksum = NULL );
void Reset( void );
/// given a name, return a profile
const BotProfile *GetProfile( const char *name, int team ) const
{
FOR_EACH_LL( m_profileList, it )
{
BotProfile *profile = m_profileList[ it ];
if ( !stricmp( name, profile->GetName() ) && profile->IsValidForTeam( team ) )
return profile;
}
return NULL;
}
/// given a template name and difficulty, return a profile
const BotProfile *GetProfileMatchingTemplate( const char *profileName, int team, BotDifficultyType difficulty, BotProfileDevice_t device, bool bAllowDupeNames = false ) const
{
FOR_EACH_LL( m_profileList, it )
{
BotProfile *profile = m_profileList[ it ];
if ( !profile->InheritsFrom( profileName ) )
continue;
if ( !profile->IsValidForTeam( team ) )
continue;
if ( !profile->IsDifficulty( difficulty ) )
continue;
if ( bAllowDupeNames == false && UTIL_IsNameTaken( profile->GetName() ) )
continue;
return profile;
}
return NULL;
}
const BotProfileList *GetProfileList( void ) const { return &m_profileList; } ///< return list of all profiles
const BotProfile *GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType, bool forceMatchHighestDifficulty = false ) const; ///< return random unused profile that matches the given difficulty level
const char * GetCustomSkin( int index ); ///< Returns custom skin name at a particular index
const char * GetCustomSkinModelname( int index ); ///< Returns custom skin modelname at a particular index
const char * GetCustomSkinFname( int index ); ///< Returns custom skin filename at a particular index
int GetCustomSkinIndex( const char *name, const char *filename = NULL ); ///< Looks up a custom skin index by name
typedef CUtlVector<char *> VoiceBankList;
const VoiceBankList *GetVoiceBanks( void ) const { return &m_voiceBanks; }
int FindVoiceBankIndex( const char *filename ); ///< return index of the (custom) bot phrase db, inserting it if needed
protected:
void ParseDifficultySetting( unsigned char &difficultyFlags, char* token);
void ParseWeaponPreference( bool &isFirstWeaponPref, int &weaponPreferenceCount, CSWeaponID* weaponPreference, char* token );
protected:
BotProfileList m_profileList; ///< the list of all bot profiles
BotProfileList m_templateList; ///< the list of all bot templates
VoiceBankList m_voiceBanks;
char *m_skins[ NumCustomSkins ]; ///< Custom skin names
char *m_skinModelnames[ NumCustomSkins ]; ///< Custom skin modelnames
char *m_skinFilenames[ NumCustomSkins ]; ///< Custom skin filenames
int m_nextSkin; ///< Next custom skin to allocate
};
/// the global singleton for accessing BotProfiles
extern BotProfileManager *TheBotProfiles;
#endif // _BOT_PROFILE_H_

View File

@@ -0,0 +1,682 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_shareddefs.h"
#include "engine/IEngineSound.h"
#include "keyvalues.h"
#include "bot.h"
#include "bot_util.h"
#include "bot_profile.h"
#include "cs_bot.h"
#include <ctype.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static int s_iBeamSprite = 0;
extern ConVar mp_randomspawn_dist;
extern ConVar mp_randomspawn_los;
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given name is already in use by another player
*/
bool UTIL_IsNameTaken( const char *name, bool ignoreHumans )
{
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
if (player->IsPlayer() && player->IsBot())
{
// bots can have prefixes so we need to check the name
// against the profile name instead.
CCSBot *bot = dynamic_cast<CCSBot *>(player);
if ( bot && bot->GetProfile()->GetName() && FStrEq(name, bot->GetProfile()->GetName()))
{
return true;
}
}
else
{
if (!ignoreHumans)
{
if (FStrEq( name, player->GetPlayerName() ))
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
int UTIL_ClientsInGame( void )
{
int count = 0;
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBaseEntity *player = UTIL_PlayerByIndex( i );
if (player == NULL)
continue;
count++;
}
return count;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the number of non-bots on the given team
*/
int UTIL_HumansOnTeam( int teamID, bool isAlive )
{
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->GetTeamNumber() != teamID)
continue;
if (isAlive && !player->IsAlive())
continue;
count++;
}
return count;
}
//--------------------------------------------------------------------------------------------------------------
int UTIL_BotsInGame( void )
{
int count = 0;
for (int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex( i ));
if ( player == NULL )
continue;
if ( !player->IsBot() )
continue;
count++;
}
return count;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Kick a bot from the given team. If no bot exists on the team, return false.
*/
bool UTIL_KickBotFromTeam( int kickTeam, bool bQueue )
{
int i;
// try to kick a dead bot first
for ( i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
if (!player->IsBot())
continue;
if ( player->GetPendingTeamNumber() == TEAM_SPECTATOR )
{
// bot has already been flagged for kicking
continue;
}
if ( !player->IsAlive() )
{
// Address issue with bots getting kicked during half-time (this was resulting in bots from the wrong team being kicked)
if ( player->CanKickFromTeam( kickTeam ) )
{
if ( bQueue )
{
// bots flagged as spectators will be kicked at the beginning of the next round restart
player->SetPendingTeamNum( TEAM_SPECTATOR );
}
else
{
// its a bot on the right team - kick it
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", engine->GetPlayerUserId( player->edict() ) ) );
}
return true;
}
}
}
// no dead bots, kick any bot on the given team
for ( i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
if (!player->IsBot())
continue;
if ( player->GetPendingTeamNumber() == TEAM_SPECTATOR )
{
// bot has already been flagged for kicking
continue;
}
// Address issue with bots getting kicked during half-time (this was resulting in bots from the wrong team being kicked)
if ( player->CanKickFromTeam( kickTeam ) )
{
if ( bQueue )
{
// bots flagged as spectators will be kicked at the beginning of the next round restart
player->SetPendingTeamNum( TEAM_SPECTATOR );
}
else
{
// its a bot on the right team - kick it
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", engine->GetPlayerUserId( player->edict() ) ) );
}
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if all of the members of the given team are bots
*/
bool UTIL_IsTeamAllBots( int team )
{
int botCount = 0;
for( int i=1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
// skip players on other teams
if (player->GetTeamNumber() != team)
continue;
// if not a bot, fail the test
if (!player->IsBot())
return false;
// is a bot on given team
++botCount;
}
// if team is empty, there are no bots
return (botCount) ? true : false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the closest active player to the given position.
* If 'distance' is non-NULL, the distance to the closest player is returned in it.
*/
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance )
{
CBasePlayer *closePlayer = NULL;
float closeDistSq = 999999999999.9f;
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (!IsEntityValid( player ))
continue;
if (!player->IsAlive())
continue;
Vector playerOrigin = GetCentroid( player );
float distSq = (playerOrigin - pos).LengthSqr();
if (distSq < closeDistSq)
{
closeDistSq = distSq;
closePlayer = static_cast<CBasePlayer *>( player );
}
}
if (distance)
*distance = (float)sqrt( closeDistSq );
return closePlayer;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the closest active player on the given team to the given position.
* If 'distance' is non-NULL, the distance to the closest player is returned in it.
*/
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance )
{
CBasePlayer *closePlayer = NULL;
float closeDistSq = 999999999999.9f;
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (!IsEntityValid( player ))
continue;
if (!player->IsAlive())
continue;
if (player->GetTeamNumber() != team)
continue;
Vector playerOrigin = GetCentroid( player );
float distSq = (playerOrigin - pos).LengthSqr();
if (distSq < closeDistSq)
{
closeDistSq = distSq;
closePlayer = static_cast<CBasePlayer *>( player );
}
}
if (distance)
*distance = (float)sqrt( closeDistSq );
return closePlayer;
}
//--------------------------------------------------------------------------------------------------------------
// Takes the bot pointer and constructs the net name using the current bot name prefix.
void UTIL_ConstructBotNetName( char *name, int nameLength, const BotProfile *profile )
{
if (profile == NULL)
{
name[0] = 0;
return;
}
// if there is no bot prefix just use the profile name.
if ((cv_bot_prefix.GetString() == NULL) || (strlen(cv_bot_prefix.GetString()) == 0))
{
Q_strncpy( name, profile->GetName(), nameLength );
return;
}
// find the highest difficulty
const char *diffStr = BotDifficultyName[0];
for ( int i=BOT_EXPERT; i>0; --i )
{
if ( profile->IsDifficulty( (BotDifficultyType)i ) )
{
diffStr = BotDifficultyName[i];
break;
}
}
const char *weaponStr = NULL;
if ( profile->GetWeaponPreferenceCount() )
{
weaponStr = profile->GetWeaponPreferenceAsString( 0 );
const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr );
char wpnName[128];
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
if ( hWpnInfo != GetInvalidWeaponInfoHandle() )
{
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
if ( pWeaponInfo )
{
CSWeaponType weaponType = pWeaponInfo->GetWeaponType();
weaponStr = WeaponClassAsString( weaponType );
}
}
}
if ( !weaponStr )
{
weaponStr = "";
}
char skillStr[16];
Q_snprintf( skillStr, sizeof( skillStr ), "%.0f", profile->GetSkill()*100 );
char temp[MAX_PLAYER_NAME_LENGTH*2];
char prefix[MAX_PLAYER_NAME_LENGTH*2];
Q_strncpy( temp, cv_bot_prefix.GetString(), sizeof( temp ) );
Q_StrSubst( temp, "<difficulty>", diffStr, prefix, sizeof( prefix ) );
Q_StrSubst( prefix, "<weaponclass>", weaponStr, temp, sizeof( temp ) );
Q_StrSubst( temp, "<skill>", skillStr, prefix, sizeof( prefix ) );
Q_snprintf( name, nameLength, "%s %s", prefix, profile->GetName() );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if anyone on the given team can see the given spot
*/
bool UTIL_IsVisibleToTeam( const Vector &spot, int team )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
if (!player->IsAlive())
continue;
if (player->GetTeamNumber() != team)
continue;
trace_t result;
UTIL_TraceLine( player->EyePosition(), spot, CONTENTS_SOLID, player, COLLISION_GROUP_NONE, &result );
if ( result.fraction == 1.0f )
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if anyone on the given team can see the given spot
*/
bool UTIL_IsRandomSpawnFarEnoughAwayFromTeam( const Vector &spot, int team )
{
if ( mp_randomspawn_dist.GetInt() <= 0 )
return true;
if ( mp_randomspawn_los.GetInt() == 0 )
return true;
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if (player == NULL)
continue;
if (!player->IsAlive())
continue;
if (player->GetTeamNumber() != team)
continue;
if ( mp_randomspawn_dist.GetInt() > 0 && (player->GetAbsOrigin()).DistTo( spot ) < mp_randomspawn_dist.GetInt() )
{
//NDebugOverlay::Line( player->EyePosition(), spot + Vector( 0, 0, 32 ), 255, 0, 0, true, 4 );
//NDebugOverlay::Line( spot, spot + Vector( 0, 0, 64 ), 255, 128, 0, true, 4 );
continue;
}
else
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------------------------------------------
void UTIL_DrawBeamFromEnt( int i, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
{
/* BOTPORT: What is the replacement for MESSAGE_BEGIN?
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd ); // vecEnd = origin???
WRITE_BYTE( TE_BEAMENTPOINT );
WRITE_SHORT( i );
WRITE_COORD( vecEnd.x );
WRITE_COORD( vecEnd.y );
WRITE_COORD( vecEnd.z );
WRITE_SHORT( s_iBeamSprite );
WRITE_BYTE( 0 ); // startframe
WRITE_BYTE( 0 ); // framerate
WRITE_BYTE( iLifetime ); // life
WRITE_BYTE( 10 ); // width
WRITE_BYTE( 0 ); // noise
WRITE_BYTE( bRed ); // r, g, b
WRITE_BYTE( bGreen ); // r, g, b
WRITE_BYTE( bBlue ); // r, g, b
WRITE_BYTE( 255 ); // brightness
WRITE_BYTE( 0 ); // speed
MESSAGE_END();
*/
}
//------------------------------------------------------------------------------------------------------------
void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
{
NDebugOverlay::Line( vecStart, vecEnd, bRed, bGreen, bBlue, true, 0.1f );
/*
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart );
WRITE_BYTE( TE_BEAMPOINTS );
WRITE_COORD( vecStart.x );
WRITE_COORD( vecStart.y );
WRITE_COORD( vecStart.z );
WRITE_COORD( vecEnd.x );
WRITE_COORD( vecEnd.y );
WRITE_COORD( vecEnd.z );
WRITE_SHORT( s_iBeamSprite );
WRITE_BYTE( 0 ); // startframe
WRITE_BYTE( 0 ); // framerate
WRITE_BYTE( iLifetime ); // life
WRITE_BYTE( 10 ); // width
WRITE_BYTE( 0 ); // noise
WRITE_BYTE( bRed ); // r, g, b
WRITE_BYTE( bGreen ); // r, g, b
WRITE_BYTE( bBlue ); // r, g, b
WRITE_BYTE( 255 ); // brightness
WRITE_BYTE( 0 ); // speed
MESSAGE_END();
*/
}
//------------------------------------------------------------------------------------------------------------
void CONSOLE_ECHO( char * pszMsg, ... )
{
va_list argptr;
static char szStr[1024];
va_start( argptr, pszMsg );
vsprintf( szStr, pszMsg, argptr );
va_end( argptr );
Msg( "%s", szStr );
}
//------------------------------------------------------------------------------------------------------------
void BotPrecache( void )
{
s_iBeamSprite = CBaseEntity::PrecacheModel( "sprites/smoke.vmt" );
}
//------------------------------------------------------------------------------------------------------------
#define COS_TABLE_SIZE 256
static float cosTable[ COS_TABLE_SIZE ];
void InitBotTrig( void )
{
for( int i=0; i<COS_TABLE_SIZE; ++i )
{
float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1));
cosTable[i] = (float)cos( angle );
}
}
float BotCOS( float angle )
{
angle = AngleNormalizePositive( angle );
int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
return cosTable[i];
}
float BotSIN( float angle )
{
angle = AngleNormalizePositive( angle - 90 );
int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
return cosTable[i];
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send a "hint" message to all players, dead or alive.
*/
void HintMessageToAllPlayers( const char *message )
{
hudtextparms_t textParms;
textParms.x = -1.0f;
textParms.y = -1.0f;
textParms.fadeinTime = 1.0f;
textParms.fadeoutTime = 5.0f;
textParms.holdTime = 5.0f;
textParms.fxTime = 0.0f;
textParms.r1 = 100;
textParms.g1 = 255;
textParms.b1 = 100;
textParms.r2 = 255;
textParms.g2 = 255;
textParms.b2 = 255;
textParms.effect = 0;
textParms.channel = 0;
UTIL_HudMessageAll( textParms, message );
}
//--------------------------------------------------------------------------------------------------------------------
/**
* Return true if moving from "start" to "finish" will cross a player's line of fire.
* The path from "start" to "finish" is assumed to be a straight line.
* "start" and "finish" are assumed to be points on the ground.
*/
bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore, int ignoreTeam )
{
for ( int p=1; p <= gpGlobals->maxClients; ++p )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( p ) );
if (!IsEntityValid( player ))
continue;
if (player == ignore)
continue;
if (!player->IsAlive())
continue;
if (ignoreTeam && player->GetTeamNumber() == ignoreTeam)
continue;
// compute player's unit aiming vector
Vector viewForward;
AngleVectors( player->GetFinalAimAngle(), &viewForward );
const float longRange = 5000.0f;
Vector playerOrigin = GetCentroid( player );
Vector playerTarget = playerOrigin + longRange * viewForward;
Vector result( 0, 0, 0 );
if (IsIntersecting2D( start, finish, playerOrigin, playerTarget, &result ))
{
// simple check to see if intersection lies in the Z range of the path
float loZ, hiZ;
if (start.z < finish.z)
{
loZ = start.z;
hiZ = finish.z;
}
else
{
loZ = finish.z;
hiZ = start.z;
}
if (result.z >= loZ && result.z <= hiZ + HumanHeight)
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Performs a simple case-insensitive string comparison, honoring trailing * wildcards
*/
bool WildcardMatch( const char *query, const char *test )
{
if ( !query || !test )
return false;
while ( *test && *query )
{
char nameChar = *test;
char queryChar = *query;
if ( tolower(nameChar) != tolower(queryChar) ) // case-insensitive
break;
++test;
++query;
}
if ( *query == 0 && *test == 0 )
return true;
// Support trailing *
if ( *query == '*' )
return true;
return false;
}

View File

@@ -0,0 +1,172 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef BOT_UTIL_H
#define BOT_UTIL_H
#include "convar.h"
#include "util.h"
//--------------------------------------------------------------------------------------------------------------
enum PriorityType
{
PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_UNINTERRUPTABLE
};
extern ConVar cv_bot_traceview;
extern ConVar cv_bot_stop;
extern ConVar cv_bot_show_nav;
extern ConVar cv_bot_walk;
extern ConVar cv_bot_difficulty;
extern ConVar cv_bot_debug;
extern ConVar cv_bot_debug_target;
extern ConVar cv_bot_quota;
extern ConVar cv_bot_quota_mode;
extern ConVar cv_bot_prefix;
extern ConVar cv_bot_allow_rogues;
extern ConVar cv_bot_allow_pistols;
extern ConVar cv_bot_allow_shotguns;
extern ConVar cv_bot_allow_sub_machine_guns;
extern ConVar cv_bot_allow_rifles;
extern ConVar cv_bot_allow_machine_guns;
extern ConVar cv_bot_allow_grenades;
extern ConVar cv_bot_allow_snipers;
extern ConVar cv_bot_allow_shield;
extern ConVar cv_bot_join_team;
extern ConVar cv_bot_join_after_player;
extern ConVar cv_bot_auto_vacate;
extern ConVar cv_bot_zombie;
extern ConVar cv_bot_defer_to_human_goals;
extern ConVar cv_bot_defer_to_human_items;
extern ConVar cv_bot_chatter;
extern ConVar cv_bot_profile_db;
extern ConVar cv_bot_dont_shoot;
extern ConVar cv_bot_eco_limit;
extern ConVar cv_bot_auto_follow;
extern ConVar cv_bot_flipout;
#if CS_CONTROLLABLE_BOTS_ENABLED
extern ConVar cv_bot_controllable;
#endif
#define RAD_TO_DEG( deg ) ((deg) * 180.0 / M_PI)
#define DEG_TO_RAD( rad ) ((rad) * M_PI / 180.0)
#define SIGN( num ) (((num) < 0) ? -1 : 1)
#define ABS( num ) (SIGN(num) * (num))
#define CREATE_FAKE_CLIENT ( *g_engfuncs.pfnCreateFakeClient )
#define GET_USERINFO ( *g_engfuncs.pfnGetInfoKeyBuffer )
#define SET_KEY_VALUE ( *g_engfuncs.pfnSetKeyValue )
#define SET_CLIENT_KEY_VALUE ( *g_engfuncs.pfnSetClientKeyValue )
class BotProfile;
extern void BotPrecache( void );
extern int UTIL_ClientsInGame( void );
extern bool UTIL_IsNameTaken( const char *name, bool ignoreHumans = false ); ///< return true if given name is already in use by another player
#define IS_ALIVE true
extern int UTIL_HumansOnTeam( int teamID, bool isAlive = false );
extern int UTIL_BotsInGame( void );
extern bool UTIL_IsTeamAllBots( int team );
extern void UTIL_DrawBeamFromEnt( int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue );
extern void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue );
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance = NULL );
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance = NULL );
extern bool UTIL_KickBotFromTeam( int kickTeam, bool bQueue = false ); ///< kick a bot from the given team. If no bot exists on the team, return false, if bQueue is true kick will occur at round restart
extern bool UTIL_IsVisibleToTeam( const Vector &spot, int team ); ///< return true if anyone on the given team can see the given spot
extern bool UTIL_IsRandomSpawnFarEnoughAwayFromTeam( const Vector &spot, int team ); ///< return true if this spot is far enough away from everyone on specified team defined via
/// return true if moving from "start" to "finish" will cross a player's line of fire.
extern bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore = NULL, int ignoreTeam = 0 );
extern void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *bot); ///< constructs a complete name including prefix
/**
* Echos text to the console, and prints it on the client's screen. This is NOT tied to the developer cvar.
* If you are adding debugging output in cstrike, use UTIL_DPrintf() (debug.h) instead.
*/
extern void CONSOLE_ECHO( char * pszMsg, ... );
extern void InitBotTrig( void );
extern float BotCOS( float angle );
extern float BotSIN( float angle );
extern void HintMessageToAllPlayers( const char *message );
bool WildcardMatch( const char *query, const char *test ); ///< Performs a simple case-insensitive string comparison, honoring trailing * wildcards
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if the given entity is valid
*/
inline bool IsEntityValid( CBaseEntity *entity )
{
if (entity == NULL)
return false;
if (FNullEnt( entity->edict() ))
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Given two line segments: startA to endA, and startB to endB, return true if they intesect
* and put the intersection point in "result".
* Note that this computes the intersection of the 2D (x,y) projection of the line segments.
*/
inline bool IsIntersecting2D( const Vector &startA, const Vector &endA,
const Vector &startB, const Vector &endB,
Vector *result = NULL )
{
float denom = (endA.x - startA.x) * (endB.y - startB.y) - (endA.y - startA.y) * (endB.x - startB.x);
if (denom == 0.0f)
{
// parallel
return false;
}
float numS = (startA.y - startB.y) * (endB.x - startB.x) - (startA.x - startB.x) * (endB.y - startB.y);
if (numS == 0.0f)
{
// coincident
return true;
}
float numT = (startA.y - startB.y) * (endA.x - startA.x) - (startA.x - startB.x) * (endA.y - startA.y);
float s = numS / denom;
if (s < 0.0f || s > 1.0f)
{
// intersection is not within line segment of startA to endA
return false;
}
float t = numT / denom;
if (t < 0.0f || t > 1.0f)
{
// intersection is not within line segment of startB to endB
return false;
}
// compute intesection point
if (result)
*result = startA + s * (endA - startA);
return true;
}
#endif

View File

@@ -0,0 +1,57 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// improv_locomotor.h
// Interface for moving Improvs along computed paths
// Author: Michael Booth, July 2004
#ifndef _IMPROV_LOCOMOTOR_H_
#define _IMPROV_LOCOMOTOR_H_
// TODO: Remove duplicate methods from CImprov, and update CImprov to use this class
/**
* A locomotor owns the movement of an Improv
*/
class CImprovLocomotor
{
public:
virtual const Vector &GetCentroid( void ) const = 0;
virtual const Vector &GetFeet( void ) const = 0; ///< return position of "feet" - point below centroid of improv at feet level
virtual const Vector &GetEyes( void ) const = 0;
virtual float GetMoveAngle( void ) const = 0; ///< return direction of movement
virtual CNavArea *GetLastKnownArea( void ) const = 0;
virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ) = 0; ///< find "simple" ground height, treating current nav area as part of the floor
virtual void Crouch( void ) = 0;
virtual void StandUp( void ) = 0; ///< "un-crouch"
virtual bool IsCrouching( void ) const = 0;
virtual void Jump( void ) = 0; ///< initiate a jump
virtual bool IsJumping( void ) const = 0;
virtual void Run( void ) = 0; ///< set movement speed to running
virtual void Walk( void ) = 0; ///< set movement speed to walking
virtual bool IsRunning( void ) const = 0;
virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ) = 0; ///< 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 ) = 0; ///< traverse given ladder
virtual bool IsUsingLadder( void ) const = 0;
enum MoveToFailureType
{
FAIL_INVALID_PATH,
FAIL_STUCK,
FAIL_FELL_OFF,
};
virtual void TrackPath( const Vector &pathGoal, float deltaT ) = 0; ///< 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
};
#endif // _IMPROV_LOCOMOTOR_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_path.h
// Navigation Path encapsulation
// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003
#ifndef _NAV_PATH_H_
#define _NAV_PATH_H_
#include "cs_nav_area.h"
#include "bot_util.h"
class CImprovLocomotor;
//--------------------------------------------------------------------------------------------------------
/**
* The CNavPath class encapsulates a path through space
*/
class CNavPath
{
public:
CNavPath( 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( CNavPath *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
CNavPath *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 // _NAV_PATH_H_

View File

@@ -0,0 +1,226 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: dll-agnostic routines (no dll dependencies here)
//
// $NoKeywords: $
//=============================================================================//
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#include "cbase.h"
#include <ctype.h>
#include "shared_util.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static char s_shared_token[ 1500 ];
static char s_shared_quote = '\"';
//--------------------------------------------------------------------------------------------------------------
char * SharedVarArgs(PRINTF_FORMAT_STRING const char *format, ...)
{
va_list argptr;
const int BufLen = 1024;
const int NumBuffers = 4;
static char string[NumBuffers][BufLen];
static int curstring = 0;
curstring = ( curstring + 1 ) % NumBuffers;
va_start (argptr, format);
_vsnprintf( string[curstring], BufLen, format, argptr );
va_end (argptr);
return string[curstring];
}
//--------------------------------------------------------------------------------------------------------------
char * BufPrintf(char *buf, int& len, PRINTF_FORMAT_STRING const char *fmt, ...)
{
if (len <= 0)
return NULL;
va_list argptr;
va_start(argptr, fmt);
_vsnprintf(buf, len, fmt, argptr);
va_end(argptr);
// Make sure the buffer is null-terminated.
buf[ len - 1 ] = 0;
len -= strlen(buf);
return buf + strlen(buf);
}
#ifdef _WIN32
//--------------------------------------------------------------------------------------------------------------
wchar_t * BufWPrintf(wchar_t *buf, int& len, PRINTF_FORMAT_STRING const wchar_t *fmt, ...)
{
if (len <= 0)
return NULL;
va_list argptr;
va_start(argptr, fmt);
_vsnwprintf(buf, len, fmt, argptr);
va_end(argptr);
// Make sure the buffer is null-terminated.
buf[ len - 1 ] = 0;
len -= wcslen(buf);
return buf + wcslen(buf);
}
#endif
//--------------------------------------------------------------------------------------------------------------
#ifdef _WIN32
const wchar_t * NumAsWString( int val )
{
const int BufLen = 16;
static wchar_t buf[BufLen];
int len = BufLen;
BufWPrintf( buf, len, L"%d", val );
return buf;
}
#endif
// dgoodenough - PS3 needs this guy as well.
// PS3_BUILDFIX
#ifdef _PS3
const wchar_t * NumAsWString( int val )
{
const int BufLen = 16;
static wchar_t buf[BufLen];
char szBuf[BufLen];
Q_snprintf(szBuf, BufLen, "%d", val );
szBuf[BufLen - 1] = 0;
for ( int i = 0; i < BufLen; ++i )
{
buf[i] = szBuf[i];
}
return buf;
}
#endif
//--------------------------------------------------------------------------------------------------------------
const char * NumAsString( int val )
{
const int BufLen = 16;
static char buf[BufLen];
int len = BufLen;
BufPrintf( buf, len, "%d", val );
return buf;
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns the token parsed by SharedParse()
*/
char *SharedGetToken( void )
{
return s_shared_token;
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns the token parsed by SharedParse()
*/
void SharedSetQuoteChar( char c )
{
s_shared_quote = c;
}
//--------------------------------------------------------------------------------------------------------
/**
* Parse a token out of a string
*/
const char *SharedParse( const char *data )
{
int c;
int len;
len = 0;
s_shared_token[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c=='/' && data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
// handle quoted strings specially
if (c == s_shared_quote)
{
data++;
while (1)
{
c = *data++;
if (c==s_shared_quote || !c)
{
s_shared_token[len] = 0;
return data;
}
s_shared_token[len] = c;
len++;
}
}
// parse single characters
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' )
{
s_shared_token[len] = c;
len++;
s_shared_token[len] = 0;
return data+1;
}
// parse a regular word
do
{
s_shared_token[len] = c;
data++;
len++;
c = *data;
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' )
break;
} while (c>32);
s_shared_token[len] = 0;
return data;
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns true if additional data is waiting to be processed on this line
*/
bool SharedTokenWaiting( const char *buffer )
{
const char *p;
p = buffer;
while ( *p && *p!='\n')
{
if ( !V_isspace( *p ) || V_isalnum( *p ) )
return true;
p++;
}
return false;
}

View File

@@ -0,0 +1,106 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: dll-agnostic routines (no dll dependencies here)
//
// $NoKeywords: $
//=============================================================================//
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#ifndef SHARED_UTIL_H
#define SHARED_UTIL_H
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------
/**
* Returns the token parsed by SharedParse()
*/
char *SharedGetToken( void );
//--------------------------------------------------------------------------------------------------------
/**
* Sets the character used to delimit quoted strings. Default is '\"'. Be sure to set it back when done.
*/
void SharedSetQuoteChar( char c );
//--------------------------------------------------------------------------------------------------------
/**
* Parse a token out of a string
*/
const char *SharedParse( const char *data );
//--------------------------------------------------------------------------------------------------------
/**
* Returns true if additional data is waiting to be processed on this line
*/
bool SharedTokenWaiting( const char *buffer );
//--------------------------------------------------------------------------------------------------------
/**
* Simple utility function to allocate memory and duplicate a string
*/
/*
inline char *CloneString( const char *str )
{
char *cloneStr = new char [ strlen(str)+1 ];
strcpy( cloneStr, str );
return cloneStr;
}*/
//--------------------------------------------------------------------------------------------------------
/**
* Simple utility function to allocate memory and duplicate a wide string
*/
#ifdef _WIN32
extern inline wchar_t *CloneWString( const wchar_t *str );
// {
// wchar_t *cloneStr = new wchar_t [ wcslen(str)+1 ];
// wcscpy( cloneStr, str );
// return cloneStr;
// }
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* snprintf-alike that allows multiple prints into a buffer
*/
char * BufPrintf(char *buf, int& len, PRINTF_FORMAT_STRING const char *fmt, ...);
//--------------------------------------------------------------------------------------------------------------
/**
* wide char version of BufPrintf
*/
#ifdef _WIN32
wchar_t * BufWPrintf(wchar_t *buf, int& len, PRINTF_FORMAT_STRING const wchar_t *fmt, ...);
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* convenience function that prints an int into a static wchar_t*
*/
#ifdef _WIN32
const wchar_t * NumAsWString( int val );
#endif
// dgoodenough - PS3 needs this guy as well.
// PS3_BUILDFIX
#ifdef _PS3
const wchar_t * NumAsWString( int val );
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* convenience function that prints an int into a static char*
*/
const char * NumAsString( int val );
//--------------------------------------------------------------------------------------------------------------
/**
* convenience function that composes a string into a static char*
*/
char * SharedVarArgs(PRINTF_FORMAT_STRING const char *format, ...);
#include "tier0/memdbgoff.h"
#endif // SHARED_UTIL_H