Main Page | Class Hierarchy | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

ai_main.c

Go to the documentation of this file.
00001 /*
00002 ===========================================================================
00003 Copyright (C) 1999-2005 Id Software, Inc.
00004 
00005 This file is part of Quake III Arena source code.
00006 
00007 Quake III Arena source code is free software; you can redistribute it
00008 and/or modify it under the terms of the GNU General Public License as
00009 published by the Free Software Foundation; either version 2 of the License,
00010 or (at your option) any later version.
00011 
00012 Quake III Arena source code is distributed in the hope that it will be
00013 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Foobar; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 ===========================================================================
00021 */
00022 //
00023 
00024 /*****************************************************************************
00025  * name:        ai_main.c
00026  *
00027  * desc:        Quake3 bot AI
00028  *
00029  * $Archive: /MissionPack/code/game/ai_main.c $
00030  *
00031  *****************************************************************************/
00032 
00033 
00034 #include "g_local.h"
00035 #include "q_shared.h"
00036 #include "botlib.h"     //bot lib interface
00037 #include "be_aas.h"
00038 #include "be_ea.h"
00039 #include "be_ai_char.h"
00040 #include "be_ai_chat.h"
00041 #include "be_ai_gen.h"
00042 #include "be_ai_goal.h"
00043 #include "be_ai_move.h"
00044 #include "be_ai_weap.h"
00045 //
00046 #include "ai_main.h"
00047 #include "ai_dmq3.h"
00048 #include "ai_chat.h"
00049 #include "ai_cmd.h"
00050 #include "ai_dmnet.h"
00051 #include "ai_vcmd.h"
00052 
00053 //
00054 #include "chars.h"
00055 #include "inv.h"
00056 #include "syn.h"
00057 
00058 #define MAX_PATH        144
00059 
00060 
00061 //bot states
00062 bot_state_t *botstates[MAX_CLIENTS];
00063 //number of bots
00064 int numbots;
00065 //floating point time
00066 float floattime;
00067 //time to do a regular update
00068 float regularupdate_time;
00069 //
00070 int bot_interbreed;
00071 int bot_interbreedmatchcount;
00072 //
00073 vmCvar_t bot_thinktime;
00074 vmCvar_t bot_memorydump;
00075 vmCvar_t bot_saveroutingcache;
00076 vmCvar_t bot_pause;
00077 vmCvar_t bot_report;
00078 vmCvar_t bot_testsolid;
00079 vmCvar_t bot_testclusters;
00080 vmCvar_t bot_developer;
00081 vmCvar_t bot_interbreedchar;
00082 vmCvar_t bot_interbreedbots;
00083 vmCvar_t bot_interbreedcycle;
00084 vmCvar_t bot_interbreedwrite;
00085 
00086 
00087 void ExitLevel( void );
00088 
00089 
00090 /*
00091 ==================
00092 BotAI_Print
00093 ==================
00094 */
00095 void QDECL BotAI_Print(int type, char *fmt, ...) {
00096     char str[2048];
00097     va_list ap;
00098 
00099     va_start(ap, fmt);
00100     vsprintf(str, fmt, ap);
00101     va_end(ap);
00102 
00103     switch(type) {
00104         case PRT_MESSAGE: {
00105             G_Printf("%s", str);
00106             break;
00107         }
00108         case PRT_WARNING: {
00109             G_Printf( S_COLOR_YELLOW "Warning: %s", str );
00110             break;
00111         }
00112         case PRT_ERROR: {
00113             G_Printf( S_COLOR_RED "Error: %s", str );
00114             break;
00115         }
00116         case PRT_FATAL: {
00117             G_Printf( S_COLOR_RED "Fatal: %s", str );
00118             break;
00119         }
00120         case PRT_EXIT: {
00121             G_Error( S_COLOR_RED "Exit: %s", str );
00122             break;
00123         }
00124         default: {
00125             G_Printf( "unknown print type\n" );
00126             break;
00127         }
00128     }
00129 }
00130 
00131 
00132 /*
00133 ==================
00134 BotAI_Trace
00135 ==================
00136 */
00137 void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) {
00138     trace_t trace;
00139 
00140     trap_Trace(&trace, start, mins, maxs, end, passent, contentmask);
00141     //copy the trace information
00142     bsptrace->allsolid = trace.allsolid;
00143     bsptrace->startsolid = trace.startsolid;
00144     bsptrace->fraction = trace.fraction;
00145     VectorCopy(trace.endpos, bsptrace->endpos);
00146     bsptrace->plane.dist = trace.plane.dist;
00147     VectorCopy(trace.plane.normal, bsptrace->plane.normal);
00148     bsptrace->plane.signbits = trace.plane.signbits;
00149     bsptrace->plane.type = trace.plane.type;
00150     bsptrace->surface.value = trace.surfaceFlags;
00151     bsptrace->ent = trace.entityNum;
00152     bsptrace->exp_dist = 0;
00153     bsptrace->sidenum = 0;
00154     bsptrace->contents = 0;
00155 }
00156 
00157 /*
00158 ==================
00159 BotAI_GetClientState
00160 ==================
00161 */
00162 int BotAI_GetClientState( int clientNum, playerState_t *state ) {
00163     gentity_t   *ent;
00164 
00165     ent = &g_entities[clientNum];
00166     if ( !ent->inuse ) {
00167         return qfalse;
00168     }
00169     if ( !ent->client ) {
00170         return qfalse;
00171     }
00172 
00173     memcpy( state, &ent->client->ps, sizeof(playerState_t) );
00174     return qtrue;
00175 }
00176 
00177 /*
00178 ==================
00179 BotAI_GetEntityState
00180 ==================
00181 */
00182 int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
00183     gentity_t   *ent;
00184 
00185     ent = &g_entities[entityNum];
00186     memset( state, 0, sizeof(entityState_t) );
00187     if (!ent->inuse) return qfalse;
00188     if (!ent->r.linked) return qfalse;
00189     if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
00190     memcpy( state, &ent->s, sizeof(entityState_t) );
00191     return qtrue;
00192 }
00193 
00194 /*
00195 ==================
00196 BotAI_GetSnapshotEntity
00197 ==================
00198 */
00199 int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
00200     int     entNum;
00201 
00202     entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
00203     if ( entNum == -1 ) {
00204         memset(state, 0, sizeof(entityState_t));
00205         return -1;
00206     }
00207 
00208     BotAI_GetEntityState( entNum, state );
00209 
00210     return sequence + 1;
00211 }
00212 
00213 /*
00214 ==================
00215 BotAI_BotInitialChat
00216 ==================
00217 */
00218 void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) {
00219     int     i, mcontext;
00220     va_list ap;
00221     char    *p;
00222     char    *vars[MAX_MATCHVARIABLES];
00223 
00224     memset(vars, 0, sizeof(vars));
00225     va_start(ap, type);
00226     p = va_arg(ap, char *);
00227     for (i = 0; i < MAX_MATCHVARIABLES; i++) {
00228         if( !p ) {
00229             break;
00230         }
00231         vars[i] = p;
00232         p = va_arg(ap, char *);
00233     }
00234     va_end(ap);
00235 
00236     mcontext = BotSynonymContext(bs);
00237 
00238     trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] );
00239 }
00240 
00241 
00242 /*
00243 ==================
00244 BotTestAAS
00245 ==================
00246 */
00247 void BotTestAAS(vec3_t origin) {
00248     int areanum;
00249     aas_areainfo_t info;
00250 
00251     trap_Cvar_Update(&bot_testsolid);
00252     trap_Cvar_Update(&bot_testclusters);
00253     if (bot_testsolid.integer) {
00254         if (!trap_AAS_Initialized()) return;
00255         areanum = BotPointAreaNum(origin);
00256         if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area");
00257         else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area");
00258     }
00259     else if (bot_testclusters.integer) {
00260         if (!trap_AAS_Initialized()) return;
00261         areanum = BotPointAreaNum(origin);
00262         if (!areanum)
00263             BotAI_Print(PRT_MESSAGE, "\r^1Solid!                              ");
00264         else {
00265             trap_AAS_AreaInfo(areanum, &info);
00266             BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d       ", areanum, info.cluster);
00267         }
00268     }
00269 }
00270 
00271 /*
00272 ==================
00273 BotReportStatus
00274 ==================
00275 */
00276 void BotReportStatus(bot_state_t *bs) {
00277     char goalname[MAX_MESSAGE_SIZE];
00278     char netname[MAX_MESSAGE_SIZE];
00279     char *leader, flagstatus[32];
00280     //
00281     ClientName(bs->client, netname, sizeof(netname));
00282     if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L";
00283     else leader = " ";
00284 
00285     strcpy(flagstatus, "  ");
00286     if (gametype == GT_CTF) {
00287         if (BotCTFCarryingFlag(bs)) {
00288             if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F ");
00289             else strcpy(flagstatus, S_COLOR_BLUE"F ");
00290         }
00291     }
00292 #ifdef MISSIONPACK
00293     else if (gametype == GT_1FCTF) {
00294         if (Bot1FCTFCarryingFlag(bs)) {
00295             if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F ");
00296             else strcpy(flagstatus, S_COLOR_BLUE"F ");
00297         }
00298     }
00299     else if (gametype == GT_HARVESTER) {
00300         if (BotHarvesterCarryingCubes(bs)) {
00301             if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]);
00302             else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]);
00303         }
00304     }
00305 #endif
00306 
00307     switch(bs->ltgtype) {
00308         case LTG_TEAMHELP:
00309         {
00310             EasyClientName(bs->teammate, goalname, sizeof(goalname));
00311             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname);
00312             break;
00313         }
00314         case LTG_TEAMACCOMPANY:
00315         {
00316             EasyClientName(bs->teammate, goalname, sizeof(goalname));
00317             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname);
00318             break;
00319         }
00320         case LTG_DEFENDKEYAREA:
00321         {
00322             trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
00323             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname);
00324             break;
00325         }
00326         case LTG_GETITEM:
00327         {
00328             trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
00329             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname);
00330             break;
00331         }
00332         case LTG_KILL:
00333         {
00334             ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname));
00335             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname);
00336             break;
00337         }
00338         case LTG_CAMP:
00339         case LTG_CAMPORDER:
00340         {
00341             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus);
00342             break;
00343         }
00344         case LTG_PATROL:
00345         {
00346             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus);
00347             break;
00348         }
00349         case LTG_GETFLAG:
00350         {
00351             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus);
00352             break;
00353         }
00354         case LTG_RUSHBASE:
00355         {
00356             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus);
00357             break;
00358         }
00359         case LTG_RETURNFLAG:
00360         {
00361             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus);
00362             break;
00363         }
00364         case LTG_ATTACKENEMYBASE:
00365         {
00366             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus);
00367             break;
00368         }
00369         case LTG_HARVEST:
00370         {
00371             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus);
00372             break;
00373         }
00374         default:
00375         {
00376             BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus);
00377             break;
00378         }
00379     }
00380 }
00381 
00382 /*
00383 ==================
00384 BotTeamplayReport
00385 ==================
00386 */
00387 void BotTeamplayReport(void) {
00388     int i;
00389     char buf[MAX_INFO_STRING];
00390 
00391     BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n");
00392     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00393         //
00394         if ( !botstates[i] || !botstates[i]->inuse ) continue;
00395         //
00396         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00397         //if no config string or no name
00398         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00399         //skip spectators
00400         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) {
00401             BotReportStatus(botstates[i]);
00402         }
00403     }
00404     BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n");
00405     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00406         //
00407         if ( !botstates[i] || !botstates[i]->inuse ) continue;
00408         //
00409         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00410         //if no config string or no name
00411         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00412         //skip spectators
00413         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) {
00414             BotReportStatus(botstates[i]);
00415         }
00416     }
00417 }
00418 
00419 /*
00420 ==================
00421 BotSetInfoConfigString
00422 ==================
00423 */
00424 void BotSetInfoConfigString(bot_state_t *bs) {
00425     char goalname[MAX_MESSAGE_SIZE];
00426     char netname[MAX_MESSAGE_SIZE];
00427     char action[MAX_MESSAGE_SIZE];
00428     char *leader, carrying[32], *cs;
00429     bot_goal_t goal;
00430     //
00431     ClientName(bs->client, netname, sizeof(netname));
00432     if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L";
00433     else leader = " ";
00434 
00435     strcpy(carrying, "  ");
00436     if (gametype == GT_CTF) {
00437         if (BotCTFCarryingFlag(bs)) {
00438             strcpy(carrying, "F ");
00439         }
00440     }
00441 #ifdef MISSIONPACK
00442     else if (gametype == GT_1FCTF) {
00443         if (Bot1FCTFCarryingFlag(bs)) {
00444             strcpy(carrying, "F ");
00445         }
00446     }
00447     else if (gametype == GT_HARVESTER) {
00448         if (BotHarvesterCarryingCubes(bs)) {
00449             if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]);
00450             else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]);
00451         }
00452     }
00453 #endif
00454 
00455     switch(bs->ltgtype) {
00456         case LTG_TEAMHELP:
00457         {
00458             EasyClientName(bs->teammate, goalname, sizeof(goalname));
00459             Com_sprintf(action, sizeof(action), "helping %s", goalname);
00460             break;
00461         }
00462         case LTG_TEAMACCOMPANY:
00463         {
00464             EasyClientName(bs->teammate, goalname, sizeof(goalname));
00465             Com_sprintf(action, sizeof(action), "accompanying %s", goalname);
00466             break;
00467         }
00468         case LTG_DEFENDKEYAREA:
00469         {
00470             trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
00471             Com_sprintf(action, sizeof(action), "defending %s", goalname);
00472             break;
00473         }
00474         case LTG_GETITEM:
00475         {
00476             trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
00477             Com_sprintf(action, sizeof(action), "getting item %s", goalname);
00478             break;
00479         }
00480         case LTG_KILL:
00481         {
00482             ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname));
00483             Com_sprintf(action, sizeof(action), "killing %s", goalname);
00484             break;
00485         }
00486         case LTG_CAMP:
00487         case LTG_CAMPORDER:
00488         {
00489             Com_sprintf(action, sizeof(action), "camping");
00490             break;
00491         }
00492         case LTG_PATROL:
00493         {
00494             Com_sprintf(action, sizeof(action), "patrolling");
00495             break;
00496         }
00497         case LTG_GETFLAG:
00498         {
00499             Com_sprintf(action, sizeof(action), "capturing flag");
00500             break;
00501         }
00502         case LTG_RUSHBASE:
00503         {
00504             Com_sprintf(action, sizeof(action), "rushing base");
00505             break;
00506         }
00507         case LTG_RETURNFLAG:
00508         {
00509             Com_sprintf(action, sizeof(action), "returning flag");
00510             break;
00511         }
00512         case LTG_ATTACKENEMYBASE:
00513         {
00514             Com_sprintf(action, sizeof(action), "attacking the enemy base");
00515             break;
00516         }
00517         case LTG_HARVEST:
00518         {
00519             Com_sprintf(action, sizeof(action), "harvesting");
00520             break;
00521         }
00522         default:
00523         {
00524             trap_BotGetTopGoal(bs->gs, &goal);
00525             trap_BotGoalName(goal.number, goalname, sizeof(goalname));
00526             Com_sprintf(action, sizeof(action), "roaming %s", goalname);
00527             break;
00528         }
00529     }
00530     cs = va("l\\%s\\c\\%s\\a\\%s",
00531                 leader,
00532                 carrying,
00533                 action);
00534     trap_SetConfigstring (CS_BOTINFO + bs->client, cs);
00535 }
00536 
00537 /*
00538 ==============
00539 BotUpdateInfoConfigStrings
00540 ==============
00541 */
00542 void BotUpdateInfoConfigStrings(void) {
00543     int i;
00544     char buf[MAX_INFO_STRING];
00545 
00546     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00547         //
00548         if ( !botstates[i] || !botstates[i]->inuse )
00549             continue;
00550         //
00551         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00552         //if no config string or no name
00553         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n")))
00554             continue;
00555         BotSetInfoConfigString(botstates[i]);
00556     }
00557 }
00558 
00559 /*
00560 ==============
00561 BotInterbreedBots
00562 ==============
00563 */
00564 void BotInterbreedBots(void) {
00565     float ranks[MAX_CLIENTS];
00566     int parent1, parent2, child;
00567     int i;
00568 
00569     // get rankings for all the bots
00570     for (i = 0; i < MAX_CLIENTS; i++) {
00571         if ( botstates[i] && botstates[i]->inuse ) {
00572             ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
00573         }
00574         else {
00575             ranks[i] = -1;
00576         }
00577     }
00578 
00579     if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) {
00580         trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs);
00581         trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1);
00582     }
00583     // reset the kills and deaths
00584     for (i = 0; i < MAX_CLIENTS; i++) {
00585         if (botstates[i] && botstates[i]->inuse) {
00586             botstates[i]->num_kills = 0;
00587             botstates[i]->num_deaths = 0;
00588         }
00589     }
00590 }
00591 
00592 /*
00593 ==============
00594 BotWriteInterbreeded
00595 ==============
00596 */
00597 void BotWriteInterbreeded(char *filename) {
00598     float rank, bestrank;
00599     int i, bestbot;
00600 
00601     bestrank = 0;
00602     bestbot = -1;
00603     // get the best bot
00604     for (i = 0; i < MAX_CLIENTS; i++) {
00605         if ( botstates[i] && botstates[i]->inuse ) {
00606             rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
00607         }
00608         else {
00609             rank = -1;
00610         }
00611         if (rank > bestrank) {
00612             bestrank = rank;
00613             bestbot = i;
00614         }
00615     }
00616     if (bestbot >= 0) {
00617         //write out the new goal fuzzy logic
00618         trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename);
00619     }
00620 }
00621 
00622 /*
00623 ==============
00624 BotInterbreedEndMatch
00625 
00626 add link back into ExitLevel?
00627 ==============
00628 */
00629 void BotInterbreedEndMatch(void) {
00630 
00631     if (!bot_interbreed) return;
00632     bot_interbreedmatchcount++;
00633     if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) {
00634         bot_interbreedmatchcount = 0;
00635         //
00636         trap_Cvar_Update(&bot_interbreedwrite);
00637         if (strlen(bot_interbreedwrite.string)) {
00638             BotWriteInterbreeded(bot_interbreedwrite.string);
00639             trap_Cvar_Set("bot_interbreedwrite", "");
00640         }
00641         BotInterbreedBots();
00642     }
00643 }
00644 
00645 /*
00646 ==============
00647 BotInterbreeding
00648 ==============
00649 */
00650 void BotInterbreeding(void) {
00651     int i;
00652 
00653     trap_Cvar_Update(&bot_interbreedchar);
00654     if (!strlen(bot_interbreedchar.string)) return;
00655     //make sure we are in tournament mode
00656     if (gametype != GT_TOURNAMENT) {
00657         trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT));
00658         ExitLevel();
00659         return;
00660     }
00661     //shutdown all the bots
00662     for (i = 0; i < MAX_CLIENTS; i++) {
00663         if (botstates[i] && botstates[i]->inuse) {
00664             BotAIShutdownClient(botstates[i]->client, qfalse);
00665         }
00666     }
00667     //make sure all item weight configs are reloaded and Not shared
00668     trap_BotLibVarSet("bot_reloadcharacters", "1");
00669     //add a number of bots using the desired bot character
00670     for (i = 0; i < bot_interbreedbots.integer; i++) {
00671         trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n",
00672                         bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) );
00673     }
00674     //
00675     trap_Cvar_Set("bot_interbreedchar", "");
00676     bot_interbreed = qtrue;
00677 }
00678 
00679 /*
00680 ==============
00681 BotEntityInfo
00682 ==============
00683 */
00684 void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
00685     trap_AAS_EntityInfo(entnum, info);
00686 }
00687 
00688 /*
00689 ==============
00690 NumBots
00691 ==============
00692 */
00693 int NumBots(void) {
00694     return numbots;
00695 }
00696 
00697 /*
00698 ==============
00699 BotTeamLeader
00700 ==============
00701 */
00702 int BotTeamLeader(bot_state_t *bs) {
00703     int leader;
00704 
00705     leader = ClientFromName(bs->teamleader);
00706     if (leader < 0) return qfalse;
00707     if (!botstates[leader] || !botstates[leader]->inuse) return qfalse;
00708     return qtrue;
00709 }
00710 
00711 /*
00712 ==============
00713 AngleDifference
00714 ==============
00715 */
00716 float AngleDifference(float ang1, float ang2) {
00717     float diff;
00718 
00719     diff = ang1 - ang2;
00720     if (ang1 > ang2) {
00721         if (diff > 180.0) diff -= 360.0;
00722     }
00723     else {
00724         if (diff < -180.0) diff += 360.0;
00725     }
00726     return diff;
00727 }
00728 
00729 /*
00730 ==============
00731 BotChangeViewAngle
00732 ==============
00733 */
00734 float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
00735     float move;
00736 
00737     angle = AngleMod(angle);
00738     ideal_angle = AngleMod(ideal_angle);
00739     if (angle == ideal_angle) return angle;
00740     move = ideal_angle - angle;
00741     if (ideal_angle > angle) {
00742         if (move > 180.0) move -= 360.0;
00743     }
00744     else {
00745         if (move < -180.0) move += 360.0;
00746     }
00747     if (move > 0) {
00748         if (move > speed) move = speed;
00749     }
00750     else {
00751         if (move < -speed) move = -speed;
00752     }
00753     return AngleMod(angle + move);
00754 }
00755 
00756 /*
00757 ==============
00758 BotChangeViewAngles
00759 ==============
00760 */
00761 void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
00762     float diff, factor, maxchange, anglespeed, disired_speed;
00763     int i;
00764 
00765     if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
00766     //
00767     if (bs->enemy >= 0) {
00768         factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1);
00769         maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800);
00770     }
00771     else {
00772         factor = 0.05f;
00773         maxchange = 360;
00774     }
00775     if (maxchange < 240) maxchange = 240;
00776     maxchange *= thinktime;
00777     for (i = 0; i < 2; i++) {
00778         //
00779         if (bot_challenge.integer) {
00780             //smooth slowdown view model
00781             diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]));
00782             anglespeed = diff * factor;
00783             if (anglespeed > maxchange) anglespeed = maxchange;
00784             bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i],
00785                                             bs->ideal_viewangles[i], anglespeed);
00786         }
00787         else {
00788             //over reaction view model
00789             bs->viewangles[i] = AngleMod(bs->viewangles[i]);
00790             bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]);
00791             diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]);
00792             disired_speed = diff * factor;
00793             bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed);
00794             if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange;
00795             if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange;
00796             anglespeed = bs->viewanglespeed[i];
00797             if (anglespeed > maxchange) anglespeed = maxchange;
00798             if (anglespeed < -maxchange) anglespeed = -maxchange;
00799             bs->viewangles[i] += anglespeed;
00800             bs->viewangles[i] = AngleMod(bs->viewangles[i]);
00801             //demping
00802             bs->viewanglespeed[i] *= 0.45 * (1 - factor);
00803         }
00804         //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);`
00805         //bs->viewangles[i] = bs->ideal_viewangles[i];
00806     }
00807     //bs->viewangles[PITCH] = 0;
00808     if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
00809     //elementary action: view
00810     trap_EA_View(bs->client, bs->viewangles);
00811 }
00812 
00813 /*
00814 ==============
00815 BotInputToUserCommand
00816 ==============
00817 */
00818 void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) {
00819     vec3_t angles, forward, right;
00820     short temp;
00821     int j;
00822 
00823     //clear the whole structure
00824     memset(ucmd, 0, sizeof(usercmd_t));
00825     //
00826     //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
00827     //the duration for the user command in milli seconds
00828     ucmd->serverTime = time;
00829     //
00830     if (bi->actionflags & ACTION_DELAYEDJUMP) {
00831         bi->actionflags |= ACTION_JUMP;
00832         bi->actionflags &= ~ACTION_DELAYEDJUMP;
00833     }
00834     //set the buttons
00835     if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK;
00836     if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
00837     if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
00838     if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
00839     if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
00840     if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
00841     if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
00842     if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
00843     if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
00844     if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
00845     if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
00846     if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
00847     //
00848     ucmd->weapon = bi->weapon;
00849     //set the view angles
00850     //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
00851     ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
00852     ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
00853     ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
00854     //subtract the delta angles
00855     for (j = 0; j < 3; j++) {
00856         temp = ucmd->angles[j] - delta_angles[j];
00857         /*NOTE: disabled because temp should be mod first
00858         if ( j == PITCH ) {
00859             // don't let the player look up or down more than 90 degrees
00860             if ( temp > 16000 ) temp = 16000;
00861             else if ( temp < -16000 ) temp = -16000;
00862         }
00863         */
00864         ucmd->angles[j] = temp;
00865     }
00866     //NOTE: movement is relative to the REAL view angles
00867     //get the horizontal forward and right vector
00868     //get the pitch in the range [-180, 180]
00869     if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
00870     else angles[PITCH] = 0;
00871     angles[YAW] = bi->viewangles[YAW];
00872     angles[ROLL] = 0;
00873     AngleVectors(angles, forward, right, NULL);
00874     //bot input speed is in the range [0, 400]
00875     bi->speed = bi->speed * 127 / 400;
00876     //set the view independent movement
00877     ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
00878     ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;
00879     ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
00880     //normal keyboard movement
00881     if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127;
00882     if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127;
00883     if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127;
00884     if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127;
00885     //jump/moveup
00886     if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
00887     //crouch/movedown
00888     if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
00889     //
00890     //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
00891     //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
00892 }
00893 
00894 /*
00895 ==============
00896 BotUpdateInput
00897 ==============
00898 */
00899 void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
00900     bot_input_t bi;
00901     int j;
00902 
00903     //add the delta angles to the bot's current view angles
00904     for (j = 0; j < 3; j++) {
00905         bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00906     }
00907     //change the bot view angles
00908     BotChangeViewAngles(bs, (float) elapsed_time / 1000);
00909     //retrieve the bot input
00910     trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
00911     //respawn hack
00912     if (bi.actionflags & ACTION_RESPAWN) {
00913         if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
00914     }
00915     //convert the bot input to a usercmd
00916     BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time);
00917     //subtract the delta angles
00918     for (j = 0; j < 3; j++) {
00919         bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00920     }
00921 }
00922 
00923 /*
00924 ==============
00925 BotAIRegularUpdate
00926 ==============
00927 */
00928 void BotAIRegularUpdate(void) {
00929     if (regularupdate_time < FloatTime()) {
00930         trap_BotUpdateEntityItems();
00931         regularupdate_time = FloatTime() + 0.3;
00932     }
00933 }
00934 
00935 /*
00936 ==============
00937 RemoveColorEscapeSequences
00938 ==============
00939 */
00940 void RemoveColorEscapeSequences( char *text ) {
00941     int i, l;
00942 
00943     l = 0;
00944     for ( i = 0; text[i]; i++ ) {
00945         if (Q_IsColorString(&text[i])) {
00946             i++;
00947             continue;
00948         }
00949         if (text[i] > 0x7E)
00950             continue;
00951         text[l++] = text[i];
00952     }
00953     text[l] = '\0';
00954 }
00955 
00956 /*
00957 ==============
00958 BotAI
00959 ==============
00960 */
00961 int BotAI(int client, float thinktime) {
00962     bot_state_t *bs;
00963     char buf[1024], *args;
00964     int j;
00965 
00966     trap_EA_ResetInput(client);
00967     //
00968     bs = botstates[client];
00969     if (!bs || !bs->inuse) {
00970         BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
00971         return qfalse;
00972     }
00973 
00974     //retrieve the current client state
00975     BotAI_GetClientState( client, &bs->cur_ps );
00976 
00977     //retrieve any waiting server commands
00978     while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) {
00979         //have buf point to the command and args to the command arguments
00980         args = strchr( buf, ' ');
00981         if (!args) continue;
00982         *args++ = '\0';
00983 
00984         //remove color espace sequences from the arguments
00985         RemoveColorEscapeSequences( args );
00986 
00987         if (!Q_stricmp(buf, "cp "))
00988             { /*CenterPrintf*/ }
00989         else if (!Q_stricmp(buf, "cs"))
00990             { /*ConfigStringModified*/ }
00991         else if (!Q_stricmp(buf, "print")) {
00992             //remove first and last quote from the chat message
00993             memmove(args, args+1, strlen(args));
00994             args[strlen(args)-1] = '\0';
00995             trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args);
00996         }
00997         else if (!Q_stricmp(buf, "chat")) {
00998             //remove first and last quote from the chat message
00999             memmove(args, args+1, strlen(args));
01000             args[strlen(args)-1] = '\0';
01001             trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
01002         }
01003         else if (!Q_stricmp(buf, "tchat")) {
01004             //remove first and last quote from the chat message
01005             memmove(args, args+1, strlen(args));
01006             args[strlen(args)-1] = '\0';
01007             trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
01008         }
01009 #ifdef MISSIONPACK
01010         else if (!Q_stricmp(buf, "vchat")) {
01011             BotVoiceChatCommand(bs, SAY_ALL, args);
01012         }
01013         else if (!Q_stricmp(buf, "vtchat")) {
01014             BotVoiceChatCommand(bs, SAY_TEAM, args);
01015         }
01016         else if (!Q_stricmp(buf, "vtell")) {
01017             BotVoiceChatCommand(bs, SAY_TELL, args);
01018         }
01019 #endif
01020         else if (!Q_stricmp(buf, "scores"))
01021             { /*FIXME: parse scores?*/ }
01022         else if (!Q_stricmp(buf, "clientLevelShot"))
01023             { /*ignore*/ }
01024     }
01025     //add the delta angles to the bot's current view angles
01026     for (j = 0; j < 3; j++) {
01027         bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
01028     }
01029     //increase the local time of the bot
01030     bs->ltime += thinktime;
01031     //
01032     bs->thinktime = thinktime;
01033     //origin of the bot
01034     VectorCopy(bs->cur_ps.origin, bs->origin);
01035     //eye coordinates of the bot
01036     VectorCopy(bs->cur_ps.origin, bs->eye);
01037     bs->eye[2] += bs->cur_ps.viewheight;
01038     //get the area the bot is in
01039     bs->areanum = BotPointAreaNum(bs->origin);
01040     //the real AI
01041     BotDeathmatchAI(bs, thinktime);
01042     //set the weapon selection every AI frame
01043     trap_EA_SelectWeapon(bs->client, bs->weaponnum);
01044     //subtract the delta angles
01045     for (j = 0; j < 3; j++) {
01046         bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
01047     }
01048     //everything was ok
01049     return qtrue;
01050 }
01051 
01052 /*
01053 ==================
01054 BotScheduleBotThink
01055 ==================
01056 */
01057 void BotScheduleBotThink(void) {
01058     int i, botnum;
01059 
01060     botnum = 0;
01061 
01062     for( i = 0; i < MAX_CLIENTS; i++ ) {
01063         if( !botstates[i] || !botstates[i]->inuse ) {
01064             continue;
01065         }
01066         //initialize the bot think residual time
01067         botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots;
01068         botnum++;
01069     }
01070 }
01071 
01072 /*
01073 ==============
01074 BotWriteSessionData
01075 ==============
01076 */
01077 void BotWriteSessionData(bot_state_t *bs) {
01078     const char  *s;
01079     const char  *var;
01080 
01081     s = va(
01082             "%i %i %i %i %i %i %i %i"
01083             " %f %f %f"
01084             " %f %f %f"
01085             " %f %f %f",
01086         bs->lastgoal_decisionmaker,
01087         bs->lastgoal_ltgtype,
01088         bs->lastgoal_teammate,
01089         bs->lastgoal_teamgoal.areanum,
01090         bs->lastgoal_teamgoal.entitynum,
01091         bs->lastgoal_teamgoal.flags,
01092         bs->lastgoal_teamgoal.iteminfo,
01093         bs->lastgoal_teamgoal.number,
01094         bs->lastgoal_teamgoal.origin[0],
01095         bs->lastgoal_teamgoal.origin[1],
01096         bs->lastgoal_teamgoal.origin[2],
01097         bs->lastgoal_teamgoal.mins[0],
01098         bs->lastgoal_teamgoal.mins[1],
01099         bs->lastgoal_teamgoal.mins[2],
01100         bs->lastgoal_teamgoal.maxs[0],
01101         bs->lastgoal_teamgoal.maxs[1],
01102         bs->lastgoal_teamgoal.maxs[2]
01103         );
01104 
01105     var = va( "botsession%i", bs->client );
01106 
01107     trap_Cvar_Set( var, s );
01108 }
01109 
01110 /*
01111 ==============
01112 BotReadSessionData
01113 ==============
01114 */
01115 void BotReadSessionData(bot_state_t *bs) {
01116     char    s[MAX_STRING_CHARS];
01117     const char  *var;
01118 
01119     var = va( "botsession%i", bs->client );
01120     trap_Cvar_VariableStringBuffer( var, s, sizeof(s) );
01121 
01122     sscanf(s,
01123             "%i %i %i %i %i %i %i %i"
01124             " %f %f %f"
01125             " %f %f %f"
01126             " %f %f %f",
01127         &bs->lastgoal_decisionmaker,
01128         &bs->lastgoal_ltgtype,
01129         &bs->lastgoal_teammate,
01130         &bs->lastgoal_teamgoal.areanum,
01131         &bs->lastgoal_teamgoal.entitynum,
01132         &bs->lastgoal_teamgoal.flags,
01133         &bs->lastgoal_teamgoal.iteminfo,
01134         &bs->lastgoal_teamgoal.number,
01135         &bs->lastgoal_teamgoal.origin[0],
01136         &bs->lastgoal_teamgoal.origin[1],
01137         &bs->lastgoal_teamgoal.origin[2],
01138         &bs->lastgoal_teamgoal.mins[0],
01139         &bs->lastgoal_teamgoal.mins[1],
01140         &bs->lastgoal_teamgoal.mins[2],
01141         &bs->lastgoal_teamgoal.maxs[0],
01142         &bs->lastgoal_teamgoal.maxs[1],
01143         &bs->lastgoal_teamgoal.maxs[2]
01144         );
01145 }
01146 
01147 /*
01148 ==============
01149 BotAISetupClient
01150 ==============
01151 */
01152 int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) {
01153     char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH];
01154     bot_state_t *bs;
01155     int errnum;
01156 
01157     if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t));
01158     bs = botstates[client];
01159 
01160     if (bs && bs->inuse) {
01161         BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
01162         return qfalse;
01163     }
01164 
01165     if (!trap_AAS_Initialized()) {
01166         BotAI_Print(PRT_FATAL, "AAS not initialized\n");
01167         return qfalse;
01168     }
01169 
01170     //load the bot character
01171     bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill);
01172     if (!bs->character) {
01173         BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile);
01174         return qfalse;
01175     }
01176     //copy the settings
01177     memcpy(&bs->settings, settings, sizeof(bot_settings_t));
01178     //allocate a goal state
01179     bs->gs = trap_BotAllocGoalState(client);
01180     //load the item weights
01181     trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH);
01182     errnum = trap_BotLoadItemWeights(bs->gs, filename);
01183     if (errnum != BLERR_NOERROR) {
01184         trap_BotFreeGoalState(bs->gs);
01185         return qfalse;
01186     }
01187     //allocate a weapon state
01188     bs->ws = trap_BotAllocWeaponState();
01189     //load the weapon weights
01190     trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH);
01191     errnum = trap_BotLoadWeaponWeights(bs->ws, filename);
01192     if (errnum != BLERR_NOERROR) {
01193         trap_BotFreeGoalState(bs->gs);
01194         trap_BotFreeWeaponState(bs->ws);
01195         return qfalse;
01196     }
01197     //allocate a chat state
01198     bs->cs = trap_BotAllocChatState();
01199     //load the chat file
01200     trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH);
01201     trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH);
01202     errnum = trap_BotLoadChatFile(bs->cs, filename, name);
01203     if (errnum != BLERR_NOERROR) {
01204         trap_BotFreeChatState(bs->cs);
01205         trap_BotFreeGoalState(bs->gs);
01206         trap_BotFreeWeaponState(bs->ws);
01207         return qfalse;
01208     }
01209     //get the gender characteristic
01210     trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH);
01211     //set the chat gender
01212     if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs,