/*
===========================================================================
Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
RTCW MP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
RTCW MP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with RTCW MP Source Code. If not, see .
In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
// g_bot.c
#include "g_local.h"
#include "../botai/botai.h"
static int g_numBots;
static char g_botInfos[MAX_BOTS][MAX_INFO_STRING];
int g_numArenas;
static char g_arenaInfos[MAX_ARENAS][MAX_INFO_STRING];
#define BOT_BEGIN_DELAY_BASE 2000
#define BOT_BEGIN_DELAY_INCREMENT 1500
#define BOT_SPAWN_QUEUE_DEPTH 16
typedef struct {
int clientNum;
int spawnTime;
} botSpawnQueue_t;
static int botBeginDelay;
static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
vmCvar_t bot_minplayers;
extern gentity_t *podium1;
extern gentity_t *podium2;
extern gentity_t *podium3;
// TTimo gcc: defined but not used
#if 0
/*
===============
G_LoadArenas
===============
*/
static void G_LoadArenas( void ) {
#ifdef QUAKESTUFF
int len;
char *filename;
vmCvar_t arenasFile;
fileHandle_t f;
int n;
char buf[MAX_ARENAS_TEXT];
trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT | CVAR_ROM );
if ( *arenasFile.string ) {
filename = arenasFile.string;
} else {
filename = "scripts/arenas.txt";
}
len = trap_FS_FOpenFile( filename, &f, FS_READ );
if ( !f ) {
trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
return;
}
if ( len >= MAX_ARENAS_TEXT ) {
trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
trap_FS_FCloseFile( f );
return;
}
trap_FS_Read( buf, len, f );
buf[len] = 0;
trap_FS_FCloseFile( f );
g_numArenas = COM_ParseInfos( buf, MAX_ARENAS, g_arenaInfos );
trap_Printf( va( "%i arenas parsed\n", g_numArenas ) );
for ( n = 0; n < g_numArenas; n++ ) {
Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
}
#endif
}
#endif
/*
===============
G_GetArenaInfoByNumber
===============
*/
const char *G_GetArenaInfoByMap( const char *map ) {
int n;
for ( n = 0; n < g_numArenas; n++ ) {
if ( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) {
return g_arenaInfos[n];
}
}
return NULL;
}
/*
=================
PlayerIntroSound
=================
*/
static void PlayerIntroSound( const char *modelAndSkin ) {
char model[MAX_QPATH];
char *skin;
Q_strncpyz( model, modelAndSkin, sizeof( model ) );
skin = Q_strrchr( model, '/' );
if ( skin ) {
*skin++ = '\0';
} else {
skin = model;
}
if ( Q_stricmp( skin, "default" ) == 0 ) {
skin = model;
}
trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) );
}
/*
===============
G_AddRandomBot
===============
*/
void G_AddRandomBot( int team ) {
int i, n, num, skill;
char *value, netname[36], *teamstr;
gclient_t *cl;
num = 0;
for ( n = 0; n < g_numBots ; n++ ) {
value = Info_ValueForKey( g_botInfos[n], "name" );
//
for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
cl = level.clients + i;
if ( cl->pers.connected != CON_CONNECTED ) {
continue;
}
if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) {
continue;
}
if ( team >= 0 && cl->sess.sessionTeam != team ) {
continue;
}
if ( !Q_stricmp( value, cl->pers.netname ) ) {
break;
}
}
if ( i >= g_maxclients.integer ) {
num++;
}
}
num = random() * num;
for ( n = 0; n < g_numBots ; n++ ) {
value = Info_ValueForKey( g_botInfos[n], "name" );
//
for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
cl = level.clients + i;
if ( cl->pers.connected != CON_CONNECTED ) {
continue;
}
if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) {
continue;
}
if ( team >= 0 && cl->sess.sessionTeam != team ) {
continue;
}
if ( !Q_stricmp( value, cl->pers.netname ) ) {
break;
}
}
if ( i >= g_maxclients.integer ) {
num--;
if ( num <= 0 ) {
skill = trap_Cvar_VariableIntegerValue( "g_spSkill" );
if ( team == TEAM_RED ) {
teamstr = "red";
} else if ( team == TEAM_BLUE ) {
teamstr = "blue";
} else { teamstr = "";}
strncpy( netname, value, sizeof( netname ) - 1 );
netname[sizeof( netname ) - 1] = '\0';
Q_CleanStr( netname );
trap_SendConsoleCommand( EXEC_INSERT, va( "addbot %s %i %s %i\n", netname, skill, teamstr, 0 ) );
return;
}
}
}
}
/*
===============
G_RemoveRandomBot
===============
*/
int G_RemoveRandomBot( int team ) {
int i;
char netname[36];
gclient_t *cl;
for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
cl = level.clients + i;
if ( cl->pers.connected != CON_CONNECTED ) {
continue;
}
if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) {
continue;
}
if ( team >= 0 && cl->sess.sessionTeam != team ) {
continue;
}
strcpy( netname, cl->pers.netname );
Q_CleanStr( netname );
trap_SendConsoleCommand( EXEC_INSERT, va( "kick %s\n", netname ) );
return qtrue;
}
return qfalse;
}
/*
===============
G_CountHumanPlayers
===============
*/
int G_CountHumanPlayers( int team ) {
int i, num;
gclient_t *cl;
num = 0;
for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
cl = level.clients + i;
if ( cl->pers.connected != CON_CONNECTED ) {
continue;
}
if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
continue;
}
if ( team >= 0 && cl->sess.sessionTeam != team ) {
continue;
}
num++;
}
return num;
}
/*
===============
G_CountBotPlayers
===============
*/
int G_CountBotPlayers( int team ) {
int i, n, num;
gclient_t *cl;
num = 0;
for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
cl = level.clients + i;
if ( cl->pers.connected != CON_CONNECTED ) {
continue;
}
if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) {
continue;
}
if ( team >= 0 && cl->sess.sessionTeam != team ) {
continue;
}
num++;
}
for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
if ( !botSpawnQueue[n].spawnTime ) {
continue;
}
if ( botSpawnQueue[n].spawnTime > level.time ) {
continue;
}
num++;
}
return num;
}
/*
===============
G_CheckMinimumPlayers
===============
*/
void G_CheckMinimumPlayers( void ) {
int minplayers;
int humanplayers, botplayers;
static int checkminimumplayers_time;
//only check once each 10 seconds
if ( checkminimumplayers_time > level.time - 10000 ) {
return;
}
checkminimumplayers_time = level.time;
trap_Cvar_Update( &bot_minplayers );
minplayers = bot_minplayers.integer;
if ( minplayers <= 0 ) {
return;
}
if ( g_gametype.integer >= GT_TEAM ) {
if ( minplayers >= g_maxclients.integer / 2 ) {
minplayers = ( g_maxclients.integer / 2 ) - 1;
}
humanplayers = G_CountHumanPlayers( TEAM_RED );
botplayers = G_CountBotPlayers( TEAM_RED );
//
if ( humanplayers + botplayers < minplayers ) {
G_AddRandomBot( TEAM_RED );
} else if ( humanplayers + botplayers > minplayers && botplayers ) {
G_RemoveRandomBot( TEAM_RED );
}
//
humanplayers = G_CountHumanPlayers( TEAM_BLUE );
botplayers = G_CountBotPlayers( TEAM_BLUE );
//
if ( humanplayers + botplayers < minplayers ) {
G_AddRandomBot( TEAM_BLUE );
} else if ( humanplayers + botplayers > minplayers && botplayers ) {
G_RemoveRandomBot( TEAM_BLUE );
}
} else if ( g_gametype.integer == GT_TOURNAMENT ) {
if ( minplayers >= g_maxclients.integer ) {
minplayers = g_maxclients.integer - 1;
}
humanplayers = G_CountHumanPlayers( -1 );
botplayers = G_CountBotPlayers( -1 );
//
if ( humanplayers + botplayers < minplayers ) {
G_AddRandomBot( TEAM_FREE );
} else if ( humanplayers + botplayers > minplayers && botplayers ) {
// try to remove spectators first
if ( !G_RemoveRandomBot( TEAM_SPECTATOR ) ) {
// just remove the bot that is playing
G_RemoveRandomBot( -1 );
}
}
} else if ( g_gametype.integer == GT_FFA ) {
if ( minplayers >= g_maxclients.integer ) {
minplayers = g_maxclients.integer - 1;
}
humanplayers = G_CountHumanPlayers( TEAM_FREE );
botplayers = G_CountBotPlayers( TEAM_FREE );
//
if ( humanplayers + botplayers < minplayers ) {
G_AddRandomBot( TEAM_FREE );
} else if ( humanplayers + botplayers > minplayers && botplayers ) {
G_RemoveRandomBot( TEAM_FREE );
}
}
}
/*
===============
G_CheckBotSpawn
===============
*/
void G_CheckBotSpawn( void ) {
int n;
char userinfo[MAX_INFO_STRING];
G_CheckMinimumPlayers();
for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
if ( !botSpawnQueue[n].spawnTime ) {
continue;
}
if ( botSpawnQueue[n].spawnTime > level.time ) {
continue;
}
ClientBegin( botSpawnQueue[n].clientNum );
botSpawnQueue[n].spawnTime = 0;
if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof( userinfo ) );
PlayerIntroSound( Info_ValueForKey( userinfo, "model" ) );
}
}
}
/*
===============
AddBotToSpawnQueue
===============
*/
static void AddBotToSpawnQueue( int clientNum, int delay ) {
int n;
for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
if ( !botSpawnQueue[n].spawnTime ) {
botSpawnQueue[n].spawnTime = level.time + delay;
botSpawnQueue[n].clientNum = clientNum;
return;
}
}
G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" );
ClientBegin( clientNum );
}
/*
===============
G_QueueBotBegin
===============
*/
void G_QueueBotBegin( int clientNum ) {
AddBotToSpawnQueue( clientNum, botBeginDelay );
botBeginDelay += BOT_BEGIN_DELAY_INCREMENT;
}
/*
===============
G_BotConnect
===============
*/
qboolean G_BotConnect( int clientNum, qboolean restart ) {
bot_settings_t settings;
char userinfo[MAX_INFO_STRING];
trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof( settings.characterfile ) );
settings.skill = atoi( Info_ValueForKey( userinfo, "skill" ) );
Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof( settings.team ) );
if ( !BotAISetupClient( clientNum, &settings ) ) {
trap_DropClient( clientNum, "BotAISetupClient failed" );
return qfalse;
}
if ( restart && g_gametype.integer == GT_SINGLE_PLAYER ) {
g_entities[clientNum].botDelayBegin = qtrue;
} else {
g_entities[clientNum].botDelayBegin = qfalse;
}
return qtrue;
}
/*
===============
G_AddBot
===============
*/
static void G_AddBot( const char *name, int skill, const char *team, int delay ) {
int clientNum;
char *botinfo;
gentity_t *bot;
char *key;
char *s;
char *botname;
char *model;
char userinfo[MAX_INFO_STRING];
// get the botinfo from bots.txt
botinfo = G_GetBotInfoByName( name );
if ( !botinfo ) {
G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name );
return;
}
// create the bot's userinfo
userinfo[0] = '\0';
botname = Info_ValueForKey( botinfo, "funname" );
if ( !botname[0] ) {
botname = Info_ValueForKey( botinfo, "name" );
}
Info_SetValueForKey( userinfo, "name", botname );
Info_SetValueForKey( userinfo, "rate", "25000" );
Info_SetValueForKey( userinfo, "snaps", "20" );
Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) );
if ( skill == 1 ) {
Info_SetValueForKey( userinfo, "handicap", "50" );
} else if ( skill == 2 ) {
Info_SetValueForKey( userinfo, "handicap", "70" );
} else if ( skill == 3 ) {
Info_SetValueForKey( userinfo, "handicap", "90" );
}
key = "model";
model = Info_ValueForKey( botinfo, key );
if ( !*model ) {
model = "visor/default";
}
Info_SetValueForKey( userinfo, key, model );
key = "gender";
s = Info_ValueForKey( botinfo, key );
if ( !*s ) {
s = "male";
}
Info_SetValueForKey( userinfo, "sex", s );
key = "color";
s = Info_ValueForKey( botinfo, key );
if ( !*s ) {
s = "4";
}
Info_SetValueForKey( userinfo, key, s );
s = Info_ValueForKey( botinfo, "aifile" );
if ( !*s ) {
trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" );
return;
}
// have the server allocate a client slot
clientNum = trap_BotAllocateClient();
if ( clientNum == -1 ) {
G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" );
G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" );
return;
}
// initialize the bot settings
if ( !team || !*team ) {
if ( g_gametype.integer == GT_TEAM || g_gametype.integer == GT_CTF ) {
if ( PickTeam( clientNum ) == TEAM_RED ) {
team = "red";
} else {
team = "blue";
}
} else {
team = "red";
}
}
Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) );
Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) );
Info_SetValueForKey( userinfo, "team", team );
bot = &g_entities[ clientNum ];
bot->r.svFlags |= SVF_BOT;
bot->inuse = qtrue;
// register the userinfo
trap_SetUserinfo( clientNum, userinfo );
// have it connect to the game as a normal client
if ( ClientConnect( clientNum, qtrue, qtrue ) ) {
return;
}
if ( delay == 0 ) {
ClientBegin( clientNum );
return;
}
AddBotToSpawnQueue( clientNum, delay );
}
/*
===============
Svcmd_AddBot_f
===============
*/
void Svcmd_AddBot_f( void ) {
int skill;
int delay;
char name[MAX_TOKEN_CHARS];
char string[MAX_TOKEN_CHARS];
char team[MAX_TOKEN_CHARS];
// are bots enabled?
if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
return;
}
// name
trap_Argv( 1, name, sizeof( name ) );
if ( !name[0] ) {
trap_Printf( "Usage: Addbot [skill 1-4] [team] [msec delay]\n" );
return;
}
// skill
trap_Argv( 2, string, sizeof( string ) );
if ( !string[0] ) {
skill = 4;
} else {
skill = atoi( string );
}
// team
trap_Argv( 3, team, sizeof( team ) );
// delay
trap_Argv( 4, string, sizeof( string ) );
if ( !string[0] ) {
delay = 0;
} else {
delay = atoi( string );
}
G_AddBot( name, skill, team, delay );
// if this was issued during gameplay and we are playing locally,
// go ahead and load the bot's media immediately
if ( level.time - level.startTime > 1000 &&
trap_Cvar_VariableIntegerValue( "cl_running" ) ) {
trap_SendServerCommand( -1, "loaddeferred\n" ); // spelling fixed (SA)
}
}
// TTimo gcc: defined but not used
#if 0
/*
===============
G_SpawnBots
===============
*/
static void G_SpawnBots( char *botList, int baseDelay ) {
char *bot;
char *p;
int skill;
int delay;
char bots[MAX_INFO_VALUE];
podium1 = NULL;
podium2 = NULL;
podium3 = NULL;
skill = trap_Cvar_VariableIntegerValue( "g_spSkill" );
if ( skill < 1 || skill > 5 ) {
trap_Cvar_Set( "g_spSkill", "2" );
skill = 2;
}
Q_strncpyz( bots, botList, sizeof( bots ) );
p = &bots[0];
delay = baseDelay;
while ( *p ) {
//skip spaces
while ( *p && *p == ' ' ) {
p++;
}
if ( !p ) {
break;
}
// mark start of bot name
bot = p;
// skip until space of null
while ( *p && *p != ' ' ) {
p++;
}
if ( *p ) {
*p++ = 0;
}
// we must add the bot this way, calling G_AddBot directly at this stage
// does "Bad Things"
trap_SendConsoleCommand( EXEC_INSERT, va( "addbot %s %i free %i\n", bot, skill, delay ) );
delay += BOT_BEGIN_DELAY_INCREMENT;
}
}
#endif
// TTimo gcc: defined but not used
#if 0
/*
===============
G_LoadBots
===============
*/
static void G_LoadBots( void ) {
#ifdef QUAKESTUFF
int len;
char *filename;
vmCvar_t botsFile;
fileHandle_t f;
char buf[MAX_BOTS_TEXT];
if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
return;
}
trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM );
if ( *botsFile.string ) {
filename = botsFile.string;
} else {
filename = "scripts/bots.txt";
}
len = trap_FS_FOpenFile( filename, &f, FS_READ );
if ( !f ) {
trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
return;
}
if ( len >= MAX_BOTS_TEXT ) {
trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
trap_FS_FCloseFile( f );
return;
}
trap_FS_Read( buf, len, f );
buf[len] = 0;
trap_FS_FCloseFile( f );
g_numBots = COM_ParseInfos( buf, MAX_BOTS, g_botInfos );
trap_Printf( va( "%i bots parsed\n", g_numBots ) );
#endif
}
#endif
/*
===============
G_GetBotInfoByNumber
===============
*/
char *G_GetBotInfoByNumber( int num ) {
if ( num < 0 || num >= g_numBots ) {
trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
return NULL;
}
return g_botInfos[num];
}
/*
===============
G_GetBotInfoByName
===============
*/
char *G_GetBotInfoByName( const char *name ) {
int n;
char *value;
for ( n = 0; n < g_numBots ; n++ ) {
value = Info_ValueForKey( g_botInfos[n], "name" );
if ( !Q_stricmp( value, name ) ) {
return g_botInfos[n];
}
}
return NULL;
}
/*
===============
G_InitBots
===============
*/
void G_InitBots( qboolean restart ) {
// Ridah, we don't need this anymore
return;
// done.
/*
int fragLimit;
int timeLimit;
const char *arenainfo;
char *strValue;
int basedelay;
char map[MAX_QPATH];
char serverinfo[MAX_INFO_STRING];
G_LoadBots();
G_LoadArenas();
trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO );
if( g_gametype.integer == GT_SINGLE_PLAYER ) {
trap_GetServerinfo( serverinfo, sizeof(serverinfo) );
Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) );
arenainfo = G_GetArenaInfoByMap( map );
if ( !arenainfo ) {
return;
}
strValue = Info_ValueForKey( arenainfo, "fraglimit" );
fragLimit = atoi( strValue );
if ( fragLimit ) {
trap_Cvar_Set( "fraglimit", strValue );
}
else {
trap_Cvar_Set( "fraglimit", "0" );
}
strValue = Info_ValueForKey( arenainfo, "timelimit" );
timeLimit = atoi( strValue );
if ( timeLimit ) {
trap_Cvar_Set( "timelimit", strValue );
}
else {
trap_Cvar_Set( "timelimit", "0" );
}
if ( !fragLimit && !timeLimit ) {
trap_Cvar_Set( "fraglimit", "10" );
trap_Cvar_Set( "timelimit", "0" );
}
basedelay = BOT_BEGIN_DELAY_BASE;
strValue = Info_ValueForKey( arenainfo, "special" );
if( Q_stricmp( strValue, "training" ) == 0 ) {
basedelay += 10000;
}
if( !restart ) {
G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay );
}
}
*/
}