initial
This commit is contained in:
116
game/shared/cstrike15/bot/bot.cpp
Normal file
116
game/shared/cstrike15/bot/bot.cpp
Normal 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();
|
||||
}
|
||||
|
||||
1174
game/shared/cstrike15/bot/bot.h
Normal file
1174
game/shared/cstrike15/bot/bot.h
Normal file
File diff suppressed because it is too large
Load Diff
58
game/shared/cstrike15/bot/bot_constants.h
Normal file
58
game/shared/cstrike15/bot/bot_constants.h
Normal 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
|
||||
496
game/shared/cstrike15/bot/bot_hide.cpp
Normal file
496
game/shared/cstrike15/bot/bot_hide.cpp
Normal 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 ];
|
||||
}
|
||||
|
||||
352
game/shared/cstrike15/bot/bot_manager.cpp
Normal file
352
game/shared/cstrike15/bot/bot_manager.cpp
Normal 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();
|
||||
}
|
||||
193
game/shared/cstrike15/bot/bot_manager.h
Normal file
193
game/shared/cstrike15/bot/bot_manager.h
Normal 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
|
||||
852
game/shared/cstrike15/bot/bot_profile.cpp
Normal file
852
game/shared/cstrike15/bot/bot_profile.cpp
Normal 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];
|
||||
}
|
||||
|
||||
424
game/shared/cstrike15/bot/bot_profile.h
Normal file
424
game/shared/cstrike15/bot/bot_profile.h
Normal 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_
|
||||
682
game/shared/cstrike15/bot/bot_util.cpp
Normal file
682
game/shared/cstrike15/bot/bot_util.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
172
game/shared/cstrike15/bot/bot_util.h
Normal file
172
game/shared/cstrike15/bot/bot_util.h
Normal 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
|
||||
57
game/shared/cstrike15/bot/improv_locomotor.h
Normal file
57
game/shared/cstrike15/bot/improv_locomotor.h
Normal 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_
|
||||
1208
game/shared/cstrike15/bot/nav_path.cpp
Normal file
1208
game/shared/cstrike15/bot/nav_path.cpp
Normal file
File diff suppressed because it is too large
Load Diff
246
game/shared/cstrike15/bot/nav_path.h
Normal file
246
game/shared/cstrike15/bot/nav_path.h
Normal 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_
|
||||
|
||||
226
game/shared/cstrike15/bot/shared_util.cpp
Normal file
226
game/shared/cstrike15/bot/shared_util.cpp
Normal 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;
|
||||
}
|
||||
106
game/shared/cstrike15/bot/shared_util.h
Normal file
106
game/shared/cstrike15/bot/shared_util.h
Normal 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
|
||||
Reference in New Issue
Block a user