aboutsummaryrefslogtreecommitdiff
path: root/code/game/g_bot.c
diff options
context:
space:
mode:
Diffstat (limited to 'code/game/g_bot.c')
-rw-r--r--code/game/g_bot.c1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/code/game/g_bot.c b/code/game/g_bot.c
new file mode 100644
index 0000000..4c9cf8c
--- /dev/null
+++ b/code/game/g_bot.c
@@ -0,0 +1,1013 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena 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 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// g_bot.c
+
+#include "g_local.h"
+
+
+static int g_numBots;
+static char *g_botInfos[MAX_BOTS];
+
+
+int g_numArenas;
+static char *g_arenaInfos[MAX_ARENAS];
+
+
+#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 botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
+
+vmCvar_t bot_minplayers;
+
+extern gentity_t *podium1;
+extern gentity_t *podium2;
+extern gentity_t *podium3;
+
+float trap_Cvar_VariableValue( const char *var_name ) {
+ char buf[128];
+
+ trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf));
+ return atof(buf);
+}
+
+
+
+/*
+===============
+G_ParseInfos
+===============
+*/
+int G_ParseInfos( char *buf, int max, char *infos[] ) {
+ char *token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+ char info[MAX_INFO_STRING];
+
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &buf );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( strcmp( token, "{" ) ) {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if ( count == max ) {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ info[0] = '\0';
+ while ( 1 ) {
+ token = COM_ParseExt( &buf, qtrue );
+ if ( !token[0] ) {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+ if ( !strcmp( token, "}" ) ) {
+ break;
+ }
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+ if ( !token[0] ) {
+ strcpy( token, "<NULL>" );
+ }
+ Info_SetValueForKey( info, key, token );
+ }
+ //NOTE: extra space for arena number
+ infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
+ if (infos[count]) {
+ strcpy(infos[count], info);
+ count++;
+ }
+ }
+ return count;
+}
+
+/*
+===============
+G_LoadArenasFromFile
+===============
+*/
+static void G_LoadArenasFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_ARENAS_TEXT];
+
+ 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 += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] );
+}
+
+/*
+===============
+G_LoadArenas
+===============
+*/
+static void G_LoadArenas( void ) {
+ int numdirs;
+ vmCvar_t arenasFile;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i, n;
+ int dirlen;
+
+ g_numArenas = 0;
+
+ trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
+ if( *arenasFile.string ) {
+ G_LoadArenasFromFile(arenasFile.string);
+ }
+ else {
+ G_LoadArenasFromFile("scripts/arenas.txt");
+ }
+
+ // get all arenas from .arena files
+ numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ G_LoadArenasFromFile(filename);
+ }
+ 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 ) );
+ }
+}
+
+
+/*
+===============
+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;
+ float 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_VariableValue( "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 %f %s %i\n", netname, skill, teamstr, 0) );
+ return;
+ }
+ }
+ }
+}
+
+/*
+===============
+G_RemoveRandomBot
+===============
+*/
+int G_RemoveRandomBot( int team ) {
+ int i;
+ 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;
+ }
+ trap_SendConsoleCommand( EXEC_INSERT, va("clientkick %d\n", cl->ps.clientNum) );
+ 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;
+
+ if (level.intermissiontime) return;
+ //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_VALUE];
+
+ 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_RemoveQueuedBotBegin
+
+Called on client disconnect to make sure the delayed spawn
+doesn't happen on a freed index
+===============
+*/
+void G_RemoveQueuedBotBegin( int clientNum ) {
+ int n;
+
+ for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
+ if( botSpawnQueue[n].clientNum == clientNum ) {
+ botSpawnQueue[n].spawnTime = 0;
+ return;
+ }
+ }
+}
+
+
+/*
+===============
+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 = atof( Info_ValueForKey( userinfo, "skill" ) );
+ Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) );
+
+ if (!BotAISetupClient( clientNum, &settings, restart )) {
+ trap_DropClient( clientNum, "BotAISetupClient failed" );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+G_AddBot
+===============
+*/
+static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) {
+ int clientNum;
+ char *botinfo;
+ gentity_t *bot;
+ char *key;
+ char *s;
+ char *botname;
+ char *model;
+ char *headmodel;
+ 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" );
+ }
+ // check for an alternative name
+ if (altname && altname[0]) {
+ botname = altname;
+ }
+ Info_SetValueForKey( userinfo, "name", botname );
+ Info_SetValueForKey( userinfo, "rate", "25000" );
+ Info_SetValueForKey( userinfo, "snaps", "20" );
+ Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) );
+
+ if ( skill >= 1 && skill < 2 ) {
+ Info_SetValueForKey( userinfo, "handicap", "50" );
+ }
+ else if ( skill >= 2 && skill < 3 ) {
+ Info_SetValueForKey( userinfo, "handicap", "70" );
+ }
+ else if ( skill >= 3 && skill < 4 ) {
+ Info_SetValueForKey( userinfo, "handicap", "90" );
+ }
+
+ key = "model";
+ model = Info_ValueForKey( botinfo, key );
+ if ( !*model ) {
+ model = "visor/default";
+ }
+ Info_SetValueForKey( userinfo, key, model );
+ key = "team_model";
+ Info_SetValueForKey( userinfo, key, model );
+
+ key = "headmodel";
+ headmodel = Info_ValueForKey( botinfo, key );
+ if ( !*headmodel ) {
+ headmodel = model;
+ }
+ Info_SetValueForKey( userinfo, key, headmodel );
+ key = "team_headmodel";
+ Info_SetValueForKey( userinfo, key, headmodel );
+
+ key = "gender";
+ s = Info_ValueForKey( botinfo, key );
+ if ( !*s ) {
+ s = "male";
+ }
+ Info_SetValueForKey( userinfo, "sex", s );
+
+ key = "color1";
+ s = Info_ValueForKey( botinfo, key );
+ if ( !*s ) {
+ s = "4";
+ }
+ Info_SetValueForKey( userinfo, key, s );
+
+ key = "color2";
+ s = Info_ValueForKey( botinfo, key );
+ if ( !*s ) {
+ s = "5";
+ }
+ 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 ) {
+ 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( "%5.2f", 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 ) {
+ float skill;
+ int delay;
+ char name[MAX_TOKEN_CHARS];
+ char altname[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 <botname> [skill 1-5] [team] [msec delay] [altname]\n" );
+ return;
+ }
+
+ // skill
+ trap_Argv( 2, string, sizeof( string ) );
+ if ( !string[0] ) {
+ skill = 4;
+ }
+ else {
+ skill = atof( string );
+ }
+
+ // team
+ trap_Argv( 3, team, sizeof( team ) );
+
+ // delay
+ trap_Argv( 4, string, sizeof( string ) );
+ if ( !string[0] ) {
+ delay = 0;
+ }
+ else {
+ delay = atoi( string );
+ }
+
+ // alternative name
+ trap_Argv( 5, altname, sizeof( altname ) );
+
+ G_AddBot( name, skill, team, delay, altname );
+
+ // 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, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo
+ }
+}
+
+/*
+===============
+Svcmd_BotList_f
+===============
+*/
+void Svcmd_BotList_f( void ) {
+ int i;
+ char name[MAX_TOKEN_CHARS];
+ char funname[MAX_TOKEN_CHARS];
+ char model[MAX_TOKEN_CHARS];
+ char aifile[MAX_TOKEN_CHARS];
+
+ trap_Printf("^1name model aifile funname\n");
+ for (i = 0; i < g_numBots; i++) {
+ strcpy(name, Info_ValueForKey( g_botInfos[i], "name" ));
+ if ( !*name ) {
+ strcpy(name, "UnnamedPlayer");
+ }
+ strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" ));
+ if ( !*funname ) {
+ strcpy(funname, "");
+ }
+ strcpy(model, Info_ValueForKey( g_botInfos[i], "model" ));
+ if ( !*model ) {
+ strcpy(model, "visor/default");
+ }
+ strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile"));
+ if (!*aifile ) {
+ strcpy(aifile, "bots/default_c.c");
+ }
+ trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname));
+ }
+}
+
+
+/*
+===============
+G_SpawnBots
+===============
+*/
+static void G_SpawnBots( char *botList, int baseDelay ) {
+ char *bot;
+ char *p;
+ float skill;
+ int delay;
+ char bots[MAX_INFO_VALUE];
+
+ podium1 = NULL;
+ podium2 = NULL;
+ podium3 = NULL;
+
+ skill = trap_Cvar_VariableValue( "g_spSkill" );
+ if( skill < 1 ) {
+ trap_Cvar_Set( "g_spSkill", "1" );
+ skill = 1;
+ }
+ else if ( skill > 5 ) {
+ trap_Cvar_Set( "g_spSkill", "5" );
+ skill = 5;
+ }
+
+ 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 %f free %i\n", bot, skill, delay) );
+
+ delay += BOT_BEGIN_DELAY_INCREMENT;
+ }
+}
+
+
+/*
+===============
+G_LoadBotsFromFile
+===============
+*/
+static void G_LoadBotsFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_BOTS_TEXT];
+
+ 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 += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] );
+}
+
+/*
+===============
+G_LoadBots
+===============
+*/
+static void G_LoadBots( void ) {
+ vmCvar_t botsFile;
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i;
+ int dirlen;
+
+ if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
+ return;
+ }
+
+ g_numBots = 0;
+
+ trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
+ if( *botsFile.string ) {
+ G_LoadBotsFromFile(botsFile.string);
+ }
+ else {
+ G_LoadBotsFromFile("scripts/bots.txt");
+ }
+
+ // get all bots from .bot files
+ numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ G_LoadBotsFromFile(filename);
+ }
+ trap_Printf( va( "%i bots parsed\n", g_numBots ) );
+}
+
+
+
+/*
+===============
+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 ) {
+ 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 );
+ }
+ }
+}