etqw-sdk/source/game/botai/BotAI_FightUtils.cpp

3614 lines
111 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../ContentMask.h"
#include "BotThreadData.h"
#include "BotAI_Main.h"
/*
================
idBotAI::Bot_FindEnemy
We'll sort thru the clients, and ignore certain clients if we're too busy
to be buggered (carrying obj, planting/hacking, etc) or they're not valid enemies
(in disguise, hidden by smoke, etc).
================
*/
bool idBotAI::Bot_FindEnemy( int ignoreClientNum ) {
bool hasAttackedMate;
bool hasAttackedCriticalMate;
bool hasObj;
bool isTouchingItems;
bool isDefusingOurBomb;
bool inFront;
bool botGotShotRecently;
bool botIsBigShot;
bool audible;
bool isVisible;
bool isFacingUs;
bool isFiringWeapon;
bool isNearOurObj;
bool hasBeenNarced;
int i;
int infantry = 0;
int vehicle = 0;
int aircraft = 0;
int heardPriority;
int entClientNum = -1;
int heardClientNum = -1;
float dist;
float botSightDist = botWorld->botGoalInfo.botSightDist;
float entDist = idMath::INFINITY;
float heardDist = idMath::INFINITY;
proxyInfo_t vehicleInfo;
idVec3 vec;
hammerTime = false;
hammerClient = -1;
testFireShot = false;
numVisEnemies = 0;
//mal: some debugging stuff....
if ( botWorld->gameLocalInfo.botIgnoreEnemies == 1 ) {
return false;
} else if ( botWorld->gameLocalInfo.botIgnoreEnemies == 2 ) {
if ( botInfo->team == GDF ) {
return false;
}
} else if ( botWorld->gameLocalInfo.botIgnoreEnemies == 3 ) {
if ( botInfo->team == STROGG ) {
return false;
}
}
if ( Bot_ShouldIgnoreEnemies() ) {
return false;
}
#ifdef _XENON
if ( briefPlayerTime > botWorld->gameLocalInfo.time ) {
return false;
}
#endif
idVec3 turretLoc;
if ( Bot_IsUnderAttackByAPT( turretLoc ) && Bot_IsAttackingDeployables() ) {
return false;
}
if ( botInfo->weapInfo.covertToolInfo.entNum != 0 && botInfo->team == STROGG && botInfo->weapInfo.covertToolInfo.clientIsUsing == true ) { //mal: if using the flyer hive, dont worry about enemies.
return false;
}
if ( botWorld->botGoalInfo.teamRetreatInfo[ botInfo->team ].retreatTime > botWorld->gameLocalInfo.time ) {
botSightDist = 1000.0f;
}
botIsBigShot = Bot_CheckCombatExceptions();
for ( i = 0; i < MAX_CLIENTS; i++ ) {
if ( ignoreClientNum == i ) {
continue;
}
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
if ( i == botNum ) {
continue; //mal: dont try to fight ourselves!
}
if ( EnemyIsIgnored( i )) {
continue; //mal: dont try to fight someone we've flagged to ignore for whatever reason!
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( playerInfo.isNoTarget ) {
continue;
} //mal: dont target clients that have notarget set - this is useful for debugging, etc.
if ( playerInfo.inLimbo ) {
continue;
}
if ( playerInfo.isActor ) {
continue;
}
if ( playerInfo.isDisguised && botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
continue;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
if ( Bot_CheckForHumanInteractingWithEntity( i ) == true ) {
continue;
}
}
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: pick the driver of a vehicle as the target, NOT passengers, unless there is no driver - then kill whoever.
GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo );
if ( vehicleInfo.type != MCP ) {
if ( vehicleInfo.driverEntNum != i && vehicleInfo.driverEntNum != -1 ) {
continue;
}
vec = vehicleInfo.origin - botInfo->origin;
float vehicleDist = vec.LengthSqr();
if ( vehicleInfo.xyspeed > 600.0f && vehicleDist > Square( 1900.0f ) && !InFrontOfVehicle( playerInfo.proxyInfo.entNum, botInfo->origin ) ) { //mal: if they're in a mad dash away from us, forget about them!
continue;
}
if ( ( vehicleInfo.type == GOLIATH || vehicleInfo.type == TITAN || vehicleInfo.type == DESECRATOR ) && vehicleInfo.health > ( vehicleInfo.maxHealth / 3 ) ) {
bool canNadeAttack = ( !botInfo->weapInfo.hasNadeAmmo || vehicleDist > Square( GRENADE_ATTACK_DIST ) ) ? false : true;
if ( botInfo->classType == FIELDOPS ) {
if ( vehicleDist > Square( GRENADE_ATTACK_DIST ) ) {
if ( !Bot_HasWorkingDeployable() || !ClassWeaponCharged( AIRCAN ) || Bot_EnemyAITInArea( vehicleInfo.origin ) ) {
continue;
}
} else {
if ( !canNadeAttack && !ClassWeaponCharged( AIRCAN ) && !botInfo->weapInfo.primaryWeapHasAmmo ) {
continue;
}
}
} else if ( botInfo->classType == COVERTOPS ) {
if ( !canNadeAttack && ( botInfo->weapInfo.primaryWeapon != SNIPERRIFLE || !botInfo->weapInfo.primaryWeapHasAmmo ) ) {
continue;
}
} else if ( botInfo->classType == SOLDIER ) {
if ( !canNadeAttack && ( botInfo->weapInfo.primaryWeapon != ROCKET || !botInfo->weapInfo.primaryWeapHasAmmo ) ) {
continue;
}
} else {
if ( !canNadeAttack && vehicleDist > Square( INFANTRY_ATTACK_HEAVY_DIST ) && !botInfo->weapInfo.primaryWeapHasAmmo ) {
continue;
}
}
}
} else {
if ( vehicleInfo.isImmobilized && vehicleInfo.driverEntNum == i ) {
continue;
}
}
}
if ( playerInfo.invulnerableEndTime > botWorld->gameLocalInfo.time ) {
continue; //mal: ignore revived/just spawned in clients - get the ppl around them!
}
if ( playerInfo.health <= 0 ) {
continue;
}
if ( playerInfo.team == botInfo->team ) {
continue;
}
hasAttackedCriticalMate = ClientHasAttackedTeammate( i, true, 3000 );
int attackeMateAwarenessTime = ( playerInfo.isDisguised ) ? 3000 : 9000;
hasAttackedMate = ClientHasAttackedTeammate( i, false, attackeMateAwarenessTime );
hasObj = ClientHasObj( i );
isTouchingItems = ( playerInfo.touchingItemTime + BOT_THINK_DELAY_TIME < botWorld->gameLocalInfo.time ) ? false : true;
isDefusingOurBomb = ClientIsDefusingOurTeamCharge( i );
inFront = InFrontOfClient( botNum, playerInfo.origin );
isFacingUs = InFrontOfClient( i, botInfo->origin );
botGotShotRecently = ( botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) ? false : true;
isFiringWeapon = playerInfo.weapInfo.isFiringWeap;
isNearOurObj = ( LocationDistFromCurrentObj( botInfo->team, playerInfo.origin ) < 2500.0f ) ? true : false;
hasBeenNarced = ( playerInfo.covertWarningTime + 5000 < botWorld->gameLocalInfo.time ) ? false : true;
bool hasAttackedActorsFriend = ClientHasAttackedActorsMate( i, 3000 );
if ( botIsBigShot && botInfo->classType == MEDIC && !hasObj && !isDefusingOurBomb ) { //mal: medics need to do their jobs, with a LOT less restrictions then others.
continue;
}
if ( botIsBigShot && ( !isFacingUs || !inFront ) && !hasObj && !isNearOurObj && !isDefusingOurBomb ) {
continue;
} //mal: if we're trying to do an important obj, dont get into a fight with everyone.
if ( botInfo->isDisguised ) { //mal: dont attack, unless they have the obj on them, or they shot us, or they're attacking an important mate, or they're defusing our team's bomb!
if ( !hasObj && ( !botGotShotRecently || ( botInfo->lastAttacker != i && botInfo->lastAttacker != botInfo->disguisedClient ) || botThreadData.random.RandomInt( 100 ) < 75 ) &&
!hasAttackedCriticalMate && !isDefusingOurBomb && Client_IsCriticalForCurrentObj( i, 2500.0f ) == false ) {
continue;
}
}
if ( botInfo->isDisguised ) {
if ( !botGotShotRecently && aiState == NBG && nbgType == STALK_VICTIM && nbgTarget == i ) {
continue;
}
}
vec = playerInfo.viewOrigin - botInfo->viewOrigin;
dist = vec.LengthSqr();
if ( botInfo->isActor && ( !hasAttackedActorsFriend && dist > Square( 900.0f ) ) ) {
continue;
}
if ( botWorld->botGoalInfo.teamRetreatInfo[ botInfo->team ].retreatTime > botWorld->gameLocalInfo.time ) { //mal: the bots should try to fall back, not get caught up in combat.
if ( !inFront && ( !botGotShotRecently || botInfo->lastAttacker != i ) ) {
continue;
}
if ( dist > Square( botSightDist ) ) {
continue;
}
}
if ( dist > Square( botSightDist ) ) {
if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo && playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
if ( dist > Square( WEAPON_LOCK_DIST ) ) {
continue;
}
} else if ( botInfo->classType == FIELDOPS && ClassWeaponCharged( AIRCAN ) && Bot_HasWorkingDeployable() && !Bot_EnemyAITInArea( playerInfo.origin ) ) {
if ( !LocationVis2Sky( playerInfo.origin ) ) {
continue;
}
if ( dist > Square( WEAPON_LOCK_DIST ) && !ClientIsDangerous( i ) ) {
continue;
}
deployableInfo_t deployableInfo;
GetDeployableInfo( true, 0, deployableInfo );
if ( dist > Square( deployableInfo.maxAttackRange ) ) {
continue;
}
bool vis2Sky = LocationVis2Sky( playerInfo.origin );
if ( ClientIsDangerous( i ) && vis2Sky ) { //mal: we'll slam campers, just because!
hammerTime = true;
hammerVehicle = false;
hammerLocation = playerInfo.origin;
hammerClient = i;
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo );
if ( deployableInfo.type == ROCKET_ARTILLERY ) {
hammerVehicle = true;
}
hammerLocation = vehicleInfo.origin;
hammerLocation.z += ( vehicleInfo.bbox[ 1 ][ 2 ] - vehicleInfo.bbox[ 0 ][ 2 ] ) * 0.2f;//mal: 20%
}
} else if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE && deployableInfo.type == ROCKET_ARTILLERY && vis2Sky ) {
GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo );
if ( vehicleInfo.type != HUSKY && vehicleInfo.type != ICARUS && vehicleInfo.type != PLATYPUS ) {
float vehicleOffset = botThreadData.GetVehicleTargetOffset( vehicleInfo.type );
hammerTime = true;
hammerVehicle = true;
hammerLocation = vehicleInfo.origin;
hammerLocation.z += ( vehicleInfo.bbox[ 1 ][ 2 ] - vehicleInfo.bbox[ 0 ][ 2 ] ) * vehicleOffset;
hammerClient = i;
} else {
continue;
}
} else { //mal: need to have some ppl around to make it worth our while....
int numEnemiesInArea = ClientsInArea( botNum, playerInfo.origin, 1024.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, true, false, false, false );
int numFriendsInArea = ClientsInArea( botNum, playerInfo.origin, 1024.0f, botInfo->team, NOCLASS, false, true, false, false, false );
if ( numEnemiesInArea < 2 || numFriendsInArea > 2 ) {
continue;
}
hammerTime = true;
hammerVehicle = false;
hammerLocation = playerInfo.origin;
hammerClient = i;
}
} else if ( botInfo->classType == COVERTOPS && botInfo->weapInfo.primaryWeapon == SNIPERRIFLE && botInfo->weapInfo.primaryWeapHasAmmo && !botIsBigShot ) {
if ( dist > Square( MAX_SNIPER_VIEW_DIST ) ) {
continue;
}
} else {
if ( aiState == LTG && ltgType == HUNT_GOAL && ltgTarget == i ) {
continue;
}
if ( dist > Square( botSightDist * 2.0f ) || !ClientIsMarkedForDeath( i, false ) ) {
if ( botInfo->xySpeed == 0.0f && botInfo->lastAttacker == i && botInfo->lastAttackerTime + 100 > botWorld->gameLocalInfo.time ) { //mal: if getting hit, dont just stand there, move around.
Bot_ResetState( false, true );
}
continue;
}
}
}
if ( playerInfo.isDisguised ) {
if ( ( playerInfo.disguisedClient != botNum || dist > Square( COVERT_SIGHT_DIST * 2.0f ) ) && !hasAttackedMate && !DisguisedClientIsActingOdd( i ) ) {
if ( inFront && botThreadData.GetBotSkill() == BOT_SKILL_EXPERT ) {
if ( ( ( !isTouchingItems || ( playerInfo.health == playerInfo.maxHealth && !playerInfo.weapInfo.primaryWeapNeedsAmmo ) || dist > Square( COVERT_SIGHT_DIST ) ) && ( !hasBeenNarced || dist > Square( COVERT_SIGHT_DIST ) ) ) ) {
continue; //mal: won't "see" disguised clients, unless they look like us, or theyre in front of us touching our/their items, or someone warned us about them!
}
} else {
continue;
}
}
}
if ( !ClientHasObj( i ) ) {
audible = ClientIsAudibleToBot( i ); //mal: if we can hear you, we'll skip the FOV test in the vis check below
} else {
audible = true;
dist = Square( 500.0f ); //mal: if you've got the docs, your our priority target, unless someone else is right on top of us!
}
isVisible = ClientIsVisibleToBot( i, !audible, false );
//mal: if client isn't visible, but is audible, see if we should hunt them down - ESPECIALLY if they're attacking a nearby critical teammate or are carrying docs!
if ( !isVisible ) {
if ( audible ) {
heardPriority = Bot_ShouldInvestigateNoise( i );
if ( heardPriority != 0 ) {
if ( dist < Square( heardDist ) ) {
if ( heardPriority == 2 ) {
heardDist = Square( 200.0f ); //mal: we heard someone we should worry about, so they get somewhat priority!
} else if ( heardDist == 3 ) {
heardDist = Square( 100.0f ); //mal: we heard someone we should REALLY worry about, so they get TOTAL priority!
} else {
heardDist = dist;
}
heardClientNum = i;
}
}
}
continue;
}
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO || botWorld->botGoalInfo.gameIsOnFinalObjective || botWorld->botGoalInfo.attackingTeam == botInfo->team ) { //mal: don't be too good about picking our targets in training mode if we're the defenders, unless its the final obj....
if ( isDefusingOurBomb ) {
dist = Square( 100.0f );
}
if ( hasAttackedCriticalMate && inFront && botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
dist = Square( 600.0f ); //mal: will give higher priority to someone attacking a critical mate, if we can see it happening.
}
if ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
if ( Client_IsCriticalForCurrentObj( i, 1200.0f ) ) {
dist = Square( 700.0f ); //mal: if your a critical client, we're more likely to kill you.
}
}
if ( botWorld->botGoalInfo.mapHasMCPGoal ) {
if ( playerInfo.proxyInfo.entNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) {
dist = Square( 800.0f );
}
} //mal: if your in MCP, you get higher priority then a normal enemy.
} else {
if ( !playerInfo.isBot || playerInfo.isActor ) {
dist += Square( TRAINING_MODE_RANGE_ADDITION );
}
}
numVisEnemies++;
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
if ( vehicleInfo.isAirborneVehicle ) {
if ( vehicleInfo.type != ICARUS ) {
aircraft++;
} else {
infantry++; //mal: the icarus doesn't really count as a aircraft threat.
}
} else {
if ( vehicleInfo.type != HUSKY ) {
vehicle++; //mal: tanks, water craft, and everything else falls into this group.
} else {
infantry++; //mal: ditto for the husky - doesn't really count as much of a vehicle threat.
}
}
} else {
infantry++;
}
if ( dist < entDist ) {
entClientNum = i;
entDist = dist;
}
}
if ( entClientNum != -1 ) {
enemy = entClientNum;
enemySpawnID = botWorld->clientInfo[ entClientNum ].spawnID;
if ( hammerClient != -1 && enemy != hammerClient ) {
hammerTime = false;
}
if ( aiState == LTG && ltgType == HUNT_GOAL && ltgTarget == enemy ) {
enemyIsHuntGoal = true;
} else {
enemyIsHuntGoal = false;
}
enemyInfo.enemy_FS_Pos = botWorld->clientInfo[ entClientNum ].origin;
enemyInfo.enemy_LS_Pos = enemyInfo.enemy_FS_Pos;
bot_FS_Enemy_Pos = botInfo->origin;
bot_LS_Enemy_Pos = bot_FS_Enemy_Pos;
enemyInfo.enemyLastVisTime = botWorld->gameLocalInfo.time;
enemyAcquireTime = botWorld->gameLocalInfo.time;
Bot_SetAttackTimeDelay( inFront ); //mal: this sets a delay on how long the bot should take to see enemy, based on bot's state.
COMBAT_AI_SUB_NODE = NULL; //mal: reset the bot's combat AI node
COMBAT_MOVEMENT_STATE = NULL;
combatNBGType = NO_COMBAT_TYPE;
if ( numVisEnemies > 2 ) { //mal: if a big wave of enemies is incoming, let everyone else know!
if ( botThreadData.random.RandomInt( 100 ) > 90 ) {
if ( vehicle > infantry && vehicle > aircraft ) {
Bot_AddDelayedChat( botNum, INCOMING_VEHICLE, 1 );
} else if ( aircraft > infantry ) {
Bot_AddDelayedChat( botNum, INCOMING_AIRCRAFT, 1 );
} else {
Bot_AddDelayedChat( botNum, INCOMING_INFANTRY, 1 );
}
}
}
return true;
} else if ( heardClientNum != -1 ) { //mal: we dont see an enemy, but did we find someone we want to investigate?
if ( ClientHasObj( heardClientNum ) ) {
ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY;
} else if ( aiState == NBG && nbgType == DEFENSE_CAMP ) {
ltgTime = botWorld->gameLocalInfo.time + 15000;
} else {
ltgTime = botWorld->gameLocalInfo.time + 60000;
}
aiState = LTG;
ltgType = HUNT_GOAL;
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_HuntGoal;
ltgTarget = heardClientNum;
ltgTargetSpawnID = botWorld->clientInfo[ heardClientNum ].spawnID;
}
return false;
}
/*
================
idBotAI::ClientIsAudibleToBot
Cheaply tells us if the client is question is making enough noise for this bot
to "notice" them and possibly consider them an enemy.
================
*/
bool idBotAI::ClientIsAudibleToBot( int clientNum ) {
heardClient = -1; // reset this every frame.
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) { // stupid bot - you can't hear anyone!
return false;
}
if ( botWorld->botGoalInfo.teamRetreatInfo[ botInfo->team ].retreatTime > botWorld->gameLocalInfo.time ) {
return false;
}
if ( ClientIsDefusingOurTeamCharge( clientNum ) ) {
return true;
}
if ( aiState == LTG && ( ltgType == DEFENSE_CAMP_GOAL || ltgType == HUNT_GOAL ) ) {
return false;
}
bool audible = false;
float hearingRange = PLAYER_HEARING_DIST;
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
idVec3 vec = client.origin - botInfo->origin;
float dist = vec.LengthSqr();
bool skipBattleSenseSoundChecks = false;
bool inVehicle = ( botVehicleInfo != NULL ) ? true : false;
if ( !inVehicle ) { //mal: if its far enough away, make sure its actually in the same area, and not a floor above/below us.
bool botOutSide = LocationVis2Sky( botInfo->origin );
bool enemyOutSide = LocationVis2Sky( client.origin );
if ( botOutSide != enemyOutSide && botInfo->areaNum != client.areaNum && idMath::Fabs( vec.z ) > 150.0f ) {
skipBattleSenseSoundChecks = true;
}
}
if ( !skipBattleSenseSoundChecks ) {
float weapFireHearingRange = hearingRange;
if ( client.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE && client.weapInfo.weapon == SNIPERRIFLE && botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY ) {
weapFireHearingRange = SNIPER_HEARING_DIST;
}
if ( client.weapInfo.isFiringWeap && dist < Square( weapFireHearingRange ) ) {
heardClient = clientNum;
audible = true;
}
if ( dist < Square( FOOTSTEP_DIST ) && client.xySpeed >= RUNNING_SPEED && botInfo->xySpeed < WALKING_SPEED ) {
if ( client.abilities.stroggCovertNoFootSteps == false || client.isDisguised || client.classType != COVERTOPS ) {
heardClient = clientNum;
audible = true;
}
}
if ( !client.isDisguised && dist < Square( FOOTSTEP_DIST ) && client.isPanting && botInfo->xySpeed < WALKING_SPEED && !botInfo->isPanting && botWorld->gameLocalInfo.botSkill == BOT_SKILL_EXPERT ) {
audible = true;
heardClient = clientNum;
} //mal: REALLY high skilled bots will hear the enemy running around, panting. We check for disguised clients elsewhere.
}
if ( ClientHasObj( clientNum ) && ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY || client.isInRadar ) ) {
audible = true;
}
if ( client.isInRadar && dist < Square( 1500.0f ) && ( client.abilities.gdfStealthToRadar == false || client.classType != COVERTOPS ) ) { //mal_TODO: test me!!!
audible = true;
}
if ( ClientIsMarkedForDeath( clientNum, false ) ) {
audible = true;
}
if ( ClientIsDangerous( clientNum ) ) { //mal: we're "aware" of campers, and will kill them if they're vis to us.
heardClient = clientNum;
audible = true;
}
if ( botInfo->lastAttacker == clientNum && botInfo->lastAttackerTime + 3000 > botWorld->gameLocalInfo.time ) {
audible = true;
}
if ( botVehicleInfo != NULL && client.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
if ( ( client.targetLockEntNum == botNum || client.targetLockEntNum == botInfo->proxyInfo.entNum ) && client.targetLockTime + 3000 < botWorld->gameLocalInfo.time ) {
audible = true;
}
}
if ( botThreadData.AllowDebugData() ) {
if ( audible != false ) {
// common->Printf("I'm aware of client #: %i\n", clientNum );
}
}
return audible;
}
/*
================
idBotAI::ClientIsVisibleToBot
Tells us if the client is question is visible to the bot.
Not the cheapest function to call!
================
*/
bool idBotAI::ClientIsVisibleToBot ( int clientNum, bool useFOV, bool saveEnt ) {
int entNum = -1;
trace_t tr;
proxyInfo_t vehicleInfo;
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
idVec3 enemyView;
if ( client.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE && !ClientHasObj( clientNum ) ) {
if ( ClientObscuredBySmokeToBot( clientNum ) ) {
return false;
}
}
if ( client.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) {
enemyView = GetPlayerViewPosition( clientNum ); //mal: look them in the eye...
} else {
GetVehicleInfo( client.proxyInfo.entNum, vehicleInfo );
enemyView = vehicleInfo.origin;
float enemyVehicleOffset = botThreadData.GetVehicleTargetOffset( vehicleInfo.type );
enemyView.z += ( vehicleInfo.bbox[ 1 ][ 2 ] - vehicleInfo.bbox[ 0 ][ 2 ] ) * enemyVehicleOffset; //mal: look at the top of their vehicle - if we can see it, we can shoot it!
}
//mal: see if hes in our field of view, if thats desired...
if ( useFOV ) {
if ( botVehicleInfo == NULL || botInfo->proxyInfo.hasTurretWeapon ) {
if ( !InFrontOfClient( botNum, enemyView ) ) {
return false;
}
} else {
if ( !InFrontOfVehicle( botInfo->proxyInfo.entNum, enemyView ) ) {
return false;
}
}
}
if ( botVehicleInfo != NULL ) {
entNum = botInfo->proxyInfo.entNum;
}
//mal: hes in our FOV (or we don't care about FOV) - need to see if hes visible now!
botThreadData.clip->TracePointExt( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, enemyView, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ), ( entNum == -1 ) ? NULL : GetGameEntity( entNum ) );
if ( saveEnt ) {
if ( ( client.isLeaning || client.usingMountedGPMG ) && tr.fraction == 1.0f ) {
gunTargetEntNum = clientNum;
} else {
gunTargetEntNum = tr.c.entityNum; //mal: lets save off whos in our gun sights for later...
}
}
if ( tr.c.entityNum != clientNum && tr.fraction != 1.0f ) {
if ( client.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: is the bastid in a vehicle?
if ( tr.c.entityNum != client.proxyInfo.entNum ) {
return false;
}
} else {
return false;
}
}
if ( botVehicleInfo != NULL && botVehicleInfo->canRotateInPlace && botVehicleInfo->type != MCP ) { //mal: tanks need a 2nd trace, to make sure we can actually see AND shoot the target!
botThreadData.clip->TracePointExt( CLIP_DEBUG_PARMS tr, botInfo->proxyInfo.weaponOrigin, enemyView, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ), GetGameEntity( botVehicleInfo->entNum ) );
if ( tr.c.entityNum != clientNum && tr.fraction != 1.0f ) {
if ( client.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: is the bastid in a vehicle?
if ( tr.c.entityNum != client.proxyInfo.entNum ) {
return false;
}
} else {
return false;
}
}
}
return true; //mal: we see him!
}
/*
================
idBotAI::ClientObscuredBySmokeToBot
Check to see if the client in question is obscured from the bot's view
by a cloud of smoke.
================
*/
bool idBotAI::ClientObscuredBySmokeToBot ( int clientNum ) {
int smokeExplodeDelay = 6000;
float maxSmokeRadius = 320.0f;
float maxSmokeRadiusTime = 5000;
float smokeRadius;
float dist;
idVec3 point;
idVec3 start = botInfo->viewOrigin;
idVec3 end = botWorld->clientInfo[ clientNum ].viewOrigin;
idVec3 temp;
idVec3 vec, pVec, vProj;
point = botWorld->clientInfo[ clientNum ].origin - botInfo->origin;
if ( point.LengthSqr() < Square( 450.0f ) ) {
return false;
} //mal: too close for it to matter.
for( int i = 0; i < MAX_CLIENTS; i++ ) {
const smokeBombInfo_t &smokeNade = botWorld->smokeGrenades[ i ];
if ( smokeNade.entNum == 0 && smokeNade.birthTime + 30000 < botWorld->gameLocalInfo.time ) {
continue;
}
if ( smokeNade.birthTime + smokeExplodeDelay > botWorld->gameLocalInfo.time ) {
continue;
} //mal: takes a few seconds for the effect to be generated
if ( smokeNade.xySpeed > 50.0f ) {
continue;
} //mal: dont worry about it if its still moving
point = smokeNade.origin;
point[ 2 ] += 32.0f; //mal: raise it off the ground.
smokeRadius = maxSmokeRadius * ( ( botWorld->gameLocalInfo.time - ( smokeNade.birthTime + smokeExplodeDelay - 3000 ) ) / maxSmokeRadiusTime );
if ( smokeRadius > maxSmokeRadius ) {
smokeRadius = maxSmokeRadius;
}
if ( botThreadData.AllowDebugData() ) {
gameRenderWorld->DebugCircle( colorRed, point, idVec3( 0, 0, 1 ), smokeRadius, 24, 16, true );
gameRenderWorld->DebugLine( colorOrange, point, point + idVec3( 0, 0, 128.0f ) );
}
pVec = point - start;
vec = end - start;
float lengthSqr = vec.LengthSqr();
float f = ( pVec * vec ) / lengthSqr;
f = idMath::ClampFloat( 0.0f, 1.0f, f );
vProj = pVec - ( f * vec );
dist = vProj.LengthSqr();
if ( dist < Square( smokeRadius ) ) {
return true;
}
}
return false;
}
/*
================
idBotAI::Bot_SetAttackTimeDelay
Sets how long the bot should delay "awareness" of an enemy.
Is influenced by what state the bot is currently in.
================
*/
void idBotAI::Bot_SetAttackTimeDelay( bool inFront ) {
if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP ) ) {
timeTilAttackEnemy = botWorld->gameLocalInfo.time + 450; // a bit longer, because bot was busy doing something.
} else if ( aiState == COMBAT ) {
timeTilAttackEnemy = botWorld->gameLocalInfo.time + 100; // really fast if already in a fighting mood
} else { // LTG, etc
timeTilAttackEnemy = botWorld->gameLocalInfo.time + 300; // not too long normally.
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
timeTilAttackEnemy += 1000; //mal: if training mode, add a bit of "lag" in our targettting of enemies
} else if ( botWorld->gameLocalInfo.botAimSkill == 0 ) {
timeTilAttackEnemy += 1000; //mal: if low skill, add a bit of "lag" in our targettting of enemies
} else if ( botWorld->gameLocalInfo.botAimSkill < 2 ) {
timeTilAttackEnemy += 500; //mal: if low skill, add a bit of "lag" in our targettting of enemies
} else {
if ( botInfo->isDisguised ) { //mal: if we were disguised and decided to fight, obviously we already see our enemy, so be quicker about attacking.
fastAwareness = true;
}
if ( ClientIsDangerous( enemy ) ) { //mal: be quicker about catching campers.
fastAwareness = true;
}
if ( Client_IsCriticalForCurrentObj( botNum, 1500.0f ) && inFront != false ) { //mal: if the guys in front of us, we're prolly aware of him already, just didnt want a distracting fight.
fastAwareness = true;
}
if ( aiState == NBG && ( nbgType == CAMP || nbgType == DEFENSE_CAMP ) && heardClient == enemy ) { //mal: if we were camping and heard this guy coming, we're gonna react faster.
fastAwareness = true;
}
}
if ( fastAwareness != false ) {
timeTilAttackEnemy = botWorld->gameLocalInfo.time + 50; //REALLY fast - only used for enemies that we already "spotted" somewhere else, or had purposely ignored for some reason.
fastAwareness = false;
}
}
/*
================
idBotAI::Bot_CheckCurrentStateForCombat
================
*/
void idBotAI::Bot_CheckCurrentStateForCombat() {
float evadeDist = Square( 1500.0f );
idVec3 vec;
//mal: this code will decide if the bot should chase, flank, flee, etc.
//mal: will make this decsion based on health, weapon, ammo, what the enemy is/has, how many enemies are vis, and what the bot is trying to do ( heal/revive, get some obj, etc).
if ( AIStack.STACK_AI_NODE != NULL ) {
if ( ( AIStack.isPriority && !botInfo->isDisguised ) || ClientHasObj( botNum ) || Client_IsCriticalForCurrentObj( botNum, 2500.0f ) ) {
if ( AIStack.stackActionNum != ACTION_NULL ) {
vec = botThreadData.botActions[ AIStack.stackActionNum ]->origin - botInfo->origin;
if ( botThreadData.botActions[ AIStack.stackActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_DEFENSE_CAMP ) {
evadeDist = Square( 10000.0f );
}
} else if ( AIStack.stackClientNum != -1 ) {
vec = botWorld->clientInfo[ AIStack.stackClientNum ].origin - botInfo->origin;
} else if ( AIStack.stackEntNum != -1 ) {
if ( AIStack.stackEntNum < MAX_CARRYABLES && ( AIStack.stackLTGType == RECOVER_GOAL || AIStack.stackLTGType == STEAL_GOAL ) ) {
vec = botWorld->botGoalInfo.carryableObjs[ AIStack.stackEntNum ].origin;
evadeDist = Square( 3500.0f );
} else {
vec = botInfo->origin;
}
} else { //mal: not currently supported - so just fight.
vec = botInfo->origin;
}
}
if ( ClientHasObj( botNum ) || Client_IsCriticalForCurrentObj( botNum, 2500.0f ) ) {
evadeDist = Square( 3500.0f );
}
if ( vec.LengthSqr() < evadeDist ) {
COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_EvadeEnemy; //mal: if bot has a priority target, and its close, try to avoid combat as much as possible.
return;
}
}
COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_AttackEnemy;
}
/*
================
idBotAI::ClassWeaponCharged
================
*/
bool idBotAI::ClassWeaponCharged( const playerWeaponTypes_t weaponNum ) {
//mal_TODO: add skill based code someday! also class code for other classes! also exceptions that may arise when bot gets combat awards
if ( weaponNum == HEALTH || weaponNum == AMMO_PACK ) {
int baseCharge = 80;
if ( weaponNum == HEALTH && botInfo->abilities.fasterMedicCharge ) {
baseCharge = 90;
}
if ( botInfo->supplyChargeUsed < baseCharge ) {
return true;
} else {
return false;
}
}
if ( weaponNum == NEEDLE ) {
if ( botInfo->classChargeUsed == 0 ) {
return true;
} else {
return false;
}
}
if ( weaponNum == SHIELD_GUN ) {
if ( botInfo->deviceChargeUsed < 50 ) {
return true;
} else {
return false;
}
}
if ( weaponNum == AIRCAN ) {
if ( botInfo->fireSupportChargedUsed == 0 ) {
return true;
} else {
return false;
}
}
if ( weaponNum == THIRD_EYE ) {
if ( botInfo->deviceChargeUsed == 0 && botInfo->weapInfo.covertToolInfo.entNum == 0 ) {
return true;
} else {
return false;
}
}
if ( weaponNum == TELEPORTER ) {
if ( botInfo->deviceChargeUsed == 0 && botInfo->hasTeleporterInWorld == false ) {
return true;
} else {
return false;
}
}
if ( weaponNum == SMOKE_NADE || weaponNum == FLYER_HIVE ) {
if ( botInfo->deviceChargeUsed == 0 ) {
return true;
} else {
return false;
}
}
if ( weaponNum == SUPPLY_MARKER ) {
if ( botInfo->supplyChargeUsed == 0 ) {
return true;
} else {
return false;
}
}
return false;
}
/*
================
idBotAI::UpdateEnemyInfo
================
*/
void idBotAI::UpdateEnemyInfo() {
idVec3 vec;
if ( !ClientIsVisibleToBot( enemy, false, true ) ) {
enemyInfo.enemyVisible = false;
} else {
enemyInfo.enemyVisible = true;
}
if ( enemyInfo.enemyVisible ) {
enemyInfo.enemy_LS_Pos = botWorld->clientInfo[ enemy ].origin;
bot_LS_Enemy_Pos = botInfo->origin;
enemyInfo.enemy_NS_Pos = vec3_zero;
enemyInfo.enemyLastVisTime = botWorld->gameLocalInfo.time;
}
vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
enemyInfo.enemyHeight = idMath::Ftoi( vec.z );
enemyInfo.enemyDist = vec.LengthFast();
enemyInfo.enemyInfont = ( botVehicleInfo == NULL ) ? InFrontOfClient( botNum, botWorld->clientInfo[ enemy ].origin ) : InFrontOfVehicle( botInfo->proxyInfo.entNum, botWorld->clientInfo[ enemy ].origin );
enemyInfo.enemyFacingBot = ( botWorld->clientInfo[ enemy ].proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) ? InFrontOfClient( enemy, botInfo->origin ) : InFrontOfVehicle( botWorld->clientInfo[ enemy ].proxyInfo.entNum, botInfo->origin );
}
/*
================
idBotAI::Bot_FindBetterEnemy
We'll sort thru the clients, and ignore certain clients if we're too busy
to be buggered (carrying obj, planting/hacking, etc) or they're not valid enemies
(in disguise, hidden by smoke, etc). Our current enemy will be the standard we measure
others against.
================
*/
bool idBotAI::Bot_FindBetterEnemy() {
bool useFOV = true;
int i;
int entClientNum = enemy;
float dist;
float entDist;
float sightDist = ENEMY_SIGHT_BUSY_DIST;
idVec3 vec;
if ( enemy == -1 ) { //mal: we lost our enemy for some reason, so just skip finding a new one til next frame.
return false;
}
if ( ignoreNewEnemiesTime > botWorld->gameLocalInfo.time ) {
return false;
}
if ( botWorld->clientInfo[ enemy ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: if we're attacking the MCP, its the priority.
if ( botWorld->clientInfo[ enemy ].proxyInfo.entNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) {
return false;
}
}
vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
entDist = vec.LengthSqr();
if ( !enemyInfo.enemyVisible ) {
entDist = idMath::INFINITY;
}
const clientInfo_t& enemyPlayerInfo = botWorld->clientInfo[ enemy ];
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !botWorld->botGoalInfo.gameIsOnFinalObjective && ( !enemyPlayerInfo.isBot || enemyPlayerInfo.isActor ) ) { //mal: dont worry about keeping our human target in training mode, unless its the final obj...
entDist += Square( TRAINING_MODE_RANGE_ADDITION );
sightDist += TRAINING_MODE_RANGE_ADDITION;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
sightDist = 700.0f;
}
numVisEnemies = 1; //mal: our current enemy is always visible to us
//mal_TODO: this will need to be VASTLY improved as time goes on!!!!
for ( i = 0; i < MAX_CLIENTS; i++ ) {
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
if ( i == botNum ) {
continue; //mal: dont try to fight ourselves!
}
if ( i == enemy ) { //mal: ignore an enemy we already have
continue;
}
if ( EnemyIsIgnored( i ) ) {
continue; //mal: dont try to fight someone we've flagged to ignore for whatever reason!
}
//mal: if we're in the middle of a critical obj, dont go looking for trouble, unless they're shooting us!
if ( Client_IsCriticalForCurrentObj( botNum, -1.0f ) && ( botInfo->lastAttacker != i || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) ) {
continue;
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( playerInfo.isNoTarget ) {
continue;
} //mal: dont target clients that have notarget set - this is useful for debugging, etc.
if ( playerInfo.isDisguised && playerInfo.disguisedClient != botNum ) {
continue; //mal: won't "see" disguised clients, unless they look like us!
}
if ( playerInfo.inLimbo ) {
continue;
}
if ( playerInfo.isActor ) {
continue;
}
if ( playerInfo.invulnerableEndTime > botWorld->gameLocalInfo.time ) {
continue; //mal: ignore revived/just spawned in clients - get the ppl around them!
}
if ( playerInfo.health <= 0 ) {
continue;
}
if ( playerInfo.team == botInfo->team ) {
continue;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !botWorld->botGoalInfo.gameIsOnFinalObjective && !playerInfo.isBot && enemyPlayerInfo.isBot ) { //mal: dont worry about human targets in training mode if we have a bot one, unless its the final obj...
continue;
}
vec = playerInfo.viewOrigin - botInfo->viewOrigin;
dist = vec.LengthSqr();
if ( dist > Square( sightDist ) ) {
continue;
}
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO || botWorld->botGoalInfo.gameIsOnFinalObjective ) { //mal: dont worry about picking the best targets in training mode, unless its the final obj...
if ( Client_IsCriticalForCurrentObj( i, sightDist ) ) { //mal: if a critical class, get high priority
dist = Square( 100 );
ignoreNewEnemiesTime = botWorld->gameLocalInfo.time + IGNORE_NEW_ENEMIES_TIME;
if ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY ) { //mal: high skill bots are aware of enemies near obj.
useFOV = false;
}
}
if ( ClientHasAttackedTeammate( i, true, 3000 ) ) {
dist = Square( 150 );
ignoreNewEnemiesTime = botWorld->gameLocalInfo.time + IGNORE_NEW_ENEMIES_TIME;
if ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY ) { //mal: high skill bots are aware of enemies attacking our critical friends.
useFOV = false;
}
}
if ( ClientHasObj( i ) ) { //mal: if have docs, get HIGHER priority.
dist = Square( 50 );
ignoreNewEnemiesTime = botWorld->gameLocalInfo.time + IGNORE_NEW_ENEMIES_TIME;
if ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY ) { //mal: high skill bots are aware of enemies near obj.
useFOV = false;
}
}
if ( botWorld->botGoalInfo.mapHasMCPGoal ) {
if ( playerInfo.proxyInfo.entNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) {
dist = Square( 200.0f );
ignoreNewEnemiesTime = botWorld->gameLocalInfo.time + IGNORE_NEW_ENEMIES_TIME;
if ( botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY ) { //mal: high skill bots are aware of enemies near obj.
useFOV = false;
}
}
}
}
if ( !ClientIsVisibleToBot( i, useFOV, false ) ) {
continue;
}
numVisEnemies++;
if ( dist < entDist )
{
entClientNum = i;
entDist = dist;
}
}
if ( entClientNum != enemy ) {
enemy = entClientNum;
enemySpawnID = botWorld->clientInfo[ entClientNum ].spawnID;
enemyInfo.enemy_FS_Pos = botWorld->clientInfo[ entClientNum ].origin;
enemyInfo.enemy_LS_Pos = enemyInfo.enemy_FS_Pos;
enemyIsHuntGoal = false;
enemyAcquireTime = botWorld->gameLocalInfo.time;
enemyInfo.enemyLastVisTime = botWorld->gameLocalInfo.time;
bot_FS_Enemy_Pos = botInfo->origin;
bot_LS_Enemy_Pos = bot_FS_Enemy_Pos;
COMBAT_AI_SUB_NODE = NULL; //mal: reset the bot's combat AI node and movement state.
COMBAT_MOVEMENT_STATE = NULL;
combatNBGType = NO_COMBAT_TYPE;
return true;
}
return false;
}
/*
================
idBotAI::Bot_PickBestWeapon
================
*/
void idBotAI::Bot_PickBestWeapon( bool useNades ) {
bool isAirborneVehicle = false;
bool isGroundVehicle = false;
bool enemyIsMovingSlow;
int ourEnemiesAroundEnemy;
int ourFriendsAroundEnemy;
if ( weapSwitchTime > botWorld->gameLocalInfo.time ) {
return;
}
if ( botWorld->gameLocalInfo.inWarmup ) {
return;
}
if ( botInfo->usingMountedGPMG ) {
return;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO || botWorld->gameLocalInfo.botSkill <= BOT_SKILL_NORMAL ) {
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
useNades = false;
}
}
if ( botWorld->gameLocalInfo.botKnifeOnly != false ) {
botIdealWeapNum = NULL_WEAP;
botIdealWeapSlot = MELEE;
return;
}
const clientInfo_t& enemyClient = botWorld->clientInfo[ enemy ];
if ( botInfo->classType == COVERTOPS ) {
if ( !enemyInfo.enemyFacingBot && enemyInfo.enemyDist < 300.0f && enemyClient.xySpeed <= RUNNING_SPEED && enemyClient.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) {
if ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) {
botIdealWeapSlot = MELEE;
return;
}
}
} //mal: we got the drop on someone - knife them in the back for major humiliation points!
ourEnemiesAroundEnemy = ClientsInArea( botNum, enemyClient.origin, 700.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
ourFriendsAroundEnemy = ClientsInArea( botNum, enemyClient.origin, 700.0f, botInfo->team, NOCLASS, false, false, false, false, false );
enemyIsMovingSlow = ( enemyClient.xySpeed < 600.0f ) ? true : false; //mal: if enemy is moving too fast, cant target with things like nades, etc.
if ( enemyClient.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
proxyInfo_t vehicleInfo;
GetVehicleInfo( enemyClient.proxyInfo.entNum, vehicleInfo );
isAirborneVehicle = ( vehicleInfo.isAirborneVehicle != false ) ? true : false;
isGroundVehicle = ( vehicleInfo.type < ICARUS && !( vehicleInfo.flags & WATER ) ) ? true : false;
}
switch( botInfo->classType ) {
case MEDIC:
if ( useNades && useNadeOnEnemy == false ) {
if ( ( !enemyInfo.enemyFacingBot || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < GRENADE_ATTACK_DIST && enemyInfo.enemyHeight < 100.0f && botInfo->weapInfo.hasNadeAmmo ) {
useNadeOnEnemy = true;
}
}
if ( useNadeOnEnemy == false ) {
if ( botInfo->weapInfo.primaryWeapon == SHOTGUN ) {
if ( enemyInfo.enemyDist < SHOTGUN_DIST ) {
botIdealWeapSlot = GUN;
} else {
botIdealWeapSlot = SIDEARM;
}
} else {
botIdealWeapSlot = GUN;
}
}
break;
case FIELDOPS:
if ( hammerTime ) {
if ( ClassWeaponCharged( AIRCAN ) || ( hammerVehicle && botInfo->weapInfo.artyAttackInfo.deathTime > botWorld->gameLocalInfo.time ) ) {
botIdealWeapNum = BINOCS;
botIdealWeapSlot = NO_WEAPON;
break;
} else {
hammerTime = false;
hammerVehicle = false;
resetEnemy = true;
break; //mal: dont do anything for a frame.
}
}
if ( useNades && useNadeOnEnemy == false && useAircanOnEnemy == false) {
if ( ( !enemyInfo.enemyFacingBot || ClientIsDefusingOurTeamCharge( enemy ) || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 700.0f && enemyInfo.enemyDist < 1700.0f && enemyInfo.enemyHeight < 100.0f && ClassWeaponCharged( AIRCAN ) && LocationVis2Sky( enemyClient.origin ) ) {
useAircanOnEnemy = true;
} else if ( ( !enemyInfo.enemyFacingBot || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < GRENADE_ATTACK_DIST && enemyInfo.enemyHeight < 100.0f && botInfo->weapInfo.hasNadeAmmo ) {
useNadeOnEnemy = true;
}
}
if ( useNadeOnEnemy == false && useAircanOnEnemy == false ) {
botIdealWeapSlot = GUN; //mal: the weap autoswitch code will handle switching to pistol if gun is out of ammo.
}
break;
case ENGINEER: //mal_TODO: need to add code here for the eng's nade launcher. Currently its a upgrade, but it may change.
if ( useNades && useNadeOnEnemy == false ) {
if ( ( !enemyInfo.enemyFacingBot || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < GRENADE_ATTACK_DIST && enemyInfo.enemyHeight < 100.0f && botInfo->weapInfo.hasNadeAmmo ) {
useNadeOnEnemy = true;
}
}
if ( useNadeOnEnemy == false ) {
if ( botInfo->weapInfo.primaryWeapon == SHOTGUN ) {
if ( enemyInfo.enemyDist < SHOTGUN_DIST ) {
botIdealWeapSlot = GUN; //mal: the weap autoswitch code will handle switching to pistol if gun is out of ammo.
} else {
botIdealWeapSlot = SIDEARM;
}
} else {
botIdealWeapSlot = GUN;
}
}
break;
case COVERTOPS:
if ( useNades && useNadeOnEnemy == false ) {
if ( ( !enemyInfo.enemyFacingBot || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < GRENADE_ATTACK_DIST && enemyInfo.enemyHeight < 100.0f && botInfo->weapInfo.hasNadeAmmo ) {
useNadeOnEnemy = true;
}
}
if ( useNadeOnEnemy == false ) {
if ( botInfo->weapInfo.primaryWeapon == SNIPERRIFLE ) {
if ( ( enemyInfo.enemyDist > 900.0f || enemyInfo.enemyDist < 300.0f ) && botInfo->enemiesInArea < 2 && ( botInfo->weapInfo.primaryWeapClipEmpty == false || enemyInfo.enemyDist > 700.0f || botInfo->friendsInArea > 0 ) ) {
botIdealWeapSlot = GUN; //mal: if my enemy is far away, or REALLY close, and I'm not surrounded, and I have a bullet in the clip, or my enemy is far enough away for me to safely reload or I have some friends near me to cover me while I reload, I'll use my sniperrifle.
} else {
botIdealWeapSlot = SIDEARM;
}
} else {
botIdealWeapSlot = GUN;
}
}
break;
case SOLDIER:
if ( useNades && useNadeOnEnemy == false ) {
if ( ( !enemyInfo.enemyFacingBot || ourEnemiesAroundEnemy > 2 || isGroundVehicle == true && ( isAirborneVehicle == false && enemyIsMovingSlow ) ) && enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < GRENADE_ATTACK_DIST && enemyInfo.enemyHeight < 100.0f && botInfo->weapInfo.hasNadeAmmo && ( botInfo->weapInfo.primaryWeapon != ROCKET && botInfo->weapInfo.primaryWeapon != HEAVY_MG ) ) {
useNadeOnEnemy = true;
}
}
if ( useNadeOnEnemy == false ) {
if ( botInfo->weapInfo.primaryWeapon == SHOTGUN ) {
if ( enemyInfo.enemyDist < SHOTGUN_DIST ) {
botIdealWeapSlot = GUN; //mal: the weap autoswitch code will handle switching to pistol if gun is out of ammo.
} else {
if ( botInfo->team == STROGG ) {
if ( enemyInfo.enemyDist > LIGHTNING_GUN_DIST ) {
botIdealWeapSlot = GUN;
} else {
botIdealWeapSlot = SIDEARM; //mal: lightning gun is ranged.
}
} else {
botIdealWeapSlot = SIDEARM;
}
}
} else if ( botInfo->weapInfo.primaryWeapon == SMG ) { //mal: the easiest of the bunch! :-D
botIdealWeapSlot = GUN;
} else if ( botInfo->weapInfo.primaryWeapon == HEAVY_MG ) {
if ( enemyInfo.enemyDist < 2500.0f && ( botInfo->weapInfo.primaryWeapClipEmpty == false || enemyInfo.enemyDist > 700.0f || botInfo->friendsInArea > 0 ) ) {
botIdealWeapSlot = GUN; //mal: the weap autoswitch code will handle switching to pistol if gun is out of ammo.
} else {
botIdealWeapSlot = SIDEARM;
}
} else if ( botInfo->weapInfo.primaryWeapon == ROCKET ) {
if ( enemyClient.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE && ourFriendsAroundEnemy == 0 && enemyInfo.enemyDist < WEAPON_LOCK_DIST ) { //mal: dont kill our friends!
botIdealWeapSlot = GUN;
} else {
if ( enemyInfo.enemyDist > 500.0f && enemyInfo.enemyDist < 2500.0f && ( botInfo->weapInfo.primaryWeapClipEmpty == false || enemyInfo.enemyDist > 1200.0f || enemyClient.friendsInArea > 1 ) && ourFriendsAroundEnemy == 0 ) {
botIdealWeapSlot = GUN;
} else {
if ( botInfo->team == STROGG ) {
if ( enemyInfo.enemyDist > LIGHTNING_GUN_DIST ) {
botIdealWeapSlot = GUN;
} else {
botIdealWeapSlot = SIDEARM; //mal: lightning gun is ranged.
}
} else {
botIdealWeapSlot = SIDEARM;
}
}
}
}
}
break;
default:
botIdealWeapSlot = GUN;
break;
}
weapSwitchTime = botWorld->gameLocalInfo.time + 1500; //mal: dont constantly switch back and forth between weapons.
if ( useAircanOnEnemy != false ) {
botIdealWeapSlot = NO_WEAPON;
botIdealWeapNum = AIRCAN;
} else if ( useNadeOnEnemy != false ) {
botIdealWeapSlot = NADE;
botIdealWeapNum = NULL_WEAP;
if ( combatMoveType != GRENADE_ATTACK ) {
COMBAT_MOVEMENT_STATE = NULL;
}
}
}
/*
================
idBotAI::Bot_PickPostCombatGoal
Decide if we should do something to our recently deceased enemy.
TODO: add more possible post combat behaviors?
================
*/
bool idBotAI::Bot_PickPostCombatGoal() {
if ( !ClientIsValid( enemy, -1 ) ) {
return false;
}
if ( Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) { //mal: bots who are critical to the war effort will ignore such sillyness.
return false;
}
if ( AIStack.STACK_AI_NODE != NULL && AIStack.isPriority == true ) { //mal: we were doing something important before we got in combat - get back to it!
return false;
}
if ( ( botInfo->classType != MEDIC || botInfo->team != STROGG ) && botInfo->classType != COVERTOPS && botThreadData.random.RandomInt( 100 ) > 80 ) {
if ( !botWorld->clientInfo[ enemy ].inLimbo && !botWorld->clientInfo[ enemy ].inWater && !botWorld->clientInfo[ enemy ].isBot ) {
if ( botWorld->clientInfo[ enemy ].areaNum > 0 ) {
idVec3 vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
if ( vec.LengthSqr() < Square( 700.0f ) ) {
nbgTarget = enemy;
nbgTargetSpawnID = enemySpawnID;
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_HumiliateEnemy;
Bot_ResetEnemy();
return true;
}
}
}
}
//mal: slows the game down too much in ETQW.
/*
if ( botThreadData.random.RandomInt( 100 ) > 80 && !LocationVis2Sky( botInfo->origin ) ) { //mal: sometimes, when indoors, take a sec to look around for more enemies.
nbgOrigin = enemyInfo.enemy_FS_Pos;
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Pause;
Bot_ResetEnemy();
return true;
}
*/
return false;
}
/*
================
idBotAI::Bot_ShouldChaseHiddenEnemy
================
*/
bool idBotAI::Bot_ShouldChaseHiddenEnemy( bool chase ) {
proxyInfo_t vehicleInfo;
idVec3 vec, origin;
chaseEnemy = false;
chasingEnemy = false;
if ( enemyIsHuntGoal ) {
return true;
}
if ( botInfo->usingMountedGPMG ) {
return false;
}
if ( ClientHasObj( botNum ) || Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) { //mal: never chase if we are important!
return false;
}
if ( botInfo->health <= 30 ) {
if ( botThreadData.random.RandomInt( 100 ) > 50 ) { //mal: if we're in bad shape, sometimes we'll give up the fight
return false;
}
}
if ( !botInfo->weapInfo.primaryWeapHasAmmo && !ClientHasObj( enemy ) ) { //mal: we don't have any ammo, and hes not that important, so let him go...
return false;
}
if ( botWorld->clientInfo[ enemy ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
GetVehicleInfo( botWorld->clientInfo[ enemy ].proxyInfo.entNum, vehicleInfo );
if ( vehicleInfo.inWater ) {
return false;
} //mal: hes in the water, but we can't chase him, so forget him!
if ( !ClientHasObj( enemy ) && !Client_IsCriticalForCurrentObj( enemy, 2000.0f ) ) {
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
return false;
}
}
}
if ( AIStack.STACK_AI_NODE != NULL && AIStack.isPriority != false && !ClientHasObj( enemy ) ) {
return false;
} // we have something important on our mind, and cant chase ATM - UNLESS they have the obj! Always chase them!
origin = ( chase ) ? enemyInfo.enemy_NS_Pos : enemyInfo.enemy_LS_Pos;
vec = origin - botInfo->origin;
if ( vec.LengthSqr() > Square( ENEMY_CHASE_DIST ) ) {
return false;
} // too far away to chase
vec = origin - botWorld->clientInfo[ enemy ].origin;
if ( vec.LengthSqr() > Square( 900.0f ) && !ClientHasObj( enemy ) && !Client_IsCriticalForCurrentObj( enemy, 1500.0f ) && !ClientIsDangerous( enemy ) ) { // unless he's REALLY close or a major threat, sometimes we'll stop the chase, just because....
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
return false;
}
}
//mal_TODO: add more stuff here as we need to.
if ( chase ) {
chaseEnemy = true;
}
chasingEnemy = true;
return true;
}
/*
================
idBotAI::Bot_CheckShouldUseGrenade
================
*/
bool idBotAI::Bot_CheckShouldUseGrenade( bool targetVisible ) {
bool useGrenade = false;
idVec3 vec;
float dist;
if ( botInfo->weapInfo.hasNadeAmmo == false ) {
return false;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
return false;
}
}
if ( !targetVisible ) {
if ( ( lastGrenadeTime + 15000 ) > botWorld->gameLocalInfo.time ) { //mal: we already threw a nade recently - dont do so again.
return false;
}
if ( enemyInfo.enemy_LS_Pos == vec3_zero ) {
return false;
}
vec = enemyInfo.enemy_LS_Pos - botInfo->origin;
if ( vec.LengthSqr() > Square( GRENADE_THROW_MAXDIST ) ) {
return false;
}
if ( botThreadData.random.RandomInt( 100 ) > 85 ) {
return false;
}
vec = enemyInfo.enemy_LS_Pos - botWorld->clientInfo[ enemy ].origin;
if ( vec.LengthSqr() > Square( 900.0f ) ) {
return false;
}
trace_t tr;
botThreadData.clip->TracePointExt( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, enemyInfo.enemy_LS_Pos, MASK_SHOT_BOUNDINGBOX | MASK_VEHICLESOLID | CONTENTS_FORCEFIELD, GetGameEntity( botNum ), NULL );
if ( tr.fraction < 1.0f ) {
return false;
}
useGrenade = true;
safeGrenade = ( botThreadData.random.RandomInt( 100 ) > 40 ) ? true : false; // for safe nade - will stand back and throw, else will charge up to enemy and toss at them
} else { //mal: target is visible!
int clients;
vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
dist = vec.LengthFast();
if ( dist > GRENADE_THROW_MAXDIST ) { // too far away!
return false;
}
if ( !enemyInfo.enemyFacingBot ) { //sneak attack!
return true;
}
clients = ClientsInArea( -1, botWorld->clientInfo[ enemy ].origin, 600.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
if ( clients > 3 ) { // bunch of them in the same area - take advantage of this! need more clients in same area to make this worthwhile
return true;
}
if ( enemyInfo.enemyHeight <= -150 ) { // we have the height advantage - so use it!
return true;
}
}
return useGrenade;
}
/*
================
idBotAI::UpdateNonVisEnemyInfo
================
*/
void idBotAI::UpdateNonVisEnemyInfo() {
if ( !ClientIsValid( enemy, -1 ) ) {
return;
}
trace_t tr;
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, enemyInfo.enemy_LS_Pos, botWorld->clientInfo[ enemy ].origin, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( enemy ) );
if ( tr.fraction == 1.0f ) {
enemyInfo.enemy_NS_Pos = botWorld->clientInfo[ enemy ].origin;
}
}
/*
================
idBotAI::Bot_ThrowGrenade
================
*/
bool idBotAI::Bot_ThrowGrenade( const idVec3 &origin, bool fastNade ) {
int fuseTime = ( fastNade == true ) ? 250 : 3000;
float height;
idVec3 vec = origin - botInfo->origin;
float dist = vec.LengthSqr();
// Bot_LookAtLocation( origin, INSTANT_TURN );
if ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) {
botUcmd->botCmds.attack = true;
botUcmd->botCmds.constantFire = true;
height = vec[ 2 ];
idVec3 nadeTarget = origin;
if ( dist > Square( GRENADE_THROW_MINDIST ) && height < 150.0f ) {
nadeTarget.z += 32.0f;
botUcmd->botCmds.throwNade = true;
}
Bot_LookAtLocation( nadeTarget, INSTANT_TURN, true );
if ( botThreadData.AllowDebugData() ) {
gameRenderWorld->DebugLine( colorLtGrey, botInfo->viewOrigin, nadeTarget, 99 );
}
if ( botInfo->weapInfo.weapon == EMP ) {
if ( botThreadData.random.RandomInt( 100 ) > 90 ) {
botUcmd->botCmds.attack = false;
lastGrenadeTime = botWorld->gameLocalInfo.time;
useNadeOnEnemy = false;
return true;
}
}
if ( botInfo->weapInfo.grenadeFuseStart != -1 ) {
if ( botWorld->gameLocalInfo.time > ( ( botInfo->weapInfo.grenadeFuseStart * 1000 ) + fuseTime ) ) {
botUcmd->botCmds.attack = false;
lastGrenadeTime = botWorld->gameLocalInfo.time;
useNadeOnEnemy = false;
return true;
}
}
}
return false;
}
/*
================
idBotAI::Bot_CheckCombatExceptions
================
*/
bool idBotAI::Bot_CheckCombatExceptions() {
int attackerHealth;
float distToAttackerSqr = Square( 99999.0f );
float ignoreDangerDist = 2500.0f;
idVec3 vec;
if ( botInfo->lastAttacker > -1 && botInfo->lastAttacker < MAX_CLIENTS ) {
attackerHealth = botWorld->clientInfo[ botInfo->lastAttacker ].health;
vec = botWorld->clientInfo[ botInfo->lastAttacker ].origin - botInfo->origin;
distToAttackerSqr = vec.LengthSqr();
} else { //mal: paranoid safety check!
attackerHealth = 0;
} //mal: check to see if the guy who attacked us last is dead - in which case we won't worry about having been shot by him recently!
if ( botInfo->onLadder ) {
if ( botInfo->lastAttackerTime + 1500 < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
}
}
if ( aiState == LTG && ( ltgType == STEAL_GOAL || ltgType == DELIVER_GOAL || ltgType == RECOVER_GOAL ) ) {
if ( botInfo->lastAttackerTime + 1500 < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
}
}
switch ( botInfo->classType ) {
case MEDIC: {
if ( ( aiState == NBG && nbgType == SUPPLY_TEAMMATE ) ) { // medics will try to heal humans no matter what!
if ( botInfo->isActor ) {
return false;
}
if ( ClientIsValid( nbgTarget, -1 ) ) {
const clientInfo_t& player = botWorld->clientInfo[ nbgTarget ];
if ( !player.isBot ) { //mal: always make the human feel special and try to heal him no matter what, even tho it will likely get us killed.
return true;
}
}
}
if ( ( aiState == NBG && nbgType == REVIVE_TEAMMATE ) ) { // medics will try to revive before fighting, unless they are under attack!
if ( DisguisedKillerInArea() && botInfo->team != GDF ) {
return false;
}
if ( botInfo->team == GDF && distToAttackerSqr > Square( 1500.0f ) ) { //mal: our intstant revive means we can get the job done before without dying ( hopefully! ).
return true;
}
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to revive, the more friendlies we can bring to bear on our enemy, the better chance we've got of winning!
if ( ClientIsValid( nbgTarget, -1 ) ) {
const clientInfo_t& player = botWorld->clientInfo[ nbgTarget ];
if ( !player.isBot ) {
return true;
}
}
if ( botInfo->team == STROGG ) { //mal: strogg take so much longer to revive, that if we're under attack, we MUST decide whether to engage!
return false;
}
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
vec = botWorld->clientInfo[ nbgTarget ].origin - botInfo->origin;
if ( vec.LengthFast() < 500.0f ) { // really close to our target - take a chance to revive them
return true;
}
if ( botInfo->enemiesInArea == 0 || botInfo->friendsInArea > 1 ) {
return true;
}
}
if ( aiState == LTG && ( ltgType == DEFENSE_CAMP_GOAL || ltgType == DESTROY_DEPLOYABLE_GOAL || ltgType == FDA_GOAL || ltgType == STEAL_SPAWN_GOAL ) ) {
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to build. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( botInfo->enemiesInArea < 2 || botInfo->friendsInArea > 0 ) { //mal: we'll ignore only 1 enemy, hopefully theres some backup around here.
return true;
}
break;
}
break;
}
case ENGINEER: {
if ( ( aiState == NBG && ( nbgType == BUILD || nbgType == DEFUSE_BOMB || nbgType == PLANT_MINE ) ) || Client_IsCriticalForCurrentObj( botNum, 900.0f ) || ( aiState == LTG && ( ltgType == DEFENSE_CAMP_GOAL || ltgType == DESTROY_DEPLOYABLE_GOAL || ltgType == FDA_GOAL || ltgType == FIX_MCP || ltgType == STEAL_SPAWN_GOAL ) ) ) { // eng is actually in the process of building ATM - lets see if he should ignore any enemies - engs will try to build at all costs, unless they are under attack!
if ( actionNum != -1 ) {
if ( botThreadData.botActions[ actionNum ]->GetHumanObj() == ACTION_MINOR_OBJ_BUILD || botThreadData.botActions[ actionNum ]->GetStroggObj() == ACTION_MINOR_OBJ_BUILD ) {
return false; //mal: dont care about minor build objs - they're not worth dying for!
}
}
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to build. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( CriticalEnemyClientNearUs( SOLDIER ) ) {
return false;
}
if ( aiState == LTG && ltgType == FIX_MCP ) {
if ( attackerHealth > 0 ) {
proxyInfo_t enemyVehicle;
GetVehicleInfo( botWorld->clientInfo[ botInfo->lastAttacker ].proxyInfo.entNum, enemyVehicle );
if ( enemyVehicle.entNum != 0 ) {
if ( enemyVehicle.isAirborneVehicle ) {
return true;
}
idVec3 vec = enemyVehicle.origin - botInfo->origin;
if ( vec.LengthSqr() > Square( 1500.0f ) ) {
return true;
}
}
}
}
if ( botInfo->enemiesInArea < 2 || botInfo->friendsInArea > 0 ) { //mal: we'll ignore only 1 enemy, hopefully theres some backup around here.
return true;
}
break;
}
if ( ( aiState == NBG && nbgType == DROP_DEPLOYABLE ) || ( aiState == LTG && ltgType == DROP_DEPLOYABLE_GOAL ) ) {
if ( actionNum != -1 ) {
if ( botThreadData.botActions[ actionNum ]->ActionIsPriority() == false ) {
return false;
}
}
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to build. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( botInfo->enemiesInArea == 0 ) {
return true;
}
break;
}
break;
}
case COVERTOPS: {
if ( Client_IsCriticalForCurrentObj( botNum, 900.0f ) || ( aiState == NBG && nbgType == HACK ) || ( aiState == LTG && ( ltgType == DEFENSE_CAMP_GOAL || ltgType == DESTROY_DEPLOYABLE_GOAL || ltgType == FDA_GOAL || ltgType == STEAL_SPAWN_GOAL ) ) ) {
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to hack. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( botInfo->enemiesInArea < 2 || botInfo->friendsInArea > 0 ) { //mal: we'll ignore only 1 enemy, hopefully theres some backup around here.
return true;
}
break;
}
break;
}
case FIELDOPS: {
if ( aiState == LTG && ( ltgType == DESTROY_DEPLOYABLE_GOAL || ltgType == FDA_GOAL || ltgType == STEAL_SPAWN_GOAL || ltgType == DEFENSE_CAMP_GOAL || ( ltgType == FIRESUPPORT_CAMP_GOAL && ltgReached == false ) ) ) {
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to destroy. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( botInfo->enemiesInArea < 2 || botInfo->friendsInArea > 0 ) { //mal: we'll ignore only 1 enemy, hopefully theres some backup around here.
return true;
}
break;
}
}
case SOLDIER: {
if ( Client_IsCriticalForCurrentObj( botNum, 900.0f ) || ( aiState == NBG && nbgType == PLANT_BOMB ) || ( aiState == LTG && ( ltgType == DEFENSE_CAMP_GOAL || ltgType == DESTROY_DEPLOYABLE_GOAL || ltgType == FDA_GOAL || ltgType == STEAL_SPAWN_GOAL ) ) ) {
if ( actionNum != ACTION_NULL ) {
if ( ClientHasChargeInWorld( botNum, true, actionNum ) ) { //mal: if we've planted, and are just camping nearby, DO attack anyone we see!
return false;
}
}
if ( ( botInfo->lastAttackerTime + 1000 ) < botWorld->gameLocalInfo.time || attackerHealth <= 0 || distToAttackerSqr > Square( ignoreDangerDist ) ) {
return true;
} // if not under direct attack - try to plant. This is important!
if ( attackerHealth > 0 ) { //mal: if the guy who shot us is in front of us, NEVER ignore him!
if ( InFrontOfClient( botNum, botWorld->clientInfo[ botInfo->lastAttacker ].origin ) ) {
return false;
}
}
if ( CriticalEnemyClientNearUs( ENGINEER ) ) {
return false;
}
if ( botInfo->enemiesInArea < 2 || botInfo->friendsInArea > 0 ) { //mal: we'll ignore only 1 enemy, if we've got some backup around here.
return true;
}
break;
}
break;
}
}
return false;
}
/*
================
idBotAI::ClientsInArea
================
*/
int idBotAI::ClientsInArea( int ignoreClientNum, const idVec3 &org, float range, int team, const playerClassTypes_t clientClass, bool inFront, bool vis2Sky, bool ignoreInvulnerable, bool ignoreDisguised, bool ignoreInVehicle, bool humanOnly ) {
int i;
int clients = 0;
idVec3 vec;
for( i = 0; i < MAX_CLIENTS; i++ ) {
if ( i == ignoreClientNum ) { // dont scan the client who started this
continue;
}
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( !playerInfo.inGame ) {
continue;
}
if ( playerInfo.health <= 0 ) { //mal: dont count dead clients!
continue;
}
if ( playerInfo.isNoTarget ) { //mal: let me debug behavior!
continue;
}
if ( humanOnly ) {
if ( playerInfo.isBot ) {
continue;
}
}
if ( clientClass != NOCLASS ) {
if ( playerInfo.classType != clientClass ) {
continue;
}
}
if ( ignoreInVehicle ) {
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
continue;
}
}
if ( ignoreDisguised ) {
if ( playerInfo.isDisguised ) {
continue;
}
}
if ( ignoreInvulnerable ) {
if ( playerInfo.invulnerableEndTime > botWorld->gameLocalInfo.time ) {
continue; //mal: ignore revived/just spawned in clients
}
}
if ( vis2Sky ) {
if ( !LocationVis2Sky( playerInfo.origin ) ) {
continue;
}
}
if ( team != NOTEAM ) {
if ( playerInfo.team != team ) {
continue;
}
}
vec = playerInfo.origin - org;
if ( vec.LengthSqr() > Square( range ) ) {
continue;
}
if ( inFront && ignoreClientNum != -1 ) {
if ( !InFrontOfClient( ignoreClientNum, playerInfo.origin )) {
continue;
}
}
clients++;
}
return clients;
}
/*
================
idBotAI::Bot_CheckShouldUseAircan
================
*/
bool idBotAI::Bot_CheckShouldUseAircan( bool targetVisible ) {
idVec3 vec;
float dist;
if ( botInfo->classType != FIELDOPS ) {
return false;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
return false;
}
}
if ( !ClassWeaponCharged( AIRCAN )) {
return false;
}
if ( !targetVisible ) {
if ( !LocationVis2Sky( enemyInfo.enemy_LS_Pos )) {
return false;
}
if ( enemyInfo.enemy_LS_Pos == vec3_zero ) {
return false;
}
vec = enemyInfo.enemy_LS_Pos - botInfo->origin;
dist = vec.LengthFast();
vec = enemyInfo.enemy_LS_Pos - botWorld->clientInfo[ enemy ].origin;
if ( dist > 900.0f ) {
return false;
}
if ( vec.LengthFast() > 900.0f ) {
return false;
}
return true;
} else { //mal: target is visible!
int clients;
vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
dist = vec.LengthFast();
if ( !LocationVis2Sky( botWorld->clientInfo[ enemy ].origin ) ) {
return false;
}
if ( dist > 900.0f ) { // too far away!
return false;
}
if ( !enemyInfo.enemyFacingBot ) { //sneak attack!
return true;
}
clients = ClientsInArea( -1, botWorld->clientInfo[ enemy ].origin, 600.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, true, false, false, false );
if ( clients > 1 ) { // bunch of them in the same area - take advantage of this!
return true;
}
if ( enemyInfo.enemyHeight <= -100 ) { // we have the height advantage - so use it!
return true;
}
}
return false;
}
/*
================
idBotAI::Bot_UseCannister
================
*/
void idBotAI::Bot_UseCannister( const playerWeaponTypes_t weapType, const idVec3 &origin ) {
botIdealWeapNum = weapType;
botIdealWeapSlot = NO_WEAPON;
if ( botInfo->weapInfo.isReady && botInfo->weapInfo.weapon == weapType ) {
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
botUcmd->botCmds.attack = false;
} else {
botUcmd->botCmds.attack = true;
}
idVec3 vec = origin - botInfo->origin;
float height = vec.z;
if ( vec.LengthSqr() > Square( 700.0f ) && height < 150.0f ) {
botUcmd->botCmds.throwNade = true;
}
}
Bot_LookAtLocation( origin, FAST_TURN );
if ( weapType == AIRCAN ) {
if ( !ClassWeaponCharged( AIRCAN ) ) {
useAircanOnEnemy = false;
weapSwitchTime = 0;
}
}
if ( weapType == SUPPLY_MARKER ) {
botUcmd->botCmds.droppingSupplyCrate = true;
}
}
/*
================
idBotAI::EnemyValid
================
*/
bool idBotAI::EnemyValid() {
if ( !ClientIsValid( enemy, -1 ) ) {
return false;
}
const clientInfo_t& playerEnemyInfo = botWorld->clientInfo[ enemy ];
if ( playerEnemyInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
if ( !botInfo->weapInfo.primaryWeapHasAmmo ) {
return false;
}
}
if ( enemyInfo.enemyDist > botWorld->botGoalInfo.botSightDist ) {
if ( botInfo->classType == COVERTOPS && botInfo->weapInfo.primaryWeapon == SNIPERRIFLE && botInfo->weapInfo.primaryWeapHasAmmo ) {
return true;
}
if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo ) {
if ( botWorld->clientInfo[ enemy ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
return true;
}
}
idVec3 enemyOrg = botWorld->clientInfo[ enemy ].origin;
if ( botInfo->classType == FIELDOPS && ClassWeaponCharged( AIRCAN ) && Bot_HasWorkingDeployable() && !Bot_EnemyAITInArea( enemyOrg ) ) {
return true;
}
return false;
}
return true;
}
/*
================
idBotAI::BotLeftEnemysSight
================
*/
bool idBotAI::BotLeftEnemysSight() {
trace_t tr;
idVec3 otherView = ( botWorld->clientInfo[ enemy ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) ? botWorld->clientInfo[ enemy ].origin : botWorld->clientInfo[ enemy ].viewOrigin;
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, bot_LS_Enemy_Pos, otherView, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
if ( tr.fraction != 1.0f ) {
return false;
} else {
return true; // our enemy can still see the pos we were in when we last saw the enemy!
}
}
/*
================
idBotAI::Bot_CheckAttack
================
*/
void idBotAI::Bot_CheckAttack() {
bool useLockon = ( botInfo->weapInfo.weapon == ROCKET && botWorld->clientInfo[ enemy ].proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) ? true : false;
float minUseScopeDist = ( botInfo->weapInfo.weapon == HEAVY_MG ) ? 400.0f : 700.0f;
if ( botWorld->gameLocalInfo.botAimSkill > 0 ) {
if ( ( botInfo->xySpeed == 0.0f || botInfo->posture == IS_CROUCHED || botInfo->posture == IS_PRONE || combatMoveType == STAND_GROUND_ATTACK || useLockon || botInfo->weapInfo.weapon == HEAVY_MG ) && enemyInfo.enemyDist > minUseScopeDist ) {
if ( botInfo->weapInfo.weapon == SMG || botInfo->weapInfo.weapon == PISTOL || botInfo->weapInfo.weapon == SHOTGUN || botInfo->weapInfo.weapon == SCOPED_SMG || botInfo->weapInfo.weapon == SNIPERRIFLE || botInfo->weapInfo.weapon == HEAVY_MG || useLockon ) {
botUcmd->botCmds.altAttackOn = true;
}
}
}
if ( timeOnTarget > botWorld->gameLocalInfo.time && hammerTime == false ) { //mal: dont fire until we get the enemy in our "sights".
return;
}
bool shotIsBlocked = false;
bool botWantsToAttack = false;
if ( botWorld->gameLocalInfo.botAimSkill == 0 && botInfo->weapInfo.weapon != ROCKET && botInfo->weapInfo.weapon != HEAVY_MG && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) { //mal: low skill bots won't shoot that much
if ( botThreadData.random.RandomInt( 100 ) > 10 ) {
return;
}
}
if ( gunTargetEntNum != -1 ) {
if ( EntityIsClient( gunTargetEntNum, true ) || botWorld->gameLocalInfo.inWarmup ) { //mal: always unload on enemies, even if not OUR enemy. In warmup - shoot everyone.
botWantsToAttack = true;
} else if ( EntityIsClient( gunTargetEntNum, false ) ) { //mal: never shoot a teammate
shotIsBlocked = true;
} else if ( EntityIsVehicle( gunTargetEntNum, true, true ) || botWorld->gameLocalInfo.inWarmup ) { //mal: enemy vehicles are OK to shoot, as long as theres someone in them. In warmup, will shoot any vehicle.
if ( useLockon ) {
botUcmd->botCmds.altAttackOn = true;
if ( botInfo->targetLockEntNum != -1 && botInfo->targetLocked != false ) {
botWantsToAttack = true;
}
} else {
botWantsToAttack = true;
}
} else if ( EntityIsVehicle( gunTargetEntNum, false, false ) ) { //mal: never shoot friendly vehicles.
shotIsBlocked = true;
} else if ( EntityIsDeployable( gunTargetEntNum, false ) ) { //mal: deployable in the way.
shotIsBlocked = true;
} else if ( gunTargetEntNum == ENTITYNUM_WORLD ) {
shotIsBlocked = true;
} else {
botWantsToAttack = true;
}
}
if ( !shotIsBlocked ) {
shotIsBlockedCounter = 0;
} else {
shotIsBlockedCounter++;
}
if ( !botWantsToAttack ) {
return;
}
if ( testFireShot ) {
if ( botInfo->weapInfo.weapon == SMG ) {
timeOnTarget = botWorld->gameLocalInfo.time + 1500; //mal: we want to "test" if our target returns a "ping", so shoot, wait a sec, then blast them!
}
testFireShot = false;
} //mal: no point if we dont have a SMG type weapon.
if ( botInfo->weapInfo.weapon == KNIFE ) {
if ( enemyInfo.enemyDist < 125.0f ) {
botUcmd->botCmds.attack = true;
botUcmd->botCmds.constantFire = true;
}
return;
}
if ( ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) && botInfo->weapInfo.hasNadeAmmo ) {
bool quickNade = false;
if ( enemyInfo.enemyFacingBot ) {
quickNade = true;
}
Bot_ThrowGrenade( botWorld->clientInfo[ enemy ].origin, quickNade );
return;
}
if ( botInfo->weapInfo.weapon == BINOCS ) {
botUcmd->botCmds.attack = true;
botUcmd->botCmds.constantFire = true;
return;
}
if ( botInfo->weapInfo.weapon == AIRCAN ) {
if ( ClassWeaponCharged( AIRCAN ) ) {
Bot_UseCannister( AIRCAN, botWorld->clientInfo[ enemy ].origin );
return;
} else {
weapSwitchTime = 0;
useAircanOnEnemy = false;
return;
}
}
botUcmd->botCmds.attack = true;
if ( botInfo->weapInfo.weapon == PISTOL && botInfo->team == STROGG && botInfo->classType == SOLDIER ) {
botUcmd->botCmds.constantFire = true;
return;
}
if ( botInfo->weapInfo.weapon == SNIPERRIFLE && botInfo->weapInfo.isFiringWeap ) {
timeOnTarget = botWorld->gameLocalInfo.time + 900; //mal: wait for nearly a second before fire again, so aim isn't all wonky.
}
}
/*
================
idBotAI::CheckClientAttacker
check if clientNum has been attacked recently.
Returns the client who did the attacking.
================
*/
int idBotAI::CheckClientAttacker( int clientNum, int checkTimeInSeconds ) {
int attacker = -1;
if ( ( botWorld->clientInfo[ clientNum ].lastAttackerTime + ( checkTimeInSeconds * 1000 ) ) > botWorld->gameLocalInfo.time ) {
attacker = botWorld->clientInfo[ clientNum ].lastAttacker;
if ( ClientIsDead( attacker ) ) {
return -1;
}
if ( ClientIsIgnored( attacker ) ) {
return -1;
}
if ( botWorld->clientInfo[ attacker ].team == botWorld->clientInfo[ clientNum ].team ) {
return -1;
}
idVec3 vec = botWorld->clientInfo[ attacker ].origin - botInfo->origin;
if ( vec.LengthSqr() > Square( botWorld->botGoalInfo.botSightDist ) ) {
return -1;
}
}
return attacker;
}
/*
================
idBotAI::Bot_CanBackStabClient
================
*/
bool idBotAI::Bot_CanBackStabClient( int clientNum, float backStabDist ) {
int travelFlags = ( botInfo->team == GDF ) ? TFL_VALID_GDF : TFL_VALID_STROGG;
aasTraceFloor_t trace;
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
idVec3 end = player.viewOrigin; //player.origin;
end += ( -backStabDist * player.viewAxis[ 0 ] );
travelFlags &= ~TFL_WALKOFFLEDGE;
botAAS.aas->TraceFloor( trace, player.viewOrigin /*player.origin*/, player.areaNum, end, travelFlags );
if ( botThreadData.AllowDebugData() ) {
gameRenderWorld->DebugLine( colorRed, player.viewOrigin, end );
}
if ( trace.fraction >= 1.0f ) {
botBackStabMoveGoal = end;
return true;
}
return false;
}
/*
================
idBotAI::ClientHasAttackedMate
Has this client attacked any of our teammates recently?
Can be used to spot coverts who run around knifing teammates or anyone who might be causing trouble.
Can be filtered to only check if important mates were attacked.
================
*/
bool idBotAI::ClientHasAttackedTeammate( int clientNum, bool criticalOnly, int time ) {
int i;
for( i = 0; i < MAX_CLIENTS; i++ ) {
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( playerInfo.team != botInfo->team ) {
continue;
}
if ( criticalOnly != false ) {
if ( botInfo->team == GDF ) {
if ( playerInfo.classType == botWorld->botGoalInfo.team_GDF_criticalClass && playerInfo.lastAttackerTime + time > botWorld->gameLocalInfo.time && playerInfo.lastAttacker == clientNum ) {
return true;
}
} else {
if ( playerInfo.classType == botWorld->botGoalInfo.team_STROGG_criticalClass && playerInfo.lastAttackerTime + time > botWorld->gameLocalInfo.time && playerInfo.lastAttacker == clientNum ) {
return true;
}
}
} else {
if ( playerInfo.lastAttackerTime + time > botWorld->gameLocalInfo.time && playerInfo.lastAttacker == clientNum ) {
return true;
}
}
}
return false;
}
/*
================
idBotAI::DisguisedKillerInArea
Is there a disguised killer ( covert ops ) in the area thats been killing some of our teammates?
If so, the bot should be more aware of its surroundings, and not skip any part of its
enemy checks.
================
*/
bool idBotAI::DisguisedKillerInArea() {
int i;
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.isDisguised ) {
continue;
}
if ( playerInfo.team == botInfo->team ) {
continue;
}
if ( ClientHasAttackedTeammate( i , false, 3000 ) ) {
return true;
}
}
return false;
}
/*
================
idBotAI::ClientIsDefusingOurTeamCharge
Is this client trying to defuse our team's charge? In which case, he should become a priority target!
================
*/
bool idBotAI::ClientIsDefusingOurTeamCharge( int clientNum ) {
int botActionNum;
const clientInfo_t& playerInfo = botWorld->clientInfo[ clientNum ];
idVec3 vec;
if ( playerInfo.classType != ENGINEER ) {
return false;
}
if ( botInfo->team == GDF ) {
botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction;
if ( botActionNum == -1 ) {
return false;
}
if ( botThreadData.botActions[ botActionNum ]->GetHumanObj() == ACTION_HE_CHARGE ) {
if ( botThreadData.botActions[ botActionNum ]->ArmedChargesInsideActionBBox( -1 ) ) {
vec = botThreadData.botActions[ botActionNum ]->GetActionOrigin() - playerInfo.origin;
if ( vec.LengthSqr() < Square( 1500.0f ) ) {
return true;
}
}
}
} else {
botActionNum = botWorld->botGoalInfo.team_STROGG_PrimaryAction;
if ( botActionNum == -1 ) {
return false;
}
if ( botThreadData.botActions[ botActionNum ]->GetStroggObj() == ACTION_HE_CHARGE ) {
if ( botThreadData.botActions[ botActionNum ]->ArmedChargesInsideActionBBox( -1 ) ) {
vec = botThreadData.botActions[ botActionNum ]->GetActionOrigin() - playerInfo.origin;
if ( vec.LengthSqr() < Square( 1500.0f ) ) {
return true;
}
}
}
}
return false;
}
/*
================
idBotAI::Bot_PickChaseType
Choose how the bot will chase the enemy.
If the enemy is making a lot of noise, use that info to chase the enemy, ONLY if the bot is highskilled.
================
*/
void idBotAI::Bot_PickChaseType() {
bool isAudible;
idVec3 vec;
vec = enemyInfo.enemy_LS_Pos - botInfo->origin;
isAudible = ClientIsAudibleToBot( enemy );
if ( vec.LengthFast() < ENEMY_CHASE_DIST && ( ( isAudible && botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) || ( botThreadData.random.RandomInt( 100 ) > 40 && botThreadData.GetBotSkill() == BOT_SKILL_EXPERT ) ) ) {
COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_ChaseUnseenEnemy;
} else {
COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_ChaseEnemy;
}
}
/*
================
idBotAI::Bot_CheckIfShouldInvestigateNoise
Checks to see if a bot should go investigate a client making some noise. Wont do this if busy doing something
more important!
================
*/
int idBotAI::Bot_ShouldInvestigateNoise( int clientNum ) {
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
return 0;
} //mal: dumb bots dont worry about this kind of stuff!
if ( ClientIsIgnored( clientNum ) ) {
return 0;
}
if ( aiState == LTG && ltgType == HUNT_GOAL ) { //mal: already doing something about this!
return 0;
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ clientNum ];
int travelTime;
if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) { //mal: if we can't reach you, can't really investigate you.
return 0;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !botWorld->botGoalInfo.gameIsOnFinalObjective && !playerInfo.isBot ) { //mal: dont worry about noises made by human players in training mode, unless its the final obj...
return 0;
}
if ( botInfo->isActor ) { //mal: dont ever leave the player!
return 0;
}
if ( ClientIsDefusingOurTeamCharge( clientNum ) ) {
return 3;
}
if ( aiState == LTG && ( ltgType != ROAM_GOAL && ltgType != CAMP_GOAL && ltgType != INVESTIGATE_ACTION ) ) {
return 0;
}
if ( aiState == NBG && ( nbgType != CAMP && nbgType != DESTROY_DANGER && nbgType != SNIPE && nbgType != DEFENSE_CAMP ) ) {
return 0;
}
if ( ClientIsMarkedForDeath( clientNum, true ) ) {
if ( Bot_LTGIsAvailable( clientNum, ACTION_NULL, HUNT_GOAL, 1 ) ) {
Bot_AddDelayedChat( botNum, ACKNOWLEDGE_YES, 2 );
return 3;
}
}
if ( stayInPosition == true ) { //mal: we've decided to hold our ground ( low skills bots do this always, high skill sometimes to be unpredictable ), so leave.
return 0;
}
if ( ClientHasObj( clientNum ) ) {
return 3;
}
if ( Client_IsCriticalForCurrentObj( clientNum, 1500.0f ) ) {
return 3;
}
if ( ClientIsDangerous( clientNum ) ) { //mal: we're not gonna sit back and let someone camp us!
return 3;
}
if ( playerInfo.lastAttackClient > -1 && playerInfo.lastAttackClient < MAX_CLIENTS && playerInfo.lastAttackClientTime + 5000 > botWorld->gameLocalInfo.time ) {
if ( Client_IsCriticalForCurrentObj( playerInfo.lastAttackClient, -1.0f ) ) {
return 2;
}
}
return 1;
}
/*
================
idBotAI::Bot_SetTimeOnTarget
Sets how long the bot should delay before he fires his weapon at the enemy.
================
*/
void idBotAI::Bot_SetTimeOnTarget( bool inVehicle ) {
bool inFront = InFrontOfClient( botNum, botWorld->clientInfo[ enemy ].origin );
if ( inVehicle ) {
if ( botVehicleInfo->driverEntNum == botNum ) {
timeOnTarget = botWorld->gameLocalInfo.time + 1500;
} else {
timeOnTarget = botWorld->gameLocalInfo.time + 150;
}
} else {
if ( botInfo->weapInfo.primaryWeapon == SNIPERRIFLE && !botInfo->weapInfo.isScopeUp ) { //mal: give us time to get scope up.
timeOnTarget = botWorld->gameLocalInfo.time + 1500;
return;
}
if ( botWorld->gameLocalInfo.botAimSkill >= 3 ) {
timeOnTarget = 50;
return;
}
if ( !inFront ) {
if ( botWorld->gameLocalInfo.botAimSkill == 0 ) {
timeOnTarget = botWorld->gameLocalInfo.time + 950;
} else {
timeOnTarget = botWorld->gameLocalInfo.time + 250;
}
} else {
if ( botWorld->gameLocalInfo.botAimSkill == 0 ) {
timeOnTarget = botWorld->gameLocalInfo.time + 550;
} else {
timeOnTarget = botWorld->gameLocalInfo.time + 150;
}
}
}
}
/*
================
idBotAI::Client_HasMultipleAttackers
================
*/
bool idBotAI::Client_HasMultipleAttackers( int clientNum ) {
int n = 0;
const clientInfo_t& client = botWorld->clientInfo[ clientNum ];
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if ( i == clientNum ) {
continue;
}
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
if ( player.inGame == false || player.team == NOTEAM || player.team != client.team ) {
continue;
}
if ( player.lastAttackClient != clientNum || player.lastAttackClientTime + 5000 < botWorld->gameLocalInfo.time ) {
continue;
}
n++;
}
if ( n > 1 ) {
return true;
}
return false;
}
/*
================
idBotAI::Bot_IsNearTeamGoalUnderAttack
In the bots travels, he may move near a team goal thats currently under attack. Its possible that when the event of the goal being under attack was first sent,
the bot was too far away, or too busy to answer it. If so, he should stop doing whatever hes doing, and move to that goal to defend it.
================
*/
bool idBotAI::Bot_IsNearTeamGoalUnderAttack() {
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: only the smartest bots will do this.
return false;
}
if ( Client_IsCriticalForCurrentObj( botNum, -1 ) && botWorld->botGoalInfo.attackingTeam == botInfo->team ) {
return false;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !botWorld->botGoalInfo.gameIsOnFinalObjective ) { //mal: dont worry about the obj if we're in training mode, unless its the final one...
return false;
}
if ( aiState == LTG && ( ltgType == DEFUSE_GOAL || ltgType == PLANT_GOAL || ltgType == BUILD_GOAL || ltgType == HACK_GOAL || ltgType == FOLLOW_TEAMMATE_BY_REQUEST || ltgType == HUNT_GOAL ) ) {
return false;
}
if ( aiState == NBG && ( nbgType == BUILD || nbgType == HACK || nbgType == DEFUSE_BOMB || nbgType == PLANT_BOMB || nbgType == INVESTIGATE_CAMP || nbgType == GRAB_SUPPLIES || nbgType == REVIVE_TEAMMATE || nbgType == TK_REVIVE_TEAMMATE || nbgType == SUPPLY_TEAMMATE ) ) {
return false;
}
if ( ClientHasObj( botNum ) ) {
return false;
}
if ( botInfo->isActor ) {
return false;
}
if ( botVehicleInfo != NULL ) { //mal: if we're in a vehicle, ignore this goal.
return false;
}
#ifdef STROGG_INSTANT_REVIVE
if ( aiState == NBG && ( nbgType == REVIVE_TEAMMATE || nbgType == TK_REVIVE_TEAMMATE || nbgType == SUPPLY_TEAMMATE ) ) { //mal: medics need a chance to finish their job!
return false;
}
if ( botInfo->classType == MEDIC && Bot_HasTeamWoundedInArea( true ) ) {
return false;
}
#else
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 ( botInfo->classType == MEDIC && botInfo->team == GDF && Bot_HasTeamWoundedInArea( true ) ) { //mal: strogg medics take too long to revive to make this worthwhile
return false;
}
#endif
if ( botInfo->classType == ENGINEER && nextObjChargeCheckTime < botWorld->gameLocalInfo.time && Bot_CheckChargeExistsOnObjInWorld() ) {
nextObjChargeCheckTime = botWorld->gameLocalInfo.time + 5000;
Bot_ResetState( false, true );
return true;
}
if ( aiState == LTG && ltgType == PROTECT_CHARGE ) { //mal: if we're guarding, only heal those close to our goal.
if ( botInfo->classType == MEDIC && Bot_HasTeamWoundedInArea( false, MEDIC_RANGE_BUSY ) ) {
return false;
}
}
if ( aiState == LTG && ( ltgType == INVESTIGATE_ACTION || ltgType == PROTECT_CHARGE ) ) { //mal: already doing something about this, so keep doing it.
return true;
} //mal: this needs to be checked last.
if ( botInfo->team == botWorld->botGoalInfo.attackingTeam && ignorePlantedChargeTime < botWorld->gameLocalInfo.time ) { //mal: if we're on the attacking team, check our charge.
if ( Bot_LTGIsAvailable( -1, -1, PROTECT_CHARGE, MAX_NUM_DEFEND_CHARGE_CLIENTS ) ) {
for( int i = 0; i < MAX_CHARGES; i++ ) {
const plantedChargeInfo_t& charge = botWorld->chargeInfo[ i ];
if ( charge.entNum == 0 ) {
continue;
}
if ( charge.team != botInfo->team ) {
continue;
}
if ( !charge.isOnObjective ) {
continue;
}
if ( charge.areaNum == 0 ) {
continue;
}
if ( charge.state != BOMB_ARMED ) {
continue;
}
idVec3 vec = charge.origin - botInfo->origin;
float awareOfChargeDist = 2500.0f;
if ( vec.LengthSqr() > Square( awareOfChargeDist ) ) {
continue;
}
int matesInArea = ClientsInArea( botNum, charge.origin, 300.0f, botInfo->team, NOCLASS, false, false, false, false, true );
if ( matesInArea > MAX_NUM_DEFEND_CHARGE_CLIENTS ) {
continue;
} //mal: already some ppl there, so lets do something else.
int enemyEngineersInArea = ClientsInArea( botNum, charge.origin, awareOfChargeDist, ( botInfo->team == GDF ) ? STROGG : GDF, ENGINEER, false, false, false, false, true );
if ( enemyEngineersInArea == 0 ) {
continue;
} //mal: noone there to defuse our charge, so why worry about it?
Bot_ResetState( true, true );
aiState = LTG;
ltgType = PROTECT_CHARGE;
ltgTarget = charge.entNum;
ltgTargetSpawnID = charge.spawnID;
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_ProtectCharge;
return true;
}
}
} else {
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_DEFUSE && botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_PREVENT_BUILD &&
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_PREVENT_HACK && botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_HE_CHARGE &&
botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_PREVENT_STEAL && botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_PREVENT_DELIVER ) {
continue;
}
if ( !botThreadData.botActions[ i ]->ActionIsPriority() ) {
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_HE_CHARGE || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DEFUSE && botInfo->classType != ENGINEER ) {
continue;
}
}
if ( ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_HE_CHARGE || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DEFUSE ) && !botThreadData.botActions[ i ]->ArmedChargesInsideActionBBox( -1 ) ) {
continue;
}
int enemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), BOT_INVESTIGATE_RANGE, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_PREVENT_BUILD && ( botThreadData.botActions[ i ]->GetActionState() == ACTION_STATE_NORMAL && enemiesInArea == 0 ) ) {
continue;
}
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_PREVENT_HACK && ( botThreadData.botActions[ i ]->GetActionState() == ACTION_STATE_NORMAL && enemiesInArea == 0 ) ) {
continue;
}
if ( ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_PREVENT_STEAL || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_PREVENT_DELIVER ) && enemiesInArea == 0 ) {
continue;
}
if ( lastCheckActionTime > botWorld->gameLocalInfo.time ) { //mal: don't repeat ourselves, unless there is a critical enemy nearby
if ( botThreadData.GetBotSkill() != BOT_SKILL_EXPERT ) { //mal: only the smartest bots will do this.
continue;
}
int criticalEnemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), CRITICAL_ENEMY_CLOSE_TO_GOAL_RANGE, ( botInfo->team == GDF ) ? STROGG : GDF, TeamCriticalClass( ( botInfo->team == GDF ) ? STROGG : GDF ), false, false, false, false, false, true );
if ( criticalEnemiesInArea == 0 ) {
continue;
}
}
int matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, botInfo->team, NOCLASS, false, false, false, false, true );
if ( matesInArea > MIN_NUM_INVESTIGATE_CLIENTS ) {
continue;
} //mal: already some ppl there, so lets do something else.
idVec3 vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin;
if ( vec.LengthSqr() > Square( BOT_INVESTIGATE_RANGE ) ) { //mal: its too far away, we'll never make it in time!
continue;
}
Bot_ResetState( true, true );
actionNum = i;
ltgTime = botWorld->gameLocalInfo.time + 30000;
lastCheckActionTime = botWorld->gameLocalInfo.time + 20000; //mal: dont do this again for a while.
aiState = LTG;
ltgType = INVESTIGATE_ACTION;
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_InvestigateGoal;
return true;
}
}
return false;
}
/*
================
idBotAI::DisguisedClientIsActingOdd
Disguised clients only have access to the knife/gun of the disguised client they impersonate. Most clients won't ever run around with a knife in hand, so if a client
is walking around with a knife, acting goofy, a high skill bot may "spot" them and attack. Also, look around and see if the disguised client he impersonates is nearby.
================
*/
bool idBotAI::DisguisedClientIsActingOdd( int clientNum ) {
if ( botWorld->gameLocalInfo.botSkill < BOT_SKILL_EXPERT ) {
return false;
}
if ( ignoreSpyTime > botWorld->gameLocalInfo.time ) {
return false;
}
if ( Bot_IsBusy() ) {
return false;
}
ignoreSpyTime = botWorld->gameLocalInfo.time + 1500;
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
int attackingClient;
if ( Bot_TeammateHasClientAsEnemy( clientNum, attackingClient ) ) { //mal: if bot teammate has covert as enemy - the covert becomes our enemy too.
return true;
}
idVec3 vec = player.origin - botInfo->origin;
if ( vec.LengthSqr() > Square( COVERT_SIGHT_DIST ) ) {
return false;
}
if ( player.isPanting && !botInfo->isPanting && botInfo->xySpeed < WALKING_SPEED ) {
return true;
} //mal: high skill bot will "hear" you making your teams "heavy panting" noise, and will hunt you down.
if ( !InFrontOfClient( botNum, player.origin ) ) {
return false;
}
float dist = vec.LengthSqr();
const clientInfo_t& mate = botWorld->clientInfo[ player.disguisedClient ];
vec = mate.origin - botInfo->origin;
if ( vec.LengthSqr() < Square( COVERT_SIGHT_DIST ) ) {
if ( InFrontOfClient( botNum, mate.origin ) ) {
testFireShot = true;
return true;
}
} //mal: dirty spy - I see you!!
if ( botInfo->xySpeed >= RUNNING_SPEED ) { //mal: when we're on the move, kinda hard to do this.
return false;
}
int randChance = 90;
if ( player.covertWarningTime + 5000 > botWorld->gameLocalInfo.time ) {
randChance = 70;
}
if ( player.weapInfo.weapon == KNIFE ) { //mal: kinda odd to see someone running around with a knife out. Hmmmm......
if ( botThreadData.random.RandomInt( 100 ) > randChance ) {
testFireShot = true;
return true;
}
}
return false;
}
/*
================
idBotAI::ClientIsDangerous
Target campers, and clients who are on a killing spree.
================
*/
bool idBotAI::ClientIsDangerous( int clientNum ) {
if ( !ClientIsValid( clientNum, -1 ) ) {
return false;
}
const clientInfo_t& player = botWorld->clientInfo[ clientNum ];
if ( player.isBot ) {
return false;
}
if ( player.killsSinceSpawn < KILLING_SPREE && !player.isCamper ) {
return false;
}
return true;
}
/*
================
idBotAI::ClientIsMarkedForDeath
Checks to see if this client, or the vehicle hes in, has been marked for death.
================
*/
bool idBotAI::ClientIsMarkedForDeath( int clientNum, bool clearRequest ) {
if ( !ClientIsValid( clientNum, -1 ) ) {
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.killTargetNum == -1 ) {
continue;
}
if ( player.killTargetUpdateTime + MAX_TARGET_TIME < botWorld->gameLocalInfo.time ) {
continue;
}
if ( player.killTargetNum < MAX_CLIENTS && player.killTargetNum != clientNum ) {
continue;
}
entityTypes_t entityType = FindEntityType( player.killTargetNum, player.killTargetSpawnID );
if ( entityType == ENTITY_NULL || entityType == ENTITY_DEPLOYABLE ) {
continue;
}
if ( entityType == ENTITY_VEHICLE && botWorld->clientInfo[ clientNum ].proxyInfo.entNum != player.killTargetNum ) {
continue;
}
if ( clearRequest ) {
botUcmd->ackKillForClient = i;
}
isMarked = true;
break;
}
return isMarked;
}
/*
================
idBotAI::DeployableIsMarkedForDeath
Checks to see if this deployable has been marked for death.
================
*/
bool idBotAI::DeployableIsMarkedForDeath( 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.killTargetNum == -1 ) {
continue;
}
if ( player.killTargetUpdateTime + MAX_TARGET_TIME < botWorld->gameLocalInfo.time ) {
continue;
}
if ( player.killTargetNum < MAX_CLIENTS ) {
continue;
}
entityTypes_t entityType = FindEntityType( player.killTargetNum, player.killTargetSpawnID );
if ( entityType == ENTITY_NULL || entityType == ENTITY_VEHICLE || entityType == ENTITY_PLAYER ) {
continue;
}
if ( deployable.entNum != player.killTargetNum ) {
continue;
}
if ( clearRequest ) {
botUcmd->ackKillForClient = i;
}
isMarked = true;
break;
}
return isMarked;
}
/*
================
idBotAI::Flyer_FindEnemy
A find enemy that only works for the flyer hive.
================
*/
int idBotAI::Flyer_FindEnemy( float range) {
int enemyNum = -1;
float closest = idMath::INFINITY;
for ( int i = 0; i < MAX_CLIENTS; i++ ) {
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
if ( i == botNum ) {
continue; //mal: dont try to fight ourselves!
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( playerInfo.inLimbo ) {
continue;
}
if ( playerInfo.isDisguised ) {
continue;
}
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !playerInfo.isBot ) { //mal: dont harass humans in training mode.
continue;
}
if ( playerInfo.isNoTarget ) {
continue;
}
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
continue;
}
if ( playerInfo.invulnerableEndTime > botWorld->gameLocalInfo.time ) {
continue; //mal: ignore revived/just spawned in clients - get the ppl around them!
}
if ( playerInfo.health <= 0 ) {
continue;
}
if ( playerInfo.team == botInfo->team ) {
continue;
}
idVec3 vec = playerInfo.viewOrigin - botInfo->weapInfo.covertToolInfo.origin;
float distSqr = vec.LengthSqr();
if ( distSqr > Square( range ) ) {
continue;
}
bool clientHasLotsOfFriendsInArea = ( ClientsInArea( botNum, playerInfo.origin, 700.0f, GDF, NOCLASS, false, false, false, true, true ) > 0 ) ? true : false;
bool clientIsDangerous = ClientIsDangerous( i );
if ( clientHasLotsOfFriendsInArea ) { //mal: juicy target!
distSqr = 500.0f;
} else if ( Client_IsCriticalForCurrentObj( i, 3000.0f ) ) { //mal: juicier target!
distSqr = 400.0f;
} else if ( clientIsDangerous ) { //mal: juiciest target!
distSqr = 100.0f;
}
if ( distSqr < closest ) {
enemyNum = i;
closest = distSqr;
}
}
return enemyNum;
}
/*
============
idBotAI::Bot_CanAttackVehicles
============
*/
bool idBotAI::Bot_CanAttackVehicles() {
//mal_TODO: add a check for engs who have the grenade launcher.
if ( botVehicleInfo == NULL ) {
if ( ( botInfo->classType != SOLDIER || botInfo->weapInfo.primaryWeapon != ROCKET ) && ( botInfo->classType != FIELDOPS || !Bot_HasWorkingDeployable( false, ROCKET_ARTILLERY ) ) ) {
return false;
}
} else {
if ( Bot_IsInHeavyAttackVehicle() ) {
return false;
}
}
return true;
}
/*
============
idBotAI::Bot_HasEnemySniperInArea
============
*/
bool idBotAI::Bot_HasEnemySniperInArea( float range ) {
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) {
return false;
}
bool hasSniper = false;
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.team == botInfo->team || player.health <= 0 ) {
continue;
}
if ( player.weapInfo.primaryWeapon != SNIPERRIFLE || !player.weapInfo.primaryWeapHasAmmo ) {
continue;
}
if ( !player.isCamper && player.killsSinceSpawn < KILLING_SPREE ) {
continue;
}
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
continue;
}
idVec3 vec = player.origin - botInfo->origin;
if ( vec.LengthSqr() > Square( range ) ) {
continue;
}
idVec3 dangerOrg = player.viewOrigin;
dangerOrg += ( vec.LengthFast() * player.viewAxis[ 0 ] );
vec = dangerOrg - botInfo->origin;
if ( vec.LengthSqr() > Square( MAX_SNIPER_CROSSHAIR_DANGER_DIST ) ) {
continue;
}
hasSniper = true;
break;
}
return hasSniper;
}
/*
============
idBotAI::Bot_IsUnderAttackByAPT
============
*/
bool idBotAI::Bot_IsUnderAttackByAPT( idVec3& turretLocation ) {
bool isUnderAttack = false;
for( int i = 0; i < MAX_DEPLOYABLES; i++ ) {
const deployableInfo_t& deployable = botWorld->deployableInfo[ i ];
if ( deployable.entNum == 0 ) {
continue;
}
if ( deployable.health <= 0 ) {
continue;
}
if ( !deployable.inPlace ) {
continue;
}
if ( deployable.ownerClientNum == -1 ) {
continue;
}
if ( deployable.type != APT ) {
continue;
}
if ( deployable.enemyEntNum != botNum ) {
continue;
}
turretLocation = deployable.origin;
turretLocation.z += DEPLOYABLE_PATH_ORIGIN_OFFSET;
isUnderAttack = true;
break;
}
return isUnderAttack;
}
/*
============
idBotAI::Bot_TeammateHasClientAsEnemy
============
*/
bool idBotAI::Bot_TeammateHasClientAsEnemy( int clientNum, int& attackingClient ) {
bool isTargeted = false;
attackingClient = -1;
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 == false ) {
continue;
}
if ( player.team != botInfo->team ) {
continue;
}
if ( player.health <= 0 ) {
continue;
}
if ( botThreadData.bots[ i ] == NULL ) {
continue;
}
if ( botThreadData.bots[ i ]->GetEnemyNum() != clientNum ) {
continue;
}
isTargeted = true;
attackingClient = i;
break;
}
return isTargeted;
}
/*
================
idBotAI::ClientHasAttackedActorsMate
================
*/
bool idBotAI::ClientHasAttackedActorsMate( int clientNum, int time ) {
if ( !ClientIsValid( botThreadData.actorMissionInfo.targetClientNum, -1 ) ) {
return false;
}
if ( !ClientIsValid( clientNum, -1 ) ) {
return false;
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ clientNum ];
if ( playerInfo.lastAttackerTime + time > botWorld->gameLocalInfo.time && playerInfo.lastAttacker == botThreadData.actorMissionInfo.targetClientNum ) {
return true;
}
return false;
}
/*
============
idBotAI::Bot_ShouldIgnoreEnemies
The latest versions of ETQW make it worthwhile to make suicide runs on objs. Update the bots to let them do so as well.
============
*/
bool idBotAI::Bot_ShouldIgnoreEnemies() {
if ( ClientHasObj( botNum ) ) {
if ( botWorld->botGoalInfo.deliverActionNumber > -1 && botWorld->botGoalInfo.deliverActionNumber < botThreadData.botActions.Num() ) {
idVec3 vec = botThreadData.botActions[ botWorld->botGoalInfo.deliverActionNumber ]->GetActionOrigin() - botInfo->origin;
if ( vec.LengthSqr() < Square( 1500.0f ) ) {
return true;
}
}
}
if ( Client_IsCriticalForCurrentObj( botNum, 900.0f ) && botInfo->classType != SOLDIER ) {
return true;
}
switch ( botInfo->classType ) {
case MEDIC: {
if ( aiState == NBG && nbgType == REVIVE_TEAMMATE ) {
return true;
}
break;
}
case ENGINEER: {
if ( aiState == NBG && ( nbgType == BUILD || nbgType == DEFUSE_BOMB || nbgType == FIXING_MCP ) ) {
return true;
}
break;
}
case COVERTOPS: {
if ( aiState == NBG && nbgType == HACK ) {
return true;
}
break;
}
case SOLDIER: {
if ( aiState == NBG && nbgType == PLANT_BOMB && !ClientHasChargeInWorld( botNum, true, ACTION_NULL ) ) {
return true;
}
break;
}
}
return false;
}
/*
============
idBotAI::Bot_CheckIfEnemyHasUsInTheirSightsWhenInAirVehicle
============
*/
bool idBotAI::Bot_CheckIfEnemyHasUsInTheirSightsWhenInAirVehicle() {
if ( botWorld->gameLocalInfo.botSkill != BOT_SKILL_EXPERT ) {
return false;
}
bool hasEnemy = 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.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
proxyInfo_t enemyVehicle;
GetVehicleInfo( player.proxyInfo.entNum, enemyVehicle );
if ( enemyVehicle.entNum == 0 ) {
continue;
}
if ( enemyVehicle.type != TITAN && enemyVehicle.type != DESECRATOR && enemyVehicle.type != GOLIATH && enemyVehicle.type != TROJAN ) {
continue;
}
if ( enemyVehicle.type == TROJAN && enemyVehicle.driverEntNum == i ) { //mal: dont worry about trojan driver
continue;
}
if ( ( enemyVehicle.type == TITAN || enemyVehicle.type == DESECRATOR ) && enemyVehicle.driverEntNum != i ) { //mal: dont worry about tank gunners
continue;
}
idVec3 vec = player.origin - botInfo->origin;
idVec3 dangerOrg = player.viewOrigin;
dangerOrg += ( vec.LengthFast() * player.viewAxis[ 0 ] );
vec = dangerOrg - botInfo->origin;
if ( vec.LengthSqr() > Square( 500.0f ) ) {
continue;
}
} else {
if ( player.weapInfo.weapon != ROCKET && player.weapInfo.weapon != HEAVY_MG ) {
continue;
}
idVec3 vec = player.origin - botInfo->origin;
float distToEnemy = vec.LengthFast();
#ifdef _XENON
float avoidDist = ( player.weapInfo.weapon == ROCKET ) ? FLYER_WORRY_ABOUT_ROCKETS_MAX_DIST : 3500.0f; //mal: make it a bit easier on the Xbox to take out flyers.
#else
float avoidDist = 3500.0f;
#endif
if ( distToEnemy > avoidDist ) {
continue;
}
idVec3 dangerOrg = player.viewOrigin;
dangerOrg += ( distToEnemy * player.viewAxis[ 0 ] );
vec = dangerOrg - botInfo->origin;
if ( vec.LengthSqr() > Square( 300.0f ) ) {
continue;
}
}
hasEnemy = true;
break;
}
return hasEnemy;
}