mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-12-15 14:50:58 +00:00
552 lines
14 KiB
C
552 lines
14 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com)
|
|
|
|
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_vcmd.c
|
|
*
|
|
* desc: Quake3 bot AI
|
|
*
|
|
* $Archive: /MissionPack/code/game/ai_vcmd.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 "ai_vcmd.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"
|
|
|
|
|
|
typedef struct voiceCommand_s
|
|
{
|
|
char *cmd;
|
|
void (*func)(bot_state_t *bs, int client, int mode);
|
|
} voiceCommand_t;
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_GetFlag
|
|
==================
|
|
*/
|
|
void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) {
|
|
//
|
|
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;
|
|
}
|
|
//
|
|
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
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_Offense
|
|
==================
|
|
*/
|
|
void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) {
|
|
if ( gametype == GT_CTF
|
|
#ifdef MISSIONPACK
|
|
|| gametype == GT_1FCTF
|
|
#endif
|
|
) {
|
|
BotVoiceChat_GetFlag(bs, client, mode);
|
|
return;
|
|
}
|
|
#ifdef MISSIONPACK
|
|
if (gametype == GT_HARVESTER) {
|
|
//
|
|
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);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
//
|
|
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
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_Defend
|
|
==================
|
|
*/
|
|
void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) {
|
|
#ifdef MISSIONPACK
|
|
if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) {
|
|
//
|
|
switch(BotTeam(bs)) {
|
|
case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break;
|
|
case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break;
|
|
default: return;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
if (gametype == GT_CTF
|
|
#ifdef MISSIONPACK
|
|
|| gametype == GT_1FCTF
|
|
#endif
|
|
) {
|
|
//
|
|
switch(BotTeam(bs)) {
|
|
case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break;
|
|
case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break;
|
|
default: return;
|
|
}
|
|
}
|
|
else {
|
|
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_DEFENDKEYAREA;
|
|
//get the team goal 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
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_DefendFlag
|
|
==================
|
|
*/
|
|
void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) {
|
|
BotVoiceChat_Defend(bs, client, mode);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_Patrol
|
|
==================
|
|
*/
|
|
void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) {
|
|
//
|
|
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);
|
|
BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL);
|
|
//
|
|
BotSetTeamStatus(bs);
|
|
#ifdef DEBUG
|
|
BotPrintTeamGoal(bs);
|
|
#endif //DEBUG
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_Camp
|
|
==================
|
|
*/
|
|
void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) {
|
|
int areanum;
|
|
aas_entityinfo_t entinfo;
|
|
char netname[MAX_NETNAME];
|
|
|
|
//
|
|
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", EasyClientName(client, netname, sizeof(netname)), NULL);
|
|
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 = FloatTime() + TEAM_CAMP_TIME;
|
|
//the teammate that requested the camping
|
|
bs->teammate = client;
|
|
//not arrived yet
|
|
bs->arrive_time = 0;
|
|
//
|
|
BotSetTeamStatus(bs);
|
|
// remember last ordered task
|
|
BotRememberLastOrderedTask(bs);
|
|
#ifdef DEBUG
|
|
BotPrintTeamGoal(bs);
|
|
#endif //DEBUG
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_FollowMe
|
|
==================
|
|
*/
|
|
void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) {
|
|
int areanum;
|
|
aas_entityinfo_t entinfo;
|
|
char netname[MAX_NETNAME];
|
|
|
|
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 the other is not visible
|
|
if (bs->teamgoal.entitynum < 0) {
|
|
BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL);
|
|
trap_BotEnterChat(bs->cs, client, CHAT_TELL);
|
|
return;
|
|
}
|
|
//
|
|
bs->decisionmaker = client;
|
|
bs->ordered = qtrue;
|
|
bs->order_time = FloatTime();
|
|
//the team mate
|
|
bs->teammate = client;
|
|
//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 = FloatTime() + TEAM_ACCOMPANY_TIME;
|
|
//set the ltg type
|
|
bs->ltgtype = LTG_TEAMACCOMPANY;
|
|
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
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_FollowFlagCarrier
|
|
==================
|
|
*/
|
|
void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) {
|
|
int carrier;
|
|
|
|
carrier = BotTeamFlagCarrier(bs);
|
|
if (carrier >= 0)
|
|
BotVoiceChat_FollowMe(bs, carrier, mode);
|
|
#ifdef DEBUG
|
|
BotPrintTeamGoal(bs);
|
|
#endif //DEBUG
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_ReturnFlag
|
|
==================
|
|
*/
|
|
void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) {
|
|
//if not in CTF mode
|
|
if (
|
|
gametype != GT_CTF
|
|
#ifdef MISSIONPACK
|
|
&& gametype != GT_1FCTF
|
|
#endif
|
|
) {
|
|
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_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
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_StartLeader
|
|
==================
|
|
*/
|
|
void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) {
|
|
ClientName(client, bs->teamleader, sizeof(bs->teamleader));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_StopLeader
|
|
==================
|
|
*/
|
|
void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) {
|
|
char netname[MAX_MESSAGE_SIZE];
|
|
|
|
if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) {
|
|
bs->teamleader[0] = '\0';
|
|
notleader[client] = qtrue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_WhoIsLeader
|
|
==================
|
|
*/
|
|
void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) {
|
|
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)) {
|
|
BotAI_BotInitialChat(bs, "iamteamleader", NULL);
|
|
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
|
|
BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_WantOnDefense
|
|
==================
|
|
*/
|
|
void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) {
|
|
char netname[MAX_NETNAME];
|
|
int preference;
|
|
|
|
preference = BotGetTeamMateTaskPreference(bs, client);
|
|
preference &= ~TEAMTP_ATTACKER;
|
|
preference |= TEAMTP_DEFENDER;
|
|
BotSetTeamMateTaskPreference(bs, client, preference);
|
|
//
|
|
EasyClientName(client, netname, sizeof(netname));
|
|
BotAI_BotInitialChat(bs, "keepinmind", netname, NULL);
|
|
trap_BotEnterChat(bs->cs, client, CHAT_TELL);
|
|
BotVoiceChatOnly(bs, client, VOICECHAT_YES);
|
|
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotVoiceChat_WantOnOffense
|
|
==================
|
|
*/
|
|
void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) {
|
|
char netname[MAX_NETNAME];
|
|
int preference;
|
|
|
|
preference = BotGetTeamMateTaskPreference(bs, client);
|
|
preference &= ~TEAMTP_DEFENDER;
|
|
preference |= TEAMTP_ATTACKER;
|
|
BotSetTeamMateTaskPreference(bs, client, preference);
|
|
//
|
|
EasyClientName(client, netname, sizeof(netname));
|
|
BotAI_BotInitialChat(bs, "keepinmind", netname, NULL);
|
|
trap_BotEnterChat(bs->cs, client, CHAT_TELL);
|
|
BotVoiceChatOnly(bs, client, VOICECHAT_YES);
|
|
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
|
|
}
|
|
|
|
void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) {
|
|
}
|
|
|
|
voiceCommand_t voiceCommands[] = {
|
|
{VOICECHAT_GETFLAG, BotVoiceChat_GetFlag},
|
|
{VOICECHAT_OFFENSE, BotVoiceChat_Offense },
|
|
{VOICECHAT_DEFEND, BotVoiceChat_Defend },
|
|
{VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag },
|
|
{VOICECHAT_PATROL, BotVoiceChat_Patrol },
|
|
{VOICECHAT_CAMP, BotVoiceChat_Camp },
|
|
{VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe },
|
|
{VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier },
|
|
{VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag },
|
|
{VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader },
|
|
{VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader },
|
|
{VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader },
|
|
{VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense },
|
|
{VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense },
|
|
{NULL, BotVoiceChat_Dummy}
|
|
};
|
|
|
|
int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) {
|
|
int i, clientNum;
|
|
//int voiceOnly, color;
|
|
char *ptr, buf[MAX_MESSAGE_SIZE], *cmd;
|
|
|
|
if (!TeamPlayIsOn()) {
|
|
return qfalse;
|
|
}
|
|
|
|
if ( mode == SAY_ALL ) {
|
|
return qfalse; // don't do anything with voice chats to everyone
|
|
}
|
|
|
|
Q_strncpyz(buf, voiceChat, sizeof(buf));
|
|
cmd = buf;
|
|
for (; *cmd && *cmd > ' '; cmd++);
|
|
while (*cmd && *cmd <= ' ') *cmd++ = '\0';
|
|
//voiceOnly = atoi(ptr);
|
|
for (ptr = cmd; *cmd && *cmd > ' '; cmd++);
|
|
while (*cmd && *cmd <= ' ') *cmd++ = '\0';
|
|
clientNum = atoi(ptr);
|
|
for (; *cmd && *cmd > ' '; cmd++);
|
|
while (*cmd && *cmd <= ' ') *cmd++ = '\0';
|
|
//color = atoi(ptr);
|
|
|
|
if (!BotSameTeam(bs, clientNum)) {
|
|
return qfalse;
|
|
}
|
|
|
|
for (i = 0; voiceCommands[i].cmd; i++) {
|
|
if (!Q_stricmp(cmd, voiceCommands[i].cmd)) {
|
|
voiceCommands[i].func(bs, clientNum, mode);
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|