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