reaction/code/game/ai_dmnet.c

2949 lines
84 KiB
C
Raw Normal View History

//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
2001-12-31 16:28:42 +00:00
// $Log$
2002-07-02 20:22:35 +00:00
// Revision 1.31 2002/07/02 20:22:35 jbravo
// Changed the files to use the right ui.
//
// Revision 1.30 2002/06/16 20:06:13 jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.29 2002/06/16 17:37:59 jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
2002-06-05 19:16:22 +00:00
// Revision 1.28 2002/06/05 19:16:22 makro
// Made bots choose the ssg in teamplay
//
2002-06-01 13:37:02 +00:00
// Revision 1.27 2002/06/01 13:37:02 makro
// Tweaked bandaging code
//
// Revision 1.26 2002/05/30 21:18:28 makro
// Bots should reload/bandage when roaming around
// Added "pathtarget" key to all the entities
//
// Revision 1.25 2002/05/11 14:22:06 makro
// Func_statics now reset at the beginning of each round
//
// Revision 1.24 2002/05/10 13:21:53 makro
// Mainly bot stuff. Also fixed a couple of crash bugs
//
// Revision 1.23 2002/05/05 15:18:02 makro
// Fixed some crash bugs. Bot stuff. Triggerable func_statics.
// Made flags only spawn in CTF mode
//
2002-05-04 16:13:05 +00:00
// Revision 1.22 2002/05/04 16:13:04 makro
// Bots
//
2002-05-03 18:09:20 +00:00
// Revision 1.21 2002/05/03 18:09:19 makro
// Bot stuff. Jump kicks
//
2002-05-02 23:05:25 +00:00
// Revision 1.20 2002/05/02 23:05:25 makro
// Loading screen. Jump kicks. Bot stuff
//
// Revision 1.19 2002/05/02 12:44:58 makro
// Customizable color for the loading screen text. Bot stuff
//
// Revision 1.18 2002/05/02 00:12:22 makro
// Improved reloading and ammo handling for akimbo/hc
//
// Revision 1.17 2002/05/01 05:32:45 makro
// Bots reload akimbos/handcannons. Also, they can decide whether
// or not an item on the ground is better than theirs
//
// Revision 1.16 2002/04/30 11:54:37 makro
// Bots rule ! Also, added clips to give all. Maybe some other things
//
2002-04-14 21:50:55 +00:00
// Revision 1.15 2002/04/14 21:49:52 makro
// Stuff
//
// Revision 1.14 2002/04/06 21:42:19 makro
// Changes to bot code. New surfaceparm system.
//
2002-04-05 18:52:26 +00:00
// Revision 1.13 2002/04/05 18:52:26 makro
// Cleaned things up a bit
//
// Revision 1.12 2002/04/04 18:06:44 makro
// Improved door code. Bots reply to radio treport from teammates.
// Improved reloading code.
//
// Revision 1.11 2002/04/03 17:39:36 makro
// Made bots handle incoming radio spam better
//
2002-04-03 16:33:20 +00:00
// Revision 1.10 2002/04/03 16:33:20 makro
// Bots now respond to radio commands
//
2002-04-01 12:45:54 +00:00
// Revision 1.9 2002/04/01 12:45:54 makro
// Changed some weapon names
//
// Revision 1.7 2002/03/31 19:16:56 makro
// Bandaging, reloading, opening rotating doors (still needs a lot of work),
// shooting breakables
//
// Revision 1.6 2002/01/11 19:48:29 jbravo
// Formatted the source in non DOS format.
//
2001-12-31 16:28:42 +00:00
// Revision 1.5 2001/12/31 16:28:41 jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
2001-05-06 20:50:27 +00:00
// Copyright (C) 1999-2000 Id Software, Inc.
//
/*****************************************************************************
* name: ai_dmnet.c
*
* desc: Quake3 bot AI
*
* $Archive: /MissionPack/code/game/ai_dmnet.c $
* $Author$
* $Revision$
* $Modtime: 11/14/00 2:42p $
* $Date$
*
*****************************************************************************/
#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"
2001-05-06 20:50:27 +00:00
//
#include "ai_main.h"
#include "ai_dmq3.h"
#include "ai_chat.h"
#include "ai_cmd.h"
#include "ai_dmnet.h"
#include "ai_team.h"
//data file headers
#include "chars.h" //characteristics
#include "inv.h" //indexes into the inventory
#include "syn.h" //synonyms
#include "match.h" //string matching types and vars
2001-05-06 20:50:27 +00:00
// for the voice chats
//Blaze: was there a extra ../ here?
2009-06-28 05:31:14 +00:00
#include "../ui/menudef.h"
2001-05-06 20:50:27 +00:00
2002-04-03 16:33:20 +00:00
//Makro - to get rid of the warnings
void Cmd_Bandage(gentity_t * ent);
void Cmd_DropItem_f(gentity_t * ent);
void Cmd_DropWeapon_f(gentity_t * ent);
2001-05-06 20:50:27 +00:00
//goal flag, see ../botlib/be_ai_goal.h for the other GFL_*
2001-05-06 20:50:27 +00:00
#define GFL_AIR 128
int numnodeswitches;
char nodeswitch[MAX_NODESWITCHES + 1][144];
2001-05-06 20:50:27 +00:00
#define LOOKAHEAD_DISTANCE 300
/*
==================
BotResetNodeSwitches
==================
*/
void BotResetNodeSwitches(void)
{
2001-05-06 20:50:27 +00:00
numnodeswitches = 0;
}
/*
==================
BotDumpNodeSwitches
==================
*/
void BotDumpNodeSwitches(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
int i;
char netname[MAX_NETNAME];
ClientName(bs->client, netname, sizeof(netname));
BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(),
MAX_NODESWITCHES);
2001-05-06 20:50:27 +00:00
for (i = 0; i < numnodeswitches; i++) {
BotAI_Print(PRT_MESSAGE, "%s", nodeswitch[i]);
2001-05-06 20:50:27 +00:00
}
BotAI_Print(PRT_FATAL, "");
}
/*
==================
BotRecordNodeSwitch
==================
*/
void BotRecordNodeSwitch(bot_state_t * bs, char *node, char *str, char *s)
{
2001-05-06 20:50:27 +00:00
char netname[MAX_NETNAME];
ClientName(bs->client, netname, sizeof(netname));
Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(),
node, str, s);
2001-05-06 20:50:27 +00:00
#ifdef DEBUG
if (0) {
BotAI_Print(PRT_MESSAGE, "%s", nodeswitch[numnodeswitches]);
2001-05-06 20:50:27 +00:00
}
#endif //DEBUG
2001-05-06 20:50:27 +00:00
numnodeswitches++;
}
/*
==================
BotGetAirGoal
==================
*/
int BotGetAirGoal(bot_state_t * bs, bot_goal_t * goal)
{
2001-05-06 20:50:27 +00:00
bsp_trace_t bsptrace;
vec3_t end, mins = { -15, -15, -2 }, maxs = {
15, 15, 2};
2001-05-06 20:50:27 +00:00
int areanum;
//trace up until we hit solid
VectorCopy(bs->origin, end);
end[2] += 1000;
BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP);
2001-05-06 20:50:27 +00:00
//trace down until we hit water
VectorCopy(bsptrace.endpos, end);
BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum,
CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA);
2001-05-06 20:50:27 +00:00
//if we found the water surface
if (bsptrace.fraction > 0) {
areanum = BotPointAreaNum(bsptrace.endpos);
if (areanum) {
VectorCopy(bsptrace.endpos, goal->origin);
goal->origin[2] -= 2;
goal->areanum = areanum;
goal->mins[0] = -15;
goal->mins[1] = -15;
goal->mins[2] = -1;
goal->maxs[0] = 15;
goal->maxs[1] = 15;
goal->maxs[2] = 1;
goal->flags = GFL_AIR;
goal->number = 0;
goal->iteminfo = 0;
goal->entitynum = 0;
return qtrue;
}
}
return qfalse;
}
/*
==================
BotGoForAir
==================
*/
int BotGoForAir(bot_state_t * bs, int tfl, bot_goal_t * ltg, float range)
{
2001-05-06 20:50:27 +00:00
bot_goal_t goal;
//if the bot needs air
if (bs->lastair_time < FloatTime() - 6) {
//
#ifdef DEBUG
//BotAI_Print(PRT_MESSAGE, "going for air\n");
#endif //DEBUG
2001-05-06 20:50:27 +00:00
//if we can find an air goal
if (BotGetAirGoal(bs, &goal)) {
trap_BotPushGoal(bs->gs, &goal);
return qtrue;
} else {
2001-05-06 20:50:27 +00:00
//get a nearby goal outside the water
while (trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) {
2001-05-06 20:50:27 +00:00
trap_BotGetTopGoal(bs->gs, &goal);
//if the goal is not in water
if (!
(trap_AAS_PointContents(goal.origin) &
(CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))) {
2001-05-06 20:50:27 +00:00
return qtrue;
}
trap_BotPopGoal(bs->gs);
}
trap_BotResetAvoidGoals(bs->gs);
}
}
return qfalse;
}
/*
==================
BotNearbyGoal
==================
*/
int BotNearbyGoal(bot_state_t * bs, int tfl, bot_goal_t * ltg, float range)
{
2001-05-06 20:50:27 +00:00
int ret;
//check if the bot should go for air
if (BotGoForAir(bs, tfl, ltg, range))
return qtrue;
2001-05-06 20:50:27 +00:00
//if the bot is carrying the enemy flag
if (BotCTFCarryingFlag(bs)) {
//if the bot is just a few secs away from the base
if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
2001-05-06 20:50:27 +00:00
//make the range really small
range = 50;
}
}
//
ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range);
/*
if (ret)
{
char buf[128];
//get the goal at the top of the stack
trap_BotGetTopGoal(bs->gs, &goal);
trap_BotGoalName(goal.number, buf, sizeof(buf));
BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf);
}
*/
2001-05-06 20:50:27 +00:00
return ret;
}
/*
==================
2002-05-02 23:05:25 +00:00
RQ3_Bot_WeaponToDrop
Added by Makro
==================
*/
int RQ3_Bot_WeaponToDrop(bot_state_t * bs)
{
2002-05-02 23:05:25 +00:00
if (bs->cur_ps.weapon <= WP_NONE || bs->cur_ps.weapon >= WP_NUM_WEAPONS)
return WP_NONE;
if (bs->cur_ps.weapon == WP_PISTOL || bs->cur_ps.weapon == WP_KNIFE ||
bs->cur_ps.weapon == WP_AKIMBO || bs->cur_ps.weapon == WP_GRENADE) {
2002-05-02 23:05:25 +00:00
//no special weapon selected
if (bs->inventory[INVENTORY_M4])
return WP_M4;
else if (bs->inventory[INVENTORY_M3])
return WP_M3;
else if (bs->inventory[INVENTORY_MP5])
return WP_MP5;
else if (bs->inventory[INVENTORY_HANDCANNON])
return WP_HANDCANNON;
else if (bs->inventory[INVENTORY_SSG3000])
return WP_SSG3000;
2002-05-02 23:05:25 +00:00
} else {
return bs->cur_ps.weapon;
}
return WP_NONE;
}
/*
====================================
RQ3_Bot_ComboScore
Added by Makro
Note - lots of factors should be
considered here; they aren't :/
====================================
*/
int RQ3_Bot_ComboScore(int weapon, holdable_t item)
{
if (item != HI_KEVLAR || item != HI_LASER || item != HI_BANDOLIER || item != HI_SLIPPERS || item != HI_SILENCER)
return 0;
switch (weapon) {
//Shotgun
case WP_M3:
switch (item) {
case HI_KEVLAR:
return 100;
case HI_LASER:
return 30;
case HI_BANDOLIER:
return 80;
case HI_SILENCER:
return 30;
case HI_SLIPPERS:
return 80;
default:
return 0;
}
break;
//MP5
case WP_MP5:
switch (item) {
case HI_KEVLAR:
return 90;
case HI_LASER:
return 100;
case HI_BANDOLIER:
return 70;
case HI_SILENCER:
return 80;
case HI_SLIPPERS:
return 40;
default:
return 0;
}
break;
//HANDCANNON
case WP_HANDCANNON:
switch (item) {
case HI_KEVLAR:
return 90;
case HI_LASER:
return 30;
case HI_BANDOLIER:
return 90;
case HI_SILENCER:
return 30;
case HI_SLIPPERS:
return 90;
default:
return 0;
}
break;
//SSG3000
case WP_SSG3000:
switch (item) {
case HI_KEVLAR:
return 100;
case HI_LASER:
return 40;
case HI_BANDOLIER:
return 80;
case HI_SILENCER:
return 90;
case HI_SLIPPERS:
return 40;
default:
return 0;
}
break;
//M4
case WP_M4:
switch (item) {
case HI_KEVLAR:
return 100;
case HI_LASER:
return 90;
case HI_BANDOLIER:
return 80;
case HI_SILENCER:
return 30;
case HI_SLIPPERS:
return 40;
default:
return 0;
}
break;
}
return 0;
}
/*
==================
RQ3_Bot_NeedToDropStuff
Added by Makro
==================
*/
qboolean RQ3_Bot_NeedToDropStuff(bot_state_t * bs, bot_goal_t * goal)
{
2002-05-02 23:05:25 +00:00
float attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
int itemType = IT_BAD;
2002-05-02 23:05:25 +00:00
//check the bot skill
if (random() < (1 - attack_skill))
return qfalse;
2002-05-03 18:09:20 +00:00
//if the bot doesn't have an item
if (!g_entities[goal->entitynum].item)
2002-05-03 18:09:20 +00:00
//no need to worry about dropping it
return qfalse;
2002-05-03 18:09:20 +00:00
itemType = g_entities[goal->entitynum].item->giType;
//if the bot can pick up an item
if (itemType == IT_HOLDABLE) {
if (bs->cur_ps.stats[STAT_HOLDABLE_ITEM] < 1 || bs->cur_ps.stats[STAT_HOLDABLE_ITEM] >= bg_numItems)
return qfalse;
else {
holdable_t oldItem = bg_itemlist[bs->cur_ps.stats[STAT_HOLDABLE_ITEM]].giTag;
holdable_t newItem = g_entities[goal->entitynum].item->giTag;
2002-05-02 23:05:25 +00:00
int i, oldScore, newScore, dropWeapon = WP_NONE;
//if the two items are identical
if (oldItem == newItem)
return qfalse;
2002-05-02 23:05:25 +00:00
//if the bot drops the bandolier a weapon might be dropped, too
if (oldItem == HI_BANDOLIER
&& g_entities[bs->entitynum].client->uniqueWeapons > g_RQ3_maxWeapons.integer)
2002-05-02 23:05:25 +00:00
dropWeapon = RQ3_Bot_WeaponToDrop(bs);
2002-05-03 18:09:20 +00:00
if (dropWeapon != WP_NONE) {
//if the bot doesn't have any ammo for the dropped weapon
if (bs->cur_ps.ammo[dropWeapon] <= 0 && !RQ3_Bot_CanReload(bs, dropWeapon)) {
//it shouldn't care whether or not it's dropped
dropWeapon = WP_NONE;
}
}
newScore = oldScore = 0;
//check all the weapons
for (i = 0; i < MAX_WEAPONS; i++) {
//if the bot has the weapon
if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)) {
//get the score for it
oldScore += RQ3_Bot_ComboScore(i, oldItem);
2002-05-02 23:05:25 +00:00
//don't add the score for a weapon if it's going to be dropped
if (i != dropWeapon)
newScore += RQ3_Bot_ComboScore(i, newItem);
}
}
2002-05-02 23:05:25 +00:00
//if the new item is better
if (newScore > oldScore) {
Cmd_DropItem_f(&g_entities[bs->entitynum]);
return qtrue;
}
}
//if the bot can pick up a weapon
} else if (itemType == IT_WEAPON) {
int dropWeapon = RQ3_Bot_WeaponToDrop(bs);
2002-05-02 23:05:25 +00:00
if (dropWeapon == WP_NONE)
return qfalse;
if (bs->cur_ps.ammo[dropWeapon])
return qfalse;
if (RQ3_Bot_CanReload(bs, dropWeapon))
return qfalse;
2002-05-02 23:05:25 +00:00
//Makro - the current weapon is empty, drop it
Cmd_DropWeapon_f(&g_entities[bs->entitynum]);
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "droppping weapon %i\n", dropWeapon);
#endif //DEBUG
2002-05-02 23:05:25 +00:00
return qtrue;
}
return qfalse;
}
2002-05-02 23:05:25 +00:00
2001-05-06 20:50:27 +00:00
/*
==================
BotReachedGoal
==================
*/
int BotReachedGoal(bot_state_t * bs, bot_goal_t * goal)
{
2001-05-06 20:50:27 +00:00
if (goal->flags & GFL_ITEM) {
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal)) {
if (!(goal->flags & GFL_DROPPED)) {
2002-05-02 23:05:25 +00:00
//Makro - check if the bot picked up a better weapon or item
RQ3_Bot_NeedToDropStuff(bs, goal);
2002-05-04 16:13:05 +00:00
//also added a TP check
if (gametype != GT_TEAMPLAY) {
trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1);
} else {
trap_BotSetAvoidGoalTime(bs->gs, goal->number,
FloatTime() + 20.0f + random() * 20.0f);
2002-05-04 16:13:05 +00:00
}
2001-05-06 20:50:27 +00:00
}
2002-04-03 16:33:20 +00:00
/*
if (g_entities[goal->entitynum].classname) {
G_Printf(va("^5BOT CODE: ^7Reached item of type (%s)\n", g_entities[goal->entitynum].classname));
}
*/
2001-05-06 20:50:27 +00:00
return qtrue;
}
//if the goal isn't there
if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
/*
float avoidtime;
int t;
avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number);
if (avoidtime > 0) {
t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl);
if ((float) t * 0.009 < avoidtime)
return qtrue;
}
*/
2001-05-06 20:50:27 +00:00
return qtrue;
}
//if in the goal area and below or above the goal and not swimming
if (bs->areanum == goal->areanum) {
if (bs->origin[0] > goal->origin[0] + goal->mins[0]
&& bs->origin[0] < goal->origin[0] + goal->maxs[0]) {
if (bs->origin[1] > goal->origin[1] + goal->mins[1]
&& bs->origin[1] < goal->origin[1] + goal->maxs[1]) {
2001-05-06 20:50:27 +00:00
if (!trap_AAS_Swimming(bs->origin)) {
return qtrue;
}
}
}
}
} else if (goal->flags & GFL_AIR) {
2001-05-06 20:50:27 +00:00
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal))
return qtrue;
2001-05-06 20:50:27 +00:00
//if the bot got air
if (bs->lastair_time > FloatTime() - 1)
return qtrue;
} else {
2001-05-06 20:50:27 +00:00
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal))
return qtrue;
2001-05-06 20:50:27 +00:00
}
return qfalse;
}
/*
==================
BotGetItemLongTermGoal
==================
*/
int BotGetItemLongTermGoal(bot_state_t * bs, int tfl, bot_goal_t * goal)
{
2001-05-06 20:50:27 +00:00
//if the bot has no goal
if (!trap_BotGetTopGoal(bs->gs, goal)) {
//BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
bs->ltg_time = 0;
}
//if the bot touches the current goal
else if (BotReachedGoal(bs, goal)) {
BotChooseWeapon(bs);
bs->ltg_time = 0;
}
//if it is time to find a new long term goal
if (bs->ltg_time < FloatTime()) {
//pop the current goal from the stack
trap_BotPopGoal(bs->gs);
//BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
//choose a new goal
//BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client);
if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) {
/*
char buf[128];
//get the goal at the top of the stack
trap_BotGetTopGoal(bs->gs, goal);
trap_BotGoalName(goal->number, buf, sizeof(buf));
BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf);
*/
2001-05-06 20:50:27 +00:00
bs->ltg_time = FloatTime() + 20;
} else { //the bot gets sorta stuck with all the avoid timings, shouldn't happen though
2001-05-06 20:50:27 +00:00
//
#ifdef DEBUG
char netname[128];
BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n",
ClientName(bs->client, netname, sizeof(netname)));
2001-05-06 20:50:27 +00:00
#endif
//trap_BotDumpAvoidGoals(bs->gs);
//reset the avoid goals and the avoid reach
trap_BotResetAvoidGoals(bs->gs);
trap_BotResetAvoidReach(bs->ms);
}
//get the goal at the top of the stack
return trap_BotGetTopGoal(bs->gs, goal);
}
return qtrue;
}
/*
==================
BotGetLongTermGoal
we could also create a seperate AI node for every long term goal type
however this saves us a lot of code
==================
*/
int BotGetLongTermGoal(bot_state_t * bs, int tfl, int retreat, bot_goal_t * goal)
{
2001-05-06 20:50:27 +00:00
vec3_t target, dir, dir2;
char netname[MAX_NETNAME];
char buf[MAX_MESSAGE_SIZE];
int areanum;
float croucher;
aas_entityinfo_t entinfo, botinfo;
bot_waypoint_t *wp;
if (bs->ltgtype == LTG_TEAMHELP && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)),
NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
//Makro - ACTION_AFFIRMATIVE = reload in RQ3
//trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
2001-05-06 20:50:27 +00:00
bs->teammessage_time = 0;
}
//if trying to help the team mate for more than a minute
if (bs->teamgoal_time < FloatTime())
bs->ltgtype = 0;
//if the team mate IS visible for quite some time
if (bs->teammatevisible_time < FloatTime() - 10)
bs->ltgtype = 0;
2001-05-06 20:50:27 +00:00
//get entity information of the companion
BotEntityInfo(bs->teammate, &entinfo);
//if the team mate is visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
//if close just stand still there
VectorSubtract(entinfo.origin, bs->origin, dir);
if (VectorLengthSquared(dir) < Square(100)) {
trap_BotResetAvoidReach(bs->ms);
return qfalse;
}
} else {
2001-05-06 20:50:27 +00:00
//last time the bot was NOT visible
bs->teammatevisible_time = FloatTime();
}
//if the entity information is valid (entity in PVS)
if (entinfo.valid) {
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
//update team goal
bs->teamgoal.entitynum = bs->teammate;
bs->teamgoal.areanum = areanum;
VectorCopy(entinfo.origin, bs->teamgoal.origin);
VectorSet(bs->teamgoal.mins, -8, -8, -8);
VectorSet(bs->teamgoal.maxs, 8, 8, 8);
}
}
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
return qtrue;
}
//if the bot accompanies someone
if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
BotAI_BotInitialChat(bs, "accompany_start",
EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
//Makro - ACTION_AFFIRMATIVE = reload in RQ3
//trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
2001-05-06 20:50:27 +00:00
bs->teammessage_time = 0;
}
//if accompanying the companion for 3 minutes
if (bs->teamgoal_time < FloatTime()) {
BotAI_BotInitialChat(bs, "accompany_stop",
EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->ltgtype = 0;
}
//get entity information of the companion
BotEntityInfo(bs->teammate, &entinfo);
//if the companion is visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
//update visible time
bs->teammatevisible_time = FloatTime();
VectorSubtract(entinfo.origin, bs->origin, dir);
if (VectorLengthSquared(dir) < Square(bs->formation_dist)) {
//
// if the client being followed bumps into this bot then
// the bot should back up
BotEntityInfo(bs->entitynum, &botinfo);
// if the followed client is not standing ontop of the bot
if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) {
// if the bounding boxes touch each other
if (botinfo.origin[0] + botinfo.maxs[0] >
entinfo.origin[0] + entinfo.mins[0] - 4
&& botinfo.origin[0] + botinfo.mins[0] <
entinfo.origin[0] + entinfo.maxs[0] + 4) {
if (botinfo.origin[1] + botinfo.maxs[1] >
entinfo.origin[1] + entinfo.mins[1] - 4
&& botinfo.origin[1] + botinfo.mins[1] <
entinfo.origin[1] + entinfo.maxs[1] + 4) {
if (botinfo.origin[2] + botinfo.maxs[2] >
entinfo.origin[2] + entinfo.mins[2] - 4
&& botinfo.origin[2] + botinfo.mins[2] <
entinfo.origin[2] + entinfo.maxs[2] + 4) {
2001-05-06 20:50:27 +00:00
// if the followed client looks in the direction of this bot
AngleVectors(entinfo.angles, dir, NULL, NULL);
dir[2] = 0;
VectorNormalize(dir);
//VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
VectorSubtract(bs->origin, entinfo.origin, dir2);
VectorNormalize(dir2);
if (DotProduct(dir, dir2) > 0.7) {
// back up
BotSetupForMovement(bs);
trap_BotMoveInDirection(bs->ms, dir2, 400,
MOVE_WALK);
2001-05-06 20:50:27 +00:00
}
}
}
}
}
//check if the bot wants to crouch
//don't crouch if crouched less than 5 seconds ago
if (bs->attackcrouch_time < FloatTime() - 5) {
croucher =
trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
2001-05-06 20:50:27 +00:00
if (random() < bs->thinktime * croucher) {
bs->attackcrouch_time = FloatTime() + 5 + croucher * 15;
}
}
//don't crouch when swimming
if (trap_AAS_Swimming(bs->origin))
bs->attackcrouch_time = FloatTime() - 1;
2001-05-06 20:50:27 +00:00
//if not arrived yet or arived some time ago
if (bs->arrive_time < FloatTime() - 2) {
//if not arrived yet
if (!bs->arrive_time) {
trap_EA_Gesture(bs->client);
BotAI_BotInitialChat(bs, "accompany_arrive",
EasyClientName(bs->teammate, netname,
sizeof(netname)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->arrive_time = FloatTime();
}
//if the bot wants to crouch
else if (bs->attackcrouch_time > FloatTime()) {
trap_EA_Crouch(bs->client);
}
//else do some model taunts
else if (random() < bs->thinktime * 0.05) {
//do a gesture :)
trap_EA_Gesture(bs->client);
}
}
//if just arrived look at the companion
if (bs->arrive_time > FloatTime() - 2) {
VectorSubtract(entinfo.origin, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
//else look strategically around for enemies
else if (random() < bs->thinktime * 0.8) {
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
//check if the bot wants to go for air
if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) {
trap_BotResetLastAvoidReach(bs->ms);
//get the goal at the top of the stack
//trap_BotGetTopGoal(bs->gs, &tmpgoal);
//trap_BotGoalName(tmpgoal.number, buf, 144);
//BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
//time the bot gets to pick up the nearby goal item
bs->nbg_time = FloatTime() + 8;
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
trap_BotResetAvoidReach(bs->ms);
return qfalse;
}
}
//if the entity information is valid (entity in PVS)
if (entinfo.valid) {
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
//update team goal
bs->teamgoal.entitynum = bs->teammate;
bs->teamgoal.areanum = areanum;
VectorCopy(entinfo.origin, bs->teamgoal.origin);
VectorSet(bs->teamgoal.mins, -8, -8, -8);
VectorSet(bs->teamgoal.maxs, 8, 8, 8);
}
}
//the goal the bot should go for
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//if the companion is NOT visible for too long
if (bs->teammatevisible_time < FloatTime() - 60) {
BotAI_BotInitialChat(bs, "accompany_cannotfind",
EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->ltgtype = 0;
// just to make sure the bot won't spam this message
bs->teammatevisible_time = FloatTime();
}
return qtrue;
}
//
if (bs->ltgtype == LTG_DEFENDKEYAREA) {
if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) {
2001-05-06 20:50:27 +00:00
bs->defendaway_time = 0;
}
}
//if defending a key area
if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && bs->defendaway_time < FloatTime()) {
2001-05-06 20:50:27 +00:00
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "defend_start", buf, NULL);
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE);
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//stop after 2 minutes
if (bs->teamgoal_time < FloatTime()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "defend_stop", buf, NULL);
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
bs->ltgtype = 0;
}
//if very close... go away for some time
VectorSubtract(goal->origin, bs->origin, dir);
if (VectorLengthSquared(dir) < Square(70)) {
trap_BotResetAvoidReach(bs->ms);
bs->defendaway_time = FloatTime() + 3 + 3 * random();
if (BotHasPersistantPowerupAndWeapon(bs)) {
bs->defendaway_range = 100;
} else {
2001-05-06 20:50:27 +00:00
bs->defendaway_range = 350;
}
}
return qtrue;
}
//going to kill someone
if (bs->ltgtype == LTG_KILL && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "kill_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
bs->teammessage_time = 0;
}
//
if (bs->lastkilledplayer == bs->teamgoal.entitynum) {
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "kill_done", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
bs->lastkilledplayer = -1;
bs->ltgtype = 0;
}
//
if (bs->teamgoal_time < FloatTime()) {
bs->ltgtype = 0;
}
//just roam around
return BotGetItemLongTermGoal(bs, tfl, goal);
}
//get an item
if (bs->ltgtype == LTG_GETITEM && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "getitem_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
//Makro - ACTION_AFFIRMATIVE = reload in RQ3
//trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
2001-05-06 20:50:27 +00:00
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//stop after some time
if (bs->teamgoal_time < FloatTime()) {
bs->ltgtype = 0;
}
//
if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
bs->ltgtype = 0;
} else if (BotReachedGoal(bs, goal)) {
2001-05-06 20:50:27 +00:00
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
bs->ltgtype = 0;
}
return qtrue;
}
//if camping somewhere
if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_start",
EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
//Makro - ACTION_AFFIRMATIVE = reload in RQ3
//trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
2001-05-06 20:50:27 +00:00
}
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//
if (bs->teamgoal_time < FloatTime()) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_stop", NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
}
bs->ltgtype = 0;
}
//if really near the camp spot
VectorSubtract(goal->origin, bs->origin, dir);
if (VectorLengthSquared(dir) < Square(60)) {
2001-05-06 20:50:27 +00:00
//if not arrived yet
if (!bs->arrive_time) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_arrive",
EasyClientName(bs->teammate, netname, sizeof(netname)),
NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION);
}
bs->arrive_time = FloatTime();
}
//look strategically around for enemies
if (random() < bs->thinktime * 0.8) {
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
//check if the bot wants to crouch
//don't crouch if crouched less than 5 seconds ago
if (bs->attackcrouch_time < FloatTime() - 5) {
croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
if (random() < bs->thinktime * croucher) {
bs->attackcrouch_time = FloatTime() + 5 + croucher * 15;
}
}
//if the bot wants to crouch
if (bs->attackcrouch_time > FloatTime()) {
trap_EA_Crouch(bs->client);
}
//don't crouch when swimming
if (trap_AAS_Swimming(bs->origin))
bs->attackcrouch_time = FloatTime() - 1;
2001-05-06 20:50:27 +00:00
//make sure the bot is not gonna drown
if (trap_PointContents(bs->eye, bs->entitynum) &
(CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) {
2001-05-06 20:50:27 +00:00
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_stop", NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
//
if (bs->lastgoal_ltgtype == LTG_CAMPORDER) {
bs->lastgoal_ltgtype = 0;
}
}
bs->ltgtype = 0;
}
//
2012-01-14 00:18:42 +00:00
//if (bs->camp_range > 0) {
2001-05-06 20:50:27 +00:00
//FIXME: move around a bit
2012-01-14 00:18:42 +00:00
//}
2001-05-06 20:50:27 +00:00
//
trap_BotResetAvoidReach(bs->ms);
return qfalse;
}
return qtrue;
}
//patrolling along several waypoints
if (bs->ltgtype == LTG_PATROL && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
strcpy(buf, "");
for (wp = bs->patrolpoints; wp; wp = wp->next) {
strcat(buf, wp->name);
if (wp->next)
strcat(buf, " to ");
2001-05-06 20:50:27 +00:00
}
BotAI_BotInitialChat(bs, "patrol_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
//Makro - ACTION_AFFIRMATIVE = reload in RQ3
//trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
2001-05-06 20:50:27 +00:00
bs->teammessage_time = 0;
}
//
if (!bs->curpatrolpoint) {
bs->ltgtype = 0;
return qfalse;
}
//if the bot touches the current goal
if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) {
if (bs->patrolflags & PATROL_BACK) {
if (bs->curpatrolpoint->prev) {
bs->curpatrolpoint = bs->curpatrolpoint->prev;
} else {
2001-05-06 20:50:27 +00:00
bs->curpatrolpoint = bs->curpatrolpoint->next;
bs->patrolflags &= ~PATROL_BACK;
}
} else {
2001-05-06 20:50:27 +00:00
if (bs->curpatrolpoint->next) {
bs->curpatrolpoint = bs->curpatrolpoint->next;
} else {
2001-05-06 20:50:27 +00:00
bs->curpatrolpoint = bs->curpatrolpoint->prev;
bs->patrolflags |= PATROL_BACK;
}
}
}
//stop after 5 minutes
if (bs->teamgoal_time < FloatTime()) {
BotAI_BotInitialChat(bs, "patrol_stop", NULL);
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
bs->ltgtype = 0;
}
if (!bs->curpatrolpoint) {
bs->ltgtype = 0;
return qfalse;
}
memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t));
return qtrue;
}
#ifdef CTF
if (gametype == GT_CTF) {
//if going for enemy flag
if (bs->ltgtype == LTG_GETFLAG) {
//check for bot typing status message
2001-05-06 20:50:27 +00:00
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
BotAI_BotInitialChat(bs, "captureflag_start", NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG);
bs->teammessage_time = 0;
}
//
switch (BotTeam(bs)) {
case TEAM_RED:
memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t));
break;
case TEAM_BLUE:
memcpy(goal, &ctf_redflag, sizeof(bot_goal_t));
break;
default:
bs->ltgtype = 0;
return qfalse;
}
//if touching the flag
2001-05-06 20:50:27 +00:00
if (trap_BotTouchingGoal(bs->origin, goal)) {
// make sure the bot knows the flag isn't there anymore
switch (BotTeam(bs)) {
case TEAM_RED:
bs->blueflagstatus = 1;
break;
case TEAM_BLUE:
bs->redflagstatus = 1;
break;
2001-05-06 20:50:27 +00:00
}
bs->ltgtype = 0;
}
//stop after 3 minutes
2001-05-06 20:50:27 +00:00
if (bs->teamgoal_time < FloatTime()) {
bs->ltgtype = 0;
}
2001-05-06 20:50:27 +00:00
BotAlternateRoute(bs, goal);
return qtrue;
}
//if rushing to the base
2001-05-06 20:50:27 +00:00
if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) {
switch (BotTeam(bs)) {
case TEAM_RED:
memcpy(goal, &ctf_redflag, sizeof(bot_goal_t));
break;
case TEAM_BLUE:
memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t));
break;
default:
bs->ltgtype = 0;
return qfalse;
2001-05-06 20:50:27 +00:00
}
//if not carrying the flag anymore
if (!BotCTFCarryingFlag(bs))
bs->ltgtype = 0;
//quit rushing after 2 minutes
if (bs->teamgoal_time < FloatTime())
2001-05-06 20:50:27 +00:00
bs->ltgtype = 0;
//if touching the base flag the bot should loose the enemy flag
if (trap_BotTouchingGoal(bs->origin, goal)) {
//if the bot is still carrying the enemy flag then the
//base flag is gone, now just walk near the base a bit
if (BotCTFCarryingFlag(bs)) {
trap_BotResetAvoidReach(bs->ms);
bs->rushbaseaway_time = FloatTime() + 5 + 10 * random();
//FIXME: add chat to tell the others to get back the flag
} else {
bs->ltgtype = 0;
}
2001-05-06 20:50:27 +00:00
}
BotAlternateRoute(bs, goal);
return qtrue;
}
//returning flag
if (bs->ltgtype == LTG_RETURNFLAG) {
//check for bot typing status message
2001-05-06 20:50:27 +00:00
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
BotAI_BotInitialChat(bs, "returnflag_start", NULL);
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG);
bs->teammessage_time = 0;
}
//
switch (BotTeam(bs)) {
case TEAM_RED:
memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t));
break;
case TEAM_BLUE:
memcpy(goal, &ctf_redflag, sizeof(bot_goal_t));
break;
default:
bs->ltgtype = 0;
return qfalse;
2001-05-06 20:50:27 +00:00
}
//if touching the flag
if (trap_BotTouchingGoal(bs->origin, goal))
bs->ltgtype = 0;
2001-05-06 20:50:27 +00:00
//stop after 3 minutes
if (bs->teamgoal_time < FloatTime()) {
bs->ltgtype = 0;
}
BotAlternateRoute(bs, goal);
return qtrue;
}
}
#endif //CTF
2001-05-06 20:50:27 +00:00
//normal goal stuff
return BotGetItemLongTermGoal(bs, tfl, goal);
}
/*
==================
BotLongTermGoal
==================
*/
int BotLongTermGoal(bot_state_t * bs, int tfl, int retreat, bot_goal_t * goal)
{
2001-05-06 20:50:27 +00:00
aas_entityinfo_t entinfo;
char teammate[MAX_MESSAGE_SIZE];
float squaredist;
int areanum;
vec3_t dir;
//FIXME: also have air long term goals?
//
//if the bot is leading someone and not retreating
if (bs->lead_time > 0 && !retreat) {
if (bs->lead_time < FloatTime()) {
BotAI_BotInitialChat(bs, "lead_stop",
EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->lead_time = 0;
return BotGetLongTermGoal(bs, tfl, retreat, goal);
}
//
if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) {
BotAI_BotInitialChat(bs, "followme",
EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->leadmessage_time = FloatTime();
}
//get entity information of the companion
BotEntityInfo(bs->lead_teammate, &entinfo);
//
if (entinfo.valid) {
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
//update team goal
bs->lead_teamgoal.entitynum = bs->lead_teammate;
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 the team mate is visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) {
bs->leadvisible_time = FloatTime();
}
//if the team mate is not visible for 1 seconds
if (bs->leadvisible_time < FloatTime() - 1) {
bs->leadbackup_time = FloatTime() + 2;
}
//distance towards the team mate
VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir);
squaredist = VectorLengthSquared(dir);
//if backing up towards the team mate
if (bs->leadbackup_time > FloatTime()) {
if (bs->leadmessage_time < FloatTime() - 20) {
BotAI_BotInitialChat(bs, "followme",
EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)),
NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->leadmessage_time = FloatTime();
}
//if very close to the team mate
if (squaredist < Square(100)) {
bs->leadbackup_time = 0;
}
//the bot should go back to the team mate
memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t));
return qtrue;
} else {
2001-05-06 20:50:27 +00:00
//if quite distant from the team mate
if (squaredist > Square(500)) {
if (bs->leadmessage_time < FloatTime() - 20) {
BotAI_BotInitialChat(bs, "followme",
EasyClientName(bs->lead_teammate, teammate,
sizeof(teammate)), NULL);
2001-05-06 20:50:27 +00:00
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
bs->leadmessage_time = FloatTime();
}
//look at the team mate
VectorSubtract(entinfo.origin, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
//just wait for the team mate
return qfalse;
}
}
}
return BotGetLongTermGoal(bs, tfl, retreat, goal);
}
/*
==================
AIEnter_Intermission
==================
*/
void AIEnter_Intermission(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "intermission", "", s);
2001-05-06 20:50:27 +00:00
//reset the bot state
BotResetState(bs);
//check for end level chat
if (BotChat_EndLevel(bs)) {
trap_BotEnterChat(bs->cs, 0, bs->chatto);
}
bs->ainode = AINode_Intermission;
}
/*
==================
AINode_Intermission
==================
*/
int AINode_Intermission(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
//if the intermission ended
if (!BotIntermission(bs)) {
if (BotChat_StartLevel(bs)) {
bs->stand_time = FloatTime() + BotChatTime(bs);
} else {
2001-05-06 20:50:27 +00:00
bs->stand_time = FloatTime() + 2;
}
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "intermission: chat");
2001-05-06 20:50:27 +00:00
}
return qtrue;
}
/*
==================
AIEnter_Observer
==================
*/
void AIEnter_Observer(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "observer", "", s);
2001-05-06 20:50:27 +00:00
//reset the bot state
BotResetState(bs);
bs->ainode = AINode_Observer;
}
/*
==================
AINode_Observer
==================
*/
int AINode_Observer(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
//if the bot left observer mode
if (!BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "observer: left observer");
2001-05-06 20:50:27 +00:00
}
return qtrue;
}
/*
==================
AIEnter_Stand
==================
*/
void AIEnter_Stand(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "stand", "", s);
2001-05-06 20:50:27 +00:00
bs->standfindenemy_time = FloatTime() + 1;
bs->ainode = AINode_Stand;
}
/*
==================
AINode_Stand
==================
*/
int AINode_Stand(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
//if the bot's health decreased
if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
if (BotChat_HitTalking(bs)) {
bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1;
bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1;
}
if (RQ3_Bot_CheckBandage(bs)) {
//Makro - if not bandaging already
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING) {
Cmd_Bandage(&g_entities[bs->entitynum]);
//Not 100% sure this will work, but oh well...
AIEnter_Battle_Retreat(bs, "stand: bandaging");
return qfalse;
}
}
2001-05-06 20:50:27 +00:00
}
if (bs->standfindenemy_time < FloatTime()) {
if (BotFindEnemy(bs, -1)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "stand: found enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
bs->standfindenemy_time = FloatTime() + 1;
}
// put up chat icon
trap_EA_Talk(bs->client);
// when done standing
if (bs->stand_time < FloatTime()) {
trap_BotEnterChat(bs->cs, 0, bs->chatto);
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "stand: time out");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
return qtrue;
}
typedef struct {
weapon_t weapon;
holdable_t item;
} RQ3_TPCombo_t;
static RQ3_TPCombo_t RQ3_TPCombos[] = {
{WP_M4, HI_LASER},
{WP_M4, HI_KEVLAR},
{WP_M4, HI_BANDOLIER},
{WP_MP5, HI_LASER},
{WP_MP5, HI_SILENCER},
{WP_MP5, HI_KEVLAR},
{WP_MP5, HI_BANDOLIER},
{WP_M3, HI_KEVLAR},
{WP_M3, HI_BANDOLIER},
{WP_M3, HI_SLIPPERS},
{WP_HANDCANNON, HI_KEVLAR},
{WP_HANDCANNON, HI_BANDOLIER},
{WP_HANDCANNON, HI_SLIPPERS},
{WP_SSG3000, HI_SILENCER},
{WP_SSG3000, HI_KEVLAR},
{WP_SSG3000, HI_BANDOLIER},
{WP_SSG3000, HI_SLIPPERS},
{WP_AKIMBO, HI_KEVLAR},
{WP_AKIMBO, HI_BANDOLIER}
};
static int num_RQ3_TPCombos = sizeof(RQ3_TPCombos) / sizeof(RQ3_TPCombo_t);
2001-05-06 20:50:27 +00:00
/*
==================
AIEnter_Respawn
==================
*/
void AIEnter_Respawn(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "respawn", "", s);
2001-05-06 20:50:27 +00:00
//reset some states
trap_BotResetMoveState(bs->ms);
trap_BotResetGoalState(bs->gs);
trap_BotResetAvoidGoals(bs->gs);
trap_BotResetAvoidReach(bs->ms);
//if the bot wants to chat
if (BotChat_Death(bs)) {
bs->respawn_time = FloatTime() + BotChatTime(bs);
bs->respawnchat_time = FloatTime();
} else {
2001-05-06 20:50:27 +00:00
bs->respawn_time = FloatTime() + 1 + random();
bs->respawnchat_time = 0;
}
//Makro - the last time the bot responded to a radio message
bs->radioresponse_time = 0;
//Makro - how many times the bot responded to radio messages
bs->radioresponse_count = 0;
2001-05-06 20:50:27 +00:00
//set respawn state
//Makro - special code for TP; played a little with those numbers
if (gametype != GT_TEAMPLAY) {
bs->standfindenemy_time = FloatTime() + 3;
bs->stand_time = FloatTime() + 10;
bs->check_time = FloatTime() + 4;
} else {
weapon_t tpW = WP_NONE + atoi(BotGetUserInfoKey(bs, "tpw"));
holdable_t tpI = HI_NONE + atoi(BotGetUserInfoKey(bs, "tpi"));
int index = 0;
2002-05-03 18:09:20 +00:00
bs->standfindenemy_time = bs->check_time = FloatTime() + 1;
bs->stand_time = 5;
//don't use the same combo all the time
if (random() > 0.2f) {
//choose a random weapon/item
index = (int) (random() * (num_RQ3_TPCombos - 0.1));
tpW = RQ3_TPCombos[index].weapon;
tpI = RQ3_TPCombos[index].item;
}
if (tpW <= WP_NONE || tpW >= WP_NUM_WEAPONS || tpI <= HI_NONE || tpI >= HI_NUM_HOLDABLE) {
#ifdef DEBUG
BotAI_Print(PRT_WARNING, "Bad combo %i/%i, index is %i\n", tpW, tpI, index);
#endif
tpW = WP_M4;
tpI = HI_LASER;
}
g_entities[bs->entitynum].client->teamplayWeapon = tpW;
g_entities[bs->entitynum].client->teamplayItem = tpI;
}
2001-05-06 20:50:27 +00:00
bs->respawn_wait = qfalse;
bs->ainode = AINode_Respawn;
}
/*
==================
AINode_Respawn
==================
*/
int AINode_Respawn(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
// if waiting for the actual respawn
if (bs->respawn_wait) {
if (!BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "respawn: respawned");
} else {
2001-05-06 20:50:27 +00:00
trap_EA_Respawn(bs->client);
}
} else if (bs->respawn_time < FloatTime()) {
2001-05-06 20:50:27 +00:00
//wait until respawned
bs->respawn_wait = qtrue;
//elementary action respawn
trap_EA_Respawn(bs->client);
//
if (bs->respawnchat_time) {
trap_BotEnterChat(bs->cs, 0, bs->chatto);
bs->enemy = -1;
}
}
if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) {
trap_EA_Talk(bs->client);
}
//
return qtrue;
}
/*
==================
BotSelectActivateWeapon
==================
*/
int BotSelectActivateWeapon(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
//
//Makro - I'm pretty sure it should be WP_ and not WEAPONINDEX_
2001-05-06 20:50:27 +00:00
if (bs->inventory[INVENTORY_PISTOL] > 0 && bs->inventory[INVENTORY_PISTOLAMMO] > 0)
// return WEAPONINDEX_PISTOL;
return WP_PISTOL;
else if (bs->inventory[INVENTORY_M3] > 0 && bs->inventory[INVENTORY_M3AMMO] > 0)
// return WEAPONINDEX_M3;
return WP_M3;
2001-05-06 20:50:27 +00:00
else if (bs->inventory[INVENTORY_M4] > 0 && bs->inventory[INVENTORY_M4AMMO] > 0)
// return WEAPONINDEX_M4;
return WP_M4;
2001-05-06 20:50:27 +00:00
else if (bs->inventory[INVENTORY_MP5] > 0 && bs->inventory[INVENTORY_MP5AMMO] > 0)
// return WEAPONINDEX_MP5;
return WP_MP5;
2001-05-06 20:50:27 +00:00
else if (bs->inventory[INVENTORY_SSG3000] > 0 && bs->inventory[INVENTORY_SSG3000AMMO] > 0)
// return WEAPONINDEX_SSG3000;
return WP_SSG3000;
else if (bs->inventory[INVENTORY_HANDCANNON] > 0 && bs->inventory[INVENTORY_M3AMMO] > 0)
// return WEAPONINDEX_HANDCANNON;
return WP_HANDCANNON;
else if (bs->inventory[INVENTORY_AKIMBO] > 0 && bs->inventory[INVENTORY_AKIMBOAMMO] > 0)
// return WEAPONINDEX_AKIMBO;
return WP_AKIMBO;
2001-05-06 20:50:27 +00:00
else {
return -1;
}
}
/*
==================
BotClearPath
try to deactivate obstacles like proximity mines on the bot's path
==================
*/
void BotClearPath(bot_state_t * bs, bot_moveresult_t * moveresult)
{
2001-05-06 20:50:27 +00:00
int i, bestmine;
float dist, bestdist;
vec3_t target, dir;
bsp_trace_t bsptrace;
entityState_t state;
// if there is a dead body wearing kamikze nearby
if (bs->kamikazebody) {
// if the bot's view angles and weapon are not used for movement
if (!(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON))) {
2001-05-06 20:50:27 +00:00
//
BotAI_GetEntityState(bs->kamikazebody, &state);
VectorCopy(state.pos.trBase, target);
target[2] += 8;
VectorSubtract(target, bs->eye, dir);
vectoangles(dir, moveresult->ideal_viewangles);
//
moveresult->weapon = BotSelectActivateWeapon(bs);
if (moveresult->weapon == -1) {
// FIXME: run away!
moveresult->weapon = 0;
}
if (moveresult->weapon) {
//
moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW;
// if holding the right weapon
if (bs->cur_ps.weapon == moveresult->weapon) {
// if the bot is pretty close with it's aim
if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) {
//
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum,
MASK_SHOT);
2001-05-06 20:50:27 +00:00
// if the mine is visible from the current position
if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) {
// shoot at the mine
//Makro - using custom function to allow in-combat reloads
//trap_EA_Attack(bs->client);
BotAttack(bs);
2001-05-06 20:50:27 +00:00
}
}
}
}
}
}
if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) {
bs->blockedbyavoidspot_time = FloatTime() + 5;
}
// if blocked by an avoid spot and the view angles and weapon are used for movement
if (bs->blockedbyavoidspot_time > FloatTime() &&
!(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON))) {
2001-05-06 20:50:27 +00:00
bestdist = 300;
bestmine = -1;
for (i = 0; i < bs->numproxmines; i++) {
BotAI_GetEntityState(bs->proxmines[i], &state);
VectorSubtract(state.pos.trBase, bs->origin, dir);
dist = VectorLength(dir);
if (dist < bestdist) {
bestdist = dist;
bestmine = i;
}
}
if (bestmine != -1) {
//
// state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE
//
// deactivate prox mines in the bot's path by shooting
// rockets or plasma cells etc. at them
BotAI_GetEntityState(bs->proxmines[bestmine], &state);
VectorCopy(state.pos.trBase, target);
target[2] += 2;
VectorSubtract(target, bs->eye, dir);
vectoangles(dir, moveresult->ideal_viewangles);
// if the bot has a weapon that does splash damage
//Blaze: Just grenades
if (bs->inventory[INVENTORY_GRENADE] > 0 && bs->inventory[INVENTORY_GRENADEAMMO] > 0)
//Makro - changing to WP_ constants
//moveresult->weapon = WEAPONINDEX_GRENADE;
moveresult->weapon = WP_GRENADE;
2001-05-06 20:50:27 +00:00
/*else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0)
moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER;
else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0)
moveresult->weapon = WEAPONINDEX_BFG;
*/
2001-05-06 20:50:27 +00:00
else {
moveresult->weapon = 0;
}
if (moveresult->weapon) {
//
moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW;
// if holding the right weapon
if (bs->cur_ps.weapon == moveresult->weapon) {
// if the bot is pretty close with it's aim
if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) {
//
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum,
MASK_SHOT);
2001-05-06 20:50:27 +00:00
// if the mine is visible from the current position
if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) {
// shoot at the mine
//Makro - using custom function to allow in-combat reloads
//trap_EA_Attack(bs->client);
BotAttack(bs);
2001-05-06 20:50:27 +00:00
}
}
}
}
}
}
}
/*
==================
AIEnter_Seek_ActivateEntity
==================
*/
void AIEnter_Seek_ActivateEntity(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "activate entity", "", s);
//Makro - check if the bot needs to reload/bandage
RQ3_Bot_IdleActions(bs);
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Seek_ActivateEntity;
}
/*
==================
AINode_Seek_Activate_Entity
==================
*/
int AINode_Seek_ActivateEntity(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
bot_goal_t *goal;
vec3_t target, dir, ideal_viewangles;
bot_moveresult_t moveresult;
int targetvisible;
bsp_trace_t bsptrace;
aas_entityinfo_t entinfo;
if (BotIsObserver(bs)) {
BotClearActivateGoalStack(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "active entity: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
BotClearActivateGoalStack(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "activate entity: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
BotClearActivateGoalStack(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "activate entity: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//map specific code
BotMapScripts(bs);
//no enemy
bs->enemy = -1;
// if the bot has no activate goal
if (!bs->activatestack) {
BotClearActivateGoalStack(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "activate entity: no goal");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
goal = &bs->activatestack->goal;
// initialize target being visible to false
targetvisible = qfalse;
//Makro - if the bot has to open a door
if (bs->activatestack->openDoor) {
2002-04-03 16:33:20 +00:00
//int dist;
BotEntityInfo(goal->entitynum, &entinfo);
2002-04-03 16:33:20 +00:00
//dist = Distance(bs->origin, entinfo.origin);
if (trap_BotTouchingGoal(bs->origin, goal)) {
/*
if (bot_developer.integer == 2) {
G_Printf(va("^5BOT CODE: ^7Reached door at (%i %i %i) from (%i %i %i)\n",
(int) (bs->origin[0]), (int) (bs->origin[1]), (int) (bs->origin[2]),
(int) (entinfo.origin[0]), (int) (entinfo.origin[1]), (int) (entinfo.origin[2])));
}
*/
BotPopFromActivateGoalStack(bs);
//bs->activatestack->time = 0;
Cmd_OpenDoor(&g_entities[bs->entitynum]);
2002-04-03 16:33:20 +00:00
BotMoveTowardsEnt(bs, entinfo.origin, -64);
2002-04-14 21:50:55 +00:00
return qfalse;
}
}
2001-05-06 20:50:27 +00:00
// if the bot has to shoot at a target to activate something
if (bs->activatestack->shoot) {
//
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT);
// if the shootable entity is visible from the current position
if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) {
targetvisible = qtrue;
// if holding the right weapon
// Makro - or if no weapon is set for the goal
if (bs->cur_ps.weapon == bs->activatestack->weapon || bs->activatestack->noWeapon) {
2001-05-06 20:50:27 +00:00
VectorSubtract(bs->activatestack->target, bs->eye, dir);
vectoangles(dir, ideal_viewangles);
// if the bot is pretty close with it's aim
if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) {
//Makro - using custom function to allow in-combat reloads
//trap_EA_Attack(bs->client);
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "attacking an entity; weapon = %i, required = %i\n",
bs->cur_ps.weapon, bs->activatestack->weapon);
#endif //DEBUG
BotAttack(bs);
2001-05-06 20:50:27 +00:00
}
}
}
}
// if the shoot target is visible
if (targetvisible) {
// get the entity info of the entity the bot is shooting at
BotEntityInfo(goal->entitynum, &entinfo);
// if the entity the bot shoots at moved
2002-05-04 16:13:05 +00:00
// Makro - or if the entity is no longer in use - for func_breakables
if (!VectorCompare(bs->activatestack->origin, entinfo.origin) || !(g_entities[entinfo.number].r.linked)) {
2001-05-06 20:50:27 +00:00
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n");
#endif //DEBUG
2001-05-06 20:50:27 +00:00
bs->activatestack->time = 0;
}
// if the activate goal has been activated or the bot takes too long
if (bs->activatestack->time < FloatTime()) {
BotPopFromActivateGoalStack(bs);
// if there are more activate goals on the stack
if (bs->activatestack) {
bs->activatestack->time = FloatTime() + 10;
return qfalse;
}
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "activate entity: time out");
2001-05-06 20:50:27 +00:00
return qfalse;
}
memset(&moveresult, 0, sizeof(bot_moveresult_t));
} else {
//if the bot has no goal
2001-05-06 20:50:27 +00:00
if (!goal) {
bs->activatestack->time = 0;
}
// if the bot does not have a shoot goal
else if (!bs->activatestack->shoot) {
//if the bot touches the current goal
2001-05-06 20:50:27 +00:00
if (trap_BotTouchingGoal(bs->origin, goal)) {
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
#endif //DEBUG
2001-05-06 20:50:27 +00:00
bs->activatestack->time = 0;
}
}
// if the activate goal has been activated or the bot takes too long
if (bs->activatestack->time < FloatTime()) {
BotPopFromActivateGoalStack(bs);
// if there are more activate goals on the stack
if (bs->activatestack) {
bs->activatestack->time = FloatTime() + 10;
return qfalse;
}
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "activate entity: activated");
return qfalse;
}
2001-05-06 20:50:27 +00:00
//predict obstacles
if (BotAIPredictObstacles(bs, goal))
return qfalse;
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
2001-05-06 20:50:27 +00:00
//
bs->activatestack->time = 0;
}
//check if the bot is blocked
BotAIBlocked(bs, &moveresult, qtrue);
2001-05-06 20:50:27 +00:00
}
//
BotClearPath(bs, &moveresult);
// if the bot has to shoot to activate
if (bs->activatestack->shoot) {
// if the view angles aren't yet used for the movement
if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) {
VectorSubtract(bs->activatestack->target, bs->eye, dir);
vectoangles(dir, moveresult.ideal_viewangles);
moveresult.flags |= MOVERESULT_MOVEMENTVIEW;
}
// if there's no weapon yet used for the movement
if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) {
moveresult.flags |= MOVERESULT_MOVEMENTWEAPON;
//
2001-05-06 20:50:27 +00:00
bs->activatestack->weapon = BotSelectActivateWeapon(bs);
if (bs->activatestack->weapon == -1) {
//FIXME: find a decent weapon first
bs->activatestack->weapon = 0;
}
moveresult.weapon = bs->activatestack->weapon;
}
}
// if the ideal view angles are set for movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
//if waiting for something
else if (moveresult.flags & MOVERESULT_WAITING) {
if (random() < bs->thinktime * 0.8) {
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
} else if (!(bs->flags & BFL_IDEALVIEWSET)) {
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
} else {
2001-05-06 20:50:27 +00:00
vectoangles(moveresult.movedir, bs->ideal_viewangles);
}
bs->ideal_viewangles[2] *= 0.5;
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
//if there is an enemy
if (BotFindEnemy(bs, -1)) {
if (BotWantsToRetreat(bs)) {
//keep the current long term goal and retreat
2001-08-01 19:52:17 +00:00
AIEnter_Battle_NBG(bs, "activate entity: found enemy");
} else {
2001-05-06 20:50:27 +00:00
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "activate entity: found enemy");
2001-05-06 20:50:27 +00:00
}
BotClearActivateGoalStack(bs);
}
return qtrue;
}
/*
==================
AIEnter_Seek_NBG
==================
*/
void AIEnter_Seek_NBG(bot_state_t * bs, char *s)
{
2001-05-06 20:50:27 +00:00
bot_goal_t goal;
char buf[144];
if (trap_BotGetTopGoal(bs->gs, &goal)) {
trap_BotGoalName(goal.number, buf, 144);
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "seek NBG", buf, s);
} else {
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "seek NBG", "no goal", s);
2001-05-06 20:50:27 +00:00
}
//Makro - check if the bot needs to reload/bandage
RQ3_Bot_IdleActions(bs);
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Seek_NBG;
}
/*
==================
AINode_Seek_NBG
==================
*/
int AINode_Seek_NBG(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "seek nbg: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "seek nbg: intermision");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "seek nbg: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//Makro - check if the bot needs to reload/bandage
RQ3_Bot_IdleActions(bs);
2001-05-06 20:50:27 +00:00
//no enemy
bs->enemy = -1;
//if the bot has no goal
if (!trap_BotGetTopGoal(bs->gs, &goal))
bs->nbg_time = 0;
2001-05-06 20:50:27 +00:00
//if the bot touches the current goal
else if (BotReachedGoal(bs, &goal)) {
BotChooseWeapon(bs);
bs->nbg_time = 0;
}
//
if (bs->nbg_time < FloatTime()) {
//pop the current goal from the stack
trap_BotPopGoal(bs->gs);
//check for new nearby items right away
//NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches
bs->check_time = FloatTime() + 0.05;
//go back to seek ltg
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "seek nbg: time out");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//predict obstacles
if (BotAIPredictObstacles(bs, &goal))
return qfalse;
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
bs->nbg_time = 0;
}
//check if the bot is blocked
BotAIBlocked(bs, &moveresult, qtrue);
//
BotClearPath(bs, &moveresult);
//if the viewangles are used for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
//if waiting for something
else if (moveresult.flags & MOVERESULT_WAITING) {
if (random() < bs->thinktime * 0.8) {
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
} else if (!(bs->flags & BFL_IDEALVIEWSET)) {
if (!trap_BotGetSecondGoal(bs->gs, &goal))
trap_BotGetTopGoal(bs->gs, &goal);
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
//FIXME: look at cluster portals?
else
vectoangles(moveresult.movedir, bs->ideal_viewangles);
2001-05-06 20:50:27 +00:00
bs->ideal_viewangles[2] *= 0.5;
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
2001-05-06 20:50:27 +00:00
//if there is an enemy
if (BotFindEnemy(bs, -1)) {
if (BotWantsToRetreat(bs)) {
//keep the current long term goal and retreat
2001-08-01 19:52:17 +00:00
AIEnter_Battle_NBG(bs, "seek nbg: found enemy");
} else {
2001-05-06 20:50:27 +00:00
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "seek nbg: found enemy");
2001-05-06 20:50:27 +00:00
}
}
return qtrue;
}
/*
==================
AIEnter_Seek_LTG
==================
*/
void AIEnter_Seek_LTG(bot_state_t * bs, char *s)
{
2001-05-06 20:50:27 +00:00
bot_goal_t goal;
char buf[144];
2001-05-06 20:50:27 +00:00
if (trap_BotGetTopGoal(bs->gs, &goal)) {
trap_BotGoalName(goal.number, buf, 144);
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "seek LTG", buf, s);
} else {
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "seek LTG", "no goal", s);
2001-05-06 20:50:27 +00:00
}
//Makro - check if the bot needs to reload/bandage
RQ3_Bot_IdleActions(bs);
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Seek_LTG;
}
/*
==================
AINode_Seek_LTG
==================
*/
int AINode_Seek_LTG(bot_state_t * bs)
2001-05-06 20:50:27 +00:00
{
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
int range;
2001-05-06 20:50:27 +00:00
//char buf[128];
//bot_goal_t tmpgoal;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "seek ltg: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "seek ltg: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "seek ltg: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
if (BotChat_Random(bs)) {
bs->stand_time = FloatTime() + BotChatTime(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "seek ltg: random chat");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//Makro - check if the bot needs to reload/bandage
RQ3_Bot_IdleActions(bs);
2001-05-06 20:50:27 +00:00
//no enemy
bs->enemy = -1;
//
if (bs->killedenemy_time > FloatTime() - 2) {
if (random() < bs->thinktime * 1) {
trap_EA_Gesture(bs->client);
}
}
//if there is an enemy
if (BotFindEnemy(bs, -1)) {
if (BotWantsToRetreat(bs)) {
//keep the current long term goal and retreat
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Retreat(bs, "seek ltg: found enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
} else {
2001-05-06 20:50:27 +00:00
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "seek ltg: found enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//
BotTeamGoals(bs, qfalse);
//get the current long term goal
if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
return qtrue;
}
//check for nearby goals periodicly
if (bs->check_time < FloatTime()) {
bs->check_time = FloatTime() + 0.5;
//check if the bot wants to camp
BotWantsToCamp(bs);
//
if (bs->ltgtype == LTG_DEFENDKEYAREA)
range = 400;
else
range = 150;
2001-05-06 20:50:27 +00:00
//
#ifdef CTF
if (gametype == GT_CTF) {
//if carrying a flag the bot shouldn't be distracted too much
2001-05-06 20:50:27 +00:00
if (BotCTFCarryingFlag(bs))
range = 50;
}
#endif //CTF
2001-05-06 20:50:27 +00:00
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
trap_BotResetLastAvoidReach(bs->ms);
//get the goal at the top of the stack
//trap_BotGetTopGoal(bs->gs, &tmpgoal);
//trap_BotGoalName(tmpgoal.number, buf, 144);
//BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
//time the bot gets to pick up the nearby goal item
bs->nbg_time = FloatTime() + 4 + range * 0.01;
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "ltg seek: nbg");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//predict obstacles
if (BotAIPredictObstacles(bs, &goal))
return qfalse;
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
bs->ltg_time = 0;
}
//
BotAIBlocked(bs, &moveresult, qtrue);
//
BotClearPath(bs, &moveresult);
//if the viewangles are used for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
//if waiting for something
else if (moveresult.flags & MOVERESULT_WAITING) {
if (random() < bs->thinktime * 0.8) {
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
} else if (!(bs->flags & BFL_IDEALVIEWSET)) {
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
//FIXME: look at cluster portals?
else if (VectorLengthSquared(moveresult.movedir)) {
vectoangles(moveresult.movedir, bs->ideal_viewangles);
} else if (random() < bs->thinktime * 0.8) {
2001-05-06 20:50:27 +00:00
BotRoamGoal(bs, target);
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
bs->ideal_viewangles[2] *= 0.5;
}
bs->ideal_viewangles[2] *= 0.5;
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
2001-05-06 20:50:27 +00:00
//
return qtrue;
}
/*
==================
AIEnter_Battle_Fight
==================
*/
void AIEnter_Battle_Fight(bot_state_t * bs, char *s)
{
2002-06-01 13:37:02 +00:00
float attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "battle fight", "", s);
2001-05-06 20:50:27 +00:00
trap_BotResetLastAvoidReach(bs->ms);
//Makro - check if the bot has leg damage
if (RQ3_Bot_NeedToBandage(bs) == 2) {
2002-06-01 13:37:02 +00:00
if (random() > attack_skill) {
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING) {
Cmd_Bandage(&g_entities[bs->entitynum]);
2002-06-01 13:37:02 +00:00
}
}
}
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Battle_Fight;
}
/*
==================
AIEnter_Battle_SuicidalFight
2001-05-06 20:50:27 +00:00
==================
*/
void AIEnter_Battle_SuicidalFight(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "battle fight", "", s);
2001-05-06 20:50:27 +00:00
trap_BotResetLastAvoidReach(bs->ms);
bs->ainode = AINode_Battle_Fight;
bs->flags |= BFL_FIGHTSUICIDAL;
}
/*
==================
AINode_Battle_Fight
==================
*/
int AINode_Battle_Fight(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
int areanum;
vec3_t target;
aas_entityinfo_t entinfo;
bot_moveresult_t moveresult;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "battle fight: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "battle fight: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "battle fight: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if there is another better enemy
if (BotFindEnemy(bs, bs->enemy)) {
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
#endif
}
//if no enemy
if (bs->enemy < 0) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle fight: no enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
//if the enemy is dead
if (bs->enemydeath_time) {
if (bs->enemydeath_time < FloatTime() - 1.0) {
bs->enemydeath_time = 0;
2002-06-01 13:37:02 +00:00
if (random() > 0.3f) {
//Makro - check if the bot needs to bandage
if (RQ3_Bot_NeedToBandage(bs) != 0) {
if (RQ3_Bot_CheckBandage(bs)) {
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING) {
Cmd_Bandage(&g_entities[bs->entitynum]);
2002-06-01 13:37:02 +00:00
}
}
}
} //
2001-05-06 20:50:27 +00:00
if (bs->enemysuicide) {
BotChat_EnemySuicide(bs);
}
if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
bs->stand_time = FloatTime() + BotChatTime(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "battle fight: enemy dead");
} else {
2001-05-06 20:50:27 +00:00
bs->ltg_time = 0;
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle fight: enemy dead");
2001-05-06 20:50:27 +00:00
}
return qfalse;
}
} else {
2001-05-06 20:50:27 +00:00
if (EntityIsDead(&entinfo)) {
bs->enemydeath_time = FloatTime();
}
}
//if the enemy is invisible and not shooting the bot looses track easily
if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
if (random() < 0.2) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle fight: invisible");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//
VectorCopy(entinfo.origin, target);
// if not a player enemy
if (bs->enemy >= MAX_CLIENTS) {
}
//update the reachability area and origin if possible
areanum = BotPointAreaNum(target);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(target, bs->lastenemyorigin);
bs->lastenemyareanum = areanum;
}
//update the attack inventory values
BotUpdateBattleInventory(bs, bs->enemy);
//if the bot's health decreased
if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
if (BotChat_HitNoDeath(bs)) {
bs->stand_time = FloatTime() + BotChatTime(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "battle fight: chat health decreased");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//if the bot hit someone
if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) {
if (BotChat_HitNoKill(bs)) {
bs->stand_time = FloatTime() + BotChatTime(bs);
2001-08-01 19:52:17 +00:00
AIEnter_Stand(bs, "battle fight: chat hit someone");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//if the enemy is not visible
if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
if (BotWantsToChase(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight");
2001-05-06 20:50:27 +00:00
return qfalse;
} else {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//use holdable items
BotBattleUseItems(bs);
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//choose the best weapon to fight with
BotChooseWeapon(bs);
//do attack movements
moveresult = BotAttackMove(bs, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
bs->ltg_time = 0;
}
//
BotAIBlocked(bs, &moveresult, qfalse);
//aim at the enemy
BotAimAtEnemy(bs);
//attack the enemy if possible
BotCheckAttack(bs);
//if the bot wants to retreat
if (!(bs->flags & BFL_FIGHTSUICIDAL)) {
if (BotWantsToRetreat(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat");
2001-05-06 20:50:27 +00:00
return qtrue;
}
}
return qtrue;
}
/*
==================
AIEnter_Battle_Chase
==================
*/
void AIEnter_Battle_Chase(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "battle chase", "", s);
2001-05-06 20:50:27 +00:00
bs->chase_time = FloatTime();
bs->ainode = AINode_Battle_Chase;
}
/*
==================
AINode_Battle_Chase
==================
*/
int AINode_Battle_Chase(bot_state_t * bs)
2001-05-06 20:50:27 +00:00
{
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
float range;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "battle chase: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "battle chase: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "battle chase: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle chase: no enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if the enemy is visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "battle chase");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if there is another enemy
if (BotFindEnemy(bs, -1)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "battle chase: better enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//there is no last enemy area
if (!bs->lastenemyareanum) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle chase: no enemy area");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//create the chase goal
goal.entitynum = bs->enemy;
goal.areanum = bs->lastenemyareanum;
VectorCopy(bs->lastenemyorigin, goal.origin);
VectorSet(goal.mins, -8, -8, -8);
VectorSet(goal.maxs, 8, 8, 8);
//if the last seen enemy spot is reached the enemy could not be found
if (trap_BotTouchingGoal(bs->origin, &goal))
bs->chase_time = 0;
2001-05-06 20:50:27 +00:00
//if there's no chase time left
if (!bs->chase_time || bs->chase_time < FloatTime() - 10) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle chase: time out");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//check for nearby goals periodicly
if (bs->check_time < FloatTime()) {
bs->check_time = FloatTime() + 1;
range = 150;
//
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
//the bot gets 5 seconds to pick up the nearby goal item
bs->nbg_time = FloatTime() + 0.1 * range + 1;
trap_BotResetLastAvoidReach(bs->ms);
2001-08-01 19:52:17 +00:00
AIEnter_Battle_NBG(bs, "battle chase: nbg");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//
BotUpdateBattleInventory(bs, bs->enemy);
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
bs->ltg_time = 0;
}
//
BotAIBlocked(bs, &moveresult, qfalse);
//
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
} else if (!(bs->flags & BFL_IDEALVIEWSET)) {
2001-05-06 20:50:27 +00:00
if (bs->chase_time > FloatTime() - 2) {
BotAimAtEnemy(bs);
} else {
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
} else {
2001-05-06 20:50:27 +00:00
vectoangles(moveresult.movedir, bs->ideal_viewangles);
}
}
bs->ideal_viewangles[2] *= 0.5;
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
2001-05-06 20:50:27 +00:00
//if the bot is in the area the enemy was last seen in
if (bs->areanum == bs->lastenemyareanum)
bs->chase_time = 0;
2001-05-06 20:50:27 +00:00
//if the bot wants to retreat (the bot could have been damage during the chase)
if (BotWantsToRetreat(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat");
2001-05-06 20:50:27 +00:00
return qtrue;
}
return qtrue;
}
/*
==================
AIEnter_Battle_Retreat
==================
*/
void AIEnter_Battle_Retreat(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "battle retreat", "", s);
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Battle_Retreat;
}
/*
==================
AINode_Battle_Retreat
==================
*/
int AINode_Battle_Retreat(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
bot_goal_t goal;
aas_entityinfo_t entinfo;
bot_moveresult_t moveresult;
vec3_t target, dir;
float attack_skill, range;
int areanum;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "battle retreat: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "battle retreat: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "battle retreat: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle retreat: no enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
if (EntityIsDead(&entinfo)) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_LTG(bs, "battle retreat: enemy dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if there is another better enemy
if (BotFindEnemy(bs, bs->enemy)) {
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
#endif
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//map specific code
BotMapScripts(bs);
//update the attack inventory values
BotUpdateBattleInventory(bs, bs->enemy);
//if the bot doesn't want to retreat anymore... probably picked up some nice items
if (BotWantsToChase(bs)) {
//empty the goal stack, when chasing, only the enemy is the goal
trap_BotEmptyGoalStack(bs->gs);
//go chase the enemy
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Chase(bs, "battle retreat: wants to chase");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//update the last time the enemy was visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
bs->enemyvisible_time = FloatTime();
VectorCopy(entinfo.origin, target);
// if not a player enemy
if (bs->enemy >= MAX_CLIENTS) {
}
//update the reachability area and origin if possible
areanum = BotPointAreaNum(target);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(target, bs->lastenemyorigin);
bs->lastenemyareanum = areanum;
}
}
//if the enemy is NOT visible for 4 seconds
if (bs->enemyvisible_time < FloatTime() - 4) {
//Makro - bot retreating, enemy not in sight - a good time to bandage
2002-06-01 13:37:02 +00:00
//if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
if (random() > 0.3f) {
if (RQ3_Bot_NeedToBandage(bs)) {
//If not bandaging already
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING) {
Cmd_Bandage(&g_entities[bs->entitynum]);
2002-06-01 13:37:02 +00:00
}
}
}
AIEnter_Seek_LTG(bs, "battle retreat: lost enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//else if the enemy is NOT visible
else if (bs->enemyvisible_time < FloatTime()) {
//if there is another enemy
if (BotFindEnemy(bs, -1)) {
2002-06-01 13:37:02 +00:00
if (random() < 0.5f) {
//if the bot is hurt
if (RQ3_Bot_NeedToBandage(bs)) {
//If the bot wants to bandage and not bandaging already
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING && RQ3_Bot_CheckBandage(bs)) {
Cmd_Bandage(&g_entities[bs->entitynum]);
2002-06-01 13:37:02 +00:00
}
}
}
2001-08-01 19:52:17 +00:00
AIEnter_Battle_Fight(bs, "battle retreat: another enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
} else {
if (RQ3_Bot_NeedToBandage(bs) != 0) {
2002-06-01 13:37:02 +00:00
//If not bandaging already
if (bs->cur_ps.weaponstate != WEAPON_BANDAGING) {
Cmd_Bandage(&g_entities[bs->entitynum]);
}
}
2001-05-06 20:50:27 +00:00
}
}
//
BotTeamGoals(bs, qtrue);
//use holdable items
BotBattleUseItems(bs);
//get the current long term goal while retreating
if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) {
2001-08-01 19:52:17 +00:00
AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//check for nearby goals periodicly
if (bs->check_time < FloatTime()) {
bs->check_time = FloatTime() + 1;
range = 150;
#ifdef CTF
if (gametype == GT_CTF) {
//if carrying a flag the bot shouldn't be distracted too much
2001-05-06 20:50:27 +00:00
if (BotCTFCarryingFlag(bs))
range = 50;
}
#endif //CTF
2001-05-06 20:50:27 +00:00
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
trap_BotResetLastAvoidReach(bs->ms);
//time the bot gets to pick up the nearby goal item
bs->nbg_time = FloatTime() + range / 100 + 1;
2001-08-01 19:52:17 +00:00
AIEnter_Battle_NBG(bs, "battle retreat: nbg");
2001-05-06 20:50:27 +00:00
return qfalse;
}
}
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
bs->ltg_time = 0;
}
//
BotAIBlocked(bs, &moveresult, qfalse);
//choose the best weapon to fight with
BotChooseWeapon(bs);
//if the view is fixed for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
} else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
&& !(bs->flags & BFL_IDEALVIEWSET)) {
2001-05-06 20:50:27 +00:00
attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
//if the bot is skilled anough
if (attack_skill > 0.3) {
BotAimAtEnemy(bs);
} else {
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
} else {
2001-05-06 20:50:27 +00:00
vectoangles(moveresult.movedir, bs->ideal_viewangles);
}
bs->ideal_viewangles[2] *= 0.5;
}
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
2001-05-06 20:50:27 +00:00
//attack the enemy if possible
BotCheckAttack(bs);
//
return qtrue;
}
/*
==================
AIEnter_Battle_NBG
==================
*/
void AIEnter_Battle_NBG(bot_state_t * bs, char *s)
{
2001-08-01 19:52:17 +00:00
BotRecordNodeSwitch(bs, "battle NBG", "", s);
2001-05-06 20:50:27 +00:00
bs->ainode = AINode_Battle_NBG;
}
/*
==================
AINode_Battle_NBG
==================
*/
int AINode_Battle_NBG(bot_state_t * bs)
{
2001-05-06 20:50:27 +00:00
int areanum;
bot_goal_t goal;
aas_entityinfo_t entinfo;
bot_moveresult_t moveresult;
float attack_skill;
vec3_t target, dir;
if (BotIsObserver(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Observer(bs, "battle nbg: observer");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Intermission(bs, "battle nbg: intermission");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
2001-08-01 19:52:17 +00:00
AIEnter_Respawn(bs, "battle nbg: bot dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "battle nbg: no enemy");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
if (EntityIsDead(&entinfo)) {
2001-08-01 19:52:17 +00:00
AIEnter_Seek_NBG(bs, "battle nbg: enemy dead");
2001-05-06 20:50:27 +00:00
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer)
bs->tfl |= TFL_GRAPPLEHOOK;
2001-05-06 20:50:27 +00:00
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs))
bs->tfl |= TFL_LAVA | TFL_SLIME;
2001-05-06 20:50:27 +00:00
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//update the last time the enemy was visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
bs->enemyvisible_time = FloatTime();
VectorCopy(entinfo.origin, target);
// if not a player enemy
if (bs->enemy >= MAX_CLIENTS) {
}
//update the reachability area and origin if possible
areanum = BotPointAreaNum(target);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(target, bs->lastenemyorigin);
bs->lastenemyareanum = areanum;
}
}
//if the bot has no goal or touches the current goal
if (!trap_BotGetTopGoal(bs->gs, &goal)) {
bs->nbg_time = 0;
} else if (BotReachedGoal(bs, &goal)) {
2001-05-06 20:50:27 +00:00
bs->nbg_time = 0;
}
//
if (bs->nbg_time < FloatTime()) {
//pop the current goal from the stack
trap_BotPopGoal(bs->gs);
//if the bot still has a goal
2001-08-01 19:52:17 +00:00
if (trap_BotGetTopGoal(bs->gs, &goal))
AIEnter_Battle_Retreat(bs, "battle nbg: time out");
else
AIEnter_Battle_Fight(bs, "battle nbg: time out");
2001-05-06 20:50:27 +00:00
//
return qfalse;
}
//initialize the movement state
BotSetupForMovement(bs);
//move towards the goal
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
//if the movement failed
if (moveresult.failure) {
//reset the avoid reach, otherwise bot is stuck in current area
trap_BotResetAvoidReach(bs->ms);
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
bs->nbg_time = 0;
}
//
BotAIBlocked(bs, &moveresult, qfalse);
//update the attack inventory values
BotUpdateBattleInventory(bs, bs->enemy);
//choose the best weapon to fight with
BotChooseWeapon(bs);
//if the view is fixed for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
2001-05-06 20:50:27 +00:00
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
} else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
&& !(bs->flags & BFL_IDEALVIEWSET)) {
2001-05-06 20:50:27 +00:00
attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
//if the bot is skilled anough and the enemy is visible
if (attack_skill > 0.3) {
//&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)
BotAimAtEnemy(bs);
} else {
2001-05-06 20:50:27 +00:00
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
} else {
2001-05-06 20:50:27 +00:00
vectoangles(moveresult.movedir, bs->ideal_viewangles);
}
bs->ideal_viewangles[2] *= 0.5;
}
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
bs->weaponnum = moveresult.weapon;
2001-05-06 20:50:27 +00:00
//attack the enemy if possible
BotCheckAttack(bs);
//
return qtrue;
}