8436 lines
214 KiB
C++
8436 lines
214 KiB
C++
// Copyright (C) 2007 Id Software, Inc.
|
|
//
|
|
|
|
#include "../precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
#include "../../game/ContentMask.h"
|
|
#include "BotThreadData.h"
|
|
#include "BotAI_Main.h"
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_MedicCheckForWoundedMate
|
|
|
|
Checks for wounded teammates. Works with both gdf and strogg (since the 2 are so similiar).
|
|
If the bot is already in the process of healing/reviving someone, it will pass the clients number in "healClientNum",
|
|
in which case, the bot will only look at ones that are closer then their current NBG goal.
|
|
"range" is how far bot will look to heal. This will change based on what bot is
|
|
doing (if escorting important mate, or carrying OBJ, will not go as far to heal).
|
|
================
|
|
*/
|
|
int idBotAI::Bot_MedicCheckForWoundedMate( int healClientNum, int escortClientNum, float range, int mateHealth ) {
|
|
int i;
|
|
int clientNum = -1;
|
|
int busyClient;
|
|
int matesInArea;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
//mal: dont bother doing this if bot has no charge
|
|
if ( !ClassWeaponCharged( HEALTH ) ) {
|
|
botIdealWeapSlot = GUN;
|
|
return - 1;
|
|
}
|
|
|
|
if ( botInfo->inWater ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( healClientNum != -1 ) {
|
|
vec = botWorld->clientInfo[ healClientNum ].origin - botInfo->origin;
|
|
closest = vec.LengthFast();
|
|
clientNum = healClientNum;
|
|
} //mal: if we already have someone we're trying to heal, only heal someone else if they're closer!
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue; //mal: dont try to heal ourselves!
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
//mal: some bot has this client tagging for healing.
|
|
//mal_NOTE: in this case, we dont care if a human medic is on route or nearby, just because experience shows most humans SUCK at being medics! ;-)
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, SUPPLY_TEAMMATE, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->isActor && botThreadData.actorMissionInfo.targetClientNum != i ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isNoTarget ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.classType == MEDIC ) {
|
|
continue;
|
|
} //mal: if player is a medic, ignore, they can heal themselves!
|
|
|
|
if ( playerInfo.team != botInfo->team && playerInfo.isDisguised == false ) {
|
|
continue; //mal: give no comfort to the enemy! can be tricked by disguised enemy.
|
|
}
|
|
|
|
if ( playerInfo.health == playerInfo.maxHealth ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.health >= 100 && playerInfo.lastChatTime[ HEAL_ME ] + 5000 < botWorld->gameLocalInfo.time ) { //mal: conserver our resources if your in good shape, unless you ask for it.
|
|
continue;
|
|
}
|
|
|
|
if ( healClientNum != -1 && playerInfo.xySpeed > WALKING_SPEED ) {
|
|
continue;
|
|
} //mal: ignore ppl moving around a lot, if we're already on way to someone else!
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't heal ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health >= mateHealth && i != escortClientNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.health <= 0 ) { //mal: dont bother if client is dead. we look for dead mates elsewhere
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue; //mal: dont bother if client already tapped/gibbed
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( i ) == true ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !playerInfo.hasGroundContact && !playerInfo.hasJumped ) {
|
|
continue;
|
|
} //mal: ignore ppl flying thru the air (from explosion, dropping from airplane, etc).
|
|
|
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
continue; //mal: ignore players in a vehicle/deployable.
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
} //mal: don't bother with ppl who aren't in a valid AAS area!
|
|
|
|
matesInArea = ClientsInArea( botNum, playerInfo.origin, 150.0f, botInfo->team, MEDIC, false, false, false, false, true );
|
|
|
|
if ( matesInArea > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
bool requestedHelp = false;
|
|
|
|
if ( !playerInfo.isBot && ( playerInfo.lastChatTime[ REVIVE_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time || playerInfo.lastChatTime[ HEAL_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time ) ) {
|
|
if ( botWorld->gameLocalInfo.gameIsBotMatch ) {
|
|
range = MEDIC_RANGE_REQUEST * 2.0f;
|
|
} else {
|
|
range = MEDIC_RANGE_REQUEST;
|
|
}
|
|
requestedHelp = true;
|
|
}
|
|
|
|
if ( dist > Square( range ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( escortClientNum != -1 && !requestedHelp ) {
|
|
vec = botWorld->clientInfo[ escortClientNum ].origin - playerInfo.origin;
|
|
if ( vec.LengthSqr() > Square( MEDIC_RANGE_BUSY ) ) {
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
if ( vec.LengthSqr() > Square( 500.0f ) ) {
|
|
continue; //mal: ignore this client if hes too far away from our escort client, UNLESS the client is REALLY close to us....
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( requestedHelp ) {
|
|
if ( botWorld->gameLocalInfo.gameIsBotMatch ) {
|
|
dist = 1.0f;
|
|
} else {
|
|
dist -= Square( 900.0f );
|
|
}
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player in need, and help them first!
|
|
clientNum = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
if ( clientNum == healClientNum && healClientNum != -1 ) {
|
|
clientNum = -1; //mal: dont worry about re-calcing for a client we already have!
|
|
}
|
|
|
|
return clientNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_MedicCheckForDeadMateDuringCombat
|
|
================
|
|
*/
|
|
int idBotAI::Bot_MedicCheckForDeadMateDuringCombat( const idVec3 &dangerOrg, float range ) {
|
|
|
|
int i;
|
|
int clientNum = -1;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue; //mal: dont try to heal ourselves!
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue; //mal: give no comfort to the enemy!
|
|
}
|
|
|
|
if ( playerInfo.xySpeed > 0.0f ) {
|
|
continue;
|
|
} //mal: ignore bodies flying thru the air!
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't revive ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
} //mal: can't heal someone not in a valid AAS area!
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
|
|
float tempRangeLimit = range;
|
|
|
|
if ( botWorld->gameLocalInfo.gameIsBotMatch && !playerInfo.isBot ) { //mal: go out of our way more to revive the player in an SP game.
|
|
tempRangeLimit = 2000.0f;
|
|
}
|
|
|
|
if ( dist > Square( tempRangeLimit ) ) { //mal: too far away from us - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->friendsInArea < 2 || botInfo->enemiesInArea > 1 ) {
|
|
vec = playerInfo.origin - dangerOrg;
|
|
if ( vec.LengthFast() < 500.0f ) { //mal: this guys too close to danger - have to ignore him
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player in need, and help them first!
|
|
clientNum = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return clientNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_StroggCheckForGDFBodies
|
|
================
|
|
*/
|
|
int idBotAI::Bot_StroggCheckForGDFBodies( float range ) {
|
|
|
|
int i;
|
|
int bodyNum = -1;
|
|
int mates;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
nbgTargetType = NOTYPE;
|
|
int busyClient = -1;
|
|
|
|
//mal: this is an especially dangerous thing to do - if there are lots of enemies around, DONT spawnhost.
|
|
if ( botInfo->enemiesInArea > 0 ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
//mal: first, check for wounded clients
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue; //mal: dont try to spawnhost ourselves!
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i )) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.xySpeed > 0.0f ) {
|
|
continue;
|
|
} //mal: ignore bodies flying thru the air!
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't host ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
} //mal: can't host someone not in a valid AAS area!
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthFast();
|
|
|
|
if ( dist > range ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.team_STROGG_PrimaryAction != ACTION_NULL ) {
|
|
vec = playerInfo.origin - botThreadData.botActions[ botWorld->botGoalInfo.team_STROGG_PrimaryAction ]->GetActionOrigin();
|
|
if ( vec.LengthSqr() > Square( SPAWNHOST_RELEVANT_DIST ) ) { //mal: this is too far away from the obj to matter(?)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, CREATE_SPAWNHOST, busyClient )) {
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, playerInfo.origin, 150.0f, botInfo->team , MEDIC, false, false, false, false, true );
|
|
|
|
if ( mates >= 1 ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player to host!
|
|
bodyNum = i;
|
|
closest = dist;
|
|
nbgTargetType = CLIENT; //mal: this is a client
|
|
}
|
|
}
|
|
|
|
//mal: now, check for bodies out there, and see if its closer....
|
|
for ( i = 0; i < MAX_PLAYERBODIES; i++ ) {
|
|
|
|
if ( !botWorld->playerBodies[ i ].isValid ) {
|
|
continue; //mal: no body in this client slot!
|
|
}
|
|
|
|
if ( BodyIsIgnored( i )) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].bodyTeam == botInfo->team ) {
|
|
continue; //mal: dont try to spawnhost our team
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botWorld->playerBodies[ i ].isSpawnHostAble ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.team_STROGG_PrimaryAction != ACTION_NULL ) {
|
|
vec = botWorld->playerBodies[ i ].bodyOrigin - botThreadData.botActions[ botWorld->botGoalInfo.team_STROGG_PrimaryAction ]->GetActionOrigin();
|
|
if ( vec.LengthSqr() > Square( SPAWNHOST_RELEVANT_DIST ) ) { //mal: this is too far away from the obj to matter(?)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//mal_TODO: will want to add a test here that checks if we prefer bodies that are in enemy territory.
|
|
|
|
vec = botWorld->playerBodies[ i ].bodyOrigin - botInfo->origin;
|
|
|
|
dist = vec.LengthFast();
|
|
|
|
if ( dist > range ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, botWorld->playerBodies[ i ].bodyOrigin, 150.0f, botInfo->team , MEDIC, false, false, false, false, true );
|
|
|
|
if ( mates >= 1 ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, botWorld->playerBodies[ i ].bodyOrigin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest body to host, and get them first
|
|
bodyNum = i;
|
|
closest = dist;
|
|
nbgTargetType = BODY; //mal: this is a body
|
|
}
|
|
}
|
|
|
|
return bodyNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_MedicCheckForDeadMate
|
|
|
|
Checks for dead teammates. Works with both gdf and strogg (since the 2 are so similiar).
|
|
If the bot is already in the process of healing/reviving someone, reviveClientNum != -1,
|
|
in which case, the bot will only look at ones that are
|
|
closer then their current NBG goal. "range" is how far bot will look to heal/revive. This will change based on what bot is
|
|
doing (if escorting important mate, or carrying OBJ, will not go as far to heal/revive). origin could be the bot's own origin,
|
|
or the origin of the important mate bot wants to cover.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_MedicCheckForDeadMate( int reviveClientNum, int escortClientNum, float range ) {
|
|
|
|
int i;
|
|
int clientNum = -1;
|
|
int busyClient;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
if ( reviveClientNum != -1 ) {
|
|
vec = botWorld->clientInfo[ reviveClientNum ].origin - botInfo->origin;
|
|
closest = vec.LengthFast();
|
|
clientNum = reviveClientNum;
|
|
} //mal: if we already have someone we're trying to revive, only revive someone else if they're closer!
|
|
|
|
if ( botInfo->team == STROGG && ( botInfo->enemiesInArea > 1 || enemy != -1 || botInfo->lastAttackerTime + 3000 > botWorld->gameLocalInfo.time ) ) {
|
|
return clientNum;
|
|
}
|
|
|
|
if ( range == 0.0f ) {
|
|
return clientNum;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue; //mal: dont try to heal ourselves!
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
//mal: some other bot has this client tagged for revive, so lets ignore this body.
|
|
//mal_NOTE: in this case, we dont care if a human medic is on route or nearby, just because experience shows most humans SUCK at being medics! ;-)
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, REVIVE_TEAMMATE, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue; //mal: give no comfort to the enemy!
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( i ) == true ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( playerInfo.xySpeed > 0.0f ) {
|
|
continue;
|
|
} //mal: ignore bodies flying thru the air!
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't revive ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
} //mal: can't revive someone not in a valid AAS area!
|
|
|
|
bool requestedHelp = false;
|
|
float tempRangeSqr = range;
|
|
|
|
if ( !playerInfo.isBot && ( playerInfo.lastChatTime[ REVIVE_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time || playerInfo.lastChatTime[ HEAL_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time ) ) {
|
|
tempRangeSqr = MEDIC_RANGE_REQUEST;
|
|
requestedHelp = true;
|
|
}
|
|
|
|
if ( ( botWorld->gameLocalInfo.gameIsBotMatch || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) && !playerInfo.isBot ) {
|
|
tempRangeSqr = MEDIC_RANGE_REQUEST * 2.0f;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( tempRangeSqr ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( escortClientNum != -1 && !requestedHelp ) {
|
|
vec = botWorld->clientInfo[ escortClientNum ].origin - playerInfo.origin;
|
|
if ( vec.LengthSqr() > Square( MEDIC_RANGE_BUSY ) ) {
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
if ( vec.LengthSqr() > Square( 500.0f ) ) {
|
|
continue; //mal: ignore this client if hes too far away from our escort client, UNLESS the client is REALLY close to us....
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( requestedHelp ) { //mal: humans asking for help get a bit of priority.
|
|
if ( botWorld->gameLocalInfo.gameIsBotMatch ) {
|
|
dist = 1.0f;
|
|
} else {
|
|
dist -= Square( 900.0f );
|
|
}
|
|
} else if ( Client_IsCriticalForCurrentObj( i, 2500.0f ) ) { //mal: give some priority to critical teammates who are close to the goal!
|
|
dist -= Square( 500.0f );
|
|
if ( dist < 0.0f ) {
|
|
dist = 1.0f;
|
|
}
|
|
} else if ( playerInfo.classType == MEDIC ) { //mal: next, give priority to medics - 2 medics are better then 1!
|
|
dist -= Square( 500.0f );
|
|
if ( dist < 0.0f ) {
|
|
dist = 5.0f; //mal: medics NEVER outrank critical teammates.
|
|
}
|
|
}
|
|
|
|
bool inVehicle = ( botVehicleInfo == NULL ) ? false : true;
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( inVehicle, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player in need, and help them first!
|
|
clientNum = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
if ( clientNum == reviveClientNum && reviveClientNum != -1 ) {
|
|
clientNum = -1; //mal: dont reset for a client we already have, but DO keep the dist checks up to date, so we know we still have best client!
|
|
}
|
|
|
|
return clientNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_LookAtLocation
|
|
|
|
Instantly points the bot towards "spot".
|
|
================
|
|
*/
|
|
void idBotAI::Bot_LookAtLocation( const idVec3 &spot, const botTurnTypes_t turnType, bool useOriginOnly ) {
|
|
if ( botVehicleInfo != NULL ) {
|
|
botUcmd->moveViewOrigin = spot; //mal: let the vehicle handle the angles to point at, just pass the vector.
|
|
botUcmd->viewType = VIEW_ORIGIN;
|
|
} else {
|
|
if ( useOriginOnly == false ) {
|
|
idVec3 origin = spot - botInfo->viewOrigin;
|
|
botUcmd->moveViewAngles = origin.ToAngles();
|
|
botUcmd->viewType = VIEW_ANGLES;
|
|
} else {
|
|
botUcmd->moveViewOrigin = spot; //mal: let the code handle how to look at this target ( most likely bot is using a grenade ).
|
|
botUcmd->viewType = VIEW_ORIGIN;
|
|
}
|
|
}
|
|
|
|
botUcmd->turnType = turnType;
|
|
|
|
if ( botThreadData.AllowDebugData() ) {
|
|
idVec3 end = spot;
|
|
end[ 2 ] += 48;
|
|
gameRenderWorld->DebugLine( colorOrange, spot, end, 48 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Flyer_LookAtLocation
|
|
================
|
|
*/
|
|
void idBotAI::Flyer_LookAtLocation( const idVec3 &spot ) {
|
|
idVec3 origin = spot - botInfo->weapInfo.covertToolInfo.origin;
|
|
botUcmd->moveViewAngles = origin.ToAngles();
|
|
|
|
if ( botThreadData.AllowDebugData() ) {
|
|
idVec3 end = spot;
|
|
end[ 2 ] += 48;
|
|
gameRenderWorld->DebugLine( colorOrange, spot, end, 48 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_LookAtEntity
|
|
================
|
|
*/
|
|
void idBotAI::Bot_LookAtEntity( int entityNum, const botTurnTypes_t turnType ) {
|
|
botUcmd->viewType = VIEW_ENTITY;
|
|
botUcmd->viewEntityNum = entityNum;
|
|
botUcmd->turnType = turnType;
|
|
botUcmd->moveViewAngles = ang_zero;
|
|
botUcmd->moveViewOrigin = vec3_zero;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_LookAtNothing
|
|
================
|
|
*/
|
|
void idBotAI::Bot_LookAtNothing( const botTurnTypes_t turnType ) {
|
|
botUcmd->viewType = VIEW_MOVEMENT;
|
|
botUcmd->turnType = turnType;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::InFrontOfClient
|
|
================
|
|
*/
|
|
bool idBotAI::InFrontOfClient( int clientNum, const idVec3 &origin, bool precise, float preciseValue ) {
|
|
float dotCheck = ( precise == true ) ? preciseValue : 0.0f;
|
|
idVec3 dir = origin - botWorld->clientInfo[ clientNum ].origin;
|
|
|
|
if ( precise ) {
|
|
dir.NormalizeFast();
|
|
}
|
|
|
|
if ( dir * botWorld->clientInfo[ clientNum ].viewAxis[ 0 ] > dotCheck ) {
|
|
return true;//mal: have someone in front of us..
|
|
} //mal: since we're only calculating for 90 degrees, we don't need to normalize the dir.
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ClientIsIgnored
|
|
|
|
Is this client being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::ClientIsIgnored( int clientNum ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreClients[ i ].num != clientNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreClients[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::EnemyIsIgnored
|
|
|
|
Is this ( potential ) enemy being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::EnemyIsIgnored( int clientNum ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreEnemies[ i ].num != clientNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreEnemies[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::VehicleIsIgnored
|
|
|
|
Is this vehicle being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::VehicleIsIgnored( int vehicleNum ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreVehicles[ i ].num != vehicleNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreVehicles[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreVehicle
|
|
|
|
Setups a vehicle to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreVehicle( int vehicleNum, int time ) {
|
|
|
|
bool hasSlot = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreVehicles[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreVehicles[ i ].num = vehicleNum;
|
|
ignoreVehicles[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreVehicles[ 0 ].num = vehicleNum;
|
|
ignoreVehicles[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring vehicle %i for %i msecs", vehicleNum, time );
|
|
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Vehicle Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::DeployableIsIgnored
|
|
|
|
Is this deployable being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::DeployableIsIgnored( int deployableNum ) {
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreDeployables[ i ].num != deployableNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreDeployables[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreDeployable
|
|
|
|
Setups a deployable to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreDeployable( int deployableNum, int time ) {
|
|
bool hasSlot = false;
|
|
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreDeployables[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreDeployables[ i ].num = deployableNum;
|
|
ignoreDeployables[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreDeployables[ 0 ].num = deployableNum;
|
|
ignoreDeployables[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring deployable %i for %i msecs", deployableNum, time );
|
|
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Deployable Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::BodyIsIgnored
|
|
|
|
Is this dead body being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::BodyIsIgnored( int bodyNum ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreBodies[ i ].num != bodyNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreBodies[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::SpawnHostIsIgnored
|
|
|
|
Is this spawn host being ignored?
|
|
================
|
|
*/
|
|
bool idBotAI::SpawnHostIsIgnored( int bodyNum ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreSpawnHosts[ i ].num != bodyNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreSpawnHosts[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreClient
|
|
|
|
Setups a client to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreClient( int clientNum, int time ) {
|
|
|
|
if ( botWorld->botGoalInfo.isTrainingMap && botThreadData.actorMissionInfo.targetClientNum == clientNum ) { //mal: NEVER ignore the player!
|
|
return;
|
|
}
|
|
|
|
bool hasSlot = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreClients[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreClients[ i ].num = clientNum;
|
|
ignoreClients[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreClients[ 0 ].num = clientNum;
|
|
ignoreClients[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring client %i for %i msecs", clientNum, time );
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Client Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreEnemy
|
|
|
|
Setups an enemy to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreEnemy( int clientNum, int time ) {
|
|
|
|
bool hasSlot = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreEnemies[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreEnemies[ i ].num = clientNum;
|
|
ignoreEnemies[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreEnemies[ 0 ].num = clientNum;
|
|
ignoreEnemies[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring client %i for %i msecs", clientNum, time );
|
|
if ( hasSlot == false) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Enemy Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreBody
|
|
|
|
Setups a body to be ignored - only used by meds and coverts of both teams.
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreBody( int bodyNum, int time ) {
|
|
|
|
bool hasSlot = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreBodies[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreBodies[ i ].num = bodyNum;
|
|
ignoreBodies[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreBodies[ 0 ].num = bodyNum;
|
|
ignoreBodies[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring body %i for %i msecs", bodyNum, time );
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Body Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreSpawnHost
|
|
|
|
Setups a spawnhost to be ignored - only used by meds on GDF.
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreSpawnHost( int bodyNum, int time ) {
|
|
|
|
bool hasSlot = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreSpawnHosts[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreSpawnHosts[ i ].num = bodyNum;
|
|
ignoreSpawnHosts[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreSpawnHosts[ 0 ].num = bodyNum;
|
|
ignoreSpawnHosts[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring body %i for %i msecs", bodyNum, time );
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Body Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_RandomLook
|
|
|
|
Randomly picks a spot to look at - will test to see if that the spot is a good one (so they wont be looking at a wall)
|
|
For any function that calls this, it is important to call ResetRandomLook() first, so that the random look range can
|
|
be reset.
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_RandomLook( idVec3 &position, bool ignoreDelay ) {
|
|
int turnCheck = 0;
|
|
float yaw;
|
|
idAngles ang = botInfo->viewAngles;
|
|
idVec3 end, forward;
|
|
|
|
if ( randomLookDelay > botWorld->gameLocalInfo.time && !ignoreDelay ) {
|
|
return false;
|
|
}
|
|
|
|
while( turnCheck < 4 ) {
|
|
|
|
turnCheck++;
|
|
|
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
yaw = 45.0f;
|
|
} else {
|
|
yaw = 90.0f;
|
|
}
|
|
|
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
yaw *= -1.0f;
|
|
}
|
|
|
|
ang[ PITCH ] = 0; //mal: zero this out, in case bot is looking up, down, etc.
|
|
ang[ YAW ] += yaw;
|
|
|
|
ang.ToVectors( &forward, NULL, NULL );
|
|
|
|
end = botInfo->viewOrigin;
|
|
|
|
end += ( randomLookRange * forward );
|
|
|
|
if ( end == randomLookOrg ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.AllowDebugData() ) {
|
|
idVec3 test = end;
|
|
test[ 2 ] += 24;
|
|
gameRenderWorld->DebugLine( colorDkRed, end, test, 2500 );
|
|
}
|
|
|
|
if ( botThreadData.Nav_IsDirectPath( AAS_PLAYER, botInfo->team, botInfo->areaNum, botInfo->aasOrigin, end ) ) {
|
|
position = end;
|
|
randomLookOrg = end;
|
|
randomLookDelay = botWorld->gameLocalInfo.time + 700; //mal: dont come thru here again for a little while at least.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
randomLookRange -= 200.0f; //mal: shorten the look range a bit, so that we can try again next time ( in case we're in a tight area ).
|
|
|
|
if ( randomLookRange < 100.0f ) {
|
|
ResetRandomLook();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_ClearAngleMods
|
|
|
|
Clears out angle modifiers that could interfere with medics doing their job.
|
|
Medics are the only class that will really use this one, since they can do several tasks at once.
|
|
================
|
|
*/
|
|
void idBotAI::Bot_ClearAngleMods() {
|
|
botUcmd->botCmds.lookDown = false;
|
|
botUcmd->botCmds.lookUp = false;
|
|
botUcmd->botCmds.throwNade = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_ExitAINode
|
|
================
|
|
*/
|
|
void idBotAI::Bot_ExitAINode() {
|
|
bool resetAIStack = ( aiState == LTG ) ? true : false;
|
|
lastActionNum = actionNum;
|
|
Bot_ResetState( false, resetAIStack );
|
|
ResetRandomLook();
|
|
botIdealWeapSlot = GUN;
|
|
botIdealWeapNum = NULL_WEAP;
|
|
|
|
lastAINode = "Exiting AI Node";
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckSelfSupply
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckSelfSupply() {
|
|
|
|
if ( botInfo->onLadder ) {
|
|
return false;
|
|
}
|
|
|
|
if ( selfShockTime > botWorld->gameLocalInfo.time ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->isActor && ltgPauseTime > botWorld->gameLocalInfo.time ) { //mal: dont heal ourselves if we're briefing the player
|
|
return false;
|
|
}
|
|
|
|
if ( !botInfo->inPlayZone ) {
|
|
return false;
|
|
}
|
|
|
|
bool supplySelf = false;
|
|
bool safeToExit = ( supplySelfTime < botWorld->gameLocalInfo.time ) ? true : false;
|
|
bool hasCharge = ( botInfo->classType == MEDIC ) ? ClassWeaponCharged( HEALTH ) : ClassWeaponCharged( AMMO_PACK );
|
|
weaponBankTypes_t weapon = ( aiState == LTG || ( aiState == NBG && ( nbgType == CAMP || nbgType == DEFENSE_CAMP ) ) || botInfo->classType == FIELDOPS ) ? GUN : NO_WEAPON;
|
|
|
|
if ( ignoreWeapChange ) {
|
|
weapon = botIdealWeapSlot;
|
|
}
|
|
|
|
if ( pistolTime > botWorld->gameLocalInfo.time || ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time && !botInfo->revived ) && botThreadData.GetBotSkill() > BOT_SKILL_EASY ) {
|
|
if ( botInfo->isDisguised ) {
|
|
weapon = MELEE;
|
|
} else {
|
|
weapon = SIDEARM;
|
|
}
|
|
}
|
|
|
|
if ( !hasCharge && safeToExit ) {
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP && nbgType != REVIVE_TEAMMATE && nbgType != SUPPLY_TEAMMATE && nbgType != CREATE_SPAWNHOST && nbgType != GRAB_SUPPLIES && nbgType != BUG_FOR_SUPPLIES && nbgType != AVOID_DANGER && nbgType != DESTROY_DANGER ) ) {
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
|
|
if ( classAbilityDelay > botWorld->gameLocalInfo.time ) {
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
|
|
if ( enemy != -1 && ( aiState == LTG || aiState == NBG )) { //mal: dont do it if the bot has an enemy hes aware of.
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
|
|
int idealHealth = ( aiState == LTG || aiState == COMBAT ) ? botInfo->maxHealth : 60;
|
|
|
|
if ( aiState == NBG && ( nbgType == CAMP || nbgType == DEFENSE_CAMP ) ) {
|
|
idealHealth = botInfo->maxHealth;
|
|
}
|
|
|
|
if ( botInfo->team == STROGG ) {
|
|
#ifdef PACKS_HAVE_NO_NADES
|
|
if ( botInfo->health >= idealHealth && botInfo->weapInfo.primaryWeapNeedsAmmo == false ) {
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
#else
|
|
if ( botInfo->health >= idealHealth && botInfo->weapInfo.primaryWeapNeedsAmmo == false && botInfo->weapInfo.hasNadeAmmo ) { //mal: may be rearming just for the nades
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
#endif
|
|
} else {
|
|
if ( botInfo->classType == MEDIC ) {
|
|
if ( botInfo->health >= idealHealth ) {
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
} else {
|
|
#ifdef PACKS_HAVE_NO_NADES
|
|
if ( botInfo->weapInfo.primaryWeapNeedsAmmo == false ) {
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
#else
|
|
if ( botInfo->weapInfo.primaryWeapNeedsAmmo == false && botInfo->weapInfo.hasNadeAmmo ) { //mal: may be rearming just for the nades
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ( botInfo->xySpeed > 0.0f ) { // was 100.0f
|
|
supplySelf = true;
|
|
botUcmd->botCmds.lookDown = true;
|
|
} else if ( botInfo->xySpeed == 0.0f ) {
|
|
supplySelf = true;
|
|
botUcmd->botCmds.lookUp = true;
|
|
}
|
|
|
|
if ( !supplySelf ) {
|
|
classAbilityDelay = botWorld->gameLocalInfo.time + 5000; // dont let the bot try this again for a while, if something odd is going on to prevent them from doing this!
|
|
supplySelfTime = 0;
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
}
|
|
|
|
if ( !hasCharge ) {
|
|
botIdealWeapSlot = weapon;
|
|
return false;
|
|
} else {
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( botInfo->classType == FIELDOPS ) {
|
|
botIdealWeapNum = AMMO_PACK;
|
|
} else {
|
|
botIdealWeapNum = HEALTH;
|
|
}
|
|
}
|
|
|
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + 500; //mal: dont bother to try to strafe jump if the bot wants to resupply himself!
|
|
|
|
supplySelfTime = botWorld->gameLocalInfo.time + 1200; //mal: give ourselves a chance to actually give that pack - just in case we ran out of charge with this one.
|
|
|
|
if ( isStrafeJumping != false ) { //mal: if we're already strafejumping, knock it off, and let us supply ourselves!
|
|
isStrafeJumping = false;
|
|
botUcmd->moveFlag = RUN;
|
|
}
|
|
|
|
if ( botInfo->weapInfo.isReady && ( botInfo->weapInfo.weapon == HEALTH || botInfo->weapInfo.weapon == AMMO_PACK ) ) {
|
|
botUcmd->botCmds.launchPacks = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_NBGIsAvailable
|
|
|
|
Checks to see if theres another bot out there that has this short term goal (heal, spawnhost, revive, etc)
|
|
If so, we'll do something else. For humans, will do a proximity check.
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_NBGIsAvailable( int clientNum, int actionNumber, const bot_NBG_Types_t goalType, int &busyClient ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) { //mal: dont scan ourselves.
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) { //mal: coverts ignore meds on enemy team who may try to heal them while they're disguised!
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isBot != true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() != NBG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( clientNum != -1 ) {
|
|
if ( botThreadData.bots[ i ]->GetNBGTarget() != clientNum ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( actionNumber != ACTION_NULL ) {
|
|
if ( botThreadData.bots[ i ]->GetActionNum() != actionNumber ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetNBGType() != goalType ) {
|
|
continue;
|
|
}
|
|
|
|
busyClient = i;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_LTGIsAvailable
|
|
|
|
Checks to see if theres another bot out there that has this long term goal.
|
|
If so, we'll do something else. For humans, will do a proximity check.
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_LTGIsAvailable( int clientNum, int actionNumber, const bot_LTG_Types_t goalType, int minNumClients ) {
|
|
|
|
int i;
|
|
int numClients = 0;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) { //mal: dont scan ourselves.
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isBot != true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() != LTG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetLTGType() != goalType ) {
|
|
continue;
|
|
}
|
|
|
|
if ( clientNum != -1 ) {
|
|
if ( botThreadData.bots[ i ]->GetLTGTarget() != clientNum ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( actionNumber != ACTION_NULL ) {
|
|
if ( botThreadData.bots[ i ]->GetActionNum() != actionNumber ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
numClients++;
|
|
|
|
if ( minNumClients == 1 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( numClients >= minNumClients ) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_NumClientsDoingLTGGoal
|
|
|
|
Checks to see how many bots are doing a particular LTG.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_NumClientsDoingLTGGoal( int clientNum, int actionNumber, const bot_LTG_Types_t goalType, idList< int >& busyClients ) {
|
|
|
|
int numClients = 0;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) { //mal: dont scan ourselves.
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isBot != true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() != LTG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetLTGType() != goalType ) {
|
|
continue;
|
|
}
|
|
|
|
if ( clientNum != -1 ) {
|
|
if ( botThreadData.bots[ i ]->GetLTGTarget() != clientNum ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( actionNumber != ACTION_NULL ) {
|
|
if ( botThreadData.bots[ i ]->GetActionNum() != actionNumber ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
numClients++;
|
|
busyClients.Append( i );
|
|
}
|
|
|
|
return numClients;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckWeapon
|
|
================
|
|
*/
|
|
void idBotAI::Bot_CheckWeapon() {
|
|
if ( botInfo->weapInfo.covertToolInfo.entNum != 0 && botInfo->team == STROGG ) {
|
|
return;
|
|
}
|
|
|
|
if ( ignoreWeapChange ) {
|
|
return;
|
|
}
|
|
|
|
botUcmd->botCmds.altAttackOff = true;
|
|
|
|
if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) {
|
|
botIdealWeapSlot = GUN;
|
|
}
|
|
|
|
if ( pistolTime > botWorld->gameLocalInfo.time && botThreadData.GetBotSkill() > BOT_SKILL_EASY && !NeedsReload() ) {
|
|
if ( botInfo->isDisguised ) {
|
|
botIdealWeapSlot = MELEE;
|
|
} else {
|
|
botIdealWeapSlot = SIDEARM;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) { //mal: just use the default weapon for the class we look like.
|
|
if ( botIdealWeapSlot != MELEE ) {
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( botInfo->classType == SOLDIER && ( botInfo->weapInfo.primaryWeapon == ROCKET || botInfo->weapInfo.primaryWeapon == HEAVY_MG ) && !NeedsReload() ) {
|
|
botIdealWeapSlot = SIDEARM;
|
|
} else {
|
|
botIdealWeapSlot = GUN;
|
|
}
|
|
|
|
botIdealWeapNum = NULL_WEAP;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CovertCheckForUniforms
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CovertCheckForUniforms( float range ) {
|
|
|
|
int i;
|
|
int bodyNum = -1;
|
|
int mates;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
nbgTargetType = NOTYPE;
|
|
int busyClient = -1;
|
|
|
|
if ( botInfo->isDisguised ) { //mal: already disguised, dont need to do this anymore.
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( !botWorld->gameLocalInfo.botsUseUniforms ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
//mal: this is an especially dangerous thing to do - if there are lots of enemies around, DONT steal uniforms.
|
|
if ( botInfo->enemiesInArea > 2 ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
//mal: first, check for wounded clients
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i )) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( LocationDistFromCurrentObj( botInfo->team, playerInfo.origin ) < BODY_IGNORE_RANGE && Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) {
|
|
continue;
|
|
} //mal: if body is close to our current hack goal, then ignore it - just go hack!
|
|
|
|
if ( playerInfo.xySpeed > 0.0f ) {
|
|
continue;
|
|
} //mal: ignore bodies flying thru the air!
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't steal from ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthFast();
|
|
|
|
if ( dist > range ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, STEAL_UNIFORM, busyClient )) {
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, playerInfo.origin, 150.0f, botInfo->team , COVERTOPS, false, false, false, true, true );
|
|
|
|
if ( mates >= 1 ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player
|
|
bodyNum = i;
|
|
closest = dist;
|
|
nbgTargetType = CLIENT; //mal: this is a client
|
|
}
|
|
}
|
|
|
|
//mal: now, check for bodies out there, and see if its closer....
|
|
for ( i = 0; i < MAX_PLAYERBODIES; i++ ) {
|
|
|
|
if ( !botWorld->playerBodies[ i ].isValid ) {
|
|
continue; //mal: no body in this client slot!
|
|
}
|
|
|
|
if ( BodyIsIgnored( i )) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].bodyTeam == botInfo->team ) {
|
|
continue; //mal: dont try to steal from our team
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( LocationDistFromCurrentObj( botInfo->team, botWorld->playerBodies[ i ].bodyOrigin ) < BODY_IGNORE_RANGE && Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) {
|
|
continue;
|
|
} //mal: if body is close to our current hack goal, then ignore it - just go hack!
|
|
|
|
if ( botWorld->playerBodies[ i ].bodyTeam == GDF ) {
|
|
if ( !botWorld->playerBodies[ i ].isSpawnHostAble ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( botWorld->playerBodies[ i ].uniformStolen ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = botWorld->playerBodies[ i ].bodyOrigin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( range ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, botWorld->playerBodies[ i ].bodyOrigin, 150.0f, botInfo->team , COVERTOPS, false, false, false, true, true );
|
|
|
|
if ( mates >= 1 ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, botWorld->playerBodies[ i ].bodyOrigin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
bodyNum = i;
|
|
closest = dist;
|
|
nbgTargetType = BODY; //mal: this is a body
|
|
}
|
|
}
|
|
|
|
return bodyNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CovertCheckForVictims
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CovertCheckForVictims( float range ) {
|
|
int i;
|
|
int victim = -1;
|
|
int mates;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
int busyClient = -1;
|
|
|
|
if ( !botInfo->isDisguised ) { //mal: not disguised, cant do this anymore.
|
|
return victim;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return victim;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !playerInfo.isBot ) { //mal: the bots won't backstab you in training mode, too confusing.
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isNoTarget ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->team == GDF ) {
|
|
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_EXPERT ) { //mal: high skill bots are more likely to just backstab anyone they can....
|
|
if ( playerInfo.classType != botWorld->botGoalInfo.team_STROGG_criticalClass && !ClientIsDangerous( i ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_EXPERT ) {
|
|
if ( playerInfo.classType != botWorld->botGoalInfo.team_GDF_criticalClass && !ClientIsDangerous( i ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( botInfo->disguisedClient == i ) { //mal: dont do this to the guy we stole the uniform from, he'll get wise.
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isDisguised ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isActor ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( range ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.friendsInArea > MAX_NUM_OF_FRIENDS_OF_VICTIM ) { //mal: this guys got a lot of friends around him - prolly a better idea to try something else....
|
|
return victim;
|
|
}
|
|
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, STALK_VICTIM, busyClient )) { //mal: some other bot is doing the same thing, so do something else.
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, playerInfo.origin, 500.0f, botInfo->team , COVERTOPS, false, false, false, false, true );
|
|
|
|
if ( mates >= 1 ) { //mal: someone else MAY already have the same idea, so do something else just in case!
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player to knife in the back!
|
|
victim = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return victim;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckHasVisibleMedicNearby
|
|
|
|
Matches the functionality of code in idPlayer::CalculateRenderView, to lock the player's view on a nearby medic
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckHasVisibleMedicNearby() {
|
|
|
|
int i;
|
|
trace_t trace;
|
|
idVec3 vec;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.classType != MEDIC ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 1024.0f ) ) {
|
|
continue;
|
|
}
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS trace, playerInfo.viewOrigin, botInfo->viewOrigin, CONTENTS_SOLID, GetGameEntity( botNum ) );
|
|
|
|
if ( trace.fraction >= 1.0f ){
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::LocationVis2Sky
|
|
==================
|
|
*/
|
|
bool idBotAI::LocationVis2Sky( const idVec3 &loc ) {
|
|
int areaNum = botThreadData.Nav_GetAreaNum( ( botVehicleInfo != NULL ) ? AAS_VEHICLE : AAS_PLAYER, loc );
|
|
|
|
if ( areaNum > 0 ) {
|
|
if ( botAAS.aas->GetAreaFlags( areaNum ) & AAS_AREA_OUTSIDE ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::LocationHasHeadRoom
|
|
==================
|
|
*/
|
|
bool idBotAI::LocationHasHeadRoom( const idVec3 &loc ) {
|
|
int areaNum = botThreadData.Nav_GetAreaNum( ( botVehicleInfo != NULL ) ? AAS_VEHICLE : AAS_PLAYER, loc );
|
|
|
|
if ( areaNum > 0 ) {
|
|
if ( botAAS.aas->GetAreaFlags( areaNum ) & AAS_AREA_HIGH_CEILING ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClientIsValid
|
|
==================
|
|
*/
|
|
bool idBotAI::ClientIsValid( int clientNum, int spawnID ) {
|
|
|
|
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { //mal: safety check!
|
|
return false;
|
|
}
|
|
|
|
if ( spawnID != -1 ) {
|
|
if ( botWorld->clientInfo[ clientNum ].spawnID != spawnID ) {
|
|
assert( false ); //mal: this is a temp check to test new code. //mal_TODO: REMOVE this!
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ clientNum ].inGame == false || botWorld->clientInfo[ clientNum ].team == NOTEAM ) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClientHasChargeInWorld
|
|
==================
|
|
*/
|
|
bool idBotAI::ClientHasChargeInWorld( int clientNum, bool armedOnly, int actionNumber, bool unArmedOnly ) {
|
|
|
|
bool hasBomb = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENT_CHARGES; i++ ) {
|
|
if ( botWorld->chargeInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->chargeInfo[ i ].ownerSpawnID != botWorld->clientInfo[ clientNum ].spawnID ) {
|
|
continue;
|
|
}
|
|
|
|
if ( armedOnly ) {
|
|
if ( botWorld->chargeInfo[ i ].state != BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( unArmedOnly ) {
|
|
if ( botWorld->chargeInfo[ i ].state == BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( actionNumber != ACTION_NULL ) {
|
|
if ( botThreadData.botActions[ actionNumber ]->EntityIsInsideActionBBox( botWorld->chargeInfo[ i ].entNum, PLANTED_CHARGE ) == false ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasBomb = true;
|
|
break;
|
|
}
|
|
|
|
return hasBomb;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::FindChargeInWorld
|
|
|
|
Look for a bomb out there to arm/disarm.
|
|
==================
|
|
*/
|
|
bool idBotAI::FindChargeInWorld( int actionNumber, plantedChargeInfo_t& bombInfo, bool chargeState, bool ignoreZCheck ) {
|
|
float closest = idMath::INFINITY;
|
|
bombInfo.entNum = 0; //mal: clear this out, so we can tell if we got one or not later.
|
|
playerClassTypes_t bombClassType = ( chargeState != ARM ) ? ENGINEER : SOLDIER;
|
|
|
|
for( int i = 0; i < MAX_CLIENT_CHARGES; i++ ) {
|
|
const plantedChargeInfo_t& charge = botWorld->chargeInfo[ i ];
|
|
|
|
if ( charge.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.team != botInfo->team && chargeState == ARM ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.team == botInfo->team && chargeState == DISARM ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.state == BOMB_NULL && chargeState == DISARM ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.state == BOMB_ARMED && chargeState == ARM ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = charge.origin - botInfo->origin;
|
|
|
|
float dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( 1500.0f ) ) { //mal: this bomb is too far away from us.
|
|
continue;
|
|
}
|
|
|
|
if ( chargeState == ARM ) {
|
|
if ( !ignoreZCheck ) {
|
|
if ( vec.z > 80.0f || vec.z < -80.0f ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if ( actionNumber != ACTION_NULL ) {
|
|
// if ( !botThreadData.botActions[ actionNumber ]->actionBBox.ContainsPoint( charge.origin ) ) {
|
|
// continue;
|
|
// }
|
|
// }
|
|
|
|
int matesInArea = ClientsInArea( botNum, charge.origin, PLIERS_RANGE, botInfo->team, bombClassType, false, false, false, false, true );
|
|
|
|
if ( matesInArea > 1 ) {
|
|
if ( chargeState == ARM && charge.ownerSpawnID != botInfo->spawnID ) { //mal: dont cluster around bombs, DO look for bombs that may have been dropped/planted, and the player died/left before arming/disarming.
|
|
continue;
|
|
} else if ( chargeState == DISARM && Bot_CheckIfClientHasChargeAsGoal( charge.entNum ) ) { //mal: dont ignore a bomb with lots of clients around, if noones disarming it.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest bomb, whether its ours or not.
|
|
bombInfo = charge;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return ( bombInfo.entNum != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreAction
|
|
|
|
Setups an action to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreAction( int actionNumber, int time ) {
|
|
bool hasSlot = false;
|
|
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreActions[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreActions[ i ].num = actionNumber;
|
|
ignoreActions[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreActions[ 0 ].num = actionNumber;
|
|
ignoreActions[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring Action %i for %i msecs", actionNumber, time );
|
|
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Action Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ActionIsIgnored
|
|
|
|
Is this action being ignored for some reason?
|
|
================
|
|
*/
|
|
bool idBotAI::ActionIsIgnored( int actionNumber ) {
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreActions[ i ].num != actionNumber ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreActions[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IgnoreItem
|
|
|
|
Setups an item to be ignored
|
|
================
|
|
*/
|
|
void idBotAI::Bot_IgnoreItem( int itemNumber, int time ) {
|
|
bool hasSlot = false;
|
|
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
if ( ignoreItems[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
ignoreItems[ i ].num = itemNumber;
|
|
ignoreItems[ i ].time = botWorld->gameLocalInfo.time + time;
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasSlot == false ) { //mal: if can't find a free slot ( shouldn't happen ), then just use the first slot.
|
|
ignoreItems[ 0 ].num = itemNumber;
|
|
ignoreItems[ 0 ].time = botWorld->gameLocalInfo.time + time;
|
|
}
|
|
|
|
botThreadData.Warning( "Alert! Ignoring Item %i for %i msecs", itemNumber, time );
|
|
|
|
if ( hasSlot == false ) {
|
|
botThreadData.Warning( "Alert! Ran out of free Ignore Item Slots! Using the first one!" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ItemIsIgnored
|
|
|
|
Is this item being ignored for some reason?
|
|
================
|
|
*/
|
|
bool idBotAI::ItemIsIgnored( int itemNumber ) {
|
|
for( int i = 0; i < MAX_IGNORE_ENTITIES; i++ ) {
|
|
|
|
if ( ignoreItems[ i ].num != itemNumber ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ignoreItems[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::CheckItemPackIsValid
|
|
|
|
Is our item pack still valid?
|
|
================
|
|
*/
|
|
bool idBotAI::CheckItemPackIsValid( int entNum, idVec3 &packOrg, idBounds &entBounds, int& spawnID ) {
|
|
|
|
bool foundPack = false;
|
|
int i, j;
|
|
|
|
for( j = 0; j < MAX_CLIENTS; j++ ) {
|
|
|
|
if ( foundPack ) {
|
|
break;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ j ].supplyCrate.entNum == entNum ) { //mal: our target may be a supply crate - so check the player's crates
|
|
packOrg = botWorld->clientInfo[ j ].supplyCrate.origin;
|
|
entBounds = botWorld->clientInfo[ j ].supplyCrate.bbox;
|
|
spawnID = botWorld->clientInfo[ j ].supplyCrate.spawnID;
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_ITEMS; i++ ) {
|
|
|
|
if ( botWorld->clientInfo[ j ].packs[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
packOrg = botWorld->clientInfo[ j ].packs[ i ].origin;
|
|
packOrg.z += ITEM_PACK_OFFSET;
|
|
spawnID = botWorld->clientInfo[ j ].packs[ i ].spawnID;
|
|
foundPack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return foundPack;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckForNeedyTeammates
|
|
|
|
Checks for teammates who need ammo.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CheckForNeedyTeammates( float range ) {
|
|
int i;
|
|
int clientNum = -1;
|
|
int busyClient;
|
|
int matesInArea;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
//mal: dont bother doing this if bot has no charge
|
|
if ( !ClassWeaponCharged( AMMO_PACK ) ) {
|
|
botIdealWeapSlot = GUN;
|
|
return clientNum;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return clientNum;
|
|
}
|
|
|
|
if ( botInfo->inWater ) {
|
|
return clientNum;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue; //mal: dont try to rearm ourselves!
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i )) {
|
|
continue;
|
|
}
|
|
|
|
//mal: some bot has this client tagging for healing.
|
|
//mal_NOTE: in this case, we dont care if a human FOps is on route or nearby, just because experience shows most humans SUCK at giving ammo! ;-)
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, SUPPLY_TEAMMATE, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isNoTarget ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->team == GDF ) {
|
|
if ( playerInfo.classType == FIELDOPS ) {
|
|
continue;
|
|
} //mal: if player is a Fops, ignore, they can rearm themselves!
|
|
} else {
|
|
if ( playerInfo.classType == MEDIC ) { //mal: the same for medics on the strogg team!
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team && playerInfo.isDisguised == false ) {
|
|
continue; //mal: give no comfort to the enemy! can be tricked by disguised enemy.
|
|
}
|
|
|
|
if ( !playerInfo.weapInfo.primaryWeapNeedsAmmo && playerInfo.lastChatTime[ REARM_ME ] + 5000 < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.inWater ) {
|
|
continue; //mal: can't rearm ppl who are in water!
|
|
}
|
|
|
|
if ( playerInfo.health <= 0 ) { //mal: dont bother if client is dead.
|
|
continue;
|
|
}
|
|
|
|
if ( !playerInfo.hasGroundContact && !playerInfo.hasJumped ) {
|
|
continue;
|
|
} //mal: ignore ppl flying thru the air (from explosion, dropping from airplane, etc).
|
|
|
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
continue; //mal: ignore players in a vehicle/deployable.
|
|
}
|
|
|
|
if ( playerInfo.areaNum == 0 ) {
|
|
continue;
|
|
} //mal: don't bother with ppl who aren't in a valid AAS area!
|
|
|
|
matesInArea = ClientsInArea( botNum, playerInfo.origin, 150.0f, botInfo->team, ( botInfo->classType == MEDIC ) ? MEDIC : FIELDOPS, false, false, false, false, true );
|
|
|
|
if ( matesInArea > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthFast();
|
|
|
|
bool requestedHelp = false;
|
|
float tempRange = range;
|
|
|
|
if ( !playerInfo.isBot && ( botWorld->gameLocalInfo.gameIsBotMatch || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) ) {
|
|
tempRange *= 2.0f;
|
|
}
|
|
|
|
if ( !playerInfo.isBot && ( playerInfo.lastChatTime[ REVIVE_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time || playerInfo.lastChatTime[ HEAL_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time || playerInfo.lastChatTime[ REARM_ME ] + MEDIC_REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time ) ) {
|
|
tempRange = MEDIC_RANGE_REQUEST;
|
|
requestedHelp = true;
|
|
}
|
|
|
|
if ( requestedHelp ) {
|
|
dist -= 900.0f;
|
|
}
|
|
|
|
if ( dist > tempRange ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player in need, and help them first!
|
|
clientNum = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return clientNum;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idBotAI::Client_IsCriticalForCurrentObj
|
|
|
|
Is this client critical to completing our current obj?
|
|
If range != -1, will only consider client critical if hes within range of the goal.
|
|
================
|
|
*/
|
|
bool idBotAI::Client_IsCriticalForCurrentObj( int clientNum, float range ) {
|
|
int botActionNum;
|
|
botActionGoals_t actionGoal;
|
|
botActionStates_t actionState;
|
|
idVec3 vec;
|
|
|
|
if ( !ClientIsValid( clientNum, -1 ) ) {
|
|
return false;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ clientNum ];
|
|
|
|
if ( botWorld->gameLocalInfo.heroMode != false ) { //mal: the human has decided to complete the maps goals.
|
|
if ( playerInfo.isBot != false && TeamHasHuman( botInfo->team ) ) {
|
|
return false;
|
|
}
|
|
} //mal: as long as there is at least one human on their team, bots are NEVER considered critical in hero mode.
|
|
|
|
if ( playerInfo.classType == MEDIC || playerInfo.classType == FIELDOPS ) { //mal: there currently are no medic/Fops specific objs.
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->classType == ENGINEER && botWorld->botGoalInfo.mapHasMCPGoal ) {
|
|
if ( range != -1.0f ) {
|
|
proxyInfo_t mcp;
|
|
GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcp );
|
|
|
|
if ( mcp.entNum != 0 ) {
|
|
idVec3 vec = mcp.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() < Square( range ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//mal: not sure about this below - causes a lot of issues where the a bot thats not in a critical situation, ignores combat when it really shouldn't.
|
|
/*
|
|
|
|
if ( botInfo->team == GDF ) {
|
|
if ( botWorld->botGoalInfo.team_GDF_criticalClass == botInfo->classType ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if ( botWorld->botGoalInfo.team_STROGG_criticalClass == botInfo->classType ) {
|
|
return true;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if ( playerInfo.team == GDF ) {
|
|
botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction;
|
|
|
|
if ( botActionNum == -1 || botActionNum > botThreadData.botActions.Num() ) {
|
|
return false;
|
|
}
|
|
|
|
actionGoal = botThreadData.botActions[ botActionNum ]->GetHumanObj();
|
|
} else {
|
|
botActionNum = botWorld->botGoalInfo.team_STROGG_PrimaryAction;
|
|
|
|
if ( botActionNum == -1 || botActionNum > botThreadData.botActions.Num() ) {
|
|
return false;
|
|
}
|
|
|
|
actionGoal = botThreadData.botActions[ botActionNum ]->GetStroggObj();
|
|
}
|
|
|
|
actionState = botThreadData.botActions[ botActionNum ]->GetActionState();
|
|
|
|
if ( range != -1.0f ) {
|
|
vec = botThreadData.botActions[ botActionNum ]->GetActionOrigin() - playerInfo.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( actionGoal == ACTION_HE_CHARGE && playerInfo.classType == SOLDIER && !ClientHasChargeInWorld( clientNum, true, ACTION_NULL ) ) {
|
|
return true;
|
|
} else if ( actionGoal == ACTION_DEFUSE && ( actionState == ACTION_STATE_PLANTED || Bot_CheckChargeExistsOnObjInWorld() ) && playerInfo.classType == ENGINEER ) {
|
|
return true;
|
|
} else if ( actionGoal == ACTION_MAJOR_OBJ_BUILD && playerInfo.classType == ENGINEER ) {
|
|
return true;
|
|
} else if ( actionGoal == ACTION_HACK && playerInfo.classType == COVERTOPS ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::FindBodyOfClient
|
|
================
|
|
*/
|
|
int idBotAI::FindBodyOfClient( int clientNum, bool stealUniform, const idVec3 &org ) {
|
|
|
|
int i;
|
|
int bodyNum = -1;
|
|
idVec3 vec;
|
|
|
|
//mal: look for the body belonging to clientNum.
|
|
for ( i = 0; i < MAX_PLAYERBODIES; i++ ) {
|
|
|
|
if ( !botWorld->playerBodies[ i ].isValid ) {
|
|
continue; //mal: no body in this client slot!
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].bodyOwnerClientNum != clientNum ) {
|
|
continue; //mal: dont try to steal a diff body
|
|
}
|
|
|
|
vec = botWorld->playerBodies[ i ].bodyOrigin - org;
|
|
|
|
if ( vec.LengthSqr() > Square( 100.0f ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( stealUniform == true ) {
|
|
if ( botWorld->playerBodies[ i ].uniformStolen == true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->playerBodies[ i ].isSpawnHost == true ) {
|
|
continue;
|
|
}
|
|
} else { //mal: else, we're trying to spawnhost the body, make sure its not already spawnhosted!
|
|
if ( botWorld->playerBodies[ i ].isSpawnHostAble == false ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bodyNum = i;
|
|
break;
|
|
}
|
|
|
|
return bodyNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::LocationDistFromCurrentObj
|
|
================
|
|
*/
|
|
float idBotAI::LocationDistFromCurrentObj( const playerTeamTypes_t team, const idVec3 &org ) {
|
|
|
|
int botActionNum;
|
|
botActionGoals_t actionGoal;
|
|
idVec3 vec;
|
|
|
|
if ( team == GDF ) {
|
|
botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction;
|
|
|
|
if ( botActionNum == -1 ) {
|
|
return idMath::INFINITY;
|
|
}
|
|
|
|
actionGoal = botThreadData.botActions[ botActionNum ]->GetHumanObj();
|
|
} else {
|
|
botActionNum = botWorld->botGoalInfo.team_STROGG_PrimaryAction;
|
|
|
|
if ( botActionNum == -1 ) {
|
|
return idMath::INFINITY;
|
|
}
|
|
|
|
actionGoal = botThreadData.botActions[ botActionNum ]->GetStroggObj();
|
|
}
|
|
|
|
vec = org - botThreadData.botActions[ botActionNum ]->GetActionOrigin();
|
|
|
|
return vec.LengthFast();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IsAwareOfDanger
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_IsAwareOfDanger( int entNum, const idVec3& org, const idMat3& dir ) {
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_DANGERS; i++ ) {
|
|
|
|
if ( currentDangers[ i ].num != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( currentDangers[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
if ( !DangerStillExists( currentDangers[ i ].num, currentDangers[ i ].ownerNum ) ) {
|
|
ClearDangerFromDangerList( i, false );
|
|
return false;
|
|
}
|
|
|
|
if ( org != vec3_zero ) {
|
|
currentDangers[ i ].origin = org;
|
|
}
|
|
|
|
if ( currentDangers[ i ].dir != mat3_identity ) {
|
|
currentDangers[ i ].dir = dir;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::AddDangerToAwareList
|
|
|
|
Time is in seconds.
|
|
================
|
|
*/
|
|
int idBotAI::AddDangerToAwareList( int entNum, const idVec3 &org, const dangerTypes_t dangerType, int time, int ownerClientNum, const idMat3& dir ) {
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_DANGERS; i++ ) {
|
|
|
|
if ( currentDangers[ i ].num != 0 ) {
|
|
if ( currentDangers[ i ].time > botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
currentDangers[ i ].num = entNum;
|
|
currentDangers[ i ].ownerNum = ownerClientNum;
|
|
currentDangers[ i ].origin = org;
|
|
currentDangers[ i ].type = dangerType;
|
|
currentDangers[ i ].time = ( time * 1000 ) + botWorld->gameLocalInfo.time;
|
|
currentDangers[ i ].dir = dir;
|
|
break;
|
|
}
|
|
|
|
if ( i == MAX_DANGERS ) {
|
|
botThreadData.Warning( "Danger List ran out of room!" );
|
|
i = -1;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckForDangers
|
|
|
|
This is called by the AI nodes to have the bots avoid dangers
|
|
================
|
|
*/
|
|
void idBotAI::Bot_CheckForDangers( bool inCombat ) {
|
|
bool inFront;
|
|
bool mcpDanger = false;
|
|
int dangerIndex;
|
|
int nadeDangers = 0;
|
|
int airStrikeDangers = 0;
|
|
int bombDangers = 0;
|
|
int mineDangers = 0;
|
|
int covertToolDangers = 0;
|
|
int stroyBombDangers = 0;
|
|
int turretDangers = 0;
|
|
int artyDangers = 0;
|
|
int i, j;
|
|
float dist;
|
|
float maxDist = ( botInfo->team == STROGG ) ? 512.0f : 1024.0f;
|
|
trace_t tr; //mal: an AAS trace is just too inprecise for something like this, so need to do a costly vis check.
|
|
proxyInfo_t vehicleInfo;
|
|
idVec3 vec;
|
|
|
|
if ( ignoreDangersTime > botWorld->gameLocalInfo.time ) {
|
|
return;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { //mal: low skill bots wont check for dangers at all.
|
|
return;
|
|
}
|
|
|
|
ignoreDangersTime = botWorld->gameLocalInfo.time + 100;
|
|
|
|
actionNumInsideDanger = ACTION_NULL;
|
|
|
|
turretDangerExists = false;
|
|
turretDangerEntNum = -1;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i != botNum ) {
|
|
if ( botWorld->gameLocalInfo.friendlyFireOn == false ) {
|
|
if ( botWorld->clientInfo[ i ].team == botInfo->team ) {
|
|
continue; //mal: if FF is off, dont worry about teammate grenades - ALWAYS worry about our own tho!
|
|
}
|
|
}
|
|
}
|
|
|
|
//mal: first check for grenades
|
|
for( j = 0; j < MAX_GRENADES; j++ ) {
|
|
const grenadeInfo_t& nadeInfo = botWorld->clientInfo[ i ].weapInfo.grenades[ j ];
|
|
|
|
if ( nadeInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( nadeInfo.entNum, nadeInfo.origin, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( nadeInfo.xySpeed > 150.0f ) { //mal: dont worry about nades moving thru the air
|
|
continue;
|
|
}
|
|
|
|
vec = nadeInfo.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 350.0f ) ) { //mal: if the nade if far enough away, dont worry about it!
|
|
continue;
|
|
}
|
|
|
|
inFront = InFrontOfClient( botNum, nadeInfo.origin );
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill < BOT_SKILL_EXPERT || inCombat ) { //mal: high skill bots will hear the nade ping at their back, others wont. Unless theyre in combat, in which case noone will hear it.
|
|
if ( !inFront ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !inFront && botInfo->xySpeed > WALKING_SPEED ) { //mal: if we're moving, and its behind us, ignore it.
|
|
continue;
|
|
} else {
|
|
vec = nadeInfo.origin;
|
|
|
|
vec[ 2 ] += 8; //mal: raise it up a bit, so we can see it on uneven surfaces.
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_EXPLOSIONSOLID, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( nadeInfo.entNum, nadeInfo.origin, HAND_GRENADE, 4, i ) != -1 ) {
|
|
nadeDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of a grenade!\n", botNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
//mal: first check for stroybombs
|
|
for( j = 0; j < MAX_STROYBOMBS; j++ ) {
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
if ( botWorld->gameLocalInfo.friendlyFireOn == false && botInfo->team == STROGG ) {
|
|
continue;
|
|
}
|
|
|
|
const stroyBombInfo_t& stroyBomb = botWorld->stroyBombs[ j ];
|
|
|
|
if ( stroyBomb.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( stroyBomb.entNum, stroyBomb.origin, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( stroyBomb.xySpeed > 150.0f ) { //mal: dont worry about nades moving thru the air
|
|
continue;
|
|
}
|
|
|
|
vec = stroyBomb.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 350.0f ) ) { //mal: if the nade if far enough away, dont worry about it!
|
|
continue;
|
|
}
|
|
|
|
inFront = InFrontOfClient( botNum, stroyBomb.origin );
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill < BOT_SKILL_EXPERT || inCombat ) { //mal: high skill bots will hear the nade ping at their back, others wont. Unless theyre in combat, in which case noone will hear it.
|
|
if ( !inFront ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !inFront && botInfo->xySpeed > WALKING_SPEED ) { //mal: if we're moving, and its behind us, ignore it.
|
|
continue;
|
|
} else {
|
|
vec = stroyBomb.origin;
|
|
|
|
vec[ 2 ] += 8; //mal: raise it up a bit, so we can see it on uneven surfaces.
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_EXPLOSIONSOLID, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( stroyBomb.entNum, stroyBomb.origin, STROY_BOMB_DANGER, 4, i ) != -1 ) {
|
|
stroyBombDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of a stroy bomb!\n", botNum );
|
|
}
|
|
}
|
|
|
|
//mal: next, look for airstrikes.
|
|
if ( LocationVis2Sky( botInfo->origin ) ) { //mal: airstrike only dangerous if we're outside.
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
if ( i != botNum ) {
|
|
if ( botWorld->gameLocalInfo.friendlyFireOn == false ) {
|
|
if ( botWorld->clientInfo[ i ].team == botInfo->team ) {
|
|
continue; //mal: if FF is off, dont worry about teammate airstrikes - ALWAYS worry about our own tho!
|
|
}
|
|
}
|
|
}
|
|
|
|
const airstrikeInfo_t& airstrikeInfo = botWorld->clientInfo[ i ].weapInfo.airStrikeInfo;
|
|
|
|
if ( airstrikeInfo.entNum == 0 && airstrikeInfo.timeTilStrike < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( airstrikeInfo.oldEntNum, airstrikeInfo.origin, airstrikeInfo.dir ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( airstrikeInfo.xySpeed > 500.0f && i != botNum ) {
|
|
continue;
|
|
}
|
|
|
|
vec = airstrikeInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( 1024.0f ) ) { //mal: too far away to see.
|
|
continue;
|
|
}
|
|
|
|
if ( dist > Square( 512.0f ) || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
|
|
if ( !InFrontOfClient( botNum, airstrikeInfo.origin ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = airstrikeInfo.origin;
|
|
|
|
vec[ 2 ] += 8.0f; //mal: raise it up a bit, so we can see it on uneven surfaces.
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f && tr.c.entityNum != airstrikeInfo.oldEntNum ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( airstrikeInfo.oldEntNum, airstrikeInfo.origin, THROWN_AIRSTRIKE, 10, i, airstrikeInfo.dir ) != -1 ) {
|
|
airStrikeDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of an airstrike!\n", botNum );
|
|
}
|
|
}
|
|
|
|
if ( LocationVis2Sky( botInfo->origin ) ) { //mal: arty only dangerous if we're outside.
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
if ( i != botNum ) {
|
|
if ( botWorld->gameLocalInfo.friendlyFireOn == false ) {
|
|
if ( botWorld->clientInfo[ i ].team == botInfo->team ) {
|
|
continue; //mal: if FF is off, dont worry about teammate arty strike - ALWAYS worry about our own tho!
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.deathTime < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( currentArtyDanger.time > botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
vec = botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.radius ) ) {
|
|
continue;
|
|
}
|
|
|
|
vec = botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.origin;
|
|
|
|
vec[ 2 ] += 64.0f;
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_EXPLOSIONSOLID, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
currentArtyDanger.time = botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.deathTime;
|
|
currentArtyDanger.origin = botWorld->clientInfo[ i ].weapInfo.artyAttackInfo.origin;
|
|
currentArtyDanger.num = i;
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of an arty danger!\n", botNum );
|
|
}
|
|
}
|
|
|
|
if ( botVehicleInfo == NULL ) { //mal: vehicles will need their own way to target these things!
|
|
|
|
float attackDist = ATTACK_APT_DIST;
|
|
|
|
if ( ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo ) || ( botInfo->classType == FIELDOPS && ClassWeaponCharged( AIRCAN ) && Bot_HasWorkingDeployable() ) ) {
|
|
attackDist = WEAPON_LOCK_DIST;
|
|
} //mal: these guys can safely target the deployable from a long ways away.
|
|
|
|
for( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, 3500.0f ) && ( botInfo->classType != SOLDIER || botInfo->weapInfo.primaryWeapon != ROCKET || botInfo->weapInfo.primaryWeapHasAmmo == false ) ) {
|
|
if ( deployable.disabled ) {
|
|
continue;
|
|
}
|
|
} //mal: if the APT is disabled, and we're on our way to the goal, go ahead and ignore it so we can get the job done.
|
|
|
|
if ( deployable.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !deployable.inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( aiState == NBG && nbgType == HACK_DEPLOYABLE && nbgTarget == deployable.entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.type != APT ) { //mal: we'll handle taking out strategic deployables elsewhere.
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( deployable.entNum, deployable.origin, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
vec = deployable.origin - botInfo->origin;
|
|
float distToDeployableSqr = vec.LengthSqr();
|
|
|
|
if ( distToDeployableSqr > Square( attackDist ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
if ( deployable.enemyEntNum == botNum && distToDeployableSqr < Square( ATTACK_APT_DIST ) ) {
|
|
turretDangerEntNum = deployable.entNum;
|
|
}
|
|
}
|
|
|
|
vec = deployable.origin;
|
|
vec.z += DEPLOYABLE_ORIGIN_OFFSET;
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_SHOT_BOUNDINGBOX | MASK_VEHICLESOLID | CONTENTS_FORCEFIELD, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f && tr.c.entityNum != deployable.entNum ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( deployable.entNum, deployable.origin, ANTI_PERSONAL, 10, i ) != -1 ) {
|
|
turretDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of an anti personal danger!\n", botNum );
|
|
}
|
|
}
|
|
|
|
float flyerHiveSightDist = maxDist;
|
|
float flyerHiveHearDist = 512.0f;
|
|
|
|
if ( botInfo->team == GDF && botWorld->gameLocalInfo.gameMap == VOLCANO ) { //mal: killing hives on Volcano is a priority.
|
|
flyerHiveSightDist = 2048.0f;
|
|
flyerHiveHearDist = 1024.0f;
|
|
}
|
|
|
|
//mal: next, look for flyer hives and 3rd eye cameras.
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ i ].team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
const covertToolInfo_t& covertToolInfo = botWorld->clientInfo[ i ].weapInfo.covertToolInfo;
|
|
|
|
if ( covertToolInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( covertToolInfo.entNum, covertToolInfo.origin, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
vec = covertToolInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( flyerHiveSightDist ) ) { //mal: too far away to see.
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->team == STROGG ) {
|
|
if ( !botInfo->weapInfo.hasNadeAmmo ) { //mal: shooting the 3rd eye is pointless.
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !InFrontOfClient( botNum, covertToolInfo.origin, true ) ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( dist > Square( flyerHiveHearDist ) || inCombat || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EXPERT ) {
|
|
if ( !InFrontOfClient( botNum, covertToolInfo.origin ) ) {
|
|
continue;
|
|
} // high skill bots, not in combat, will "hear" the hive moving near them, if its close.
|
|
}
|
|
}
|
|
|
|
vec = covertToolInfo.origin;
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_SHOT_BOUNDINGBOX | MASK_SHOT_RENDERMODEL, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f && tr.c.entityNum != covertToolInfo.entNum ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( covertToolInfo.entNum, covertToolInfo.origin, ( botInfo->team == STROGG ) ? THIRD_EYE_CAM : STROGG_HIVE, 30, i ) != -1 ) {
|
|
covertToolDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of an covert tool danger!\n", botNum );
|
|
}
|
|
|
|
//mal: check if there is an empty MCP in the world - we'll consider it a danger and try to kill it!
|
|
if ( botInfo->team == STROGG ) {
|
|
if ( botWorld->botGoalInfo.mapHasMCPGoal && botWorld->botGoalInfo.botGoal_MCP_VehicleNum > MAX_CLIENTS ) {
|
|
GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, vehicleInfo );
|
|
if ( !vehicleInfo.isImmobilized ) {
|
|
vec = vehicleInfo.origin - botInfo->origin;
|
|
if ( vec.LengthSqr() < Square( 3000.0f ) ) {
|
|
mcpDanger = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//mal: now, look for charges
|
|
for( i = 0; i < MAX_CLIENT_CHARGES; i++ ) {
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
const plantedChargeInfo_t& charge = botWorld->chargeInfo[ i ];
|
|
|
|
if ( charge.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.friendlyFireOn == false ) {
|
|
if ( charge.team == botInfo->team && charge.ownerSpawnID != botInfo->spawnID ) {
|
|
continue;
|
|
}
|
|
} //mal: if FF is off, dont worry about teammate charges - ALWAYS worry about our own tho!
|
|
|
|
if ( botInfo->classType == ENGINEER && charge.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( charge.entNum, charge.origin, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.state != BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.explodeTime > botWorld->gameLocalInfo.time ) {
|
|
continue; //mal: bomb isn't a danger yet.
|
|
}
|
|
|
|
vec = charge.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 1024.0f ) ) {
|
|
continue;
|
|
}
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, charge.origin, MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f && tr.c.entityNum != charge.entNum ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( charge.entNum, charge.origin, PLANTED_CHARGE, TIME_BEFORE_CHARGE_BLOWS_AWARENESS_TIME, charge.ownerEntNum ) != -1 ) {
|
|
bombDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of a bomb!\n", botNum );
|
|
}
|
|
|
|
//mal: mines come last, because they are static.
|
|
for( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( inCombat ) {
|
|
continue;
|
|
} //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( inCombat || !Bot_HasExplosives( inCombat ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ i ].team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
for( j = 0; j < MAX_MINES; j++ ) {
|
|
const plantedMineInfo_t& mineInfo = botWorld->clientInfo[ i ].weapInfo.landMines[ j ];
|
|
|
|
if ( mineInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( Bot_IsAwareOfDanger( mineInfo.entNum, vec3_zero, mat3_identity ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( mineInfo.xySpeed > 150.0f ) { //mal: dont worry about mines moving thru the air
|
|
continue;
|
|
}
|
|
|
|
if ( mineInfo.state != BOMB_ARMED ) { //mal: mine isn't armed yet, so poses no danger to us.
|
|
continue;
|
|
}
|
|
|
|
if ( mineInfo.spotted == false ) { //mal: mine isn't visible to us, so ignore it.
|
|
continue;
|
|
}
|
|
|
|
vec = mineInfo.origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( MINE_TOO_FAR_DIST ) ) { //mal: if the mine if far enough away, dont worry about it!
|
|
continue;
|
|
}
|
|
|
|
if ( dist < Square( MINE_TOO_CLOSE_DIST ) ) { //mal: if we're super close to the mine, and didn't see it til now, its too late to react now.
|
|
continue;
|
|
}
|
|
|
|
//mal: while we're here, check if our current action is within the radius of this mine, which may make it impossible to complete.
|
|
if ( Bot_CheckActionIsValid( actionNum ) ) {
|
|
vec = mineInfo.origin - botThreadData.botActions[ actionNum ]->GetActionOrigin();
|
|
if ( vec.LengthSqr() < Square( 512.0f ) ) {
|
|
actionNumInsideDanger = actionNum;
|
|
}
|
|
|
|
if ( !Bot_HasExplosives( inCombat ) ) { //mal: if the mine is on top of our action and we dont have the means to destroy it, we don't have any choice but to try to go to our goal.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int matesInArea = ClientsInArea( botNum, mineInfo.origin, MINE_TOO_CLOSE_DIST, botInfo->team, NOCLASS, false, false, false, false, false );
|
|
|
|
if ( matesInArea > 0 ) { //mal: dont target a mine if a teammate is already near it, might end up killing him.
|
|
continue;
|
|
}
|
|
|
|
inFront = InFrontOfClient( botNum, mineInfo.origin );
|
|
|
|
if ( !inFront ) {
|
|
if ( mineInfo.spotted == false ) { //mal: if its behind us, and not vis ( thus not showing up on our cmd map ) ignore it.
|
|
continue;
|
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: if we're just not too bright, ignore it.
|
|
continue;
|
|
}
|
|
} else {
|
|
vec = mineInfo.origin;
|
|
|
|
vec.z += 8.0f; //mal: raise it up a bit, so we can see it on uneven surfaces.
|
|
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, vec, MASK_EXPLOSIONSOLID, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f ) { //mal: its not visible to us, so dont worry about it.
|
|
continue;
|
|
}
|
|
|
|
if ( AddDangerToAwareList( mineInfo.entNum, mineInfo.origin + idVec3( 0.0f, 0.0f, 8.0f ), PLANTED_LANDMINE, 4, i ) != -1 ) {
|
|
mineDangers++;
|
|
}
|
|
|
|
botThreadData.Printf( "Bot #%i is aware of a landmine!\n", botNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( inCombat ) {
|
|
if ( turretDangers > 0 && botVehicleInfo == NULL ) {
|
|
combatDangerExists = false; /*true;*/ //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
turretDangerExists = true;
|
|
}
|
|
|
|
if ( nadeDangers > 0 || airStrikeDangers > 0 || bombDangers > 0 || mineDangers > 0 || covertToolDangers > 0 || stroyBombDangers > 0 || currentArtyDanger.time > botWorld->gameLocalInfo.time ) {
|
|
combatDangerExists = false; /*true;*/ //mal: 11 and a half hour change: don't avoid certain dangers in combat, as that causes the jittering issues - 4/16/08
|
|
} else {
|
|
combatDangerExists = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( aiState == NBG && ( nbgType == AVOID_DANGER || nbgType == DESTROY_DEPLOYABLE ) ) { //mal: we're already trying to avoid/destroy dangers! Keep track of them, but dont keep resetting the bots node.
|
|
return;
|
|
}
|
|
|
|
if ( nadeDangers > 0 || airStrikeDangers > 0 || bombDangers > 0 || mineDangers > 0 || covertToolDangers > 0 || turretDangers > 0 || stroyBombDangers > 0 || currentArtyDanger.time > botWorld->gameLocalInfo.time ) {
|
|
dangerIndex = FindClosestDangerIndex();
|
|
|
|
if ( dangerIndex == -1 ) {
|
|
return;
|
|
}
|
|
|
|
//mal: dynamic dangers get priority
|
|
if ( currentDangers[ dangerIndex ].type == HAND_GRENADE || currentDangers[ dangerIndex ].type == PLANTED_CHARGE || currentDangers[ dangerIndex ].type == THROWN_AIRSTRIKE || currentDangers[ dangerIndex ].type == STROY_BOMB_DANGER ) {
|
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
|
nbgTarget = dangerIndex;
|
|
|
|
if ( currentDangers[ dangerIndex ].type == THROWN_AIRSTRIKE ) {
|
|
nbgTime = botWorld->gameLocalInfo.time + 15000;
|
|
} else if ( currentDangers[ dangerIndex ].type == PLANTED_CHARGE ) {
|
|
nbgTime = nbgTime = botWorld->gameLocalInfo.time + 11000;
|
|
} else {
|
|
nbgTime = botWorld->gameLocalInfo.time + 5000;
|
|
}
|
|
|
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_AvoidDanger;
|
|
return;
|
|
}
|
|
|
|
if ( turretDangers > 0 ) {
|
|
if ( Bot_HasExplosives( false ) ) {
|
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
|
nbgTarget = currentDangers [ dangerIndex ].num;
|
|
nbgTargetType = DEPLOYABLE; //mal: this is a deployable danger.
|
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_DestroyAPTDanger; //mal: APT specific function to destroy them.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( currentDangers[ dangerIndex ].type == PLANTED_LANDMINE || currentDangers[ dangerIndex ].type == STROGG_HIVE || currentDangers[ dangerIndex ].type == THIRD_EYE_CAM ) {
|
|
if ( Bot_HasExplosives( false ) ) {
|
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
|
nbgTarget = dangerIndex;
|
|
|
|
if ( currentDangers[ dangerIndex ].type == PLANTED_LANDMINE ) {
|
|
nbgTargetType = LANDMINE_DANGER; //mal: this is a landmine.
|
|
} else if ( currentDangers[ dangerIndex ].type == STROGG_HIVE ) {
|
|
nbgTargetType = STROGG_HIVE_DANGER; //mal: this is a landmine.
|
|
} else if ( currentDangers[ dangerIndex ].type == THIRD_EYE_CAM ) {
|
|
nbgTargetType = GDF_CAM_DANGER; //mal: this is a landmine.
|
|
}
|
|
|
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_DestroyDanger; //mal: generic function to destroy dangers
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mcpDanger == true ) { //mal_TODO: make it where engs can destroy with nade launcher, and Cvops can destroy with hives.
|
|
if ( Bot_HasExplosives( false ) || ( botInfo->team == STROGG && botInfo->classType == MEDIC ) || ( botInfo->team == GDF && botInfo->classType == FIELDOPS ) ) {
|
|
nbgTargetType = MCP_DANGER;
|
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_DestroyMCP;
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClearDangerFromDangerList
|
|
==================
|
|
*/
|
|
void idBotAI::ClearDangerFromDangerList( int dangerIndex, bool isEntNum ) {
|
|
int i;
|
|
|
|
if ( !isEntNum ) {
|
|
currentDangers[ dangerIndex ].num = 0;
|
|
currentDangers[ dangerIndex ].time = 0;
|
|
} else {
|
|
for( i = 0; i < MAX_DANGERS; i++ ) {
|
|
if ( currentDangers[ i ].num != dangerIndex ) {
|
|
continue;
|
|
}
|
|
|
|
currentDangers[ i ].num = 0;
|
|
currentDangers[ i ].time = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::FindClosestDangerIndex
|
|
==================
|
|
*/
|
|
int idBotAI::FindClosestDangerIndex() {
|
|
int i;
|
|
int bestIndex = -1;
|
|
float dist;
|
|
float bestDist = idMath::INFINITY; //mal: set it to an insane number.
|
|
idVec3 vec;
|
|
|
|
for( i = 0; i < MAX_DANGERS; i++ ) {
|
|
|
|
if ( currentDangers[ i ].num == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( currentDangers[ i ].type != THROWN_AIRSTRIKE ) { //mal: airstrikes only get a time when theyre about to strike.
|
|
if ( currentDangers[ i ].time < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = currentDangers[ i ].origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( currentDangers[ i ].type == ANTI_PERSONAL ) { //mal: need to give a bit of priority to these, since they are so dangerous.
|
|
dist /= 2.0f;
|
|
|
|
if ( dist <= 0.0f ) {
|
|
dist = Square( 100.0f );
|
|
}
|
|
}
|
|
|
|
if ( dist < bestDist ) {
|
|
bestDist = dist;
|
|
bestIndex = i;
|
|
}
|
|
}
|
|
|
|
return bestIndex;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::FindMineInWorld
|
|
|
|
Look for a mine out there to arm/disarm.
|
|
==================
|
|
*/
|
|
void idBotAI::FindMineInWorld( plantedMineInfo_t& mineInfo ) {
|
|
int matesInArea;
|
|
float dist;
|
|
float closest = idMath::INFINITY;
|
|
idVec3 vec;
|
|
mineInfo.entNum = 0; //mal: clear this out, so we can tell if we got one or not later.
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) { //mal: if client is kicked, disconnected, etc....
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
for( int j = 0; j < MAX_MINES; j++ ) {
|
|
if ( playerInfo.weapInfo.landMines[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.landMines[ j ].state == BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.landMines[ j ].selfArming ) {
|
|
continue;
|
|
}
|
|
|
|
vec = playerInfo.weapInfo.landMines[ j ].origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( 1000.0f ) ) { //mal: this mine is too far away from us.
|
|
continue;
|
|
}
|
|
|
|
matesInArea = ClientsInArea( botNum, playerInfo.weapInfo.landMines[ j ].origin, PLIERS_RANGE / 2, botInfo->team, ENGINEER, false, false, false, false, true );
|
|
|
|
if ( matesInArea > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest mine, whether its ours or not.
|
|
mineInfo = playerInfo.weapInfo.landMines[ j ];
|
|
closest = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::NumPlayerMines
|
|
==================
|
|
*/
|
|
int idBotAI::NumPlayerMines() {
|
|
|
|
int i;
|
|
int m = 0;
|
|
|
|
for( i = 0; i < MAX_MINES; i++ ) {
|
|
if ( botInfo->weapInfo.landMines[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->weapInfo.landMines[ i ].state != BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
|
|
m++;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::DangerStillExists
|
|
================
|
|
*/
|
|
bool idBotAI::DangerStillExists( int entNum, int ownerClientNum ) {
|
|
|
|
bool dangerExists = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_CLIENT_CHARGES; i++ ) {
|
|
if ( botWorld->chargeInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->chargeInfo[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->chargeInfo[ i ].state == BOMB_NULL ) {
|
|
continue;
|
|
}
|
|
|
|
dangerExists = true;
|
|
break;
|
|
}
|
|
|
|
if ( dangerExists ) {
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
dangerExists = true;
|
|
break;
|
|
}
|
|
|
|
if ( dangerExists ) {
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_STROYBOMBS; i++ ) {
|
|
if ( botWorld->stroyBombs[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->stroyBombs[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
dangerExists = true;
|
|
break;
|
|
}
|
|
|
|
if ( dangerExists ) {
|
|
return true;
|
|
}
|
|
|
|
//mal: from here on down, check dangers attached to a particular player.
|
|
|
|
if ( !ClientIsValid( ownerClientNum, -1 ) ) {
|
|
return false;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ ownerClientNum ];
|
|
|
|
for( i = 0; i < MAX_GRENADES; i++ ) {
|
|
if ( playerInfo.weapInfo.grenades[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.grenades[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
dangerExists = true;
|
|
break;
|
|
}
|
|
|
|
if ( dangerExists ) {
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_MINES; i++ ) {
|
|
if ( playerInfo.weapInfo.landMines[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.landMines[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
dangerExists = true;
|
|
break;
|
|
}
|
|
|
|
if ( dangerExists ) {
|
|
return true;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.airStrikeInfo.timeTilStrike > botWorld->gameLocalInfo.time || playerInfo.weapInfo.airStrikeInfo.entNum != 0 ) {
|
|
dangerExists = true;
|
|
}
|
|
|
|
if ( playerInfo.weapInfo.covertToolInfo.entNum != 0 ) {
|
|
dangerExists = true;
|
|
}
|
|
|
|
return dangerExists;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_HasExplosives
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_HasExplosives( bool inCombat, bool makeSureSoldierWeaponReady ) {
|
|
|
|
if ( botInfo->weapInfo.hasNadeAmmo ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo ) {
|
|
if ( makeSureSoldierWeaponReady ) {
|
|
if ( !botInfo->weapInfo.primaryWeapNeedsReload ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( botInfo->classType == FIELDOPS && ClassWeaponCharged( AIRCAN ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( !inCombat ) {
|
|
#ifndef PACKS_HAVE_NO_NADES
|
|
if ( ( botInfo->classType == MEDIC && botInfo->team == STROGG ) || ( botInfo->classType == FIELDOPS && botInfo->team == GDF ) ) {
|
|
return true;
|
|
} //mal: these classes can resupply themselves to destroy any dangers they may find, so ALWAYS return true.
|
|
#endif
|
|
|
|
if ( botInfo->classType == COVERTOPS && botInfo->team == GDF && ClassWeaponCharged( THIRD_EYE ) ) {
|
|
return true;
|
|
} //mal: coverts with camera ready can use that to destroy the danger. Strogg hives are totally worthless for this task.
|
|
}
|
|
|
|
//mal_FIXME: handle cases where the bot is an eng ( with nade launcher ).
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_RunTacticalAction
|
|
================
|
|
*/
|
|
void idBotAI::Bot_RunTacticalAction() {
|
|
if ( !Bot_CheckActionIsValid( tacticalActionNum ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( tacticalActionPauseTime > botWorld->gameLocalInfo.time ) { //mal: wait a sec.
|
|
Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
|
return;
|
|
}
|
|
|
|
idVec3 actionOrg = botThreadData.botActions[ tacticalActionNum ]->GetActionOrigin();
|
|
idVec3 vec = actionOrg - botInfo->origin;
|
|
|
|
if ( vec[ 2 ] > 150.0f ) {
|
|
actionOrg[ 2 ] += 65.0f;
|
|
}
|
|
|
|
botActionGoals_t actionType = botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team );
|
|
|
|
BotAI_ResetUcmd();
|
|
Bot_LookAtLocation( actionOrg, SMOOTH_TURN );
|
|
|
|
if ( actionType == ACTION_AIRCAN_HINT ) {
|
|
Bot_UseCannister( AIRCAN, actionOrg );
|
|
} else if ( actionType == ACTION_SMOKE_HINT ) {
|
|
Bot_UseCannister( SMOKE_NADE, actionOrg );
|
|
} else if ( actionType == ACTION_NADE_HINT ) {
|
|
botIdealWeapNum = NULL_WEAP;
|
|
botIdealWeapSlot = NADE;
|
|
Bot_ThrowGrenade( actionOrg, true );
|
|
} else if ( actionType == ACTION_SUPPLY_HINT ) {
|
|
Bot_UseCannister( SUPPLY_MARKER, actionOrg );
|
|
} else if ( actionType == ACTION_SHIELD_HINT ) {
|
|
botIdealWeapNum = SHIELD_GUN;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
if ( botInfo->weapInfo.weapon == SHIELD_GUN ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
} else if ( actionType == ACTION_THIRDEYE_HINT ) {
|
|
botIdealWeapNum = THIRD_EYE;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
if ( botInfo->weapInfo.weapon == THIRD_EYE ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
} else if ( actionType == ACTION_TELEPORTER_HINT ) {
|
|
botIdealWeapNum = TELEPORTER;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( tacticalActionTimer == 0 ) {
|
|
tacticalActionTimer = botWorld->gameLocalInfo.time + 1500;
|
|
tacticalActionTimer2 = tacticalActionTimer + 2500;
|
|
}
|
|
|
|
if ( botInfo->weapInfo.weapon == TELEPORTER && tacticalActionTimer < botWorld->gameLocalInfo.time && ClassWeaponCharged( TELEPORTER ) ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
} else if ( actionType == ACTION_FLYER_HIVE_HINT ) {
|
|
if ( tacticalActionTimer == 0 && botInfo->weapInfo.covertToolInfo.entNum == 0 ) {
|
|
botIdealWeapNum = FLYER_HIVE;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( tacticalActionOrigin == vec3_zero ) {
|
|
ResetRandomLook();
|
|
Bot_RandomLook( tacticalActionOrigin, true );
|
|
}
|
|
|
|
Bot_LookAtLocation( tacticalActionOrigin, INSTANT_TURN );
|
|
|
|
if ( botInfo->weapInfo.weapon == FLYER_HIVE && ClassWeaponCharged( FLYER_HIVE ) && botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
tacticalActionTimer++;
|
|
int enemyNum = Flyer_FindEnemy( FLYER_HIVE_SIGHT_DIST );
|
|
|
|
if ( enemyNum == -1 ) {
|
|
int goalAreaNum = botThreadData.botActions[ tacticalActionNum ]->GetActionAreaNum();
|
|
idVec3 goalOrigin = botThreadData.botActions[ tacticalActionNum ]->GetActionOrigin();
|
|
idVec3 vec = goalOrigin - botInfo->weapInfo.covertToolInfo.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( botThreadData.botActions[ tacticalActionNum ]->radius ) ) {
|
|
Bot_SetupFlyerMove( goalOrigin, goalAreaNum );
|
|
|
|
idVec3 moveGoal = botAAS.path.moveGoal;
|
|
|
|
moveGoal.z += FLYER_HIVE_HEIGHT_OFFSET;
|
|
|
|
Bot_MoveToGoal( moveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
|
Flyer_LookAtLocation( moveGoal );
|
|
return;
|
|
}
|
|
} else {
|
|
clientInfo_t player = botWorld->clientInfo[ enemyNum ];
|
|
|
|
int goalAreaNum = player.areaNum;
|
|
idVec3 goalOrigin = player.origin;
|
|
|
|
idVec3 vec = goalOrigin - botInfo->weapInfo.covertToolInfo.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( FLYER_HIVE_ATTACK_DIST ) ) {
|
|
Bot_SetupFlyerMove( goalOrigin, goalAreaNum );
|
|
|
|
idVec3 moveGoal = botAAS.path.moveGoal;
|
|
|
|
moveGoal.z += FLYER_HIVE_HEIGHT_OFFSET;
|
|
|
|
Bot_MoveToGoal( moveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
|
Flyer_LookAtLocation( moveGoal );
|
|
return;
|
|
} else {
|
|
botUcmd->botCmds.attack = true;
|
|
tacticalActionTime = botWorld->gameLocalInfo.time + 1000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::TeamHasHuman
|
|
================
|
|
*/
|
|
bool idBotAI::TeamHasHuman( const playerTeamTypes_t playerTeam ) {
|
|
return ( playerTeam == GDF ) ? botWorld->gameLocalInfo.teamGDFHasHuman : botWorld->gameLocalInfo.teamStroggHasHuman;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::NeedsReload
|
|
================
|
|
*/
|
|
bool idBotAI::NeedsReload() {
|
|
if ( botInfo->weapInfo.primaryWeapNeedsReload && botInfo->weapInfo.hasAmmoForReload ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::TeamHumanNearLocation
|
|
|
|
if range == -1, it becomes a general "does a huamn exist on this team at all" check.
|
|
================
|
|
*/
|
|
bool idBotAI::TeamHumanNearLocation( const playerTeamTypes_t playerTeam, const idVec3 &loc, float range, bool ignorePlayersInVehicle, const playerClassTypes_t playerClass, bool ignoreDeadMates, bool ignoreIfJustLeftAVehicle ) {
|
|
|
|
bool hasHuman = ( playerTeam == GDF ) ? botWorld->gameLocalInfo.teamGDFHasHuman : botWorld->gameLocalInfo.teamStroggHasHuman;
|
|
|
|
if ( !hasHuman ) { //mal: if theres not even a human on this team ATM, dont worrry about it.
|
|
return false;
|
|
}
|
|
|
|
hasHuman = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerClass != NOCLASS ) {
|
|
if ( playerInfo.classType != playerClass ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ignoreIfJustLeftAVehicle ) {
|
|
if ( playerInfo.lastOwnedVehicleTime + IGNORE_IF_JUST_LEFT_VEHICLE_TIME > botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ignorePlayersInVehicle == true ) {
|
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: this player is in a vehicle, dont worry about him
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ignoreDeadMates == true ) {
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( playerInfo.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( range != -1.0f ) {
|
|
idVec3 vec = playerInfo.origin - loc;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasHuman = true;
|
|
break;
|
|
}
|
|
|
|
return hasHuman;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_ShouldUseVehicleForAction
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_ShouldUseVehicleForAction( int actionNumber, bool ignoreArmor ) {
|
|
if ( !Bot_CheckActionIsValid( actionNumber ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botsUseVehicles == false ) { //mal: if the admin doesn't want the bots driving around, dont let them.
|
|
return false;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) == NO_VEHICLE ) { //mal: this action doesn't want us to use vehicles.
|
|
return false;
|
|
}
|
|
|
|
if ( botVehicleInfo != NULL && ( ( botVehicleInfo->flags & botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) ) || botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) == NULL_VEHICLE_FLAGS ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.gameMap == ISLAND && ClientHasObj( botNum ) && botVehicleInfo == NULL ) { //mal: hack.
|
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//mal: if the action is an MCP deliever action, we ALWAYS want to use a vehicle!
|
|
|
|
int vehicleNum = -1;
|
|
int vehicleIgnoreFlags = ( ignoreArmor ) ? ARMOR : NULL_VEHICLE_FLAGS;
|
|
float checkDist = ( botWorld->gameLocalInfo.debugPersonalVehicles ) ? 100.0f : 4000.0f;
|
|
idVec3 vec = botThreadData.botActions[ actionNumber ]->GetActionOrigin() - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( checkDist ) ) {
|
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ), vehicleIgnoreFlags, true );
|
|
}
|
|
|
|
if ( vehicleNum != -1 ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_MeetsVehicleRequirementsForAction
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_MeetsVehicleRequirementsForAction( int actionNumber ) {
|
|
if ( !Bot_CheckActionIsValid( actionNumber ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !botThreadData.botActions[ actionNumber ]->requiresVehicleType ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) == NO_VEHICLE ) { //mal: this action doesn't want us to use vehicles - it COULD happen.
|
|
return true;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botsUseVehicles == false ) { //mal: if the admin doesn't want the bots driving around, dont let them.
|
|
return false;
|
|
} //mal: a vehicle only action is no good to the bot if the admin won't let the bot drive. :-(
|
|
|
|
if ( botVehicleInfo != NULL && ( ( botVehicleInfo->flags & botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) ) || botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ) == NULL_VEHICLE_FLAGS ) ) {
|
|
return true;
|
|
}
|
|
|
|
int vehicleNum = -1;
|
|
idVec3 vec = botThreadData.botActions[ actionNumber ]->GetActionOrigin() - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 6000.0f ) ) {
|
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNumber ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
|
}
|
|
|
|
if ( vehicleNum != -1 ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_GetVehicle
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_GetIntoVehicle( int vehicleNum ) {
|
|
float dist;
|
|
proxyInfo_t vehicleInfo;
|
|
idVec3 vec;
|
|
|
|
GetVehicleInfo( vehicleNum, vehicleInfo );
|
|
|
|
if ( vehicleInfo.entNum == 0 || !vehicleInfo.hasFreeSeat ) { //mal: its gone, its moving, or its full - ignore!
|
|
return false;
|
|
}
|
|
|
|
idVec3 vehicleOrigin = vehicleInfo.origin;
|
|
vehicleOrigin.z += VEHICLE_PATH_ORIGIN_OFFSET;
|
|
|
|
botUcmd->actionEntityNum = vehicleInfo.entNum;
|
|
botUcmd->actionEntitySpawnID = vehicleInfo.spawnID;
|
|
|
|
Bot_SetupMove( vehicleOrigin, -1, ACTION_NULL );
|
|
|
|
if ( MoveIsInvalid() ) {
|
|
return false;
|
|
}
|
|
|
|
Bot_MoveAlongPath( Bot_ShouldStrafeJump( vehicleOrigin ) );
|
|
|
|
vec = vehicleInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
|
|
//mal: if we're fairly close, start sending the vehicle use cmd
|
|
if ( dist < Square( 350.0f ) ) {
|
|
Bot_LookAtLocation( vehicleInfo.origin, SMOOTH_TURN );
|
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
botUcmd->botCmds.enterVehicle = true;
|
|
}
|
|
}
|
|
|
|
V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node;
|
|
V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckForNeedyVehicles
|
|
|
|
Checks for vehicles that need fixing.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CheckForNeedyVehicles( float range, bool& chatRequest ) {
|
|
bool botIsBusy = false;
|
|
bool botIsImportant = false;
|
|
chatRequest = false;
|
|
int vehicleNum = -1;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, CLOSE_TO_GOAL_RANGE ) ) {
|
|
botIsImportant = true;
|
|
}
|
|
|
|
for ( int i = 0; i < MAX_VEHICLES; i++ ) {
|
|
|
|
const proxyInfo_t& vehicleInfo = botWorld->vehicleInfo[ i ];
|
|
|
|
botIsBusy = false;
|
|
|
|
if ( vehicleInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botIsImportant && vehicleInfo.isEmpty && vehicleInfo.type != MCP ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botIsImportant && !vehicleInfo.isEmpty && vehicleInfo.type != MCP ) { //mal: need to focus on doing obj, and leave bot teammates to their own devices.
|
|
if ( vehicleInfo.driverEntNum > -1 && vehicleInfo.driverEntNum < MAX_CLIENTS ) {
|
|
const clientInfo_t& player = botWorld->clientInfo[ vehicleInfo.driverEntNum ];
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
int gunnerEntNum = Bot_GetVehicleGunnerClientNum( vehicleInfo.entNum );
|
|
|
|
if ( gunnerEntNum > -1 && gunnerEntNum < MAX_CLIENTS ) {
|
|
const clientInfo_t& player = botWorld->clientInfo[ gunnerEntNum ];
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( vehicleInfo.type == MCP && aiState == LTG && ltgType == FIX_MCP ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.type == MCP && vehicleInfo.health > ( vehicleInfo.maxHealth / 2 ) ) { //mal: get a move on!
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( vehicleInfo.entNum ) == true ) {
|
|
botIsBusy = true;
|
|
}
|
|
}
|
|
|
|
if ( VehicleIsIgnored( vehicleInfo.entNum ) && vehicleInfo.type != MCP ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.xyspeed > WALKING_SPEED ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.health == vehicleInfo.maxHealth ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
int repairMinHealth = ( vehicleInfo.maxHealth / 2 );
|
|
|
|
for( int j = 0; j < MAX_CLIENTS; j++ ) {
|
|
if ( !ClientIsValid( j, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ j ];
|
|
|
|
if ( player.proxyInfo.entNum != vehicleInfo.entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.lastChatTime[ NEED_REPAIR ] + 5000 < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.health < vehicleInfo.maxHealth ) { //mal: vehicle may have been fixed since request - or player is being silly.
|
|
chatRequest = true;
|
|
}
|
|
|
|
repairMinHealth = ( int ) ( vehicleInfo.maxHealth / 1.20f );
|
|
break;
|
|
}
|
|
|
|
bool markedForRepair = VehicleIsMarkedForRepair( vehicleInfo.entNum, false );
|
|
|
|
if ( !chatRequest && !markedForRepair && botIsImportant && vehicleInfo.type != MCP ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( markedForRepair ) {
|
|
chatRequest = true;
|
|
repairMinHealth = ( int ) ( vehicleInfo.maxHealth / 1.10f );
|
|
}
|
|
|
|
if ( vehicleInfo.health > repairMinHealth && vehicleInfo.type != MCP && vehicleInfo.damagedPartsCount <= 0 ) { //mal: its in "good enough" shape, unless its an MCP, or its missing wheels.
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( !vehicleInfo.inPlayZone ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.isFlipped ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
int busyClient;
|
|
|
|
if ( !Bot_NBGIsAvailable( vehicleInfo.entNum, ACTION_NULL, FIX_VEHICLE, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !vehicleInfo.hasGroundContact && vehicleInfo.type != DESECRATOR ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.neverDriven && vehicleInfo.type != MCP ) { //mal: dont bother with a vehicle that noones driving or cares about. Just get moving!
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.type == MCP && !botWorld->botGoalInfo.mapHasMCPGoal ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.inWater ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.areaNum == 0 ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
vec = vehicleInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
float repairDist = range;
|
|
|
|
if ( markedForRepair ) {
|
|
repairDist = 6000.0f;
|
|
}
|
|
|
|
if ( dist > Square( repairDist ) ) { //mal: too far away - ignore!
|
|
botIsBusy = true;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, vehicleInfo.origin, travelTime ) ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
if ( botIsBusy ) {
|
|
if ( chatRequest ) {
|
|
Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 );
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) { //mal: find the closest player in need, and help them first!
|
|
vehicleNum = botWorld->vehicleInfo[ i ].entNum;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return vehicleNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ClientHasVehicleInWorld
|
|
|
|
Checks if client owns a vehicle in the world.
|
|
================
|
|
*/
|
|
bool idBotAI::ClientHasVehicleInWorld( int clientNum, float range ) {
|
|
|
|
bool hasVehicle = false;
|
|
int i;
|
|
idVec3 vec;
|
|
|
|
for ( i = 0; i < MAX_VEHICLES; i++ ) {
|
|
|
|
const proxyInfo_t& vehicleInfo = botWorld->vehicleInfo[ i ];
|
|
|
|
if ( vehicleInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.ownerNum != clientNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !vehicleInfo.inPlayZone ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.isFlipped ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.type == MCP ) { //mal: noone "owns" an MCP.
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.inWater && !( vehicleInfo.flags & WATER ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicleInfo.driverEntNum != clientNum && vehicleInfo.driverEntNum != -1 ) { //mal: someone drove off in our vehicle!
|
|
continue;
|
|
}
|
|
|
|
vec = vehicleInfo.origin - botWorld->clientInfo[ clientNum ].origin;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) { //mal: its too far away to be considered "our" vehicle.
|
|
continue;
|
|
}
|
|
|
|
hasVehicle = true;
|
|
break;
|
|
}
|
|
|
|
return hasVehicle;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idBotAI::BodyIsObstructed
|
|
|
|
Checks if the client's body we are currently targeting is blocked by another clients body, or by a vehicle/deployable.
|
|
Used by medics and covert from both teams, to figure if we need to get a better position to steal/revive/spawnhost/etc.
|
|
Or if we should skip the body totally.
|
|
================
|
|
*/
|
|
bool idBotAI::BodyIsObstructed( int entNum, bool isCorpse, bool isSpawnHost ) {
|
|
|
|
int i;
|
|
idVec3 org = ( isCorpse ) ? botWorld->playerBodies[ entNum ].bodyOrigin : botWorld->clientInfo[ entNum ].origin;
|
|
idVec3 vec;
|
|
|
|
if ( isSpawnHost ) {
|
|
org = botWorld->spawnHosts[ entNum ].origin;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( i == botNum ) { //mal: don't worry about yourself!
|
|
continue;
|
|
}
|
|
|
|
if ( i == entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ i ].health > 0 ) {
|
|
continue;
|
|
}
|
|
|
|
vec = botWorld->clientInfo[ i ].origin - org;
|
|
|
|
if ( vec.LengthSqr() > 50.0f ) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_PLAYERBODIES; i++ ) {
|
|
|
|
if ( !botWorld->playerBodies[ i ].isValid ) {
|
|
continue; //mal: no body in this client slot!
|
|
}
|
|
|
|
if ( isCorpse ) {
|
|
if ( i == entNum ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = botWorld->playerBodies[ i ].bodyOrigin - org;
|
|
|
|
if ( vec.LengthSqr() > 50.0f ) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_VEHICLES; i++ ) {
|
|
|
|
if ( !botWorld->vehicleInfo[ i ].entNum == 0 ) {
|
|
continue; //mal: no vehicle in this client slot!
|
|
}
|
|
|
|
vec = botWorld->vehicleInfo[ i ].origin - org;
|
|
|
|
if ( vec.LengthSqr() > 500.0f ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botWorld->vehicleInfo[ i ].bbox.ContainsPoint( org ) ) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
vec = botWorld->deployableInfo[ i ].origin - org;
|
|
|
|
if ( vec.LengthSqr() > 500.0f ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botWorld->deployableInfo[ i ].bbox.ContainsPoint( org ) ) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idBotAI::ClientIsDead
|
|
|
|
Just a quick check, that takes into account the bot's thread delay time,
|
|
to find out if the client in question died while we were in a wait state.
|
|
================
|
|
*/
|
|
bool idBotAI::ClientIsDead( int clientNum ) {
|
|
if ( !ClientIsValid( clientNum, -1 ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ clientNum ].lastKilledTime + BOT_THINK_DELAY_TIME > botWorld->gameLocalInfo.time || botWorld->clientInfo[ clientNum ].health <= 0 ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ClientCanBeTKRevived
|
|
|
|
The bots are more courteous then your average human player. :-P
|
|
================
|
|
*/
|
|
bool idBotAI::ClientCanBeTKRevived( int clientNum ) {
|
|
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
|
|
|
|
if ( player.health > TK_REVIVE_HEALTH ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->team == STROGG ) {
|
|
return false;
|
|
} //mal: cant revive fast enough.
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_EXPERT || !botWorld->gameLocalInfo.friendlyFireOn ) { //mal: TK reviving is something only the smartest bots will do
|
|
return false;
|
|
}
|
|
|
|
if ( !botWorld->gameLocalInfo.botsUseTKRevive ) {
|
|
return false;
|
|
}
|
|
|
|
if ( player.lastAttackerTime + 3000 > botWorld->gameLocalInfo.time ) {
|
|
return false;
|
|
}
|
|
|
|
if ( player.lastAttackClientTime + 3000 > botWorld->gameLocalInfo.time ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->lastAttackerTime + 3000 > botWorld->gameLocalInfo.time ) {
|
|
return false;
|
|
}
|
|
|
|
if ( enemy != -1 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( player.isDisguised ) {
|
|
return false;
|
|
}
|
|
|
|
if ( player.weapInfo.isFiringWeap ) {
|
|
return false;
|
|
}
|
|
|
|
if ( player.targetLocked ) {
|
|
return false;
|
|
} //mal: if they've locked onto a target, don't deny them the kill
|
|
|
|
if ( player.weapInfo.weapon == DEPLOY_TOOL || player.weapInfo.weapon == BINOCS ) {
|
|
return false;
|
|
} //mal: dont tk them if they're trying to deploy something, or they're spotting - that would be confusing.
|
|
|
|
if ( player.weapInfo.weapon == PLIERS || player.weapInfo.weapon == HACK_TOOL ) {
|
|
return false;
|
|
} //mal: dont tk them if they're trying to build/arm/hack something - that would be confusing.
|
|
|
|
if ( player.weapInfo.weapon == HE_CHARGE || player.weapInfo.weapon == LANDMINE ) {
|
|
return false;
|
|
} //mal: dont tk them if they're trying to plant/mine something - that would be confusing.
|
|
|
|
if ( player.enemiesInArea > 1 || botInfo->enemiesInArea > 1 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ClientHasObj( clientNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckForSpawnHostsToDestroy
|
|
|
|
Look around for spawnhosts to destroy. Will only do this if in the process of a LTG.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CheckForSpawnHostsToDestroy( float range, bool& useChat ) {
|
|
int i, mates;
|
|
int busyClient;
|
|
int bodyNum = -1;
|
|
float closest = idMath::INFINITY;
|
|
float dist;
|
|
idVec3 vec;
|
|
useChat = false;
|
|
|
|
if ( enemy != -1 ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return bodyNum;
|
|
}
|
|
|
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: this bot is too dumb to do this.
|
|
return bodyNum;
|
|
}
|
|
|
|
for( i = 0; i < MAX_SPAWNHOSTS; i++ ) {
|
|
|
|
if ( SpawnHostIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->spawnHosts[ i ].entNum == 0 ) {
|
|
continue; //mal: no spawnhost in this slot!
|
|
}
|
|
|
|
if ( botWorld->spawnHosts[ i ].areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
//mal: someone else is destroying this spawnhost, so leave it be.
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, DESTORY_SPAWNHOST, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
bool spawnHostIsMarkedForDeath = SpawnHostIsMarkedForDeath( botWorld->spawnHosts[ i ].spawnID );
|
|
|
|
if ( !spawnHostIsMarkedForDeath ) {
|
|
//mal: should only destroy a spawnhost if its fairly close to the strogg's goal.
|
|
if ( botWorld->botGoalInfo.team_STROGG_PrimaryAction != ACTION_NULL ) {
|
|
vec = botWorld->spawnHosts[ i ].origin - botThreadData.botActions[ botWorld->botGoalInfo.team_STROGG_PrimaryAction ]->GetActionOrigin();
|
|
if ( vec.LengthSqr() > Square( SPAWNHOST_RELEVANT_DIST ) ) { //mal: this is too far away from the obj to matter(?)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
vec = botWorld->spawnHosts[ i ].origin - botInfo->origin;
|
|
|
|
dist = vec.LengthSqr();
|
|
|
|
float tempRange = range;
|
|
|
|
if ( spawnHostIsMarkedForDeath ) {
|
|
tempRange = SPAWNHOST_DESTROY_ORDER_RANGE;
|
|
}
|
|
|
|
if ( dist > Square( tempRange ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
mates = ClientsInArea( botNum, botWorld->spawnHosts[ i ].origin, 350.0f, botInfo->team , MEDIC, false, false, false, false, true );
|
|
|
|
if ( mates >= 1 ) { //mal: maybe the medic near this body is arleady zapping it....
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
bodyNum = i;
|
|
closest = dist;
|
|
|
|
if ( spawnHostIsMarkedForDeath ) {
|
|
useChat = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bodyNum;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::ObjIsOnGround
|
|
|
|
Just a quick check to see if theres an obj on the ground somewhere.
|
|
================
|
|
*/
|
|
bool idBotAI::ObjIsOnGround() {
|
|
bool onGround = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_CARRYABLES; i++ ) {
|
|
if ( botWorld->botGoalInfo.carryableObjs[ i ].onGround && botWorld->botGoalInfo.carryableObjs[ i ].entNum != 0 ) {
|
|
onGround = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return onGround;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckForNeedyDeployables
|
|
|
|
Checks for vehicles that need fixing.
|
|
================
|
|
*/
|
|
int idBotAI::Bot_CheckForNeedyDeployables( float range ) {
|
|
bool botIsBusy = false;
|
|
int i;
|
|
int deployableNum = -1;
|
|
float closest = idMath::INFINITY; //mal: set it to some large, crazy number
|
|
float dist;
|
|
idVec3 vec;
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, CLOSE_TO_GOAL_RANGE ) && botWorld->botGoalInfo.attackingTeam == botInfo->team ) {
|
|
botIsBusy = true;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( DeployableIsIgnored( botWorld->deployableInfo[ i ].entNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
const deployableInfo_t& deployableInfo = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployableInfo.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.health == 0 && deployableInfo.maxHealth == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.ownerClientNum == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !deployableInfo.inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( deployableInfo.entNum ) == true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.type == APT || deployableInfo.type == AVT ) { //mal: don't repair deployables when playing against the human in training mode.
|
|
if ( !TeamHasHuman( botInfo->team ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int busyClient;
|
|
|
|
if ( !Bot_NBGIsAvailable( deployableInfo.entNum, ACTION_NULL, FIX_DEPLOYABLE, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ deployableInfo.ownerClientNum ].team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.health == deployableInfo.maxHealth ) {
|
|
continue;
|
|
}
|
|
|
|
bool markedForRepair = DeployableIsMarkedForRepair( deployableInfo.entNum, false );
|
|
|
|
if ( deployableInfo.health > ( deployableInfo.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) && !markedForRepair ) {
|
|
if ( botIsBusy ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployableInfo.type != APT && deployableInfo.type != AVT ) { //mal: keep turrets up at all times.
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EXPERT ) { //mal: make the SP game easier for new players, harder for experts. Favor the offense for the all but the last obj.
|
|
if ( ( deployableInfo.type == AVT || deployableInfo.type == APT ) && !botWorld->botGoalInfo.gameIsOnFinalObjective && botWorld->botGoalInfo.attackingTeam != botInfo->team && botWorld->gameLocalInfo.gameIsBotMatch && TeamHasHuman( botInfo->team ) ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( ( deployableInfo.type == AVT || deployableInfo.type == APT ) && !botWorld->botGoalInfo.gameIsOnFinalObjective && botWorld->gameLocalInfo.gameIsBotMatch && botWorld->botGoalInfo.attackingTeam != botInfo->team ) { //mal: game balancing for SP....
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
vec = deployableInfo.origin - botInfo->origin;
|
|
dist = vec.LengthSqr();
|
|
float repairDist = FIX_DEPLOYABLE_DIST;
|
|
|
|
if ( markedForRepair ) {
|
|
repairDist *= 4.0f;
|
|
}
|
|
|
|
if ( dist > Square( repairDist ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !markedForRepair ) {
|
|
if ( dist > Square( range ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, deployableInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
deployableNum = botWorld->deployableInfo[ i ].entNum;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return deployableNum;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::GetDeployableInfo
|
|
|
|
Returns all the info about a particular deployable.
|
|
==================
|
|
*/
|
|
bool idBotAI::GetDeployableInfo( bool selfDeployable, int entNum, deployableInfo_t& deployableInfo ) {
|
|
bool hasSlot = false;
|
|
deployableInfo.entNum = 0;
|
|
|
|
if ( selfDeployable ) {
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].ownerClientNum != botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botWorld->deployableInfo[ i ].inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
deployableInfo = botWorld->deployableInfo[ i ];
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
} else {
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
deployableInfo = botWorld->deployableInfo[ i ];
|
|
hasSlot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hasSlot;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::DeployableAtAction
|
|
==================
|
|
*/
|
|
bool idBotAI::DeployableAtAction( int actionNumber, bool checkOwnDeployable ) {
|
|
bool hasDeployable = false;
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botWorld->deployableInfo[ i ].origin - botThreadData.botActions[ actionNumber ]->GetActionOrigin();
|
|
float dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( botThreadData.botActions[ i ]->GetRadius() ) && dist > Square( 256.0f ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( checkOwnDeployable ) {
|
|
if ( botWorld->deployableInfo[ i ].ownerClientNum != botNum ) {
|
|
hasDeployable = true;
|
|
break;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].health > ( botWorld->deployableInfo[ i ].maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
hasDeployable = true;
|
|
break;
|
|
}
|
|
} else {
|
|
hasDeployable = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hasDeployable;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_HasWorkingDeployable
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_HasWorkingDeployable( bool allowDisabledDeployables, int deployableType ) {
|
|
bool hasDeployable = false;
|
|
int i;
|
|
|
|
if ( botWorld->gameLocalInfo.botsUseDeployables == false ) { //mal: admin doesn't want us to use deployables.
|
|
return true;
|
|
}
|
|
|
|
for( i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].ownerClientNum != botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->deployableInfo[ i ].health < ( botWorld->deployableInfo[ i ].maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botWorld->deployableInfo[ i ].inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !allowDisabledDeployables ) {
|
|
if ( botWorld->deployableInfo[ i ].disabled ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( deployableType != NULL_DEPLOYABLE ) {
|
|
if ( botWorld->deployableInfo[ i ].type != deployableType ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasDeployable = true;
|
|
}
|
|
|
|
return hasDeployable;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::FindMCPStartAction
|
|
==================
|
|
*/
|
|
bool idBotAI::FindMCPStartAction( idVec3& origin ) {
|
|
|
|
bool hasMCP = false;
|
|
int i;
|
|
|
|
|
|
for( i = 0; i < botThreadData.botActions.Num(); i++ ) {
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsActive() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsValid() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_MCP_START ) {
|
|
continue;
|
|
}
|
|
|
|
origin = botThreadData.botActions[ i ]->GetActionOrigin();
|
|
hasMCP = true;
|
|
break;
|
|
}
|
|
|
|
return hasMCP;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_GetDeployableTypeForAction
|
|
==================
|
|
*/
|
|
int idBotAI::Bot_GetDeployableTypeForAction( int actionNumber ) {
|
|
|
|
bool hasABM = Bot_CheckTeamHasDeployableTypeNearAction( botInfo->team, AIT, actionNumber, DEFAULT_DEPLOYABLE_COVERAGE_RANGE );
|
|
int i;
|
|
int actionDeployableFlags = botThreadData.botActions[ actionNumber ]->GetDeployableType();
|
|
idList< int > options;
|
|
|
|
if ( actionDeployableFlags == NULL_DEPLOYABLE ) {
|
|
return NULL_DEPLOYABLE;
|
|
}
|
|
|
|
if ( botInfo->classType == COVERTOPS ) { //mal: that was easy!
|
|
return RADAR;
|
|
} else if ( botInfo->classType == FIELDOPS ) {
|
|
if ( actionDeployableFlags == NUKE ) {
|
|
return NUKE;
|
|
} else if ( actionDeployableFlags == ARTILLERY ) {
|
|
return ARTILLERY;
|
|
} else if ( actionDeployableFlags == ROCKET_ARTILLERY ) {
|
|
return ROCKET_ARTILLERY;
|
|
} else {
|
|
if ( actionDeployableFlags & NUKE ) {
|
|
options.Append( NUKE );
|
|
}
|
|
|
|
if ( actionDeployableFlags & ARTILLERY ) {
|
|
options.Append( ARTILLERY );
|
|
}
|
|
|
|
if ( actionDeployableFlags & ROCKET_ARTILLERY ) {
|
|
// if ( !Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, ROCKET_ARTILLERY, botThreadData.botActions[ actionNumber ]->GetActionOrigin(), idMath::INFINITY ) ) { //mal: Kevin wants to try with always at least 1 rocket arty on the ground.
|
|
// options.SetNum( 0, false );
|
|
// }
|
|
|
|
options.Append( ROCKET_ARTILLERY );
|
|
}
|
|
|
|
if ( options.Num() == 0 ) {
|
|
assert( false );
|
|
return NULL_DEPLOYABLE;
|
|
}
|
|
|
|
i = botThreadData.random.RandomInt( options.Num() );
|
|
|
|
return options[ i ];
|
|
}
|
|
} else if ( botInfo->classType == ENGINEER ) {
|
|
if ( actionDeployableFlags == APT ) {
|
|
return APT;
|
|
} else if ( actionDeployableFlags == AVT ) {
|
|
return AVT;
|
|
} else if ( actionDeployableFlags == AIT && !hasABM ) { //mal: only need 1 anti missile!
|
|
return AIT;
|
|
} else {
|
|
if ( actionDeployableFlags & APT ) {
|
|
options.Append( APT );
|
|
}
|
|
|
|
if ( actionDeployableFlags & AVT ) {
|
|
options.Append( AVT );
|
|
}
|
|
|
|
if ( actionDeployableFlags & AIT ) {
|
|
if ( !hasABM ) {
|
|
options.Append( AIT );
|
|
}
|
|
}
|
|
|
|
if ( options.Num() == 0 ) {
|
|
return NULL_DEPLOYABLE;
|
|
}
|
|
|
|
i = botThreadData.random.RandomInt( options.Num() );
|
|
|
|
return options[ i ];
|
|
}
|
|
}
|
|
|
|
return NULL_DEPLOYABLE;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckTeamHasDeployableTypeNearAction
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckTeamHasDeployableTypeNearAction( const playerTeamTypes_t playerTeam, int deployableType, int actionNumber, float dist ) {
|
|
bool hasDeploy = false;
|
|
idVec3 vec;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerTeam != NOTEAM ) {
|
|
if ( deployable.team != playerTeam ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( deployableType != NULL_DEPLOYABLE ) {
|
|
if ( deployable.type != deployableType ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.ownerClientNum != -1 ) {
|
|
if ( deployable.health == 0 && deployable.maxHealth == 0 ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = deployable.origin - botThreadData.botActions[ actionNumber ]->GetActionOrigin();
|
|
|
|
if ( vec.LengthSqr() > Square( dist ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasDeploy = true;
|
|
break;
|
|
}
|
|
|
|
return hasDeploy;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckTeamHasDeployableTypeNearLocation
|
|
|
|
If dist == -1.0f, this becomes a general "does team have this deployable at all" check.
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckTeamHasDeployableTypeNearLocation( const playerTeamTypes_t playerTeam, int deployableType, const idVec3& location, float dist ) {
|
|
bool hasDeploy = false;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerTeam != NOTEAM ) {
|
|
if ( deployable.team != playerTeam ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( deployableType != NULL_DEPLOYABLE ) {
|
|
if ( deployable.type != deployableType ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist != -1.0f ) {
|
|
idVec3 vec = deployable.origin - location;
|
|
|
|
if ( vec.LengthSqr() > Square( dist ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasDeploy = true;
|
|
break;
|
|
}
|
|
|
|
return hasDeploy;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckEnemyHasLockOn
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckEnemyHasLockOn( int clientNum, bool ignoreTargetIfRangeIsGreat ) {
|
|
if ( clientNum == -1 ) {
|
|
bool enemyHasLockon = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team == botInfo->team || player.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.targetLockEntNum == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.targetLockEntNum != botNum ) {
|
|
if ( player.targetLockEntNum != botInfo->proxyInfo.entNum ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ignoreTargetIfRangeIsGreat ) {
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
|
|
#ifdef _XENON
|
|
float avoidDist = FLYER_WORRY_ABOUT_ROCKETS_MAX_DIST; //mal: make it a bit easier on the Xbox to take out flyers.
|
|
#else
|
|
float avoidDist = 3500.0f;
|
|
#endif
|
|
|
|
if ( vec.LengthSqr() > Square( avoidDist ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
enemyHasLockon = true;
|
|
break;
|
|
}
|
|
|
|
return enemyHasLockon;
|
|
} else {
|
|
if ( botWorld->clientInfo[ clientNum ].targetLockEntNum == -1 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botWorld->clientInfo[ clientNum ].targetLockEntNum != botNum ) {
|
|
if ( botWorld->clientInfo[ clientNum ].targetLockEntNum != botInfo->proxyInfo.entNum ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckForSpawnHostToGrab
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckForSpawnHostToGrab() {
|
|
|
|
if ( botInfo->spawnHostEntNum != -1 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !botWorld->gameLocalInfo.botsUseSpawnHosts ) {
|
|
return false;
|
|
}
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
int spawnHost = -1;
|
|
int busyClient;
|
|
float closest = idMath::INFINITY;
|
|
idVec3 vec;
|
|
|
|
for( int i = 0; i < MAX_SPAWNHOSTS; i++ ) {
|
|
if ( SpawnHostIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->spawnHosts[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->spawnHosts[ i ].areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( SpawnHostIsUsed( botWorld->spawnHosts[ i ].entNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
//mal: should only grab a spawnhost if its fairly close to the strogg's goal.
|
|
if ( botWorld->botGoalInfo.team_STROGG_PrimaryAction != ACTION_NULL ) {
|
|
vec = botWorld->spawnHosts[ i ].origin - botThreadData.botActions[ botWorld->botGoalInfo.team_STROGG_PrimaryAction ]->GetActionOrigin();
|
|
if ( vec.LengthSqr() > Square( SPAWNHOST_RELEVANT_DIST ) ) { //mal: this is too far away from the obj to matter(?)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec = botWorld->spawnHosts[ i ].origin - botInfo->origin;
|
|
|
|
float dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( SPAWNHOST_RANGE ) ) { //mal: too far away - ignore!
|
|
continue;
|
|
}
|
|
|
|
if ( !Bot_NBGIsAvailable( i, ACTION_NULL, GRAB_SPAWNHOST, busyClient ) ) {
|
|
continue;
|
|
}
|
|
|
|
int mates = ClientsInArea( botNum, botWorld->spawnHosts[ i ].origin, 350.0f, botInfo->team , NOCLASS, false, false, false, false, true, true );
|
|
|
|
if ( mates > 0 ) { //mal: defer the spawnhost choice to the human
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, botWorld->spawnHosts[ i ].origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
spawnHost = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
if ( spawnHost != -1 ) {
|
|
aiState = NBG;
|
|
nbgTarget = spawnHost;
|
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_GrabSpawnHost;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_HasTeamWoundedInArea
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_HasTeamWoundedInArea( bool deadOnly, float medicRange ) {
|
|
bool hasWounded = false;
|
|
int playerHealth = ( deadOnly ) ? 0 : 60;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( !player.inGame || player.team == NOTEAM || player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.health > playerHealth || player.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( medicRange ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasWounded = true;
|
|
break;
|
|
}
|
|
|
|
return hasWounded;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClientHasNadeInWorld
|
|
|
|
Just a quick and dirty test to see if the client has an active grenade somewhere out there.
|
|
==================
|
|
*/
|
|
bool idBotAI::ClientHasNadeInWorld( int clientNum ) {
|
|
bool hasNade = false;
|
|
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
|
|
|
|
for( int i = 0; i < MAX_GRENADES; i++ ) {
|
|
if ( client.weapInfo.grenades[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
hasNade = true;
|
|
break;
|
|
}
|
|
|
|
return hasNade;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::PointIsClearOfTeammates
|
|
==================
|
|
*/
|
|
bool idBotAI::PointIsClearOfTeammates( const idVec3& point ) {
|
|
bool isClear = true;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) { //mal: dont scan ourselves.
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.inGame == false || player.team == NOTEAM || player.team != botInfo->team || player.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 end = player.origin - point;
|
|
float z = end.z;
|
|
|
|
if ( idMath::Fabs( z ) > 128.0f ) {
|
|
continue;
|
|
}
|
|
|
|
if ( end.LengthSqr() > Square( SAFE_PLAYER_BODY_WIDTH ) ) {
|
|
continue;
|
|
}
|
|
|
|
isClear = false;
|
|
break;
|
|
}
|
|
|
|
return isClear;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::EntityIsClient
|
|
==================
|
|
*/
|
|
bool idBotAI::EntityIsClient( int entNum, bool enemyOnly ) {
|
|
if ( entNum > -1 && entNum < MAX_CLIENTS ) {
|
|
if ( enemyOnly ) {
|
|
if ( botWorld->clientInfo[ entNum ].team != botInfo->team ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::EntityIsVehicle
|
|
==================
|
|
*/
|
|
bool idBotAI::EntityIsVehicle( int entNum, bool enemyOnly, bool occupiedOnly ) {
|
|
proxyInfo_t vehicleInfo;
|
|
GetVehicleInfo( entNum, vehicleInfo );
|
|
|
|
if ( vehicleInfo.entNum == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( occupiedOnly ) {
|
|
if ( vehicleInfo.isEmpty ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( enemyOnly ) {
|
|
if ( vehicleInfo.team == botInfo->team ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::EntityIsDeployable
|
|
==================
|
|
*/
|
|
bool idBotAI::EntityIsDeployable( int entNum, bool enemyOnly ) {
|
|
deployableInfo_t deployableInfo;
|
|
|
|
GetDeployableInfo( false, entNum, deployableInfo );
|
|
|
|
if ( deployableInfo.entNum == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( enemyOnly ) {
|
|
if ( deployableInfo.team == botInfo->team ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckForHumanWantingEscort
|
|
|
|
Check to see if theres a human out there that wants us to follow them. Even tho we might know we're too busy, still need to check to see if anyone
|
|
asked us for a escort, so we can can let them know we heard their request, but are too busy to do so.
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckForHumanWantingEscort() {
|
|
bool botTooBusy = false;
|
|
bool carrierInWorld = CarrierInWorld();
|
|
bool skipSpeedCheck = false;
|
|
int clientNum = -1;
|
|
int maxEscortClients = 1;
|
|
float escortRange = ESCORT_RANGE;
|
|
|
|
if ( botWorld->gameLocalInfo.gameIsBotMatch || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { //mal: in SP mode, let the human have more control over the bots, no matter what ( even if the human is wrong ).
|
|
maxEscortClients = 3;
|
|
}
|
|
|
|
if ( botVehicleInfo != NULL ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( botInfo->isActor ) {
|
|
return false;
|
|
}
|
|
|
|
int botIsRequestedToEscortClient = Bot_GetRequestedEscortClient(); //mal: first, check to see if someone requested us to escort them using the context menu....
|
|
|
|
if ( botVehicleInfo == NULL && botIsRequestedToEscortClient > -1 && botIsRequestedToEscortClient < MAX_CLIENTS && ( aiState != LTG || ( ltgType != FOLLOW_TEAMMATE && ltgType != FOLLOW_TEAMMATE_BY_REQUEST ) || ltgTarget != botIsRequestedToEscortClient ) ) {
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ botIsRequestedToEscortClient ];
|
|
|
|
if ( ClientIsIgnored( botIsRequestedToEscortClient ) ) {
|
|
goto skipRequestedClientCheck;
|
|
}
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, TOO_CLOSE_TO_GOAL_TO_FOLLOW_DIST ) || ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum, TOO_CLOSE_TO_DELIVER_TO_FOLLOW_DIST ) ) ) {
|
|
Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 );
|
|
Bot_IgnoreClient( botIsRequestedToEscortClient, REQUEST_CONSIDER_TIME );
|
|
return false;
|
|
}
|
|
|
|
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
proxyInfo_t vehicle;
|
|
GetVehicleInfo( player.proxyInfo.entNum, vehicle );
|
|
|
|
if ( vehicle.driverEntNum != botIsRequestedToEscortClient && !VehicleGoalsExistForVehicle( vehicle ) ) { //mal: dont be a driver for a vehicle there are no goals on the map for.
|
|
Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 );
|
|
Bot_IgnoreClient( botIsRequestedToEscortClient, REQUEST_CONSIDER_TIME );
|
|
goto skipRequestedClientCheck;
|
|
}
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, player.origin, travelTime ) ) {
|
|
Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 );
|
|
Bot_IgnoreClient( botIsRequestedToEscortClient, REQUEST_CONSIDER_TIME );
|
|
goto skipRequestedClientCheck;
|
|
}
|
|
|
|
idList< int > busyClients;
|
|
|
|
int botsFollowingThisClient = Bot_NumClientsDoingLTGGoal( botIsRequestedToEscortClient, ACTION_NULL, FOLLOW_TEAMMATE, busyClients );
|
|
botsFollowingThisClient += Bot_NumClientsDoingLTGGoal( botIsRequestedToEscortClient, ACTION_NULL, FOLLOW_TEAMMATE_BY_REQUEST, busyClients );
|
|
|
|
if ( botsFollowingThisClient >= maxEscortClients ) { //mal: too many bots following this client ATM - dump one
|
|
int j = botThreadData.random.RandomInt( busyClients.Num() );
|
|
int botToRemoveFromFollowing = busyClients[ j ];
|
|
botThreadData.bots[ botToRemoveFromFollowing ]->ResetBotsAI( true );
|
|
}
|
|
|
|
ltgTarget = botIsRequestedToEscortClient;
|
|
ltgTargetSpawnID = botWorld->clientInfo[ botIsRequestedToEscortClient ].spawnID;
|
|
aiState = LTG;
|
|
ltgType = FOLLOW_TEAMMATE_BY_REQUEST;
|
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FollowMate;
|
|
return true;
|
|
}
|
|
|
|
skipRequestedClientCheck:
|
|
|
|
if ( !botWorld->gameLocalInfo.gameIsBotMatch && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) { //mal: in SP mode, let the human have more control over the bots, no matter what ( even if the human is wrong ).
|
|
if ( Client_IsCriticalForCurrentObj( botNum, 3000.0f ) && botWorld->gameLocalInfo.heroMode == false ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( botInfo->classType == MEDIC && Bot_HasTeamWoundedInArea( true ) ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( aiState == LTG && ( ltgType != CAMP_GOAL && ltgType != ROAM_GOAL && ltgType != DEFENSE_CAMP_GOAL ) ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( aiState == NBG && ( nbgType != CAMP && nbgType != HAZE_ENEMY && nbgType != SNIPE ) ) {
|
|
botTooBusy = true;
|
|
}
|
|
} else {
|
|
skipSpeedCheck = true;
|
|
escortRange = ESCORT_RANGE * 2.0f;
|
|
|
|
if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) {
|
|
if ( ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) {
|
|
if ( !botWorld->clientInfo[ ltgTarget ].isBot ) {
|
|
botTooBusy = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( i == botNum ) { //mal: dont scan ourselves.
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue; //mal: no valid client in this client slot!
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.lastChatTime[ NEED_ESCORT ] + REQUEST_CONSIDER_TIME < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = playerInfo.origin - botInfo->origin;
|
|
float distSqr = vec.LengthSqr();
|
|
|
|
if ( !botTooBusy && distSqr > Square( escortRange ) ) { //mal: they're too far away for us to start following! Let them know tho.
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( !botTooBusy && ( Client_IsCriticalForCurrentObj( botNum, TOO_CLOSE_TO_GOAL_TO_FOLLOW_DIST ) || ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum, TOO_CLOSE_TO_DELIVER_TO_FOLLOW_DIST ) ) ) ) {
|
|
botTooBusy = true;
|
|
} //mal: we go thru the trouble of this so that we'll let the player know we're doing busy - instead of just ignoring him ( which frustrates ppl ).
|
|
|
|
if ( !botTooBusy && playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
proxyInfo_t vehicleInfo;
|
|
GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo ); //mal: we may decide to ride along in his vehicle to cause trouble if he has a gunner seat open.
|
|
|
|
if ( !VehicleIsValid( vehicleInfo.entNum, skipSpeedCheck, true ) ) { //mal: make sure it has a seat open, and is easy to reach.
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( vehicleInfo.driverEntNum != i && !VehicleGoalsExistForVehicle( vehicleInfo ) ) { //mal: we can't be the driver of a vehicle if no goals exist on the map for it.
|
|
botTooBusy = true;
|
|
}
|
|
|
|
if ( !ClientHasObj( botNum ) && carrierInWorld ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( botTooBusy ) {
|
|
if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) && ltgTarget == i ) {
|
|
break;
|
|
}
|
|
|
|
if ( botInfo->isDisguised ) {
|
|
Bot_AddDelayedChat( botNum, IM_DISGUISED, 1 );
|
|
} else {
|
|
Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 );
|
|
}
|
|
Bot_IgnoreClient( i, REQUEST_CONSIDER_TIME );
|
|
break; //mal: only need to do it once, so now leave.
|
|
}
|
|
|
|
idList< int > busyClients;
|
|
|
|
int botsFollowingThisClient = Bot_NumClientsDoingLTGGoal( i, ACTION_NULL, FOLLOW_TEAMMATE, busyClients );
|
|
|
|
botsFollowingThisClient += Bot_NumClientsDoingLTGGoal( i, ACTION_NULL, FOLLOW_TEAMMATE_BY_REQUEST, busyClients );
|
|
|
|
if ( botsFollowingThisClient >= maxEscortClients ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
clientNum = i;
|
|
break;
|
|
}
|
|
|
|
if ( clientNum != -1 ) {
|
|
ltgTarget = clientNum;
|
|
ltgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID;
|
|
aiState = LTG;
|
|
ltgType = FOLLOW_TEAMMATE_BY_REQUEST;
|
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FollowMate;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
idBotAI::SpawnHostIsUsed
|
|
==================
|
|
*/
|
|
bool idBotAI::SpawnHostIsUsed( int entNum ) {
|
|
bool isUsed = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != STROGG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.spawnHostEntNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
isUsed = true;
|
|
break;
|
|
}
|
|
|
|
return isUsed;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_HasDeployableTargetGoals
|
|
|
|
Check if the bot should attack enemy deployables.
|
|
|
|
NOTE: we have code setup to allow the HOG to attack deployables, but it needs some more TLC before its ready for primetime.
|
|
Uncomment the "botVehicleInfo->type != HOG" line below to test.
|
|
==================
|
|
*/
|
|
int idBotAI::Bot_HasDeployableTargetGoals( bool hackDeployable ) {
|
|
bool botIsBigShot = Client_IsCriticalForCurrentObj( botNum, 2500.0f );
|
|
|
|
if ( botVehicleInfo == NULL ) {
|
|
if ( botIsBigShot && ( botInfo->classType != SOLDIER || botInfo->weapInfo.primaryWeapon != ROCKET ) ) { //mal: dont go destroy deployables if we're trying to win the game.
|
|
return -1;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) && ( botInfo->classType != SOLDIER || botInfo->weapInfo.primaryWeapon != ROCKET ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool inAttackVehicle = Bot_IsInHeavyAttackVehicle();
|
|
|
|
if ( !hackDeployable ) {
|
|
if ( !Bot_HasExplosives( false ) && !inAttackVehicle ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( ClientHasFireSupportInWorld( botNum ) && !inAttackVehicle ) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool inVehicle = ( botVehicleInfo != NULL ) ? true : false;
|
|
bool inAirAttackVehicle = ( inVehicle && botVehicleInfo->type > ICARUS ) ? true : false;
|
|
|
|
if ( inVehicle && botVehicleInfo->driverEntNum != botNum ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( inVehicle && !inAttackVehicle /*&& botVehicleInfo->type != HOG */ && ( !botIsBigShot || !inAirAttackVehicle ) ) { //mal: good idea to skip attacking deployables with weak vehicles?
|
|
return -1;
|
|
}
|
|
|
|
if ( botInfo->classType == MEDIC && !inVehicle ) { //mal: not really much a medic can do on foot - they should be out healing ppl!
|
|
return -1;
|
|
}
|
|
|
|
idList< int > deployableActions;
|
|
|
|
for( int i = 0; i < botThreadData.botActions.Num(); i++ ) {
|
|
if ( !botThreadData.botActions[ i ]->ActionIsActive() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsValid() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetBaseObjForTeam( GDF ) != ACTION_DROP_DEPLOYABLE && botThreadData.botActions[ i ]->GetBaseObjForTeam( STROGG ) != ACTION_DROP_DEPLOYABLE &&
|
|
botThreadData.botActions[ i ]->GetBaseObjForTeam( GDF ) != ACTION_DROP_PRIORITY_DEPLOYABLE && botThreadData.botActions[ i ]->GetBaseObjForTeam( STROGG ) != ACTION_DROP_PRIORITY_DEPLOYABLE ) {
|
|
continue;
|
|
}
|
|
|
|
deployableActions.Append( i );
|
|
}
|
|
|
|
bool hasMCP = ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum != -1 && botWorld->botGoalInfo.mapHasMCPGoal ) ? true : false;
|
|
|
|
int targetNum = -1;
|
|
float closest = idMath::INFINITY;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( DeployableIsIgnored( deployable.entNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.ownerClientNum == -1 ) { //mal: dont attack static, map based deployables.
|
|
continue;
|
|
}
|
|
|
|
if ( inVehicle ) { //mal: if in a vehicle - attack the APT until its destroyed if we're on the attacking team.
|
|
if ( botInfo->team != botWorld->botGoalInfo.attackingTeam || deployable.type != APT ) {
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !deployable.inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( deployable.entNum ) == true ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( hackDeployable || botInfo->classType == COVERTOPS ) { //mal_TODO: remove the 2nd part when get 3rd eye camera attack working!
|
|
if ( deployable.disabled ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bool isTargetingUs = ( deployable.enemyEntNum == botNum || ( botVehicleInfo != NULL && deployable.enemyEntNum == botVehicleInfo->entNum ) ) ? true : false;
|
|
|
|
if ( botInfo->team == GDF && !botWorld->botGoalInfo.team_GDF_AttackDeployables && !isTargetingUs ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->team == STROGG && !botWorld->botGoalInfo.team_STROGG_AttackDeployables && !isTargetingUs ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.gameMap == SLIPGATE ) { //mal: need some special love for this unique map.
|
|
if ( !Bot_CheckLocationIsOnSameSideOfSlipGate( deployable.origin ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !inVehicle ) {
|
|
if ( deployable.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( botVehicleInfo->type < ICARUS ) { //mal: air vehicles have no restrictions.
|
|
if ( deployable.areaNumVehicle == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botVehicleInfo->type == HOG ) {
|
|
int travelTime;
|
|
idVec3 deployableOrigin = deployable.origin;
|
|
deployableOrigin.z += DEPLOYABLE_ORIGIN_OFFSET;
|
|
|
|
if ( !Bot_LocationIsReachable( true, deployableOrigin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( deployableActions.Num() > 0 && !inVehicle ) {
|
|
int actionNumber = botThreadData.GetDeployableActionNumber( deployableActions, deployable.origin, deployable.team, -1 );
|
|
|
|
if ( actionNumber != ACTION_NULL ) {
|
|
if ( botThreadData.botActions[ actionNumber ]->noHack ) { //mal: extended the noHack flag, so that non-coverts on foot wouldn't try to attack them either.
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
idVec3 vec = deployable.origin - botInfo->origin;
|
|
float dist = vec.LengthSqr();
|
|
|
|
int numClients = 1; //mal: by default, most deployables only need 1 client to take them out.
|
|
|
|
bool botActionValid = false;
|
|
int botActionNum;
|
|
idVec3 botActionOrg;
|
|
|
|
if ( botInfo->team == GDF ) {
|
|
botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction;
|
|
} else {
|
|
botActionNum = botWorld->botGoalInfo.team_STROGG_PrimaryAction;
|
|
}
|
|
|
|
if ( botActionNum > -1 && botActionNum < botThreadData.botActions.Num() ) {
|
|
botActionValid = true;
|
|
botActionOrg = botThreadData.botActions[ botActionNum ]->GetActionOrigin();
|
|
}
|
|
|
|
//mal: set some kind of priority depending on the deployable type.
|
|
if ( deployable.type == AIT ) {
|
|
if ( botActionValid ) {
|
|
vec = botActionOrg - deployable.origin;
|
|
if ( vec.LengthSqr() < Square( ANTI_MISSILE_RANGE ) ) {
|
|
dist -= Square( 3000.0f ); //mal: becomes a BIG priority, if it guards an area we are interested in.
|
|
numClients = 2;
|
|
}
|
|
} else {
|
|
if ( dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
}
|
|
}
|
|
} else if ( deployable.type == AVT ) {
|
|
if ( hasMCP && botInfo->team == GDF ) {
|
|
if ( deployable.enemyEntNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) {
|
|
dist = 5.0f;
|
|
numClients = 4;
|
|
} else {
|
|
if ( inAirAttackVehicle ) {
|
|
dist = 5.0f;
|
|
} else if ( isTargetingUs ) {
|
|
dist -= Square( 5000.0f );
|
|
} else {
|
|
dist -= Square( 3000.0f ); //mal: AVT get priority in MCP missions, as they're usually what holds up our progress.
|
|
}
|
|
numClients = 3;
|
|
}
|
|
} else {
|
|
if ( botActionValid ) {
|
|
vec = botActionOrg - deployable.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( GAME_PLAY_RANGE ) && dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
} else if ( isTargetingUs ) {
|
|
dist -= Square( 5000.0f );
|
|
} else {
|
|
dist -= Square( 3000.0f );
|
|
}
|
|
}
|
|
}
|
|
} else if ( deployable.type == APT ) {
|
|
if ( hackDeployable ) {
|
|
if ( !botInfo->isDisguised ) {
|
|
continue;
|
|
} else {
|
|
if ( botActionValid ) {
|
|
vec = botActionOrg - deployable.origin;
|
|
|
|
if ( vec.LengthSqr() < Square( GAME_PLAY_RANGE ) ) {
|
|
dist = 6.0f; //mal: hacking these things when we're disguised is the priority, especially if its guarding our goal!
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if ( botWorld->botGoalInfo.attackingTeam == botInfo->team ) {
|
|
if ( botActionValid ) {
|
|
vec = botActionOrg - deployable.origin;
|
|
|
|
if ( vec.LengthSqr() < Square( GAME_PLAY_RANGE ) ) {
|
|
if ( inAirAttackVehicle ) {
|
|
dist = 6.0f;
|
|
} else {
|
|
dist -= Square( 2000.0f );
|
|
}
|
|
numClients = 3;
|
|
} else {
|
|
if ( dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if ( dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE * 2.0f ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
} else if ( deployable.type == RADAR ) { //mal: I REALLY don't want the bots worrying about radar too much, unless theres nothing else out there.
|
|
if ( hackDeployable ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botActionValid ) {
|
|
vec = botActionOrg - deployable.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( BOT_ATTACK_DEPLOYABLE_RANGE * 2.0f ) && dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) ) {
|
|
continue;
|
|
} else {
|
|
dist += Square( 9000.0f );
|
|
}
|
|
} else {
|
|
if ( dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
} else {
|
|
dist += Square( 12000.0f );
|
|
}
|
|
}
|
|
} else if ( deployable.type == ARTILLERY || deployable.type == ROCKET_ARTILLERY || deployable.type == NUKE ) {
|
|
if ( !hackDeployable ) {
|
|
if ( dist > Square( BOT_ATTACK_DEPLOYABLE_RANGE ) && !inAirAttackVehicle ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if ( dist <= 1.0f ) {
|
|
// dist = 1.0f;
|
|
// }
|
|
|
|
if ( inVehicle ) {
|
|
if ( Bot_VehicleIsUnderAVTAttack() == deployable.entNum && deployable.type == AVT ) {
|
|
dist = 1.0f; //mal: if its attacking us, its RIGHT next to us as far as we're concerned.
|
|
} else {
|
|
if ( !Bot_VehicleLTGIsAvailable( deployable.entNum, ACTION_NULL, V_DESTROY_DEPLOYABLE, numClients ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
if ( !Bot_LTGIsAvailable( deployable.entNum, ACTION_NULL, DESTROY_DEPLOYABLE_GOAL, numClients ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( DeployableIsMarkedForDeath( deployable.entNum, false ) ) {
|
|
dist = 2.0f; //mal: marked for death, only a deployable attacking us would be more important.
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
if ( !inAirAttackVehicle ) { //mal: assume a flying vehicle can go anywhere.
|
|
int travelTime;
|
|
bool canReach = Bot_LocationIsReachable( false /*inVehicle*/, deployable.origin, travelTime ); //mal: sometimes APTS are in areas vehicles can't reach with AAS, but are reachable in the AAS, and a valid path is possible, so always use the player AAS.
|
|
|
|
if ( !canReach ) { //mal: can't reach the target - prolly behind a team specific shield/wall/etc.
|
|
continue;
|
|
}
|
|
|
|
if ( inVehicle ) { //mal: see if we can find a node path to our target. If not, could be on a part of the map we don't currently have access to.
|
|
idBotNode* ourNode = botThreadData.botVehicleNodes.GetNearestNode( botVehicleInfo->axis, botVehicleInfo->origin, botInfo->team, NULL_DIR, false, true, false, NULL, botVehicleInfo->flags );
|
|
idBotNode* goalNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, deployable.origin, botInfo->team, NULL_DIR, false, true, false, NULL, botVehicleInfo->flags ); // always assume the goal node is reachable
|
|
|
|
if ( ourNode == NULL || goalNode == NULL ) { //mal: crap. :-/
|
|
continue;
|
|
}
|
|
|
|
if ( goalNode != ourNode ) {
|
|
idList< idBotNode::botLink_t > pathList;
|
|
botThreadData.botVehicleNodes.CreateNodePath( botInfo, ourNode, goalNode, pathList, botVehicleInfo->flags );
|
|
|
|
if ( pathList.Num() == 0 ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
targetNum = deployable.entNum;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
return targetNum;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClientHasFireSupportInWorld
|
|
==================
|
|
*/
|
|
bool idBotAI::ClientHasFireSupportInWorld( int clientNum ) {
|
|
|
|
if ( !ClientIsValid( clientNum, -1 ) ) {
|
|
return false;
|
|
}
|
|
|
|
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
|
|
|
|
if ( client.weapInfo.airStrikeInfo.timeTilStrike > botWorld->gameLocalInfo.time || client.weapInfo.airStrikeInfo.entNum != 0 ) {
|
|
return true;
|
|
}
|
|
|
|
if ( client.weapInfo.artyAttackInfo.deathTime > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::TeamMineInArea
|
|
==================
|
|
*/
|
|
bool idBotAI::TeamMineInArea( const idVec3& org, float range ) {
|
|
bool mineNearby = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( mineNearby ) {
|
|
break;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
for( int j = 0; j < MAX_MINES; j++ ) {
|
|
|
|
if ( player.weapInfo.landMines[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.weapInfo.landMines[ j ].origin - org;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) {
|
|
continue;
|
|
}
|
|
|
|
mineNearby = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return mineNearby;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_IsBusy
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_IsBusy() {
|
|
if ( aiState == NBG && ( nbgType != CAMP && nbgType != SNIPE && nbgType != DEFENSE_CAMP && nbgType != INVESTIGATE_CAMP ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::ClientsNearObj
|
|
==================
|
|
*/
|
|
bool idBotAI::ClientsNearObj( const playerTeamTypes_t& playerTeam ) {
|
|
|
|
bool hasEnemy = true;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != playerTeam ) {
|
|
continue;
|
|
}
|
|
|
|
if ( LocationDistFromCurrentObj( botInfo->team, player.origin ) > OBJ_AWARENESS_DIST ) {
|
|
continue;
|
|
}
|
|
|
|
hasEnemy = true;
|
|
break;
|
|
}
|
|
|
|
return hasEnemy;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::CriticalEnemyClientNearUs
|
|
==================
|
|
*/
|
|
bool idBotAI::CriticalEnemyClientNearUs( const playerClassTypes_t& playerClass ) {
|
|
|
|
bool hasEnemy = true;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerClass != NOCLASS ) {
|
|
if ( player.classType != playerClass ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( BOT_AWARENESS_DIST ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerClass == SOLDIER ) {
|
|
if ( !ClientHasChargeInWorld( i, true, -1 ) && !Client_IsCriticalForCurrentObj( i, -1.0f ) ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( !Client_IsCriticalForCurrentObj( i, -1.0f ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasEnemy = true;
|
|
break;
|
|
}
|
|
|
|
return hasEnemy;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckNeedClientsOnDefense
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckNeedClientsOnDefense() {
|
|
int numBots = botThreadData.GetNumBotsOnTeam( botInfo->team );
|
|
int numDefenders = 0;
|
|
int numNeededDefenders;
|
|
|
|
if ( numBots == 1 ) { //mal: we need a defender - and its going to be this bot.
|
|
return true;
|
|
} else {
|
|
numNeededDefenders = ( numBots / 2 );
|
|
}
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() == LTG && botThreadData.bots[ i ]->GetLTGType() == DEFENSE_CAMP_GOAL ) {
|
|
numDefenders++;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() == NBG && botThreadData.bots[ i ]->GetNBGType() == DEFENSE_CAMP ) {
|
|
numDefenders++;
|
|
}
|
|
}
|
|
|
|
if ( numDefenders < numNeededDefenders ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_EnemyAITInArea
|
|
|
|
Look for AIT in range of our target. We count base AIT in here as well.
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_EnemyAITInArea( const idVec3& org ) {
|
|
bool hasAIT = false;
|
|
float range;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.type != AIT ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.ownerClientNum != -1 ) { //mal: turrets with an owner of -1 are base turrets - need to count them too!
|
|
if ( deployable.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !deployable.inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.disabled ) {
|
|
continue;
|
|
}
|
|
|
|
range = ANTI_MISSILE_RANGE;
|
|
} else {
|
|
range = BASE_ANTI_MISSILE_RANGE;
|
|
}
|
|
|
|
idVec3 vec = deployable.origin - org;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasAIT = true;
|
|
break;
|
|
}
|
|
|
|
return hasAIT;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_CheckActionIsValid
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_CheckActionIsValid( int actionNumber ) {
|
|
if ( actionNumber <= ACTION_NULL || actionNumber >= botThreadData.botActions.Num() ) {
|
|
// assert( false );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_IsInvestigatingTeamObj
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_IsInvestigatingTeamObj() {
|
|
if ( aiState != LTG || ltgType != INVESTIGATE_ACTION ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_IsDefendingTeamCharge
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_IsDefendingTeamCharge() {
|
|
if ( aiState != LTG || ltgType != PROTECT_CHARGE ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::Bot_IsAttackingDeployables
|
|
==================
|
|
*/
|
|
bool idBotAI::Bot_IsAttackingDeployables() {
|
|
if ( botVehicleInfo != NULL ) {
|
|
if ( aiState == VLTG && vLTGType == V_DESTROY_DEPLOYABLE ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if ( ( aiState == LTG && ltgType == DESTROY_DEPLOYABLE_GOAL ) || ( aiState == NBG && ( nbgType == DESTROY_DEPLOYABLE || nbgType == ENG_ATTACK_AVT ) ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::FindEntityType
|
|
==================
|
|
*/
|
|
const entityTypes_t idBotAI::FindEntityType( int entNum, int spawnID ) {
|
|
if ( entNum < MAX_CLIENTS ) {
|
|
if ( !ClientIsValid( entNum, -1 ) ) {
|
|
return ENTITY_NULL;
|
|
}
|
|
|
|
if ( spawnID != -1 ) {
|
|
if ( botWorld->clientInfo[ entNum ].spawnID != spawnID ) {
|
|
return ENTITY_NULL;
|
|
}
|
|
}
|
|
|
|
return ENTITY_PLAYER;
|
|
}
|
|
|
|
for( int i = 0; i < MAX_VEHICLES; i++ ) {
|
|
const proxyInfo_t& vehicleInfo = botWorld->vehicleInfo[ i ];
|
|
|
|
if ( vehicleInfo.entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( spawnID != -1 ) {
|
|
if ( vehicleInfo.spawnID != spawnID ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return ENTITY_VEHICLE;
|
|
}
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployableInfo = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployableInfo.entNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( spawnID != -1 ) {
|
|
if ( deployableInfo.spawnID != spawnID ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return ENTITY_DEPLOYABLE;
|
|
}
|
|
|
|
return ENTITY_NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::DeployableIsMarkedForRepair
|
|
|
|
Checks to see if this deployable has been marked for repair.
|
|
================
|
|
*/
|
|
bool idBotAI::DeployableIsMarkedForRepair( int entNum, bool clearRequest ) {
|
|
deployableInfo_t deployable;
|
|
|
|
if ( !GetDeployableInfo( false, entNum, deployable ) ) {
|
|
return false;
|
|
}
|
|
|
|
bool isMarked = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetNum == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetUpdateTime + MAX_TARGET_TIME < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetNum < MAX_CLIENTS ) {
|
|
continue;
|
|
}
|
|
|
|
entityTypes_t entityType = FindEntityType( player.repairTargetNum, player.repairTargetSpawnID );
|
|
|
|
if ( entityType == ENTITY_NULL || entityType == ENTITY_VEHICLE || entityType == ENTITY_PLAYER ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.entNum != player.repairTargetNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( clearRequest ) {
|
|
botUcmd->ackRepairForClient = i;
|
|
}
|
|
|
|
isMarked = true;
|
|
break;
|
|
}
|
|
|
|
return isMarked;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::VehicleIsMarkedForRepair
|
|
|
|
Checks to see if this vehicle has been marked for repair.
|
|
================
|
|
*/
|
|
bool idBotAI::VehicleIsMarkedForRepair( int entNum, bool clearRequest ) {
|
|
proxyInfo_t vehicle;
|
|
GetVehicleInfo( entNum, vehicle );
|
|
|
|
if ( vehicle.entNum == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
bool isMarked = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetNum == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetUpdateTime + MAX_TARGET_TIME < botWorld->gameLocalInfo.time ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.repairTargetNum < MAX_CLIENTS ) {
|
|
continue;
|
|
}
|
|
|
|
entityTypes_t entityType = FindEntityType( player.repairTargetNum, player.repairTargetSpawnID );
|
|
|
|
if ( entityType == ENTITY_NULL || entityType == ENTITY_DEPLOYABLE || entityType == ENTITY_PLAYER ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicle.entNum != player.repairTargetNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicle.health == vehicle.maxHealth ) {
|
|
botUcmd->ackRepairForClient = i;
|
|
continue;
|
|
}
|
|
|
|
if ( clearRequest ) {
|
|
botUcmd->ackRepairForClient = i;
|
|
}
|
|
|
|
isMarked = true;
|
|
break;
|
|
}
|
|
|
|
return isMarked;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckAdrenaline
|
|
|
|
Checks to see if the bot should use its adrenaline ability
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckAdrenaline( const idVec3& goalOrigin ) {
|
|
bool canShockSelf = ( botInfo->team == GDF ) ? botInfo->abilities.gdfAdrenaline : botInfo->abilities.stroggAdrenaline;
|
|
|
|
if ( !canShockSelf ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->health < ( botInfo->maxHealth - 20 ) ) { //mal: makes more sense for him to just use packs on himself.
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->classChargeUsed > 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( goalOrigin == vec3_zero ) {
|
|
if ( enemy == -1 && ( ClientHasObj( botNum ) || botInfo->enemiesInArea > 0 || ( botInfo->lastAttackerTime + 1000 ) > botWorld->gameLocalInfo.time ) ) { //mal: keep ourselves topped off with max health, to help turn the tide in battle.
|
|
botIdealWeapNum = NEEDLE;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( botInfo->weapInfo.weapon == NEEDLE ) {
|
|
botUcmd->botCmds.altFire = true;
|
|
if ( selfShockTime < botWorld->gameLocalInfo.time ) {
|
|
selfShockTime = botWorld->gameLocalInfo.time + 1000;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if ( botInfo->weapInfo.weapon != NEEDLE ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->enemiesInArea > 0 || enemy != -1 || ( botInfo->lastAttackerTime + 1000 ) > botWorld->gameLocalInfo.time ) {
|
|
idVec3 vec = goalOrigin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 100.0f ) ) {
|
|
botUcmd->botCmds.altFire = true;
|
|
if ( selfShockTime < botWorld->gameLocalInfo.time ) {
|
|
selfShockTime = botWorld->gameLocalInfo.time + 1000;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckCovertToolState
|
|
|
|
Checks to see if the bot should use covert tool.
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckCovertToolState() {
|
|
if ( botInfo->team == GDF && botInfo->weapInfo.covertToolInfo.entNum != 0 ) {
|
|
idVec3 vec = botInfo->weapInfo.covertToolInfo.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( THIRD_EYE_RANGE ) ) {
|
|
return false;
|
|
}
|
|
|
|
int enemiesInArea = ClientsInArea( botNum, botInfo->weapInfo.covertToolInfo.origin, 500.0f, STROGG, NOCLASS, false, false, false, false, false );
|
|
int friendsInArea = ClientsInArea( -1, botInfo->weapInfo.covertToolInfo.origin, 300.0f, GDF, NOCLASS, false, false, false, false, false );
|
|
bool deployablesInArea = Bot_CheckTeamHasDeployableTypeNearLocation( STROGG, NULL_DEPLOYABLE, botInfo->weapInfo.covertToolInfo.origin, 700.0f );
|
|
|
|
if ( friendsInArea == 0 && ( enemiesInArea > 0 || deployablesInArea ) ) {
|
|
botIdealWeapNum = THIRD_EYE;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( botInfo->weapInfo.weapon == THIRD_EYE ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // else if we are strogg - have our flyer hive chase down a nearby enemy and blow up on them. TODO!
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::CurrentActionIsLinkedToAction
|
|
================
|
|
*/
|
|
bool idBotAI::CurrentActionIsLinkedToAction( int curActionNum, int testActionNum ) {
|
|
if ( !Bot_CheckActionIsValid( curActionNum ) || !Bot_CheckActionIsValid( testActionNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ curActionNum ]->GetActionGroup() != botThreadData.botActions[ testActionNum ]->GetActionGroup() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ( botThreadData.botActions[ curActionNum ]->GetHumanObj() != botThreadData.botActions[ testActionNum ]->GetHumanObj() ) && ( botThreadData.botActions[ curActionNum ]->GetStroggObj() != botThreadData.botActions[ testActionNum ]->GetStroggObj() ) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::FindLinkedActionsForAction
|
|
================
|
|
*/
|
|
void idBotAI::FindLinkedActionsForAction( int testActionNum, idList< int >& testActionList, idList< int >& linkedActionList ) {
|
|
for( int i = 0; i < testActionList.Num(); i++ ) {
|
|
if ( testActionList[ i ] == testActionNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !CurrentActionIsLinkedToAction( testActionList[ i ], testActionNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
linkedActionList.Append( testActionList[ i ] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckLocationIsOnSameSideOfSlipGate
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckLocationIsOnSameSideOfSlipGate( const idVec3& origin ) {
|
|
bool botOnDesertSide = ( botInfo->origin.x < SLIPGATE_DIVIDING_PLANE_X_VALUE );
|
|
bool locOnDesertSide = ( origin.x < SLIPGATE_DIVIDING_PLANE_X_VALUE );
|
|
|
|
if ( botOnDesertSide != locOnDesertSide ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckLocationIsVisible
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckLocationIsVisible( const idVec3& location, int scanEntityNum, int ignoreEntNum, bool scanFromCrouch ) {
|
|
trace_t tr;
|
|
|
|
idVec3 botViewOrigin = botInfo->origin;
|
|
botViewOrigin.z += ( scanFromCrouch ) ? 0.0f : botWorld->gameLocalInfo.normalViewHeight;
|
|
|
|
botThreadData.clip->TracePointExt( CLIP_DEBUG_PARMS tr, botViewOrigin, location, MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, GetGameEntity( botNum ), GetGameEntity( ignoreEntNum ) );
|
|
|
|
if ( tr.fraction < 1.0f ) {
|
|
if ( scanEntityNum != -1 && tr.c.entityNum == scanEntityNum ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckHasUnArmedMineNearby
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckHasUnArmedMineNearby() {
|
|
bool hasMine = false;
|
|
|
|
for( int i = 0; i < MAX_MINES; i++ ) {
|
|
if ( botInfo->weapInfo.landMines[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botInfo->weapInfo.landMines[ i ].state == BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botInfo->weapInfo.landMines[ i ].origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( CLOSE_MINE_DIST ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasMine = true;
|
|
break;
|
|
}
|
|
|
|
return hasMine;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IsNearDroppedObj
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_IsNearDroppedObj() {
|
|
if ( botWorld->gameLocalInfo.inWarmup ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->isActor ) {
|
|
return false;
|
|
}
|
|
|
|
if ( Client_IsCriticalForCurrentObj( botNum, -1 ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == LTG && ( ltgType == DEFUSE_GOAL || ltgType == PLANT_GOAL || ltgType == BUILD_GOAL || ltgType == HACK_GOAL ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == NBG && ( nbgType == BUILD || nbgType == HACK || nbgType == DEFUSE_BOMB || nbgType == PLANT_BOMB ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == NBG && nbgType == REVIVE_TEAMMATE && botInfo->team == GDF ) { //mal: medics need a chance to finish their job! Strogg take too long to revive.
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == LTG && ( ltgType == RECOVER_GOAL || ltgType == STEAL_GOAL ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botInfo->classType == MEDIC && botInfo->team == GDF && Bot_HasTeamWoundedInArea( true ) ) { //mal: strogg medics take too long to revive to make this worthwhile
|
|
return false;
|
|
}
|
|
|
|
int bestObj = -1;
|
|
int vehicleNum;
|
|
float closest = idMath::INFINITY;
|
|
|
|
for( int i = 0; i < MAX_CARRYABLES; i++ ) {
|
|
if ( !botWorld->botGoalInfo.carryableObjs[ i ].onGround || botWorld->botGoalInfo.carryableObjs[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.carryableObjs[ i ].areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botWorld->botGoalInfo.carryableObjs[ i ].origin - botInfo->origin;
|
|
float dist = vec.LengthSqr();
|
|
|
|
if ( dist > Square( BOT_RECOVER_OBJ_RANGE ) ) {
|
|
continue;
|
|
}
|
|
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, botWorld->botGoalInfo.carryableObjs[ i ].origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dist < closest ) {
|
|
bestObj = i;
|
|
closest = dist;
|
|
}
|
|
}
|
|
|
|
if ( bestObj == -1 ) {
|
|
return false;
|
|
}
|
|
|
|
ltgTarget = bestObj;
|
|
|
|
idVec3 vec = botWorld->botGoalInfo.carryableObjs[ ltgTarget ].origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( 3000.0f ) && botWorld->gameLocalInfo.botsUseVehicles ) {
|
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, GROUND | AIR, NULL_VEHICLE_FLAGS, true );
|
|
|
|
if ( vehicleNum != -1 ) {
|
|
ltgUseVehicle = true;
|
|
} else {
|
|
ltgUseVehicle = false;
|
|
}
|
|
} else {
|
|
ltgUseVehicle = false;
|
|
}
|
|
|
|
if ( ltgUseVehicle ) {
|
|
if ( botVehicleInfo != NULL ) {
|
|
if ( botVehicleInfo->driverEntNum == botNum ) {
|
|
V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node;
|
|
V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal;
|
|
} else {
|
|
Bot_ExitVehicle();
|
|
}
|
|
}
|
|
} else {
|
|
if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle
|
|
Bot_ExitVehicle();
|
|
}
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.carryableObjs[ ltgTarget ].ownerTeam == botInfo->team ) {
|
|
ltgType = RECOVER_GOAL;
|
|
} else {
|
|
ltgType = STEAL_GOAL;
|
|
}
|
|
|
|
ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY;
|
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RecoverDroppedGoal;
|
|
|
|
if ( !ltgUseVehicle ) {
|
|
Bot_FindRouteToCurrentGoal();
|
|
}
|
|
|
|
PushAINodeOntoStack( -1, ltgTarget, ACTION_NULL, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckMountedGPMGState
|
|
|
|
Make sure the bot leaves the mountable GPMG if he doesn't want to be on it.
|
|
================
|
|
*/
|
|
void idBotAI::Bot_CheckMountedGPMGState() {
|
|
if ( aiState == LTG && botInfo->usingMountedGPMG ) {
|
|
botUcmd->botCmds.exitVehicle = true;
|
|
} else if ( aiState == NBG && botInfo->usingMountedGPMG && nbgType != MG_CAMP ) {
|
|
botUcmd->botCmds.exitVehicle = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_WantsVehicle
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_WantsVehicle() {
|
|
if ( botInfo->wantsVehicle && botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
|
|
if ( botWantsVehicleBackTime > botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_CheckIfClientHasChargeAsGoal
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_CheckIfClientHasChargeAsGoal( int chargeEntNum ) {
|
|
bool clientsDefusing = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( !player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.classType != ENGINEER ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() != NBG || botThreadData.bots[ i ]->GetNBGType() != DEFUSE_BOMB ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetNBGTarget() != chargeEntNum ) {
|
|
continue;
|
|
}
|
|
|
|
clientsDefusing = true;
|
|
break;
|
|
}
|
|
|
|
return clientsDefusing;
|
|
}
|
|
|
|
|
|
#define BOX_OFFSET 32.0f
|
|
|
|
|
|
/*
|
|
================
|
|
idBotAI::Bot_IsClearOfObstacle
|
|
================
|
|
*/
|
|
bool idBotAI::Bot_IsClearOfObstacle( const idBox& box, const float bboxHeight, const idVec3& botOrigin ) {
|
|
float minHeight, maxHeight;
|
|
|
|
box.AxisProjection( botAAS.aas->GetSettings()->invGravityDir, minHeight, maxHeight );
|
|
|
|
if ( ( minHeight > ( botOrigin.z + bboxHeight + BOX_OFFSET ) ) || ( maxHeight < ( botOrigin.z - BOX_OFFSET ) ) ) { //mal: dont see obstacles that are a floor above/below the player/vehicle.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBotAI::OtherBotsWantVehicle
|
|
================
|
|
*/
|
|
bool idBotAI::OtherBotsWantVehicle( const proxyInfo_t& vehicle ) {
|
|
bool vehicleIsWanted = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( !player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetAIState() != LTG || ( botThreadData.bots[ i ]->GetLTGUseVehicle() == false && botThreadData.bots[ i ]->GetLTGType() != GRAB_VEHICLE_GOAL && botThreadData.bots[ i ]->GetLTGType() != ENTER_HEAVY_VEHICLE ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.bots[ i ]->GetLTGTarget() != vehicle.entNum ) {
|
|
continue;
|
|
}
|
|
|
|
vehicleIsWanted = true;
|
|
break;
|
|
}
|
|
|
|
return vehicleIsWanted;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::GetPlayerViewPosition
|
|
============
|
|
*/
|
|
idVec3 idBotAI::GetPlayerViewPosition( int clientNum ) {
|
|
if ( !ClientIsValid( clientNum, -1 ) ) {
|
|
return vec3_zero;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
|
|
|
|
if ( player.isLeaning ) {
|
|
return player.viewOrigin;
|
|
}
|
|
|
|
if ( player.isLeaning || ( player.weapInfo.covertToolInfo.entNum != 0 && player.weapInfo.covertToolInfo.clientIsUsing == true ) ) {
|
|
idVec3 vec = player.origin;
|
|
vec.z += 48.0f;
|
|
|
|
if ( botThreadData.AllowDebugData() ) {
|
|
gameRenderWorld->DebugCircle( colorGreen, vec, idVec3( 0, 0, 1 ), 48, 32 );
|
|
}
|
|
return vec;
|
|
}
|
|
|
|
return player.viewOrigin;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_IsNearForwardSpawnToGrab
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_IsNearForwardSpawnToGrab() {
|
|
if ( Bot_WantsVehicle() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->isActor ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ( aiState == LTG && ( ltgType == FIX_MCP || ltgType == DRIVE_MCP ) ) || ( aiState == NBG && ( nbgType == ENG_ATTACK_AVT || nbgType == FIXING_MCP ) ) ) { //mal: the MCP has gotta be the priority!
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == LTG && ltgType == DEFUSE_GOAL ) { //mal: defusing is a priority!
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == NBG && ( nbgType == BUILD || nbgType == HACK || nbgType == DEFUSE_BOMB || nbgType == PLANT_BOMB ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.inWarmup ) {
|
|
return false;
|
|
}
|
|
|
|
if ( fdaUpdateTime > botWorld->gameLocalInfo.time ) {
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == LTG && ( ltgType == FDA_GOAL || ltgType == STEAL_SPAWN_GOAL ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( aiState == NBG && nbgType == GRAB_SPAWN ) {
|
|
return false;
|
|
}
|
|
|
|
fdaUpdateTime = botWorld->gameLocalInfo.time + 5000;
|
|
|
|
int spawnGoal = ACTION_NULL;
|
|
int stealSpawnGoal = ACTION_NULL;
|
|
|
|
for( int i = 0; i < botThreadData.botActions.Num(); i++ ) {
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsActive() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsValid() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_NULL ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_FORWARD_SPAWN && botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_DENY_SPAWNPOINT ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.isTrainingMap ) {
|
|
if ( !botThreadData.actorMissionInfo.forwardSpawnIsAllowed ) {
|
|
continue;
|
|
}
|
|
} else if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
|
if ( Bot_CheckForHumanInteractingWithEntity( botThreadData.botActions[ i ]->GetActionSpawnControllerEntNum() ) == true ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.isTrainingMap ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FORWARD_SPAWN ) {
|
|
if ( !ActionIsActiveForTrainingMode( i, botWorld->gameLocalInfo.botTrainingModeObjDelayTime ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do.
|
|
continue;
|
|
}
|
|
|
|
if ( !TeamHasHuman( botInfo->team ) ) { //mal: dont keep frustrating the human player by taking our spawn back too quick, in case he took it from us.
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ActionIsIgnored( i ) ) {
|
|
break;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FORWARD_SPAWN ) {
|
|
if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team ) { //mal: it already belongs to us.
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team || botThreadData.botActions[ i ]->GetTeamOwner() == NOTEAM ) { //mal: it already belongs to us.
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->botGoalInfo.isTrainingMap ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bot_LTG_Types_t ltgGoalType;
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FORWARD_SPAWN ) {
|
|
ltgGoalType = FDA_GOAL;
|
|
} else {
|
|
ltgGoalType = STEAL_SPAWN_GOAL;
|
|
}
|
|
|
|
if ( !Bot_LTGIsAvailable( -1, i, ltgGoalType, 1 ) ) {
|
|
break;
|
|
}
|
|
|
|
int busyClient;
|
|
|
|
if ( !Bot_NBGIsAvailable( -1, i, GRAB_SPAWN, busyClient ) ) {
|
|
break;
|
|
}
|
|
|
|
idVec3 distToSpawn = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin;
|
|
|
|
if ( distToSpawn.LengthSqr() > Square( FDA_AUTO_GRAB_DIST ) ) {
|
|
break;
|
|
}
|
|
|
|
int matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 350.0f, botInfo->team, NOCLASS, false, false, false, true, true );
|
|
|
|
if ( matesInArea > 0 ) {
|
|
break;
|
|
} //mal: already somebody there, so lets pick a different one to grab.
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FORWARD_SPAWN ) {
|
|
spawnGoal = i;
|
|
} else {
|
|
stealSpawnGoal = i;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( spawnGoal != ACTION_NULL ) {
|
|
ltgType = FDA_GOAL;
|
|
actionNum = spawnGoal;
|
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
|
ltgTime = botWorld->gameLocalInfo.time + 120000;
|
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_SpawnPointGoal;
|
|
Bot_FindRouteToCurrentGoal();
|
|
PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, false, ( routeNode != NULL ) ? true : false );
|
|
return true;
|
|
}
|
|
|
|
if ( stealSpawnGoal != ACTION_NULL ) {
|
|
ltgType = STEAL_SPAWN_GOAL;
|
|
actionNum = stealSpawnGoal;
|
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
|
ltgTime = botWorld->gameLocalInfo.time + 120000;
|
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_SpawnPointGoal;
|
|
Bot_FindRouteToCurrentGoal();
|
|
PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, false, ( routeNode != NULL ) ? true : false );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_GetDeployableOffSet
|
|
============
|
|
*/
|
|
float idBotAI::Bot_GetDeployableOffSet( int deployableType ) {
|
|
if ( deployableType == AVT ) {
|
|
return 0.45f;
|
|
} else if ( deployableType == APT ) {
|
|
return 0.40f;
|
|
} else if ( deployableType == RADAR ) {
|
|
return 0.30f;
|
|
} else if ( deployableType == NUKE ) {
|
|
return 0.40f;
|
|
} else if ( deployableType == AIT ) {
|
|
return 0.40f;
|
|
}
|
|
|
|
return 0.60f;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::ClientIsCloseToDeliverObj
|
|
============
|
|
*/
|
|
bool idBotAI::ClientIsCloseToDeliverObj( int clientNum, float desiredRange ) {
|
|
if ( botWorld->botGoalInfo.deliverActionNumber <= ACTION_NULL || botWorld->botGoalInfo.deliverActionNumber >= botThreadData.botActions.Num() ) {
|
|
return false;
|
|
}
|
|
|
|
idVec3 vec = botThreadData.botActions[ botWorld->botGoalInfo.deliverActionNumber ]->GetActionOrigin() - botWorld->clientInfo[ clientNum ].origin;
|
|
|
|
if ( botWorld->clientInfo[ clientNum ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
proxyInfo_t vehicle;
|
|
GetVehicleInfo( botWorld->clientInfo[ clientNum ].proxyInfo.entNum, vehicle );
|
|
|
|
if ( vehicle.isAirborneVehicle ) {
|
|
vec.z = 0.0f;
|
|
}
|
|
|
|
if ( vehicle.type == PLATYPUS && vehicle.hasGroundContact && vehicle.xyspeed < WALKING_SPEED ) { //mal: get out of the boat if its on the shore!
|
|
return true;
|
|
}
|
|
}
|
|
|
|
float tooCloseDist;
|
|
|
|
if ( desiredRange == -1.0f ) {
|
|
tooCloseDist = ( botWorld->gameLocalInfo.gameMap == QUARRY ) ? 5000.0f : 3000.0f;
|
|
} else {
|
|
tooCloseDist = desiredRange;
|
|
}
|
|
|
|
if ( vec.LengthSqr() < Square( tooCloseDist ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_FindNearbySafeActionToMoveToward
|
|
|
|
Find a close, valid action for the bot to move towards. It doesn't matter what the action is, we just want the bot to move away from its current position in a nice, realistic manner.
|
|
============
|
|
*/
|
|
int idBotAI::Bot_FindNearbySafeActionToMoveToward( const idVec3& origin, float minAvoidDist, bool pickRandom ) {
|
|
int actionNumber = ACTION_NULL;
|
|
int listStartNum = 0;
|
|
bool skipInactiveActions = true;
|
|
|
|
if ( botVehicleInfo != NULL && botVehicleInfo->type > ICARUS ) {
|
|
skipInactiveActions = false;
|
|
}
|
|
|
|
if ( pickRandom ) {
|
|
listStartNum = botThreadData.random.RandomInt( botThreadData.botActions.Num() );
|
|
}
|
|
|
|
for( int i = listStartNum; i < botThreadData.botActions.Num(); i++ ) {
|
|
|
|
if ( skipInactiveActions ) {
|
|
if ( !botThreadData.botActions[ i ]->ActionIsActive() ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( !botThreadData.botActions[ i ]->ActionIsValid() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_NULL ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.gameMap == SLIPGATE ) {
|
|
if ( !Bot_CheckLocationIsOnSameSideOfSlipGate( botThreadData.botActions[ i ]->GetActionOrigin() ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( botVehicleInfo == NULL && ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_VEHICLE_CAMP || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_VEHICLE_ROAM ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_AIRCAN_HINT || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_SMOKE_HINT ||
|
|
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_NADE_HINT || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_SUPPLY_HINT ||
|
|
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_SHIELD_HINT || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_TELEPORTER_HINT ||
|
|
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_THIRDEYE_HINT || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FLYER_HIVE_HINT ||
|
|
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FLYER_HIVE_LAUNCH || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FLYER_HIVE_TARGET ) {
|
|
continue;
|
|
}
|
|
|
|
if ( i == lastNearbySafeActionToMoveTowardActionNum ) { //mal: try not to repeat ourselves.
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() < Square( minAvoidDist ) ) {
|
|
continue;
|
|
}
|
|
|
|
actionNumber = i;
|
|
lastNearbySafeActionToMoveTowardActionNum = actionNumber;
|
|
break;
|
|
}
|
|
|
|
return actionNumber;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::ClientHasShieldInWorld
|
|
|
|
if considerRange != -1, the bots won't count shields that are outside the considerRange from their position.
|
|
============
|
|
*/
|
|
bool idBotAI::ClientHasShieldInWorld( int clientNum, float considerRange ) {
|
|
if ( !ClientIsValid( clientNum, -1 ) ) {
|
|
return false;
|
|
}
|
|
|
|
bool hasShield = false;
|
|
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
|
|
|
|
if ( player.classType != FIELDOPS ) {
|
|
return false;
|
|
}
|
|
|
|
for( int i = 0; i < MAX_SHIELDS; i++ ) {
|
|
if ( player.forceShields[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( considerRange != -1.0f ) {
|
|
idVec3 vec = player.forceShields[ i ].origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( considerRange ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
hasShield = true;
|
|
break;
|
|
}
|
|
|
|
return hasShield;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_DistSqrToClosestForceShield
|
|
============
|
|
*/
|
|
float idBotAI::Bot_DistSqrToClosestForceShield( idVec3& shieldOrg ) {
|
|
float closest = idMath::INFINITY;
|
|
shieldOrg = vec3_zero;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != STROGG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.classType != FIELDOPS ) {
|
|
continue;
|
|
}
|
|
|
|
for( int j = 0; j < MAX_SHIELDS; j++ ) {
|
|
if ( player.forceShields[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.forceShields[ j ].origin - botInfo->origin;
|
|
float distSqr = vec.LengthSqr();
|
|
|
|
if ( distSqr > Square( SHIELD_CONSIDER_RANGE ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( distSqr < closest ) {
|
|
shieldOrg = player.forceShields[ j ].origin;
|
|
closest = distSqr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_UseTeleporter
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_UseTeleporter() {
|
|
if ( botInfo->team == STROGG && botInfo->classType == COVERTOPS && botInfo->hasTeleporterInWorld && !botInfo->isDisguised && botTeleporterAttempts < 30 ) {
|
|
botIdealWeapNum = TELEPORTER;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
ignoreWeapChange = true;
|
|
if ( botInfo->weapInfo.weapon == TELEPORTER ) {
|
|
botTeleporterAttempts++;
|
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
|
botUcmd->botCmds.attack = true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_UseRepairDrone
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_UseRepairDrone( int entNum, const idVec3& entOrg ) {
|
|
if ( botInfo->team != STROGG || !botInfo->abilities.stroggRepairDrone || ( botInfo->weapInfo.primaryWeapNeedsAmmo && !botInfo->hasRepairDroneInWorld ) || ( !botInfo->weapInfo.primaryWeapHasAmmo && !botInfo->hasRepairDroneInWorld ) ) {
|
|
return false;
|
|
}
|
|
|
|
idVec3 vec = entOrg - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() < Square( REPAIR_DRONE_RANGE ) ) {
|
|
if ( !botInfo->hasRepairDroneInWorld ) {
|
|
trace_t tr;
|
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, entOrg, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
|
|
|
|
if ( tr.fraction < 1.0f && tr.c.entityNum != entNum ) {
|
|
return false;
|
|
}
|
|
|
|
botIdealWeapNum = PLIERS;
|
|
botIdealWeapSlot = NO_WEAPON;
|
|
|
|
if ( botInfo->weapInfo.weapon == PLIERS && botInfo->targetLocked && botInfo->targetLockEntNum == entNum ) {
|
|
botUcmd->botCmds.altFire = true;
|
|
}
|
|
|
|
Bot_LookAtEntity( entNum, SMOOTH_TURN );
|
|
} else {
|
|
botIdealWeapSlot = GUN;
|
|
|
|
if ( Bot_RandomLook( vec ) ) {
|
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_IsUnderAttackFromUnknownEnemy
|
|
|
|
Bot is under attack from someone, and has had time to react to that enemy, but hasn't - so must not be aware of them for some reason.
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_IsUnderAttackFromUnknownEnemy() {
|
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
|
|
return false;
|
|
}
|
|
|
|
if ( botInfo->lastAttackerTime + 1000 > botWorld->gameLocalInfo.time && botInfo->lastAttackerTime + 100 < botWorld->gameLocalInfo.time && enemy == -1 ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckForGrieferTargetGoals
|
|
|
|
Check to see if the bots are being camped/griefed by some dirty human, and make taking out that human a goal for the bot.
|
|
============
|
|
*/
|
|
int idBotAI::Bot_CheckForGrieferTargetGoals() {
|
|
if ( ClientHasObj( botNum ) || Client_IsCriticalForCurrentObj( botNum, 2500.0f ) ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( botVehicleInfo != NULL ) {
|
|
if ( !Bot_IsInHeavyAttackVehicle() ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( Bot_VehicleIsUnderAVTAttack() != -1 ) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool inAirAttackVehicle = ( botVehicleInfo != NULL && botVehicleInfo->type > ICARUS ) ? true : false;
|
|
int grieferClientNum = -1;
|
|
int closestAirVehicle = FindClosestVehicle( HUNT_MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, AIR, PERSONAL | AIR_TRANSPORT, true );
|
|
float attackDist = ( inAirAttackVehicle || closestAirVehicle != -1 ) ? idMath::INFINITY : MAX_ATTACK_GRIEFER_RANGE;
|
|
float closest = idMath::INFINITY;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !player.isCamper && player.killsSinceSpawn < KILLING_SPREE ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
//mal: check to see if anyone else is targeting this bozo.
|
|
if ( botVehicleInfo != NULL ) {
|
|
if ( !Bot_VehicleLTGIsAvailable( i, ACTION_NULL, V_HUNT_GOAL, 1 ) ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( !Bot_LTGIsAvailable( i, ACTION_NULL, HUNT_GOAL, 1 ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( botWorld->gameLocalInfo.gameMap == SLIPGATE ) { //mal: need some special love for this unique map.
|
|
if ( !Bot_CheckLocationIsOnSameSideOfSlipGate( player.origin ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
|
if ( botVehicleInfo == NULL && !Bot_CanAttackVehicles() ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.areaNumVehicle == 0 ) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if ( player.areaNum == 0 ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
float distSqr = vec.LengthSqr();
|
|
|
|
if ( distSqr > Square( attackDist ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( distSqr > closest ) {
|
|
continue;
|
|
}
|
|
|
|
if ( botVehicleInfo == NULL ) {
|
|
if ( closestAirVehicle == -1 ) {
|
|
int travelTime;
|
|
|
|
if ( !Bot_LocationIsReachable( false, player.origin, travelTime ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
} else if ( !inAirAttackVehicle ) { //mal: aircraft can go anywhere.
|
|
idBotNode* ourNode = botThreadData.botVehicleNodes.GetNearestNode( botVehicleInfo->axis, botVehicleInfo->origin, botInfo->team, NULL_DIR, false, true, false, NULL, botVehicleInfo->flags );
|
|
idBotNode* goalNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, player.origin, botInfo->team, NULL_DIR, false, true, false, NULL, botVehicleInfo->flags ); // always assume the goal node is reachable
|
|
|
|
if ( ourNode == NULL || goalNode == NULL ) { //mal: crap. :-/
|
|
continue;
|
|
}
|
|
|
|
if ( goalNode != ourNode ) {
|
|
idList< idBotNode::botLink_t > pathList;
|
|
botThreadData.botVehicleNodes.CreateNodePath( botInfo, ourNode, goalNode, pathList, botVehicleInfo->flags );
|
|
|
|
if ( pathList.Num() == 0 ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
grieferClientNum = i;
|
|
closest = distSqr;
|
|
}
|
|
|
|
return grieferClientNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_FindClosestAVTDanger
|
|
============
|
|
*/
|
|
int idBotAI::Bot_FindClosestAVTDanger( const idVec3& location, float range, bool useAngleCheck ) {
|
|
int deployableEntNum = -1;
|
|
float closest = idMath::INFINITY;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
|
|
|
|
if ( deployable.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.type != AVT ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( DeployableIsIgnored( deployable.entNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.ownerClientNum == -1 ) { //mal: dont attack static, map based deployables.
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !deployable.inPlace ) {
|
|
continue;
|
|
}
|
|
|
|
if ( deployable.disabled ) {
|
|
continue;
|
|
}
|
|
|
|
if ( useAngleCheck ) {
|
|
idVec3 dir = location - deployable.origin;
|
|
dir.NormalizeFast();
|
|
if ( dir * deployable.axis[ 0 ] < COS_TURRET_ANGLE_ARC ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
idVec3 vec = location - deployable.origin;
|
|
float distSqr = vec.LengthSqr();
|
|
|
|
if ( distSqr > Square( range ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( distSqr < closest ) {
|
|
deployableEntNum = deployable.entNum;
|
|
closest = distSqr;
|
|
}
|
|
}
|
|
|
|
return deployableEntNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::SpawnHostIsMarkedForDeath
|
|
============
|
|
*/
|
|
bool idBotAI::SpawnHostIsMarkedForDeath( int spawnHostSpawnID ) {
|
|
bool isMarked = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.spawnHostTargetSpawnID != spawnHostSpawnID ) {
|
|
continue;
|
|
}
|
|
|
|
isMarked = true;
|
|
break;
|
|
}
|
|
|
|
return isMarked;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_GetRequestedEscortClient
|
|
============
|
|
*/
|
|
int idBotAI::Bot_GetRequestedEscortClient() {
|
|
int escortClientNum = -1;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.escortSpawnID == botInfo->spawnID && player.escortRequestTime + REQUEST_CONSIDER_TIME > botWorld->gameLocalInfo.time ) {
|
|
escortClientNum = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return escortClientNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::ActionIsActiveForTrainingMode
|
|
============
|
|
*/
|
|
bool idBotAI::ActionIsActiveForTrainingMode( int actionNumber, int numSecondsToDelay ) {
|
|
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
|
|
return true;
|
|
}
|
|
|
|
numSecondsToDelay *= 1000;
|
|
|
|
if ( ( botThreadData.botActions[ actionNumber ]->actionActivateTime + numSecondsToDelay ) < botWorld->gameLocalInfo.time ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::TeamHumanMissionIsObjective
|
|
============
|
|
*/
|
|
bool idBotAI::TeamHumanMissionIsObjective() {
|
|
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
|
|
return false;
|
|
}
|
|
|
|
bool humanHasObjMission = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( botNum == i ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.missionEntNum != MISSION_OBJ ) {
|
|
continue;
|
|
}
|
|
|
|
humanHasObjMission = true;
|
|
break;
|
|
}
|
|
|
|
return humanHasObjMission;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckForHumanInteractingWithEntity
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_CheckForHumanInteractingWithEntity( int entNum ) {
|
|
bool hasHuman = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.missionEntNum != entNum ) {
|
|
continue;
|
|
}
|
|
|
|
hasHuman = true;
|
|
break;
|
|
}
|
|
|
|
return hasHuman;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_HasTeammateWhoCouldUseShieldCoverNearby
|
|
============
|
|
*/
|
|
int idBotAI::Bot_HasTeammateWhoCouldUseShieldCoverNearby() {
|
|
int entNum = -1;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.xySpeed > WALKING_SPEED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.weapInfo.weapon != PLIERS && player.weapInfo.weapon != NEEDLE && player.weapInfo.weapon != HACK_TOOL && player.weapInfo.weapon != HE_CHARGE ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( SHEILD_FIRE_CONSIDER_RANGE ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ClientHasCloseShieldNearby( i, SHEILD_FIRE_CONSIDER_RANGE ) ) {
|
|
continue;
|
|
}
|
|
|
|
entNum = i;
|
|
break;
|
|
}
|
|
|
|
return entNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckForNearbyTeammateWhoCouldUseSmoke
|
|
============
|
|
*/
|
|
int idBotAI::Bot_CheckForNearbyTeammateWhoCouldUseSmoke() {
|
|
if ( botInfo->isDisguised ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( ClientHasObj( botNum ) ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( !ClassWeaponCharged( SMOKE_NADE ) ) {
|
|
return -1;
|
|
}
|
|
|
|
int entNum = -1;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ClientIsIgnored( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.classType != ENGINEER ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.inWater ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: client jumped into a vehicle/deployable - forget them
|
|
continue;
|
|
}
|
|
|
|
if ( ClientIsDead( i ) || player.inLimbo ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.xySpeed > WALKING_SPEED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.weapInfo.weapon != PLIERS ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( SMOKE_CONSIDER_RANGE ) ) {
|
|
continue;
|
|
}
|
|
|
|
entNum = i;
|
|
break;
|
|
}
|
|
|
|
return entNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::ClientHasCloseShieldNearby
|
|
============
|
|
*/
|
|
bool idBotAI::ClientHasCloseShieldNearby( int clientNum, float considerRange ) {
|
|
bool hasShield = false;
|
|
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( player.team != STROGG ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.classType != FIELDOPS ) {
|
|
continue;
|
|
}
|
|
|
|
for( int j = 0; j < MAX_SHIELDS; j++ ) {
|
|
if ( player.forceShields[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = player.forceShields[ j ].origin - client.origin;
|
|
|
|
if ( vec.LengthSqr() > Square( considerRange ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasShield = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hasShield;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::TeamCriticalClass
|
|
============
|
|
*/
|
|
const playerClassTypes_t idBotAI::TeamCriticalClass( const playerTeamTypes_t playerTeam ) {
|
|
if ( playerTeam == GDF ) {
|
|
return botWorld->botGoalInfo.team_GDF_criticalClass;
|
|
} else if ( playerTeam == STROGG ) {
|
|
return botWorld->botGoalInfo.team_STROGG_criticalClass;
|
|
} else {
|
|
return NOCLASS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::FindHumanOnTeam
|
|
============
|
|
*/
|
|
|
|
int idBotAI::FindHumanOnTeam( const playerTeamTypes_t playerTeam ) {
|
|
int clientNum = -1;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( playerInfo.team != playerTeam ) {
|
|
continue;
|
|
}
|
|
|
|
clientNum = i;
|
|
break;
|
|
}
|
|
|
|
return clientNum;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckHealthCrateState
|
|
============
|
|
*/
|
|
void idBotAI::Bot_CheckHealthCrateState() {
|
|
if ( !ClassWeaponCharged( SUPPLY_MARKER ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( crateGoodTime > botWorld->gameLocalInfo.time ) {
|
|
idVec3 botOrigin = botInfo->origin;
|
|
botOrigin.z -= 64.0f;
|
|
Bot_UseCannister( SUPPLY_MARKER, botOrigin );
|
|
return;
|
|
}
|
|
|
|
int botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction;
|
|
|
|
if ( botActionNum < 0 || botActionNum > botThreadData.botActions.Num() ) {
|
|
return;
|
|
}
|
|
|
|
if ( botInfo->supplyCrate.entNum != 0 ) { //mal: our old crate is too far away from the obj, so just nuke it and drop a new one.
|
|
idVec3 crateDelta = botInfo->supplyCrate.origin - botThreadData.botActions[ botActionNum ]->GetActionOrigin();
|
|
|
|
if ( crateDelta.LengthSqr() < Square( 5000.0f ) ) {
|
|
return;
|
|
}
|
|
|
|
botUcmd->botCmds.destroySupplyCrate = true;
|
|
return;
|
|
}
|
|
|
|
idVec3 actionDelta = botThreadData.botActions[ botActionNum ]->GetActionOrigin() - botInfo->origin;
|
|
|
|
float maxRange = ( botWorld->botGoalInfo.attackingTeam == botInfo->team ) ? 3000.0f : 1200.0f;
|
|
|
|
if ( actionDelta.LengthSqr() > Square( maxRange ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !LocationVis2Sky( botInfo->origin ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( Bot_CheckIfHealthCrateInArea( 1024.0f ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( botThreadData.botVehicleNodes.ActiveVehicleNodeNearby( botInfo->origin, 640.0f ) ) { //mal: dont drop crates in the middle of bot defined roads.
|
|
return;
|
|
}
|
|
|
|
if ( Bot_CheckIfObstacleInArea( 200.0f ) ) {
|
|
return;
|
|
}
|
|
|
|
float testDist = 100.0f;
|
|
float maxSlope = 0.93f;
|
|
|
|
//mal: make sure we're not dropping it into some tight area that ppl can't path thru. Need to check all 4 directions, to make sure dont end up in a doorway.
|
|
if ( !Bot_CanMove( RIGHT, testDist, false, true ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ( botCanMoveGoal * botAAS.aas->GetSettings()->invGravityDir ) < maxSlope ) {
|
|
return;
|
|
}
|
|
|
|
if ( !Bot_CanMove( LEFT, testDist, false, true ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ( botCanMoveGoal * botAAS.aas->GetSettings()->invGravityDir ) < maxSlope ) {
|
|
return;
|
|
}
|
|
|
|
if ( !Bot_CanMove( FORWARD, testDist, false, true ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ( botCanMoveGoal * botAAS.aas->GetSettings()->invGravityDir ) < maxSlope ) {
|
|
return;
|
|
}
|
|
|
|
if ( !Bot_CanMove( BACK, testDist, false, true ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ( botCanMoveGoal * botAAS.aas->GetSettings()->invGravityDir ) < maxSlope ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 botOrigin = botInfo->origin;
|
|
|
|
botOrigin.z -= 64.0f;
|
|
|
|
Bot_UseCannister( SUPPLY_MARKER, botOrigin );
|
|
|
|
crateGoodTime = botWorld->gameLocalInfo.time + 5000;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckIfHealthCrateInArea
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_CheckIfHealthCrateInArea( float range ) {
|
|
bool crateInArea = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
|
|
|
if ( playerInfo.supplyCrateRequestTime > botWorld->gameLocalInfo.time && botInfo->supplyCrateRequestTime < botWorld->gameLocalInfo.time ) {
|
|
crateInArea = true;
|
|
break;
|
|
}
|
|
|
|
if ( playerInfo.supplyCrate.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = playerInfo.supplyCrate.origin - botInfo->origin;
|
|
|
|
if ( vec.LengthSqr() > Square( range ) ) {
|
|
continue;
|
|
}
|
|
|
|
crateInArea = true;
|
|
break;
|
|
}
|
|
|
|
return crateInArea;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckThereIsHeavyVehicleInUseAlready
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_CheckThereIsHeavyVehicleInUseAlready() {
|
|
bool hasHeavyVehicle = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( i == botNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !ClientIsValid( i, -1 ) ) {
|
|
continue;
|
|
}
|
|
|
|
const clientInfo_t& player = botWorld->clientInfo[ i ];
|
|
|
|
if ( !player.isBot ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.team != botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
if ( player.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) {
|
|
continue;
|
|
}
|
|
|
|
proxyInfo_t vehicle;
|
|
|
|
GetVehicleInfo( player.proxyInfo.entNum, vehicle );
|
|
|
|
if ( vehicle.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( vehicle.type == MCP ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !( vehicle.flags & PERSONAL ) && vehicle.flags & ARMOR || vehicle.flags & AIR ) {
|
|
hasHeavyVehicle = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hasHeavyVehicle;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::FindChargeBySpawnID
|
|
============
|
|
*/
|
|
bool idBotAI::FindChargeBySpawnID( int spawnID, plantedChargeInfo_t& bombInfo ) {
|
|
bombInfo.entNum = 0;
|
|
|
|
for( int i = 0; i < MAX_CHARGES; i++ ) {
|
|
const plantedChargeInfo_t& charge = botWorld->chargeInfo[ i ];
|
|
|
|
if ( charge.spawnID != spawnID ) {
|
|
continue;
|
|
}
|
|
|
|
bombInfo = charge;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_HasShieldInWorldNearLocation
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_HasShieldInWorldNearLocation( const idVec3& checkOrg, float checkDist ) {
|
|
if ( botInfo->team != STROGG || botInfo->classType != FIELDOPS ) {
|
|
return false;
|
|
}
|
|
|
|
bool hasShield = false;
|
|
|
|
for( int j = 0; j < MAX_SHIELDS; j++ ) {
|
|
if ( botInfo->forceShields[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botInfo->forceShields[ j ].origin - checkOrg;
|
|
|
|
if ( vec.LengthSqr() > Square( checkDist ) ) {
|
|
continue;
|
|
}
|
|
|
|
hasShield = true;
|
|
break;
|
|
}
|
|
|
|
return hasShield;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_NumShieldsInWorld
|
|
============
|
|
*/
|
|
int idBotAI::Bot_NumShieldsInWorld() {
|
|
if ( botInfo->team != STROGG || botInfo->classType != FIELDOPS ) {
|
|
return 0;
|
|
}
|
|
|
|
int numShields = 0;
|
|
|
|
for( int j = 0; j < MAX_SHIELDS; j++ ) {
|
|
if ( botInfo->forceShields[ j ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
numShields++;
|
|
}
|
|
|
|
return numShields;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::CarrierInWorld
|
|
============
|
|
*/
|
|
bool idBotAI::CarrierInWorld() {
|
|
bool carrierInWorld = false;
|
|
|
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( !ClientHasObj( i ) ) {
|
|
continue;
|
|
}
|
|
|
|
carrierInWorld = true;
|
|
break;
|
|
}
|
|
|
|
return carrierInWorld;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBotAI::Bot_CheckChargeExistsOnObjInWorld
|
|
============
|
|
*/
|
|
bool idBotAI::Bot_CheckChargeExistsOnObjInWorld() {
|
|
bool chargeExists = false;
|
|
|
|
for( int i = 0; i < MAX_CHARGES; i++ ) {
|
|
const plantedChargeInfo_t& charge = botWorld->chargeInfo[ i ];
|
|
|
|
if ( charge.entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !charge.isOnObjective ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.state != BOMB_ARMED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( charge.team == botInfo->team ) {
|
|
continue;
|
|
}
|
|
|
|
chargeExists = true;
|
|
break;
|
|
}
|
|
|
|
return chargeExists;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idBotAI::GetDeployableAtAction
|
|
==================
|
|
*/
|
|
bool idBotAI::GetDeployableAtAction( int actionNumber, deployableInfo_t& deployable ) {
|
|
bool hasDeployable = false;
|
|
|
|
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
|
|
|
|
if ( botWorld->deployableInfo[ i ].entNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 vec = botWorld->deployableInfo[ i ].origin - botThreadData.botActions[ actionNumber ]->GetActionOrigin();
|
|
float distSqr = vec.LengthSqr();
|
|
|
|
if ( distSqr > Square( botThreadData.botActions[ i ]->GetRadius() ) && distSqr > Square( 256.0f ) ) {
|
|
continue;
|
|
}
|
|
|
|
deployable = botWorld->deployableInfo[ i ];
|
|
hasDeployable = true;
|
|
break;
|
|
}
|
|
|
|
return hasDeployable;
|
|
} |