You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
15 KiB
604 lines
15 KiB
//========= Copyright 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; |
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* 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 ) |
|
{ |
|
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->IsAlive() && player->GetTeamNumber() == kickTeam) |
|
{ |
|
// its a bot on the right team - kick it |
|
engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) ); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
// no dead bots, kick any bot on the given team |
|
for ( i = 1; i <= gpGlobals->maxClients; ++i ) |
|
{ |
|
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); |
|
|
|
if (player == NULL) |
|
continue; |
|
|
|
if (!player->IsBot()) |
|
continue; |
|
|
|
if (player->GetTeamNumber() == kickTeam) |
|
{ |
|
// its a bot on the right team - kick it |
|
engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) ); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* 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->m_WeaponType; |
|
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; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------------------------ |
|
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( const 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.spr" ); |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------------ |
|
#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->EyeAngles() + player->GetPunchAngle(), &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; |
|
} |
|
|
|
|
|
|
|
|