rpg-x2/game/ai_dmnet.c
2011-06-01 14:20:56 +02:00

2030 lines
56 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
/*****************************************************************************
* name: ai_dmnet.c
*
* desc: Quake3 bot AI
*
* $Archive: /StarTrek/Code-DM/game/ai_dmnet.c $
* $Author: Mgummelt $
* $Revision: 7 $
* $Modtime: 3/28/01 11:12a $
* $Date: 3/28/01 11:15a $
*
*****************************************************************************/
#include "g_local.h"
#include "botlib.h"
#include "be_aas.h"
#include "be_ea.h"
#include "be_ai_char.h"
#include "be_ai_chat.h"
#include "be_ai_gen.h"
#include "be_ai_goal.h"
#include "be_ai_move.h"
#include "be_ai_weap.h"
//
#include "ai_main.h"
#include "ai_dmq3.h"
#include "ai_chat.h"
#include "ai_cmd.h"
#include "ai_dmnet.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
//goal flag, see be_ai_goal.h for the other GFL_*
#define GFL_AIR 128
int numnodeswitches;
char nodeswitch[MAX_NODESWITCHES+1][144];
#define LOOKAHEAD_DISTANCE 300
/*
==================
BotResetNodeSwitches
==================
*/
void BotResetNodeSwitches(void) {
numnodeswitches = 0;
}
/*
==================
BotDumpNodeSwitches
==================
*/
void BotDumpNodeSwitches(bot_state_t *bs) {
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, trap_AAS_Time(), MAX_NODESWITCHES);
for (i = 0; i < numnodeswitches; i++) {
BotAI_Print(PRT_MESSAGE, nodeswitch[i]);
}
BotAI_Print(PRT_FATAL, "");
}
/*
==================
BotRecordNodeSwitch
==================
*/
void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str) {
char netname[MAX_NETNAME];
ClientName(bs->client, netname, sizeof(netname));
Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s\n", netname, trap_AAS_Time(), node, str);
#ifdef DEBUG
if (0) {
BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]);
}
#endif //DEBUG
numnodeswitches++;
}
/*
==================
BotGetAirGoal
==================
*/
int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) {
bsp_trace_t bsptrace;
vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2};
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);
//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);
//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) {
bot_goal_t goal;
//if the bot needs air
if (bs->lastair_time < trap_AAS_Time() - 6) {
//
#ifdef DEBUG
//BotAI_Print(PRT_MESSAGE, "going for air\n");
#endif //DEBUG
//if we can find an air goal
if (BotGetAirGoal(bs, &goal)) {
trap_BotPushGoal(bs->gs, &goal);
return qtrue;
}
else {
qboolean botRoamsOnly = qtrue;
/*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
{
botRoamsOnly = qtrue;
}*/
if ( g_pModDisintegration.integer != 0 )
{
botRoamsOnly = qtrue;
}
//get a nearby goal outside the water
while( trap_BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly ) ) {
trap_BotGetTopGoal(bs->gs, &goal);
//if the goal is not in water
if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) {
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) {
int ret;
qboolean botRoamsOnly = qtrue;
//check if the bot should go for air
if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
//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) {
//make the range really small
range = 50;
}
}
//
/*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
{
botRoamsOnly = qtrue;
}*/
if ( g_pModDisintegration.integer != 0 )
{
botRoamsOnly = qtrue;
}
ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly );
/*
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", trap_AAS_Time(), buf);
}
//*/
return ret;
}
/*
==================
BotReachedGoal
==================
*/
int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) {
if (goal->flags & GFL_ITEM) {
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
//if the goal isn't there
if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) 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]) {
if (!trap_AAS_Swimming(bs->origin)) {
return qtrue;
}
}
}
}
}
else if (goal->flags & GFL_AIR) {
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
//if the bot got air
if (bs->lastair_time > trap_AAS_Time() - 1) return qtrue;
}
else {
//if touching the goal
if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
}
return qfalse;
}
/*
==================
BotGetItemLongTermGoal
==================
*/
int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) {
qboolean botRoamsOnly = qtrue;
//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 < trap_AAS_Time()) {
//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", trap_AAS_Time(), bs->client);
/*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
{
botRoamsOnly = qtrue;
}*/
if ( g_pModDisintegration.integer != 0 )
{
botRoamsOnly = qtrue;
}
if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl, botRoamsOnly)) {
/*
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", trap_AAS_Time(), buf);
//*/
bs->ltg_time = trap_AAS_Time() + 20;
}
else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though
//
#ifdef DEBUG
//char netname[128];
//BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname)));
#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) {
vec3_t target, dir;
char netname[MAX_NETNAME];
char buf[MAX_MESSAGE_SIZE];
int areanum;
float croucher;
aas_entityinfo_t entinfo;
bot_waypoint_t *wp;
if (bs->ltgtype == LTG_TEAMHELP && !retreat) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//if trying to help the team mate for more than a minute
if (bs->teamgoal_time < trap_AAS_Time())
bs->ltgtype = 0;
//if the team mate IS visible for quite some time
if (bs->teammatevisible_time < trap_AAS_Time() - 10) bs->ltgtype = 0;
//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 (VectorLength(dir) < 100) {
trap_BotResetAvoidReach(bs->ms);
return qfalse;
}
}
else {
//last time the bot was NOT visible
bs->teammatevisible_time = trap_AAS_Time();
}
//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 < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//if accompanying the companion for 3 minutes
if (bs->teamgoal_time < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
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 = trap_AAS_Time();
VectorSubtract(entinfo.origin, bs->origin, dir);
if (VectorLength(dir) < bs->formation_dist) {
//check if the bot wants to crouch
//don't crouch if crouched less than 5 seconds ago
if (bs->attackcrouch_time < trap_AAS_Time() - 5) {
croucher = 1;
if (random() < bs->thinktime * croucher) {
bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
}
}
//don't crouch when swimming
if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
//if not arrived yet or arived some time ago
if (bs->arrive_time < trap_AAS_Time() - 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);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->arrive_time = trap_AAS_Time();
}
//if the bot wants to crouch
else if (bs->attackcrouch_time > trap_AAS_Time()) {
trap_EA_Crouch(bs->client);
}
//else do some model taunts
else if (random() < bs->thinktime * 0.3) {
//do a gesture :)
trap_EA_Gesture(bs->client);
}
}
//if just arrived look at the companion
if (bs->arrive_time > trap_AAS_Time() - 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 = trap_AAS_Time() + 8;
AIEnter_Seek_NBG(bs);
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 so bot will accompany
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 < trap_AAS_Time() - 60) {
BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->ltgtype = 0;
}
return qtrue;
}
//
if (bs->ltgtype == LTG_DEFENDKEYAREA) {
if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) {
bs->defendaway_time = 0;
}
}
//if defending a key area
if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat &&
bs->defendaway_time < trap_AAS_Time()) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "defend_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//stop after 2 minutes
if (bs->teamgoal_time < trap_AAS_Time()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "defend_stop", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->ltgtype = 0;
}
//if very close... go away for some time
VectorSubtract(goal->origin, bs->origin, dir);
if (VectorLength(dir) < 70) {
trap_BotResetAvoidReach(bs->ms);
bs->defendaway_time = trap_AAS_Time() + 2 + 5 * random();
bs->defendaway_range = 250;
}
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 < trap_AAS_Time()) {
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "kill_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
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->client, CHAT_TEAM);
bs->lastkilledplayer = -1;
bs->ltgtype = 0;
}
//
if (bs->teamgoal_time < trap_AAS_Time()) {
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 < trap_AAS_Time()) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "getitem_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//stop after some time
if (bs->teamgoal_time < trap_AAS_Time()) {
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->client, CHAT_TEAM);
bs->ltgtype = 0;
}
else if (BotReachedGoal(bs, goal)) {
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
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 < trap_AAS_Time()) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
}
bs->teammessage_time = 0;
}
//set the bot goal
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
//
if (bs->teamgoal_time < trap_AAS_Time()) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_stop", NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
}
bs->ltgtype = 0;
}
//if really near the camp spot
VectorSubtract(goal->origin, bs->origin, dir);
if (VectorLength(dir) < 60)
{
//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);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
}
bs->arrive_time = trap_AAS_Time();
}
//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 < trap_AAS_Time() - 5) {
croucher = 1;
if (random() < bs->thinktime * croucher) {
bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
}
}
//if the bot wants to crouch
if (bs->attackcrouch_time > trap_AAS_Time()) {
trap_EA_Crouch(bs->client);
}
//don't crouch when swimming
if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
//make sure the bot is not gonna drown
if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
if (bs->ltgtype == LTG_CAMPORDER) {
BotAI_BotInitialChat(bs, "camp_stop", NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
}
bs->ltgtype = 0;
}
//
if (bs->camp_range > 0) {
//FIXME: move around a bit
}
//
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 < trap_AAS_Time()) {
strcpy(buf, "");
for (wp = bs->patrolpoints; wp; wp = wp->next) {
strcat(buf, wp->name);
if (wp->next) strcat(buf, " to ");
}
BotAI_BotInitialChat(bs, "patrol_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
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 {
bs->curpatrolpoint = bs->curpatrolpoint->next;
bs->patrolflags &= ~PATROL_BACK;
}
}
else {
if (bs->curpatrolpoint->next) {
bs->curpatrolpoint = bs->curpatrolpoint->next;
}
else {
bs->curpatrolpoint = bs->curpatrolpoint->prev;
bs->patrolflags |= PATROL_BACK;
}
}
}
//stop after 5 minutes
if (bs->teamgoal_time < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "patrol_stop", NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
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 going for enemy flag
if (bs->ltgtype == LTG_GETFLAG) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "captureflag_start", NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//
switch(BotCTFTeam(bs)) {
case CTF_TEAM_RED: *goal = ctf_blueflag; break;
case CTF_TEAM_BLUE: *goal = ctf_redflag; break;
default: bs->ltgtype = 0; return qfalse;
}
//if touching the flag
if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0;
//stop after 3 minutes
if (bs->teamgoal_time < trap_AAS_Time()) {
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "%s: I quit getting the flag\n", ClientName(bs->client, netname, sizeof(netname)));
#endif //DEBUG
bs->ltgtype = 0;
}
return qtrue;
}
//if rushing to the base
if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < trap_AAS_Time()) {
switch(BotCTFTeam(bs)) {
case CTF_TEAM_RED: *goal = ctf_redflag; break;
case CTF_TEAM_BLUE: *goal = ctf_blueflag; break;
default: bs->ltgtype = 0; return qfalse;
}
//if not carrying the flag anymore
if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0;
//quit rushing after 2 minutes
if (bs->teamgoal_time < trap_AAS_Time()) 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 = trap_AAS_Time() + 5 + 10 * random();
//FIXME: add chat to tell the others to get back the flag
//FIXME: Make them camp? Get health? Preserve themselves?
}
else {
bs->ltgtype = 0;
}
}
return qtrue;
}
//returning flag
if (bs->ltgtype == LTG_RETURNFLAG) {
//check for bot typing status message
if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
BotAI_BotInitialChat(bs, "returnflag_start", buf, NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->teammessage_time = 0;
}
//
if (bs->teamgoal_time < trap_AAS_Time()) {
bs->ltgtype = 0;
}
//FIXME: Uh.... we're trying to retrieve our flag, shouldn't
// we set that as our goal somewhere?
//ALSO: Can't we also easily implement the ability to pick up
// the enemy flag if it's dropped in the field by a teammate?
//just roam around
return BotGetItemLongTermGoal(bs, tfl, goal);
}
#endif //CTF
//normal goal stuff
return BotGetItemLongTermGoal(bs, tfl, goal);
}
/*
==================
BotLongTermGoal
==================
*/
int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
aas_entityinfo_t entinfo;
char teammate[MAX_MESSAGE_SIZE];
float dist;
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 < trap_AAS_Time()) {
//FIXME: add chat to tell the team mate that he/she's on his/her own
bs->lead_time = 0;
return BotGetLongTermGoal(bs, tfl, retreat, goal);
}
//
if (bs->leadmessage_time < 0 && -bs->leadmessage_time < trap_AAS_Time()) {
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->leadmessage_time = trap_AAS_Time();
}
//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 = trap_AAS_Time();
}
//if the team mate is not visible for 1 seconds
if (bs->leadvisible_time < trap_AAS_Time() - 1) {
bs->leadbackup_time = trap_AAS_Time() + 2;
}
//distance towards the team mate
VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir);
dist = VectorLength(dir);
//if backing up towards the team mate
if (bs->leadbackup_time > trap_AAS_Time()) {
if (bs->leadmessage_time < trap_AAS_Time() - 20) {
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->leadmessage_time = trap_AAS_Time();
}
//if very close to the team mate
if (dist < 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 {
//if quite distant from the team mate
if (dist > 500) {
if (bs->leadmessage_time < trap_AAS_Time() - 20) {
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
bs->leadmessage_time = trap_AAS_Time();
}
//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) {
BotRecordNodeSwitch(bs, "intermission", "");
//reset the bot state
BotResetState(bs);
//check for end level chat
if (BotChat_EndLevel(bs)) {
trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
}
bs->ainode = AINode_Intermission;
}
/*
==================
AINode_Intermission
==================
*/
int AINode_Intermission(bot_state_t *bs) {
//if the intermission ended
if (!BotIntermission(bs)) {
if (BotChat_StartLevel(bs)) {
bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
}
else {
bs->stand_time = trap_AAS_Time() + 2;
}
AIEnter_Stand(bs);
}
return qtrue;
}
/*
==================
AIEnter_Observer
==================
*/
void AIEnter_Observer(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "observer", "");
//reset the bot state
BotResetState(bs);
bs->ainode = AINode_Observer;
}
/*
==================
AINode_Observer
==================
*/
int AINode_Observer(bot_state_t *bs) {
//if the bot left observer mode
if (!BotIsObserver(bs)) {
AIEnter_Stand(bs);
}
return qtrue;
}
/*
==================
AIEnter_Stand
==================
*/
void AIEnter_Stand(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "stand", "");
bs->standfindenemy_time = trap_AAS_Time() + 1;
bs->ainode = AINode_Stand;
}
/*
==================
AINode_Stand
==================
*/
int AINode_Stand(bot_state_t *bs) {
//if the bot's health decreased
if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
if (BotChat_HitTalking(bs)) {
bs->standfindenemy_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
bs->stand_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
}
}
if (bs->standfindenemy_time < trap_AAS_Time()) {
if (BotFindEnemy(bs, -1)) {
AIEnter_Battle_Fight(bs);
return qfalse;
}
bs->standfindenemy_time = trap_AAS_Time() + 1;
}
trap_EA_Talk(bs->client);
if (bs->stand_time < trap_AAS_Time()) {
trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
AIEnter_Seek_LTG(bs);
return qfalse;
}
//
return qtrue;
}
/*
==================
AIEnter_Respawn
==================
*/
void AIEnter_Respawn(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "respawn", "");
//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 = trap_AAS_Time() + BotChatTime(bs);
bs->respawnchat_time = trap_AAS_Time();
}
else {
bs->respawn_time = trap_AAS_Time() + 1 + random();
bs->respawnchat_time = 0;
}
//set respawn state
bs->respawn_wait = qfalse;
bs->ainode = AINode_Respawn;
}
/*
==================
AINode_Respawn
==================
*/
int AINode_Respawn(bot_state_t *bs) {
if (bs->respawn_wait) {
if (!BotIsDead(bs)) {
AIEnter_Seek_LTG(bs);
}
else {
trap_EA_Respawn(bs->client);
}
}
else if (bs->respawn_time < trap_AAS_Time()) {
//wait until respawned
bs->respawn_wait = qtrue;
//elementary action respawn
trap_EA_Respawn(bs->client);
//
if (bs->respawnchat_time) {
trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
bs->enemy = -1;
}
}
if (bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5) {
trap_EA_Talk(bs->client);
}
//
return qtrue;
}
/*
==================
AIEnter_Seek_ActivateEntity
==================
*/
void AIEnter_Seek_ActivateEntity(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "activate entity", "");
bs->ainode = AINode_Seek_ActivateEntity;
}
/*
==================
AINode_Seek_Activate_Entity
==================
*/
int AINode_Seek_ActivateEntity(bot_state_t *bs) {
bot_goal_t *goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//map specific code
BotMapScripts(bs);
//no enemy
bs->enemy = -1;
//
goal = &bs->activategoal;
//if the bot has no goal
if (!goal) bs->activate_time = 0;
//if the bot touches the current goal
else if (trap_BotTouchingGoal(bs->origin, goal)) {
BotChooseWeapon(bs);
#ifdef DEBUG
BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
#endif //DEBUG
bs->activate_time = 0;
}
//
if (bs->activate_time < trap_AAS_Time()) {
AIEnter_Seek_NBG(bs);
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);
//
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
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_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
else {
//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
AIEnter_Battle_NBG(bs);
}
else {
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
AIEnter_Battle_Fight(bs);
}
}
return qtrue;
}
/*
==================
AIEnter_Seek_NBG
==================
*/
void AIEnter_Seek_NBG(bot_state_t *bs) {
bot_goal_t goal;
char buf[144];
if (trap_BotGetTopGoal(bs->gs, &goal)) {
trap_BotGoalName(goal.number, buf, 144);
BotRecordNodeSwitch(bs, "seek NBG", buf);
}
else {
BotRecordNodeSwitch(bs, "seek NBG", "no goal");
}
bs->ainode = AINode_Seek_NBG;
}
/*
==================
AINode_Seek_NBG
==================
*/
int AINode_Seek_NBG(bot_state_t *bs) {
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//no enemy
bs->enemy = -1;
//if the bot has no goal
if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0;
//if the bot touches the current goal
else if (BotReachedGoal(bs, &goal)) {
BotChooseWeapon(bs);
bs->nbg_time = 0;
}
//
if (bs->nbg_time < trap_AAS_Time()) {
//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 = trap_AAS_Time() + 0.05;
//go back to seek ltg
AIEnter_Seek_LTG(bs);
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);
//if the viewangles are used for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
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);
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);
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
AIEnter_Battle_NBG(bs);
}
else {
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
AIEnter_Battle_Fight(bs);
}
}
return qtrue;
}
/*
==================
AIEnter_Seek_LTG
==================
*/
void AIEnter_Seek_LTG(bot_state_t *bs) {
bot_goal_t goal;
char buf[144];
if (trap_BotGetTopGoal(bs->gs, &goal)) {
trap_BotGoalName(goal.number, buf, 144);
BotRecordNodeSwitch(bs, "seek LTG", buf);
}
else {
BotRecordNodeSwitch(bs, "seek LTG", "no goal");
}
bs->ainode = AINode_Seek_LTG;
}
/*
==================
AINode_Seek_LTG
==================
*/
int AINode_Seek_LTG(bot_state_t *bs)
{
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
int range;
//char buf[128];
//bot_goal_t tmpgoal;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//
if (BotChat_Random(bs)) {
bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
AIEnter_Stand(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//
if (BotCanAndWantsToRocketJump(bs)) {
bs->tfl |= TFL_ROCKETJUMP;
}
//map specific code
BotMapScripts(bs);
//no enemy
bs->enemy = -1;
//
if (bs->killedenemy_time > trap_AAS_Time() - 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
AIEnter_Battle_Retreat(bs);
return qfalse;
}
else {
trap_BotResetLastAvoidReach(bs->ms);
//empty the goal stack
trap_BotEmptyGoalStack(bs->gs);
//go fight
AIEnter_Battle_Fight(bs);
return qfalse;
}
}
#ifdef CTF
if (gametype == GT_CTF) {
//decide what to do in CTF mode
BotCTFSeekGoals(bs);
}
#endif //CTF
//get the current long term goal
if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
return qtrue;
}
//check for nearby goals periodicly
if (bs->check_time < trap_AAS_Time()) {
bs->check_time = trap_AAS_Time() + 0.5;
//check if the bot wants to camp
BotWantsToCamp(bs);
//
if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400;
else range = 150;
//
#ifdef CTF
//if carrying a flag the bot shouldn't be distracted too much
if (BotCTFCarryingFlag(bs)) range = 50;
#endif //CTF
//
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 = trap_AAS_Time() + 4 + range * 0.01;
AIEnter_Seek_NBG(bs);
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);
//if the viewangles are used for the movement
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
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_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 (VectorLength(moveresult.movedir)) {
vectoangles(moveresult.movedir, bs->ideal_viewangles);
}
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;
}
bs->ideal_viewangles[2] *= 0.5;
}
//if the weapon is used for the bot movement
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
//
return qtrue;
}
/*
==================
AIEnter_Battle_Fight
==================
*/
void AIEnter_Battle_Fight(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "battle fight", "");
trap_BotResetLastAvoidReach(bs->ms);
bs->ainode = AINode_Battle_Fight;
}
/*
==================
AIEnter_Battle_Fight
==================
*/
void AIEnter_Battle_SuicidalFight(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "battle fight", "");
trap_BotResetLastAvoidReach(bs->ms);
bs->ainode = AINode_Battle_Fight;
bs->flags |= BFL_FIGHTSUICIDAL;
}
/*
==================
AINode_Battle_Fight
==================
*/
int AINode_Battle_Fight(bot_state_t *bs) {
int areanum;
aas_entityinfo_t entinfo;
bot_moveresult_t moveresult;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
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) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
//if the enemy is dead
if (bs->enemydeath_time) {
if (bs->enemydeath_time < trap_AAS_Time() - 1.0) {
bs->enemydeath_time = 0;
if (bs->enemysuicide) {
BotChat_EnemySuicide(bs);
}
if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
AIEnter_Stand(bs);
}
else {
bs->ltg_time = 0;
AIEnter_Seek_LTG(bs);
}
return qfalse;
}
}
else {
if (EntityIsDead(&entinfo)) {
bs->enemydeath_time = trap_AAS_Time();
}
}
//if the enemy is invisible and not shooting the bot looses track easily
if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
if (random() < 0.2) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
}
//update the reachability area and origin if possible
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(entinfo.origin, 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 = trap_AAS_Time() + BotChatTime(bs);
AIEnter_Stand(bs);
return qfalse;
}
}
//if the bot hit someone
if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount)
{
if (BotChat_HitNoKill(bs))
{
bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
AIEnter_Stand(bs);
return qfalse;
}
}
//if the enemy is not visible
if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
if (BotWantsToChase(bs)) {
AIEnter_Battle_Chase(bs);
return qfalse;
}
else {
AIEnter_Seek_LTG(bs);
return qfalse;
}
}
//use holdable items
BotBattleUseItems(bs);
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//
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)) {
AIEnter_Battle_Retreat(bs);
return qtrue;
}
}
return qtrue;
}
/*
==================
AIEnter_Battle_Chase
==================
*/
void AIEnter_Battle_Chase(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "battle chase", "");
bs->chase_time = trap_AAS_Time();
bs->ainode = AINode_Battle_Chase;
}
/*
==================
AINode_Battle_Chase
==================
*/
int AINode_Battle_Chase(bot_state_t *bs)
{
bot_goal_t goal;
vec3_t target, dir;
bot_moveresult_t moveresult;
float range;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//if the enemy is visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
AIEnter_Battle_Fight(bs);
return qfalse;
}
//if there is another enemy
if (BotFindEnemy(bs, -1)) {
AIEnter_Battle_Fight(bs);
return qfalse;
}
//there is no last enemy area
if (!bs->lastenemyareanum) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//
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;
//if there's no chase time left
if (!bs->chase_time || bs->chase_time < trap_AAS_Time() - 10) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//check for nearby goals periodicly
if (bs->check_time < trap_AAS_Time()) {
bs->check_time = trap_AAS_Time() + 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 = trap_AAS_Time() + 0.1 * range + 1;
trap_BotResetLastAvoidReach(bs->ms);
AIEnter_Battle_NBG(bs);
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)) {
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
else if (!(bs->flags & BFL_IDEALVIEWSET)) {
if (bs->chase_time > trap_AAS_Time() - 2) {
BotAimAtEnemy(bs);
}
else {
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
else {
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 the bot is in the area the enemy was last seen in
if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0;
//if the bot wants to retreat (the bot could have been damage during the chase)
if (BotWantsToRetreat(bs)) {
AIEnter_Battle_Retreat(bs);
return qtrue;
}
return qtrue;
}
/*
==================
AIEnter_Battle_Retreat
==================
*/
void AIEnter_Battle_Retreat(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "battle retreat", "");
bs->ainode = AINode_Battle_Retreat;
}
/*
==================
AINode_Battle_Retreat
==================
*/
int AINode_Battle_Retreat(bot_state_t *bs) {
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)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
if (EntityIsDead(&entinfo)) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//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
AIEnter_Battle_Chase(bs);
return qfalse;
}
//update the last time the enemy was visible
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
bs->enemyvisible_time = trap_AAS_Time();
//update the reachability area and origin if possible
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(entinfo.origin, bs->lastenemyorigin);
bs->lastenemyareanum = areanum;
}
}
//if the enemy is NOT visible for 4 seconds
if (bs->enemyvisible_time < trap_AAS_Time() - 4) {
AIEnter_Seek_LTG(bs);
return qfalse;
}
//else if the enemy is NOT visible
else if (bs->enemyvisible_time < trap_AAS_Time()) {
//if there is another enemy
if (BotFindEnemy(bs, -1)) {
AIEnter_Battle_Fight(bs);
return qfalse;
}
}
//
#ifdef CTF
if (gametype == GT_CTF) {
BotCTFRetreatGoals(bs);
}
#endif //CTF
//use holdable items
BotBattleUseItems(bs);
//get the current long term goal while retreating
if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) {
AIEnter_Battle_SuicidalFight(bs);
return qfalse;
}
//check for nearby goals periodicly
if (bs->check_time < trap_AAS_Time()) {
bs->check_time = trap_AAS_Time() + 1;
range = 150;
#ifdef CTF
//if carrying a flag the bot shouldn't be distracted too much
if (BotCTFCarryingFlag(bs)) range = 100;
#endif //CTF
//
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 = trap_AAS_Time() + range / 100 + 1;
AIEnter_Battle_NBG(bs);
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)) {
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET) )
{
attack_skill = 1;
//if the bot is skilled anough
if (attack_skill > 0.3) {
BotAimAtEnemy(bs);
}
else {
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
else {
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;
//attack the enemy if possible
BotCheckAttack(bs);
//
return qtrue;
}
/*
==================
AIEnter_Battle_NBG
==================
*/
void AIEnter_Battle_NBG(bot_state_t *bs) {
BotRecordNodeSwitch(bs, "battle NBG", "");
bs->ainode = AINode_Battle_NBG;
}
/*
==================
AINode_Battle_NBG
==================
*/
int AINode_Battle_NBG(bot_state_t *bs) {
int areanum;
bot_goal_t goal;
aas_entityinfo_t entinfo;
bot_moveresult_t moveresult;
float attack_skill;
vec3_t target, dir;
if (BotIsObserver(bs)) {
AIEnter_Observer(bs);
return qfalse;
}
//if in the intermission
if (BotIntermission(bs)) {
AIEnter_Intermission(bs);
return qfalse;
}
//respawn if dead
if (BotIsDead(bs)) {
AIEnter_Respawn(bs);
return qfalse;
}
//if no enemy
if (bs->enemy < 0) {
AIEnter_Seek_NBG(bs);
return qfalse;
}
//
BotEntityInfo(bs->enemy, &entinfo);
if (EntityIsDead(&entinfo)) {
AIEnter_Seek_NBG(bs);
return qfalse;
}
//
bs->tfl = TFL_DEFAULT;
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
//if in lava or slime the bot should be able to get out
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
//
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 = trap_AAS_Time();
//update the reachability area and origin if possible
areanum = BotPointAreaNum(entinfo.origin);
if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy(entinfo.origin, 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 (trap_BotTouchingGoal(bs->origin, &goal)) {
bs->nbg_time = 0;
}
//
if (bs->nbg_time < trap_AAS_Time()) {
//pop the current goal from the stack
trap_BotPopGoal(bs->gs);
//if the bot still has a goal
if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs);
else AIEnter_Battle_Fight(bs);
//
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)) {
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
}
else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET))
{
attack_skill = 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 {
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
VectorSubtract(target, bs->origin, dir);
vectoangles(dir, bs->ideal_viewangles);
}
else {
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;
//attack the enemy if possible
BotCheckAttack(bs);
//
return qtrue;
}