home *** CD-ROM | disk | FTP | other *** search
- // Copyright (C) 1999-2000 Id Software, Inc.
- //
- /*****************************************************************************
- * name: ai_dmq3.c
- *
- * desc: Quake3 bot AI
- *
- * $Archive: /source/code/game/ai_dmq3.c $
- * $Author: Raduffy $
- * $Revision: 7 $
- * $Modtime: 1/14/00 5:28p $
- * $Date: 1/14/00 5:35p $
- *
- *****************************************************************************/
-
-
- #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"
- //
- #include "chars.h" //characteristics
- #include "inv.h" //indexes into the inventory
- #include "syn.h" //synonyms
- #include "match.h" //string matching types and vars
-
- #define IDEAL_ATTACKDIST 140
- #define WEAPONINDEX_MACHINEGUN 2
-
- #define MAX_WAYPOINTS 128
- //
- bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
- bot_waypoint_t *botai_freewaypoints;
-
- //NOTE: not using a cvars which can be updated because the game should be reloaded anyway
- int gametype; //game type
- int maxclients; //maximum number of clients
-
- vmCvar_t bot_grapple;
- vmCvar_t bot_rocketjump;
- vmCvar_t bot_fastchat;
- vmCvar_t bot_nochat;
- vmCvar_t bot_testrchat;
- vmCvar_t bot_challenge;
-
- vec3_t lastteleport_origin; //last teleport event origin
- float lastteleport_time; //last teleport event time
- int max_bspmodelindex; //maximum BSP model index
-
- //CTF flag goals
- bot_goal_t ctf_redflag;
- bot_goal_t ctf_blueflag;
-
- #ifdef CTF
- /*
- ==================
- BotCTFCarryingFlag
- ==================
- */
- int BotCTFCarryingFlag(bot_state_t *bs) {
- if (gametype != GT_CTF) return CTF_FLAG_NONE;
-
- if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
- else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
- return CTF_FLAG_NONE;
- }
-
- /*
- ==================
- BotCTFTeam
- ==================
- */
- int BotCTFTeam(bot_state_t *bs) {
- char info[1024];
-
- if (gametype != GT_CTF) return CTF_TEAM_NONE;
- if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
- //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
- return qfalse;
- }
- trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
- //
- if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED;
- else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE;
- return CTF_TEAM_NONE;
- }
-
- /*
- ==================
- BotCTFRetreatGoals
- ==================
- */
- void BotCTFRetreatGoals(bot_state_t *bs) {
- //when carrying a flag in ctf the bot should rush to the base
- if (BotCTFCarryingFlag(bs)) {
- //if not already rushing to the base
- if (bs->ltgtype != LTG_RUSHBASE) {
- bs->ltgtype = LTG_RUSHBASE;
- bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
- bs->rushbaseaway_time = 0;
- }
- }
- }
-
- /*
- ==================
- EntityIsDead
- ==================
- */
- qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
- playerState_t ps;
-
- if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
- //retrieve the current client state
- BotAI_GetClientState( entinfo->number, &ps );
- if (ps.pm_type != PM_NORMAL) return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- EntityIsInvisible
- ==================
- */
- qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
- if (entinfo->powerups & (1 << PW_INVIS)) {
- return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- EntityCarriesFlag
- ==================
- */
- qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
- if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue;
- if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) return qtrue;
- return qfalse;
- }
-
- /*
- ==================
- EntityIsShooting
- ==================
- */
- qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
- if (entinfo->flags & EF_FIRING) {
- return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- EntityIsChatting
- ==================
- */
- qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
- if (entinfo->flags & EF_TALK) {
- return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- EntityHasQuad
- ==================
- */
- qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
- if (entinfo->powerups & (1 << PW_QUAD)) {
- return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- BotCTFSeekGoals
- ==================
- */
- void BotCTFSeekGoals(bot_state_t *bs) {
- float rnd;
- int flagstatus, c;
-
- //when carrying a flag in ctf the bot should rush to the base
- if (BotCTFCarryingFlag(bs)) {
- //if not already rushing to the base
- if (bs->ltgtype != LTG_RUSHBASE) {
- bs->ltgtype = LTG_RUSHBASE;
- bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
- bs->rushbaseaway_time = 0;
- }
- else if (bs->rushbaseaway_time > trap_AAS_Time()) {
- if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus;
- else flagstatus = bs->blueflagstatus;
- //if the flag is back
- if (flagstatus == 0) {
- bs->rushbaseaway_time = 0;
- }
- }
- return;
- }
- //
- if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
- else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
- //if the enemy flag is not at it's base
- if (flagstatus == 1) {
- //if Not defending the base
- if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
- (bs->teamgoal.number == ctf_redflag.number ||
- bs->teamgoal.number == ctf_blueflag.number))) {
- //if not already accompanying someone
- if (bs->ltgtype != LTG_TEAMACCOMPANY) {
- //if there is avisible team mate flag carrier
- c = BotTeamFlagCarrierVisible(bs);
- if (c >= 0) {
- //follow the flag carrier
- //the team mate
- bs->teammate = c;
- //last time the team mate was visible
- bs->teammatevisible_time = trap_AAS_Time();
- //set the time to send a message to the team mates
- bs->teammessage_time = trap_AAS_Time() + 2 * random();
- //get the team goal time
- bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME;
- bs->ltgtype = LTG_TEAMACCOMPANY;
- bs->formation_dist = 3.5 * 32; //3.5 meter
- bs->arrive_time = 0;
- return;
- }
- }
- }
- }
- //if the base flag is stolen
- else if (flagstatus == 2) {
- //if not already going for the enemy flag
- if (bs->ltgtype != LTG_GETFLAG) {
- //if there's no bot team leader
- if (!BotTeamLeader(bs)) {
- //go for the enemy flag
- bs->ltgtype = LTG_GETFLAG;
- //no team message
- bs->teammessage_time = 1;
- //set the time the bot will stop getting the flag
- bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
- return;
- }
- }
- }
- //if both flags not at their bases
- else if (flagstatus == 3) {
- //
- if (bs->ltgtype != LTG_GETFLAG &&
- bs->ltgtype != LTG_TEAMACCOMPANY) {
- //if there is avisible team mate flag carrier
- c = BotTeamFlagCarrierVisible(bs);
- if (c >= 0) {
- //follow the flag carrier
- return;
- }
- else {
- //otherwise attack the enemy base
- }
- return;
- }
- }
- //if the bot is roaming
- if (bs->ctfroam_time > trap_AAS_Time()) return;
- //if already a CTF or team goal
- if (bs->ltgtype == LTG_TEAMHELP ||
- bs->ltgtype == LTG_TEAMACCOMPANY ||
- bs->ltgtype == LTG_DEFENDKEYAREA ||
- bs->ltgtype == LTG_GETFLAG ||
- bs->ltgtype == LTG_RUSHBASE ||
- bs->ltgtype == LTG_RETURNFLAG ||
- bs->ltgtype == LTG_CAMPORDER ||
- bs->ltgtype == LTG_PATROL) {
- return;
- }
- //if the bot has anough aggression to decide what to do
- if (BotAggression(bs) < 50) return;
- //set the time to send a message to the team mates
- bs->teammessage_time = trap_AAS_Time() + 2 * random();
- //get the flag or defend the base
- rnd = random();
- if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) {
- bs->ltgtype = LTG_GETFLAG;
- //set the time the bot will stop getting the flag
- bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
- }
- else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) {
- //
- if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
- else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
- //set the ltg type
- bs->ltgtype = LTG_DEFENDKEYAREA;
- //set the time the bot stops defending the base
- bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
- bs->defendaway_time = 0;
- }
- else {
- bs->ltgtype = 0;
- //set the time the bot will stop roaming
- bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME;
- }
- #ifdef DEBUG
- BotPrintTeamGoal(bs);
- #endif //DEBUG
- }
-
- #endif //CTF
-
- /*
- ==================
- BotPointAreaNum
- ==================
- */
- int BotPointAreaNum(vec3_t origin) {
- int areanum, numareas, areas[10];
- vec3_t end;
-
- areanum = trap_AAS_PointAreaNum(origin);
- if (areanum) return areanum;
- VectorCopy(origin, end);
- end[2] += 10;
- numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
- if (numareas > 0) return areas[0];
- return 0;
- }
-
- /*
- ==================
- ClientName
- ==================
- */
- char *ClientName(int client, char *name, int size) {
- char buf[MAX_INFO_STRING];
-
- if (client < 0 || client >= MAX_CLIENTS) {
- BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
- return "[client out of range]";
- }
- trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
- strncpy(name, Info_ValueForKey(buf, "n"), size-1);
- name[size-1] = '\0';
- Q_CleanStr( name );
- return name;
- }
-
- /*
- ==================
- ClientSkin
- ==================
- */
- char *ClientSkin(int client, char *skin, int size) {
- char buf[MAX_INFO_STRING];
-
- if (client < 0 || client >= MAX_CLIENTS) {
- BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
- return "[client out of range]";
- }
- trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
- strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
- skin[size-1] = '\0';
- return skin;
- }
-
- /*
- ==================
- ClientFromName
- ==================
- */
- int ClientFromName(char *name) {
- int i;
- char buf[MAX_INFO_STRING];
- static int maxclients;
-
- if (!maxclients)
- maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
- for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
- trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
- Q_CleanStr( buf );
- if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
- }
- return -1;
- }
-
- /*
- ==================
- stristr
- ==================
- */
- char *stristr(char *str, char *charset) {
- int i;
-
- while(*str) {
- for (i = 0; charset[i] && str[i]; i++) {
- if (toupper(charset[i]) != toupper(str[i])) break;
- }
- if (!charset[i]) return str;
- str++;
- }
- return NULL;
- }
-
- /*
- ==================
- EasyClientName
- ==================
- */
- char *EasyClientName(int client, char *buf, int size) {
- int i;
- char *str1, *str2, *ptr, c;
- char name[128];
-
- strcpy(name, ClientName(client, name, sizeof(name)));
- for (i = 0; name[i]; i++) name[i] &= 127;
- //remove all spaces
- for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
- memmove(ptr, ptr+1, strlen(ptr+1)+1);
- }
- //check for [x] and ]x[ clan names
- str1 = strstr(name, "[");
- str2 = strstr(name, "]");
- if (str1 && str2) {
- if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
- else memmove(str2, str1+1, strlen(str1+1)+1);
- }
- //remove Mr prefix
- if ((name[0] == 'm' || name[0] == 'M') &&
- (name[1] == 'r' || name[1] == 'R')) {
- memmove(name, name+2, strlen(name+2)+1);
- }
- //only allow lower case alphabet characters
- ptr = name;
- while(*ptr) {
- c = *ptr;
- if ((c >= 'a' && c <= 'z') ||
- (c >= '0' && c <= '9') || c == '_') {
- ptr++;
- }
- else if (c >= 'A' && c <= 'Z') {
- *ptr += 'a' - 'A';
- ptr++;
- }
- else {
- memmove(ptr, ptr+1, strlen(ptr + 1)+1);
- }
- }
- strncpy(buf, name, size-1);
- buf[size-1] = '\0';
- return buf;
- }
-
- /*
- ==================
- BotChooseWeapon
- ==================
- */
- void BotChooseWeapon(bot_state_t *bs) {
- int newweaponnum;
-
- if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
- bs->cur_ps.weaponstate == WEAPON_DROPPING) {
- trap_EA_SelectWeapon(bs->client, bs->weaponnum);
- }
- else {
- newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory);
- if (bs->weaponnum != newweaponnum) bs->weaponchange_time = trap_AAS_Time();
- bs->weaponnum = newweaponnum;
- //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
- trap_EA_SelectWeapon(bs->client, bs->weaponnum);
- }
- }
-
- /*
- ==================
- BotSetupForMovement
- ==================
- */
- void BotSetupForMovement(bot_state_t *bs) {
- bot_initmove_t initmove;
-
- memset(&initmove, 0, sizeof(bot_initmove_t));
- VectorCopy(bs->cur_ps.origin, initmove.origin);
- VectorCopy(bs->cur_ps.velocity, initmove.velocity);
- VectorCopy(bs->cur_ps.origin, initmove.viewoffset);
- initmove.viewoffset[2] += bs->cur_ps.viewheight;
- initmove.entitynum = bs->entitynum;
- initmove.client = bs->client;
- initmove.thinktime = bs->thinktime;
- //set the onground flag
- if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
- //set the teleported flag
- if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
- initmove.or_moveflags |= MFL_TELEPORTED;
- }
- //set the waterjump flag
- if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
- initmove.or_moveflags |= MFL_WATERJUMP;
- }
- //set presence type
- if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
- else initmove.presencetype = PRESENCE_NORMAL;
- //
- if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
- //
- VectorCopy(bs->viewangles, initmove.viewangles);
- //
- trap_BotInitMoveState(bs->ms, &initmove);
- }
-
- /*
- ==================
- BotUpdateInventory
- ==================
- */
- void BotUpdateInventory(bot_state_t *bs) {
- //armor
- bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
- //weapons
- bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0;
- bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0;
- bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0;
- bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
- bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0;
- bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0;
- bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0;
- bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0;
- bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0;
- bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0;
- //ammo
- bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN];
- bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN];
- bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
- bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN];
- bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING];
- bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER];
- bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN];
- bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG];
- //powerups
- bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
- bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
- bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
- bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
- bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0;
- bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
- bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
- bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
- bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
- bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
- bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
- //
- }
-
- /*
- ==================
- BotUpdateBattleInventory
- ==================
- */
- void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
- vec3_t dir;
- aas_entityinfo_t entinfo;
-
- BotEntityInfo(enemy, &entinfo);
- VectorSubtract(entinfo.origin, bs->origin, dir);
- bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
- dir[2] = 0;
- bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
- //FIXME: add num visible enemies and num visible team mates to the inventory
- }
-
- /*
- ==================
- BotBattleUseItems
- ==================
- */
- void BotBattleUseItems(bot_state_t *bs) {
- if (bs->inventory[INVENTORY_HEALTH] < 40) {
- if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
- if (!BotCTFCarryingFlag(bs)) {
- trap_EA_Use(bs->client);
- }
- }
- if (bs->inventory[INVENTORY_MEDKIT] > 0) {
- trap_EA_Use(bs->client);
- }
- }
- }
-
- /*
- ==================
- BotSetTeleportTime
- ==================
- */
- void BotSetTeleportTime(bot_state_t *bs) {
- if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
- bs->teleport_time = trap_AAS_Time();
- }
- bs->last_eFlags = bs->cur_ps.eFlags;
- }
-
- /*
- ==================
- BotIsDead
- ==================
- */
- qboolean BotIsDead(bot_state_t *bs) {
- return (bs->cur_ps.pm_type == PM_DEAD);
- }
-
- /*
- ==================
- BotIsObserver
- ==================
- */
- qboolean BotIsObserver(bot_state_t *bs) {
- char buf[MAX_INFO_STRING];
- if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
- trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
- if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
- return qfalse;
- }
-
- /*
- ==================
- BotIntermission
- ==================
- */
- qboolean BotIntermission(bot_state_t *bs) {
- //NOTE: we shouldn't be looking at the game code...
- if (level.intermissiontime) return qtrue;
- return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
- }
-
- /*
- ==================
- BotInLavaOrSlime
- ==================
- */
- qboolean BotInLavaOrSlime(bot_state_t *bs) {
- vec3_t feet;
-
- VectorCopy(bs->origin, feet);
- feet[2] -= 23;
- return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
- }
-
- /*
- ==================
- BotCreateWayPoint
- ==================
- */
- bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
- bot_waypoint_t *wp;
- vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
-
- wp = botai_freewaypoints;
- if ( !wp ) {
- BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
- return NULL;
- }
- botai_freewaypoints = botai_freewaypoints->next;
-
- Q_strncpyz( wp->name, name, sizeof(wp->name) );
- VectorCopy(origin, wp->goal.origin);
- VectorCopy(waypointmins, wp->goal.mins);
- VectorCopy(waypointmaxs, wp->goal.maxs);
- wp->goal.areanum = areanum;
- wp->next = NULL;
- wp->prev = NULL;
- return wp;
- }
-
- /*
- ==================
- BotFindWayPoint
- ==================
- */
- bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
- bot_waypoint_t *wp;
-
- for (wp = waypoints; wp; wp = wp->next) {
- if (!Q_stricmp(wp->name, name)) return wp;
- }
- return NULL;
- }
-
- /*
- ==================
- BotFreeWaypoints
- ==================
- */
- void BotFreeWaypoints(bot_waypoint_t *wp) {
- bot_waypoint_t *nextwp;
-
- for (; wp; wp = nextwp) {
- nextwp = wp->next;
- wp->next = botai_freewaypoints;
- botai_freewaypoints = wp;
- }
- }
-
- /*
- ==================
- BotInitWaypoints
- ==================
- */
- void BotInitWaypoints(void) {
- int i;
-
- botai_freewaypoints = NULL;
- for (i = 0; i < MAX_WAYPOINTS; i++) {
- botai_waypoints[i].next = botai_freewaypoints;
- botai_freewaypoints = &botai_waypoints[i];
- }
- }
-
- /*
- ==================
- TeamPlayIsOn
- ==================
- */
- int TeamPlayIsOn(void) {
- return ( gametype == GT_TEAM || gametype == GT_CTF );
- }
-
- /*
- ==================
- BotAggression
- ==================
- */
- float BotAggression(bot_state_t *bs) {
- //if the bot has quad
- if (bs->inventory[INVENTORY_QUAD]) {
- //if the bot is not holding the gauntlet or the enemy is really nearby
- if (bs->weaponnum != WP_GAUNTLET ||
- bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
- return 70;
- }
- }
- //if the enemy is located way higher than the bot
- if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
- //if the bot is very low on health
- if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
- //if the bot is low on health
- if (bs->inventory[INVENTORY_HEALTH] < 80) {
- //if the bot has insufficient armor
- if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
- }
- //if the bot can use the bfg
- if (bs->inventory[INVENTORY_BFG10K] > 0 &&
- bs->inventory[INVENTORY_BFGAMMO] > 7) return 100;
- //if the bot can use the railgun
- if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
- bs->inventory[INVENTORY_SLUGS] > 5) return 95;
- //if the bot can use the lightning gun
- if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
- bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90;
- //if the bot can use the rocketlauncher
- if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
- bs->inventory[INVENTORY_ROCKETS] > 5) return 90;
- //if the bot can use the plasmagun
- if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
- bs->inventory[INVENTORY_CELLS] > 40) return 85;
- //if the bot can use the grenade launcher
- if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
- bs->inventory[INVENTORY_GRENADES] > 10) return 80;
- //if the bot can use the shotgun
- if (bs->inventory[INVENTORY_SHOTGUN] > 0 &&
- bs->inventory[INVENTORY_SHELLS] > 10) return 50;
- //otherwise the bot is not feeling too good
- return 0;
- }
-
- /*
- ==================
- BotWantsToRetreat
- ==================
- */
- int BotWantsToRetreat(bot_state_t *bs) {
- aas_entityinfo_t entinfo;
-
- //always retreat when carrying a CTF flag
- if (BotCTFCarryingFlag(bs)) return qtrue;
- //
- if (bs->enemy >= 0) {
- //if the enemy is carrying a flag
- BotEntityInfo(bs->enemy, &entinfo);
- if (EntityCarriesFlag(&entinfo)) return qfalse;
- }
- //if the bot is getting the flag
- if (bs->ltgtype == LTG_GETFLAG) return qtrue;
- //
- if (BotAggression(bs) < 50) return qtrue;
- return qfalse;
- }
-
- /*
- ==================
- BotWantsToChase
- ==================
- */
- int BotWantsToChase(bot_state_t *bs) {
- aas_entityinfo_t entinfo;
-
- //always retreat when carrying a CTF flag
- if (BotCTFCarryingFlag(bs)) return qfalse;
- //if the enemy is carrying a flag
- BotEntityInfo(bs->enemy, &entinfo);
- if (EntityCarriesFlag(&entinfo)) return qtrue;
- //if the bot is getting the flag
- if (bs->ltgtype == LTG_GETFLAG) return qfalse;
- //
- if (BotAggression(bs) > 50) return qtrue;
- return qfalse;
- }
-
- /*
- ==================
- BotWantsToHelp
- ==================
- */
- int BotWantsToHelp(bot_state_t *bs) {
- return qtrue;
- }
-
- /*
- ==================
- BotCanAndWantsToRocketJump
- ==================
- */
- int BotCanAndWantsToRocketJump(bot_state_t *bs) {
- float rocketjumper;
-
- //if rocket jumping is disabled
- if (!bot_rocketjump.integer) return qfalse;
- //if no rocket launcher
- if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse;
- //if low on rockets
- if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse;
- //never rocket jump with the Quad
- if (bs->inventory[INVENTORY_QUAD]) return qfalse;
- //if low on health
- if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
- //if not full health
- if (bs->inventory[INVENTORY_HEALTH] < 90) {
- //if the bot has insufficient armor
- if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
- }
- rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1);
- if (rocketjumper < 0.5) return qfalse;
- return qtrue;
- }
-
- /*
- ==================
- BotGoCamp
- ==================
- */
- void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
- float camper;
-
- //set message time to zero so bot will NOT show any message
- bs->teammessage_time = 0;
- //set the ltg type
- bs->ltgtype = LTG_CAMP;
- //set the team goal
- memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
- //get the team goal time
- camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
- if (camper > 0.99) bs->teamgoal_time = 99999;
- else bs->teamgoal_time = 120 + 180 * camper + random() * 15;
- //set the last time the bot started camping
- bs->camp_time = trap_AAS_Time();
- //the teammate that requested the camping
- bs->teammate = 0;
- //do NOT type arrive message
- bs->arrive_time = 1;
- }
-
- /*
- ==================
- BotWantsToCamp
- ==================
- */
- int BotWantsToCamp(bot_state_t *bs) {
- float camper;
- int cs, traveltime, besttraveltime;
- bot_goal_t goal, bestgoal;
-
- camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
- if (camper < 0.1) return qfalse;
- //if the bot has a team goal
- if (bs->ltgtype == LTG_TEAMHELP ||
- bs->ltgtype == LTG_TEAMACCOMPANY ||
- bs->ltgtype == LTG_DEFENDKEYAREA ||
- bs->ltgtype == LTG_GETFLAG ||
- bs->ltgtype == LTG_RUSHBASE ||
- bs->ltgtype == LTG_CAMP ||
- bs->ltgtype == LTG_CAMPORDER ||
- bs->ltgtype == LTG_PATROL) {
- return qfalse;
- }
- //if camped recently
- if (bs->camp_time > trap_AAS_Time() - 60 + 300 * (1-camper)) return qfalse;
- //
- if (random() > camper) {
- bs->camp_time = trap_AAS_Time();
- return qfalse;
- }
- //if the bot isn't healthy anough
- if (BotAggression(bs) < 50) return qfalse;
- //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo
- if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) &&
- (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) &&
- (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) {
- return qfalse;
- }
- //find the closest camp spot
- besttraveltime = 99999;
- for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) {
- traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT);
- if (traveltime && traveltime < besttraveltime) {
- besttraveltime = traveltime;
- memcpy(&bestgoal, &goal, sizeof(bot_goal_t));
- }
- }
- if (besttraveltime > 150) return qfalse;
- //ok found a camp spot, go camp there
- BotGoCamp(bs, &bestgoal);
- //
- return qtrue;
- }
-
- /*
- ==================
- BotDontAvoid
- ==================
- */
- void BotDontAvoid(bot_state_t *bs, char *itemname) {
- bot_goal_t goal;
- int num;
-
- num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
- while(num >= 0) {
- trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
- num = trap_BotGetLevelItemGoal(num, itemname, &goal);
- }
- }
-
- /*
- ==================
- BotGoForPowerups
- ==================
- */
- void BotGoForPowerups(bot_state_t *bs) {
-
- //don't avoid any of the powerups anymore
- BotDontAvoid(bs, "Quad Damage");
- BotDontAvoid(bs, "Regeneration");
- BotDontAvoid(bs, "Battle Suit");
- BotDontAvoid(bs, "Speed");
- BotDontAvoid(bs, "Invisibility");
- //BotDontAvoid(bs, "Flight");
- //reset the long term goal time so the bot will go for the powerup
- //NOTE: the long term goal type doesn't change
- bs->ltg_time = 0;
- }
-
- /*
- ==================
- BotRoamGoal
- ==================
- */
- void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
- int pc, i;
- float len, rnd;
- vec3_t dir, bestorg, belowbestorg;
- bsp_trace_t trace;
-
- for (i = 0; i < 10; i++) {
- //start at the bot origin
- VectorCopy(bs->origin, bestorg);
- rnd = random();
- if (rnd > 0.25) {
- //add a random value to the x-coordinate
- if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
- else bestorg[0] += 800 * random() + 100;
- }
- if (rnd < 0.75) {
- //add a random value to the y-coordinate
- if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
- else bestorg[1] += 800 * random() + 100;
- }
- //add a random value to the z-coordinate (NOTE: 48 = maxjump?)
- bestorg[2] += 2 * 48 * crandom();
- //trace a line from the origin to the roam target
- BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
- //direction and length towards the roam target
- VectorSubtract(trace.endpos, bs->origin, dir);
- len = VectorNormalize(dir);
- //if the roam target is far away anough
- if (len > 200) {
- //the roam target is in the given direction before walls
- VectorScale(dir, len * trace.fraction - 40, dir);
- VectorAdd(bs->origin, dir, bestorg);
- //get the coordinates of the floor below the roam target
- belowbestorg[0] = bestorg[0];
- belowbestorg[1] = bestorg[1];
- belowbestorg[2] = bestorg[2] - 800;
- BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
- //
- if (!trace.startsolid) {
- trace.endpos[2]++;
- pc = trap_PointContents(trace.endpos, bs->entitynum);
- if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
- VectorCopy(bestorg, goal);
- return;
- }
- }
- }
- }
- VectorCopy(bestorg, goal);
- }
-
- /*
- ==================
- BotAttackMove
- ==================
- */
- bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
- int movetype, i;
- float attack_skill, jumper, croucher, dist, strafechange_time;
- float attack_dist, attack_range;
- vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
- aas_entityinfo_t entinfo;
- bot_moveresult_t moveresult;
- bot_goal_t goal;
-
- if (bs->attackchase_time > trap_AAS_Time()) {
- //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);
- //initialize the movement state
- BotSetupForMovement(bs);
- //move towards the goal
- trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
- return moveresult;
- }
- //
- memset(&moveresult, 0, sizeof(bot_moveresult_t));
- //
- attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
- jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1);
- croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
- //if the bot is really stupid
- if (attack_skill < 0.2) return moveresult;
- //initialize the movement state
- BotSetupForMovement(bs);
- //get the enemy entity info
- BotEntityInfo(bs->enemy, &entinfo);
- //direction towards the enemy
- VectorSubtract(entinfo.origin, bs->origin, forward);
- //the distance towards the enemy
- dist = VectorNormalize(forward);
- VectorNegate(forward, backward);
- //walk, crouch or jump
- movetype = MOVE_WALK;
- //
- if (bs->attackcrouch_time < trap_AAS_Time() - 1) {
- if (random() < jumper) {
- movetype = MOVE_JUMP;
- }
- //wait at least one second before crouching again
- else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) {
- bs->attackcrouch_time = trap_AAS_Time() + croucher * 5;
- }
- }
- if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH;
- //if the bot should jump
- if (movetype == MOVE_JUMP) {
- //if jumped last frame
- if (bs->attackjump_time > trap_AAS_Time()) {
- movetype = MOVE_WALK;
- }
- else {
- bs->attackjump_time = trap_AAS_Time() + 1;
- }
- }
- if (bs->cur_ps.weapon == WP_GAUNTLET) {
- attack_dist = 0;
- attack_range = 0;
- }
- else {
- attack_dist = IDEAL_ATTACKDIST;
- attack_range = 40;
- }
- //if the bot is stupid
- if (attack_skill <= 0.4) {
- //just walk to or away from the enemy
- if (dist > attack_dist + attack_range) {
- if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult;
- }
- if (dist < attack_dist - attack_range) {
- if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult;
- }
- return moveresult;
- }
- //increase the strafe time
- bs->attackstrafe_time += bs->thinktime;
- //get the strafe change time
- strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
- if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
- //if the strafe direction should be changed
- if (bs->attackstrafe_time > strafechange_time) {
- //some magic number :)
- if (random() > 0.935) {
- //flip the strafe direction
- bs->flags ^= BFL_STRAFERIGHT;
- bs->attackstrafe_time = 0;
- }
- }
- //
- for (i = 0; i < 2; i++) {
- hordir[0] = forward[0];
- hordir[1] = forward[1];
- hordir[2] = 0;
- VectorNormalize(hordir);
- //get the sideward vector
- CrossProduct(hordir, up, sideward);
- //reverse the vector depending on the strafe direction
- if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
- //randomly go back a little
- if (random() > 0.9) {
- VectorAdd(sideward, backward, sideward);
- }
- else {
- //walk forward or backward to get at the ideal attack distance
- if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward);
- else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward);
- }
- //perform the movement
- if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult;
- //movement failed, flip the strafe direction
- bs->flags ^= BFL_STRAFERIGHT;
- bs->attackstrafe_time = 0;
- }
- //bot couldn't do any usefull movement
- // bs->attackchase_time = AAS_Time() + 6;
- return moveresult;
- }
-
- /*
- ==================
- BotSameTeam
- ==================
- */
- int BotSameTeam(bot_state_t *bs, int entnum) {
- char info1[1024], info2[1024];
-
- if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
- //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
- return qfalse;
- }
- if (entnum < 0 || entnum >= MAX_CLIENTS) {
- //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
- return qfalse;
- }
- if (gametype == GT_TEAM || gametype == GT_CTF) {
- trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
- trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
- //
- if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- InFieldOfVision
- ==================
- */
- qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
- {
- int i;
- float diff, angle;
-
- for (i = 0; i < 2; i++) {
- angle = AngleMod(viewangles[i]);
- angles[i] = AngleMod(angles[i]);
- diff = angles[i] - angle;
- if (angles[i] > angle) {
- if (diff > 180.0) diff -= 360.0;
- }
- else {
- if (diff < -180.0) diff += 360.0;
- }
- if (diff > 0) {
- if (diff > fov * 0.5) return qfalse;
- }
- else {
- if (diff < -fov * 0.5) return qfalse;
- }
- }
- return qtrue;
- }
-
- /*
- ==================
- BotEntityVisible
-
- returns visibility in the range [0, 1] taking fog and water surfaces into account
- ==================
- */
- float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
- int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
- float fogdist, waterfactor, vis, bestvis;
- bsp_trace_t trace;
- aas_entityinfo_t entinfo;
- vec3_t dir, entangles, start, end, middle;
-
- //calculate middle of bounding box
- BotEntityInfo(ent, &entinfo);
- VectorAdd(entinfo.mins, entinfo.maxs, middle);
- VectorScale(middle, 0.5, middle);
- VectorAdd(entinfo.origin, middle, middle);
- //check if entity is within field of vision
- VectorSubtract(middle, eye, dir);
- vectoangles(dir, entangles);
- if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
- //
- pc = trap_AAS_PointContents(eye);
- infog = (pc & CONTENTS_SOLID);
- inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
- //
- bestvis = 0;
- for (i = 0; i < 3; i++) {
- //if the point is not in potential visible sight
- //if (!AAS_inPVS(eye, middle)) continue;
- //
- contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
- passent = viewer;
- hitent = ent;
- VectorCopy(eye, start);
- VectorCopy(middle, end);
- //if the entity is in water, lava or slime
- if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
- contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
- }
- //if eye is in water, lava or slime
- if (inwater) {
- if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
- passent = ent;
- hitent = viewer;
- VectorCopy(middle, start);
- VectorCopy(eye, end);
- }
- contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
- }
- //trace from start to end
- BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
- //if water was hit
- waterfactor = 1.0;
- if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
- //if the water surface is translucent
- if (1) {
- //trace through the water
- contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
- BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
- waterfactor = 0.5;
- }
- }
- //if a full trace or the hitent was hit
- if (trace.fraction >= 1 || trace.ent == hitent) {
- //check for fog, assuming there's only one fog brush where
- //either the viewer or the entity is in or both are in
- otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
- if (infog && otherinfog) {
- VectorSubtract(trace.endpos, eye, dir);
- fogdist = VectorLength(dir);
- }
- else if (infog) {
- VectorCopy(trace.endpos, start);
- BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
- VectorSubtract(eye, trace.endpos, dir);
- fogdist = VectorLength(dir);
- }
- else if (otherinfog) {
- VectorCopy(trace.endpos, end);
- BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
- VectorSubtract(end, trace.endpos, dir);
- fogdist = VectorLength(dir);
- }
- else {
- //if the entity and the viewer are not in fog assume there's no fog in between
- fogdist = 0;
- }
- //decrease visibility with the view distance through fog
- vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001));
- //if entering water visibility is reduced
- vis *= waterfactor;
- //
- if (vis > bestvis) bestvis = vis;
- //if pretty much no fog
- if (bestvis >= 0.95) return bestvis;
- }
- //check bottom and top of bounding box as well
- if (i == 0) middle[2] += entinfo.mins[2];
- else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
- }
- return bestvis;
- }
-
- /*
- ==================
- BotFindEnemy
- ==================
- */
- int BotFindEnemy(bot_state_t *bs, int curenemy) {
- int i, healthdecrease;
- float f, dist, curdist, alertness, easyfragger, vis;
- aas_entityinfo_t entinfo, curenemyinfo;
- vec3_t dir, angles;
-
- alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1);
- easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1);
- //check if the health decreased
- healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
- //remember the current health value
- bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
- //
- if (curenemy >= 0) {
- BotEntityInfo(curenemy, &curenemyinfo);
- if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
- VectorSubtract(curenemyinfo.origin, bs->origin, dir);
- curdist = VectorLength(dir);
- }
- else {
- curdist = 0;
- }
- //
- for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
-
- if (i == bs->client) continue;
- //if it's the current enemy
- if (i == curenemy) continue;
- //
- BotEntityInfo(i, &entinfo);
- //
- if (!entinfo.valid) continue;
- //if the enemy isn't dead and the enemy isn't the bot self
- if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
- //if the enemy is invisible and not shooting
- if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
- continue;
- }
- //if not an easy fragger don't shoot at chatting players
- if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
- //
- if (lastteleport_time > trap_AAS_Time() - 3) {
- VectorSubtract(entinfo.origin, lastteleport_origin, dir);
- if (VectorLength(dir) < 70) continue;
- }
- //calculate the distance towards the enemy
- VectorSubtract(entinfo.origin, bs->origin, dir);
- dist = VectorLength(dir);
- //if this entity is not carrying a flag
- if (!EntityCarriesFlag(&entinfo))
- {
- //if this enemy is further away than the current one
- if (curenemy >= 0 && dist > curdist) continue;
- } //end if
- //if the bot has no
- if (dist > 900 + alertness * 4000) continue;
- //if on the same team
- if (BotSameTeam(bs, i)) continue;
- //if the bot's health decreased or the enemy is shooting
- if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360;
- else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9);
- //check if the enemy is visible
- vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
- if (vis <= 0) continue;
- //if the enemy is quite far away, not shooting and the bot is not damaged
- if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo))
- {
- //check if we can avoid this enemy
- VectorSubtract(bs->origin, entinfo.origin, dir);
- vectoangles(dir, angles);
- //if the bot isn't in the fov of the enemy
- if (!InFieldOfVision(entinfo.angles, 120, angles)) {
- //update some stuff for this enemy
- BotUpdateBattleInventory(bs, i);
- //if the bot doesn't really want to fight
- if (BotWantsToRetreat(bs)) continue;
- }
- }
- //found an enemy
- bs->enemy = entinfo.number;
- if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2;
- else bs->enemysight_time = trap_AAS_Time();
- bs->enemysuicide = qfalse;
- bs->enemydeath_time = 0;
- return qtrue;
- }
- return qfalse;
- }
-
- /*
- ==================
- BotTeamFlagCarrierVisible
- ==================
- */
- int BotTeamFlagCarrierVisible(bot_state_t *bs) {
- int i;
- float vis;
- aas_entityinfo_t entinfo;
-
- for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
- if (i == bs->client) continue;
- //
- BotEntityInfo(i, &entinfo);
- //if this player is active
- if (!entinfo.valid) continue;
- //if this player is carrying a flag
- if (!EntityCarriesFlag(&entinfo)) continue;
- //if the flag carrier is not on the same team
- if (!BotSameTeam(bs, i)) continue;
- //if the flag carrier is not visible
- vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
- if (vis <= 0) continue;
- //
- return i;
- }
- return -1;
- }
-
- /*
- ==================
- BotAimAtEnemy
- ==================
- */
- void BotAimAtEnemy(bot_state_t *bs) {
- int i, enemyvisible;
- float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
- vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
- vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
- weaponinfo_t wi;
- aas_entityinfo_t entinfo;
- bot_goal_t goal;
- bsp_trace_t trace;
- vec3_t target;
-
- //if the bot has no enemy
- if (bs->enemy < 0) return;
- //
- //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
- //
- aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1);
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
- //
- if (aim_skill > 0.95) {
- //don't aim too early
- reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
- if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
- if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
- }
-
- //get the weapon information
- trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
- //get the weapon specific aim accuracy and or aim skill
- if (wi.number == WP_MACHINEGUN) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1);
- }
- if (wi.number == WP_SHOTGUN) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1);
- }
- if (wi.number == WP_GRENADE_LAUNCHER) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
- aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
- }
- if (wi.number == WP_ROCKET_LAUNCHER) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1);
- aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1);
- }
- if (wi.number == WP_LIGHTNING) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
- }
- if (wi.number == WP_RAILGUN) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
- }
- if (wi.number == WP_PLASMAGUN) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1);
- aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1);
- }
- if (wi.number == WP_BFG) {
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
- aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
- }
- //
- if (aim_accuracy <= 0) aim_accuracy = 0.0001;
- //get the enemy entity information
- BotEntityInfo(bs->enemy, &entinfo);
- //if the enemy is invisible then shoot crappy most of the time
- if (EntityIsInvisible(&entinfo)) {
- if (random() > 0.1) aim_accuracy *= 0.4;
- }
- //
- VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
- VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
- //enemy origin and velocity is remembered every 0.5 seconds
- if (bs->enemyposition_time < trap_AAS_Time()) {
- //
- bs->enemyposition_time = trap_AAS_Time() + 0.5;
- VectorCopy(enemyvelocity, bs->enemyvelocity);
- VectorCopy(entinfo.origin, bs->enemyorigin);
- }
- //if not extremely skilled
- if (aim_skill < 0.9) {
- VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
- //if the enemy moved a bit
- if (VectorLength(dir) > 48) {
- //if the enemy changed direction
- if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
- //aim accuracy should be worse now
- aim_accuracy *= 0.7;
- }
- }
- }
- //check visibility of enemy
- enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
- //if the enemy is visible
- if (enemyvisible) {
- //
- VectorCopy(entinfo.origin, bestorigin);
- bestorigin[2] += 8;
- //get the start point shooting from
- //NOTE: the x and y projectile start offsets are ignored
- VectorCopy(bs->origin, start);
- start[2] += bs->cur_ps.viewheight;
- start[2] += wi.offset[2];
- //
- BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
- //if the enemy is NOT hit
- if (trace.fraction <= 1 && trace.ent != entinfo.number) {
- bestorigin[2] += 16;
- }
- //if it is not an instant hit weapon the bot might want to predict the enemy
- if (wi.speed) {
- //
- VectorSubtract(bestorigin, bs->origin, dir);
- dist = VectorLength(dir);
- VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
- //if the enemy is NOT pretty far away and strafing just small steps left and right
- if (!(dist > 100 && VectorLength(dir) < 32)) {
- //if skilled anough do exact prediction
- if (aim_skill > 0.8 &&
- //if the weapon is ready to fire
- bs->cur_ps.weaponstate == WEAPON_READY) {
- aas_clientmove_t move;
- vec3_t origin;
-
- VectorSubtract(entinfo.origin, bs->origin, dir);
- //distance towards the enemy
- dist = VectorLength(dir);
- //direction the enemy is moving in
- VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
- //
- VectorScale(dir, 1 / entinfo.update_time, dir);
- //
- VectorCopy(entinfo.origin, origin);
- origin[2] += 1;
- //
- VectorClear(cmdmove);
- //AAS_ClearShownDebugLines();
- trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
- PRESENCE_CROUCH, qfalse,
- dir, cmdmove, 0,
- dist * 10 / wi.speed, 0.1, 0, 0, qfalse);
- VectorCopy(move.endpos, bestorigin);
- //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed);
- }
- //if not that skilled do linear prediction
- else if (aim_skill > 0.4) {
- VectorSubtract(entinfo.origin, bs->origin, dir);
- //distance towards the enemy
- dist = VectorLength(dir);
- //direction the enemy is moving in
- VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
- dir[2] = 0;
- //
- speed = VectorNormalize(dir) / entinfo.update_time;
- //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
- //best spot to aim at
- VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
- }
- }
- }
- //if the projectile does radial damage
- if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
- //if the enemy isn't standing significantly higher than the bot
- if (entinfo.origin[2] < bs->origin[2] + 16) {
- //try to aim at the ground in front of the enemy
- VectorCopy(entinfo.origin, end);
- end[2] -= 64;
- BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
- //
- VectorCopy(bestorigin, groundtarget);
- if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
- else groundtarget[2] = trace.endpos[2] - 8;
- //trace a line from projectile start to ground target
- BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
- //if hitpoint is not vertically too far from the ground target
- if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
- VectorSubtract(trace.endpos, groundtarget, dir);
- //if the hitpoint is near anough the ground target
- if (VectorLength(dir) < 60) {
- VectorSubtract(trace.endpos, start, dir);
- //if the hitpoint is far anough from the bot
- if (VectorLength(dir) > 100) {
- //check if the bot is visible from the ground target
- trace.endpos[2] += 1;
- BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
- if (trace.fraction >= 1) {
- //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
- VectorCopy(groundtarget, bestorigin);
- }
- }
- }
- }
- }
- }
- bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
- bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
- bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
- }
- else {
- //
- VectorCopy(bs->lastenemyorigin, bestorigin);
- bestorigin[2] += 8;
- //if the bot is skilled anough
- if (aim_skill > 0.5) {
- //do prediction shots around corners
- if (wi.number == WP_BFG ||
- wi.number == WP_ROCKET_LAUNCHER ||
- wi.number == WP_GRENADE_LAUNCHER) {
- //create the chase goal
- goal.entitynum = bs->client;
- goal.areanum = bs->areanum;
- VectorCopy(bs->eye, goal.origin);
- VectorSet(goal.mins, -8, -8, -8);
- VectorSet(goal.maxs, 8, 8, 8);
- //
- if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
- VectorSubtract(target, bs->eye, dir);
- if (VectorLength(dir) > 80) {
- VectorCopy(target, bestorigin);
- bestorigin[2] -= 20;
- }
- }
- aim_accuracy = 1;
- }
- }
- }
- //
- if (enemyvisible) {
- BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
- VectorCopy(trace.endpos, bs->aimtarget);
- }
- else {
- VectorCopy(bestorigin, bs->aimtarget);
- }
- //get aim direction
- VectorSubtract(bestorigin, bs->eye, dir);
- //
- if (wi.number == WP_MACHINEGUN ||
- wi.number == WP_SHOTGUN ||
- wi.number == WP_LIGHTNING ||
- wi.number == WP_RAILGUN) {
- //distance towards the enemy
- dist = VectorLength(dir);
- if (dist > 150) dist = 150;
- f = 0.6 + dist / 150 * 0.4;
- aim_accuracy *= f;
- }
- //add some random stuff to the aim direction depending on the aim accuracy
- if (aim_accuracy < 0.8) {
- VectorNormalize(dir);
- for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
- }
- //set the ideal view angles
- vectoangles(dir, bs->ideal_viewangles);
- //take the weapon spread into account for lower skilled bots
- bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
- bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
- bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
- bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
- //if the bots should be really challenging
- if (bot_challenge.integer) {
- //if the bot is really accurate and has the enemy in view for some time
- if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1) {
- //set the view angles directly
- if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
- VectorCopy(bs->ideal_viewangles, bs->viewangles);
- trap_EA_View(bs->client, bs->viewangles);
- }
- }
- }
-
- /*
- ==================
- BotCheckAttack
- ==================
- */
- void BotCheckAttack(bot_state_t *bs) {
- float points, reactiontime, fov, firethrottle;
- bsp_trace_t bsptrace;
- //float selfpreservation;
- vec3_t forward, right, start, end, dir, angles;
- weaponinfo_t wi;
- bsp_trace_t trace;
- aas_entityinfo_t entinfo;
- vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
-
- if (bs->enemy < 0) return;
- //
- reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
- if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
- if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
- //if changing weapons
- if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return;
- //check fire throttle characteristic
- if (bs->firethrottlewait_time > trap_AAS_Time()) return;
- firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1);
- if (bs->firethrottleshoot_time < trap_AAS_Time()) {
- if (random() > firethrottle) {
- bs->firethrottlewait_time = trap_AAS_Time() + firethrottle;
- bs->firethrottleshoot_time = 0;
- }
- else {
- bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle;
- bs->firethrottlewait_time = 0;
- }
- }
- //
- BotEntityInfo(bs->enemy, &entinfo);
- VectorSubtract(entinfo.origin, bs->eye, dir);
- //
- if (VectorLength(dir) < 100) fov = 120;
- else fov = 50;
- /*
- //if the enemy isn't visible
- if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) {
- //botimport.Print(PRT_MESSAGE, "enemy not visible\n");
- return;
- }*/
- vectoangles(dir, angles);
- if (!InFieldOfVision(bs->viewangles, fov, angles)) return;
- BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
- if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return;
-
- //get the weapon info
- trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
- //get the start point shooting from
- VectorCopy(bs->origin, start);
- start[2] += bs->cur_ps.viewheight;
- AngleVectors(bs->viewangles, forward, right, NULL);
- start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
- start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
- start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
- //end point aiming at
- VectorMA(start, 1000, forward, end);
- //a little back to make sure not inside a very close enemy
- VectorMA(start, -12, forward, start);
- BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
- //if won't hit the enemy
- if (trace.ent != bs->enemy) {
- //if the entity is a client
- if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
- //if a teammate is hit
- if (BotSameTeam(bs, trace.ent)) return;
- }
- //if the projectile does a radial damage
- if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
- if (trace.fraction * 1000 < wi.proj.radius) {
- points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
- if (points > 0) {
- // selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1);
- // if (random() < selfpreservation) return;
- return;
- }
- }
- //FIXME: check if a teammate gets radial damage
- }
- }
- //if fire has to be release to activate weapon
- if (wi.flags & WFL_FIRERELEASED) {
- if (bs->flags & BFL_ATTACKED) {
- trap_EA_Attack(bs->client);
- }
- }
- else {
- trap_EA_Attack(bs->client);
- }
- bs->flags ^= BFL_ATTACKED;
- }
-
- /*
- ==================
- BotMapScripts
- ==================
- */
- void BotMapScripts(bot_state_t *bs) {
- char info[1024];
- char mapname[128];
- int i, shootbutton;
- float aim_accuracy;
- aas_entityinfo_t entinfo;
- vec3_t dir;
-
- trap_GetServerinfo(info, sizeof(info));
-
- strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
- mapname[sizeof(mapname)-1] = '\0';
-
- if (!Q_stricmp(mapname, "q3tourney6")) {
- vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
- vec3_t buttonorg = {304, 352, 920};
- //NOTE: NEVER use the func_bobbing in q3tourney6
- bs->tfl &= ~TFL_FUNCBOB;
- //if the bot is below the bounding box
- if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
- if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
- if (bs->origin[2] < mins[2]) {
- return;
- }
- }
- }
- shootbutton = qfalse;
- //if an enemy is below this bounding box then shoot the button
- for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
-
- if (i == bs->client) continue;
- //
- BotEntityInfo(i, &entinfo);
- //
- if (!entinfo.valid) continue;
- //if the enemy isn't dead and the enemy isn't the bot self
- if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
- //
- if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
- if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
- if (entinfo.origin[2] < mins[2]) {
- //if there's a team mate below the crusher
- if (BotSameTeam(bs, i)) {
- shootbutton = qfalse;
- break;
- }
- else {
- shootbutton = qtrue;
- }
- }
- }
- }
- }
- if (shootbutton) {
- bs->flags |= BFL_IDEALVIEWSET;
- VectorSubtract(buttonorg, bs->eye, dir);
- vectoangles(dir, bs->ideal_viewangles);
- aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
- bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
- bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
- bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
- bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
- //
- if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) {
- trap_EA_Attack(bs->client);
- }
- }
- }
- }
-
- /*
- ==================
- BotEntityToActivate
- ==================
- */
- //#define OBSTACLEDEBUG
-
- int BotEntityToActivate(int entitynum) {
- int i, ent, cur_entities[10];
- char model[MAX_INFO_STRING], tmpmodel[128];
- char target[128], classname[128];
- float health;
- char targetname[10][128];
- aas_entityinfo_t entinfo;
-
- BotEntityInfo(entitynum, &entinfo);
- Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
- for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
- if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
- if (!strcmp(model, tmpmodel)) break;
- }
- if (!ent) {
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model);
- return 0;
- }
- trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
- if (!classname) {
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model);
- return 0;
- }
- //if it is a door
- if (!strcmp(classname, "func_door")) {
- if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
- //if health the door must be shot to open
- if (health) return ent;
- }
- }
- //get the targetname so we can find an entity with a matching target
- if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
- #ifdef OBSTACLEDEBUG
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model);
- #endif //OBSTACLEDEBUG
- return 0;
- }
- //only allows single activation chains, tree-like activation can be added back in
- cur_entities[0] = trap_AAS_NextBSPEntity(0);
- for (i = 0; i >= 0 && i < 10;) {
- for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
- if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
- if (!strcmp(targetname[i], target)) {
- cur_entities[i] = trap_AAS_NextBSPEntity(ent);
- break;
- }
- }
- if (!ent) {
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]);
- i--;
- continue;
- }
- if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]);
- continue;
- }
- if (!strcmp(classname, "func_button")) {
- //BSP button model
- return ent;
- }
- else if (!strcmp(classname, "trigger_multiple")) {
- //invisible trigger multiple box
- return ent;
- }
- else {
- i--;
- }
- }
- BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname);
- return 0;
- }
-
- /*
- ==================
- BotSetMovedir
- ==================
- */
- vec3_t VEC_UP = {0, -1, 0};
- vec3_t MOVEDIR_UP = {0, 0, 1};
- vec3_t VEC_DOWN = {0, -2, 0};
- vec3_t MOVEDIR_DOWN = {0, 0, -1};
-
- void BotSetMovedir(vec3_t angles, vec3_t movedir) {
- if (VectorCompare(angles, VEC_UP)) {
- VectorCopy(MOVEDIR_UP, movedir);
- }
- else if (VectorCompare(angles, VEC_DOWN)) {
- VectorCopy(MOVEDIR_DOWN, movedir);
- }
- else {
- AngleVectors(angles, movedir, NULL, NULL);
- }
- }
-
- /*
- ==================
- BotModelMinsMaxs
-
- this is ugly
- ==================
- */
- void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) {
- gentity_t *ent;
- int i;
-
- ent = &g_entities[0];
- for (i = 0; i < level.num_entities; i++, ent++) {
- if ( !ent->inuse ) {
- continue;
- }
- if ( ent->s.eType != eType) {
- continue;
- }
- if (ent->s.modelindex == modelindex) {
- VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
- VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
- return;
- }
- }
- VectorClear(mins);
- VectorClear(maxs);
- }
-
- /*
- ==================
- BotAIBlocked
-
- very basic handling of bots being blocked by other entities
- check what kind of entity is blocking the bot and try to activate
- it otherwise try to walk around the entity
- before the bot ends in this part of the AI it should predict which doors to open,
- which buttons to activate etc.
- ==================
- */
- void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
- int movetype, ent, i, areas[10], numareas, modelindex;
- char classname[128], model[128];
- float lip, dist, health, angle;
- vec3_t hordir, size, start, end, mins, maxs, sideward, angles;
- vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
- vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
- aas_entityinfo_t entinfo;
- //bsp_trace_t bsptrace;
- #ifdef OBSTACLEDEBUG
- char netname[MAX_NETNAME];
- char buf[128];
- #endif
-
- if (!moveresult->blocked) {
- bs->notblocked_time = trap_AAS_Time();
- return;
- }
- //
- BotEntityInfo(moveresult->blockentity, &entinfo);
- #ifdef OBSTACLEDEBUG
- ClientName(bs->client, netname, sizeof(netname));
- BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
- #endif OBSTACLEDEBUG
- //if blocked by a bsp model and the bot wants to activate it if possible
- if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) {
- //find the bsp entity which should be activated in order to remove
- //the blocking entity
- ent = BotEntityToActivate(entinfo.number);
- if (!ent) {
- strcpy(classname, "");
- #ifdef OBSTACLEDEBUG
- BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname)));
- #endif //OBSTACLEDEBUG
- }
- else {
- trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
- #ifdef OBSTACLEDEBUG
- ClientName(bs->client, netname, sizeof(netname));
- BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname);
- #endif OBSTACLEDEBUG
- }
- if (!strcmp(classname, "func_button")) {
- //create a bot goal towards the button
- trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
- modelindex = atoi(model+1);
- //if the model is not loaded
- if (!modelindex) return;
- VectorClear(angles);
- BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
- //get the lip of the button
- trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip);
- if (!lip) lip = 4;
- //get the move direction from the angle
- trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle);
- VectorSet(angles, 0, angle, 0);
- BotSetMovedir(angles, movedir);
- //button size
- VectorSubtract(maxs, mins, size);
- //button origin
- VectorAdd(mins, maxs, origin);
- VectorScale(origin, 0.5, origin);
- //touch distance of the button
- dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
- dist *= 0.5;
- //
- trap_AAS_FloatForBSPEpairKey(ent, "health", &health);
- //if the button is shootable
- if (health) {
- //FIXME: walk to a point where the button is visible and shoot at the button
- //calculate the goal origin
- VectorMA(origin, -dist, movedir, goalorigin);
- //
- VectorSubtract(goalorigin, bs->origin, movedir);
- vectoangles(movedir, moveresult->ideal_viewangles);
- moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
- moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
- //select the machinegun and shoot
- trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN);
- if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) {
- trap_EA_Attack(bs->client);
- }
- return;
- }
- else {
- //add bounding box size to the dist
- trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
- for (i = 0; i < 3; i++) {
- if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
- else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
- }
- //calculate the goal origin
- VectorMA(origin, -dist, movedir, goalorigin);
- //
- VectorCopy(goalorigin, start);
- start[2] += 24;
- VectorCopy(start, end);
- end[2] -= 100;
- numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
- //
- for (i = 0; i < numareas; i++) {
- if (trap_AAS_AreaReachability(areas[i])) {
- break;
- }
- }
- if (i < numareas) {
- //
- #ifdef OBSTACLEDEBUG
- if (bs->activatemessage_time < trap_AAS_Time()) {
- Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n",
- goalorigin[0], goalorigin[1], goalorigin[2], areas[i]);
- trap_EA_Say(bs->client, buf);
- bs->activatemessage_time = trap_AAS_Time() + 5;
- }
- #endif //OBSTACLEDEBUG
- //
- VectorCopy(origin, bs->activategoal.origin);
- bs->activategoal.areanum = areas[i];
- VectorSubtract(mins, origin, bs->activategoal.mins);
- VectorSubtract(maxs, origin, bs->activategoal.maxs);
- //
- for (i = 0; i < 3; i++)
- {
- if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
- else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
- } //end for
- //
- bs->activategoal.entitynum = entinfo.number;
- bs->activategoal.number = 0;
- bs->activategoal.flags = 0;
- bs->activate_time = trap_AAS_Time() + 10;
- AIEnter_Seek_ActivateEntity(bs);
- return;
- }
- else {
- #ifdef OBSTACLEDEBUG
- if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n");
- else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n");
- #endif //OBSTACLEDEBUG
- if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
- else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
- }
- }
- }
- else if (!strcmp(classname, "func_door")) {
- //shoot at the shootable door
- trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
- modelindex = atoi(model+1);
- //if the model is not loaded
- if (!modelindex) return;
- VectorClear(angles);
- BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
- //door origin
- VectorAdd(mins, maxs, origin);
- VectorScale(origin, 0.5, origin);
- //
- VectorSubtract(origin, bs->eye, movedir);
- vectoangles(movedir, moveresult->ideal_viewangles);
- moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
- moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
- //select the machinegun and shoot
- trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN);
- if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) {
- trap_EA_Attack(bs->client);
- }
- return;
- }
- }
- //just some basic dynamic obstacle avoidance code
- hordir[0] = moveresult->movedir[0];
- hordir[1] = moveresult->movedir[1];
- hordir[2] = 0;
- //if no direction just take a random direction
- if (VectorNormalize(hordir) < 0.1) {
- VectorSet(angles, 0, 360 * random(), 0);
- AngleVectors(angles, hordir, NULL, NULL);
- }
- //
- //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
- //else
- movetype = MOVE_WALK;
- //if there's an obstacle at the bot's feet and head then
- //the bot might be able to crouch through
- VectorCopy(bs->origin, start);
- start[2] += 18;
- VectorMA(start, 5, hordir, end);
- VectorSet(mins, -16, -16, -24);
- VectorSet(maxs, 16, 16, 4);
- //
- //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
- //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
- //get the sideward vector
- CrossProduct(hordir, up, sideward);
- //
- if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
- //try to crouch straight forward?
- if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
- //perform the movement
- if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
- //flip the avoid direction flag
- bs->flags ^= BFL_AVOIDRIGHT;
- //flip the direction
- VectorNegate(sideward, sideward);
- //move in the other direction
- trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
- }
- }
- //
- if (bs->notblocked_time < trap_AAS_Time() - 0.4) {
- //just reset goals and hope the bot will go into another direction
- //is this still needed??
- if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
- else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
- }
- }
-
- /*
- ==================
- BotCheckConsoleMessages
- ==================
- */
- void BotCheckConsoleMessages(bot_state_t *bs) {
- char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
- float chat_reply;
- int context, handle;
- bot_consolemessage_t m;
- bot_match_t match;
-
- //the name of this bot
- ClientName(bs->client, botname, sizeof(botname));
- //
- while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
- //if the chat state is flooded with messages the bot will read them quickly
- if (trap_BotNumConsoleMessages(bs->cs) < 10) {
- //if it is a chat message the bot needs some time to read it
- if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break;
- }
- //
- ptr = m.message;
- //if it is a chat message then don't unify white spaces and don't
- //replace synonyms in the netname
- if (m.type == CMS_CHAT) {
- //
- if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
- ptr = m.message + match.variables[MESSAGE].offset;
- }
- }
- //unify the white spaces in the message
- trap_UnifyWhiteSpaces(ptr);
- //replace synonyms in the right context
- context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
- if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM;
- else context |= CONTEXT_CTFBLUETEAM;
- trap_BotReplaceSynonyms(ptr, context);
- //if there's no match
- if (!BotMatchMessage(bs, m.message)) {
- //if it is a chat message
- if (m.type == CMS_CHAT && !bot_nochat.integer) {
- //
- if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
- trap_BotRemoveConsoleMessage(bs->cs, handle);
- continue;
- }
- //don't use eliza chats with team messages
- if (match.subtype & ST_TEAM) {
- trap_BotRemoveConsoleMessage(bs->cs, handle);
- continue;
- }
- //
- trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
- trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
- //if this is a message from the bot self
- if (!Q_stricmp(netname, botname)) {
- trap_BotRemoveConsoleMessage(bs->cs, handle);
- continue;
- }
- //unify the message
- trap_UnifyWhiteSpaces(message);
- //
- trap_Cvar_Update(&bot_testrchat);
- if (bot_testrchat.integer) {
- //
- trap_BotLibVarSet("bot_testrchat", "1");
- //if bot replies with a chat message
- if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
- NULL, NULL,
- NULL, NULL,
- NULL, NULL,
- botname, netname)) {
- BotAI_Print(PRT_MESSAGE, "------------------------\n");
- }
- else {
- BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
- }
- }
- //if at a valid chat position and not chatting already
- else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) {
- chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1);
- if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
- //if bot replies with a chat message
- if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
- NULL, NULL,
- NULL, NULL,
- NULL, NULL,
- botname, netname)) {
- //remove the console message
- trap_BotRemoveConsoleMessage(bs->cs, handle);
- bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
- AIEnter_Stand(bs);
- //EA_Say(bs->client, bs->cs.chatmessage);
- break;
- }
- }
- }
- }
- }
- //remove the console message
- trap_BotRemoveConsoleMessage(bs->cs, handle);
- }
- }
-
- /*
- ==================
- BotCheckEvents
- ==================
- */
- void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
- int event;
- char buf[128];
- //
- //NOTE: this sucks, we're accessing the gentity_t directly
- //but there's no other fast way to do it right now
- if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
- return;
- }
- bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
- //if it's an event only entity
- if (state->eType > ET_EVENTS) {
- event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
- }
- else {
- event = state->event & ~EV_EVENT_BITS;
- }
- //
- switch(event) {
- //client obituary event
- case EV_OBITUARY:
- {
- int target, attacker, mod;
-
- target = state->otherEntityNum;
- attacker = state->otherEntityNum2;
- mod = state->eventParm;
- //
- if (target == bs->client) {
- bs->botdeathtype = mod;
- bs->lastkilledby = attacker;
- //
- if (target == attacker) bs->botsuicide = qtrue;
- else bs->botsuicide = qfalse;
- //
- bs->num_deaths++;
- }
- //else if this client was killed by the bot
- else if (attacker == bs->client) {
- bs->enemydeathtype = mod;
- bs->lastkilledplayer = target;
- bs->killedenemy_time = trap_AAS_Time();
- //
- bs->num_kills++;
- }
- else if (attacker == bs->enemy && target == attacker) {
- bs->enemysuicide = qtrue;
- }
- break;
- }
- case EV_GLOBAL_SOUND:
- {
- if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
- BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
- break;
- }
- trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
- if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) {
- //red flag is returned
- bs->redflagstatus = 0;
- bs->flagstatuschanged = qtrue;
- }
- else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) {
- //blue flag is returned
- bs->blueflagstatus = 0;
- bs->flagstatuschanged = qtrue;
- }
- else if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
- //powerup respawned... go get it
- BotGoForPowerups(bs);
- }
- break;
- }
- case EV_PLAYER_TELEPORT_IN:
- {
- VectorCopy(state->origin, lastteleport_origin);
- lastteleport_time = trap_AAS_Time();
- break;
- }
- case EV_GENERAL_SOUND:
- {
- //if this sound is played on the bot
- if (state->number == bs->client) {
- if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
- BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
- break;
- }
- //check out the sound
- trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
- //if falling into a death pit
- if (!strcmp(buf, "*falling1.wav")) {
- //if the bot has a personal teleporter
- if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
- //use the holdable item
- trap_EA_Use(bs->client);
- }
- }
- }
- break;
- }
- }
- }
-
- /*
- ==================
- BotCheckSnapshot
- ==================
- */
- void BotCheckSnapshot(bot_state_t *bs) {
- int ent;
- entityState_t state;
-
- //
- ent = 0;
- while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
- //check the entity state for events
- BotCheckEvents(bs, &state);
- }
- //check the player state for events
- BotAI_GetEntityState(bs->client, &state);
- //copy the player state events to the entity state
- state.event = bs->cur_ps.externalEvent;
- state.eventParm = bs->cur_ps.externalEventParm;
- //
- BotCheckEvents(bs, &state);
- }
-
- /*
- ==================
- BotCheckAir
- ==================
- */
- void BotCheckAir(bot_state_t *bs) {
- if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
- if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
- return;
- }
- }
- bs->lastair_time = trap_AAS_Time();
- }
-
- /*
- ==================
- BotDeathmatchAI
- ==================
- */
- void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
- char gender[144], name[144], buf[144];
- char userinfo[MAX_INFO_STRING];
- int i;
-
- //if the bot has just been setup
- if (bs->setupcount > 0) {
- bs->setupcount--;
- if (bs->setupcount > 0) return;
- //get the gender characteristic
- trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
- //set the bot gender
- trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
- Info_SetValueForKey(userinfo, "sex", gender);
- trap_SetUserinfo(bs->client, userinfo);
- //set the team
- if ( g_gametype.integer != GT_TOURNAMENT ) {
- Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
- trap_EA_Command(bs->client, buf);
- }
- //set the chat gender
- if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
- else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
- else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
- //set the chat name
- ClientName(bs->client, name, sizeof(name));
- trap_BotSetChatName(bs->cs, name);
- //
- bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
- bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
- //
- bs->setupcount = 0;
- }
- //no ideal view set
- bs->flags &= ~BFL_IDEALVIEWSET;
- //set the teleport time
- BotSetTeleportTime(bs);
- //update some inventory values
- BotUpdateInventory(bs);
- //check the console messages
- BotCheckConsoleMessages(bs);
- //check out the snapshot
- BotCheckSnapshot(bs);
- //check for air
- BotCheckAir(bs);
- //if not in the intermission and not in observer mode
- if (!BotIntermission(bs) && !BotIsObserver(bs)) {
- //do team AI
- BotTeamAI(bs);
- }
- //if the bot has no ai node
- if (!bs->ainode) {
- AIEnter_Seek_LTG(bs);
- }
- //if the bot entered the game less than 8 seconds ago
- if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) {
- if (BotChat_EnterGame(bs)) {
- bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
- AIEnter_Stand(bs);
- }
- bs->entergamechat = qtrue;
- }
- //reset the node switches from the previous frame
- BotResetNodeSwitches();
- //execute AI nodes
- for (i = 0; i < MAX_NODESWITCHES; i++) {
- if (bs->ainode(bs)) break;
- }
- //if the bot removed itself :)
- if (!bs->inuse) return;
- //if the bot executed too many AI nodes
- if (i >= MAX_NODESWITCHES) {
- trap_BotDumpGoalStack(bs->gs);
- trap_BotDumpAvoidGoals(bs->gs);
- BotDumpNodeSwitches(bs);
- ClientName(bs->client, name, sizeof(name));
- BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES);
- }
- //
- bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
- bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
- }
-
- /*
- ==================
- BotSetupDeathmatchAI
- ==================
- */
- void BotSetupDeathmatchAI(void) {
- int ent, modelnum;
- char model[128];
-
- gametype = trap_Cvar_VariableIntegerValue("g_gametype");
- maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
-
- trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
- trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
- trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
- trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
- trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
- trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
- //
- if (gametype == GT_CTF) {
- if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
- BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
- if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
- BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
- }
-
- max_bspmodelindex = 0;
- for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
- if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
- if (model[0] == '*') {
- modelnum = atoi(model+1);
- if (modelnum > max_bspmodelindex)
- max_bspmodelindex = modelnum;
- }
- }
- //initialize the waypoint heap
- BotInitWaypoints();
- }
-
- /*
- ==================
- BotShutdownDeathmatchAI
- ==================
- */
- void BotShutdownDeathmatchAI(void) {
- }
-
-