2011-02-18 14:31:32 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
2015-11-13 22:41:34 +00:00
|
|
|
Copyright (C) 2002-2015 Q3Rally Team (Per Thormann - q3rally@gmail.com)
|
2011-02-18 14:31:32 +00:00
|
|
|
|
|
|
|
This file is part of q3rally source code.
|
|
|
|
|
|
|
|
q3rally 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.
|
|
|
|
|
|
|
|
q3rally 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 q3rally; 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;
|
|
|
|
}
|