ioef/code/game/ai_dmnet.c
Thilo Schulz eb9fe030c4 Batch of bug fixes for gamecode. Patch compiled and log message written by Tobias Kuehnhammer (#5144)
################################################################################
This Patch fixes:
################################################################################

- The "fraglimit warning" was not played at all, if on the blue team.
- The "where" console command was broken.
- Obelisk explosion wasn't drawn if no Rocketlauncher was loaded.
- Impact marks sometimes didn't draw at all.
- IMPORTANT BUGFIX: No killing for cheaters with Lightning gun and Gauntlet.
- If two doors are close to each other a spectator couldn't fly through them.
- More robust, efficient and logical respawning routine.
  NOTE: The game.qvm will get notable smaller and will use LESS MEMORY!
- Drowning sounds are fixed. Now they are played as intended. (as the id
comment
  in the source code shows).
- Some AI bugs (OVERFLOW!) in the bot movement code.
- Several "Team Arena" Overload and Harvester bugs.
- Stops bots from attacking a team mate (player) who only changed teams.
- Some voice chats and CTF commands fixed.
- "Team_ReturnFlag" was called twice, which did wired things sometimes. 
  NOTE: (G_RunItem checks CONTENTS_NODROP already!)
- A bugfix for Gauntlet animation.
- Incorrect CTF scoring.
- A bunch of corrected comments and print lines ("\n").
- Some regularity of expression and some small trivial bugs.

################################################################################
Details:
################################################################################

********************************************************************************
BUG: in gamemode GT_TEAM the fraglimit warning will not be played if joining
the
     blue team!
--------------------------------------------------------------------------------
Solution: In "CG_CheckLocalSounds": if cgs.scores2 > highScore, highScore
should
          be cgs.scores2.
********************************************************************************
BUG: the "where" console command doesn't work as expected (it's always 0 0 0) 
     but not in id Quake 3 Arena. It seems that now Ioquake3 is affected!
--------------------------------------------------------------------------------
Solution: In Function "Cmd_Where_f" ent->s.origin should be 
          ent->r.currentOrigin.
********************************************************************************
BUG: in gamemode GT_OBELISK obelisk explosion won't be drawn if there is no 
     Rocketlauncher loaded. (The "maps without Rocketlauncher" bug)
--------------------------------------------------------------------------------
Solution: in "cg_main.c": cgs.media.rocketExplosionShader should be registered 
          if gamemode is GT_OBELISK.
********************************************************************************
BUG: Impact marks sometimes doesn't draw at all. Not easy to reproduce if you 
     don't play (io)Quake3 every day and know the places where it happens! ;) 
     But anyway...
     Test: start q3dm12 go to "Long Jump Canyon" (where the small platform 
     teleporter for the BFG is) place yourself at the point where the railgun 
     spawns, look in the direction where the red suspended armor is. Now shoot
     at the sloped wall on the out/leftside of the door you see. (the sloped 
     wall should be nearly in the center of your screen now). If you choose the 
     correct brush face and shoot up and down at this brush face, the impact 
     marks sometimes aren't visible.

     There are hundreds of custom maps where this can happen!
--------------------------------------------------------------------------------
Solution: I replaced the function "SnapVectorTowards" with the one from 
         "Wolfenstein - Enemy Territory (GPL Source Code)"
********************************************************************************
BUG: Normally "NOCLIP" cheaters are logically not allowed to fire a gun.
     Unfortunatly the Gauntlet (and Lightning gun) was forgotten and not 
     restricted to that. All weapons except those two were handled correct. 
--------------------------------------------------------------------------------
Solution: Make Gauntlet and Lightning gun not firing for someone who cheats 
          with "NOCLIP" (like all other weapons).
********************************************************************************
NOTE: A few bugfixes are not mine and are reported here: 
      http://www.quake3world.com/forum/viewtopic.php?f=16&t=9179.

      Thanks to Quake3world, for all those years and the good guys there!
********************************************************************************
BUG: During making a mod I found a very strange bug, which mainly occurs if
     someone tries to implement a lot of singleplayer monsters which should
walk
     slowly (like the "Crash" bot). So if someone wants to make slow down bots
     or monsters when they are walking towards a goal and alter the function
     "BotMoveInGoalArea" then the bots/monsters do stupid things. Otherwise and
     this is the default (also buggy) behavior they start running although they
     shouldn't (as seen with the "Crash" bot and will not be fixed here).
--------------------------------------------------------------------------------
Solution: Fix overflow in bot user command. BUGFIX from "Hunt" mod by J. 
          Hoffman.
********************************************************************************
BUG: in function "BotMoveToGoal" the special elevator case doesn't make sense.
--------------------------------------------------------------------------------
Solution: in "be_ai_move.c": ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) ||
                              (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) 
                   should be ((result->flags & MOVERESULT_ONTOPOF_ELEVATOR) ||
                              (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)).
********************************************************************************
BUG: in function "BotWantsToRetreat" and "BotWantsToChase" this is wrong:
     "(bs->enemy != redobelisk.entitynum || bs->enemy !=
blueobelisk.entitynum)"
--------------------------------------------------------------------------------
Solution: "... redobelisk.entitynum) && (bs->enemy != blueobelisk.." is
correct.
********************************************************************************
BUG: in gamemode GT_OBELISK there are too many node switches for bots 
     (test: mpq3tourney6 with many bots). If that happens, game becomes 
     unplayable. I don't know if this is the best solution but here it is:
--------------------------------------------------------------------------------
Solution: In function "AINode_Battle_Fight" right after:
if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy))
{
   I added this: 
#ifdef MISSIONPACK
    if (bs->enemy == redobelisk.entitynum || bs->enemy == 
                                                        blueobelisk.entitynum)
{
    AIEnter_Battle_Chase(bs, "battle fight: obelisk out of sight");
    return qfalse;
    }
#endif
********************************************************************************
BUG: in gamemode >= GT_TEAM, after team change, bots will (sometimes) not stop
     shooting at you, although you are on their team now. It seems that the 
     configstrings are f***** up or not reliable in this case!
--------------------------------------------------------------------------------
Solution: In function "BotTeam" and "BotSameTeam" get the real team values.
********************************************************************************
BUG: Some of the bots voice commands are wrong. They are commanded to attack
the
     enemy base but they say "Okay, I will defend!"
--------------------------------------------------------------------------------
Solution: Corrected some voice commands in "BotCTFOrders_FlagNotAtBase" and
         "Bot1FCTFOrders_EnemyDroppedFlag"
********************************************************************************
BUG: Spectators couldn't fly through doors if they are very close to each
other.
     You can test it with some regular id maps (q3dm14, q3dm12) but there are
     also many custom maps where this can happen! This is annoying because in 
     the worst case you can't move at all and are caught inside a door.
--------------------------------------------------------------------------------
Solution: There is a solution in a mod called "Hunt" by J. Hoffman. 
          Bugfix is included in this patch!
********************************************************************************
BUG: During making a mod I found it very hard to implement some of my ideas
     (something like "Limbo" or "Meeting") because of the way the player spawn
     effect, intermission and spawning on victory pads is handled. I reworked
it
     a bit and simplified it so that the effect is handled when a client      
     respawns
     (as the name says) and not when a client begins. I think this will help 
     more
     mod makers everytime they want to make changes to spawning of players,
bots
     on victory pads or monsters... and want to avoid spectators with 
     Machineguns
     which can kill and score... :()

     NOTE: I also renamed the poorly named function "respawn" 
           to "ClientRespawn". If someone searches the code base for "respawn" 
           it was really hard to find the correct place for what was 
           meant. "respawn" is used so often, that you really get headache ... 
           now with "ClientRespawn" it's easier!

     IMPORTANT: The whole respawning, moving to intermission point and 
                everything related to that is now done in a more reliable way 
                without changing the default behavior. (How critical the whole 
                spwaning mess was did you see by yourself (ioquake3 rev. 2076). 
                With this patch it's safer. 
                Trust me, I spent hours of fixing silly problems...
-------------------------------------------------------------------------------- 
Solution: Simplified "ClientBegin" and moved the teleport event  
          to "ClientSpawn".
********************************************************************************
BUG: If a player is dying or hurted under water the hurt/dying sounds AND the
     drowning sounds are played together. This is silly. Moreover it's no good
     idea to let the server play client sounds! There was a solution in a mod 
     called "Q3A++" by Dan 'Neurobasher' Gomes which fixes the problem.
--------------------------------------------------------------------------------
Solution: Created a "CG_WaterLevel" function to play the appropriate sounds.
********************************************************************************

################################################################################
2011-08-01 11:39:33 +00:00

2623 lines
78 KiB
C

/*
===========================================================================
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 Quake III Arena source code; 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/botlib.h"
#include "../botlib/be_aas.h"
#include "../botlib/be_ea.h"
#include "../botlib/be_ai_char.h"
#include "../botlib/be_ai_chat.h"
#include "../botlib/be_ai_gen.h"
#include "../botlib/be_ai_goal.h"
#include "../botlib/be_ai_move.h"
#include "../botlib/be_ai_weap.h"
//
#include "ai_main.h"
#include "ai_dmq3.h"
#include "ai_chat.h"
#include "ai_cmd.h"
#include "ai_dmnet.h"
#include "ai_team.h"
//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 ../botlib/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, "%s", 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, "%s", 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 a flag or cubes
if (BotCTFCarryingFlag(bs)
#ifdef MISSIONPACK
|| Bot1FCTFCarryingFlag(bs) || BotHarvesterCarryingCubes(bs)
#endif
) {
//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;
}
//
//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;
else if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && bs->inventory[INVENTORY_MINES] > 0)
return WEAPONINDEX_PROXLAUNCHER;
#endif
else if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && bs->inventory[INVENTORY_GRENADES] > 0)
return WEAPONINDEX_GRENADE_LAUNCHER;
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 its 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 its 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 its 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;
bs->flags &= ~BFL_FIGHTSUICIDAL;
}
/*
==================
AIEnter_Battle_SuicidalFight
==================
*/
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)) {
#ifdef MISSIONPACK
if (bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum) {
AIEnter_Battle_Chase(bs, "battle fight: obelisk out of sight");
return qfalse;
}
#endif
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;
}