2165 lines
66 KiB
C++
2165 lines
66 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_FindBestCombatMovement
|
||
|
|
||
|
Finds the best combat movement for the bot while on foot.
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_FindBestCombatMovement() {
|
||
|
int result, k;
|
||
|
int travelFlags = ( botInfo->team == GDF ) ? TFL_VALID_GDF : TFL_VALID_STROGG;
|
||
|
|
||
|
combatMoveFailedCount = 0;
|
||
|
combatMoveTime = -1;
|
||
|
|
||
|
if ( enemy == -1 ) {
|
||
|
assert( false );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->usingMountedGPMG ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Null_Move_Attack;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( hammerTime ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_DEMO ) { //mal: silly bot! Don't move around too much or be too hard to hit in training mode.
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Stand_Ground_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == KNIFE ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Knife_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Grenade_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( combatDangerExists ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Avoid_Danger_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& enemyClient = botWorld->clientInfo[ enemy ];
|
||
|
|
||
|
if ( botInfo->inWater && !enemyClient.inWater ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == SNIPERRIFLE ) {
|
||
|
if ( enemyInfo.enemyDist > 900.0f ) {
|
||
|
if ( Bot_CanProne( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Prone_Attack_Movement;
|
||
|
return;
|
||
|
} else if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) || !enemyInfo.enemyFacingBot ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( enemyClient.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: dont be a easy target if fighting someone in a vehicle.
|
||
|
if ( enemyInfo.enemyDist < 4000 ) {
|
||
|
result = botThreadData.random.RandomInt( 4 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crazy_Jump_Attack_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 2 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( enemyInfo.enemyDist > 1000.0f ) { //mal: if our enemy is a fair distance away, but we can't reach them, just stand or crouch.
|
||
|
int travelTime;
|
||
|
bool canReach = Bot_LocationIsReachable( false, enemyClient.aasOrigin, travelTime );
|
||
|
|
||
|
if ( !canReach || travelTime > Bot_ApproxTravelTimeToLocation( botInfo->origin, enemyClient.origin, false ) * TRAVEL_TIME_MULTIPLY ) {
|
||
|
if ( Bot_CanCrouch( enemy ) && botInfo->weapInfo.primaryWeapon != ROCKET ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: next, check if we have the height advantage - in which case, we'll keep it! ONLY smarter bots will do this....
|
||
|
if ( botThreadData.GetBotSkill() > BOT_SKILL_EASY && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
|
||
|
if ( enemyInfo.enemyHeight < -150 && enemyInfo.enemyDist > 900.0f ) { //mal: if we have the height advantage, run some special checks!
|
||
|
aasTraceFloor_t trace;
|
||
|
idVec3 enemyOrg = enemyClient.origin;
|
||
|
travelFlags &= ~TFL_WALKOFFLEDGE;
|
||
|
|
||
|
botAAS.aas->TraceFloor( trace, botInfo->aasOrigin, botInfo->areaNum, enemyOrg, travelFlags );
|
||
|
|
||
|
if ( trace.fraction < 1.0f ) {
|
||
|
if ( Bot_CanCrouch( enemy ) && botInfo->weapInfo.primaryWeapon != ROCKET ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
}
|
||
|
return;
|
||
|
} else {
|
||
|
|
||
|
result = botThreadData.random.RandomInt( 3 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
return;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
if ( Bot_CanCrouch( enemy ) && botInfo->weapInfo.primaryWeapon != ROCKET ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement; //mal: if can't crouch, find something better to do....
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.isReloading && ( botInfo->weapInfo.weapon != SNIPERRIFLE && enemyInfo.enemyDist > 900.0f ) ) { //mal: what should the bot do while they're reloading their gun.
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: low skill bots aren't too smart
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
idVec3 shieldOrg;
|
||
|
float distToShieldSqr = Bot_DistSqrToClosestForceShield( shieldOrg ); //mal: GDF and Strogg will use shields on the ground, if noone else is.
|
||
|
|
||
|
if ( distToShieldSqr != -1.0f && distToShieldSqr <= Square( SHIELD_CONSIDER_RANGE ) ) {
|
||
|
int clientsInArea = ClientsInArea( botNum, shieldOrg, 150.0f, NOTEAM, NOCLASS, false, false, false, false, false );
|
||
|
|
||
|
if ( clientsInArea > 0 ) { //mal: shields aren't big enough for too many players
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_MoveTo_Shield_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
result = botThreadData.random.RandomInt( 4 );
|
||
|
|
||
|
//mal: the point here is we want to be constantly moving around and making ourselves a harder target, when we're so vulnerable.
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crazy_Jump_Attack_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 2 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: stupid bot! Just stand there and take your punishment. >:-D
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crazy_Jump_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == HEAVY_MG ) {
|
||
|
if ( enemyInfo.enemyDist > 1000.0f && !Client_HasMultipleAttackers( botNum ) ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if ( !enemyInfo.enemyFacingBot && ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) && !Client_HasMultipleAttackers( botNum ) ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} //mal: if we got the drop on you, just crouch down, or stand, and unload into you.
|
||
|
|
||
|
if ( enemyInfo.enemyDist < 900.0f ) {
|
||
|
k = 4;
|
||
|
} else {
|
||
|
k = 3;
|
||
|
}
|
||
|
|
||
|
result = botThreadData.random.RandomInt( k );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 2 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement; //mal: this will be skipped if enemy too far away.
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == ROCKET ) {
|
||
|
if ( enemyClient.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: always try to use rockets for vehicles
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) && !enemyInfo.enemyFacingBot && !Client_HasMultipleAttackers( botNum ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
if ( enemyInfo.enemyDist < 700.0f ) {
|
||
|
k = 4;
|
||
|
} else {
|
||
|
k = 3;
|
||
|
}
|
||
|
|
||
|
result = botThreadData.random.RandomInt( k );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 2 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement; //mal: this will only be run if dist < 700
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == SHOTGUN ) {
|
||
|
if ( enemyInfo.enemyDist < 700.0f ) {
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) && !enemyInfo.enemyFacingBot && !Client_HasMultipleAttackers( botNum ) ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
result = botThreadData.random.RandomInt( 3 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
result = botThreadData.random.RandomInt( 3 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: if we reach here, bot has a pistol or a smg of some kind
|
||
|
|
||
|
if ( enemyInfo.enemyDist > 2500.0f && !Client_HasMultipleAttackers( botNum ) && !enemyInfo.enemyFacingBot ) {
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 5000 < botWorld->gameLocalInfo.time ) ) { //mal: if we got the drop on someone, just crouch down to shoot.
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
result = botThreadData.random.RandomInt( 3 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
} else if ( result == 1) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( enemyInfo.enemyDist < 1500.0f ) {
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) && !enemyInfo.enemyFacingBot ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
result = botThreadData.random.RandomInt( 4 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crazy_Jump_Attack_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else if ( result == 2 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Circle_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ( botInfo->lastAttacker != enemy || botInfo->lastAttackerTime + 3000 < botWorld->gameLocalInfo.time ) && !enemyInfo.enemyFacingBot && !Client_HasMultipleAttackers( botNum ) ) {
|
||
|
if ( Bot_CanCrouch( enemy ) ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Crouch_Attack_Movement;
|
||
|
return;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Stand_Ground_Attack_Movement;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
result = botThreadData.random.RandomInt( 3 );
|
||
|
|
||
|
if ( result == 0 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Run_And_Gun_Movement;
|
||
|
} else if ( result == 1 ) {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Hal_Strafe_Attack_Movement;
|
||
|
} else {
|
||
|
COMBAT_MOVEMENT_STATE = &idBotAI::Enter_Side_Strafe_Attack_Movement;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_SetupMove
|
||
|
|
||
|
Sets up the bot's path goal
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_SetupMove( const idVec3 &org, int clientNum, int actionNumber, int areaNum ) {
|
||
|
idVec3 goalOrigin;
|
||
|
|
||
|
if ( botAAS.aas == NULL ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
botAAS.triedToMoveThisFrame = true;
|
||
|
|
||
|
BuildObstacleList( false, false );
|
||
|
|
||
|
int travelFlags, walkTravelFlags;
|
||
|
|
||
|
if ( !botInfo->isDisguised ) { //mal: if disguised, we can go anywhere we want.
|
||
|
if ( botInfo->team == GDF ) {
|
||
|
travelFlags = TravelFlagForTeam();
|
||
|
walkTravelFlags = TravelFlagWalkForTeam();
|
||
|
} else {
|
||
|
travelFlags = TravelFlagForTeam();
|
||
|
walkTravelFlags = TravelFlagWalkForTeam();
|
||
|
}
|
||
|
} else {
|
||
|
travelFlags = TFL_VALID_GDF_AND_STROGG;
|
||
|
walkTravelFlags = TFL_VALID_WALK_GDF_AND_STROGG;
|
||
|
}
|
||
|
|
||
|
if ( Bot_ReachedCurrentRouteNode() ) {
|
||
|
Bot_FindNextRouteToGoal();
|
||
|
}
|
||
|
|
||
|
idBounds bbox = botAAS.aas->GetSettings()->boundingBox;
|
||
|
|
||
|
if ( routeNode != NULL && aiState == LTG ) {
|
||
|
areaNum = botAAS.aas->PointReachableAreaNum( routeNode->origin, bbox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() );
|
||
|
goalOrigin = routeNode->origin;
|
||
|
|
||
|
if ( botInfo->areaNum != areaNum ) {
|
||
|
botAAS.aas->PushPointIntoArea( areaNum, goalOrigin );
|
||
|
}
|
||
|
} else {
|
||
|
if ( clientNum != -1 ) {
|
||
|
areaNum = botWorld->clientInfo[ clientNum ].areaNum;
|
||
|
if ( botInfo->areaNum != areaNum ) {
|
||
|
goalOrigin = botWorld->clientInfo[ clientNum ].aasOrigin;
|
||
|
} else {
|
||
|
goalOrigin = botWorld->clientInfo[ clientNum ].origin;
|
||
|
}
|
||
|
} else if ( actionNumber != -1 ) {
|
||
|
areaNum = botThreadData.botActions[ actionNumber ]->areaNum;
|
||
|
goalOrigin = botThreadData.botActions[ actionNumber ]->origin;
|
||
|
|
||
|
if ( botInfo->areaNum != areaNum ) {
|
||
|
botAAS.aas->PushPointIntoArea( areaNum, goalOrigin );
|
||
|
}
|
||
|
} else {
|
||
|
if ( areaNum == 0 ) {
|
||
|
areaNum = botAAS.aas->PointReachableAreaNum( org, bbox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() );
|
||
|
}
|
||
|
|
||
|
goalOrigin = org;
|
||
|
|
||
|
if ( botInfo->areaNum != areaNum ) {
|
||
|
botAAS.aas->PushPointIntoArea( areaNum, goalOrigin );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
idObstacleAvoidance::obstaclePath_t path;
|
||
|
|
||
|
botAAS.hasPath = botAAS.aas->WalkPathToGoal( botAAS.path, botInfo->areaNum, botInfo->aasOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags );
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_showPath.GetInteger() == botNum ) {
|
||
|
botAAS.aas->ShowWalkPath( botInfo->areaNum, botInfo->aasOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags );
|
||
|
gameRenderWorld->DebugCircle( colorGreen, org, idVec3( 0, 0, 1 ), 32, 32 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botInfo->localBounds, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botInfo->aasOrigin, botAAS.path.moveGoal, path );
|
||
|
|
||
|
if ( !botAAS.hasClearPath && routeNode != NULL && aiState == LTG ) { //mal: our route is inside of an obstacle we can't get around, so pick the next route in the path chain.
|
||
|
Bot_FindNextRouteToGoal();
|
||
|
}
|
||
|
|
||
|
botAAS.path.moveGoal = path.seekPos;
|
||
|
|
||
|
if ( path.firstObstacle != -1 && botWorld->gameLocalInfo.botsCanDecayObstacles ) {
|
||
|
proxyInfo_t vehicle;
|
||
|
GetVehicleInfo( path.firstObstacle, vehicle );
|
||
|
|
||
|
if ( vehicle.entNum != 0 && vehicle.type != MCP && vehicle.isEmpty && vehicle.xyspeed <= WALKING_SPEED && !vehicle.neverDriven ) {
|
||
|
int humansInArea = ClientsInArea( botNum, botInfo->origin, 300.0f, vehicle.team, NOCLASS, false, false , false, true, true, true );
|
||
|
|
||
|
if ( humansInArea == 0 && OtherBotsWantVehicle( vehicle ) == false ) {
|
||
|
if ( vehicle.spawnID != vehicleObstacleSpawnID ) {
|
||
|
vehicleObstacleSpawnID = vehicle.spawnID;
|
||
|
vehicleObstacleTime = botWorld->gameLocalInfo.time;
|
||
|
} else if ( vehicleObstacleTime + 5000 < botWorld->gameLocalInfo.time ) {
|
||
|
botUcmd->decayObstacleSpawnID = vehicle.spawnID;
|
||
|
vehicleObstacleSpawnID = -1;
|
||
|
vehicleObstacleTime = -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( path.firstObstacle != -1 ) {
|
||
|
botAAS.path.type = PATHTYPE_WALK;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
if ( botAAS.hasClearPath && path.firstObstacle != -1 ) {
|
||
|
botAAS.path.moveGoal = path.seekPos;
|
||
|
botAAS.path.type = PATHTYPE_WALK;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
botAAS.obstacleNum = path.firstObstacle;
|
||
|
|
||
|
if ( !botWorld->gameLocalInfo.inWarmup && botThreadData.random.RandomInt( 100 ) > 98 ) {
|
||
|
if ( path.firstObstacle > -1 && path.firstObstacle < MAX_CLIENTS ) {
|
||
|
const clientInfo_t& blockingClient = botWorld->clientInfo[ path.firstObstacle ];
|
||
|
if ( blockingClient.team == botInfo->team && !blockingClient.isBot ) {
|
||
|
|
||
|
//mal: ONLY say move if both the bot and the client in question are not just spawning in!
|
||
|
if ( blockingClient.invulnerableEndTime < botWorld->gameLocalInfo.time && botInfo->invulnerableEndTime < botWorld->gameLocalInfo.time ) {
|
||
|
idVec3 tempOrigin = blockingClient.origin - botInfo->origin;
|
||
|
|
||
|
if ( tempOrigin.LengthSqr() < Square( 50.0f ) ) {
|
||
|
botUcmd->desiredChat = MOVE;
|
||
|
botUcmd->botCmds.shoveClient = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: play with the origin just a bit, so that its more at eye level. Gets us realistic view goals on the cheap.
|
||
|
botAAS.path.viewGoal = botAAS.path.moveGoal;
|
||
|
idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin;
|
||
|
if ( idMath::Fabs( viewDelta.z ) < 100.0f ) {
|
||
|
botAAS.path.viewGoal.z = botInfo->viewOrigin.z;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_SetupQuickMove
|
||
|
|
||
|
A fast version of SetupMove, that just has the bot blindly move towards its goal pos, only doing dynamic obstacle avoidance checks.
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_SetupQuickMove( const idVec3 &org, bool largePlayerBBox ) {
|
||
|
idVec3 tempOrigin;
|
||
|
|
||
|
BuildObstacleList( largePlayerBBox, false );
|
||
|
|
||
|
if ( Bot_ReachedCurrentRouteNode() ) {
|
||
|
Bot_FindNextRouteToGoal();
|
||
|
}
|
||
|
|
||
|
idObstacleAvoidance::obstaclePath_t path;
|
||
|
|
||
|
botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botInfo->localBounds, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botInfo->origin, org, path, true );
|
||
|
|
||
|
botAAS.path.moveGoal = path.seekPos;
|
||
|
|
||
|
if ( path.firstObstacle > -1 && path.firstObstacle < MAX_CLIENTS && botThreadData.random.RandomInt( 100 ) > 98 ) {
|
||
|
if ( botWorld->clientInfo[ path.firstObstacle ].team == botInfo->team && !botWorld->clientInfo[ path.firstObstacle ].isBot ) {
|
||
|
tempOrigin = botWorld->clientInfo[ path.firstObstacle ].origin - botInfo->origin;
|
||
|
|
||
|
if ( tempOrigin.LengthSqr() < Square( 50.0f ) ) {
|
||
|
botUcmd->desiredChat = MOVE;
|
||
|
botUcmd->botCmds.shoveClient = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botAAS.obstacleNum = path.firstObstacle;
|
||
|
botAAS.hasPath = path.hasValidPath; //mal: safety check - let the bot know if its blind wanderings just isn't working out.
|
||
|
|
||
|
//mal: play with the origin just a bit, so that its more at eye level. Gets us realistic view goals on the cheap.
|
||
|
botAAS.path.viewGoal = botAAS.path.moveGoal;
|
||
|
idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin;
|
||
|
if ( idMath::Fabs( viewDelta.z ) < 100.0f ) {
|
||
|
botAAS.path.viewGoal.z = botInfo->viewOrigin.z;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_CanMove
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_CanMove( const moveDirections_t direction, float gUnits, bool copyEndPos, bool endPosMustBeOutside ) {
|
||
|
idVec3 end;
|
||
|
|
||
|
end = botInfo->origin;
|
||
|
|
||
|
switch ( direction ) {
|
||
|
|
||
|
case FORWARD:
|
||
|
end += ( gUnits * botInfo->viewAxis[ 0 ] );
|
||
|
break;
|
||
|
|
||
|
case BACK:
|
||
|
end += ( -gUnits * botInfo->viewAxis[ 0 ] );
|
||
|
break;
|
||
|
|
||
|
case LEFT:
|
||
|
end += ( -gUnits * ( botInfo->viewAxis[ 1 ] * -1 ) );
|
||
|
break;
|
||
|
|
||
|
case RIGHT:
|
||
|
end += ( gUnits * ( botInfo->viewAxis[ 1 ] * -1 ) );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
gameRenderWorld->DebugLine(colorGreen, botInfo->origin, end, 16 );
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.Nav_IsDirectPath( AAS_PLAYER, botInfo->team, botInfo->areaNum, botInfo->aasOrigin, end ) ) {
|
||
|
if ( endPosMustBeOutside ) {
|
||
|
if ( !LocationVis2Sky( end ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
botCanMoveGoal = end;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_MoveToGoal
|
||
|
|
||
|
Sets where the bot would like to move.
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_MoveToGoal( const idVec3 &spot1, const idVec3 &spot2, const botMoveFlags_t moveFlag, const botMoveTypes_t moveType ) {
|
||
|
if ( botUcmd->specialMoveType != SKIP_MOVE ) {
|
||
|
botUcmd->moveFlag = moveFlag;
|
||
|
botUcmd->moveType = moveType;
|
||
|
botUcmd->moveGoal = spot1;
|
||
|
botUcmd->moveGoal2 = spot2;
|
||
|
|
||
|
float movementSpeed = SPRINTING_SPEED;
|
||
|
|
||
|
if ( spot1.IsZero() ) {
|
||
|
movementSpeed = 0.0f;
|
||
|
} else if ( moveFlag == WALK || moveFlag == CROUCH || moveFlag == PRONE ) {
|
||
|
movementSpeed = WALKING_SPEED;
|
||
|
} else if ( moveFlag == RUN ) {
|
||
|
movementSpeed = RUNNING_SPEED;
|
||
|
}
|
||
|
|
||
|
if ( botAAS.obstacleNum != -1 && botInfo->xySpeed < movementSpeed ) {
|
||
|
botAAS.blockedByObstacleCounterOnFoot++;
|
||
|
} else {
|
||
|
botAAS.blockedByObstacleCounterOnFoot = 0;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debug.GetBool() ) {
|
||
|
idVec3 test = spot1;
|
||
|
test[ 2 ] += 24;
|
||
|
gameRenderWorld->DebugLine(colorYellow, spot1, test, 16 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_MoveAlongPath
|
||
|
moves the bot along the current botAAS.path, performing any special moves as required
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_MoveAlongPath( botMoveFlags_t defaultMoveFlags ) {
|
||
|
if ( botAAS.hasPath == false ) {
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, defaultMoveFlags, NULLMOVETYPE );
|
||
|
Bot_LookAtNothing( SMOOTH_TURN );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch ( botAAS.path.type ) {
|
||
|
|
||
|
case PATHTYPE_JUMP: {
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, botAAS.path.reachability->GetEnd(), SPRINT, LOCATION_JUMP );
|
||
|
Bot_LookAtNothing( SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PATHTYPE_BARRIERJUMP: {
|
||
|
// look at some arbitrary point 30 units past the end of the reachability, this prevents them from spinning once they jump up on the barrier
|
||
|
idVec2 reachVec = ( botAAS.path.reachability->GetEnd() - botAAS.path.reachability->GetStart() ).ToVec2();
|
||
|
reachVec.Normalize();
|
||
|
reachVec *= 30.0f;
|
||
|
idVec3 lookAtPoint = botAAS.path.reachability->GetEnd();
|
||
|
lookAtPoint.x += reachVec.x;
|
||
|
lookAtPoint.y += reachVec.y;
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, botAAS.path.reachability->GetEnd(), RUN, LOCATION_BARRIERJUMP );
|
||
|
Bot_LookAtLocation( lookAtPoint, SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PATHTYPE_LADDER: {
|
||
|
idVec3 end = botAAS.path.reachability->GetEnd();
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
||
|
Bot_LookAtLocation( end, SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PATHTYPE_WALKOFFBARRIER: {
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, defaultMoveFlags, LOCATION_WALKOFFLEDGE );
|
||
|
Bot_LookAtLocation( botAAS.path.viewGoal, SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PATHTYPE_WALKOFFLEDGE: {
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, defaultMoveFlags, LOCATION_WALKOFFLEDGE );
|
||
|
Bot_LookAtLocation( botAAS.path.viewGoal, SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default: { //mal: check to see if we're under attack from an enemy we can't see - if so, move around a bit to make ourselves harder to hit.
|
||
|
|
||
|
bool safeToJump = false;
|
||
|
botMoveTypes_t defaultMoveType = NULLMOVETYPE;
|
||
|
|
||
|
idVec3 vec = botAAS.path.moveGoal - botInfo->origin;
|
||
|
|
||
|
float moveDist = 300.0f;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( moveDist ) ) {
|
||
|
safeToJump = true;
|
||
|
}
|
||
|
|
||
|
if ( strafeToAvoidDangerTime < botWorld->gameLocalInfo.time && LocationVis2Sky( botInfo->origin ) && safeToJump && ( Bot_IsUnderAttackFromUnknownEnemy() || Bot_HasEnemySniperInArea( MAX_SNIPER_VIEW_DIST ) ) ) {
|
||
|
if ( ClientIsValid( botInfo->lastAttacker, -1 ) ) {
|
||
|
if ( strafeToAvoidDangerTime < botWorld->gameLocalInfo.time ) {
|
||
|
strafeToAvoidDangerTime = botWorld->gameLocalInfo.time + 500;
|
||
|
bool strafeRight = ( botThreadData.random.RandomInt( 100 ) > 50 ) ? true : false;
|
||
|
|
||
|
if ( strafeRight ) {
|
||
|
if ( Bot_CanMove( RIGHT, moveDist, true ) ) {
|
||
|
strafeToAvoidDangerDir = RIGHT;
|
||
|
} else if ( Bot_CanMove( LEFT, moveDist, true ) ) {
|
||
|
strafeToAvoidDangerDir = LEFT;
|
||
|
}
|
||
|
} else {
|
||
|
if ( Bot_CanMove( LEFT, moveDist, true ) ) {
|
||
|
strafeToAvoidDangerDir = LEFT;
|
||
|
} else if ( Bot_CanMove( RIGHT, moveDist, true ) ) {
|
||
|
strafeToAvoidDangerDir = RIGHT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( strafeToAvoidDangerTime > botWorld->gameLocalInfo.time ) {
|
||
|
if ( strafeToAvoidDangerDir == RIGHT ) {
|
||
|
defaultMoveType = RANDOM_JUMP_RIGHT;
|
||
|
} else if ( strafeToAvoidDangerDir == LEFT ) {
|
||
|
defaultMoveType = RANDOM_JUMP_LEFT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, defaultMoveFlags, defaultMoveType );
|
||
|
Bot_LookAtLocation( botAAS.path.viewGoal, SMOOTH_TURN );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botAAS.path.type != PATHTYPE_LADDER && botInfo->onLadder && botInfo->xySpeed < 25.0f ) {
|
||
|
botUcmd->viewType = VIEW_RANDOM;
|
||
|
botUcmd->turnType = INSTANT_TURN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_CanCrouch
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_CanCrouch( int clientNum ) {
|
||
|
trace_t tr;
|
||
|
idVec3 selfView = botInfo->origin;
|
||
|
idVec3 otherView = botWorld->clientInfo[ clientNum ].viewOrigin;
|
||
|
selfView[ 2 ] += botWorld->gameLocalInfo.crouchViewHeight;
|
||
|
|
||
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, selfView, otherView, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
|
||
|
|
||
|
if ( tr.fraction < 1.0f ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_CanProne
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_CanProne( int clientNum ) {
|
||
|
#ifdef _XENON
|
||
|
return false;
|
||
|
#endif
|
||
|
|
||
|
trace_t tr;
|
||
|
idVec3 selfView = botInfo->origin;
|
||
|
idVec3 otherView = botWorld->clientInfo[ clientNum ].viewOrigin;
|
||
|
|
||
|
selfView[ 2 ] += botWorld->gameLocalInfo.proneViewHeight;
|
||
|
|
||
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, selfView, otherView, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
|
||
|
|
||
|
if ( tr.fraction < 1.0f ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_ShouldStrafeJump
|
||
|
|
||
|
Should this bot strafe jump, or just sprint? Will skip checking for a while, if the test ever fails.
|
||
|
ONLY bots doing an long term goal will think about strafe jumping(?).
|
||
|
================
|
||
|
*/
|
||
|
const botMoveFlags_t idBotAI::Bot_ShouldStrafeJump( const idVec3 &targetOrigin ) {
|
||
|
float strafeDist = Square( ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) ? 700.0f : 1900.0f );
|
||
|
idVec3 end;
|
||
|
|
||
|
int delayTime = 0;//1000; //mal: just a second delay by default
|
||
|
|
||
|
if ( skipStrafeJumpTime >= botWorld->gameLocalInfo.time ) {
|
||
|
isStrafeJumping = false;
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
||
|
if ( botWorld->gameLocalInfo.gameIsBotMatch && TeamHasHuman( botInfo->team ) ) { //mal: always sprint in SP mode
|
||
|
return SPRINT;
|
||
|
} else {
|
||
|
return RUN;
|
||
|
}
|
||
|
} else {
|
||
|
return SPRINT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
end = targetOrigin - botInfo->origin; //mal: dont strafejump close to the target: it gives us away, and we need to have gun out, ready to fight!
|
||
|
|
||
|
if ( end.LengthSqr() < strafeDist ) {
|
||
|
isStrafeJumping = false;
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime;
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() > BOT_SKILL_EASY && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) {
|
||
|
return SPRINT;
|
||
|
} else {
|
||
|
return RUN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !botWorld->gameLocalInfo.botsCanStrafeJump ) { //mal: bot cant strafe jump if server admin won't let them
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime; //mal: only for a second, in case admin changes mind.....
|
||
|
isStrafeJumping = false;
|
||
|
|
||
|
if ( botInfo->enemiesInArea == 0 && enemy == -1 && botThreadData.GetBotSkill() > BOT_SKILL_EASY ) {
|
||
|
pistolTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
||
|
if ( botWorld->gameLocalInfo.gameIsBotMatch && TeamHasHuman( botInfo->team ) ) { //mal: always sprint in SP mode
|
||
|
return SPRINT;
|
||
|
}
|
||
|
return RUN;
|
||
|
} else {
|
||
|
return SPRINT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { //mal: low skill bots wont bother to strafe jump, or sprint, just run!
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime; //mal: in case admin changes mind.
|
||
|
isStrafeJumping = false;
|
||
|
pistolTime = 0;
|
||
|
if ( botWorld->gameLocalInfo.gameIsBotMatch && TeamHasHuman( botInfo->team ) ) { //mal: always sprint in SP mode
|
||
|
return SPRINT;
|
||
|
}
|
||
|
return RUN;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.GetBotSkill() < BOT_SKILL_EXPERT ) { //mal: lower skill bots wont bother to strafe jump, just sprint ( but will use knife for extra speed )
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime; //mal: in case admin changes mind.
|
||
|
isStrafeJumping = false;
|
||
|
|
||
|
if ( botInfo->enemiesInArea == 0 && enemy == -1 ) {
|
||
|
pistolTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
return SPRINT;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->xySpeed < SPRINTING_SPEED ) {
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime;
|
||
|
isStrafeJumping = false;
|
||
|
return SPRINT;
|
||
|
}
|
||
|
|
||
|
if ( !LocationVis2Sky( botInfo->origin ) ) { //mal: if indoors, make sure we have enough ceiling room
|
||
|
if ( !LocationHasHeadRoom( botInfo->origin ) ) {
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime;
|
||
|
isStrafeJumping = false;
|
||
|
|
||
|
if ( botInfo->enemiesInArea == 0 && enemy == -1 ) {
|
||
|
pistolTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
return SPRINT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
end = botInfo->viewOrigin;
|
||
|
|
||
|
end += ( 300.0f * botInfo->viewAxis[ 0 ] );
|
||
|
|
||
|
if ( botThreadData.Nav_IsDirectPath( AAS_PLAYER, botInfo->team, botInfo->areaNum, botInfo->aasOrigin, end ) ) {
|
||
|
isStrafeJumping = true;
|
||
|
pistolTime = botWorld->gameLocalInfo.time + 5000;
|
||
|
return STRAFEJUMP;
|
||
|
}
|
||
|
|
||
|
//mal: we're not going to strafe jump...
|
||
|
skipStrafeJumpTime = botWorld->gameLocalInfo.time + delayTime;
|
||
|
isStrafeJumping = false;
|
||
|
|
||
|
if ( botInfo->enemiesInArea == 0 && enemy == -1 ) {
|
||
|
pistolTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
return SPRINT;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::MoveIsInvalid
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::MoveIsInvalid() {
|
||
|
if ( botAAS.hasPath == false ) {
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type > ICARUS ) {
|
||
|
badMoveTime++;
|
||
|
} else if ( botInfo->hasGroundContact || botInfo->inWater ) { //mal: the "hasGroundContact" check was recently added. 8/20/07
|
||
|
badMoveTime++;
|
||
|
}
|
||
|
} else {
|
||
|
badMoveTime = 0;
|
||
|
}
|
||
|
|
||
|
if ( badMoveTime > 30 ) { //mal: WAS 150
|
||
|
badMoveTime = 0; //mal: reset this for next time
|
||
|
|
||
|
moveErrorCounter.moveErrorCount++;
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debug.GetBool() ) {
|
||
|
common->Warning("Move is invalid for bot client %i!", botNum );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_CheckBlockingOtherClients
|
||
|
|
||
|
Gives us a quick and cheap way to find out if the bot is somehow blocking a teammate, and moves them away from us.
|
||
|
This won't be called in AI nodes that have the bot busy doing something.
|
||
|
================
|
||
|
*/
|
||
|
int idBotAI::Bot_CheckBlockingOtherClients( int ignoreClient, float avoidDist ) {
|
||
|
int blockedClient = -1;
|
||
|
|
||
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
||
|
if ( !ClientIsValid( i, -1 ) ) {
|
||
|
continue; //mal: no valid client in this client slot!
|
||
|
}
|
||
|
|
||
|
if ( ClientIsIgnored( i ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( i == botNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( i == ignoreClient ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
||
|
|
||
|
if ( playerInfo.health <= 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.team != botInfo->team ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.weapInfo.isFiringWeap ) { //mal: if we're in someones way, try to move out of the way.
|
||
|
|
||
|
idVec3 vec = playerInfo.origin - botInfo->origin;
|
||
|
float avoidTraceDist = 500.0f;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( avoidTraceDist ) ) { //the bot is too far away to worry about being in your way.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
trace_t tr;
|
||
|
idVec3 end = playerInfo.viewOrigin;
|
||
|
end += ( avoidTraceDist * playerInfo.viewAxis[ 0 ] );
|
||
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, playerInfo.viewOrigin, end, MASK_SHOT_BOUNDINGBOX, GetGameEntity( i ) );
|
||
|
|
||
|
if ( tr.c.entityNum == botNum ) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
idVec3 vec = playerInfo.origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( avoidDist ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
blockedClient = i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return blockedClient;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_MoveAwayFromClient
|
||
|
|
||
|
Moves us away from the client we're blocking.
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_MoveAwayFromClient( int clientNum, bool randomStrafe ) {
|
||
|
|
||
|
botMoveFlags_t botMoveFlag;
|
||
|
|
||
|
if ( botInfo->posture == IS_CROUCHED || botInfo->posture == IS_PRONE ) {
|
||
|
botMoveFlag = CROUCH;
|
||
|
} else {
|
||
|
botMoveFlag = WALK;
|
||
|
}
|
||
|
|
||
|
botMoveTypes_t botMoveType = NULLMOVETYPE;
|
||
|
|
||
|
if ( randomStrafe ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
botMoveType = STRAFE_RIGHT;
|
||
|
} else {
|
||
|
botMoveType = STRAFE_LEFT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bot_SetupQuickMove( botInfo->origin, false );
|
||
|
Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, botMoveFlag, botMoveType );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_FindStanceForLocation
|
||
|
|
||
|
Finds the correct stance for the bot given a particular location ( used in reviving, planting mines/charges, spawnhosting, etc ).
|
||
|
ex: this way, a medic bot isn't trying to crouch over someone above them, etc.
|
||
|
================
|
||
|
*/
|
||
|
const botMoveFlags_t idBotAI::Bot_FindStanceForLocation( const idVec3 &org, bool allowProne ) {
|
||
|
idVec3 vec = org - botInfo->origin;
|
||
|
float locHeight = vec.z;
|
||
|
|
||
|
if ( locHeight > 60.0f ) { //40
|
||
|
return WALK;
|
||
|
} else if ( locHeight > 20.0f ) { //20
|
||
|
return CROUCH;
|
||
|
} else if ( allowProne ) {
|
||
|
return PRONE;
|
||
|
}
|
||
|
|
||
|
return CROUCH;
|
||
|
}
|
||
|
|
||
|
#define DYNAMIC_OBSTACLE_CULL_DISTANCE 4096
|
||
|
#define DYNAMIC_OBSTACLE_CONSIDER_DISTANCE 256
|
||
|
#define OBSTACLE_CULL_DISTANCE 2048.0f
|
||
|
//#define USE_OBSTACLE_PVS
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::AddGroundObstacles
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::AddGroundObstacles( bool largePlayerBBox, bool inVehicle ) {
|
||
|
bool botShouldMoveCautiously = false;
|
||
|
float avoidObstacleRange;
|
||
|
|
||
|
#ifdef USE_OBSTACLE_PVS
|
||
|
const byte *pvs = botAAS.aas->GetObstaclePVS( inVehicle ? botInfo->areaNumVehicle : botInfo->areaNum );
|
||
|
#endif
|
||
|
|
||
|
float bboxHeight = botAAS.aas->GetSettings()->boundingBox[ 1 ].z - botAAS.aas->GetSettings()->boundingBox[ 0 ].z;
|
||
|
idVec3 playerOrigin = ( botVehicleInfo != NULL ) ? botVehicleInfo->origin : botInfo->origin;
|
||
|
|
||
|
//mal: deployables first
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid the deployable we're trying to interact with!
|
||
|
if ( botUcmd->actionEntityNum != -1 && deployable.entNum == botUcmd->actionEntityNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = deployable.origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( OBSTACLE_CULL_DISTANCE ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idBox box( deployable.bbox, deployable.origin, deployable.axis );
|
||
|
|
||
|
if ( Bot_IsClearOfObstacle( box, bboxHeight, playerOrigin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
obstacles.AddObstacle( box, deployable.entNum );
|
||
|
|
||
|
if ( inVehicle ) {
|
||
|
if ( InFrontOfVehicle( botInfo->proxyInfo.entNum, deployable.origin ) ) {
|
||
|
botShouldMoveCautiously = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 5000 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type == MCP ) {
|
||
|
return botShouldMoveCautiously;
|
||
|
}
|
||
|
|
||
|
//mal: players next
|
||
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
||
|
|
||
|
if ( !ClientIsValid( i, -1 ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid ourselves! :-P
|
||
|
if ( i == botNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid the client we're trying to interact with!
|
||
|
if ( botUcmd->actionEntityNum != -1 && i == botUcmd->actionEntityNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
||
|
|
||
|
if ( playerInfo.health <= 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: we'll test vehicles seperately.
|
||
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: ignore players outside the valid AAS space
|
||
|
if ( playerInfo.areaNum == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid our enemy - we may want to get in his face.
|
||
|
if ( i == enemy ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.team != botInfo->team ) {
|
||
|
//mal: if we're in a vehicle, run them over! >:-D
|
||
|
if ( botVehicleInfo != NULL ) {
|
||
|
continue;
|
||
|
} else {
|
||
|
//mal: if on foot - only avoid enemy players if we can see them and we're not disguised.
|
||
|
if ( !botInfo->isDisguised && !InFrontOfClient( botNum, playerInfo.origin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if ( botVehicleInfo == NULL ) {
|
||
|
if ( !InFrontOfClient( botNum, playerInfo.origin, true ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: if in a vehicle, avoid our fellow players better!
|
||
|
avoidObstacleRange = 1024.0f;
|
||
|
bool inFrontOfOurVehicle = ( inVehicle == false ) ? false : InFrontOfVehicle( botVehicleInfo->entNum, playerInfo.origin );
|
||
|
float ourForwardSpeed = ( inVehicle == true ) ? botVehicleInfo->forwardSpeed : botInfo->xySpeed;
|
||
|
|
||
|
idVec3 vec = playerInfo.origin - botInfo->origin;
|
||
|
float distToPlayerSqr = vec.LengthSqr();
|
||
|
|
||
|
//mal: keep the range pretty tight.
|
||
|
if ( distToPlayerSqr > Square( avoidObstacleRange ) ) {
|
||
|
if ( inFrontOfOurVehicle && ourForwardSpeed > BASE_VEHICLE_SPEED && distToPlayerSqr < Square( 2000.0f ) ) {
|
||
|
botShouldMoveCautiously = true;
|
||
|
if ( !playerInfo.isBot ) {
|
||
|
botUcmd->desiredChat = MOVE; //mal: let the player know to move it or lose it!
|
||
|
botUcmd->botCmds.honkHorn = true;
|
||
|
}
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idBox box;
|
||
|
if ( largePlayerBBox ) {
|
||
|
box = idBox( playerInfo.origin, idVec3( 64.0f, 64.0f, 84.0f ), playerInfo.bodyAxis );
|
||
|
} else {
|
||
|
box = idBox( playerInfo.localBounds, playerInfo.origin, playerInfo.bodyAxis );
|
||
|
}
|
||
|
|
||
|
if ( Bot_IsClearOfObstacle( box, bboxHeight, playerOrigin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( inVehicle ) {
|
||
|
if ( inFrontOfOurVehicle ) {
|
||
|
botShouldMoveCautiously = true;
|
||
|
|
||
|
if ( botVehicleInfo->flags & PERSONAL ) { //mal: personal vehicles always keep moving, and just avoid.
|
||
|
obstacles.AddObstacle( box, i );
|
||
|
} else if ( playerInfo.xySpeed < WALKING_SPEED ) { //mal: if the player is stopped, or moving REALLY slow, avoid him
|
||
|
obstacles.AddObstacle( box, i );
|
||
|
} else if ( InFrontOfClient( i, botVehicleInfo->origin ) && distToPlayerSqr < Square( 512.0f ) && InFrontOfVehicle( botVehicleInfo->entNum, playerInfo.origin, true ) ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + 100; //mal: else, just stop and wait for him to pass.
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
obstacles.AddObstacle( box, i );
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 128 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: player's supply crates next
|
||
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
||
|
|
||
|
if ( !ClientIsValid( i, -1 ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
||
|
|
||
|
if ( playerInfo.supplyCrate.entNum == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: this crate hasn't settled yet, and is prolly still dropping from the air.
|
||
|
if ( playerInfo.supplyCrate.areaNum == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid the crate we're trying to interact with!
|
||
|
if ( botUcmd->actionEntityNum != -1 && playerInfo.supplyCrate.entNum == botUcmd->actionEntityNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = playerInfo.supplyCrate.origin - botInfo->origin;
|
||
|
|
||
|
//mal: if in a vehicle, avoid better!
|
||
|
if ( botVehicleInfo != NULL ) {
|
||
|
avoidObstacleRange = 1500.0f;
|
||
|
} else {
|
||
|
avoidObstacleRange = 1024.0f;
|
||
|
}
|
||
|
|
||
|
//mal: keep the range pretty tight.
|
||
|
if ( vec.LengthSqr() > Square( avoidObstacleRange ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idBox box;
|
||
|
|
||
|
if ( botVehicleInfo != NULL ) {
|
||
|
box = idBox( playerInfo.supplyCrate.origin, idVec3( 64.0f, 64.0f, 64.0f ), mat3_identity );
|
||
|
} else {
|
||
|
box = idBox( playerInfo.supplyCrate.origin, idVec3( 32.0f, 32.0f, 64.0f ), mat3_identity );
|
||
|
}
|
||
|
|
||
|
if ( Bot_IsClearOfObstacle( box, bboxHeight, playerOrigin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
obstacles.AddObstacle( box, playerInfo.supplyCrate.entNum );
|
||
|
|
||
|
if ( inVehicle ) {
|
||
|
if ( InFrontOfVehicle( botVehicleInfo->entNum, playerInfo.supplyCrate.origin ) ) {
|
||
|
botShouldMoveCautiously = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 128 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
//mal: arty strikes next
|
||
|
if ( currentArtyDanger.time > botWorld->gameLocalInfo.time ) {
|
||
|
|
||
|
idVec3 vec = currentArtyDanger.origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() < Square( 2048.0f ) ) {
|
||
|
idVec3 vec = currentArtyDanger.origin;
|
||
|
vec[ 2 ] += 128.0f;
|
||
|
idBox box( vec, idVec3( 1024.0f, 1024.0f, 128.0f ), mat3_identity );
|
||
|
|
||
|
if ( !Bot_IsClearOfObstacle( box, bboxHeight, playerOrigin ) ) {
|
||
|
obstacles.AddObstacle( box, MAX_GENTITIES + currentArtyDanger.num ); //mal: arty strikes dont have an ent num, so we just pass a bogus one for the ob avoid system
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 128 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: then general dangers we are aware of ( landmines, grenades, airstrikes, charges, etc ).
|
||
|
for( int i = 0; i < MAX_DANGERS; i++ ) {
|
||
|
|
||
|
if ( currentDangers[ i ].num == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( currentDangers[ i ].time < botWorld->gameLocalInfo.time ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !DangerStillExists( currentDangers[ i ].num, currentDangers[ i ].ownerNum ) ) {
|
||
|
ClearDangerFromDangerList( i, false );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = currentDangers[ i ].origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( DYNAMIC_OBSTACLE_CULL_DISTANCE ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
vec = currentDangers[ i ].origin;
|
||
|
|
||
|
idBox box;
|
||
|
if ( currentDangers[ i ].type == HAND_GRENADE ) {
|
||
|
vec[ 2 ] += 128.0f;
|
||
|
box = idBox( vec, idVec3( 320.0f, 320.0f, 128.0f ), mat3_identity );
|
||
|
} else if ( currentDangers[ i ].type == PLANTED_LANDMINE ) {
|
||
|
box = idBox( vec, idVec3( 320.0f, 320.0f, 128.0f ), mat3_identity );
|
||
|
} else if ( currentDangers[ i ].type == PLANTED_CHARGE ) {
|
||
|
box = idBox( vec, idVec3( 512.0f, 512.0f, 128.0f ), mat3_identity );
|
||
|
} else if ( currentDangers[ i ].type == THROWN_AIRSTRIKE ) {
|
||
|
box = idBox( vec, idVec3( 1024.0f, 768.0f, 64.0f ), currentDangers[ i ].dir );
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
gameRenderWorld->DebugBox( colorBlue, box );
|
||
|
}
|
||
|
} else if ( currentDangers[ i ].type == STROY_BOMB_DANGER ) {
|
||
|
box = idBox( vec, idVec3( 320.0f, 320.0f, 128.0f ), mat3_identity );
|
||
|
} else {
|
||
|
//mal: its not a danger, or one that we currently support.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !box.Expand( DYNAMIC_OBSTACLE_CONSIDER_DISTANCE ).ContainsPoint( botInfo->origin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
obstacles.AddObstacle( box, currentDangers[ i ].num );
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 128 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
return botShouldMoveCautiously;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::AddGroundAndAirObstacles
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::AddGroundAndAirObstacles( bool largePlayerBBox, bool inVehicle, bool inAirVehicle ) {
|
||
|
bool botShouldMoveCautiously = false;
|
||
|
|
||
|
#ifdef USE_OBSTACLE_PVS
|
||
|
const byte *pvs = botAAS.aas->GetObstaclePVS( inVehicle ? botInfo->areaNumVehicle : botInfo->areaNum );
|
||
|
int aasType = inVehicle ? AAS_VEHICLE : AAS_PLAYER;
|
||
|
#endif
|
||
|
|
||
|
float bboxHeight = botAAS.aas->GetSettings()->boundingBox[ 1 ].z - botAAS.aas->GetSettings()->boundingBox[ 0 ].z;
|
||
|
idVec3 playerOrigin = ( botVehicleInfo != NULL ) ? botVehicleInfo->origin : botInfo->origin;
|
||
|
|
||
|
//mal: manually placed obstacles
|
||
|
for( int i = 0; i < botThreadData.botObstacles.Num(); i++ ) {
|
||
|
idBotObstacle *obstacle = botThreadData.botObstacles[ i ];
|
||
|
|
||
|
#ifdef USE_OBSTACLE_PVS
|
||
|
if ( !inAirVehicle && !IsInObstaclePVS( pvs, obstacle->areaNum[aasType] ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
idVec3 vec = obstacle->bbox.GetCenter() - botInfo->origin;
|
||
|
float radiusSqr = obstacle->bbox.GetExtents().LengthSqr();
|
||
|
float avoidObstacleRangeSqr;
|
||
|
|
||
|
//mal: if in a vehicle, avoid better!
|
||
|
if ( botVehicleInfo != NULL ) {
|
||
|
avoidObstacleRangeSqr = radiusSqr + Square( 1024.0f );
|
||
|
} else {
|
||
|
avoidObstacleRangeSqr = radiusSqr + Square( 512.0f );
|
||
|
}
|
||
|
|
||
|
if ( vec.LengthSqr() > avoidObstacleRangeSqr ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( Bot_IsClearOfObstacle( obstacle->bbox, bboxHeight, playerOrigin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
obstacles.AddObstacle( obstacle->bbox, obstacle->num );
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type == MCP ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//mal: vehicles
|
||
|
for( int i = 0; i < MAX_VEHICLES; i++ ) {
|
||
|
|
||
|
const proxyInfo_t& vehicleInfo = botWorld->vehicleInfo[ i ];
|
||
|
|
||
|
if ( vehicleInfo.entNum == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont avoid the vehicle we're trying to interact with!
|
||
|
if ( botUcmd->actionEntityNum != -1 && vehicleInfo.entNum == botUcmd->actionEntityNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: dont count vehicles we're in.
|
||
|
if ( vehicleInfo.entNum == botInfo->proxyInfo.entNum ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
#ifdef USE_OBSTACLE_PVS
|
||
|
if ( !inAirVehicle && !IsInObstaclePVS( pvs, inVehicle ? vehicleInfo.areaNumVehicle : vehicleInfo.areaNum ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type != HUSKY && botInfo->team == GDF && vehicleInfo.type == ICARUS ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type <= ICARUS && vehicleInfo.type > ICARUS && !vehicleInfo.hasGroundContact ) { //mal: ground vehicles shouldn't worry about air vehicles in the air.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->type > ICARUS && vehicleInfo.type < ICARUS ) { //mal: air vehicles shouldn't worry about ground vehicles
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = vehicleInfo.origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( DYNAMIC_OBSTACLE_CULL_DISTANCE ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: enlarge the box based on the vehicles speed.
|
||
|
float expand = vehicleInfo.forwardSpeed * 2.0f;
|
||
|
idBox box( vehicleInfo.bbox, vehicleInfo.origin, vehicleInfo.axis );
|
||
|
box.ExpandSelf( idMath::Fabs( expand * 0.5f ), 0.0f, 0.0f );
|
||
|
box.TranslateSelf( box.GetAxis()[0] * expand * 0.5f );
|
||
|
|
||
|
if ( Bot_IsClearOfObstacle( box, bboxHeight, playerOrigin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
bool inFront = false;
|
||
|
|
||
|
if ( botVehicleInfo == NULL ) {
|
||
|
inFront = true;
|
||
|
} else {
|
||
|
if ( InFrontOfVehicle( botInfo->proxyInfo.entNum, vehicleInfo.origin ) && distSqr < Square( OBSTACLE_CULL_DISTANCE ) ) {
|
||
|
botShouldMoveCautiously = true;
|
||
|
inFront = true;
|
||
|
}
|
||
|
|
||
|
if ( inFront && distSqr < Square( 1024.0f ) ) {
|
||
|
if ( botVehicleInfo->type == GOLIATH ) {
|
||
|
if ( vehicleInfo.driverEntNum != -1 && distSqr < Square( 512.0f ) ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME; //mal: Goliath just stops and waits for everyone, since its so big, and the AAS bbox for it isn't.
|
||
|
}
|
||
|
} else if ( botVehicleInfo->flags & ARMOR && !( vehicleInfo.flags & ARMOR ) ) {
|
||
|
if ( InFrontOfVehicle( vehicleInfo.entNum, botVehicleInfo->origin, true ) && vehicleInfo.driverEntNum != -1 && vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME; //mal: just stop and wait for him to pass, since hes smaller, and more nimble.
|
||
|
continue;
|
||
|
}
|
||
|
} else if ( botVehicleInfo->flags & ARMOR && vehicleInfo.flags & ARMOR ) {
|
||
|
if ( InFrontOfVehicle( vehicleInfo.entNum, botVehicleInfo->origin, true ) ) { //mal: ok - this is the pain: what is the other guy doing?
|
||
|
if ( vehicleInfo.driverEntNum != -1 ) {
|
||
|
clientInfo_t driver = botWorld->clientInfo[ vehicleInfo.driverEntNum ];
|
||
|
|
||
|
if ( !driver.isBot ) {
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME; //mal: always defer to the human.
|
||
|
continue;
|
||
|
}
|
||
|
} else {
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED && botThreadData.bots[ vehicleInfo.driverEntNum ] != NULL && botThreadData.bots[ vehicleInfo.driverEntNum ]->vehiclePauseTime < botWorld->gameLocalInfo.time ) { //mal: hes not pausing, so we will.
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if ( !( botVehicleInfo->flags & PERSONAL ) && !( vehicleInfo.flags & ARMOR ) ) {
|
||
|
if ( InFrontOfVehicle( vehicleInfo.entNum, botVehicleInfo->origin, true ) ) { //mal: ok - this is the pain: what is the other guy doing?
|
||
|
if ( vehicleInfo.driverEntNum != -1 ) {
|
||
|
clientInfo_t driver = botWorld->clientInfo[ vehicleInfo.driverEntNum ];
|
||
|
|
||
|
if ( !driver.isBot ) {
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME; //mal: always defer to the human.
|
||
|
continue;
|
||
|
}
|
||
|
} else {
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED && botThreadData.bots[ vehicleInfo.driverEntNum ] != NULL && botThreadData.bots[ vehicleInfo.driverEntNum ]->vehiclePauseTime < botWorld->gameLocalInfo.time ) { //mal: hes not pausing, so we will.
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if ( !( botVehicleInfo->flags & PERSONAL ) && ( vehicleInfo.flags & ARMOR ) ) { //mal: small vehicles won't worry about armor - unless the driver is human
|
||
|
if ( InFrontOfVehicle( vehicleInfo.entNum, botVehicleInfo->origin, true ) ) { //mal: ok - this is the pain: what is the other guy doing?
|
||
|
if ( vehicleInfo.driverEntNum != -1 ) {
|
||
|
clientInfo_t driver = botWorld->clientInfo[ vehicleInfo.driverEntNum ];
|
||
|
|
||
|
if ( !driver.isBot ) {
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME; //mal: always defer to the human.
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->flags & ARMOR && !( vehicleInfo.flags & ARMOR ) ) {
|
||
|
if ( inFront && distSqr > Square( 512.0f ) && !InFrontOfVehicle( vehicleInfo.entNum, botVehicleInfo->origin ) && vehicleInfo.xyspeed > 0.0f && vehicleInfo.type != MCP ) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL && distSqr < Square( 1024.0f ) && InFrontOfVehicle( botInfo->proxyInfo.entNum, vehicleInfo.origin, true ) && vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
vehiclePauseTime = botWorld->gameLocalInfo.time + VEHICLE_PAUSE_TIME;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !inFront && !box.Expand( DYNAMIC_OBSTACLE_CONSIDER_DISTANCE ).ContainsPoint( botInfo->origin ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
obstacles.AddObstacle( box, vehicleInfo.entNum );
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_debugObstacles.GetBool() ) {
|
||
|
gameRenderWorld->DebugBox( colorGreen, box, 128 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return botShouldMoveCautiously;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::BuildObstacleList
|
||
|
|
||
|
Builds a list of the current obstacles around the player, so that the bot can avoid them.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::BuildObstacleList( bool largePlayerBBox, bool inVehicle ) {
|
||
|
bool inAirVehicle = false;
|
||
|
bool botShouldMoveCautiously = false;
|
||
|
|
||
|
obstacles.ClearObstacles();
|
||
|
|
||
|
if ( botVehicleInfo != NULL && botVehicleInfo->driverEntNum != botNum ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->onLadder ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( inVehicle && botVehicleInfo != NULL ) {
|
||
|
if ( botVehicleInfo->type > ICARUS ) {
|
||
|
inAirVehicle = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !inAirVehicle ) {
|
||
|
botShouldMoveCautiously |= AddGroundObstacles( largePlayerBBox, inVehicle );
|
||
|
}
|
||
|
|
||
|
botShouldMoveCautiously |= AddGroundAndAirObstacles( largePlayerBBox, inVehicle, inAirVehicle );
|
||
|
|
||
|
return botShouldMoveCautiously;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::FlyerHive_BuildPlayerObstacleList
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::FlyerHive_BuildPlayerObstacleList() {
|
||
|
|
||
|
obstacles.ClearObstacles();
|
||
|
|
||
|
for( int i = 0; i < MAX_CLIENTS; i++ ) {
|
||
|
if ( !ClientIsValid( i, -1 ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
|
||
|
|
||
|
if ( playerInfo.health <= 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//mal: ignore players outside the valid AAS space
|
||
|
if ( playerInfo.areaNum == 0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = playerInfo.origin - botInfo->weapInfo.covertToolInfo.origin;
|
||
|
|
||
|
//mal: keep the range pretty tight.
|
||
|
if ( vec.LengthSqr() > Square( 100.0f ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idBox box = idBox( playerInfo.origin, idVec3( 64.0f, 64.0f, 84.0f ), playerInfo.bodyAxis );
|
||
|
|
||
|
obstacles.AddObstacle( box, i );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_ApproxTravelTimeToLocation
|
||
|
|
||
|
Just does a "as the crow flies" check of how long it would take, approx, to reach a location.
|
||
|
================
|
||
|
*/
|
||
|
int idBotAI::Bot_ApproxTravelTimeToLocation( const idVec3 & from, const idVec3 &to, bool inVehicle ) const {
|
||
|
float dist = ( to - from ).LengthFast();
|
||
|
|
||
|
dist *= 100.0f / ( ( inVehicle ) ? BASE_VEHICLE_SPEED : RUNNING_SPEED ); //mal: just use this speed as a base, since we can't know for sure if the bot can/will sprint/strafejump, or what vehicle its in.
|
||
|
|
||
|
if ( dist < 1.0f ) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return ( int ) dist;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_FindRouteToCurrentGoal
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_FindRouteToCurrentGoal() {
|
||
|
|
||
|
int i;
|
||
|
idVec3 vec;
|
||
|
routeNode = NULL;
|
||
|
|
||
|
if ( !botWorld->gameLocalInfo.debugAltRoutes ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( actionNum < 0 || actionNum > botThreadData.botActions.Num() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->routeID == -1 ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( botVehicleInfo != NULL ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for( i = 0; i < botThreadData.botRoutes.Num(); i++ ) {
|
||
|
|
||
|
if ( botThreadData.botRoutes[ i ]->groupID != botThreadData.botActions[ actionNum ]->routeID ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botRoutes[ i ]->isHeadNode == false ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botRoutes[ i ]->active == false ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botRoutes[ i ]->team != botInfo->team && botThreadData.botRoutes[ i ]->team != NOTEAM ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botRoutes[ i ]->origin - botInfo->origin;
|
||
|
vec.z = 0.0f; //mal: dont take into account height!
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( botThreadData.botRoutes[ i ]->radius ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
routeNode = botThreadData.botRoutes[ i ];
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_FindNextRouteToGoal
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_FindNextRouteToGoal() {
|
||
|
|
||
|
bool useStack = false;
|
||
|
int i;
|
||
|
idBotRoutes* tempRoute;
|
||
|
idList< idBotRoutes* > tempRouteList;
|
||
|
|
||
|
if ( routeNode == NULL && AIStack.routeNode == NULL ) { //mal: oops!
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( routeNode == AIStack.routeNode || ( routeNode == NULL && AIStack.routeNode != NULL ) ) {
|
||
|
useStack = true;
|
||
|
}
|
||
|
|
||
|
if ( routeNode != NULL ) {
|
||
|
tempRoute = routeNode;
|
||
|
} else if ( AIStack.routeNode != NULL ) {
|
||
|
tempRoute = AIStack.routeNode;
|
||
|
} else {
|
||
|
assert( false );
|
||
|
routeNode = NULL;
|
||
|
AIStack.routeNode = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( tempRoute->routeLinks.Num() == 0 ) { //mal: reached the end of the road
|
||
|
routeNode = NULL;
|
||
|
AIStack.routeNode = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
tempRouteList.Clear();
|
||
|
|
||
|
for( i = 0; i < tempRoute->routeLinks.Num(); i++ ) { //mal: go thru and find only the active route links.
|
||
|
if ( !tempRoute->routeLinks[ i ]->active ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
tempRouteList.Append( tempRoute->routeLinks[ i ] );
|
||
|
}
|
||
|
|
||
|
if ( tempRouteList.Num() == 0 ) {
|
||
|
routeNode = NULL;
|
||
|
AIStack.routeNode = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
i = botThreadData.random.RandomInt( tempRouteList.Num() );
|
||
|
|
||
|
if ( tempRouteList[ i ] == NULL ) { //mal: should NEVER happen!
|
||
|
routeNode = NULL;
|
||
|
assert( false );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
routeNode = tempRouteList[ i ];
|
||
|
|
||
|
if ( useStack ) {
|
||
|
AIStack.routeNode = tempRouteList[ i ]; //mal: update the stack too, so if we leave our AI node, we'll see the new route when we come back to it.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_ReachedCurrentRouteNode
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_ReachedCurrentRouteNode() {
|
||
|
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( routeNode == NULL ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( routeNode->active == false ) { //mal: someone turned it off while we were on the way - find a different path.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = routeNode->origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( routeNode->radius ) || botInfo->onLadder ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_DirectionToLocation
|
||
|
|
||
|
Short and simple. Doesn't need to be really precise, just the general dir to target.
|
||
|
================
|
||
|
*/
|
||
|
const moveDirections_t idBotAI::Bot_DirectionToLocation( const idVec3& location, bool precise ) {
|
||
|
idMat3 axis;
|
||
|
idVec3 org;
|
||
|
|
||
|
if ( botVehicleInfo == NULL ) {
|
||
|
axis = botInfo->bodyAxis;
|
||
|
org = botInfo->origin;
|
||
|
} else {
|
||
|
axis = botVehicleInfo->axis;
|
||
|
org = botVehicleInfo->origin;
|
||
|
}
|
||
|
|
||
|
idVec3 dir = location - org;
|
||
|
dir.NormalizeFast();
|
||
|
|
||
|
if ( precise ) {
|
||
|
if ( dir * axis[ 0 ] > 0.95f ) {
|
||
|
return FORWARD;
|
||
|
} else if ( -dir * axis[ 0 ] > 0.50f ) {
|
||
|
return BACK;
|
||
|
} else if ( dir * axis[ 1 ] > 0.50f ) {
|
||
|
return LEFT;
|
||
|
} else {
|
||
|
return RIGHT;
|
||
|
}
|
||
|
} else {
|
||
|
if ( dir * axis[ 0 ] > 0.50f ) {
|
||
|
return FORWARD;
|
||
|
} else if ( -dir * axis[ 0 ] > 0.50f ) {
|
||
|
return BACK;
|
||
|
} else if ( dir * axis[ 1 ] > 0.50f ) {
|
||
|
return LEFT;
|
||
|
} else {
|
||
|
return RIGHT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_LocationIsReachable
|
||
|
|
||
|
Use the AAS to find out if a location is reachable.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_LocationIsReachable( bool inVehicle, const idVec3& loc, int& travelTime ) {
|
||
|
int travelFlags = ( botInfo->team == GDF ) ? TFL_VALID_GDF : TFL_VALID_STROGG;
|
||
|
int botAreaNum = ( inVehicle ) ? botInfo->areaNumVehicle : botInfo->areaNum;
|
||
|
int locAreaNum = botThreadData.Nav_GetAreaNum( ( inVehicle ) ? AAS_VEHICLE : AAS_PLAYER, loc );
|
||
|
const aasReachability_t *reach;
|
||
|
idVec3 botOrigin = ( inVehicle ) ? botInfo->aasVehicleOrigin : botInfo->aasOrigin;
|
||
|
|
||
|
if ( botInfo->isDisguised ) { //mal: if disguised, we can go anywhere we want.
|
||
|
travelFlags = TFL_VALID_GDF_AND_STROGG;
|
||
|
}
|
||
|
|
||
|
idAAS* aas;
|
||
|
|
||
|
if ( inVehicle ) {
|
||
|
aas = botThreadData.GetAAS( AAS_VEHICLE );
|
||
|
} else {
|
||
|
aas = botThreadData.GetAAS( AAS_PLAYER );
|
||
|
}
|
||
|
|
||
|
if ( aas == NULL ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !aas->RouteToGoalArea( botAreaNum, botOrigin, locAreaNum, travelFlags, travelTime, &reach ) ) {
|
||
|
return false;
|
||
|
}//mal: can't reach the target - prolly behind a team specific shield/wall/etc.
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_SetupFlyerMove
|
||
|
|
||
|
Sets up the bot's path goal with the flyer hive.
|
||
|
================
|
||
|
*/
|
||
|
void idBotAI::Bot_SetupFlyerMove( idVec3& goalOrigin, int goalAreaNum ) {
|
||
|
if ( botAAS.aas == NULL ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FlyerHive_BuildPlayerObstacleList();
|
||
|
|
||
|
int travelFlags = TFL_VALID_STROGG;
|
||
|
int walkTravelFlags = TFL_VALID_WALK_STROGG;
|
||
|
|
||
|
idVec3 hiveOrigin = botInfo->weapInfo.covertToolInfo.origin;
|
||
|
int hiveAreaNum = botAAS.aas->PointReachableAreaNum( hiveOrigin, botAAS.aas->GetSettings()->boundingBox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() );
|
||
|
|
||
|
botAAS.aas->PushPointIntoArea( hiveAreaNum, hiveOrigin );
|
||
|
|
||
|
if ( goalAreaNum == 0 ) {
|
||
|
goalAreaNum = botAAS.aas->PointReachableAreaNum( goalOrigin, botAAS.aas->GetSettings()->boundingBox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() );
|
||
|
}
|
||
|
|
||
|
if ( hiveAreaNum != goalAreaNum ) {
|
||
|
botAAS.aas->PushPointIntoArea( goalAreaNum, goalOrigin );
|
||
|
}
|
||
|
|
||
|
idObstacleAvoidance::obstaclePath_t path;
|
||
|
|
||
|
botAAS.hasPath = botAAS.aas->WalkPathToGoal( botAAS.path, hiveAreaNum, hiveOrigin, goalAreaNum, goalOrigin, travelFlags, walkTravelFlags );
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
if ( bot_showPath.GetInteger() == botNum ) {
|
||
|
botAAS.aas->ShowWalkPath( hiveAreaNum, hiveOrigin, goalAreaNum, goalOrigin, travelFlags, walkTravelFlags );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botInfo->localBounds, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, hiveOrigin, botAAS.path.moveGoal, path );
|
||
|
|
||
|
botAAS.path.moveGoal = path.seekPos;
|
||
|
botAAS.obstacleNum = path.firstObstacle;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
idBotAI::TravelFlagForTeam
|
||
|
============
|
||
|
*/
|
||
|
int idBotAI::TravelFlagForTeam() const {
|
||
|
switch( botInfo->team ) {
|
||
|
case GDF: return TFL_VALID_GDF;
|
||
|
case STROGG: return TFL_VALID_STROGG;
|
||
|
}
|
||
|
return TFL_VALID_GDF_AND_STROGG;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
idBotAI::TravelFlagWalkForTeam
|
||
|
============
|
||
|
*/
|
||
|
int idBotAI::TravelFlagWalkForTeam() const {
|
||
|
switch( botInfo->team ) {
|
||
|
case GDF: return TFL_VALID_WALK_GDF;
|
||
|
case STROGG: return TFL_VALID_WALK_STROGG;
|
||
|
}
|
||
|
return TFL_VALID_WALK_GDF_AND_STROGG;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
idBotAI::TravelFlagInvalidForTeam
|
||
|
============
|
||
|
*/
|
||
|
int idBotAI::TravelFlagInvalidForTeam() const {
|
||
|
switch( botInfo->team ) {
|
||
|
case GDF: return TFL_INVALID|TFL_INVALID_GDF;
|
||
|
case STROGG: return TFL_INVALID|TFL_INVALID_STROGG;
|
||
|
}
|
||
|
return TFL_INVALID|TFL_INVALID_GDF|TFL_INVALID_STROGG;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::LocationIsReachable
|
||
|
|
||
|
Use the AAS to find out if a location is reachable.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LocationIsReachable( bool inVehicle, const idVec3& loc1, const idVec3& loc2, int& travelTime ) {
|
||
|
int travelFlags = ( botInfo->team == GDF ) ? TFL_VALID_GDF : TFL_VALID_STROGG;
|
||
|
int loc1AreaNum = botThreadData.Nav_GetAreaNum( ( inVehicle ) ? AAS_VEHICLE : AAS_PLAYER, loc1 );
|
||
|
int loc2AreaNum = botThreadData.Nav_GetAreaNum( ( inVehicle ) ? AAS_VEHICLE : AAS_PLAYER, loc2 );
|
||
|
const aasReachability_t *reach;
|
||
|
idVec3 startOrg = loc1;
|
||
|
|
||
|
idAAS* aas;
|
||
|
|
||
|
if ( inVehicle ) {
|
||
|
aas = botThreadData.GetAAS( AAS_VEHICLE );
|
||
|
} else {
|
||
|
aas = botThreadData.GetAAS( AAS_PLAYER );
|
||
|
}
|
||
|
|
||
|
if ( aas == NULL ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( loc1AreaNum != loc2AreaNum ) {
|
||
|
aas->PushPointIntoArea( loc1AreaNum, startOrg );
|
||
|
}
|
||
|
|
||
|
if ( !aas->RouteToGoalArea( loc1AreaNum, startOrg, loc2AreaNum, travelFlags, travelTime, &reach ) ) {
|
||
|
return false;
|
||
|
}//mal: can't reach the target - prolly behind a team specific shield/wall/etc.
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Bot_CheckIfObstacleInArea
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Bot_CheckIfObstacleInArea( float minAvoidDist ) {
|
||
|
bool hasObstacle = false;
|
||
|
|
||
|
for( int i = 0; i < botThreadData.botObstacles.Num(); i++ ) {
|
||
|
|
||
|
idBotObstacle *obstacle = botThreadData.botObstacles[ i ];
|
||
|
idVec3 vec = obstacle->bbox.GetCenter() - botInfo->origin;
|
||
|
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
distSqr -= obstacle->bbox.GetExtents().LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( minAvoidDist ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
hasObstacle = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return hasObstacle;
|
||
|
}
|
||
|
|