source-engine/game/shared/cstrike/bot/bot_util.cpp

605 lines
15 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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;
}