1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP 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 SP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 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.
25 
26 ===========================================================================
27 */
28 
29 // g_bot.c
30 
31 #include "g_local.h"
32 #include "../botlib/botai.h"
33 
34 
35 static int g_numBots;
36 static char g_botInfos[MAX_BOTS][MAX_INFO_STRING];
37 
38 
39 int g_numArenas;
40 static char g_arenaInfos[MAX_ARENAS][MAX_INFO_STRING];
41 
42 
43 #define BOT_BEGIN_DELAY_BASE        2000
44 #define BOT_BEGIN_DELAY_INCREMENT   1500
45 
46 #define BOT_SPAWN_QUEUE_DEPTH   16
47 
48 typedef struct {
49 	int clientNum;
50 	int spawnTime;
51 } botSpawnQueue_t;
52 
53 static int botBeginDelay;
54 static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
55 
56 vmCvar_t bot_minplayers;
57 
58 extern gentity_t    *podium1;
59 extern gentity_t    *podium2;
60 extern gentity_t    *podium3;
61 
62 /*
63 ===============
64 G_LoadArenas
65 ===============
66 */
67 /*
68 static void G_LoadArenas( void ) {
69 #ifdef QUAKESTUFF
70 	int			len;
71 	char		*filename;
72 	vmCvar_t	arenasFile;
73 	fileHandle_t	f;
74 	int			n;
75 	char		buf[MAX_ARENAS_TEXT];
76 
77 	trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
78 	if( *arenasFile.string ) {
79 		filename = arenasFile.string;
80 	}
81 	else {
82 		filename = "scripts/arenas.txt";
83 	}
84 
85 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
86 	if ( !f ) {
87 		trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
88 		return;
89 	}
90 	if ( len >= MAX_ARENAS_TEXT ) {
91 		trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
92 		trap_FS_FCloseFile( f );
93 		return;
94 	}
95 
96 	trap_FS_Read( buf, len, f );
97 	buf[len] = 0;
98 	trap_FS_FCloseFile( f );
99 
100 	g_numArenas = COM_ParseInfos( buf, MAX_ARENAS, g_arenaInfos );
101 	trap_Print( va( "%i arenas parsed\n", g_numArenas ) );
102 
103 	for( n = 0; n < g_numArenas; n++ ) {
104 		Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
105 	}
106 #endif
107 }
108 */
109 
110 
111 /*
112 ===============
113 G_GetArenaInfoByNumber
114 ===============
115 */
G_GetArenaInfoByMap(const char * map)116 const char *G_GetArenaInfoByMap( const char *map ) {
117 	int n;
118 
119 	for ( n = 0; n < g_numArenas; n++ ) {
120 		if ( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) {
121 			return g_arenaInfos[n];
122 		}
123 	}
124 
125 	return NULL;
126 }
127 
128 
129 /*
130 =================
131 PlayerIntroSound
132 =================
133 */
PlayerIntroSound(const char * modelAndSkin)134 static void PlayerIntroSound( const char *modelAndSkin ) {
135 	char model[MAX_QPATH];
136 	char    *skin;
137 
138 	Q_strncpyz( model, modelAndSkin, sizeof( model ) );
139 	skin = strrchr( model, '/' );
140 	if ( skin ) {
141 		*skin++ = '\0';
142 	} else {
143 		skin = model;
144 	}
145 
146 	if ( Q_stricmp( skin, "default" ) == 0 ) {
147 		skin = model;
148 	}
149 
150 	trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) );
151 }
152 
153 /*
154 ===============
155 G_CountBotPlayersByName
156 
157 Check connected and connecting (delay join) bots.
158 
159 Returns number of bots with name on specified team or whole server if team is -1.
160 ===============
161 */
G_CountBotPlayersByName(const char * name,int team)162 int G_CountBotPlayersByName( const char *name, int team ) {
163 	int			i, num;
164 	gclient_t   *cl;
165 
166 	num = 0;
167 	for ( i=0 ; i< g_maxclients.integer ; i++ ) {
168 		cl = level.clients + i;
169 		if ( cl->pers.connected == CON_DISCONNECTED ) {
170 			continue;
171 		}
172 		if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
173 			continue;
174 		}
175 		if ( team >= 0 && cl->sess.sessionTeam != team ) {
176 			continue;
177 		}
178 		if ( name && Q_stricmp( name, cl->pers.netname ) ) {
179 			continue;
180 		}
181 		num++;
182 	}
183 	return num;
184 }
185 
186 /*
187 ===============
188 G_SelectRandomBotInfo
189 
190 Get random least used bot info on team or whole server if team is -1.
191 ===============
192 */
G_SelectRandomBotInfo(int team)193 int G_SelectRandomBotInfo( int team ) {
194 	int	selection[MAX_BOTS];
195 	int	n, num;
196 	int	count, bestCount;
197 	char	*value;
198 
199 	// don't add duplicate bots to the server if there are less bots than bot types
200 	if ( team != -1 && G_CountBotPlayersByName( NULL, -1 ) < g_numBots ) {
201 		team = -1;
202 	}
203 
204 	num = 0;
205 	bestCount = MAX_CLIENTS;
206 	for ( n = 0; n < g_numBots ; n++ ) {
207 		value = Info_ValueForKey( g_botInfos[n], "funname" );
208 		if ( !value[0] ) {
209 			value = Info_ValueForKey( g_botInfos[n], "name" );
210 		}
211 		//
212 		count = G_CountBotPlayersByName( value, team );
213 
214 		if ( count < bestCount ) {
215 			bestCount = count;
216 			num = 0;
217 		}
218 
219 		if ( count == bestCount ) {
220 			selection[num++] = n;
221 
222 			if ( num == MAX_BOTS ) {
223 				break;
224 			}
225 		}
226 	}
227 
228 	if ( num > 0 ) {
229 		num = random() * ( num - 1 );
230 		return selection[num];
231 	}
232 
233 	return -1;
234 }
235 
236 /*
237 ===============
238 G_AddRandomBot
239 ===============
240 */
G_AddRandomBot(int team)241 void G_AddRandomBot( int team ) {
242 	char	*teamstr;
243 	int	skill;
244 
245 	skill = trap_Cvar_VariableIntegerValue( "g_spSkill" );
246 	if ( team == TEAM_RED ) {
247 		teamstr = "red";
248 	} else if ( team == TEAM_BLUE ) {
249 		teamstr = "blue";
250 	} else {
251 		teamstr = "free";
252 	}
253 	trap_SendConsoleCommand( EXEC_INSERT, va( "addbot random %i %s %i\n", skill, teamstr, 0 ) );
254 }
255 
256 /*
257 ===============
258 G_RemoveRandomBot
259 ===============
260 */
G_RemoveRandomBot(int team)261 int G_RemoveRandomBot( int team ) {
262 	int i;
263 	gclient_t   *cl;
264 
265 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
266 		cl = level.clients + i;
267 		if ( cl->pers.connected != CON_CONNECTED ) {
268 			continue;
269 		}
270 		if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
271 			continue;
272 		}
273 		if ( team >= 0 && cl->sess.sessionTeam != team ) {
274 			continue;
275 		}
276 		trap_SendConsoleCommand( EXEC_INSERT, va("clientkick %d\n", i) );
277 		return qtrue;
278 	}
279 	return qfalse;
280 }
281 
282 /*
283 ===============
284 G_CountHumanPlayers
285 ===============
286 */
G_CountHumanPlayers(int team)287 int G_CountHumanPlayers( int team ) {
288 	int i, num;
289 	gclient_t   *cl;
290 
291 	num = 0;
292 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
293 		cl = level.clients + i;
294 		if ( cl->pers.connected != CON_CONNECTED ) {
295 			continue;
296 		}
297 		if ( g_entities[i].r.svFlags & SVF_BOT ) {
298 			continue;
299 		}
300 		if ( team >= 0 && cl->sess.sessionTeam != team ) {
301 			continue;
302 		}
303 		num++;
304 	}
305 	return num;
306 }
307 
308 /*
309 ===============
310 G_CountBotPlayers
311 
312 Check connected and connecting (delay join) bots.
313 ===============
314 */
G_CountBotPlayers(int team)315 int G_CountBotPlayers( int team ) {
316 	int i, num;
317 	gclient_t   *cl;
318 
319 	num = 0;
320 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
321 		cl = level.clients + i;
322 		if ( cl->pers.connected == CON_DISCONNECTED ) {
323 			continue;
324 		}
325 		if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
326 			continue;
327 		}
328 		if ( team >= 0 && cl->sess.sessionTeam != team ) {
329 			continue;
330 		}
331 		num++;
332 	}
333 	return num;
334 }
335 
336 /*
337 ===============
338 G_CheckMinimumPlayers
339 ===============
340 */
G_CheckMinimumPlayers(void)341 void G_CheckMinimumPlayers( void ) {
342 	int minplayers;
343 	int humanplayers, botplayers;
344 	static int checkminimumplayers_time;
345 
346 	//only check once each 10 seconds
347 	if ( checkminimumplayers_time > level.time - 10000 ) {
348 		return;
349 	}
350 	checkminimumplayers_time = level.time;
351 	trap_Cvar_Update( &bot_minplayers );
352 	minplayers = bot_minplayers.integer;
353 	if ( minplayers <= 0 ) {
354 		return;
355 	}
356 
357 	if ( g_gametype.integer >= GT_TEAM ) {
358 		if ( minplayers >= g_maxclients.integer / 2 ) {
359 			minplayers = ( g_maxclients.integer / 2 ) - 1;
360 		}
361 
362 		humanplayers = G_CountHumanPlayers( TEAM_RED );
363 		botplayers = G_CountBotPlayers( TEAM_RED );
364 		//
365 		if ( humanplayers + botplayers < minplayers ) {
366 			G_AddRandomBot( TEAM_RED );
367 		} else if ( humanplayers + botplayers > minplayers && botplayers ) {
368 			G_RemoveRandomBot( TEAM_RED );
369 		}
370 		//
371 		humanplayers = G_CountHumanPlayers( TEAM_BLUE );
372 		botplayers = G_CountBotPlayers( TEAM_BLUE );
373 		//
374 		if ( humanplayers + botplayers < minplayers ) {
375 			G_AddRandomBot( TEAM_BLUE );
376 		} else if ( humanplayers + botplayers > minplayers && botplayers ) {
377 			G_RemoveRandomBot( TEAM_BLUE );
378 		}
379 	} else if ( g_gametype.integer == GT_TOURNAMENT )     {
380 		if ( minplayers >= g_maxclients.integer ) {
381 			minplayers = g_maxclients.integer - 1;
382 		}
383 		humanplayers = G_CountHumanPlayers( -1 );
384 		botplayers = G_CountBotPlayers( -1 );
385 		//
386 		if ( humanplayers + botplayers < minplayers ) {
387 			G_AddRandomBot( TEAM_FREE );
388 		} else if ( humanplayers + botplayers > minplayers && botplayers ) {
389 			// try to remove spectators first
390 			if ( !G_RemoveRandomBot( TEAM_SPECTATOR ) ) {
391 				// just remove the bot that is playing
392 				G_RemoveRandomBot( -1 );
393 			}
394 		}
395 	} else if ( g_gametype.integer == GT_FFA )     {
396 		if ( minplayers >= g_maxclients.integer ) {
397 			minplayers = g_maxclients.integer - 1;
398 		}
399 		humanplayers = G_CountHumanPlayers( TEAM_FREE );
400 		botplayers = G_CountBotPlayers( TEAM_FREE );
401 		//
402 		if ( humanplayers + botplayers < minplayers ) {
403 			G_AddRandomBot( TEAM_FREE );
404 		} else if ( humanplayers + botplayers > minplayers && botplayers ) {
405 			G_RemoveRandomBot( TEAM_FREE );
406 		}
407 	}
408 }
409 
410 /*
411 ===============
412 G_CheckBotSpawn
413 ===============
414 */
G_CheckBotSpawn(void)415 void G_CheckBotSpawn( void ) {
416 	int n;
417 	char userinfo[MAX_INFO_VALUE];
418 
419 	G_CheckMinimumPlayers();
420 
421 	for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
422 		if ( !botSpawnQueue[n].spawnTime ) {
423 			continue;
424 		}
425 		if ( botSpawnQueue[n].spawnTime > level.time ) {
426 			continue;
427 		}
428 		ClientBegin( botSpawnQueue[n].clientNum );
429 		botSpawnQueue[n].spawnTime = 0;
430 
431 		if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
432 			trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof( userinfo ) );
433 			PlayerIntroSound( Info_ValueForKey( userinfo, "model" ) );
434 		}
435 	}
436 }
437 
438 
439 /*
440 ===============
441 AddBotToSpawnQueue
442 ===============
443 */
AddBotToSpawnQueue(int clientNum,int delay)444 static void AddBotToSpawnQueue( int clientNum, int delay ) {
445 	int n;
446 
447 	for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
448 		if ( !botSpawnQueue[n].spawnTime ) {
449 			botSpawnQueue[n].spawnTime = level.time + delay;
450 			botSpawnQueue[n].clientNum = clientNum;
451 			return;
452 		}
453 	}
454 
455 	G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" );
456 	ClientBegin( clientNum );
457 }
458 
459 
460 /*
461 ===============
462 G_QueueBotBegin
463 ===============
464 */
G_QueueBotBegin(int clientNum)465 void G_QueueBotBegin( int clientNum ) {
466 	AddBotToSpawnQueue( clientNum, botBeginDelay );
467 	botBeginDelay += BOT_BEGIN_DELAY_INCREMENT;
468 }
469 
470 
471 /*
472 ===============
473 G_BotConnect
474 ===============
475 */
G_BotConnect(int clientNum,qboolean restart)476 qboolean G_BotConnect( int clientNum, qboolean restart ) {
477 	bot_settings_t settings;
478 	char userinfo[MAX_INFO_STRING];
479 
480 	trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
481 
482 	Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof( settings.characterfile ) );
483 	settings.skill = atoi( Info_ValueForKey( userinfo, "skill" ) );
484 
485 	if ( !BotAISetupClient( clientNum, &settings ) ) {
486 		trap_DropClient( clientNum, "BotAISetupClient failed" );
487 		return qfalse;
488 	}
489 
490 	if ( restart && g_gametype.integer == GT_SINGLE_PLAYER ) {
491 		g_entities[clientNum].botDelayBegin = qtrue;
492 	} else {
493 		g_entities[clientNum].botDelayBegin = qfalse;
494 	}
495 
496 	return qtrue;
497 }
498 
499 
500 /*
501 ===============
502 G_AddBot
503 ===============
504 */
G_AddBot(const char * name,int skill,const char * team,int delay)505 static void G_AddBot( const char *name, int skill, const char *team, int delay ) {
506 	int		clientNum;
507 	int		teamNum;
508 	int		botinfoNum;
509 	char            *botinfo;
510 	char            *key;
511 	char            *s;
512 	char            *botname;
513 	char            *model;
514 	char userinfo[MAX_INFO_STRING];
515 
516 	// have the server allocate a client slot
517 	clientNum = trap_BotAllocateClient();
518 	if ( clientNum == -1 ) {
519 		G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" );
520 		G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" );
521 		return;
522 	}
523 
524 	// set default team
525 	if( !team || !*team ) {
526 		if( g_gametype.integer >= GT_TEAM ) {
527 			if( PickTeam(clientNum) == TEAM_RED) {
528 				team = "red";
529 			}
530 			else {
531 				team = "blue";
532 			}
533 		}
534 		else {
535 			team = "free";
536 		}
537 	}
538 
539 	// get the botinfo from bots.txt
540 	if ( Q_stricmp( name, "random" ) == 0 ) {
541 		if ( Q_stricmp( team, "red" ) == 0 || Q_stricmp( team, "r" ) == 0 ) {
542 			teamNum = TEAM_RED;
543 		}
544 		else if ( Q_stricmp( team, "blue" ) == 0 || Q_stricmp( team, "b" ) == 0 ) {
545 			teamNum = TEAM_BLUE;
546 		}
547 		else if ( !Q_stricmp( team, "spectator" ) || !Q_stricmp( team, "s" ) ) {
548 			teamNum = TEAM_SPECTATOR;
549 		}
550 		else {
551 			teamNum = TEAM_FREE;
552 		}
553 
554 		botinfoNum = G_SelectRandomBotInfo( teamNum );
555 
556 		if ( botinfoNum < 0 ) {
557 			G_Printf( S_COLOR_RED "Error: Cannot add random bot, no bot info available.\n" );
558 			trap_BotFreeClient( clientNum );
559 			return;
560 		}
561 
562 		botinfo = G_GetBotInfoByNumber( botinfoNum );
563 	}
564 	else {
565 		botinfo = G_GetBotInfoByName( name );
566 	}
567 
568 	if ( !botinfo ) {
569 		G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name );
570 		trap_BotFreeClient( clientNum );
571 		return;
572 	}
573 
574 	// create the bot's userinfo
575 	userinfo[0] = '\0';
576 
577 	botname = Info_ValueForKey( botinfo, "funname" );
578 	if ( !botname[0] ) {
579 		botname = Info_ValueForKey( botinfo, "name" );
580 	}
581 	Info_SetValueForKey( userinfo, "name", botname );
582 	Info_SetValueForKey( userinfo, "rate", "25000" );
583 	Info_SetValueForKey( userinfo, "snaps", "20" );
584 	Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) );
585 	Info_SetValueForKey( userinfo, "teampref", team );
586 
587 	if ( skill == 1 ) {
588 		Info_SetValueForKey( userinfo, "handicap", "50" );
589 	} else if ( skill == 2 )   {
590 		Info_SetValueForKey( userinfo, "handicap", "70" );
591 	} else if ( skill == 3 )   {
592 		Info_SetValueForKey( userinfo, "handicap", "90" );
593 	}
594 
595 	key = "model";
596 	model = Info_ValueForKey( botinfo, key );
597 	if ( !*model ) {
598 		model = "visor/default";
599 	}
600 	Info_SetValueForKey( userinfo, key, model );
601 
602 	key = "gender";
603 	s = Info_ValueForKey( botinfo, key );
604 	if ( !*s ) {
605 		s = "male";
606 	}
607 	Info_SetValueForKey( userinfo, "sex", s );
608 
609 	key = "color";
610 	s = Info_ValueForKey( botinfo, key );
611 	if ( !*s ) {
612 		s = "4";
613 	}
614 	Info_SetValueForKey( userinfo, key, s );
615 
616 	s = Info_ValueForKey( botinfo, "aifile" );
617 	if ( !*s ) {
618 		trap_Print( S_COLOR_RED "Error: bot has no aifile specified\n" );
619 		trap_BotFreeClient( clientNum );
620 		return;
621 	}
622 	Info_SetValueForKey( userinfo, "characterfile", s );
623 
624 	// register the userinfo
625 	trap_SetUserinfo( clientNum, userinfo );
626 
627 	// have it connect to the game as a normal client
628 	if ( ClientConnect( clientNum, qtrue, qtrue ) ) {
629 		return;
630 	}
631 
632 	if ( delay == 0 ) {
633 		ClientBegin( clientNum );
634 		return;
635 	}
636 
637 	AddBotToSpawnQueue( clientNum, delay );
638 }
639 
640 
641 /*
642 ===============
643 Svcmd_AddBot_f
644 ===============
645 */
Svcmd_AddBot_f(void)646 void Svcmd_AddBot_f( void ) {
647 	int skill;
648 	int delay;
649 	char name[MAX_TOKEN_CHARS];
650 	char string[MAX_TOKEN_CHARS];
651 	char team[MAX_TOKEN_CHARS];
652 
653 	// are bots enabled?
654 	if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
655 		return;
656 	}
657 
658 	// name
659 	trap_Argv( 1, name, sizeof( name ) );
660 	if ( !name[0] ) {
661 		trap_Print( "Usage: Addbot <botname> [skill 1-4] [team] [msec delay]\n" );
662 		return;
663 	}
664 
665 	// skill
666 	trap_Argv( 2, string, sizeof( string ) );
667 	if ( !string[0] ) {
668 		skill = 4;
669 	} else {
670 		skill = Com_Clamp( 1, 5, atoi( string ) );
671 	}
672 
673 	// team
674 	trap_Argv( 3, team, sizeof( team ) );
675 
676 	// delay
677 	trap_Argv( 4, string, sizeof( string ) );
678 	if ( !string[0] ) {
679 		delay = 0;
680 	} else {
681 		delay = atoi( string );
682 	}
683 
684 	G_AddBot( name, skill, team, delay );
685 
686 	// if this was issued during gameplay and we are playing locally,
687 	// go ahead and load the bot's media immediately
688 	if ( level.time - level.startTime > 1000 &&
689 		 trap_Cvar_VariableIntegerValue( "cl_running" ) ) {
690 		trap_SendServerCommand( -1, "loaddeferred\n" );   // spelling fixed (SA)
691 	}
692 }
693 
694 
695 /*
696 ===============
697 G_SpawnBots
698 ===============
699 */
700 /*
701 static void G_SpawnBots( char *botList, int baseDelay ) {
702 	char		*bot;
703 	char		*p;
704 	int			skill;
705 	int			delay;
706 	char		bots[MAX_INFO_VALUE];
707 
708 	podium1 = NULL;
709 	podium2 = NULL;
710 	podium3 = NULL;
711 
712 	skill = trap_Cvar_VariableIntegerValue( "g_spSkill" );
713 	if( skill < 1 || skill > 5 ) {
714 		trap_Cvar_Set( "g_spSkill", "2" );
715 		skill = 2;
716 	}
717 
718 	Q_strncpyz( bots, botList, sizeof(bots) );
719 	p = &bots[0];
720 	delay = baseDelay;
721 	while( *p ) {
722 		//skip spaces
723 		while( *p && *p == ' ' ) {
724 			p++;
725 		}
726 		if( !*p ) {
727 			break;
728 		}
729 
730 		// mark start of bot name
731 		bot = p;
732 
733 		// skip until space of null
734 		while( *p && *p != ' ' ) {
735 			p++;
736 		}
737 		if( *p ) {
738 			*p++ = 0;
739 		}
740 
741 		// we must add the bot this way, calling G_AddBot directly at this stage
742 		// does "Bad Things"
743 		trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %i free %i\n", bot, skill, delay) );
744 
745 		delay += BOT_BEGIN_DELAY_INCREMENT;
746 	}
747 }
748 */
749 
750 
751 /*
752 ===============
753 G_LoadBots
754 ===============
755 */
756 // TTimo: unused
757 /*
758 static void G_LoadBots( void ) {
759 #ifdef QUAKESTUFF
760 	int			len;
761 	char		*filename;
762 	vmCvar_t	botsFile;
763 	fileHandle_t	f;
764 	char		buf[MAX_BOTS_TEXT];
765 
766 	if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
767 		return;
768 	}
769 
770 	trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
771 	if( *botsFile.string ) {
772 		filename = botsFile.string;
773 	}
774 	else {
775 		filename = "scripts/bots.txt";
776 	}
777 
778 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
779 	if ( !f ) {
780 		trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
781 		return;
782 	}
783 	if ( len >= MAX_BOTS_TEXT ) {
784 		trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
785 		trap_FS_FCloseFile( f );
786 		return;
787 	}
788 
789 	trap_FS_Read( buf, len, f );
790 	buf[len] = 0;
791 	trap_FS_FCloseFile( f );
792 
793 	g_numBots = COM_ParseInfos( buf, MAX_BOTS, g_botInfos );
794 	trap_Print( va( "%i bots parsed\n", g_numBots ) );
795 #endif
796 }
797 */
798 
799 /*
800 ===============
801 G_GetBotInfoByNumber
802 ===============
803 */
G_GetBotInfoByNumber(int num)804 char *G_GetBotInfoByNumber( int num ) {
805 	if ( num < 0 || num >= g_numBots ) {
806 		trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
807 		return NULL;
808 	}
809 	return g_botInfos[num];
810 }
811 
812 
813 /*
814 ===============
815 G_GetBotInfoByName
816 ===============
817 */
G_GetBotInfoByName(const char * name)818 char *G_GetBotInfoByName( const char *name ) {
819 	int n;
820 	char    *value;
821 
822 	for ( n = 0; n < g_numBots ; n++ ) {
823 		value = Info_ValueForKey( g_botInfos[n], "name" );
824 		if ( !Q_stricmp( value, name ) ) {
825 			return g_botInfos[n];
826 		}
827 	}
828 
829 	return NULL;
830 }
831 
832 /*
833 ===============
834 G_InitBots
835 ===============
836 */
837 /*
838 void G_InitBots( qboolean restart ) {
839 
840 	// Ridah, we don't need this anymore
841 	return;
842 	// done.
843 	int			fragLimit;
844 	int			timeLimit;
845 	const char	*arenainfo;
846 	char		*strValue;
847 	int			basedelay;
848 	char		map[MAX_QPATH];
849 	char		serverinfo[MAX_INFO_STRING];
850 
851 	G_LoadBots();
852 	G_LoadArenas();
853 
854 	trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO );
855 
856 	if( g_gametype.integer == GT_SINGLE_PLAYER ) {
857 		trap_GetServerinfo( serverinfo, sizeof(serverinfo) );
858 		Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) );
859 		arenainfo = G_GetArenaInfoByMap( map );
860 		if ( !arenainfo ) {
861 			return;
862 		}
863 
864 		strValue = Info_ValueForKey( arenainfo, "fraglimit" );
865 		fragLimit = atoi( strValue );
866 		if ( fragLimit ) {
867 			trap_Cvar_Set( "fraglimit", strValue );
868 		}
869 		else {
870 			trap_Cvar_Set( "fraglimit", "0" );
871 		}
872 
873 		strValue = Info_ValueForKey( arenainfo, "timelimit" );
874 		timeLimit = atoi( strValue );
875 		if ( timeLimit ) {
876 			trap_Cvar_Set( "timelimit", strValue );
877 		}
878 		else {
879 			trap_Cvar_Set( "timelimit", "0" );
880 		}
881 
882 		if ( !fragLimit && !timeLimit ) {
883 			trap_Cvar_Set( "fraglimit", "10" );
884 			trap_Cvar_Set( "timelimit", "0" );
885 		}
886 
887 		basedelay = BOT_BEGIN_DELAY_BASE;
888 		strValue = Info_ValueForKey( arenainfo, "special" );
889 		if( Q_stricmp( strValue, "training" ) == 0 ) {
890 			basedelay += 10000;
891 		}
892 
893 		if( !restart ) {
894 			G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay );
895 		}
896 	}
897 }
898 */
899