aboutsummaryrefslogtreecommitdiff
path: root/code/game/ai_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'code/game/ai_cmd.c')
-rw-r--r--code/game/ai_cmd.c1992
1 files changed, 1992 insertions, 0 deletions
diff --git a/code/game/ai_cmd.c b/code/game/ai_cmd.c
new file mode 100644
index 0000000..db95485
--- /dev/null
+++ b/code/game/ai_cmd.c
@@ -0,0 +1,1992 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: ai_cmd.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /MissionPack/code/game/ai_cmd.c $
+ *
+ *****************************************************************************/
+
+#include "g_local.h"
+#include "../botlib/botlib.h"
+#include "../botlib/be_aas.h"
+#include "../botlib/be_ea.h"
+#include "../botlib/be_ai_char.h"
+#include "../botlib/be_ai_chat.h"
+#include "../botlib/be_ai_gen.h"
+#include "../botlib/be_ai_goal.h"
+#include "../botlib/be_ai_move.h"
+#include "../botlib/be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+#include "ai_team.h"
+//
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+// for the voice chats
+#include "../../ui/menudef.h"
+
+int notleader[MAX_CLIENTS];
+
+#ifdef DEBUG
+/*
+==================
+BotPrintTeamGoal
+==================
+*/
+void BotPrintTeamGoal(bot_state_t *bs) {
+ char netname[MAX_NETNAME];
+ float t;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ t = bs->teamgoal_time - FloatTime();
+ switch(bs->ltgtype) {
+ case LTG_TEAMHELP:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_TEAMACCOMPANY:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_GETFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_RUSHBASE:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_RETURNFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t);
+ break;
+ }
+#ifdef MISSIONPACK
+ case LTG_ATTACKENEMYBASE:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_HARVEST:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t);
+ break;
+ }
+#endif
+ case LTG_DEFENDKEYAREA:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_GETITEM:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_KILL:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_CAMP:
+ case LTG_CAMPORDER:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_PATROL:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t);
+ break;
+ }
+ default:
+ {
+ if (bs->ctfroam_time > FloatTime()) {
+ t = bs->ctfroam_time - FloatTime();
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t);
+ }
+ else {
+ BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname);
+ }
+ }
+ }
+}
+#endif //DEBUG
+
+/*
+==================
+BotGetItemTeamGoal
+
+FIXME: add stuff like "upper rocket launcher"
+"the rl near the railgun", "lower grenade launcher" etc.
+==================
+*/
+int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) {
+ int i;
+
+ if (!strlen(goalname)) return qfalse;
+ i = -1;
+ do {
+ i = trap_BotGetLevelItemGoal(i, goalname, goal);
+ if (i > 0) {
+ //do NOT defend dropped items
+ if (goal->flags & GFL_DROPPED)
+ continue;
+ return qtrue;
+ }
+ } while(i > 0);
+ return qfalse;
+}
+
+/*
+==================
+BotGetMessageTeamGoal
+==================
+*/
+int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) {
+ bot_waypoint_t *cp;
+
+ if (BotGetItemTeamGoal(goalname, goal)) return qtrue;
+
+ cp = BotFindWayPoint(bs->checkpoints, goalname);
+ if (cp) {
+ memcpy(goal, &cp->goal, sizeof(bot_goal_t));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotGetTime
+==================
+*/
+float BotGetTime(bot_match_t *match) {
+ bot_match_t timematch;
+ char timestring[MAX_MESSAGE_SIZE];
+ float t;
+
+ //if the matched string has a time
+ if (match->subtype & ST_TIME) {
+ //get the time string
+ trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE);
+ //match it to find out if the time is in seconds or minutes
+ if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) {
+ if (timematch.type == MSG_FOREVER) {
+ t = 99999999.0f;
+ }
+ else if (timematch.type == MSG_FORAWHILE) {
+ t = 10 * 60; // 10 minutes
+ }
+ else if (timematch.type == MSG_FORALONGTIME) {
+ t = 30 * 60; // 30 minutes
+ }
+ else {
+ trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE);
+ if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60;
+ else if (timematch.type == MSG_SECONDS) t = atof(timestring);
+ else t = 0;
+ }
+ //if there's a valid time
+ if (t > 0) return FloatTime() + t;
+ }
+ }
+ return 0;
+}
+
+/*
+==================
+FindClientByName
+==================
+*/
+int FindClientByName(char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclients;
+
+ if (!maxclients)
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ ClientName(i, buf, sizeof(buf));
+ if (!Q_stricmp(buf, name)) return i;
+ }
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ ClientName(i, buf, sizeof(buf));
+ if (stristr(buf, name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+FindEnemyByName
+==================
+*/
+int FindEnemyByName(bot_state_t *bs, char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclients;
+
+ if (!maxclients)
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ if (BotSameTeam(bs, i)) continue;
+ ClientName(i, buf, sizeof(buf));
+ if (!Q_stricmp(buf, name)) return i;
+ }
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ if (BotSameTeam(bs, i)) continue;
+ ClientName(i, buf, sizeof(buf));
+ if (stristr(buf, name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+NumPlayersOnSameTeam
+==================
+*/
+int NumPlayersOnSameTeam(bot_state_t *bs) {
+ int i, num;
+ char buf[MAX_INFO_STRING];
+ static int maxclients;
+
+ if (!maxclients)
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ num = 0;
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING);
+ if (strlen(buf)) {
+ if (BotSameTeam(bs, i+1)) num++;
+ }
+ }
+ return num;
+}
+
+/*
+==================
+TeamPlayIsOn
+==================
+*/
+int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) {
+ char keyarea[MAX_MESSAGE_SIZE];
+ int patrolflags;
+ bot_waypoint_t *wp, *newwp, *newpatrolpoints;
+ bot_match_t keyareamatch;
+ bot_goal_t goal;
+
+ newpatrolpoints = NULL;
+ patrolflags = 0;
+ //
+ trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE);
+ //
+ while(1) {
+ if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) {
+ trap_EA_SayTeam(bs->client, "what do you say?");
+ BotFreeWaypoints(newpatrolpoints);
+ bs->patrolpoints = NULL;
+ return qfalse;
+ }
+ trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE);
+ if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL);
+ //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
+ BotFreeWaypoints(newpatrolpoints);
+ bs->patrolpoints = NULL;
+ return qfalse;
+ }
+ //create a new waypoint
+ newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum);
+ if (!newwp)
+ break;
+ //add the waypoint to the patrol points
+ newwp->next = NULL;
+ for (wp = newpatrolpoints; wp && wp->next; wp = wp->next);
+ if (!wp) {
+ newpatrolpoints = newwp;
+ newwp->prev = NULL;
+ }
+ else {
+ wp->next = newwp;
+ newwp->prev = wp;
+ }
+ //
+ if (keyareamatch.subtype & ST_BACK) {
+ patrolflags = PATROL_LOOP;
+ break;
+ }
+ else if (keyareamatch.subtype & ST_REVERSE) {
+ patrolflags = PATROL_REVERSE;
+ break;
+ }
+ else if (keyareamatch.subtype & ST_MORE) {
+ trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE);
+ }
+ else {
+ break;
+ }
+ }
+ //
+ if (!newpatrolpoints || !newpatrolpoints->next) {
+ trap_EA_SayTeam(bs->client, "I need more key points to patrol\n");
+ BotFreeWaypoints(newpatrolpoints);
+ newpatrolpoints = NULL;
+ return qfalse;
+ }
+ //
+ BotFreeWaypoints(bs->patrolpoints);
+ bs->patrolpoints = newpatrolpoints;
+ //
+ bs->curpatrolpoint = bs->patrolpoints;
+ bs->patrolflags = patrolflags;
+ //
+ return qtrue;
+}
+
+/*
+==================
+BotAddressedToBot
+==================
+*/
+int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) {
+ char addressedto[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ char name[MAX_MESSAGE_SIZE];
+ char botname[128];
+ int client;
+ bot_match_t addresseematch;
+
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientOnSameTeamFromName(bs, netname);
+ if (client < 0) return qfalse;
+ //if the message is addressed to someone
+ if (match->subtype & ST_ADDRESSED) {
+ trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto));
+ //the name of this bot
+ ClientName(bs->client, botname, 128);
+ //
+ while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) {
+ if (addresseematch.type == MSG_EVERYONE) {
+ return qtrue;
+ }
+ else if (addresseematch.type == MSG_MULTIPLENAMES) {
+ trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name));
+ if (strlen(name)) {
+ if (stristr(botname, name)) return qtrue;
+ if (stristr(bs->subteam, name)) return qtrue;
+ }
+ trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE);
+ }
+ else {
+ trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE);
+ if (strlen(name)) {
+ if (stristr(botname, name)) return qtrue;
+ if (stristr(bs->subteam, name)) return qtrue;
+ }
+ break;
+ }
+ }
+ //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto);
+ //trap_EA_Say(bs->client, buf);
+ return qfalse;
+ }
+ else {
+ bot_match_t tellmatch;
+
+ tellmatch.type = 0;
+ //if this message wasn't directed solely to this bot
+ if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) ||
+ tellmatch.type != MSG_CHATTELL) {
+ //make sure not everyone reacts to this message
+ if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotGPSToPosition
+==================
+*/
+int BotGPSToPosition(char *buf, vec3_t position) {
+ int i, j = 0;
+ int num, sign;
+
+ for (i = 0; i < 3; i++) {
+ num = 0;
+ while(buf[j] == ' ') j++;
+ if (buf[j] == '-') {
+ j++;
+ sign = -1;
+ }
+ else {
+ sign = 1;
+ }
+ while (buf[j]) {
+ if (buf[j] >= '0' && buf[j] <= '9') {
+ num = num * 10 + buf[j] - '0';
+ j++;
+ }
+ else {
+ j++;
+ break;
+ }
+ }
+ BotAI_Print(PRT_MESSAGE, "%d\n", sign * num);
+ position[i] = (float) sign * num;
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotMatch_HelpAccompany
+==================
+*/
+void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) {
+ int client, other, areanum;
+ char teammate[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ char itemname[MAX_MESSAGE_SIZE];
+ bot_match_t teammatematch;
+ aas_entityinfo_t entinfo;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the team mate name
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ //get the client to help
+ if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) &&
+ //if someone asks for him or herself
+ teammatematch.type == MSG_ME) {
+ //get the netname
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ other = qfalse;
+ }
+ else {
+ //asked for someone else
+ client = FindClientByName(teammate);
+ //if this is the bot self
+ if (client == bs->client) {
+ other = qfalse;
+ }
+ else if (!BotSameTeam(bs, client)) {
+ //FIXME: say "I don't help the enemy"
+ return;
+ }
+ else {
+ other = qtrue;
+ }
+ }
+ //if the bot doesn't know who to help (FindClientByName returned -1)
+ if (client < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ return;
+ }
+ //don't help or accompany yourself
+ if (client == bs->client) {
+ return;
+ }
+ //
+ bs->teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum) {// && trap_AAS_AreaReachability(areanum)) {
+ bs->teamgoal.entitynum = client;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ }
+ //if no teamgoal yet
+ if (bs->teamgoal.entitynum < 0) {
+ //if near an item
+ if (match->subtype & ST_NEARITEM) {
+ //get the match variable
+ trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ }
+ }
+ //
+ if (bs->teamgoal.entitynum < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TEAM);
+ return;
+ }
+ //the team mate
+ bs->teammate = client;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = ClientFromName(netname);
+ //the team mate who ordered
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //last time the team mate was assumed visible
+ bs->teammatevisible_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the ltg type
+ if (match->type == MSG_HELP) {
+ bs->ltgtype = LTG_TEAMHELP;
+ if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME;
+ }
+ else {
+ bs->ltgtype = LTG_TEAMACCOMPANY;
+ if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
+ bs->formation_dist = 3.5 * 32; //3.5 meter
+ bs->arrive_time = 0;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+ }
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_DefendKeyArea
+==================
+*/
+void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) {
+ char itemname[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the match variable
+ trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = ClientFromName(netname);
+ //the team mate who ordered
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_DEFENDKEYAREA;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time
+ if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
+ //away from defending
+ bs->defendaway_time = 0;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_GetItem
+==================
+*/
+void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) {
+ char itemname[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the match variable
+ trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientOnSameTeamFromName(bs, netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_GETITEM;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME;
+ //
+ BotSetTeamStatus(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_Camp
+==================
+*/
+void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) {
+ int client, areanum;
+ char netname[MAX_MESSAGE_SIZE];
+ char itemname[MAX_MESSAGE_SIZE];
+ aas_entityinfo_t entinfo;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //asked for someone else
+ client = FindClientByName(netname);
+ //if there's no valid client with this name
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //get the match variable
+ trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname));
+ //in CTF it could be the base
+ if (match->subtype & ST_THERE) {
+ //camp at the spot the bot is currently standing
+ bs->teamgoal.entitynum = bs->entitynum;
+ bs->teamgoal.areanum = bs->areanum;
+ VectorCopy(bs->origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ else if (match->subtype & ST_HERE) {
+ //if this is the bot self
+ if (client == bs->client) return;
+ //
+ bs->teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum) {// && trap_AAS_AreaReachability(areanum)) {
+ //NOTE: just assume the bot knows where the person is
+ //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) {
+ bs->teamgoal.entitynum = client;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ //}
+ }
+ }
+ //if the other is not visible
+ if (bs->teamgoal.entitynum < 0) {
+ BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ return;
+ }
+ }
+ else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //client = ClientFromName(netname);
+ //trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ return;
+ }
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_CAMPORDER;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time
+ if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME;
+ //not arrived yet
+ bs->arrive_time = 0;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_Patrol
+==================
+*/
+void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the patrol waypoints
+ if (!BotGetPatrolWaypoints(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_PATROL;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time if not set already
+ if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_GetFlag
+==================
+*/
+void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (gametype == GT_CTF) {
+ if (!ctf_redflag.areanum || !ctf_blueflag.areanum)
+ return;
+ }
+#ifdef MISSIONPACK
+ else if (gametype == GT_1FCTF) {
+ if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum)
+ return;
+ }
+#endif
+ else {
+ return;
+ }
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_GETFLAG;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
+ // get an alternate route in ctf
+ if (gametype == GT_CTF) {
+ //get an alternative route goal towards the enemy base
+ BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
+ }
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_AttackEnemyBase
+==================
+*/
+void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (gametype == GT_CTF) {
+ BotMatch_GetFlag(bs, match);
+ }
+#ifdef MISSIONPACK
+ else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) {
+ if (!redobelisk.areanum || !blueobelisk.areanum)
+ return;
+ }
+#endif
+ else {
+ return;
+ }
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_ATTACKENEMYBASE;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME;
+ bs->attackaway_time = 0;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+#ifdef MISSIONPACK
+/*
+==================
+BotMatch_Harvest
+==================
+*/
+void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (gametype == GT_HARVESTER) {
+ if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum)
+ return;
+ }
+ else {
+ return;
+ }
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_HARVEST;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME;
+ bs->harvestaway_time = 0;
+ //
+ BotSetTeamStatus(bs);
+ // remember last ordered task
+ BotRememberLastOrderedTask(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+#endif
+
+/*
+==================
+BotMatch_RushBase
+==================
+*/
+void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (gametype == GT_CTF) {
+ if (!ctf_redflag.areanum || !ctf_blueflag.areanum)
+ return;
+ }
+#ifdef MISSIONPACK
+ else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) {
+ if (!redobelisk.areanum || !blueobelisk.areanum)
+ return;
+ }
+#endif
+ else {
+ return;
+ }
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_RUSHBASE;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+ //
+ BotSetTeamStatus(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_TaskPreference
+==================
+*/
+void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_NETNAME];
+ char teammatename[MAX_MESSAGE_SIZE];
+ int teammate, preference;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) != 0) return;
+
+ trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename));
+ teammate = ClientFromName(teammatename);
+ if (teammate < 0) return;
+
+ preference = BotGetTeamMateTaskPreference(bs, teammate);
+ switch(match->subtype)
+ {
+ case ST_DEFENDER:
+ {
+ preference &= ~TEAMTP_ATTACKER;
+ preference |= TEAMTP_DEFENDER;
+ break;
+ }
+ case ST_ATTACKER:
+ {
+ preference &= ~TEAMTP_DEFENDER;
+ preference |= TEAMTP_ATTACKER;
+ break;
+ }
+ case ST_ROAMER:
+ {
+ preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER);
+ break;
+ }
+ }
+ BotSetTeamMateTaskPreference(bs, teammate, preference);
+ //
+ EasyClientName(teammate, teammatename, sizeof(teammatename));
+ BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL);
+ trap_BotEnterChat(bs->cs, teammate, CHAT_TELL);
+ BotVoiceChatOnly(bs, teammate, VOICECHAT_YES);
+ trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
+}
+
+/*
+==================
+BotMatch_ReturnFlag
+==================
+*/
+void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ //if not in CTF mode
+ if (
+ gametype != GT_CTF
+#ifdef MISSIONPACK
+ && gametype != GT_1FCTF
+#endif
+ )
+ return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match))
+ return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //
+ client = FindClientByName(netname);
+ //
+ bs->decisionmaker = client;
+ bs->ordered = qtrue;
+ bs->order_time = FloatTime();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_RETURNFLAG;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME;
+ bs->rushbaseaway_time = 0;
+ //
+ BotSetTeamStatus(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_JoinSubteam
+==================
+*/
+void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) {
+ char teammate[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the sub team name
+ trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate));
+ //set the sub team name
+ strncpy(bs->subteam, teammate, 32);
+ bs->subteam[31] = '\0';
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL);
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+}
+
+/*
+==================
+BotMatch_LeaveSubteam
+==================
+*/
+void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ if (strlen(bs->subteam))
+ {
+ BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL);
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ } //end if
+ strcpy(bs->subteam, "");
+}
+
+/*
+==================
+BotMatch_LeaveSubteam
+==================
+*/
+void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) {
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ if (strlen(bs->subteam)) {
+ BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "noteam", NULL);
+ }
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_CheckPoint
+==================
+*/
+void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) {
+ int areanum, client;
+ char buf[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ vec3_t position;
+ bot_waypoint_t *cp;
+
+ if (!TeamPlayIsOn()) return;
+ //
+ trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE);
+ VectorClear(position);
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ //BotGPSToPosition(buf, position);
+ sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]);
+ position[2] += 0.5;
+ areanum = BotPointAreaNum(position);
+ if (!areanum) {
+ if (BotAddressedToBot(bs, match)) {
+ BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ }
+ return;
+ }
+ //
+ trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE);
+ //check if there already exists a checkpoint with this name
+ cp = BotFindWayPoint(bs->checkpoints, buf);
+ if (cp) {
+ if (cp->next) cp->next->prev = cp->prev;
+ if (cp->prev) cp->prev->next = cp->next;
+ else bs->checkpoints = cp->next;
+ cp->inuse = qfalse;
+ }
+ //create a new check point
+ cp = BotCreateWayPoint(buf, position, areanum);
+ //add the check point to the bot's known chech points
+ cp->next = bs->checkpoints;
+ if (bs->checkpoints) bs->checkpoints->prev = cp;
+ bs->checkpoints = cp;
+ //
+ if (BotAddressedToBot(bs, match)) {
+ Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0],
+ cp->goal.origin[1],
+ cp->goal.origin[2]);
+
+ BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ }
+}
+
+/*
+==================
+BotMatch_FormationSpace
+==================
+*/
+void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) {
+ char buf[MAX_MESSAGE_SIZE];
+ float space;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE);
+ //if it's the distance in feet
+ if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf);
+ //else it's in meters
+ else space = 32 * atof(buf);
+ //check if the formation intervening space is valid
+ if (space < 48 || space > 500) space = 100;
+ bs->formation_dist = space;
+}
+
+/*
+==================
+BotMatch_Dismiss
+==================
+*/
+void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ //
+ bs->decisionmaker = client;
+ //
+ bs->ltgtype = 0;
+ bs->lead_time = 0;
+ bs->lastgoal_ltgtype = 0;
+ //
+ BotAI_BotInitialChat(bs, "dismissed", NULL);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+}
+
+/*
+==================
+BotMatch_Suicide
+==================
+*/
+void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_EA_Command(bs->client, "kill");
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ //
+ BotVoiceChat(bs, client, VOICECHAT_TAUNT);
+ trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
+}
+
+/*
+==================
+BotMatch_StartTeamLeaderShip
+==================
+*/
+void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char teammate[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //if chats for him or herself
+ if (match->subtype & ST_I) {
+ //get the team mate that will be the team leader
+ trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate));
+ strncpy(bs->teamleader, teammate, sizeof(bs->teamleader));
+ bs->teamleader[sizeof(bs->teamleader)-1] = '\0';
+ }
+ //chats for someone else
+ else {
+ //get the team mate that will be the team leader
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ client = FindClientByName(teammate);
+ if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader));
+ }
+}
+
+/*
+==================
+BotMatch_StopTeamLeaderShip
+==================
+*/
+void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char teammate[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //get the team mate that stops being the team leader
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ //if chats for him or herself
+ if (match->subtype & ST_I) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = FindClientByName(netname);
+ }
+ //chats for someone else
+ else {
+ client = FindClientByName(teammate);
+ } //end else
+ if (client >= 0) {
+ if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) {
+ bs->teamleader[0] = '\0';
+ notleader[client] = qtrue;
+ }
+ }
+}
+
+/*
+==================
+BotMatch_WhoIsTeamLeader
+==================
+*/
+void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ //if this bot IS the team leader
+ if (!Q_stricmp(netname, bs->teamleader)) {
+ trap_EA_SayTeam(bs->client, "I'm the team leader\n");
+ }
+}
+
+/*
+==================
+BotMatch_WhatAreYouDoing
+==================
+*/
+void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ char goalname[MAX_MESSAGE_SIZE];
+ int client;
+
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ switch(bs->ltgtype) {
+ case LTG_TEAMHELP:
+ {
+ EasyClientName(bs->teammate, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "helping", netname, NULL);
+ break;
+ }
+ case LTG_TEAMACCOMPANY:
+ {
+ EasyClientName(bs->teammate, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "accompanying", netname, NULL);
+ break;
+ }
+ case LTG_DEFENDKEYAREA:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_BotInitialChat(bs, "defending", goalname, NULL);
+ break;
+ }
+ case LTG_GETITEM:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL);
+ break;
+ }
+ case LTG_KILL:
+ {
+ ClientName(bs->teamgoal.entitynum, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "killing", netname, NULL);
+ break;
+ }
+ case LTG_CAMP:
+ case LTG_CAMPORDER:
+ {
+ BotAI_BotInitialChat(bs, "camping", NULL);
+ break;
+ }
+ case LTG_PATROL:
+ {
+ BotAI_BotInitialChat(bs, "patrolling", NULL);
+ break;
+ }
+ case LTG_GETFLAG:
+ {
+ BotAI_BotInitialChat(bs, "capturingflag", NULL);
+ break;
+ }
+ case LTG_RUSHBASE:
+ {
+ BotAI_BotInitialChat(bs, "rushingbase", NULL);
+ break;
+ }
+ case LTG_RETURNFLAG:
+ {
+ BotAI_BotInitialChat(bs, "returningflag", NULL);
+ break;
+ }
+#ifdef MISSIONPACK
+ case LTG_ATTACKENEMYBASE:
+ {
+ BotAI_BotInitialChat(bs, "attackingenemybase", NULL);
+ break;
+ }
+ case LTG_HARVEST:
+ {
+ BotAI_BotInitialChat(bs, "harvesting", NULL);
+ break;
+ }
+#endif
+ default:
+ {
+ BotAI_BotInitialChat(bs, "roaming", NULL);
+ break;
+ }
+ }
+ //chat what the bot is doing
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+}
+
+/*
+==================
+BotMatch_WhatIsMyCommand
+==================
+*/
+void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_NETNAME];
+
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) != 0) return;
+ bs->forceorders = qtrue;
+}
+
+/*
+==================
+BotNearestVisibleItem
+==================
+*/
+float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) {
+ int i;
+ char name[64];
+ bot_goal_t tmpgoal;
+ float dist, bestdist;
+ vec3_t dir;
+ bsp_trace_t trace;
+
+ bestdist = 999999;
+ i = -1;
+ do {
+ i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal);
+ trap_BotGoalName(tmpgoal.number, name, sizeof(name));
+ if (Q_stricmp(itemname, name) != 0)
+ continue;
+ VectorSubtract(tmpgoal.origin, bs->origin, dir);
+ dist = VectorLength(dir);
+ if (dist < bestdist) {
+ //trace from start to end
+ BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ if (trace.fraction >= 1.0) {
+ bestdist = dist;
+ memcpy(goal, &tmpgoal, sizeof(bot_goal_t));
+ }
+ }
+ } while(i > 0);
+ return bestdist;
+}
+
+/*
+==================
+BotMatch_WhereAreYou
+==================
+*/
+void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) {
+ float dist, bestdist;
+ int i, bestitem, redtt, bluett, client;
+ bot_goal_t goal;
+ char netname[MAX_MESSAGE_SIZE];
+ char *nearbyitems[] = {
+ "Shotgun",
+ "Grenade Launcher",
+ "Rocket Launcher",
+ "Plasmagun",
+ "Railgun",
+ "Lightning Gun",
+ "BFG10K",
+ "Quad Damage",
+ "Regeneration",
+ "Battle Suit",
+ "Speed",
+ "Invisibility",
+ "Flight",
+ "Armor",
+ "Heavy Armor",
+ "Red Flag",
+ "Blue Flag",
+#ifdef MISSIONPACK
+ "Nailgun",
+ "Prox Launcher",
+ "Chaingun",
+ "Scout",
+ "Guard",
+ "Doubler",
+ "Ammo Regen",
+ "Neutral Flag",
+ "Red Obelisk",
+ "Blue Obelisk",
+ "Neutral Obelisk",
+#endif
+ NULL
+ };
+ //
+ if (!TeamPlayIsOn())
+ return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match))
+ return;
+
+ bestitem = -1;
+ bestdist = 999999;
+ for (i = 0; nearbyitems[i]; i++) {
+ dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal);
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestitem = i;
+ }
+ }
+ if (bestitem != -1) {
+ if (gametype == GT_CTF
+#ifdef MISSIONPACK
+ || gametype == GT_1FCTF
+#endif
+ ) {
+ redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT);
+ bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT);
+ if (redtt < (redtt + bluett) * 0.4) {
+ BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL);
+ }
+ else if (bluett < (redtt + bluett) * 0.4) {
+ BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL);
+ }
+ }
+#ifdef MISSIONPACK
+ else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) {
+ redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT);
+ bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT);
+ if (redtt < (redtt + bluett) * 0.4) {
+ BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL);
+ }
+ else if (bluett < (redtt + bluett) * 0.4) {
+ BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL);
+ }
+ }
+#endif
+ else {
+ BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL);
+ }
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ }
+}
+
+/*
+==================
+BotMatch_LeadTheWay
+==================
+*/
+void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) {
+ aas_entityinfo_t entinfo;
+ char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE];
+ int client, areanum, other;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //if someone asks for someone else
+ if (match->subtype & ST_SOMEONE) {
+ //get the team mate name
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ client = FindClientByName(teammate);
+ //if this is the bot self
+ if (client == bs->client) {
+ other = qfalse;
+ }
+ else if (!BotSameTeam(bs, client)) {
+ //FIXME: say "I don't help the enemy"
+ return;
+ }
+ else {
+ other = qtrue;
+ }
+ }
+ else {
+ //get the netname
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ other = qfalse;
+ }
+ //if the bot doesn't know who to help (FindClientByName returned -1)
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //
+ bs->lead_teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum) { // && trap_AAS_AreaReachability(areanum)) {
+ bs->lead_teamgoal.entitynum = client;
+ bs->lead_teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->lead_teamgoal.origin);
+ VectorSet(bs->lead_teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8);
+ }
+ }
+
+ if (bs->teamgoal.entitynum < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ bs->lead_teammate = client;
+ bs->lead_time = FloatTime() + TEAM_LEAD_TIME;
+ bs->leadvisible_time = 0;
+ bs->leadmessage_time = -(FloatTime() + 2 * random());
+}
+
+/*
+==================
+BotMatch_Kill
+==================
+*/
+void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) {
+ char enemy[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+
+ trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy));
+ //
+ client = FindEnemyByName(bs, enemy);
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", enemy, NULL);
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ trap_BotEnterChat(bs->cs, client, CHAT_TELL);
+ return;
+ }
+ bs->teamgoal.entitynum = client;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = FloatTime() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_KILL;
+ //set the team goal time
+ bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE;
+ //
+ BotSetTeamStatus(bs);
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_CTF
+==================
+*/
+void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) {
+
+ char flag[128], netname[MAX_NETNAME];
+
+ if (gametype == GT_CTF) {
+ trap_BotMatchVariable(match, FLAG, flag, sizeof(flag));
+ if (match->subtype & ST_GOTFLAG) {
+ if (!Q_stricmp(flag, "red")) {
+ bs->redflagstatus = 1;
+ if (BotTeam(bs) == TEAM_BLUE) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ bs->flagcarrier = ClientFromName(netname);
+ }
+ }
+ else {
+ bs->blueflagstatus = 1;
+ if (BotTeam(bs) == TEAM_RED) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ bs->flagcarrier = ClientFromName(netname);
+ }
+ }
+ bs->flagstatuschanged = 1;
+ bs->lastflagcapture_time = FloatTime();
+ }
+ else if (match->subtype & ST_CAPTUREDFLAG) {
+ bs->redflagstatus = 0;
+ bs->blueflagstatus = 0;
+ bs->flagcarrier = 0;
+ bs->flagstatuschanged = 1;
+ }
+ else if (match->subtype & ST_RETURNEDFLAG) {
+ if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0;
+ else bs->blueflagstatus = 0;
+ bs->flagstatuschanged = 1;
+ }
+ }
+#ifdef MISSIONPACK
+ else if (gametype == GT_1FCTF) {
+ if (match->subtype & ST_1FCTFGOTFLAG) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ bs->flagcarrier = ClientFromName(netname);
+ }
+ }
+#endif
+}
+
+void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char netname[MAX_NETNAME];
+
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = FindClientByName(netname);
+ if (client >= 0) {
+ notleader[client] = qfalse;
+ }
+ //NOTE: eliza chats will catch this
+ //Com_sprintf(buf, sizeof(buf), "heya %s", netname);
+ //EA_Say(bs->client, buf);
+}
+
+void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char netname[MAX_NETNAME];
+
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = FindClientByName(netname);
+ if (!BotSameTeam(bs, client))
+ return;
+ Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader));
+}
+
+/*
+==================
+BotMatchMessage
+==================
+*/
+int BotMatchMessage(bot_state_t *bs, char *message) {
+ bot_match_t match;
+
+ match.type = 0;
+ //if it is an unknown message
+ if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC
+ |MTCONTEXT_INITIALTEAMCHAT
+ |MTCONTEXT_CTF)) {
+ return qfalse;
+ }
+ //react to the found message
+ switch(match.type)
+ {
+ case MSG_HELP: //someone calling for help
+ case MSG_ACCOMPANY: //someone calling for company
+ {
+ BotMatch_HelpAccompany(bs, &match);
+ break;
+ }
+ case MSG_DEFENDKEYAREA: //teamplay defend a key area
+ {
+ BotMatch_DefendKeyArea(bs, &match);
+ break;
+ }
+ case MSG_CAMP: //camp somewhere
+ {
+ BotMatch_Camp(bs, &match);
+ break;
+ }
+ case MSG_PATROL: //patrol between several key areas
+ {
+ BotMatch_Patrol(bs, &match);
+ break;
+ }
+ //CTF & 1FCTF
+ case MSG_GETFLAG: //ctf get the enemy flag
+ {
+ BotMatch_GetFlag(bs, &match);
+ break;
+ }
+#ifdef MISSIONPACK
+ //CTF & 1FCTF & Obelisk & Harvester
+ case MSG_ATTACKENEMYBASE:
+ {
+ BotMatch_AttackEnemyBase(bs, &match);
+ break;
+ }
+ //Harvester
+ case MSG_HARVEST:
+ {
+ BotMatch_Harvest(bs, &match);
+ break;
+ }
+#endif
+ //CTF & 1FCTF & Harvester
+ case MSG_RUSHBASE: //ctf rush to the base
+ {
+ BotMatch_RushBase(bs, &match);
+ break;
+ }
+ //CTF & 1FCTF
+ case MSG_RETURNFLAG:
+ {
+ BotMatch_ReturnFlag(bs, &match);
+ break;
+ }
+ //CTF & 1FCTF & Obelisk & Harvester
+ case MSG_TASKPREFERENCE:
+ {
+ BotMatch_TaskPreference(bs, &match);
+ break;
+ }
+ //CTF & 1FCTF
+ case MSG_CTF:
+ {
+ BotMatch_CTF(bs, &match);
+ break;
+ }
+ case MSG_GETITEM:
+ {
+ BotMatch_GetItem(bs, &match);
+ break;
+ }
+ case MSG_JOINSUBTEAM: //join a sub team
+ {
+ BotMatch_JoinSubteam(bs, &match);
+ break;
+ }
+ case MSG_LEAVESUBTEAM: //leave a sub team
+ {
+ BotMatch_LeaveSubteam(bs, &match);
+ break;
+ }
+ case MSG_WHICHTEAM:
+ {
+ BotMatch_WhichTeam(bs, &match);
+ break;
+ }
+ case MSG_CHECKPOINT: //remember a check point
+ {
+ BotMatch_CheckPoint(bs, &match);
+ break;
+ }
+ case MSG_CREATENEWFORMATION: //start the creation of a new formation
+ {
+ trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged");
+ break;
+ }
+ case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation
+ {
+ trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged");
+ break;
+ }
+ case MSG_FORMATIONSPACE: //set the formation space
+ {
+ BotMatch_FormationSpace(bs, &match);
+ break;
+ }
+ case MSG_DOFORMATION: //form a certain formation
+ {
+ break;
+ }
+ case MSG_DISMISS: //dismiss someone
+ {
+ BotMatch_Dismiss(bs, &match);
+ break;
+ }
+ case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader
+ {
+ BotMatch_StartTeamLeaderShip(bs, &match);
+ break;
+ }
+ case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader
+ {
+ BotMatch_StopTeamLeaderShip(bs, &match);
+ break;
+ }
+ case MSG_WHOISTEAMLAEDER:
+ {
+ BotMatch_WhoIsTeamLeader(bs, &match);
+ break;
+ }
+ case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing
+ {
+ BotMatch_WhatAreYouDoing(bs, &match);
+ break;
+ }
+ case MSG_WHATISMYCOMMAND:
+ {
+ BotMatch_WhatIsMyCommand(bs, &match);
+ break;
+ }
+ case MSG_WHEREAREYOU:
+ {
+ BotMatch_WhereAreYou(bs, &match);
+ break;
+ }
+ case MSG_LEADTHEWAY:
+ {
+ BotMatch_LeadTheWay(bs, &match);
+ break;
+ }
+ case MSG_KILL:
+ {
+ BotMatch_Kill(bs, &match);
+ break;
+ }
+ case MSG_ENTERGAME: //someone entered the game
+ {
+ BotMatch_EnterGame(bs, &match);
+ break;
+ }
+ case MSG_NEWLEADER:
+ {
+ BotMatch_NewLeader(bs, &match);
+ break;
+ }
+ case MSG_WAIT:
+ {
+ break;
+ }
+ case MSG_SUICIDE:
+ {
+ BotMatch_Suicide(bs, &match);
+ break;
+ }
+ default:
+ {
+ BotAI_Print(PRT_MESSAGE, "unknown match type\n");
+ break;
+ }
+ }
+ return qtrue;
+}