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

ai_chat.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_chat.c
00026  *
00027  * desc:        Quake3 bot AI
00028  *
00029  * $Archive: /MissionPack/code/game/ai_chat.c $
00030  *
00031  *****************************************************************************/
00032 
00033 #include "g_local.h"
00034 #include "botlib.h"
00035 #include "be_aas.h"
00036 #include "be_ea.h"
00037 #include "be_ai_char.h"
00038 #include "be_ai_chat.h"
00039 #include "be_ai_gen.h"
00040 #include "be_ai_goal.h"
00041 #include "be_ai_move.h"
00042 #include "be_ai_weap.h"
00043 //
00044 #include "ai_main.h"
00045 #include "ai_dmq3.h"
00046 #include "ai_chat.h"
00047 #include "ai_cmd.h"
00048 #include "ai_dmnet.h"
00049 //
00050 #include "chars.h"              //characteristics
00051 #include "inv.h"                //indexes into the inventory
00052 #include "syn.h"                //synonyms
00053 #include "match.h"              //string matching types and vars
00054 
00055 // for the voice chats
00056 #ifdef MISSIONPACK // bk001205
00057 #include "../../ui/menudef.h"
00058 #endif
00059 
00060 #define TIME_BETWEENCHATTING    25
00061 
00062 
00063 /*
00064 ==================
00065 BotNumActivePlayers
00066 ==================
00067 */
00068 int BotNumActivePlayers(void) {
00069     int i, num;
00070     char buf[MAX_INFO_STRING];
00071     static int maxclients;
00072 
00073     if (!maxclients)
00074         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00075 
00076     num = 0;
00077     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00078         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00079         //if no config string or no name
00080         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00081         //skip spectators
00082         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00083         //
00084         num++;
00085     }
00086     return num;
00087 }
00088 
00089 /*
00090 ==================
00091 BotIsFirstInRankings
00092 ==================
00093 */
00094 int BotIsFirstInRankings(bot_state_t *bs) {
00095     int i, score;
00096     char buf[MAX_INFO_STRING];
00097     static int maxclients;
00098     playerState_t ps;
00099 
00100     if (!maxclients)
00101         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00102 
00103     score = bs->cur_ps.persistant[PERS_SCORE];
00104     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00105         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00106         //if no config string or no name
00107         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00108         //skip spectators
00109         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00110         //
00111         BotAI_GetClientState(i, &ps);
00112         if (score < ps.persistant[PERS_SCORE]) return qfalse;
00113     }
00114     return qtrue;
00115 }
00116 
00117 /*
00118 ==================
00119 BotIsLastInRankings
00120 ==================
00121 */
00122 int BotIsLastInRankings(bot_state_t *bs) {
00123     int i, score;
00124     char buf[MAX_INFO_STRING];
00125     static int maxclients;
00126     playerState_t ps;
00127 
00128     if (!maxclients)
00129         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00130 
00131     score = bs->cur_ps.persistant[PERS_SCORE];
00132     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00133         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00134         //if no config string or no name
00135         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00136         //skip spectators
00137         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00138         //
00139         BotAI_GetClientState(i, &ps);
00140         if (score > ps.persistant[PERS_SCORE]) return qfalse;
00141     }
00142     return qtrue;
00143 }
00144 
00145 /*
00146 ==================
00147 BotFirstClientInRankings
00148 ==================
00149 */
00150 char *BotFirstClientInRankings(void) {
00151     int i, bestscore, bestclient;
00152     char buf[MAX_INFO_STRING];
00153     static char name[32];
00154     static int maxclients;
00155     playerState_t ps;
00156 
00157     if (!maxclients)
00158         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00159 
00160     bestscore = -999999;
00161     bestclient = 0;
00162     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00163         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00164         //if no config string or no name
00165         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00166         //skip spectators
00167         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00168         //
00169         BotAI_GetClientState(i, &ps);
00170         if (ps.persistant[PERS_SCORE] > bestscore) {
00171             bestscore = ps.persistant[PERS_SCORE];
00172             bestclient = i;
00173         }
00174     }
00175     EasyClientName(bestclient, name, 32);
00176     return name;
00177 }
00178 
00179 /*
00180 ==================
00181 BotLastClientInRankings
00182 ==================
00183 */
00184 char *BotLastClientInRankings(void) {
00185     int i, worstscore, bestclient;
00186     char buf[MAX_INFO_STRING];
00187     static char name[32];
00188     static int maxclients;
00189     playerState_t ps;
00190 
00191     if (!maxclients)
00192         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00193 
00194     worstscore = 999999;
00195     bestclient = 0;
00196     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00197         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00198         //if no config string or no name
00199         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00200         //skip spectators
00201         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00202         //
00203         BotAI_GetClientState(i, &ps);
00204         if (ps.persistant[PERS_SCORE] < worstscore) {
00205             worstscore = ps.persistant[PERS_SCORE];
00206             bestclient = i;
00207         }
00208     }
00209     EasyClientName(bestclient, name, 32);
00210     return name;
00211 }
00212 
00213 /*
00214 ==================
00215 BotRandomOpponentName
00216 ==================
00217 */
00218 char *BotRandomOpponentName(bot_state_t *bs) {
00219     int i, count;
00220     char buf[MAX_INFO_STRING];
00221     int opponents[MAX_CLIENTS], numopponents;
00222     static int maxclients;
00223     static char name[32];
00224 
00225     if (!maxclients)
00226         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
00227 
00228     numopponents = 0;
00229     opponents[0] = 0;
00230     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
00231         if (i == bs->client) continue;
00232         //
00233         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
00234         //if no config string or no name
00235         if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
00236         //skip spectators
00237         if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
00238         //skip team mates
00239         if (BotSameTeam(bs, i)) continue;
00240         //
00241         opponents[numopponents] = i;
00242         numopponents++;
00243     }
00244     count = random() * numopponents;
00245     for (i = 0; i < numopponents; i++) {
00246         count--;
00247         if (count <= 0) {
00248             EasyClientName(opponents[i], name, sizeof(name));
00249             return name;
00250         }
00251     }
00252     EasyClientName(opponents[0], name, sizeof(name));
00253     return name;
00254 }
00255 
00256 /*
00257 ==================
00258 BotMapTitle
00259 ==================
00260 */
00261 
00262 char *BotMapTitle(void) {
00263     char info[1024];
00264     static char mapname[128];
00265 
00266     trap_GetServerinfo(info, sizeof(info));
00267 
00268     strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
00269     mapname[sizeof(mapname)-1] = '\0';
00270 
00271     return mapname;
00272 }
00273 
00274 
00275 /*
00276 ==================
00277 BotWeaponNameForMeansOfDeath
00278 ==================
00279 */
00280 
00281 char *BotWeaponNameForMeansOfDeath(int mod) {
00282     switch(mod) {
00283         case MOD_SHOTGUN: return "Shotgun";
00284         case MOD_GAUNTLET: return "Gauntlet";
00285         case MOD_MACHINEGUN: return "Machinegun";
00286         case MOD_GRENADE:
00287         case MOD_GRENADE_SPLASH: return "Grenade Launcher";
00288         case MOD_ROCKET:
00289         case MOD_ROCKET_SPLASH: return "Rocket Launcher";
00290         case MOD_PLASMA:
00291         case MOD_PLASMA_SPLASH: return "Plasmagun";
00292         case MOD_RAILGUN: return "Railgun";
00293         case MOD_LIGHTNING: return "Lightning Gun";
00294         case MOD_BFG:
00295         case MOD_BFG_SPLASH: return "BFG10K";
00296 #ifdef MISSIONPACK
00297         case MOD_NAIL: return "Nailgun";
00298         case MOD_CHAINGUN: return "Chaingun";
00299         case MOD_PROXIMITY_MINE: return "Proximity Launcher";
00300         case MOD_KAMIKAZE: return "Kamikaze";
00301         case MOD_JUICED: return "Prox mine";
00302 #endif
00303         case MOD_GRAPPLE: return "Grapple";
00304         default: return "[unknown weapon]";
00305     }
00306 }
00307 
00308 /*
00309 ==================
00310 BotRandomWeaponName
00311 ==================
00312 */
00313 char *BotRandomWeaponName(void) {
00314     int rnd;
00315 
00316 #ifdef MISSIONPACK
00317     rnd = random() * 11.9;
00318 #else
00319     rnd = random() * 8.9;
00320 #endif
00321     switch(rnd) {
00322         case 0: return "Gauntlet";
00323         case 1: return "Shotgun";
00324         case 2: return "Machinegun";
00325         case 3: return "Grenade Launcher";
00326         case 4: return "Rocket Launcher";
00327         case 5: return "Plasmagun";
00328         case 6: return "Railgun";
00329         case 7: return "Lightning Gun";
00330 #ifdef MISSIONPACK
00331         case 8: return "Nailgun";
00332         case 9: return "Chaingun";
00333         case 10: return "Proximity Launcher";
00334 #endif
00335         default: return "BFG10K";
00336     }
00337 }
00338 
00339 /*
00340 ==================
00341 BotVisibleEnemies
00342 ==================
00343 */
00344 int BotVisibleEnemies(bot_state_t *bs) {
00345     float vis;
00346     int i;
00347     aas_entityinfo_t entinfo;
00348 
00349     for (i = 0; i < MAX_CLIENTS; i++) {
00350 
00351         if (i == bs->client) continue;
00352         //
00353         BotEntityInfo(i, &entinfo);
00354         //
00355         if (!entinfo.valid) continue;
00356         //if the enemy isn't dead and the enemy isn't the bot self
00357         if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
00358         //if the enemy is invisible and not shooting
00359         if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
00360             continue;
00361         }
00362         //if on the same team
00363         if (BotSameTeam(bs, i)) continue;
00364         //check if the enemy is visible
00365         vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
00366         if (vis > 0) return qtrue;
00367     }
00368     return qfalse;
00369 }
00370 
00371 /*
00372 ==================
00373 BotValidChatPosition
00374 ==================
00375 */
00376 int BotValidChatPosition(bot_state_t *bs) {
00377     vec3_t point, start, end, mins, maxs;
00378     bsp_trace_t trace;
00379 
00380     //if the bot is dead all positions are valid
00381     if (BotIsDead(bs)) return qtrue;
00382     //never start chatting with a powerup
00383     if (bs->inventory[INVENTORY_QUAD] ||
00384         bs->inventory[INVENTORY_HASTE] ||
00385         bs->inventory[INVENTORY_INVISIBILITY] ||
00386         bs->inventory[INVENTORY_REGEN] ||
00387         bs->inventory[INVENTORY_FLIGHT]) return qfalse;
00388     //must be on the ground
00389     //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse;
00390     //do not chat if in lava or slime
00391     VectorCopy(bs->origin, point);
00392     point[2] -= 24;
00393     if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse;
00394     //do not chat if under water
00395     VectorCopy(bs->origin, point);
00396     point[2] += 32;
00397     if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse;
00398     //must be standing on the world entity
00399     VectorCopy(bs->origin, start);
00400     VectorCopy(bs->origin, end);
00401     start[2] += 1;
00402     end[2] -= 10;
00403     trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs);
00404     BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID);
00405     if (trace.ent != ENTITYNUM_WORLD) return qfalse;
00406     //the bot is in a position where it can chat
00407     return qtrue;
00408 }
00409 
00410 /*
00411 ==================
00412 BotChat_EnterGame
00413 ==================
00414 */
00415 int BotChat_EnterGame(bot_state_t *bs) {
00416     char name[32];
00417     float rnd;
00418 
00419     if (bot_nochat.integer) return qfalse;
00420     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00421     //don't chat in teamplay
00422     if (TeamPlayIsOn()) return qfalse;
00423     // don't chat in tournament mode
00424     if (gametype == GT_TOURNAMENT) return qfalse;
00425     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1);
00426     if (!bot_fastchat.integer) {
00427         if (random() > rnd) return qfalse;
00428     }
00429     if (BotNumActivePlayers() <= 1) return qfalse;
00430     if (!BotValidChatPosition(bs)) return qfalse;
00431     BotAI_BotInitialChat(bs, "game_enter",
00432                 EasyClientName(bs->client, name, 32),   // 0
00433                 BotRandomOpponentName(bs),              // 1
00434                 "[invalid var]",                        // 2
00435                 "[invalid var]",                        // 3
00436                 BotMapTitle(),                          // 4
00437                 NULL);
00438     bs->lastchat_time = FloatTime();
00439     bs->chatto = CHAT_ALL;
00440     return qtrue;
00441 }
00442 
00443 /*
00444 ==================
00445 BotChat_ExitGame
00446 ==================
00447 */
00448 int BotChat_ExitGame(bot_state_t *bs) {
00449     char name[32];
00450     float rnd;
00451 
00452     if (bot_nochat.integer) return qfalse;
00453     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00454     //don't chat in teamplay
00455     if (TeamPlayIsOn()) return qfalse;
00456     // don't chat in tournament mode
00457     if (gametype == GT_TOURNAMENT) return qfalse;
00458     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1);
00459     if (!bot_fastchat.integer) {
00460         if (random() > rnd) return qfalse;
00461     }
00462     if (BotNumActivePlayers() <= 1) return qfalse;
00463     //
00464     BotAI_BotInitialChat(bs, "game_exit",
00465                 EasyClientName(bs->client, name, 32),   // 0
00466                 BotRandomOpponentName(bs),              // 1
00467                 "[invalid var]",                        // 2
00468                 "[invalid var]",                        // 3
00469                 BotMapTitle(),                          // 4
00470                 NULL);
00471     bs->lastchat_time = FloatTime();
00472     bs->chatto = CHAT_ALL;
00473     return qtrue;
00474 }
00475 
00476 /*
00477 ==================
00478 BotChat_StartLevel
00479 ==================
00480 */
00481 int BotChat_StartLevel(bot_state_t *bs) {
00482     char name[32];
00483     float rnd;
00484 
00485     if (bot_nochat.integer) return qfalse;
00486     if (BotIsObserver(bs)) return qfalse;
00487     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00488     //don't chat in teamplay
00489     if (TeamPlayIsOn()) {
00490         trap_EA_Command(bs->client, "vtaunt");
00491         return qfalse;
00492     }
00493     // don't chat in tournament mode
00494     if (gametype == GT_TOURNAMENT) return qfalse;
00495     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1);
00496     if (!bot_fastchat.integer) {
00497         if (random() > rnd) return qfalse;
00498     }
00499     if (BotNumActivePlayers() <= 1) return qfalse;
00500     BotAI_BotInitialChat(bs, "level_start",
00501                 EasyClientName(bs->client, name, 32),   // 0
00502                 NULL);
00503     bs->lastchat_time = FloatTime();
00504     bs->chatto = CHAT_ALL;
00505     return qtrue;
00506 }
00507 
00508 /*
00509 ==================
00510 BotChat_EndLevel
00511 ==================
00512 */
00513 int BotChat_EndLevel(bot_state_t *bs) {
00514     char name[32];
00515     float rnd;
00516 
00517     if (bot_nochat.integer) return qfalse;
00518     if (BotIsObserver(bs)) return qfalse;
00519     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00520     // teamplay
00521     if (TeamPlayIsOn()) 
00522     {
00523         if (BotIsFirstInRankings(bs)) {
00524             trap_EA_Command(bs->client, "vtaunt");
00525         }
00526         return qtrue;
00527     }
00528     // don't chat in tournament mode
00529     if (gametype == GT_TOURNAMENT) return qfalse;
00530     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1);
00531     if (!bot_fastchat.integer) {
00532         if (random() > rnd) return qfalse;
00533     }
00534     if (BotNumActivePlayers() <= 1) return qfalse;
00535     //
00536     if (BotIsFirstInRankings(bs)) {
00537         BotAI_BotInitialChat(bs, "level_end_victory",
00538                 EasyClientName(bs->client, name, 32),   // 0
00539                 BotRandomOpponentName(bs),              // 1
00540                 "[invalid var]",                        // 2
00541                 BotLastClientInRankings(),              // 3
00542                 BotMapTitle(),                          // 4
00543                 NULL);
00544     }
00545     else if (BotIsLastInRankings(bs)) {
00546         BotAI_BotInitialChat(bs, "level_end_lose",
00547                 EasyClientName(bs->client, name, 32),   // 0
00548                 BotRandomOpponentName(bs),              // 1
00549                 BotFirstClientInRankings(),             // 2
00550                 "[invalid var]",                        // 3
00551                 BotMapTitle(),                          // 4
00552                 NULL);
00553     }
00554     else {
00555         BotAI_BotInitialChat(bs, "level_end",
00556                 EasyClientName(bs->client, name, 32),   // 0
00557                 BotRandomOpponentName(bs),              // 1
00558                 BotFirstClientInRankings(),             // 2
00559                 BotLastClientInRankings(),              // 3
00560                 BotMapTitle(),                          // 4
00561                 NULL);
00562     }
00563     bs->lastchat_time = FloatTime();
00564     bs->chatto = CHAT_ALL;
00565     return qtrue;
00566 }
00567 
00568 /*
00569 ==================
00570 BotChat_Death
00571 ==================
00572 */
00573 int BotChat_Death(bot_state_t *bs) {
00574     char name[32];
00575     float rnd;
00576 
00577     if (bot_nochat.integer) return qfalse;
00578     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00579     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1);
00580     // don't chat in tournament mode
00581     if (gametype == GT_TOURNAMENT) return qfalse;
00582     //if fast chatting is off
00583     if (!bot_fastchat.integer) {
00584         if (random() > rnd) return qfalse;
00585     }
00586     if (BotNumActivePlayers() <= 1) return qfalse;
00587     //
00588     if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS)
00589         EasyClientName(bs->lastkilledby, name, 32);
00590     else
00591         strcpy(name, "[world]");
00592     //
00593     if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) {
00594         if (bs->lastkilledby == bs->client) return qfalse;
00595         BotAI_BotInitialChat(bs, "death_teammate", name, NULL);
00596         bs->chatto = CHAT_TEAM;
00597     }
00598     else
00599     {
00600         //teamplay
00601         if (TeamPlayIsOn()) {
00602             trap_EA_Command(bs->client, "vtaunt");
00603             return qtrue;
00604         }
00605         //
00606         if (bs->botdeathtype == MOD_WATER)
00607             BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL);
00608         else if (bs->botdeathtype == MOD_SLIME)
00609             BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL);
00610         else if (bs->botdeathtype == MOD_LAVA)
00611             BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL);
00612         else if (bs->botdeathtype == MOD_FALLING)
00613             BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL);
00614         else if (bs->botsuicide || //all other suicides by own weapon
00615                 bs->botdeathtype == MOD_CRUSH ||
00616                 bs->botdeathtype == MOD_SUICIDE ||
00617                 bs->botdeathtype == MOD_TARGET_LASER ||
00618                 bs->botdeathtype == MOD_TRIGGER_HURT ||
00619                 bs->botdeathtype == MOD_UNKNOWN)
00620             BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL);
00621         else if (bs->botdeathtype == MOD_TELEFRAG)
00622             BotAI_BotInitialChat(bs, "death_telefrag", name, NULL);
00623 #ifdef MISSIONPACK
00624         else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze"))
00625             BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL);
00626 #endif
00627         else {
00628             if ((bs->botdeathtype == MOD_GAUNTLET ||
00629                 bs->botdeathtype == MOD_RAILGUN ||
00630                 bs->botdeathtype == MOD_BFG ||
00631                 bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) {
00632 
00633                 if (bs->botdeathtype == MOD_GAUNTLET)
00634                     BotAI_BotInitialChat(bs, "death_gauntlet",
00635                             name,                                               // 0
00636                             BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
00637                             NULL);
00638                 else if (bs->botdeathtype == MOD_RAILGUN)
00639                     BotAI_BotInitialChat(bs, "death_rail",
00640                             name,                                               // 0
00641                             BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
00642                             NULL);
00643                 else
00644                     BotAI_BotInitialChat(bs, "death_bfg",
00645                             name,                                               // 0
00646                             BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
00647                             NULL);
00648             }
00649             //choose between insult and praise
00650             else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) {
00651                 BotAI_BotInitialChat(bs, "death_insult",
00652                             name,                                               // 0
00653                             BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
00654                             NULL);
00655             }
00656             else {
00657                 BotAI_BotInitialChat(bs, "death_praise",
00658                             name,                                               // 0
00659                             BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
00660                             NULL);
00661             }
00662         }
00663         bs->chatto = CHAT_ALL;
00664     }
00665     bs->lastchat_time = FloatTime();
00666     return qtrue;
00667 }
00668 
00669 /*
00670 ==================
00671 BotChat_Kill
00672 ==================
00673 */
00674 int BotChat_Kill(bot_state_t *bs) {
00675     char name[32];
00676     float rnd;
00677 
00678     if (bot_nochat.integer) return qfalse;
00679     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00680     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1);
00681     // don't chat in tournament mode
00682     if (gametype == GT_TOURNAMENT) return qfalse;
00683     //if fast chat is off
00684     if (!bot_fastchat.integer) {
00685         if (random() > rnd) return qfalse;
00686     }
00687     if (bs->lastkilledplayer == bs->client) return qfalse;
00688     if (BotNumActivePlayers() <= 1) return qfalse;
00689     if (!BotValidChatPosition(bs)) return qfalse;
00690     //
00691     if (BotVisibleEnemies(bs)) return qfalse;
00692     //
00693     EasyClientName(bs->lastkilledplayer, name, 32);
00694     //
00695     bs->chatto = CHAT_ALL;
00696     if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) {
00697         BotAI_BotInitialChat(bs, "kill_teammate", name, NULL);
00698         bs->chatto = CHAT_TEAM;
00699     }
00700     else
00701     {
00702         //don't chat in teamplay
00703         if (TeamPlayIsOn()) {
00704             trap_EA_Command(bs->client, "vtaunt");
00705             return qfalse;          // don't wait
00706         }
00707         //
00708         if (bs->enemydeathtype == MOD_GAUNTLET) {
00709             BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL);
00710         }
00711         else if (bs->enemydeathtype == MOD_RAILGUN) {
00712             BotAI_BotInitialChat(bs, "kill_rail", name, NULL);
00713         }
00714         else if (bs->enemydeathtype == MOD_TELEFRAG) {
00715             BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL);
00716         }
00717 #ifdef MISSIONPACK
00718         else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze"))
00719             BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL);
00720 #endif
00721         //choose between insult and praise
00722         else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) {
00723             BotAI_BotInitialChat(bs, "kill_insult", name, NULL);
00724         }
00725         else {
00726             BotAI_BotInitialChat(bs, "kill_praise", name, NULL);
00727         }
00728     }
00729     bs->lastchat_time = FloatTime();
00730     return qtrue;
00731 }
00732 
00733 /*
00734 ==================
00735 BotChat_EnemySuicide
00736 ==================
00737 */
00738 int BotChat_EnemySuicide(bot_state_t *bs) {
00739     char name[32];
00740     float rnd;
00741 
00742     if (bot_nochat.integer) return qfalse;
00743     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00744     if (BotNumActivePlayers() <= 1) return qfalse;
00745     //
00746     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1);
00747     //don't chat in teamplay
00748     if (TeamPlayIsOn()) return qfalse;
00749     // don't chat in tournament mode
00750     if (gametype == GT_TOURNAMENT) return qfalse;
00751     //if fast chat is off
00752     if (!bot_fastchat.integer) {
00753         if (random() > rnd) return qfalse;
00754     }
00755     if (!BotValidChatPosition(bs)) return qfalse;
00756     //
00757     if (BotVisibleEnemies(bs)) return qfalse;
00758     //
00759     if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32);
00760     else strcpy(name, "");
00761     BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL);
00762     bs->lastchat_time = FloatTime();
00763     bs->chatto = CHAT_ALL;
00764     return qtrue;
00765 }
00766 
00767 /*
00768 ==================
00769 BotChat_HitTalking
00770 ==================
00771 */
00772 int BotChat_HitTalking(bot_state_t *bs) {
00773     char name[32], *weap;
00774     int lasthurt_client;
00775     float rnd;
00776 
00777     if (bot_nochat.integer) return qfalse;
00778     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00779     if (BotNumActivePlayers() <= 1) return qfalse;
00780     lasthurt_client = g_entities[bs->client].client->lasthurt_client;
00781     if (!lasthurt_client) return qfalse;
00782     if (lasthurt_client == bs->client) return qfalse;
00783     //
00784     if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse;
00785     //
00786     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1);
00787     //don't chat in teamplay
00788     if (TeamPlayIsOn()) return qfalse;
00789     // don't chat in tournament mode
00790     if (gametype == GT_TOURNAMENT) return qfalse;
00791     //if fast chat is off
00792     if (!bot_fastchat.integer) {
00793         if (random() > rnd * 0.5) return qfalse;
00794     }
00795     if (!BotValidChatPosition(bs)) return qfalse;
00796     //
00797     ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name));
00798     weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client);
00799     //
00800     BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL);
00801     bs->lastchat_time = FloatTime();
00802     bs->chatto = CHAT_ALL;
00803     return qtrue;
00804 }
00805 
00806 /*
00807 ==================
00808 BotChat_HitNoDeath
00809 ==================
00810 */
00811 int BotChat_HitNoDeath(bot_state_t *bs) {
00812     char name[32], *weap;
00813     float rnd;
00814     int lasthurt_client;
00815     aas_entityinfo_t entinfo;
00816 
00817     lasthurt_client = g_entities[bs->client].client->lasthurt_client;
00818     if (!lasthurt_client) return qfalse;
00819     if (lasthurt_client == bs->client) return qfalse;
00820     //
00821     if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse;
00822     //
00823     if (bot_nochat.integer) return qfalse;
00824     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00825     if (BotNumActivePlayers() <= 1) return qfalse;
00826     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1);
00827     //don't chat in teamplay
00828     if (TeamPlayIsOn()) return qfalse;
00829     // don't chat in tournament mode
00830     if (gametype == GT_TOURNAMENT) return qfalse;
00831     //if fast chat is off
00832     if (!bot_fastchat.integer) {
00833         if (random() > rnd * 0.5) return qfalse;
00834     }
00835     if (!BotValidChatPosition(bs)) return qfalse;
00836     //
00837     if (BotVisibleEnemies(bs)) return qfalse;
00838     //
00839     BotEntityInfo(bs->enemy, &entinfo);
00840     if (EntityIsShooting(&entinfo)) return qfalse;
00841     //
00842     ClientName(lasthurt_client, name, sizeof(name));
00843     weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod);
00844     //
00845     BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL);
00846     bs->lastchat_time = FloatTime();
00847     bs->chatto = CHAT_ALL;
00848     return qtrue;
00849 }
00850 
00851 /*
00852 ==================
00853 BotChat_HitNoKill
00854 ==================
00855 */
00856 int BotChat_HitNoKill(bot_state_t *bs) {
00857     char name[32], *weap;
00858     float rnd;
00859     aas_entityinfo_t entinfo;
00860 
00861     if (bot_nochat.integer) return qfalse;
00862     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00863     if (BotNumActivePlayers() <= 1) return qfalse;
00864     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1);
00865     //don't chat in teamplay
00866     if (TeamPlayIsOn()) return qfalse;
00867     // don't chat in tournament mode
00868     if (gametype == GT_TOURNAMENT) return qfalse;
00869     //if fast chat is off
00870     if (!bot_fastchat.integer) {
00871         if (random() > rnd * 0.5) return qfalse;
00872     }
00873     if (!BotValidChatPosition(bs)) return qfalse;
00874     //
00875     if (BotVisibleEnemies(bs)) return qfalse;
00876     //
00877     BotEntityInfo(bs->enemy, &entinfo);
00878     if (EntityIsShooting(&entinfo)) return qfalse;
00879     //
00880     ClientName(bs->enemy, name, sizeof(name));
00881     weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod);
00882     //
00883     BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL);
00884     bs->lastchat_time = FloatTime();
00885     bs->chatto = CHAT_ALL;
00886     return qtrue;
00887 }
00888 
00889 /*
00890 ==================
00891 BotChat_Random
00892 ==================
00893 */
00894 int BotChat_Random(bot_state_t *bs) {
00895     float rnd;
00896     char name[32];
00897 
00898     if (bot_nochat.integer) return qfalse;
00899     if (BotIsObserver(bs)) return qfalse;
00900     if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse;
00901     // don't chat in tournament mode
00902     if (gametype == GT_TOURNAMENT) return qfalse;
00903     //don't chat when doing something important :)
00904     if (bs->ltgtype == LTG_TEAMHELP ||
00905         bs->ltgtype == LTG_TEAMACCOMPANY ||
00906         bs->ltgtype == LTG_RUSHBASE) return qfalse;
00907     //
00908     rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1);
00909     if (random() > bs->thinktime * 0.1) return qfalse;
00910     if (!bot_fastchat.integer) {
00911         if (random() > rnd) return qfalse;
00912         if (random() > 0.25) return qfalse;
00913     }
00914     if (BotNumActivePlayers() <= 1) return qfalse;
00915     //
00916     if (!BotValidChatPosition(bs)) return qfalse;
00917     //
00918     if (BotVisibleEnemies(bs)) return qfalse;
00919     //
00920     if (bs->lastkilledplayer == bs->client) {
00921         strcpy(name, BotRandomOpponentName(bs));
00922     }
00923     else {
00924         EasyClientName(bs->lastkilledplayer, name, sizeof(name));
00925     }
00926     if (TeamPlayIsOn()) {
00927         trap_EA_Command(bs->client, "vtaunt");
00928         return qfalse;          // don't wait
00929     }
00930     //
00931     if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) {
00932         BotAI_BotInitialChat(bs, "random_misc",
00933                     BotRandomOpponentName(bs),  // 0
00934                     name,                       // 1
00935                     "[invalid var]",            // 2
00936                     "[invalid var]",            // 3
00937                     BotMapTitle(),              // 4
00938                     BotRandomWeaponName(),      // 5
00939                     NULL);
00940     }
00941     else {
00942         BotAI_BotInitialChat(bs, "random_insult",
00943                     BotRandomOpponentName(bs),  // 0
00944                     name,                       // 1
00945                     "[invalid var]",            // 2
00946                     "[invalid var]",            // 3
00947                     BotMapTitle(),              // 4
00948                     BotRandomWeaponName(),      // 5
00949                     NULL);
00950     }
00951     bs->lastchat_time = FloatTime();
00952     bs->chatto = CHAT_ALL;
00953     return qtrue;
00954 }
00955 
00956 /*
00957 ==================
00958 BotChatTime
00959 ==================
00960 */
00961 float BotChatTime(bot_state_t *bs) {
00962     int cpm;
00963 
00964     cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000);
00965 
00966     return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm;
00967 }
00968 
00969 /*
00970 ==================
00971 BotChatTest
00972 ==================
00973 */
00974 void BotChatTest(bot_state_t *bs) {
00975 
00976     char name[32];
00977     char *weap;
00978     int num, i;
00979 
00980     num = trap_BotNumInitialChats(bs->cs, "game_enter");
00981     for (i = 0; i < num; i++)
00982     {
00983         BotAI_BotInitialChat(bs, "game_enter",
00984                     EasyClientName(bs->client, name, 32),   // 0
00985                     BotRandomOpponentName(bs),              // 1
00986                     "[invalid var]",                        // 2
00987                     "[invalid var]",                        // 3
00988                     BotMapTitle(),                          // 4
00989                     NULL);
00990         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
00991     }
00992     num = trap_BotNumInitialChats(bs->cs, "game_exit");
00993     for (i = 0; i < num; i++)
00994     {
00995         BotAI_BotInitialChat(bs, "game_exit",
00996                     EasyClientName(bs->client, name, 32),   // 0
00997                     BotRandomOpponentName(bs),              // 1
00998                     "[invalid var]",                        // 2
00999                     "[invalid var]",                        // 3
01000                     BotMapTitle(),                          // 4
01001                     NULL);
01002         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01003     }
01004     num = trap_BotNumInitialChats(bs->cs, "level_start");
01005     for (i = 0; i < num; i++)
01006     {
01007         BotAI_BotInitialChat(bs, "level_start",
01008                     EasyClientName(bs->client, name, 32),   // 0
01009                     NULL);
01010         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01011     }
01012     num = trap_BotNumInitialChats(bs->cs, "level_end_victory");
01013     for (i = 0; i < num; i++)
01014     {
01015         BotAI_BotInitialChat(bs, "level_end_victory",
01016                 EasyClientName(bs->client, name, 32),   // 0
01017                 BotRandomOpponentName(bs),              // 1
01018                 BotFirstClientInRankings(),             // 2
01019                 BotLastClientInRankings(),              // 3
01020                 BotMapTitle(),                          // 4
01021                 NULL);
01022         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01023     }
01024     num = trap_BotNumInitialChats(bs->cs, "level_end_lose");
01025     for (i = 0; i < num; i++)
01026     {
01027         BotAI_BotInitialChat(bs, "level_end_lose",
01028                 EasyClientName(bs->client, name, 32),   // 0
01029                 BotRandomOpponentName(bs),              // 1
01030                 BotFirstClientInRankings(),             // 2
01031                 BotLastClientInRankings(),              // 3
01032                 BotMapTitle(),                          // 4
01033                 NULL);
01034         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01035     }
01036     num = trap_BotNumInitialChats(bs->cs, "level_end");
01037     for (i = 0; i < num; i++)
01038     {
01039         BotAI_BotInitialChat(bs, "level_end",
01040                 EasyClientName(bs->client, name, 32),   // 0
01041                 BotRandomOpponentName(bs),              // 1
01042                 BotFirstClientInRankings(),             // 2
01043                 BotLastClientInRankings(),              // 3
01044                 BotMapTitle(),                          // 4
01045                 NULL);
01046         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01047     }
01048     EasyClientName(bs->lastkilledby, name, sizeof(name));
01049     num = trap_BotNumInitialChats(bs->cs, "death_drown");
01050     for (i = 0; i < num; i++)
01051     {
01052         //
01053         BotAI_BotInitialChat(bs, "death_drown", name, NULL);
01054         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01055     }
01056     num = trap_BotNumInitialChats(bs->cs, "death_slime");
01057     for (i = 0; i < num; i++)
01058     {
01059         BotAI_BotInitialChat(bs, "death_slime", name, NULL);
01060         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01061     }
01062     num = trap_BotNumInitialChats(bs->cs, "death_lava");
01063     for (i = 0; i < num; i++)
01064     {
01065         BotAI_BotInitialChat(bs, "death_lava", name, NULL);
01066         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01067     }
01068     num = trap_BotNumInitialChats(bs->cs, "death_cratered");
01069     for (i = 0; i < num; i++)
01070     {
01071         BotAI_BotInitialChat(bs, "death_cratered", name, NULL);
01072         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01073     }
01074     num = trap_BotNumInitialChats(bs->cs, "death_suicide");
01075     for (i = 0; i < num; i++)
01076     {
01077         BotAI_BotInitialChat(bs, "death_suicide", name, NULL);
01078         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01079     }
01080     num = trap_BotNumInitialChats(bs->cs, "death_telefrag");
01081     for (i = 0; i < num; i++)
01082     {
01083         BotAI_BotInitialChat(bs, "death_telefrag", name, NULL);
01084         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01085     }
01086     num = trap_BotNumInitialChats(bs->cs, "death_gauntlet");
01087     for (i = 0; i < num; i++)
01088     {
01089         BotAI_BotInitialChat(bs, "death_gauntlet",
01090                 name,                                               // 0
01091                 BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
01092                 NULL);
01093         trap_BotEnterChat(bs->cs, 0, CHAT_ALL);
01094     }
01095     num = trap_BotNumInitialChats(bs->cs, "death_rail");
01096     for (i = 0; i < num; i++)
01097     {
01098         BotAI_BotInitialChat(bs, "death_rail",
01099                 name,                                               // 0
01100                 BotWeaponNameForMeansOfDeath(bs->botdeathtype),     // 1
01101                 NULL);
01102         trap_BotEnterChat(bs->