2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
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.
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.
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
===========================================================================
29
static char *g_botInfos[MAX_BOTS];
33
static char *g_arenaInfos[MAX_ARENAS];
36
#define BOT_BEGIN_DELAY_BASE 2000
37
#define BOT_BEGIN_DELAY_INCREMENT 1500
39
#define BOT_SPAWN_QUEUE_DEPTH 16
46
//static int botBeginDelay = 0; // bk001206 - unused, init
47
static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
49
vmCvar_t bot_minplayers;
51
extern gentity_t *podium1;
52
extern gentity_t *podium2;
53
extern gentity_t *podium3;
55
float trap_Cvar_VariableValue( const char *var_name ) {
58
trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf));
69
int G_ParseInfos( char *buf, int max, char *infos[] ) {
72
char key[MAX_TOKEN_CHARS];
73
char info[MAX_INFO_STRING];
78
token = COM_Parse( &buf );
82
if ( strcmp( token, "{" ) ) {
83
Com_Printf( "Missing { in info file\n" );
88
Com_Printf( "Max infos exceeded\n" );
94
token = COM_ParseExt( &buf, qtrue );
96
Com_Printf( "Unexpected end of info file\n" );
99
if ( !strcmp( token, "}" ) ) {
102
Q_strncpyz( key, token, sizeof( key ) );
104
token = COM_ParseExt( &buf, qfalse );
106
strcpy( token, "<NULL>" );
108
Info_SetValueForKey( info, key, token );
110
//NOTE: extra space for arena number
111
infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
113
strcpy(infos[count], info);
125
static void G_LoadArenasFromFile( char *filename ) {
128
char buf[MAX_ARENAS_TEXT];
130
len = trap_FS_FOpenFile( filename, &f, FS_READ );
132
trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
135
if ( len >= MAX_ARENAS_TEXT ) {
136
trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
137
trap_FS_FCloseFile( f );
141
trap_FS_Read( buf, len, f );
143
trap_FS_FCloseFile( f );
145
g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] );
153
static void G_LoadArenas( void ) {
164
trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
165
if( *arenasFile.string ) {
166
G_LoadArenasFromFile(arenasFile.string);
169
G_LoadArenasFromFile("scripts/arenas.txt");
172
// get all arenas from .arena files
173
numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
175
for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
176
dirlen = strlen(dirptr);
177
strcpy(filename, "scripts/");
178
strcat(filename, dirptr);
179
G_LoadArenasFromFile(filename);
181
trap_Printf( va( "%i arenas parsed\n", g_numArenas ) );
183
for( n = 0; n < g_numArenas; n++ ) {
184
Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
191
G_GetArenaInfoByNumber
194
const char *G_GetArenaInfoByMap( const char *map ) {
197
for( n = 0; n < g_numArenas; n++ ) {
198
if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) {
199
return g_arenaInfos[n];
212
static void PlayerIntroSound( const char *modelAndSkin ) {
213
char model[MAX_QPATH];
216
Q_strncpyz( model, modelAndSkin, sizeof(model) );
217
skin = Q_strrchr( model, '/' );
225
if( Q_stricmp( skin, "default" ) == 0 ) {
229
trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) );
237
void G_AddRandomBot( int team ) {
240
char *value, netname[36], *teamstr;
244
for ( n = 0; n < g_numBots ; n++ ) {
245
value = Info_ValueForKey( g_botInfos[n], "name" );
247
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
248
cl = level.clients + i;
249
if ( cl->pers.connected != CON_CONNECTED ) {
252
if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
255
if ( team >= 0 && cl->sess.sessionTeam != team ) {
258
if ( !Q_stricmp( value, cl->pers.netname ) ) {
262
if (i >= g_maxclients.integer) {
266
num = random() * num;
267
for ( n = 0; n < g_numBots ; n++ ) {
268
value = Info_ValueForKey( g_botInfos[n], "name" );
270
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
271
cl = level.clients + i;
272
if ( cl->pers.connected != CON_CONNECTED ) {
275
if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
278
if ( team >= 0 && cl->sess.sessionTeam != team ) {
281
if ( !Q_stricmp( value, cl->pers.netname ) ) {
285
if (i >= g_maxclients.integer) {
288
skill = trap_Cvar_VariableValue( "g_spSkill" );
289
if (team == TEAM_RED) teamstr = "red";
290
else if (team == TEAM_BLUE) teamstr = "blue";
292
strncpy(netname, value, sizeof(netname)-1);
293
netname[sizeof(netname)-1] = '\0';
295
trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) );
307
int G_RemoveRandomBot( int team ) {
312
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
313
cl = level.clients + i;
314
if ( cl->pers.connected != CON_CONNECTED ) {
317
if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
320
if ( team >= 0 && cl->sess.sessionTeam != team ) {
323
strcpy(netname, cl->pers.netname);
325
trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) );
336
int G_CountHumanPlayers( int team ) {
341
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
342
cl = level.clients + i;
343
if ( cl->pers.connected != CON_CONNECTED ) {
346
if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
349
if ( team >= 0 && cl->sess.sessionTeam != team ) {
362
int G_CountBotPlayers( int team ) {
367
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
368
cl = level.clients + i;
369
if ( cl->pers.connected != CON_CONNECTED ) {
372
if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
375
if ( team >= 0 && cl->sess.sessionTeam != team ) {
380
for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
381
if( !botSpawnQueue[n].spawnTime ) {
384
if ( botSpawnQueue[n].spawnTime > level.time ) {
394
G_CheckMinimumPlayers
397
void G_CheckMinimumPlayers( void ) {
399
int humanplayers, botplayers;
400
static int checkminimumplayers_time;
402
if (level.intermissiontime) return;
403
//only check once each 10 seconds
404
if (checkminimumplayers_time > level.time - 10000) {
407
checkminimumplayers_time = level.time;
408
trap_Cvar_Update(&bot_minplayers);
409
minplayers = bot_minplayers.integer;
410
if (minplayers <= 0) return;
412
if (g_gametype.integer >= GT_TEAM) {
413
if (minplayers >= g_maxclients.integer / 2) {
414
minplayers = (g_maxclients.integer / 2) -1;
417
humanplayers = G_CountHumanPlayers( TEAM_RED );
418
botplayers = G_CountBotPlayers( TEAM_RED );
420
if (humanplayers + botplayers < minplayers) {
421
G_AddRandomBot( TEAM_RED );
422
} else if (humanplayers + botplayers > minplayers && botplayers) {
423
G_RemoveRandomBot( TEAM_RED );
426
humanplayers = G_CountHumanPlayers( TEAM_BLUE );
427
botplayers = G_CountBotPlayers( TEAM_BLUE );
429
if (humanplayers + botplayers < minplayers) {
430
G_AddRandomBot( TEAM_BLUE );
431
} else if (humanplayers + botplayers > minplayers && botplayers) {
432
G_RemoveRandomBot( TEAM_BLUE );
435
else if (g_gametype.integer == GT_TOURNAMENT ) {
436
if (minplayers >= g_maxclients.integer) {
437
minplayers = g_maxclients.integer-1;
439
humanplayers = G_CountHumanPlayers( -1 );
440
botplayers = G_CountBotPlayers( -1 );
442
if (humanplayers + botplayers < minplayers) {
443
G_AddRandomBot( TEAM_FREE );
444
} else if (humanplayers + botplayers > minplayers && botplayers) {
445
// try to remove spectators first
446
if (!G_RemoveRandomBot( TEAM_SPECTATOR )) {
447
// just remove the bot that is playing
448
G_RemoveRandomBot( -1 );
452
else if (g_gametype.integer == GT_FFA) {
453
if (minplayers >= g_maxclients.integer) {
454
minplayers = g_maxclients.integer-1;
456
humanplayers = G_CountHumanPlayers( TEAM_FREE );
457
botplayers = G_CountBotPlayers( TEAM_FREE );
459
if (humanplayers + botplayers < minplayers) {
460
G_AddRandomBot( TEAM_FREE );
461
} else if (humanplayers + botplayers > minplayers && botplayers) {
462
G_RemoveRandomBot( TEAM_FREE );
472
void G_CheckBotSpawn( void ) {
474
char userinfo[MAX_INFO_VALUE];
476
G_CheckMinimumPlayers();
478
for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
479
if( !botSpawnQueue[n].spawnTime ) {
482
if ( botSpawnQueue[n].spawnTime > level.time ) {
485
ClientBegin( botSpawnQueue[n].clientNum );
486
botSpawnQueue[n].spawnTime = 0;
488
if( g_gametype.integer == GT_SINGLE_PLAYER ) {
489
trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) );
490
PlayerIntroSound( Info_ValueForKey (userinfo, "model") );
501
static void AddBotToSpawnQueue( int clientNum, int delay ) {
504
for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
505
if( !botSpawnQueue[n].spawnTime ) {
506
botSpawnQueue[n].spawnTime = level.time + delay;
507
botSpawnQueue[n].clientNum = clientNum;
512
G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" );
513
ClientBegin( clientNum );
519
G_RemoveQueuedBotBegin
521
Called on client disconnect to make sure the delayed spawn
522
doesn't happen on a freed index
525
void G_RemoveQueuedBotBegin( int clientNum ) {
528
for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
529
if( botSpawnQueue[n].clientNum == clientNum ) {
530
botSpawnQueue[n].spawnTime = 0;
542
qboolean G_BotConnect( int clientNum, qboolean restart ) {
543
bot_settings_t settings;
544
char userinfo[MAX_INFO_STRING];
546
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
548
Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) );
549
settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) );
550
Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) );
552
if (!BotAISetupClient( clientNum, &settings, restart )) {
553
trap_DropClient( clientNum, "BotAISetupClient failed" );
566
static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) {
575
char userinfo[MAX_INFO_STRING];
577
// get the botinfo from bots.txt
578
botinfo = G_GetBotInfoByName( name );
580
G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name );
584
// create the bot's userinfo
587
botname = Info_ValueForKey( botinfo, "funname" );
589
botname = Info_ValueForKey( botinfo, "name" );
591
// check for an alternative name
592
if (altname && altname[0]) {
595
Info_SetValueForKey( userinfo, "name", botname );
596
Info_SetValueForKey( userinfo, "rate", "25000" );
597
Info_SetValueForKey( userinfo, "snaps", "20" );
598
Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) );
600
if ( skill >= 1 && skill < 2 ) {
601
Info_SetValueForKey( userinfo, "handicap", "50" );
603
else if ( skill >= 2 && skill < 3 ) {
604
Info_SetValueForKey( userinfo, "handicap", "70" );
606
else if ( skill >= 3 && skill < 4 ) {
607
Info_SetValueForKey( userinfo, "handicap", "90" );
611
model = Info_ValueForKey( botinfo, key );
613
model = "visor/default";
615
Info_SetValueForKey( userinfo, key, model );
617
Info_SetValueForKey( userinfo, key, model );
620
headmodel = Info_ValueForKey( botinfo, key );
624
Info_SetValueForKey( userinfo, key, headmodel );
625
key = "team_headmodel";
626
Info_SetValueForKey( userinfo, key, headmodel );
629
s = Info_ValueForKey( botinfo, key );
633
Info_SetValueForKey( userinfo, "sex", s );
636
s = Info_ValueForKey( botinfo, key );
640
Info_SetValueForKey( userinfo, key, s );
643
s = Info_ValueForKey( botinfo, key );
647
Info_SetValueForKey( userinfo, key, s );
649
s = Info_ValueForKey(botinfo, "aifile");
651
trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" );
655
// have the server allocate a client slot
656
clientNum = trap_BotAllocateClient();
657
if ( clientNum == -1 ) {
658
G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" );
659
G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" );
663
// initialize the bot settings
664
if( !team || !*team ) {
665
if( g_gametype.integer >= GT_TEAM ) {
666
if( PickTeam(clientNum) == TEAM_RED) {
677
Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) );
678
Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) );
679
Info_SetValueForKey( userinfo, "team", team );
681
bot = &g_entities[ clientNum ];
682
bot->r.svFlags |= SVF_BOT;
685
// register the userinfo
686
trap_SetUserinfo( clientNum, userinfo );
688
// have it connect to the game as a normal client
689
if ( ClientConnect( clientNum, qtrue, qtrue ) ) {
694
ClientBegin( clientNum );
698
AddBotToSpawnQueue( clientNum, delay );
707
void Svcmd_AddBot_f( void ) {
710
char name[MAX_TOKEN_CHARS];
711
char altname[MAX_TOKEN_CHARS];
712
char string[MAX_TOKEN_CHARS];
713
char team[MAX_TOKEN_CHARS];
716
if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
721
trap_Argv( 1, name, sizeof( name ) );
723
trap_Printf( "Usage: Addbot <botname> [skill 1-5] [team] [msec delay] [altname]\n" );
728
trap_Argv( 2, string, sizeof( string ) );
733
skill = atof( string );
737
trap_Argv( 3, team, sizeof( team ) );
740
trap_Argv( 4, string, sizeof( string ) );
745
delay = atoi( string );
749
trap_Argv( 5, altname, sizeof( altname ) );
751
G_AddBot( name, skill, team, delay, altname );
753
// if this was issued during gameplay and we are playing locally,
754
// go ahead and load the bot's media immediately
755
if ( level.time - level.startTime > 1000 &&
756
trap_Cvar_VariableIntegerValue( "cl_running" ) ) {
757
trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo
766
void Svcmd_BotList_f( void ) {
768
char name[MAX_TOKEN_CHARS];
769
char funname[MAX_TOKEN_CHARS];
770
char model[MAX_TOKEN_CHARS];
771
char aifile[MAX_TOKEN_CHARS];
773
trap_Printf("^1name model aifile funname\n");
774
for (i = 0; i < g_numBots; i++) {
775
strcpy(name, Info_ValueForKey( g_botInfos[i], "name" ));
777
strcpy(name, "UnnamedPlayer");
779
strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" ));
783
strcpy(model, Info_ValueForKey( g_botInfos[i], "model" ));
785
strcpy(model, "visor/default");
787
strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile"));
789
strcpy(aifile, "bots/default_c.c");
791
trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname));
801
static void G_SpawnBots( char *botList, int baseDelay ) {
806
char bots[MAX_INFO_VALUE];
812
skill = trap_Cvar_VariableValue( "g_spSkill" );
814
trap_Cvar_Set( "g_spSkill", "1" );
817
else if ( skill > 5 ) {
818
trap_Cvar_Set( "g_spSkill", "5" );
822
Q_strncpyz( bots, botList, sizeof(bots) );
827
while( *p && *p == ' ' ) {
834
// mark start of bot name
837
// skip until space of null
838
while( *p && *p != ' ' ) {
845
// we must add the bot this way, calling G_AddBot directly at this stage
847
trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) );
849
delay += BOT_BEGIN_DELAY_INCREMENT;
859
static void G_LoadBotsFromFile( char *filename ) {
862
char buf[MAX_BOTS_TEXT];
864
len = trap_FS_FOpenFile( filename, &f, FS_READ );
866
trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
869
if ( len >= MAX_BOTS_TEXT ) {
870
trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
871
trap_FS_FCloseFile( f );
875
trap_FS_Read( buf, len, f );
877
trap_FS_FCloseFile( f );
879
g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] );
887
static void G_LoadBots( void ) {
896
if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
902
trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
903
if( *botsFile.string ) {
904
G_LoadBotsFromFile(botsFile.string);
907
G_LoadBotsFromFile("scripts/bots.txt");
910
// get all bots from .bot files
911
numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
913
for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
914
dirlen = strlen(dirptr);
915
strcpy(filename, "scripts/");
916
strcat(filename, dirptr);
917
G_LoadBotsFromFile(filename);
919
trap_Printf( va( "%i bots parsed\n", g_numBots ) );
929
char *G_GetBotInfoByNumber( int num ) {
930
if( num < 0 || num >= g_numBots ) {
931
trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
934
return g_botInfos[num];
943
char *G_GetBotInfoByName( const char *name ) {
947
for ( n = 0; n < g_numBots ; n++ ) {
948
value = Info_ValueForKey( g_botInfos[n], "name" );
949
if ( !Q_stricmp( value, name ) ) {
950
return g_botInfos[n];
962
void G_InitBots( qboolean restart ) {
965
const char *arenainfo;
969
char serverinfo[MAX_INFO_STRING];
974
trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO );
976
if( g_gametype.integer == GT_SINGLE_PLAYER ) {
977
trap_GetServerinfo( serverinfo, sizeof(serverinfo) );
978
Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) );
979
arenainfo = G_GetArenaInfoByMap( map );
984
strValue = Info_ValueForKey( arenainfo, "fraglimit" );
985
fragLimit = atoi( strValue );
987
trap_Cvar_Set( "fraglimit", strValue );
990
trap_Cvar_Set( "fraglimit", "0" );
993
strValue = Info_ValueForKey( arenainfo, "timelimit" );
994
timeLimit = atoi( strValue );
996
trap_Cvar_Set( "timelimit", strValue );
999
trap_Cvar_Set( "timelimit", "0" );
1002
if ( !fragLimit && !timeLimit ) {
1003
trap_Cvar_Set( "fraglimit", "10" );
1004
trap_Cvar_Set( "timelimit", "0" );
1007
basedelay = BOT_BEGIN_DELAY_BASE;
1008
strValue = Info_ValueForKey( arenainfo, "special" );
1009
if( Q_stricmp( strValue, "training" ) == 0 ) {
1014
G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay );