codemp/game/g_bot.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 // g_bot.c
00004 
00005 #include "g_local.h"
00006 
00007 
00008 static int              g_numBots;
00009 static char             *g_botInfos[MAX_BOTS];
00010 
00011 
00012 int                             g_numArenas;
00013 static char             *g_arenaInfos[MAX_ARENAS];
00014 
00015 
00016 #define BOT_BEGIN_DELAY_BASE            2000
00017 #define BOT_BEGIN_DELAY_INCREMENT       1500
00018 
00019 #define BOT_SPAWN_QUEUE_DEPTH   16
00020 
00021 typedef struct {
00022         int             clientNum;
00023         int             spawnTime;
00024 } botSpawnQueue_t;
00025 
00026 //static int                    botBeginDelay = 0;  // bk001206 - unused, init
00027 static botSpawnQueue_t  botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
00028 
00029 vmCvar_t bot_minplayers;
00030 
00031 extern gentity_t        *podium1;
00032 extern gentity_t        *podium2;
00033 extern gentity_t        *podium3;
00034 
00035 #include "../namespace_begin.h"
00036 float trap_Cvar_VariableValue( const char *var_name ) {
00037         char buf[128];
00038 
00039         trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf));
00040         return atof(buf);
00041 }
00042 #include "../namespace_end.h"
00043 
00044 
00045 /*
00046 ===============
00047 G_ParseInfos
00048 ===============
00049 */
00050 int G_ParseInfos( char *buf, int max, char *infos[] ) {
00051         char    *token;
00052         int             count;
00053         char    key[MAX_TOKEN_CHARS];
00054         char    info[MAX_INFO_STRING];
00055 
00056         count = 0;
00057 
00058         while ( 1 ) {
00059                 token = COM_Parse( (const char **)(&buf) );
00060                 if ( !token[0] ) {
00061                         break;
00062                 }
00063                 if ( strcmp( token, "{" ) ) {
00064                         Com_Printf( "Missing { in info file\n" );
00065                         break;
00066                 }
00067 
00068                 if ( count == max ) {
00069                         Com_Printf( "Max infos exceeded\n" );
00070                         break;
00071                 }
00072 
00073                 info[0] = '\0';
00074                 while ( 1 ) {
00075                         token = COM_ParseExt( (const char **)(&buf), qtrue );
00076                         if ( !token[0] ) {
00077                                 Com_Printf( "Unexpected end of info file\n" );
00078                                 break;
00079                         }
00080                         if ( !strcmp( token, "}" ) ) {
00081                                 break;
00082                         }
00083                         Q_strncpyz( key, token, sizeof( key ) );
00084 
00085                         token = COM_ParseExt( (const char **)(&buf), qfalse );
00086                         if ( !token[0] ) {
00087                                 strcpy( token, "<NULL>" );
00088                         }
00089                         Info_SetValueForKey( info, key, token );
00090                 }
00091                 //NOTE: extra space for arena number
00092                 infos[count] = (char *) G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
00093                 if (infos[count]) {
00094                         strcpy(infos[count], info);
00095                         count++;
00096                 }
00097         }
00098         return count;
00099 }
00100 
00101 /*
00102 ===============
00103 G_LoadArenasFromFile
00104 ===============
00105 */
00106 static void G_LoadArenasFromFile( char *filename ) {
00107         int                             len;
00108         fileHandle_t    f;
00109         char                    buf[MAX_ARENAS_TEXT];
00110 
00111         len = trap_FS_FOpenFile( filename, &f, FS_READ );
00112         if ( !f ) {
00113                 trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
00114                 return;
00115         }
00116         if ( len >= MAX_ARENAS_TEXT ) {
00117                 trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
00118                 trap_FS_FCloseFile( f );
00119                 return;
00120         }
00121 
00122         trap_FS_Read( buf, len, f );
00123         buf[len] = 0;
00124         trap_FS_FCloseFile( f );
00125 
00126         g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] );
00127 }
00128 
00129 int G_GetMapTypeBits(char *type)
00130 {
00131         int typeBits = 0;
00132 
00133         if( *type ) {
00134                 if( strstr( type, "ffa" ) ) {
00135                         typeBits |= (1 << GT_FFA);
00136                         typeBits |= (1 << GT_TEAM);
00137                 }
00138                 if( strstr( type, "holocron" ) ) {
00139                         typeBits |= (1 << GT_HOLOCRON);
00140                 }
00141                 if( strstr( type, "jedimaster" ) ) {
00142                         typeBits |= (1 << GT_JEDIMASTER);
00143                 }
00144                 if( strstr( type, "duel" ) ) {
00145                         typeBits |= (1 << GT_DUEL);
00146                         typeBits |= (1 << GT_POWERDUEL);
00147                 }
00148                 if( strstr( type, "powerduel" ) ) {
00149                         typeBits |= (1 << GT_DUEL);
00150                         typeBits |= (1 << GT_POWERDUEL);
00151                 }
00152                 if( strstr( type, "siege" ) ) {
00153                         typeBits |= (1 << GT_SIEGE);
00154                 }
00155                 if( strstr( type, "ctf" ) ) {
00156                         typeBits |= (1 << GT_CTF);
00157                 }
00158                 if( strstr( type, "cty" ) ) {
00159                         typeBits |= (1 << GT_CTY);
00160                 }
00161         } else {
00162                 typeBits |= (1 << GT_FFA);
00163         }
00164 
00165         return typeBits;
00166 }
00167 
00168 qboolean G_DoesMapSupportGametype(const char *mapname, int gametype)
00169 {
00170         int                     typeBits = 0;
00171         int                     thisLevel = -1;
00172         int                     n = 0;
00173         char            *type = NULL;
00174 
00175         if (!g_arenaInfos[0])
00176         {
00177                 return qfalse;
00178         }
00179 
00180         if (!mapname || !mapname[0])
00181         {
00182                 return qfalse;
00183         }
00184 
00185         for( n = 0; n < g_numArenas; n++ )
00186         {
00187                 type = Info_ValueForKey( g_arenaInfos[n], "map" );
00188 
00189                 if (Q_stricmp(mapname, type) == 0)
00190                 {
00191                         thisLevel = n;
00192                         break;
00193                 }
00194         }
00195 
00196         if (thisLevel == -1)
00197         {
00198                 return qfalse;
00199         }
00200 
00201         type = Info_ValueForKey(g_arenaInfos[thisLevel], "type");
00202 
00203         typeBits = G_GetMapTypeBits(type);
00204         if (typeBits & (1 << gametype))
00205         { //the map in question supports the gametype in question, so..
00206                 return qtrue;
00207         }
00208 
00209         return qfalse;
00210 }
00211 
00212 //rww - auto-obtain nextmap. I could've sworn Q3 had something like this, but I guess not.
00213 const char *G_RefreshNextMap(int gametype, qboolean forced)
00214 {
00215         int                     typeBits = 0;
00216         int                     thisLevel = 0;
00217         int                     desiredMap = 0;
00218         int                     n = 0;
00219         char            *type = NULL;
00220         qboolean        loopingUp = qfalse;
00221         vmCvar_t        mapname;
00222 
00223         if (!g_autoMapCycle.integer && !forced)
00224         {
00225                 return NULL;
00226         }
00227 
00228         if (!g_arenaInfos[0])
00229         {
00230                 return NULL;
00231         }
00232 
00233         trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
00234         for( n = 0; n < g_numArenas; n++ )
00235         {
00236                 type = Info_ValueForKey( g_arenaInfos[n], "map" );
00237 
00238                 if (Q_stricmp(mapname.string, type) == 0)
00239                 {
00240                         thisLevel = n;
00241                         break;
00242                 }
00243         }
00244 
00245         desiredMap = thisLevel;
00246 
00247         n = thisLevel+1;
00248         while (n != thisLevel)
00249         { //now cycle through the arena list and find the next map that matches the gametype we're in
00250                 if (!g_arenaInfos[n] || n >= g_numArenas)
00251                 {
00252                         if (loopingUp)
00253                         { //this shouldn't happen, but if it does we have a null entry break in the arena file
00254                           //if this is the case just break out of the loop instead of sticking in an infinite loop
00255                                 break;
00256                         }
00257                         n = 0;
00258                         loopingUp = qtrue;
00259                 }
00260 
00261                 type = Info_ValueForKey(g_arenaInfos[n], "type");
00262                 
00263                 typeBits = G_GetMapTypeBits(type);
00264                 if (typeBits & (1 << gametype))
00265                 {
00266                         desiredMap = n;
00267                         break;
00268                 }
00269 
00270                 n++;
00271         }
00272 
00273         if (desiredMap == thisLevel)
00274         { //If this is the only level for this game mode or we just can't find a map for this game mode, then nextmap
00275           //will always restart.
00276                 trap_Cvar_Set( "nextmap", "map_restart 0");
00277         }
00278         else
00279         { //otherwise we have a valid nextmap to cycle to, so use it.
00280                 type = Info_ValueForKey( g_arenaInfos[desiredMap], "map" );
00281                 trap_Cvar_Set( "nextmap", va("map %s", type));
00282         }
00283 
00284         return Info_ValueForKey( g_arenaInfos[desiredMap], "map" );
00285 }
00286 
00287 /*
00288 ===============
00289 G_LoadArenas
00290 ===============
00291 */
00292 static void G_LoadArenas( void ) {
00293         int                     numdirs;
00294         char            filename[128];
00295         char            dirlist[1024];
00296         char*           dirptr;
00297         int                     i, n;
00298         int                     dirlen;
00299 
00300         g_numArenas = 0;
00301 
00302         // get all arenas from .arena files
00303         numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
00304         dirptr  = dirlist;
00305         for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
00306                 dirlen = strlen(dirptr);
00307                 strcpy(filename, "scripts/");
00308                 strcat(filename, dirptr);
00309                 G_LoadArenasFromFile(filename);
00310         }
00311 //      trap_Printf( va( "%i arenas parsed\n", g_numArenas ) );
00312         
00313         for( n = 0; n < g_numArenas; n++ ) {
00314                 Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
00315         }
00316 
00317         G_RefreshNextMap(g_gametype.integer, qfalse);
00318 }
00319 
00320 
00321 /*
00322 ===============
00323 G_GetArenaInfoByNumber
00324 ===============
00325 */
00326 const char *G_GetArenaInfoByMap( const char *map ) {
00327         int                     n;
00328 
00329         for( n = 0; n < g_numArenas; n++ ) {
00330                 if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) {
00331                         return g_arenaInfos[n];
00332                 }
00333         }
00334 
00335         return NULL;
00336 }
00337 
00338 #if 0
00339 /*
00340 =================
00341 PlayerIntroSound
00342 =================
00343 */
00344 static void PlayerIntroSound( const char *modelAndSkin ) {
00345         char    model[MAX_QPATH];
00346         char    *skin;
00347 
00348         Q_strncpyz( model, modelAndSkin, sizeof(model) );
00349         skin = Q_strrchr( model, '/' );
00350         if ( skin ) {
00351                 *skin++ = '\0';
00352         }
00353         else {
00354                 skin = model;
00355         }
00356 
00357         if( Q_stricmp( skin, "default" ) == 0 ) {
00358                 skin = model;
00359         }
00360 
00361         trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) );
00362 }
00363 #endif
00364 
00365 /*
00366 ===============
00367 G_AddRandomBot
00368 ===============
00369 */
00370 void G_AddRandomBot( int team ) {
00371         int             i, n, num;
00372         float   skill;
00373         char    *value, netname[36], *teamstr;
00374         gclient_t       *cl;
00375 
00376         num = 0;
00377         for ( n = 0; n < g_numBots ; n++ ) {
00378                 value = Info_ValueForKey( g_botInfos[n], "name" );
00379                 //
00380                 for ( i=0 ; i< g_maxclients.integer ; i++ ) {
00381                         cl = level.clients + i;
00382                         if ( cl->pers.connected != CON_CONNECTED ) {
00383                                 continue;
00384                         }
00385                         if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
00386                                 continue;
00387                         }
00388                         if (g_gametype.integer == GT_SIEGE)
00389                         {
00390                                 if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) {
00391                                         continue;
00392                                 }
00393                         }
00394                         else
00395                         {
00396                                 if ( team >= 0 && cl->sess.sessionTeam != team ) {
00397                                         continue;
00398                                 }
00399                         }
00400                         if ( !Q_stricmp( value, cl->pers.netname ) ) {
00401                                 break;
00402                         }
00403                 }
00404                 if (i >= g_maxclients.integer) {
00405                         num++;
00406                 }
00407         }
00408         num = random() * num;
00409         for ( n = 0; n < g_numBots ; n++ ) {
00410                 value = Info_ValueForKey( g_botInfos[n], "name" );
00411                 //
00412                 for ( i=0 ; i< g_maxclients.integer ; i++ ) {
00413                         cl = level.clients + i;
00414                         if ( cl->pers.connected != CON_CONNECTED ) {
00415                                 continue;
00416                         }
00417                         if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
00418                                 continue;
00419                         }
00420                         if (g_gametype.integer == GT_SIEGE)
00421                         {
00422                                 if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) {
00423                                         continue;
00424                                 }
00425                         }
00426                         else
00427                         {
00428                                 if ( team >= 0 && cl->sess.sessionTeam != team ) {
00429                                         continue;
00430                                 }
00431                         }
00432                         if ( !Q_stricmp( value, cl->pers.netname ) ) {
00433                                 break;
00434                         }
00435                 }
00436                 if (i >= g_maxclients.integer) {
00437                         num--;
00438                         if (num <= 0) {
00439                                 skill = trap_Cvar_VariableValue( "g_spSkill" );
00440                                 if (team == TEAM_RED) teamstr = "red";
00441                                 else if (team == TEAM_BLUE) teamstr = "blue";
00442                                 else teamstr = "";
00443                                 strncpy(netname, value, sizeof(netname)-1);
00444                                 netname[sizeof(netname)-1] = '\0';
00445                                 Q_CleanStr(netname);
00446                                 trap_SendConsoleCommand( EXEC_INSERT, va("addbot \"%s\" %f %s %i\n", netname, skill, teamstr, 0) );
00447                                 return;
00448                         }
00449                 }
00450         }
00451 }
00452 
00453 /*
00454 ===============
00455 G_RemoveRandomBot
00456 ===============
00457 */
00458 int G_RemoveRandomBot( int team ) {
00459         int i;
00460         char netname[36];
00461         gclient_t       *cl;
00462 
00463         for ( i=0 ; i< g_maxclients.integer ; i++ ) {
00464                 cl = level.clients + i;
00465                 if ( cl->pers.connected != CON_CONNECTED ) {
00466                         continue;
00467                 }
00468                 if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
00469                         continue;
00470                 }
00471                 if (g_gametype.integer == GT_SIEGE)
00472                 {
00473                         if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) {
00474                                 continue;
00475                         }
00476                 }
00477                 else
00478                 {
00479                         if ( team >= 0 && cl->sess.sessionTeam != team ) {
00480                                 continue;
00481                         }
00482                 }
00483                 strcpy(netname, cl->pers.netname);
00484                 Q_CleanStr(netname);
00485                 trap_SendConsoleCommand( EXEC_INSERT, va("kick \"%s\"\n", netname) );
00486                 return qtrue;
00487         }
00488         return qfalse;
00489 }
00490 
00491 /*
00492 ===============
00493 G_CountHumanPlayers
00494 ===============
00495 */
00496 int G_CountHumanPlayers( int team ) {
00497         int i, num;
00498         gclient_t       *cl;
00499 
00500         num = 0;
00501         for ( i=0 ; i< g_maxclients.integer ; i++ ) {
00502                 cl = level.clients + i;
00503                 if ( cl->pers.connected != CON_CONNECTED ) {
00504                         continue;
00505                 }
00506                 if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
00507                         continue;
00508                 }
00509                 if ( team >= 0 && cl->sess.sessionTeam != team ) {
00510                         continue;
00511                 }
00512                 num++;
00513         }
00514         return num;
00515 }
00516 
00517 /*
00518 ===============
00519 G_CountBotPlayers
00520 ===============
00521 */
00522 int G_CountBotPlayers( int team ) {
00523         int i, n, num;
00524         gclient_t       *cl;
00525 
00526         num = 0;
00527         for ( i=0 ; i< g_maxclients.integer ; i++ ) {
00528                 cl = level.clients + i;
00529                 if ( cl->pers.connected != CON_CONNECTED ) {
00530                         continue;
00531                 }
00532                 if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
00533                         continue;
00534                 }
00535                 if (g_gametype.integer == GT_SIEGE)
00536                 {
00537                         if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) {
00538                                 continue;
00539                         }
00540                 }
00541                 else
00542                 {
00543                         if ( team >= 0 && cl->sess.sessionTeam != team ) {
00544                                 continue;
00545                         }
00546                 }
00547                 num++;
00548         }
00549         for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
00550                 if( !botSpawnQueue[n].spawnTime ) {
00551                         continue;
00552                 }
00553                 if ( botSpawnQueue[n].spawnTime > level.time ) {
00554                         continue;
00555                 }
00556                 num++;
00557         }
00558         return num;
00559 }
00560 
00561 /*
00562 ===============
00563 G_CheckMinimumPlayers
00564 ===============
00565 */
00566 void G_CheckMinimumPlayers( void ) {
00567         int minplayers;
00568         int humanplayers, botplayers;
00569         static int checkminimumplayers_time;
00570 
00571         if (g_gametype.integer == GT_SIEGE)
00572         {
00573                 return;
00574         }
00575 
00576         if (level.intermissiontime) return;
00577         //only check once each 10 seconds
00578         if (checkminimumplayers_time > level.time - 10000) {
00579                 return;
00580         }
00581         checkminimumplayers_time = level.time;
00582         trap_Cvar_Update(&bot_minplayers);
00583         minplayers = bot_minplayers.integer;
00584         if (minplayers <= 0) return;
00585 
00586         if (minplayers > g_maxclients.integer)
00587         {
00588                 minplayers = g_maxclients.integer;
00589         }
00590 
00591         humanplayers = G_CountHumanPlayers( -1 );
00592         botplayers = G_CountBotPlayers( -1 );
00593 
00594         if ((humanplayers+botplayers) < minplayers)
00595         {
00596                 G_AddRandomBot(-1);
00597         }
00598         else if ((humanplayers+botplayers) > minplayers && botplayers)
00599         {
00600                 // try to remove spectators first
00601                 if (!G_RemoveRandomBot(TEAM_SPECTATOR))
00602                 {
00603                         // just remove the bot that is playing
00604                         G_RemoveRandomBot(-1);
00605                 }
00606         }
00607 
00608         /*
00609         if (g_gametype.integer >= GT_TEAM) {
00610                 int humanplayers2, botplayers2;
00611                 if (minplayers >= g_maxclients.integer / 2) {
00612                         minplayers = (g_maxclients.integer / 2) -1;
00613                 }
00614 
00615                 humanplayers = G_CountHumanPlayers( TEAM_RED );
00616                 botplayers = G_CountBotPlayers( TEAM_RED );
00617                 humanplayers2 = G_CountHumanPlayers( TEAM_BLUE );
00618                 botplayers2 = G_CountBotPlayers( TEAM_BLUE );
00619                 //
00620                 if ((humanplayers+botplayers+humanplayers2+botplayers) < minplayers)
00621                 {
00622                         if ((humanplayers+botplayers) < (humanplayers2+botplayers2))
00623                         {
00624                                 G_AddRandomBot( TEAM_RED );
00625                         }
00626                         else
00627                         {
00628                                 G_AddRandomBot( TEAM_BLUE );
00629                         }
00630                 }
00631                 else if ((humanplayers+botplayers+humanplayers2+botplayers) > minplayers && botplayers)
00632                 {
00633                         if ((humanplayers+botplayers) < (humanplayers2+botplayers2))
00634                         {
00635                                 G_RemoveRandomBot( TEAM_BLUE );
00636                         }
00637                         else
00638                         {
00639                                 G_RemoveRandomBot( TEAM_RED );
00640                         }
00641                 }
00642         }
00643         else if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) {
00644                 if (minplayers >= g_maxclients.integer) {
00645                         minplayers = g_maxclients.integer-1;
00646                 }
00647                 humanplayers = G_CountHumanPlayers( -1 );
00648                 botplayers = G_CountBotPlayers( -1 );
00649                 //
00650                 if (humanplayers + botplayers < minplayers) {
00651                         G_AddRandomBot( TEAM_FREE );
00652                 } else if (humanplayers + botplayers > minplayers && botplayers) {
00653                         // try to remove spectators first
00654                         if (!G_RemoveRandomBot( TEAM_SPECTATOR )) {
00655                                 // just remove the bot that is playing
00656                                 G_RemoveRandomBot( -1 );
00657                         }
00658                 }
00659         }
00660         else if (g_gametype.integer == GT_FFA) {
00661                 if (minplayers >= g_maxclients.integer) {
00662                         minplayers = g_maxclients.integer-1;
00663                 }
00664                 humanplayers = G_CountHumanPlayers( TEAM_FREE );
00665                 botplayers = G_CountBotPlayers( TEAM_FREE );
00666                 //
00667                 if (humanplayers + botplayers < minplayers) {
00668                         G_AddRandomBot( TEAM_FREE );
00669                 } else if (humanplayers + botplayers > minplayers && botplayers) {
00670                         G_RemoveRandomBot( TEAM_FREE );
00671                 }
00672         }
00673         else if (g_gametype.integer == GT_HOLOCRON || g_gametype.integer == GT_JEDIMASTER) {
00674                 if (minplayers >= g_maxclients.integer) {
00675                         minplayers = g_maxclients.integer-1;
00676                 }
00677                 humanplayers = G_CountHumanPlayers( TEAM_FREE );
00678                 botplayers = G_CountBotPlayers( TEAM_FREE );
00679                 //
00680                 if (humanplayers + botplayers < minplayers) {
00681                         G_AddRandomBot( TEAM_FREE );
00682                 } else if (humanplayers + botplayers > minplayers && botplayers) {
00683                         G_RemoveRandomBot( TEAM_FREE );
00684                 }
00685         }
00686         */
00687 }
00688 
00689 /*
00690 ===============
00691 G_CheckBotSpawn
00692 ===============
00693 */
00694 void G_CheckBotSpawn( void ) {
00695         int             n;
00696 
00697         G_CheckMinimumPlayers();
00698 
00699         for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
00700                 if( !botSpawnQueue[n].spawnTime ) {
00701                         continue;
00702                 }
00703                 if ( botSpawnQueue[n].spawnTime > level.time ) {
00704                         continue;
00705                 }
00706                 ClientBegin( botSpawnQueue[n].clientNum, qfalse );
00707                 botSpawnQueue[n].spawnTime = 0;
00708 
00709                 /*
00710                 if( g_gametype.integer == GT_SINGLE_PLAYER ) {
00711                         trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) );
00712                         PlayerIntroSound( Info_ValueForKey (userinfo, "model") );
00713                 }
00714                 */
00715         }
00716 }
00717 
00718 
00719 /*
00720 ===============
00721 AddBotToSpawnQueue
00722 ===============
00723 */
00724 static void AddBotToSpawnQueue( int clientNum, int delay ) {
00725         int             n;
00726 
00727         for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
00728                 if( !botSpawnQueue[n].spawnTime ) {
00729                         botSpawnQueue[n].spawnTime = level.time + delay;
00730                         botSpawnQueue[n].clientNum = clientNum;
00731                         return;
00732                 }
00733         }
00734 
00735         G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" );
00736         ClientBegin( clientNum, qfalse );
00737 }
00738 
00739 
00740 /*
00741 ===============
00742 G_RemoveQueuedBotBegin
00743 
00744 Called on client disconnect to make sure the delayed spawn
00745 doesn't happen on a freed index
00746 ===============
00747 */
00748 void G_RemoveQueuedBotBegin( int clientNum ) {
00749         int             n;
00750 
00751         for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
00752                 if( botSpawnQueue[n].clientNum == clientNum ) {
00753                         botSpawnQueue[n].spawnTime = 0;
00754                         return;
00755                 }
00756         }
00757 }
00758 
00759 
00760 /*
00761 ===============
00762 G_BotConnect
00763 ===============
00764 */
00765 qboolean G_BotConnect( int clientNum, qboolean restart ) {
00766         bot_settings_t  settings;
00767         char                    userinfo[MAX_INFO_STRING];
00768 
00769         trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
00770 
00771         Q_strncpyz( settings.personalityfile, Info_ValueForKey( userinfo, "personality" ), sizeof(settings.personalityfile) );
00772         settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) );
00773         Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) );
00774 
00775         if (!BotAISetupClient( clientNum, &settings, restart )) {
00776                 trap_DropClient( clientNum, "BotAISetupClient failed" );
00777                 return qfalse;
00778         }
00779 
00780         return qtrue;
00781 }
00782 
00783 
00784 /*
00785 ===============
00786 G_AddBot
00787 ===============
00788 */
00789 static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) {
00790         int                             clientNum;
00791         char                    *botinfo;
00792         gentity_t               *bot;
00793         char                    *key;
00794         char                    *s;
00795         char                    *botname;
00796         char                    *model;
00797 //      char                    *headmodel;
00798         char                    userinfo[MAX_INFO_STRING];
00799         int                             preTeam = 0;
00800 
00801         // get the botinfo from bots.txt
00802         botinfo = G_GetBotInfoByName( name );
00803         if ( !botinfo ) {
00804                 G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name );
00805                 return;
00806         }
00807 
00808         // create the bot's userinfo
00809         userinfo[0] = '\0';
00810 
00811         botname = Info_ValueForKey( botinfo, "funname" );
00812         if( !botname[0] ) {
00813                 botname = Info_ValueForKey( botinfo, "name" );
00814         }
00815         // check for an alternative name
00816         if (altname && altname[0]) {
00817                 botname = altname;
00818         }
00819         Info_SetValueForKey( userinfo, "name", botname );
00820         Info_SetValueForKey( userinfo, "rate", "25000" );
00821         Info_SetValueForKey( userinfo, "snaps", "20" );
00822         Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) );
00823 
00824         if ( skill >= 1 && skill < 2 ) {
00825                 Info_SetValueForKey( userinfo, "handicap", "50" );
00826         }
00827         else if ( skill >= 2 && skill < 3 ) {
00828                 Info_SetValueForKey( userinfo, "handicap", "70" );
00829         }
00830         else if ( skill >= 3 && skill < 4 ) {
00831                 Info_SetValueForKey( userinfo, "handicap", "90" );
00832         }
00833 
00834         key = "model";
00835         model = Info_ValueForKey( botinfo, key );
00836         if ( !*model ) {
00837                 model = "kyle/default";
00838         }
00839         Info_SetValueForKey( userinfo, key, model );
00840 
00841 /*      key = "headmodel";
00842         headmodel = Info_ValueForKey( botinfo, key );
00843         if ( !*headmodel ) {
00844                 headmodel = model;
00845         }
00846         Info_SetValueForKey( userinfo, key, headmodel );
00847         key = "team_headmodel";
00848         Info_SetValueForKey( userinfo, key, headmodel );
00849 */
00850         key = "gender";
00851         s = Info_ValueForKey( botinfo, key );
00852         if ( !*s ) {
00853                 s = "male";
00854         }
00855         Info_SetValueForKey( userinfo, "sex", s );
00856 
00857         key = "color1";
00858         s = Info_ValueForKey( botinfo, key );
00859         if ( !*s ) {
00860                 s = "4";
00861         }
00862         Info_SetValueForKey( userinfo, key, s );
00863 
00864         key = "color2";
00865         s = Info_ValueForKey( botinfo, key );
00866         if ( !*s ) {
00867                 s = "4";
00868         }
00869         Info_SetValueForKey( userinfo, key, s );
00870 
00871         key = "saber1";
00872         s = Info_ValueForKey( botinfo, key );
00873         if ( !*s ) {
00874                 s = "single_1";
00875         }
00876         Info_SetValueForKey( userinfo, key, s );
00877 
00878         key = "saber2";
00879         s = Info_ValueForKey( botinfo, key );
00880         if ( !*s ) {
00881                 s = "none";
00882         }
00883         Info_SetValueForKey( userinfo, key, s );
00884 
00885         s = Info_ValueForKey(botinfo, "personality");
00886         if (!*s )
00887         {
00888                 Info_SetValueForKey( userinfo, "personality", "botfiles/default.jkb" );
00889         }
00890         else
00891         {
00892                 Info_SetValueForKey( userinfo, "personality", s );
00893         }
00894 
00895         // have the server allocate a client slot
00896         clientNum = trap_BotAllocateClient();
00897         if ( clientNum == -1 ) {
00898 //              G_Printf( S_COLOR_RED "Unable to add bot.  All player slots are in use.\n" );
00899 //              G_Printf( S_COLOR_RED "Start server with more 'open' slots.\n" );
00900                 trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "UNABLE_TO_ADD_BOT")));
00901                 return;
00902         }
00903 
00904         // initialize the bot settings
00905         if( !team || !*team ) {
00906                 if( g_gametype.integer >= GT_TEAM ) {
00907                         if( PickTeam(clientNum) == TEAM_RED) {
00908                                 team = "red";
00909                         }
00910                         else {
00911                                 team = "blue";
00912                         }
00913                 }
00914                 else {
00915                         team = "red";
00916                 }
00917         }
00918 //      Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) );
00919         Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) );
00920         Info_SetValueForKey( userinfo, "team", team );
00921 
00922         bot = &g_entities[ clientNum ];
00923         bot->r.svFlags |= SVF_BOT;
00924         bot->inuse = qtrue;
00925 
00926         // register the userinfo
00927         trap_SetUserinfo( clientNum, userinfo );
00928 
00929         if (g_gametype.integer >= GT_TEAM)
00930         {
00931                 if (team && Q_stricmp(team, "red") == 0)
00932                 {
00933                         bot->client->sess.sessionTeam = TEAM_RED;
00934                 }
00935                 else if (team && Q_stricmp(team, "blue") == 0)
00936                 {
00937                         bot->client->sess.sessionTeam = TEAM_BLUE;
00938                 }
00939                 else
00940                 {
00941                         bot->client->sess.sessionTeam = PickTeam( -1 );
00942                 }
00943         }
00944 
00945         if (g_gametype.integer == GT_SIEGE)
00946         {
00947                 bot->client->sess.siegeDesiredTeam = bot->client->sess.sessionTeam;
00948                 bot->client->sess.sessionTeam = TEAM_SPECTATOR;
00949         }
00950 
00951         preTeam = bot->client->sess.sessionTeam;
00952 
00953         // have it connect to the game as a normal client
00954         if ( ClientConnect( clientNum, qtrue, qtrue ) ) {
00955                 return;
00956         }
00957 
00958         if (bot->client->sess.sessionTeam != preTeam)
00959         {
00960                 trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING);
00961 
00962                 if (bot->client->sess.sessionTeam == TEAM_SPECTATOR)
00963                 {
00964                         bot->client->sess.sessionTeam = preTeam;
00965                 }
00966 
00967                 if (bot->client->sess.sessionTeam == TEAM_RED)
00968                 {
00969                         team = "Red";
00970                 }
00971                 else
00972                 {
00973                         if (g_gametype.integer == GT_SIEGE)
00974                         {
00975                                 if (bot->client->sess.sessionTeam == TEAM_BLUE)
00976                                 {
00977                                         team = "Blue";
00978                                 }
00979                                 else
00980                                 {
00981                                         team = "s";
00982                                 }
00983                         }
00984                         else
00985                         {
00986                                 team = "Blue";
00987                         }
00988                 }
00989 
00990                 Info_SetValueForKey( userinfo, "team", team );
00991 
00992                 trap_SetUserinfo( clientNum, userinfo );
00993 
00994                 bot->client->ps.persistant[ PERS_TEAM ] = bot->client->sess.sessionTeam;
00995 
00996                 G_ReadSessionData( bot->client );
00997                 ClientUserinfoChanged( clientNum );
00998         }
00999 
01000         if (g_gametype.integer == GT_DUEL ||
01001                 g_gametype.integer == GT_POWERDUEL)
01002         {
01003                 int loners = 0;
01004                 int doubles = 0;
01005 
01006                 bot->client->sess.duelTeam = 0;
01007                 G_PowerDuelCount(&loners, &doubles, qtrue);
01008 
01009                 if (!doubles || loners > (doubles/2))
01010                 {
01011             bot->client->sess.duelTeam = DUELTEAM_DOUBLE;
01012                 }
01013                 else
01014                 {
01015             bot->client->sess.duelTeam = DUELTEAM_LONE;
01016                 }
01017 
01018                 bot->client->sess.sessionTeam = TEAM_SPECTATOR;
01019                 SetTeam(bot, "s");
01020         }
01021         else
01022         {
01023                 if( delay == 0 ) {
01024                         ClientBegin( clientNum, qfalse );
01025                         return;
01026                 }
01027 
01028                 AddBotToSpawnQueue( clientNum, delay );
01029         }
01030 }
01031 
01032 
01033 /*
01034 ===============
01035 Svcmd_AddBot_f
01036 ===============
01037 */
01038 void Svcmd_AddBot_f( void ) {
01039         float                   skill;
01040         int                             delay;
01041         char                    name[MAX_TOKEN_CHARS];
01042         char                    altname[MAX_TOKEN_CHARS];
01043         char                    string[MAX_TOKEN_CHARS];
01044         char                    team[MAX_TOKEN_CHARS];
01045 
01046         // are bots enabled?
01047         if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
01048                 return;
01049         }
01050 
01051         // name
01052         trap_Argv( 1, name, sizeof( name ) );
01053         if ( !name[0] ) {
01054                 trap_Printf( "Usage: Addbot <botname> [skill 1-5] [team] [msec delay] [altname]\n" );
01055                 return;
01056         }
01057 
01058         // skill
01059         trap_Argv( 2, string, sizeof( string ) );
01060         if ( !string[0] ) {
01061                 skill = 4;
01062         }
01063         else {
01064                 skill = atof( string );
01065         }
01066 
01067         // team
01068         trap_Argv( 3, team, sizeof( team ) );
01069 
01070         // delay
01071         trap_Argv( 4, string, sizeof( string ) );
01072         if ( !string[0] ) {
01073                 delay = 0;
01074         }
01075         else {
01076                 delay = atoi( string );
01077         }
01078 
01079         // alternative name
01080         trap_Argv( 5, altname, sizeof( altname ) );
01081 
01082         G_AddBot( name, skill, team, delay, altname );
01083 
01084         // if this was issued during gameplay and we are playing locally,
01085         // go ahead and load the bot's media immediately
01086         if ( level.time - level.startTime > 1000 &&
01087                 trap_Cvar_VariableIntegerValue( "cl_running" ) ) {
01088                 trap_SendServerCommand( -1, "loaddefered\n" );  // FIXME: spelled wrong, but not changing for demo
01089         }
01090 }
01091 
01092 /*
01093 ===============
01094 Svcmd_BotList_f
01095 ===============
01096 */
01097 void Svcmd_BotList_f( void ) {
01098         int i;
01099         char name[MAX_TOKEN_CHARS];
01100         char funname[MAX_TOKEN_CHARS];
01101         char model[MAX_TOKEN_CHARS];
01102         char personality[MAX_TOKEN_CHARS];
01103 
01104         trap_Printf("^1name             model            personality              funname\n");
01105         for (i = 0; i < g_numBots; i++) {
01106                 strcpy(name, Info_ValueForKey( g_botInfos[i], "name" ));
01107                 if ( !*name ) {
01108                         strcpy(name, "Padawan");
01109                 }
01110                 strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" ));
01111                 if ( !*funname ) {
01112                         strcpy(funname, "");
01113                 }
01114                 strcpy(model, Info_ValueForKey( g_botInfos[i], "model" ));
01115                 if ( !*model ) {
01116                         strcpy(model, "kyle/default");
01117                 }
01118                 strcpy(personality, Info_ValueForKey( g_botInfos[i], "personality"));
01119                 if (!*personality ) {
01120                         strcpy(personality, "botfiles/kyle.jkb");
01121                 }
01122                 trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, personality, funname));
01123         }
01124 }
01125 
01126 #if 0
01127 /*
01128 ===============
01129 G_SpawnBots
01130 ===============
01131 */
01132 static void G_SpawnBots( char *botList, int baseDelay ) {
01133         char            *bot;
01134         char            *p;
01135         float           skill;
01136         int                     delay;
01137         char            bots[MAX_INFO_VALUE];
01138 
01139         podium1 = NULL;
01140         podium2 = NULL;
01141         podium3 = NULL;
01142 
01143         skill = trap_Cvar_VariableValue( "g_spSkill" );
01144         if( skill < 1 ) {
01145                 trap_Cvar_Set( "g_spSkill", "1" );
01146                 skill = 1;
01147         }
01148         else if ( skill > 5 ) {
01149                 trap_Cvar_Set( "g_spSkill", "5" );
01150                 skill = 5;
01151         }
01152 
01153         Q_strncpyz( bots, botList, sizeof(bots) );
01154         p = &bots[0];
01155         delay = baseDelay;
01156         while( *p ) {
01157                 //skip spaces
01158                 while( *p && *p == ' ' ) {
01159                         p++;
01160                 }
01161                 if( !p ) {
01162                         break;
01163                 }
01164 
01165                 // mark start of bot name
01166                 bot = p;
01167 
01168                 // skip until space of null
01169                 while( *p && *p != ' ' ) {
01170                         p++;
01171                 }
01172                 if( *p ) {
01173                         *p++ = 0;
01174                 }
01175 
01176                 // we must add the bot this way, calling G_AddBot directly at this stage
01177                 // does "Bad Things"
01178                 trap_SendConsoleCommand( EXEC_INSERT, va("addbot \"%s\" %f free %i\n", bot, skill, delay) );
01179 
01180                 delay += BOT_BEGIN_DELAY_INCREMENT;
01181         }
01182 }
01183 #endif
01184 
01185 
01186 /*
01187 ===============
01188 G_LoadBotsFromFile
01189 ===============
01190 */
01191 static void G_LoadBotsFromFile( char *filename ) {
01192         int                             len;
01193         fileHandle_t    f;
01194         char                    buf[MAX_BOTS_TEXT];
01195 
01196         len = trap_FS_FOpenFile( filename, &f, FS_READ );
01197         if ( !f ) {
01198                 trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
01199                 return;
01200         }
01201         if ( len >= MAX_BOTS_TEXT ) {
01202                 trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
01203                 trap_FS_FCloseFile( f );
01204                 return;
01205         }
01206 
01207         trap_FS_Read( buf, len, f );
01208         buf[len] = 0;
01209         trap_FS_FCloseFile( f );
01210 
01211         g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] );
01212 }
01213 
01214 /*
01215 ===============
01216 G_LoadBots
01217 ===============
01218 */
01219 static void G_LoadBots( void ) {
01220         vmCvar_t        botsFile;
01221         int                     numdirs;
01222         char            filename[128];
01223         char            dirlist[1024];
01224         char*           dirptr;
01225         int                     i;
01226         int                     dirlen;
01227 
01228         if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
01229                 return;
01230         }
01231 
01232         g_numBots = 0;
01233 
01234         trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
01235         if( *botsFile.string ) {
01236                 G_LoadBotsFromFile(botsFile.string);
01237         }
01238         else {
01239                 //G_LoadBotsFromFile("scripts/bots.txt");
01240                 G_LoadBotsFromFile("botfiles/bots.txt");
01241         }
01242 
01243         // get all bots from .bot files
01244         numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
01245         dirptr  = dirlist;
01246         for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
01247                 dirlen = strlen(dirptr);
01248                 strcpy(filename, "scripts/");
01249                 strcat(filename, dirptr);
01250                 G_LoadBotsFromFile(filename);
01251         }
01252 //      trap_Printf( va( "%i bots parsed\n", g_numBots ) );
01253 }
01254 
01255 
01256 
01257 /*
01258 ===============
01259 G_GetBotInfoByNumber
01260 ===============
01261 */
01262 char *G_GetBotInfoByNumber( int num ) {
01263         if( num < 0 || num >= g_numBots ) {
01264                 trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
01265                 return NULL;
01266         }
01267         return g_botInfos[num];
01268 }
01269 
01270 
01271 /*
01272 ===============
01273 G_GetBotInfoByName
01274 ===============
01275 */
01276 char *G_GetBotInfoByName( const char *name ) {
01277         int             n;
01278         char    *value;
01279 
01280         for ( n = 0; n < g_numBots ; n++ ) {
01281                 value = Info_ValueForKey( g_botInfos[n], "name" );
01282                 if ( !Q_stricmp( value, name ) ) {
01283                         return g_botInfos[n];
01284                 }
01285         }
01286 
01287         return NULL;
01288 }
01289 
01290 //rww - pd
01291 void LoadPath_ThisLevel(void);
01292 //end rww
01293 
01294 /*
01295 ===============
01296 G_InitBots
01297 ===============
01298 */
01299 void G_InitBots( qboolean restart ) {
01300         G_LoadBots();
01301         G_LoadArenas();
01302 
01303         trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO );
01304 
01305         //rww - new bot route stuff
01306         LoadPath_ThisLevel();
01307         //end rww
01308 }