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.
682 lines
17 KiB
682 lines
17 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
// Author: Michael S. Booth ([email protected]), 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;
|
|
}
|
|
|
|
|
|
|