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