//----------------------------------------------------------------------------- // // $Id$ // //----------------------------------------------------------------------------- // // $Log$ // Revision 1.15 2003/03/28 10:36:02 jbravo // Tweaking the replacement system a bit. Reactionmale now the default model // // Revision 1.14 2002/08/20 20:07:21 jbravo // Fixed the bot_minplayer system. // // Revision 1.13 2002/06/16 20:06:14 jbravo // Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap" // // Revision 1.12 2002/06/12 19:59:39 jbravo // Removed unused variables // // Revision 1.11 2002/06/12 03:37:38 blaze // some fixes for the add bot code // // Revision 1.10 2002/06/02 22:23:27 makro // no message // // Revision 1.9 2002/05/10 13:21:53 makro // Mainly bot stuff. Also fixed a couple of crash bugs // // Revision 1.8 2002/05/03 18:09:20 makro // Bot stuff. Jump kicks // // Revision 1.7 2002/04/30 11:54:37 makro // Bots rule ! Also, added clips to give all. Maybe some other things // // Revision 1.6 2002/01/11 19:48:30 jbravo // Formatted the source in non DOS format. // // Revision 1.5 2001/12/31 16:28:42 jbravo // I made a Booboo with the Log tag. // // //----------------------------------------------------------------------------- // Copyright (C) 1999-2000 Id Software, Inc. // // 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 int botBeginDelay = 0; // bk001206 - unused, init 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, ""); } 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\n", 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 = 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 = ""; Q_strncpyz(netname, value, sizeof(netname)); 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 (g_gametype.integer >= GT_TEAM) { if (team >= 0 && cl->sess.savedTeam != team) continue; } else { 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 (g_gametype.integer >= GT_TEAM) { if (team >= 0 && cl->sess.savedTeam != team) continue; } else { 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 *s; char *botname; char *model; char *headmodel; char userinfo[MAX_INFO_STRING]; weapon_t tpWeapon = WP_M4; holdable_t tpItem = HI_LASER; // 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"); } model = Info_ValueForKey(botinfo, "model"); if (!*model) { // Elder: changed to our default model = "reactionmale/default"; } Info_SetValueForKey(userinfo, "model", model); Info_SetValueForKey(userinfo, "team_model", model); headmodel = Info_ValueForKey(botinfo, "headmodel"); if (!*headmodel) { headmodel = model; } Info_SetValueForKey(userinfo, "headmodel", headmodel); Info_SetValueForKey(userinfo, "team_headmodel", headmodel); s = Info_ValueForKey(botinfo, "gender"); if (!*s) { s = "male"; } Info_SetValueForKey(userinfo, "sex", s); s = Info_ValueForKey(botinfo, "color1"); if (!*s) { s = "4"; } Info_SetValueForKey(userinfo, "color1", s); s = Info_ValueForKey(botinfo, "color2"); if (!*s) { s = "5"; } Info_SetValueForKey(userinfo, "color2", 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); if (g_gametype.integer == GT_TEAMPLAY) { //Makro - load custom weapon/item from bot file tpWeapon = CharToWeapon(Info_ValueForKey(botinfo, "weapon"), WP_M4); tpItem = CharToItem(Info_ValueForKey(botinfo, "item"), HI_LASER); } Info_SetValueForKey(userinfo, "tpw", va("%i", tpWeapon - WP_NONE)); Info_SetValueForKey(userinfo, "tpi", va("%i", tpItem - HI_NONE)); 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); //Makro - load custom weapon/item from bot file if (g_gametype.integer == GT_TEAMPLAY) { bot->client->teamplayWeapon = tpWeapon; bot->client->teamplayItem = tpItem; } return; } AddBotToSpawnQueue(clientNum, delay); //Makro - load custom weapon/item from bot file if (g_gametype.integer == GT_TEAMPLAY) { bot->client->teamplayWeapon = tpWeapon; bot->client->teamplayItem = tpItem; } } /* =============== 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 [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")) { //Makro - fixed spelling //trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo trap_SendServerCommand(-1, "loaddeferred\n"); } } /* =============== 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\n", 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); } } }