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