/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_dmnet.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_dmnet.c $ * *****************************************************************************/ #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" #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 // for the voice chats #include "../../ui/menudef.h" //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, FloatTime(), 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 *s) { 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); #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 < FloatTime() - 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 { //get a nearby goal outside the water while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { 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; //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; } } // 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); } */ 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)) { if (!(goal->flags & GFL_DROPPED)) { trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); } 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; } */ 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 > FloatTime() - 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) { //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); */ bs->ltg_time = FloatTime() + 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, 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); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); 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; //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 { //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); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); 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); 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) { // 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); } } } } } //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; } } //don't crouch when swimming if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; //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); 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; AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); 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); 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) { bs->defendaway_time = 0; } } //if defending a key area if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && bs->defendaway_time < FloatTime()) { //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 { 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); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); 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)) { 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); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } 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)) { //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->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; //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->decisionmaker, CHAT_TELL); // if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { bs->lastgoal_ltgtype = 0; } } 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 < FloatTime()) { 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->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); 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 < 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 if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "captureflag_start", NULL); 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 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; } bs->ltgtype = 0; } //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } //if rushing to the base 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; } //if not carrying the flag anymore if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) 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; } } BotAlternateRoute(bs, goal); return qtrue; } //returning flag if (bs->ltgtype == LTG_RETURNFLAG) { //check for bot typing status message 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; } //if touching the flag if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (bs->ltgtype == LTG_GETFLAG) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "captureflag_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); bs->teammessage_time = 0; } memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); //if touching the flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->ltgtype = 0; } //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } return qtrue; } //if rushing to the base if (bs->ltgtype == LTG_RUSHBASE) { 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 not carrying the flag anymore if (!Bot1FCTFCarryingFlag(bs)) { bs->ltgtype = 0; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } //attack the enemy base if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); 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; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 2 + 5 * random(); } return qtrue; } //returning flag if (bs->ltgtype == LTG_RETURNFLAG) { //check for bot typing status message 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; } // if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //just roam around return BotGetItemLongTermGoal(bs, tfl, goal); } } else if (gametype == GT_OBELISK) { if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if the bot no longer wants to attack the obelisk if (BotFeelingBad(bs) > 50) { return BotGetItemLongTermGoal(bs, tfl, goal); } //if touching the obelisk if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 3 + 5 * random(); } // or very close to the obelisk VectorSubtract(bs->origin, goal->origin, dir); if (VectorLengthSquared(dir) < Square(60)) { bs->attackaway_time = FloatTime() + 3 + 5 * random(); } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); //just move towards the obelisk return qtrue; } } else if (gametype == GT_HARVESTER) { //if rushing to the base if (bs->ltgtype == LTG_RUSHBASE) { switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: BotGoHarvest(bs); return qfalse; } //if not carrying any cubes if (!BotHarvesterCarryingCubes(bs)) { BotGoHarvest(bs); return qfalse; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { BotGoHarvest(bs); return qfalse; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { BotGoHarvest(bs); return qfalse; } BotAlternateRoute(bs, goal); return qtrue; } //attack the enemy base if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 2 + 5 * random(); } return qtrue; } //harvest cubes if (bs->ltgtype == LTG_HARVEST && bs->harvestaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "harvest_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); // if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } // if (trap_BotTouchingGoal(bs->origin, goal)) { bs->harvestaway_time = FloatTime() + 4 + 3 * random(); } return qtrue; } } #endif //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 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); 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); 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); 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 { //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); 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) { BotRecordNodeSwitch(bs, "intermission", "", s); //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) { //if the intermission ended if (!BotIntermission(bs)) { if (BotChat_StartLevel(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); } else { bs->stand_time = FloatTime() + 2; } AIEnter_Stand(bs, "intermission: chat"); } return qtrue; } /* ================== AIEnter_Observer ================== */ void AIEnter_Observer(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "observer", "", s); //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, "observer: left observer"); } return qtrue; } /* ================== AIEnter_Stand ================== */ void AIEnter_Stand(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "stand", "", s); bs->standfindenemy_time = FloatTime() + 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 = FloatTime() + BotChatTime(bs) + 0.1; bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; } } if (bs->standfindenemy_time < FloatTime()) { if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "stand: found enemy"); 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); AIEnter_Seek_LTG(bs, "stand: time out"); return qfalse; } // return qtrue; } /* ================== AIEnter_Respawn ================== */ void AIEnter_Respawn(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "respawn", "", s); //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 { bs->respawn_time = FloatTime() + 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 waiting for the actual respawn if (bs->respawn_wait) { if (!BotIsDead(bs)) { AIEnter_Seek_LTG(bs, "respawn: respawned"); } else { trap_EA_Respawn(bs->client); } } else if (bs->respawn_time < FloatTime()) { // 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) { // if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) return WEAPONINDEX_MACHINEGUN; else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) return WEAPONINDEX_SHOTGUN; else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) return WEAPONINDEX_PLASMAGUN; else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) return WEAPONINDEX_LIGHTNING; #ifdef MISSIONPACK else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) return WEAPONINDEX_CHAINGUN; else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) return WEAPONINDEX_NAILGUN; #endif else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) return WEAPONINDEX_RAILGUN; else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) return WEAPONINDEX_ROCKET_LAUNCHER; else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) return WEAPONINDEX_BFG; 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) { 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)) ) { // 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); // if the mine is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { // shoot at the mine trap_EA_Attack(bs->client); } } } } } } 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)) ) { 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 if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) moveresult->weapon = WEAPONINDEX_PLASMAGUN; 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; 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); // if the mine is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { // shoot at the mine trap_EA_Attack(bs->client); } } } } } } } /* ================== AIEnter_Seek_ActivateEntity ================== */ void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "activate entity", "", s); 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, ideal_viewangles; bot_moveresult_t moveresult; int targetvisible; bsp_trace_t bsptrace; aas_entityinfo_t entinfo; if (BotIsObserver(bs)) { BotClearActivateGoalStack(bs); AIEnter_Observer(bs, "active entity: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { BotClearActivateGoalStack(bs); AIEnter_Intermission(bs, "activate entity: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { BotClearActivateGoalStack(bs); AIEnter_Respawn(bs, "activate entity: bot dead"); 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; // if the bot has no activate goal if (!bs->activatestack) { BotClearActivateGoalStack(bs); AIEnter_Seek_NBG(bs, "activate entity: no goal"); return qfalse; } // goal = &bs->activatestack->goal; // initialize target being visible to false targetvisible = qfalse; // 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 if (bs->cur_ps.weapon == bs->activatestack->weapon) { 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)) { trap_EA_Attack(bs->client); } } } } // 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 if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); #endif //DEBUG 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; } AIEnter_Seek_NBG(bs, "activate entity: time out"); return qfalse; } memset(&moveresult, 0, sizeof(bot_moveresult_t)); } else { // if the bot has no goal 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 if (trap_BotTouchingGoal(bs->origin, goal)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); #endif //DEBUG 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; } AIEnter_Seek_NBG(bs, "activate entity: activated"); 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->activatestack->time = 0; } //check if the bot is blocked BotAIBlocked(bs, &moveresult, qtrue); } // 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; // 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)) { 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, "activate entity: found enemy"); } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "activate entity: found enemy"); } BotClearActivateGoalStack(bs); } return qtrue; } /* ================== AIEnter_Seek_NBG ================== */ void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { bot_goal_t goal; char buf[144]; if (trap_BotGetTopGoal(bs->gs, &goal)) { trap_BotGoalName(goal.number, buf, 144); BotRecordNodeSwitch(bs, "seek NBG", buf, s); } else { BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); } 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, "seek nbg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "seek nbg: intermision"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "seek nbg: bot dead"); 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 < 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 AIEnter_Seek_LTG(bs, "seek nbg: time out"); 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)) { 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, "seek nbg: found enemy"); } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); } } return qtrue; } /* ================== AIEnter_Seek_LTG ================== */ void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { bot_goal_t goal; char buf[144]; if (trap_BotGetTopGoal(bs->gs, &goal)) { trap_BotGoalName(goal.number, buf, 144); BotRecordNodeSwitch(bs, "seek LTG", buf, s); } else { BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); } 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, "seek ltg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "seek ltg: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "seek ltg: bot dead"); return qfalse; } // if (BotChat_Random(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "seek ltg: random chat"); 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 > 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 AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); return qfalse; } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); 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; // #ifdef CTF if (gametype == GT_CTF) { //if carrying a flag the bot shouldn't be distracted too much if (BotCTFCarryingFlag(bs)) range = 50; } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) range = 50; } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) range = 80; } #endif // 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; AIEnter_Seek_NBG(bs, "ltg seek: nbg"); 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)) { 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 (VectorLengthSquared(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, char *s) { BotRecordNodeSwitch(bs, "battle fight", "", s); trap_BotResetLastAvoidReach(bs->ms); bs->ainode = AINode_Battle_Fight; } /* ================== AIEnter_Battle_Fight ================== */ void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle fight", "", s); 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; vec3_t target; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "battle fight: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle fight: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle fight: bot dead"); 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, "battle fight: no enemy"); 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; if (bs->enemysuicide) { BotChat_EnemySuicide(bs); } if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "battle fight: enemy dead"); } else { bs->ltg_time = 0; AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); } return qfalse; } } else { 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) { AIEnter_Seek_LTG(bs, "battle fight: invisible"); return qfalse; } } // VectorCopy(entinfo.origin, target); // if not a player enemy if (bs->enemy >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //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); AIEnter_Stand(bs, "battle fight: chat health decreased"); 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); AIEnter_Stand(bs, "battle fight: chat hit someone"); 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, "battle fight: enemy out of sight"); return qfalse; } else { AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); 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, "battle fight: wants to retreat"); return qtrue; } } return qtrue; } /* ================== AIEnter_Battle_Chase ================== */ void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle chase", "", s); bs->chase_time = FloatTime(); 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, "battle chase: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle chase: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle chase: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_LTG(bs, "battle chase: no enemy"); return qfalse; } //if the enemy is visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { AIEnter_Battle_Fight(bs, "battle chase"); return qfalse; } //if there is another enemy if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "battle chase: better enemy"); return qfalse; } //there is no last enemy area if (!bs->lastenemyareanum) { AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); 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 < FloatTime() - 10) { AIEnter_Seek_LTG(bs, "battle chase: time out"); 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); AIEnter_Battle_NBG(bs, "battle chase: nbg"); 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 > FloatTime() - 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, "battle chase: wants to retreat"); return qtrue; } return qtrue; } /* ================== AIEnter_Battle_Retreat ================== */ void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle retreat", "", s); 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, "battle retreat: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle retreat: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle retreat: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); return qfalse; } // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsDead(&entinfo)) { AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); 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; //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, "battle retreat: wants to chase"); 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) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //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) { AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); return qfalse; } //else if the enemy is NOT visible else if (bs->enemyvisible_time < FloatTime()) { //if there is another enemy if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); return qfalse; } } // BotTeamGoals(bs, qtrue); //use holdable items BotBattleUseItems(bs); //get the current long term goal while retreating if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); 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 if (BotCTFCarryingFlag(bs)) range = 50; } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) range = 50; } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) range = 80; } #endif // 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; AIEnter_Battle_NBG(bs, "battle retreat: nbg"); 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 = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 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, char *s) { BotRecordNodeSwitch(bs, "battle NBG", "", s); 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, "battle nbg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle nbg: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle nbg: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); return qfalse; } // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsDead(&entinfo)) { AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); 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 = FloatTime(); VectorCopy(entinfo.origin, target); // if not a player enemy if (bs->enemy >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //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)) { 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 if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs, "battle nbg: time out"); else AIEnter_Battle_Fight(bs, "battle nbg: time out"); // 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 = 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 { 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; }