3913 lines
110 KiB
C++
3913 lines
110 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_LTG::Enter_LTG_FollowMate
|
||
|
|
||
|
The bot has a teammate he wants to follow (and protect!). The goal finder will define how long we should be
|
||
|
following our teammate, and can reassign us if for some reason we run out of time.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_FollowMate() {
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + FOLLOW_MATE_TIMELIMIT;
|
||
|
|
||
|
PushAINodeOntoStack( ltgTarget, -1, ACTION_NULL, FOLLOW_MATE_TIMELIMIT, true, false ); //mal: by default, we dont use a vehicle for this node
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_FollowMate;
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
|
||
|
ltgChat = true;
|
||
|
|
||
|
ltgDist = 175.0f; //mal: want to get close to client the first time
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgUseShield = true;
|
||
|
|
||
|
routeNode = NULL;
|
||
|
|
||
|
ltgUseSmoke = true;
|
||
|
|
||
|
ResetRandomLook();
|
||
|
|
||
|
blockingTeammateCounter = 0;
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
ltgPauseTime = 0;
|
||
|
|
||
|
ltgTryMoveCounter = 0;
|
||
|
|
||
|
ltgMoveDir = NULL_DIR;
|
||
|
|
||
|
ltgCounter = 25 + ( botThreadData.random.RandomInt( 45 ) ); //mal: just a cute bit to make their crouching look more random
|
||
|
|
||
|
if ( ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) { //mal: client disconnected/got kicked/went spec, etc.
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ ltgTarget ];
|
||
|
|
||
|
if ( !playerInfo.isBot ) {
|
||
|
idVec3 vec = playerInfo.origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( MEDIC_ACK_MIN_DIST * 2.0f ) ) {
|
||
|
Bot_AddDelayedChat( botNum, LETS_GO, 1 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lastAINode = "Follow Mate";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_FollowMate
|
||
|
|
||
|
The bot has a teammate he wants to follow (and protect!).
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_FollowMate() {
|
||
|
bool rideVehicle = true;
|
||
|
int attacker;
|
||
|
float dist;
|
||
|
float maxDist = 475.0f; //mal: the furthest the bot will let you go, before he reacts.
|
||
|
proxyInfo_t vehicleInfo;
|
||
|
botMoveFlags_t defaultMove = SPRINT;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) { //mal: client disconnected/got kicked/went spec, etc.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( Client_IsCriticalForCurrentObj( botNum, TOO_CLOSE_TO_GOAL_TO_FOLLOW_DIST ) || ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum, TOO_CLOSE_TO_DELIVER_TO_FOLLOW_DIST ) ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ ltgTarget ];
|
||
|
|
||
|
if ( playerInfo.inLimbo ) { //mal: hes gone jim! Do something else
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ClientIsDead( ltgTarget ) && botInfo->classType != MEDIC ) { //mal: hes gone jim! Do something else. A medic will revive, and continue to escort!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.isDisguised || botInfo->isDisguised ) { //mal: if a covert decided to disguise himself, dont escort anymore - would give them away. Also, don't escort if we're disguised.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.isBot ) { //mal: dont follow a bot thats following someone else.
|
||
|
if ( botThreadData.bots[ ltgTarget ] != NULL && botThreadData.bots[ ltgTarget ]->GetAIState() == LTG && ( botThreadData.bots[ ltgTarget ]->GetLTGType() == FOLLOW_TEAMMATE || botThreadData.bots[ ltgTarget ]->GetLTGType() == FOLLOW_TEAMMATE_BY_REQUEST ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ltgMoveTime < botWorld->gameLocalInfo.time ) {
|
||
|
ltgTryMoveCounter = 0;
|
||
|
}
|
||
|
|
||
|
int moveAwayFromClientCrossHairCounter = ( playerInfo.weapInfo.isFiringWeap ) ? 1 : 10; //mal: if our friend is shooting, move out of the way quicker!
|
||
|
|
||
|
vec = playerInfo.origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.botFollowPlayer == 2 ) {
|
||
|
rideVehicle = false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->team != playerInfo.team ) {
|
||
|
rideVehicle = false;
|
||
|
}
|
||
|
|
||
|
if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE && rideVehicle ) { //mal: client jumped into a vehicle/deployable - jump in if we can, or just forget them.
|
||
|
GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo );
|
||
|
|
||
|
if ( !VehicleIsValid( vehicleInfo.entNum, false, true ) || vehicleInfo.flags & PERSONAL ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
} //mal: theres no room for us on this ride, so forget following this player.
|
||
|
|
||
|
if ( ltgChat && playerInfo.isBot == false ) {
|
||
|
Bot_AddDelayedChat( botNum, HOLD_VEHICLE, 0 );
|
||
|
ltgChat = false;
|
||
|
} //mal: let the teammate we're escorting know we're going to follow him into the vehicle
|
||
|
|
||
|
Bot_GetIntoVehicle( vehicleInfo.entNum );
|
||
|
vLTGTarget = ltgTarget;
|
||
|
vLTGTargetSpawnID = ltgTargetSpawnID;
|
||
|
V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node;
|
||
|
V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RideWithMate;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ( playerInfo.posture == IS_CROUCHED || playerInfo.posture == IS_PRONE ) && playerInfo.crouchCounter > ltgCounter ) {
|
||
|
if ( dist < Square( maxDist ) ) {
|
||
|
defaultMove = CROUCH;
|
||
|
}
|
||
|
} else if ( playerInfo.xySpeed == 0.0f && dist < Square( ltgDist ) ) {
|
||
|
defaultMove = WALK;
|
||
|
} else if ( dist < Square( 900.0f ) ) {
|
||
|
defaultMove = RUN;
|
||
|
}
|
||
|
|
||
|
botUcmd->actionEntityNum = ltgTarget; //mal: let the game and obstacle avoidance know we want to interact with this entity.
|
||
|
botUcmd->actionEntitySpawnID = playerInfo.spawnID;
|
||
|
|
||
|
if ( dist > Square( ltgDist ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreClient( ltgTarget, CLIENT_IGNORE_TIME ); //mal: no valid path to this client for some reason - ignore him for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( maxDist * 2 ) ) ? Bot_ShouldStrafeJump( playerInfo.origin ) : defaultMove );
|
||
|
|
||
|
if ( dist < Square( maxDist ) && botAAS.blockedByObstacleCounterOnFoot > MAX_FRAMES_BLOCKED_BY_OBSTACLE_ON_FOOT ) {
|
||
|
ltgDist += 5.0f;
|
||
|
}//mal: if we've been trying to reach our target for too long, and got caught up on something or someone, relax the dist requirements a bit.
|
||
|
|
||
|
ltgTimer = 0; //mal: we're chasing our friend, so reset the timer.
|
||
|
ltgReached = false;
|
||
|
ltgTryMoveCounter = 0;
|
||
|
ResetRandomLook();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->classType == FIELDOPS && botInfo->team == STROGG && ltgUseShield && botInfo->lastShieldDroppedTime + 1500 < botWorld->gameLocalInfo.time ) {
|
||
|
int numShieldsInWorld = Bot_NumShieldsInWorld();
|
||
|
|
||
|
if ( numShieldsInWorld < 2 && playerInfo.xySpeed < WALKING_SPEED && ClassWeaponCharged( SHIELD_GUN ) && ( playerInfo.weapInfo.weapon == PLIERS || playerInfo.weapInfo.weapon == NEEDLE || playerInfo.weapInfo.weapon == HACK_TOOL || playerInfo.weapInfo.weapon == HE_CHARGE ) ) {
|
||
|
bool hasShield = Bot_HasShieldInWorldNearLocation( playerInfo.origin, 300.0f );
|
||
|
float maxDistToPlayer = ( hasShield ) ? 40.0f : SHIELD_FIRING_RANGE;
|
||
|
|
||
|
if ( tacticalActionIgnoreTime < botWorld->gameLocalInfo.time ) {
|
||
|
tacticalActionIgnoreTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
if ( dist > Square( maxDistToPlayer ) || botInfo->onLadder ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
ltgUseShield = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ltgPauseTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
|
||
|
Bot_MoveAlongPath( RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( hasShield ) { //mal: if we've dropped a shield in front of him, drop one behind him to form a bubble of protection.
|
||
|
Bot_LookAtLocation( ltgOrigin, SMOOTH_TURN );
|
||
|
} else {
|
||
|
idVec3 loc = playerInfo.viewOrigin;
|
||
|
loc.z += 32.0f;
|
||
|
Bot_LookAtLocation( loc, SMOOTH_TURN );
|
||
|
|
||
|
if ( ltgOrigin.IsZero() ) {
|
||
|
ltgOrigin = playerInfo.origin;
|
||
|
vec = playerInfo.origin - botInfo->origin;
|
||
|
float distToPlayer = vec.LengthFast();
|
||
|
float zValue = ltgOrigin.z;
|
||
|
ltgOrigin += ( -( distToPlayer ) * playerInfo.viewAxis[ 0 ] );
|
||
|
ltgOrigin.z = ( zValue - 32.0f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botIdealWeapNum = SHIELD_GUN;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
ltgTimer = 0; //mal: we're chasing our friend, so reset the timer.
|
||
|
ltgReached = false;
|
||
|
ltgTryMoveCounter = 0;
|
||
|
ResetRandomLook();
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == SHIELD_GUN && ltgPauseTime < botWorld->gameLocalInfo.time ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
} else if ( ltgUseSmoke && botInfo->classType == COVERTOPS && botInfo->team == GDF && ClassWeaponCharged( SMOKE_NADE ) ) {
|
||
|
if ( playerInfo.xySpeed < WALKING_SPEED && ( playerInfo.weapInfo.weapon == PLIERS || playerInfo.weapInfo.weapon == HACK_TOOL || playerInfo.weapInfo.weapon == HE_CHARGE ) ) {
|
||
|
|
||
|
if ( dist > Square( 350.0f ) || botInfo->onLadder ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
ltgUseSmoke = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( ltgTarget, SMOOTH_TURN );
|
||
|
|
||
|
botIdealWeapNum = SMOKE_NADE;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
ltgTimer = 0; //mal: we're chasing our friend, so reset the timer.
|
||
|
ltgReached = false;
|
||
|
ltgTryMoveCounter = 0;
|
||
|
ResetRandomLook();
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == SMOKE_NADE && botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( enemy == -1 ) {
|
||
|
attacker = CheckClientAttacker( ltgTarget, 1 );
|
||
|
|
||
|
if ( attacker != -1 ) {
|
||
|
int travelTime;
|
||
|
const clientInfo_t& attackerInfo = botWorld->clientInfo[ attacker ];
|
||
|
if ( Bot_LocationIsReachable( false, attackerInfo.origin, travelTime ) ) {
|
||
|
aiState = LTG;
|
||
|
ltgType = HUNT_GOAL;
|
||
|
ltgTime = botWorld->gameLocalInfo.time + 15000;
|
||
|
ROOT_AI_NODE = &idBotAI::Run_LTG_Node;
|
||
|
LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_HuntGoal;
|
||
|
ltgTarget = attacker;
|
||
|
ltgTargetSpawnID = attackerInfo.spawnID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//mal: make sure we can see our friend - if not, we may need to move closer.
|
||
|
if ( !botThreadData.Nav_IsDirectPath( AAS_PLAYER, botInfo->team, botInfo->areaNum, botInfo->aasOrigin, playerInfo.origin ) && dist > Square( 75.0f ) && playerInfo.xySpeed == 0.0f && ltgDist != 130.0f ) {
|
||
|
ltgDist = 130.0f;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( dist < Square( 50.0f ) && ltgTryMoveCounter < MAX_MOVE_ATTEMPTS ) { // too close, back up some!
|
||
|
ltgTryMoveCounter++;
|
||
|
if ( Bot_CanMove( BACK, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( ltgTarget, SMOOTH_TURN );
|
||
|
ltgTimer = 0; //mal: we're retreating from our friend, so reset the timer.
|
||
|
ltgReached = false;
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
|
||
|
if ( ltgDist < maxDist ) {
|
||
|
ltgDist += 5.0f;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int blockedClient = Bot_CheckBlockingOtherClients( ltgTarget );
|
||
|
|
||
|
if ( blockedClient != -1 && ltgTryMoveCounter < MAX_MOVE_ATTEMPTS ) {
|
||
|
ltgTryMoveCounter++;
|
||
|
if ( Bot_CanMove( BACK, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( blockedClient, SMOOTH_TURN );
|
||
|
ltgTimer = 0;
|
||
|
ltgReached = false;
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
if ( ltgDist < maxDist ) {
|
||
|
ltgDist += 5.0f;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool shouldAvoidTeammate = false;
|
||
|
|
||
|
if ( !playerInfo.isBot && playerInfo.weapInfo.weapon != HEALTH ) { //mal: dont avoid other bots, or medics trying to help us out.
|
||
|
shouldAvoidTeammate = true;
|
||
|
}
|
||
|
|
||
|
if ( shouldAvoidTeammate ) {
|
||
|
idVec3 dir = botInfo->origin - playerInfo.origin;
|
||
|
dir.NormalizeFast();
|
||
|
if ( dir * playerInfo.viewAxis[ 0 ] > 0.98f ) {
|
||
|
blockingTeammateCounter++;
|
||
|
} else {
|
||
|
blockingTeammateCounter = 0;
|
||
|
ltgMoveDir = NULL_DIR;
|
||
|
}
|
||
|
} else {
|
||
|
blockingTeammateCounter = 0;
|
||
|
ltgMoveDir = NULL_DIR;
|
||
|
}
|
||
|
|
||
|
if ( blockingTeammateCounter > moveAwayFromClientCrossHairCounter && ltgTryMoveCounter < MAX_MOVE_ATTEMPTS ) {
|
||
|
ltgTryMoveCounter++;
|
||
|
if ( ltgMoveDir == NULL_DIR ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
ltgMoveDir = RIGHT;
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
ltgMoveDir = LEFT;
|
||
|
} else {
|
||
|
ltgMoveDir = BACK;
|
||
|
}
|
||
|
} else {
|
||
|
if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
ltgMoveDir = LEFT;
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
ltgMoveDir = RIGHT;
|
||
|
} else {
|
||
|
ltgMoveDir = BACK;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botMoveTypes_t botMoveType;
|
||
|
|
||
|
if ( ltgMoveDir == RIGHT ) {
|
||
|
botMoveType = STRAFE_RIGHT;
|
||
|
} else if ( ltgMoveDir == LEFT ) {
|
||
|
botMoveType = STRAFE_LEFT;
|
||
|
} else {
|
||
|
botMoveType = BACKSTEP;
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( ltgTarget, SMOOTH_TURN ); //mal: look at our target for a bit when first reached.
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, defaultMove, botMoveType );
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
ltgReached = false;
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
|
||
|
if ( ltgTimer == 0 ) { //mal: add a little variety to the bots following dist, so hes not so "robotic" about being near you.
|
||
|
ltgDist = 175.0f + ( float ) botThreadData.random.RandomInt( 301 );
|
||
|
ltgTimer = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
if ( ltgTimer > botWorld->gameLocalInfo.time ) {
|
||
|
Bot_LookAtEntity( ltgTarget, SMOOTH_TURN ); //mal: look at our target for a bit when first reached.
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 97 || ltgReached == false ) {
|
||
|
idVec3 vec;
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
ltgReached = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_RoamGoal
|
||
|
|
||
|
The bot has a location on the map he wants to visit. When he reaches it, he'll move on to somewhere else.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_RoamGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_RoamGoal;
|
||
|
|
||
|
ltgType = ROAM_GOAL;
|
||
|
|
||
|
stayInPosition = false;
|
||
|
|
||
|
//mal: higher skill bots are more likely to respond to heard sounds, and go after the person making them, then lower skilled bots.
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
|
||
|
stayInPosition = true;
|
||
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 70 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lastAINode = "Roam Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_RoamGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_RoamGoal() {
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( actionNumInsideDanger == actionNum ) { //mal: if our action is encased inside a danger ( landmine ), and we're not destroying it, must not be able to, so leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_ExitAINode(); //mal: reached our roam goal, so just leave.
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_CampGoal
|
||
|
|
||
|
The bot has a location on the map he wants to camp. When he reaches it, he'll pop into the NBG AI node for camping.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_CampGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_CampGoal;
|
||
|
|
||
|
stayInPosition = false;
|
||
|
|
||
|
//mal: higher skill bots are more likely to respond to heard sounds, and go after the person making them, then lower skilled bots.
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
|
||
|
stayInPosition = true;
|
||
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 70 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ltgType == DEFENSE_CAMP_GOAL ) {
|
||
|
lastAINode = "Defense Camp Goal";
|
||
|
stayInPosition = true;
|
||
|
} else {
|
||
|
lastAINode = "Camp Goal";
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_CampGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_CampGoal() {
|
||
|
|
||
|
int mates, vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.AllowDebugData() && bot_debugActionGoalNumber.GetInteger() == actionNum ) {
|
||
|
goto debugSkipChecks;
|
||
|
}
|
||
|
|
||
|
if ( !botInfo->weapInfo.primaryWeapHasAmmo ) { //mal: no ammo - dont camp.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( actionNumInsideDanger == actionNum ) { //mal: if our action is encased inside a danger ( landmine ), and we're not destroying it, must not be able to, so leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mates = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), ( ltgType == DEFENSE_CAMP_GOAL ) ? DEFENSE_CAMP_MATE_RANGE : 150.0f, botInfo->team, NOCLASS, false, false, false, false, true );
|
||
|
|
||
|
if ( mates > 0 ) { //mal: don't try to camp a spot someone else may already be at.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
debugSkipChecks:
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Camp;
|
||
|
nbgTime = botWorld->gameLocalInfo.time + ( botThreadData.botActions[ actionNum ]->actionTimeInSeconds * 1000 ); //mal: timelimit is specified by action itself!
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_ErrorThink
|
||
|
|
||
|
A special, safety node for the bots if they have nothing to think about ( i.e. the map has no goals yet ).
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_ErrorThink() {
|
||
|
|
||
|
idVec3 vec;
|
||
|
|
||
|
lastAINode = "Error Think";
|
||
|
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 98 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 98 ) {
|
||
|
botUcmd->botCmds.hasNoGoals = true; //mal: pass a warning to the player to let them know we have no goals
|
||
|
}
|
||
|
|
||
|
Bot_ResetState( true, true );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_BuildGoal
|
||
|
|
||
|
The bot has an objective on the map that he wants to build.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_BuildGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_BuildGoal;
|
||
|
|
||
|
lastAINode = "Build Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_BuildGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_BuildGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgType == BUILD_GOAL ) {
|
||
|
if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: player decided to be a hero, so let them.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Build;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_PlantGoal
|
||
|
|
||
|
The bot has an objective on the map that he wants to plant explosives on.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_PlantGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_PlantGoal;
|
||
|
|
||
|
ltgType = PLANT_GOAL;
|
||
|
|
||
|
//mal: let everyone know what he wants to do
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetVOChat() != NULL_CHAT ) {
|
||
|
Bot_AddDelayedChat( botNum, botThreadData.botActions[ actionNum ]->GetVOChat(), 3 );
|
||
|
} else {
|
||
|
Bot_AddDelayedChat( botNum, GENERIC_PLANT, 3 );
|
||
|
}
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
ltgTryMoveCounter = 0;
|
||
|
|
||
|
lastAINode = "Plant Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_PlantGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_PlantGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: player decided to be a hero, so let them.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ltgMoveTime < botWorld->gameLocalInfo.time ) {
|
||
|
ltgTryMoveCounter = 0;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
idBox playerBox = idBox( botInfo->localBounds, botInfo->origin, botInfo->bodyAxis );
|
||
|
|
||
|
if ( dist < Square( ACTION_CHECK_TEAMMATE_DIST ) ) {
|
||
|
if ( botThreadData.botActions[ actionNum ]->CheckTeamMemberIsInsideAction( botNum, botInfo->team, botInfo->classType, true ) && !botThreadData.botActions[ actionNum ]->actionBBox.IntersectsBox( playerBox ) ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 95 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
botIdealWeapSlot = GUN;
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
|
||
|
int blockedClient = Bot_CheckBlockingOtherClients( -1 ); //mal: try not to block other clients who may also want to plant, or get by.
|
||
|
|
||
|
if ( blockedClient != -1 && ltgTryMoveCounter < MAX_MOVE_ATTEMPTS ) {
|
||
|
ltgTryMoveCounter++;
|
||
|
if ( Bot_CanMove( BACK, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( blockedClient, SMOOTH_TURN );
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_PlantBomb;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_HackGoal
|
||
|
|
||
|
The bot has an objective on the map that he wants to hack.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_HackGoal() {
|
||
|
|
||
|
if ( botInfo->weapInfo.primaryWeapon == SNIPERRIFLE ) {
|
||
|
botUcmd->botCmds.switchAwayFromSniperRifle = true;
|
||
|
}
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_HackGoal;
|
||
|
|
||
|
lastAINode = "Hack Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_HackGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_HackGoal() {
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: player decided to be a hero, so let them.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Hack;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_DefuseGoal
|
||
|
|
||
|
The bot has an objective on the map that hes trying to protect, so he'll defuse any planted charges that may be on it.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_DefuseGoal() {
|
||
|
|
||
|
ltgType = DEFUSE_GOAL;
|
||
|
|
||
|
PushAINodeOntoStack( -1, -1, actionNum, DEFAULT_LTG_TIME, true, ltgUseVehicle );
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_DefuseGoal;
|
||
|
|
||
|
lastAINode = "Defuse Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_DefuseGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_DefuseGoal() {
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: player decided to be a hero, so let them.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->ArmedChargesInsideActionBBox( -1 ) ) { //mal: someone defused all the charges, so leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
plantedChargeInfo_t bombInfo;
|
||
|
bool isNearBomb = FindChargeInWorld( actionNum, bombInfo, DISARM ); //mal: looks silly to walk by charge to move towards the action, so check if we're close to it.
|
||
|
|
||
|
if ( !isNearBomb && ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_DefuseBomb;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_HuntGoal
|
||
|
|
||
|
The bot has a target on the map he wants to pay a none too frienldy visit. When he reaches it, he'll kill it, unless he runs out of time.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_HuntGoal() {
|
||
|
|
||
|
ltgType = HUNT_GOAL;
|
||
|
|
||
|
routeNode = NULL;
|
||
|
|
||
|
if ( ltgTime > 30000 ) { //mal: if we've decided to do this for a while, push it onto the stack.
|
||
|
PushAINodeOntoStack( -1, -1, actionNum, DEFAULT_LTG_TIME, false, false );
|
||
|
}
|
||
|
|
||
|
ltgTime += botWorld->gameLocalInfo.time;
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_HuntGoal;
|
||
|
|
||
|
fastAwareness = true;
|
||
|
|
||
|
lastAINode = "Hunt Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_HuntGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_HuntGoal() {
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
int ignoreHuntGoalClientTime = 5000;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ClearAIStack();
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreClient( ltgTarget, ignoreHuntGoalClientTime );
|
||
|
fastAwareness = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) {
|
||
|
Bot_ClearAIStack();
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreClient( ltgTarget, ignoreHuntGoalClientTime );
|
||
|
fastAwareness = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ClientIsDead( ltgTarget ) ) { //mal: targets dead already
|
||
|
Bot_ClearAIStack();
|
||
|
Bot_ExitAINode();
|
||
|
fastAwareness = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& playerInfo = botWorld->clientInfo[ ltgTarget ];
|
||
|
|
||
|
botUcmd->actionEntityNum = ltgTarget;
|
||
|
botUcmd->actionEntitySpawnID = playerInfo.spawnID;
|
||
|
|
||
|
vec = playerInfo.origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( 50.0f ) || botInfo->onLadder || botInfo->inWater ) {
|
||
|
Bot_SetupMove( playerInfo.origin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ClearAIStack();
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreClient( ltgTarget, ignoreHuntGoalClientTime );
|
||
|
fastAwareness = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 500.0f ) ) ? Bot_ShouldStrafeJump( playerInfo.origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_ClearAIStack();
|
||
|
Bot_ExitAINode(); //mal: if we get this far, and still no enemy, somethings goofy, so just leave.
|
||
|
Bot_IgnoreClient( ltgTarget, ignoreHuntGoalClientTime );
|
||
|
fastAwareness = false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_MineGoal
|
||
|
|
||
|
The bot has a location on the map he wants to plant mines at.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_MineGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_MineGoal;
|
||
|
|
||
|
if ( ltgType == PRIORITY_MINE_GOAL ) {
|
||
|
lastAINode = "Priority Mine Goal";
|
||
|
} else {
|
||
|
lastAINode = "Mine Goal";
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_MineGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_MineGoal() {
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->ArmedMinesInsideActionBBox() ) { //mal: someone already planted at the spot we wanted to, so just leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_PlantMine;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_SnipeGoal
|
||
|
|
||
|
The bot has a location on the map he wants to snipe at.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_SnipeGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_SnipeGoal;
|
||
|
|
||
|
ltgType = SNIPE_GOAL;
|
||
|
|
||
|
lastAINode = "Sniper Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_SnipeGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_SnipeGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
int matesInArea;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botInfo->weapInfo.primaryWeapHasAmmo ) { //mal: no ammo - dont camp.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->isDisguised ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
matesInArea = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), 150.0f, botInfo->team, COVERTOPS, false, false, false, true, true );
|
||
|
|
||
|
if ( matesInArea > 0 ) { //mal: someone there camping already ( prolly a human ), so just leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Snipe;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_InvestigateGoal
|
||
|
|
||
|
The bot has an action that he wants to check out - its prolly under attack.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_InvestigateGoal() {
|
||
|
|
||
|
ltgType = INVESTIGATE_ACTION;
|
||
|
|
||
|
PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, false );
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_InvestigateGoal;
|
||
|
|
||
|
stayInPosition = false;
|
||
|
|
||
|
routeNode = NULL;
|
||
|
|
||
|
//mal: higher skill bots are more likely to respond to heard sounds, and go after the person making them, then lower skilled bots.
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
|
||
|
stayInPosition = true;
|
||
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 70 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lastAINode = "Investigate Action";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_InvestigateGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_InvestigateGoal() {
|
||
|
|
||
|
int matesInArea;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off, so we dont care about it now.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->ActionIsPriority() ) { //mal: action has been flagged as not being a priority anymore, so do something else.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int enemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), BOT_INVESTIGATE_RANGE, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetActionState() == ACTION_STATE_NORMAL && enemiesInArea == 0 ) { //mal: its back to normal, we can go about our business.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
matesInArea = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), 350.0f, botInfo->team, NOCLASS, false, false, false, false, false );
|
||
|
|
||
|
if ( matesInArea > MIN_NUM_INVESTIGATE_CLIENTS ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;;
|
||
|
} //mal: already some ppl there, so lets do something else.
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( CLOSE_ENOUGH_TO_INVESTIGATE_GOAL_RANGE ) || botInfo->onLadder ) { //mal: get REALLY close to the action in question, so we can see thru smoke, other obstalces that may be in the way.
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 500.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: if we get here, it means we've reached our target action. If it was a plant, lets decide to camp it to cover it until an eng arrives.
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetObjForTeam( botInfo->team ) == ACTION_DEFUSE && botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
if ( !Bot_NBGIsAvailable( -1, actionNum, CAMP, matesInArea ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
} else {
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Camp;
|
||
|
nbgTime = botWorld->gameLocalInfo.time + 30000; //mal: if we're camping at an action we investigated, only do so for 30 secs ( bomb will be defused/blow by then anyhow ).
|
||
|
}
|
||
|
} else {
|
||
|
int criticalEnemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), CRITICAL_ENEMY_CLOSE_TO_GOAL_RANGE, ( botInfo->team == GDF ) ? STROGG : GDF, TeamCriticalClass( ( botInfo->team == GDF ) ? STROGG : GDF ), false, false, false, false, false );
|
||
|
|
||
|
if ( criticalEnemiesInArea == 0 ) {
|
||
|
Bot_ExitAINode(); //mal: reached our goal, so just leave.
|
||
|
} else {
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_Camp;
|
||
|
nbgTime = botWorld->gameLocalInfo.time + 5000;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Enter_LTG_UseVehicle
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_UseVehicle() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_UseVehicle;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ResetRandomLook();
|
||
|
|
||
|
if ( ltgType == DRIVE_MCP ) {
|
||
|
ltgReached = FindMCPStartAction( ltgOrigin );
|
||
|
lastAINode = "Entering MCP";
|
||
|
} else {
|
||
|
lastAINode = "Entering Vehicle";
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::LTG_UseVehicle
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_UseVehicle() {
|
||
|
float dist;
|
||
|
proxyInfo_t vehicleInfo;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgType == DRIVE_MCP ) {
|
||
|
ltgTarget = FindClosestVehicle( idMath::INFINITY, botInfo->origin, MCP, NULL_VEHICLE_FLAGS, NULL_VEHICLE_FLAGS, false ); //mal: let the bots ride shotgun
|
||
|
} else if ( ltgType == GRAB_VEHICLE_GOAL ) {
|
||
|
ltgTarget = FindClosestVehicle( MAX_VEHICLE_RANGE, botThreadData.botActions[ actionNum ]->GetActionOrigin(), NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
} else if ( ltgType == ENTER_HEAVY_VEHICLE ) {
|
||
|
ltgTarget = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, AIR, PERSONAL, true ); //mal: try an air vehicle first - then the action's vehicle.
|
||
|
|
||
|
if ( ltgTarget == -1 ) {
|
||
|
ltgTarget = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), PERSONAL, true );
|
||
|
}
|
||
|
} else {
|
||
|
ltgTarget = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
}
|
||
|
|
||
|
if ( ltgTarget == -1 ) { //mal: if can't find a vehicle anymore, cant do a vehicle only mission.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
GetVehicleInfo( ltgTarget, vehicleInfo );
|
||
|
|
||
|
if ( vehicleInfo.entNum == 0 ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgType == DRIVE_MCP ) {
|
||
|
if ( !vehicleInfo.hasGroundContact ) {
|
||
|
if ( ltgReached ) {
|
||
|
vec = ltgOrigin - botInfo->origin;
|
||
|
if ( vec.LengthSqr() > Square( 250.0f ) ) {
|
||
|
Bot_SetupMove( ltgOrigin, -1, ACTION_NULL );
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( Bot_ShouldStrafeJump( ltgOrigin ) );
|
||
|
return true;
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 90 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN );
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
} else { //mal: someone forgot to add a MCP start action, so just stand here and wait for it to drop before we run to it.
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 90 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN );
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( vehicleInfo.driverEntNum != -1 ) { //mal: someone took off in our ride!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
botUcmd->actionEntityNum = vehicleInfo.entNum;
|
||
|
botUcmd->actionEntitySpawnID = vehicleInfo.spawnID;
|
||
|
|
||
|
idVec3 goalOrigin;
|
||
|
|
||
|
if ( ltgType == GRAB_VEHICLE_GOAL && ltgReached == false ) {
|
||
|
vec = botThreadData.botActions[ actionNum ]->GetActionOrigin() - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( botThreadData.botActions[ actionNum ]->radius ) ) {
|
||
|
goalOrigin = botThreadData.botActions[ actionNum ]->GetActionOrigin();
|
||
|
} else {
|
||
|
goalOrigin = vehicleInfo.origin;
|
||
|
ltgReached = true;
|
||
|
}
|
||
|
} else {
|
||
|
goalOrigin = vehicleInfo.origin;
|
||
|
}
|
||
|
|
||
|
goalOrigin.z += 32.0f;
|
||
|
|
||
|
Bot_SetupMove( goalOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
vec = vehicleInfo.origin - botInfo->origin;
|
||
|
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) ) ? Bot_ShouldStrafeJump( goalOrigin ) : RUN );
|
||
|
|
||
|
//mal: if we're fairly close, start sending the vehicle use cmd
|
||
|
if ( dist < Square( 350.0f ) ) {
|
||
|
Bot_LookAtLocation( vehicleInfo.origin, SMOOTH_TURN );
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
botUcmd->botCmds.enterVehicle = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_FixMCP
|
||
|
|
||
|
The bot has a MCP that he wants to check fix.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_FixMCP() {
|
||
|
|
||
|
ltgType = FIX_MCP;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it!
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_FixMCP;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgPauseTime = 0;
|
||
|
|
||
|
lastAINode = "Fix MCP";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_FixMCP
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_FixMCP() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
proxyInfo_t vehicleInfo;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( !botWorld->botGoalInfo.mapHasMCPGoal ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum == -1 ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, NULL_VEHICLE_FLAGS, NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, vehicleInfo );
|
||
|
|
||
|
if ( vehicleInfo.entNum == 0 ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( vehicleInfo.health == vehicleInfo.maxHealth ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( vehicleInfo.xyspeed > WALKING_SPEED ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( vehicleInfo.inWater || !vehicleInfo.inPlayZone || vehicleInfo.areaNum == 0 || vehicleInfo.isFlipped ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( vehicleInfo.health > ( vehicleInfo.maxHealth / 2 ) && vehicleInfo.driverEntNum == -1 ) { //mal: get the MCP to the outpost once its ready to move!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
botUcmd->actionEntityNum = vehicleInfo.entNum;
|
||
|
botUcmd->actionEntitySpawnID = vehicleInfo.spawnID;
|
||
|
|
||
|
vec = vehicleInfo.origin - botInfo->origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( 700.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
idVec3 vehicleOrigin = vehicleInfo.origin;
|
||
|
vehicleOrigin.z += VEHICLE_PATH_ORIGIN_OFFSET;
|
||
|
|
||
|
Bot_SetupMove( vehicleOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( Bot_ShouldStrafeJump( vehicleInfo.origin ) );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int avtDanger = Bot_FindClosestAVTDanger( vehicleInfo.origin, AVT_NEAR_MCP_CONSIDER_RANGE, true ); //mal: decide if we should fix the MCP, or attack a nearby AVT, that will just make our job harder anyhow.
|
||
|
|
||
|
if ( avtDanger != -1 && Bot_HasExplosives( false ) ) {
|
||
|
nbgTarget = avtDanger;
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_EngAttackAVTNearMCP;
|
||
|
} else {
|
||
|
nbgTarget = vehicleInfo.entNum;
|
||
|
nbgTargetType = VEHICLE;
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_FixProxyEntity;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_SpawnPointGoal
|
||
|
|
||
|
The bot has an action that he wants to check out - its prolly under attack.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_SpawnPointGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_SpawnPointGoal;
|
||
|
|
||
|
if ( ltgType == STEAL_SPAWN_GOAL ) {
|
||
|
lastAINode = "Grab Enemy Spawn";
|
||
|
} else {
|
||
|
lastAINode = "Grab Spawn";
|
||
|
}
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
|
||
|
botTeleporterAttempts = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_SpawnPointGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_SpawnPointGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
if ( Bot_UseTeleporter() ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
if ( Bot_UseTeleporter() ) {
|
||
|
return true;
|
||
|
}
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off, so we dont care about it now.
|
||
|
if ( Bot_UseTeleporter() ) {
|
||
|
return true;
|
||
|
}
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgType == STEAL_SPAWN_GOAL ) {
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetTeamOwner() == NOTEAM ) { //mal: its back to normal, we can go about our business.
|
||
|
if ( Bot_UseTeleporter() ) {
|
||
|
return true;
|
||
|
}
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetTeamOwner() == botInfo->team ) { //mal: its ours!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
||
|
if ( Bot_CheckForHumanInteractingWithEntity( botThreadData.botActions[ actionNum ]->GetActionSpawnControllerEntNum() ) == true ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( ltgType == STEAL_SPAWN_GOAL ) { //mal: drop a teleporter pad down a safe dist away, so we can make a quick exit after we've grabbed the spawn.
|
||
|
if ( botInfo->team == STROGG && botInfo->classType == COVERTOPS && !botInfo->hasTeleporterInWorld && !botInfo->isDisguised ) {
|
||
|
if ( ClassWeaponCharged( TELEPORTER ) && distSqr > Square( MIN_TELEPORTER_RANGE ) && distSqr < Square( MAX_TELEPORTER_RANGE ) ) {
|
||
|
botIdealWeapNum = TELEPORTER;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
ignoreWeapChange = true;
|
||
|
ltgTimer++;
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == TELEPORTER && ltgTimer > 15 ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
botUcmd->botCmds.lookDown = true;
|
||
|
ltgTimer = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( distSqr > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_GrabSpawnPoint;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_DeliverGoal
|
||
|
|
||
|
The bot has the docs/gold/parts/key/brain/etc - lets get it where it needs to be.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_DeliverGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_DeliverGoal;
|
||
|
|
||
|
ResetRandomLook();
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
lastAINode = "Deliver Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_DeliverGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_DeliverGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !ClientHasObj( botNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: the deliver point may have a delay, in which case we will wait and look around for enemies while our goal is "transmitted".
|
||
|
Bot_LookAtLocation( botThreadData.botActions[ actionNum ]->actionBBox.GetCenter(), SMOOTH_TURN );
|
||
|
ltgReached = true;
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, WALK, NULLMOVETYPE );
|
||
|
botUcmd->botCmds.activate = true;
|
||
|
botUcmd->botCmds.activateHeld = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_StealGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_StealGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_StealGoal;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgOrigin = botThreadData.botActions[ actionNum ]->actionBBox.GetCenter();
|
||
|
|
||
|
lastAINode = "Steal Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_StealGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_StealGoal() {
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ClientHasObj( botNum ) ) { //mal: we got it - lets run for it!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ltgReached == false ) {
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ltgReached = true;
|
||
|
}
|
||
|
|
||
|
Bot_SetupMove( ltgOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( SPRINT );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_RecoverDroppedGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_RecoverDroppedGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_RecoverDroppedGoal;
|
||
|
|
||
|
lastAINode = "Recover Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_RecoverDroppedGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_RecoverDroppedGoal() {
|
||
|
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( botWorld->botGoalInfo.carryableObjs[ ltgTarget ].onGround == false || botWorld->botGoalInfo.carryableObjs[ ltgTarget ].entNum == 0 ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, GROUND, NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botWorld->botGoalInfo.carryableObjs[ ltgTarget ].origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( 15.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( botWorld->botGoalInfo.carryableObjs[ ltgTarget ].origin, -1, ACTION_NULL, botWorld->botGoalInfo.carryableObjs[ ltgTarget ].areaNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( enemy == -1 ) ? Bot_ShouldStrafeJump( botWorld->botGoalInfo.carryableObjs[ ltgTarget ].origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_DeployableGoal
|
||
|
|
||
|
The bot has a location on the map he wants to drop a deployable.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_DeployableGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_DeployableGoal;
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ltgTarget = Bot_GetDeployableTypeForAction( actionNum );
|
||
|
|
||
|
if ( ltgTarget == NULL_DEPLOYABLE ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgType == DROP_PRIORITY_DEPLOYABLE_GOAL ) {
|
||
|
lastAINode = "Place Priority Deployable Goal";
|
||
|
} else {
|
||
|
lastAINode = "Place Deployable Goal";
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_DeployableGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_DeployableGoal() {
|
||
|
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->isDisguised ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( DeployableAtAction( actionNum, true ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
int vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
deployableInfo_t deployable;
|
||
|
|
||
|
if ( GetDeployableAtAction( actionNum, deployable ) ) {
|
||
|
botUcmd->actionEntityNum = deployable.entNum; //mal: let the game and obstacle avoidance know we want to interact with this entity.
|
||
|
botUcmd->actionEntitySpawnID = deployable.spawnID;
|
||
|
}
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 500.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_PlaceDeployable;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Enter_LTG_DestroyDeployable
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_DestroyDeployable() {
|
||
|
|
||
|
ltgType = DESTROY_DEPLOYABLE_GOAL;
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_DestroyDeployable;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + 60000;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
|
||
|
ltgReachedTarget = false;
|
||
|
|
||
|
rocketLauncherLockFailedTime = 0;
|
||
|
|
||
|
ltgMoveDir = ( botThreadData.random.RandomInt( 100 ) > 50 ) ? RIGHT : LEFT;
|
||
|
|
||
|
lastAINode = "Destroying Deployable";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::LTG_DestroyDeployable
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_DestroyDeployable() {
|
||
|
bool useArty = false;
|
||
|
bool wantsMove = false;
|
||
|
bool isVisible = false;
|
||
|
bool attackGoal = false;
|
||
|
float dist;
|
||
|
deployableInfo_t deployableInfo;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !GetDeployableInfo( false, ltgTarget, deployableInfo ) ) { //mal: doesn't exist anymore.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) {
|
||
|
if ( Bot_CheckForHumanInteractingWithEntity( deployableInfo.entNum ) == true ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ClientHasFireSupportInWorld( botNum ) ) { //mal: fire and forget if firesupport on its way.
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ( deployableInfo.disabled && botInfo->classType == COVERTOPS ) || deployableInfo.health < ( deployableInfo.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) { //mal: its dead ( enough ).
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_HasExplosives( false ) ) {
|
||
|
#ifdef PACKS_HAVE_NO_NADES
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
#else
|
||
|
if ( deployableInfo.enemyEntNum != botNum ) { //mal: is it safe to stand here?
|
||
|
if ( ( botInfo->classType == MEDIC && botInfo->team == STROGG ) || ( botInfo->classType == FIELDOPS && botInfo->team == GDF ) ) {
|
||
|
if ( supplySelfTime < botWorld->gameLocalInfo.time ) {
|
||
|
supplySelfTime = botWorld->gameLocalInfo.time + 5000;
|
||
|
}
|
||
|
return true; //mal: we'll wait here while we resupply ourselves.
|
||
|
}
|
||
|
} else {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
vec = deployableInfo.origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
idVec3 deployableOrigin = deployableInfo.origin;
|
||
|
deployableOrigin.z += DEPLOYABLE_ORIGIN_OFFSET;
|
||
|
|
||
|
trace_t tr;
|
||
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, deployableOrigin, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
|
||
|
|
||
|
if ( tr.fraction == 1.0f || tr.c.entityNum == deployableInfo.entNum ) {
|
||
|
isVisible = true;
|
||
|
}
|
||
|
|
||
|
if ( isVisible ) {
|
||
|
if ( botInfo->classType == FIELDOPS && LocationVis2Sky( deployableInfo.origin ) && ClassWeaponCharged( AIRCAN ) && Bot_HasWorkingDeployable() && deployableInfo.type != AIT && !Bot_EnemyAITInArea( deployableInfo.origin ) ) {
|
||
|
attackGoal = true;
|
||
|
} else if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo && dist < Square( WEAPON_LOCK_DIST ) ) {
|
||
|
attackGoal = true;
|
||
|
} else if ( dist < Square( ATTACK_APT_DIST ) ) {
|
||
|
attackGoal = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !attackGoal ) {
|
||
|
botUcmd->actionEntityNum = deployableInfo.entNum;
|
||
|
botUcmd->actionEntitySpawnID = deployableInfo.spawnID;
|
||
|
|
||
|
Bot_SetupMove( deployableOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) ) ? SPRINT : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
if ( dist < Square( ATTACK_APT_DIST ) ) {
|
||
|
if ( botInfo->isDisguised ) {
|
||
|
botUcmd->botCmds.dropDisguise = true;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->classType == FIELDOPS && LocationVis2Sky( deployableInfo.origin ) && ClassWeaponCharged( AIRCAN ) ) {
|
||
|
botIdealWeapNum = AIRCAN;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
} else if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo ) {
|
||
|
botIdealWeapSlot = GUN;
|
||
|
} else if ( botInfo->classType == COVERTOPS && botInfo->team == GDF && ClassWeaponCharged( THIRD_EYE ) ) {
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
botIdealWeapNum = THIRD_EYE;
|
||
|
} else if ( botInfo->weapInfo.hasNadeAmmo ) {
|
||
|
botIdealWeapSlot = NADE;
|
||
|
botIdealWeapNum = NULL_WEAP;
|
||
|
} else {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botInfo->classType == FIELDOPS && ClassWeaponCharged( AIRCAN ) && Bot_HasWorkingDeployable() && deployableInfo.type != AIT && !Bot_EnemyAITInArea( deployableInfo.origin ) ) {
|
||
|
useArty = true;
|
||
|
} else if ( botInfo->classType == SOLDIER && botInfo->weapInfo.primaryWeapon == ROCKET && botInfo->weapInfo.primaryWeapHasAmmo ) {
|
||
|
botIdealWeapSlot = GUN;
|
||
|
} else {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( deployableInfo.enemyEntNum == botNum && !useArty ) {
|
||
|
wantsMove = true;
|
||
|
}
|
||
|
|
||
|
if ( useArty ) {
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
|
||
|
vec = deployableInfo.origin - botInfo->origin;
|
||
|
idVec3 org = deployableInfo.origin;
|
||
|
|
||
|
if ( vec.z < 20.0f ) {
|
||
|
org.z += 16.0f;
|
||
|
}
|
||
|
|
||
|
Bot_LookAtLocation( org, SMOOTH_TURN );
|
||
|
botIdealWeapNum = BINOCS;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
|
||
|
if ( ltgTimer == 0 ) {
|
||
|
ltgTimer = botWorld->gameLocalInfo.time + ARTY_LOCKON_TIME;
|
||
|
}
|
||
|
|
||
|
if ( ltgTimer > botWorld->gameLocalInfo.time ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == BINOCS ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
botUcmd->botCmds.constantFire = true;
|
||
|
return true;
|
||
|
}
|
||
|
} else if ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) {
|
||
|
bool fastNade = true;
|
||
|
|
||
|
if ( dist > Square( GRENADE_THROW_MAXDIST ) ) {
|
||
|
Bot_SetupMove( deployableOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( SPRINT );
|
||
|
fastNade = false;
|
||
|
}
|
||
|
|
||
|
idVec3 deployableOrigin = deployableInfo.origin;
|
||
|
deployableOrigin.z += 16.0f;
|
||
|
Bot_ThrowGrenade( deployableOrigin, fastNade );
|
||
|
} else if ( botInfo->weapInfo.weapon == AIRCAN ) {
|
||
|
Bot_UseCannister( AIRCAN, deployableInfo.origin );
|
||
|
} else if ( botInfo->weapInfo.weapon == THIRD_EYE ) {
|
||
|
if ( !ClassWeaponCharged( THIRD_EYE ) ) {
|
||
|
if ( !ltgReachedTarget ) { //mal: if just got here, find a nearby action to move towards, just to get us out of here.
|
||
|
actionNum = Bot_FindNearbySafeActionToMoveToward( deployableOrigin, THIRD_EYE_SAFE_RANGE );
|
||
|
|
||
|
if ( actionNum == ACTION_NULL ) { //mal: OH NOES!1 Panic and lets get out of here.
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ltgReachedTarget = true;
|
||
|
}
|
||
|
|
||
|
if ( dist >= Square( THIRD_EYE_SAFE_RANGE ) ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
Bot_LookAtLocation( deployableOrigin, SMOOTH_TURN );
|
||
|
} else {
|
||
|
Bot_SetupMove( botThreadData.botActions[ actionNum ]->GetActionOrigin(), -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( SPRINT );
|
||
|
}
|
||
|
} else {
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
Bot_LookAtLocation( deployableOrigin, SMOOTH_TURN );
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
}
|
||
|
} else if ( botIdealWeapSlot == GUN || botIdealWeapSlot == SIDEARM ) {
|
||
|
if ( dist < Square( 500.0f ) ) { // too close, back up some!
|
||
|
if ( Bot_CanMove( BACK, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, RUN, NULLMOVETYPE );
|
||
|
}
|
||
|
|
||
|
Bot_LookAtLocation( deployableOrigin, SMOOTH_TURN );
|
||
|
} else {
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
Bot_LookAtLocation( deployableOrigin, SMOOTH_TURN );
|
||
|
}
|
||
|
|
||
|
if ( ltgReached == false ) {
|
||
|
rocketLauncherLockFailedTime = botWorld->gameLocalInfo.time + 5000;
|
||
|
ltgReached = true;
|
||
|
} else {
|
||
|
botUcmd->botCmds.altAttackOn = true;
|
||
|
}
|
||
|
|
||
|
if ( rocketLauncherLockFailedTime < botWorld->gameLocalInfo.time && botInfo->targetLockEntNum != deployableInfo.entNum ) { //mal: can't get a lock for some reason - ignore this deployable.
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->targetLocked != false ) {
|
||
|
if ( botInfo->targetLockEntNum == deployableInfo.entNum ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
rocketLauncherLockFailedTime = botWorld->gameLocalInfo.time + 10000;
|
||
|
} else {
|
||
|
botUcmd->botCmds.altAttackOn = false;
|
||
|
ltgReached = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( wantsMove ) {
|
||
|
if ( dist < Square( ATTACK_APT_DIST ) ) {
|
||
|
vec = deployableOrigin;
|
||
|
vec += ( -350.0f * deployableInfo.axis[ 0 ] );
|
||
|
|
||
|
botUcmd->actionEntityNum = deployableInfo.entNum;
|
||
|
botUcmd->actionEntitySpawnID = deployableInfo.spawnID;
|
||
|
|
||
|
Bot_SetupMove( vec, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreDeployable( deployableInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) ) ? SPRINT : RUN );
|
||
|
} else {
|
||
|
if ( ltgMoveDir == NULL_DIR ) {
|
||
|
ltgMoveDir = ( botThreadData.random.RandomInt( 100 ) > 50 ) ? RIGHT : LEFT;
|
||
|
}
|
||
|
|
||
|
if ( Bot_CanMove( ltgMoveDir, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, RUN, ( ltgMoveDir == RIGHT ) ? RANDOM_JUMP_RIGHT : RANDOM_JUMP_LEFT );
|
||
|
} else {
|
||
|
ltgMoveDir = NULL_DIR;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_FireSupportGoal
|
||
|
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_FireSupportGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_FireSupportGoal;
|
||
|
|
||
|
ltgType = FIRESUPPORT_CAMP_GOAL;
|
||
|
|
||
|
stayInPosition = false;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
|
||
|
ltgExit = false;
|
||
|
|
||
|
//mal: higher skill bots are more likely to respond to heard sounds, and go after the person making them, then lower skilled bots.
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
|
||
|
stayInPosition = true;
|
||
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 70 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lastAINode = "FireSupport Camp Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_FireSupportGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_FireSupportGoal() {
|
||
|
int vehicleNum;
|
||
|
float dist;
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ClientHasFireSupportInWorld( botNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
deployableInfo_t deployableInfo;
|
||
|
|
||
|
if ( !GetDeployableInfo( true, -1, deployableInfo ) ) { //mal: doesn't exist anymore.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( deployableInfo.health < ( deployableInfo.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->blindFire == true && ltgExit == false ) {
|
||
|
if ( botWorld->gameLocalInfo.time + 10000 > ltgTime && ClassWeaponCharged( AIRCAN ) ) {
|
||
|
ltgExit = true;
|
||
|
int k = 0;
|
||
|
int i;
|
||
|
int linkedTargets[ MAX_LINKACTIONS ];
|
||
|
|
||
|
for( i = 0; i < MAX_LINKACTIONS; i++ ) {
|
||
|
if ( botThreadData.botActions[ actionNum ]->actionTargets[ i ].inuse != false ) {
|
||
|
linkedTargets[ k++ ] = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i = botThreadData.random.RandomInt( k );
|
||
|
ltgOrigin = botThreadData.botActions[ actionNum ]->actionTargets[ i ].origin;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( actionNumInsideDanger == actionNum ) { //mal: if our action is encased inside a danger ( landmine ), and we're not destroying it, must not be able to, so leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgUseVehicle ) {
|
||
|
vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ actionNum ]->GetActionVehicleFlags( botInfo->team ), NULL_VEHICLE_FLAGS, true );
|
||
|
|
||
|
if ( vehicleNum == -1 ) {
|
||
|
ltgUseVehicle = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_GetIntoVehicle( vehicleNum ) ) {
|
||
|
ltgUseVehicle = false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ltgExit == false && ( botThreadData.random.RandomInt( 100 ) > 98 || ltgReached == false ) ) {
|
||
|
int k = 0;
|
||
|
int i;
|
||
|
int linkedTargets[ MAX_LINKACTIONS ];
|
||
|
|
||
|
for( i = 0; i < MAX_LINKACTIONS; i++ ) {
|
||
|
if ( botThreadData.botActions[ actionNum ]->actionTargets[ i ].inuse != false ) {
|
||
|
linkedTargets[ k++ ] = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i = botThreadData.random.RandomInt( k );
|
||
|
|
||
|
Bot_LookAtLocation( botThreadData.botActions[ actionNum ]->actionTargets[ i ].origin, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
ltgOrigin = botThreadData.botActions[ actionNum ]->actionTargets[ i ].origin;
|
||
|
}
|
||
|
|
||
|
if ( ltgReached == false ) {
|
||
|
ltgTime = botWorld->gameLocalInfo.time + ( botThreadData.botActions[ actionNum ]->actionTimeInSeconds * 1000 );
|
||
|
ltgReached = true;
|
||
|
}
|
||
|
|
||
|
int enemiesInArea = ClientsInArea( botNum, ltgOrigin, 1700.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
|
||
|
int friendsInArea = ClientsInArea( botNum, ltgOrigin, 900.0f, botInfo->team , NOCLASS, false, false, false, false, false );
|
||
|
|
||
|
bool deployablesInArea = Bot_CheckTeamHasDeployableTypeNearLocation( ( botInfo->team == GDF ) ? STROGG : GDF, NULL_DEPLOYABLE, ltgOrigin, 1700.0f );
|
||
|
|
||
|
if ( ( enemiesInArea == 0 && deployablesInArea == false && ltgExit == false ) || friendsInArea > 2 ) { //mal: no target of value in this area, or too many friendlies, so dont bother.
|
||
|
ltgTimer = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
botIdealWeapNum = BINOCS;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
Bot_LookAtLocation( ltgOrigin, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
|
||
|
if ( ltgTimer == 0 ) {
|
||
|
ltgTimer = botWorld->gameLocalInfo.time + ARTY_LOCKON_TIME;
|
||
|
}
|
||
|
|
||
|
if ( ltgTimer > botWorld->gameLocalInfo.time ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == BINOCS ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
botUcmd->botCmds.constantFire = true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_HackDeployableGoal
|
||
|
|
||
|
The bot has a deployable on the map he wants to hack.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_HackDeployableGoal() {
|
||
|
|
||
|
ltgType = HACK_DEPLOYABLE_GOAL;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME;
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_HackDeployableGoal;
|
||
|
|
||
|
lastAINode = "Hack Deployable Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_HackDeployableGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_HackDeployableGoal() {
|
||
|
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
deployableInfo_t deployable;
|
||
|
|
||
|
if ( !GetDeployableInfo( false, ltgTarget, deployable ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( deployable.disabled || deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int travelTime;
|
||
|
|
||
|
if ( !Bot_LocationIsReachable( false, deployable.origin, travelTime ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
botUcmd->actionEntityNum = ltgTarget; //mal: let the game and obstacle avoidance know we want to interact with this entity.
|
||
|
botUcmd->actionEntitySpawnID = deployable.spawnID;
|
||
|
|
||
|
idVec3 vec = deployable.origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( 512.0f ) || botInfo->onLadder ) {
|
||
|
idVec3 moveGoal = deployable.origin;
|
||
|
moveGoal.z += DEPLOYABLE_PATH_ORIGIN_OFFSET;
|
||
|
|
||
|
Bot_SetupMove( moveGoal, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreDeployable( deployable.entNum, DEPLOYABLE_IGNORE_TIME );//mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( deployable.origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
nbgTarget = ltgTarget;
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_HackDeployable;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Enter_LTG_MG_CampGoal
|
||
|
|
||
|
The bot has a MG nest on the map he wants to man.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_MG_CampGoal() {
|
||
|
|
||
|
ltgType = MG_CAMP_GOAL;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME;
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_MG_CampGoal;
|
||
|
|
||
|
lastAINode = "MG Camp Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::LTG_MG_CampGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_MG_CampGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->isDisguised ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->ActionIsActive() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetActionState() == ACTION_STATE_GUN_DESTROYED ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = botThreadData.botActions[ actionNum ]->GetActionOrigin() - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( 150.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME );//mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->GetActionOrigin() ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_MG_Camp;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::Enter_LTG_FlyerHiveGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_FlyerHiveGoal() {
|
||
|
|
||
|
ltgType = FLYER_HIVE_GOAL;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME;
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_FlyerHiveGoal;
|
||
|
|
||
|
lastAINode = "Flyer Hive Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI::LTG_FlyerHiveGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_FlyerHiveGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->ActionIsActive() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = botThreadData.botActions[ actionNum ]->GetActionOrigin() - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( 150.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME );//mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->GetActionOrigin() ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->actionTargets[ 0 ].inuse == false ) {
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
botThreadData.Warning("Map Error! No action target for the bot to look at! The flyer hive launch entity needs exactly 1 action target!" );
|
||
|
}
|
||
|
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_LookAtLocation( botThreadData.botActions[ actionNum ]->actionTargets[ 0 ].origin, INSTANT_TURN );
|
||
|
|
||
|
int i;
|
||
|
idList< int > hiveTargets; //mal: look for a target for this hive to fly to.
|
||
|
|
||
|
for( i = 0; i < botThreadData.botActions.Num(); i++ ) {
|
||
|
if ( !botThreadData.botActions[ i ]->ActionIsActive() ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ i ]->ActionIsValid() ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_FLYER_HIVE_TARGET ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
hiveTargets.Append( i );
|
||
|
}
|
||
|
|
||
|
if ( hiveTargets.Num() == 0 ) {
|
||
|
if ( botThreadData.AllowDebugData() ) {
|
||
|
botThreadData.Warning("Map Error! No valid flyer hive target found for flyer hive goal!" );
|
||
|
}
|
||
|
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( hiveTargets.Num() == 1 ) {
|
||
|
actionNum = hiveTargets[ 0 ];
|
||
|
} else {
|
||
|
int j = botThreadData.random.RandomInt( hiveTargets.Num() );
|
||
|
actionNum = hiveTargets[ j ];
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_FlyerHive;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_RepairGunGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_RepairGunGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_RepairGunGoal;
|
||
|
|
||
|
ltgType = MG_REPAIR_GOAL;
|
||
|
|
||
|
lastAINode = "Repair Gun Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_RepairGunGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_RepairGunGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetActionState() == ACTION_STATE_GUN_READY ) { //mal: someone else fixed it.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
float actionRadius = ( botThreadData.botActions[ actionNum ]->radius <= 40.0f ) ? 50.0f : botThreadData.botActions[ actionNum ]->radius; //mal: hack to override bad radius values. Less then 40 is usually bad.
|
||
|
float dist = vec.LengthSqr();
|
||
|
|
||
|
if ( dist > Square( actionRadius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( dist > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: reached our action - pop into the short term goal AI node to execute it!
|
||
|
ROOT_AI_NODE = &idBotAI::Run_NBG_Node;
|
||
|
NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_FixGun;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_EnterVehicleGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_EnterVehicleGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_EnterVehicleGoal;
|
||
|
|
||
|
ltgType = ENTER_VEHICLE_GOAL;
|
||
|
|
||
|
lastAINode = "Enter Vehicle Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_EnterVehicleGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_EnterVehicleGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_IgnoreVehicle( ltgTarget, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
proxyInfo_t vehicleInfo;
|
||
|
GetVehicleInfo( ltgTarget, vehicleInfo );
|
||
|
|
||
|
if ( vehicleInfo.entNum == 0 || !VehicleIsValid( vehicleInfo.entNum ) ) {
|
||
|
Bot_IgnoreVehicle( vehicleInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idVec3 vehicleOrigin = vehicleInfo.origin;
|
||
|
vehicleOrigin.z += VEHICLE_PATH_ORIGIN_OFFSET;
|
||
|
|
||
|
botUcmd->actionEntityNum = vehicleInfo.entNum;
|
||
|
botUcmd->actionEntitySpawnID = vehicleInfo.spawnID;
|
||
|
|
||
|
Bot_SetupMove( vehicleOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreVehicle( vehicleInfo.entNum, 15000 );
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( Bot_ShouldStrafeJump( vehicleOrigin ) );
|
||
|
|
||
|
idVec3 vec = vehicleInfo.origin - botInfo->origin;
|
||
|
|
||
|
//mal: if we're fairly close, start sending the vehicle use cmd
|
||
|
if ( vec.LengthSqr() < Square( 350.0f ) ) {
|
||
|
Bot_LookAtLocation( vehicleInfo.origin, SMOOTH_TURN );
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
botUcmd->botCmds.enterVehicle = true;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_ActorEscortPlayerToGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_ActorEscortPlayerToGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_ActorEscortPlayerToGoal;
|
||
|
|
||
|
ltgType = ACTOR_GOAL;
|
||
|
|
||
|
lastAINode = "Actor Goal";
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgTimer = 0;
|
||
|
|
||
|
ltgCounter = 0;
|
||
|
|
||
|
ltgTime = 0;
|
||
|
|
||
|
ltgReachedTarget = false;
|
||
|
|
||
|
if ( botThreadData.actorMissionInfo.hasBriefedPlayer ) {
|
||
|
ltgChat = false;
|
||
|
} else {
|
||
|
ltgChat = true;
|
||
|
|
||
|
if ( !botThreadData.actorMissionInfo.hasEnteredActorNode ) {
|
||
|
ltgCounter = botWorld->gameLocalInfo.time + botThreadData.actorMissionInfo.goalPauseTime;
|
||
|
botUcmd->botCmds.actorHasEnteredActorNode = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_ActorEscortPlayerToGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_ActorEscortPlayerToGoal() {
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetObjForTeam( botInfo->team ) == ACTION_DENY_SPAWNPOINT && botThreadData.botActions[ actionNum ]->GetTeamOwner() == NOTEAM ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetObjForTeam( botInfo->team ) == ACTION_FORWARD_SPAWN && botThreadData.botActions[ actionNum ]->GetTeamOwner() == botInfo->team ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
botUcmd->botCmds.actorSurrenderStatus = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgCounter > botWorld->gameLocalInfo.time ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 94 ) {
|
||
|
idVec3 vec;
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& player = botWorld->clientInfo[ ltgTarget ];
|
||
|
|
||
|
if ( ltgTime > botWorld->gameLocalInfo.time ) {
|
||
|
Bot_LookAtLocation( player.viewOrigin, SMOOTH_TURN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
||
|
Bot_ExitAINode();
|
||
|
botUcmd->botCmds.actorSurrenderStatus = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( player.inLimbo || ( !player.hasGroundContact && player.invulnerableEndTime > botWorld->gameLocalInfo.time ) ) { //mal: if our player gets killed, don't do anything til he respawns.
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 95 ) {
|
||
|
idVec3 vec;
|
||
|
Bot_RandomLook( vec );
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN );
|
||
|
}
|
||
|
|
||
|
ltgReached = false;
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( player.health <= 0 || ( player.revived && player.spawnTime + 1000 > botWorld->gameLocalInfo.time ) ) { //mal: if our player died, or was just revived, just exit for now.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = player.origin - botInfo->origin;
|
||
|
vec.z = 0.0f;
|
||
|
float botDistToPlayerSqr = vec.LengthSqr();
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - player.origin;
|
||
|
vec.z = 0.0f;
|
||
|
float playerDistToGoalSqr = vec.LengthSqr();
|
||
|
|
||
|
vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
vec.z = 0.0f;
|
||
|
float botDistToGoalSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetObjForTeam( botInfo->team ) == ACTION_HE_CHARGE && ClientHasChargeInWorld( ltgTarget, true, actionNum ) ) {
|
||
|
if ( botDistToPlayerSqr > Square( 175.0f ) || botInfo->onLadder ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( SPRINT );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
botMoveFlags_t defaultMove = NULLMOVEFLAG;
|
||
|
|
||
|
if ( player.posture == IS_CROUCHED ) {
|
||
|
defaultMove = CROUCH;
|
||
|
}
|
||
|
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 94 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
} //mal: cover our player while we wait down the charge counter.
|
||
|
|
||
|
if ( botThreadData.botActions[ actionNum ]->GetObjForTeam( botInfo->team ) == ACTION_HE_CHARGE && ClientHasChargeInWorld( ltgTarget, false, actionNum, true ) ) {
|
||
|
plantedChargeInfo_t bombInfo;
|
||
|
FindChargeInWorld( actionNum, bombInfo, ARM );
|
||
|
|
||
|
vec = bombInfo.origin - player.origin;
|
||
|
|
||
|
if ( vec.LengthSqr() > Square( 200.0f ) && botThreadData.random.RandomInt( 1000 ) > 900 ) {
|
||
|
Bot_AddDelayedChat( botNum, GOT_YOUR_BACK, 1 ); //mal: stay away from that bomb!
|
||
|
}
|
||
|
} //mal: woah buddy! Get back there and arm your charge!
|
||
|
|
||
|
if ( ltgReached == false ) {
|
||
|
if ( ltgMoveTime < botWorld->gameLocalInfo.time && ( botDistToPlayerSqr > Square( 1000.0f ) || ltgReachedTarget == false || botInfo->onLadder ) ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
if ( ltgReachedTarget == false ) { // was 150 below
|
||
|
if ( botDistToPlayerSqr < Square( 200.0f ) && player.hasGroundContact && botThreadData.Nav_IsDirectPath( AAS_PLAYER, botInfo->team, botInfo->areaNum, botInfo->origin, player.origin ) ) {
|
||
|
ltgReachedTarget = true;
|
||
|
if ( ltgChat == true ) {
|
||
|
botUcmd->botCmds.actorBriefedPlayer = true; //mal: let the game know we've briefed the player, so we don't do it again.
|
||
|
ltgPauseTime = botWorld->gameLocalInfo.time + ( 1000 * botThreadData.actorMissionInfo.chatPauseTime );
|
||
|
timeTilAttackEnemy = -1;
|
||
|
enemy = -1;
|
||
|
} else {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( botDistToPlayerSqr < Square( 250.0f ) && player.xySpeed < RUNNING_SPEED ) ? RUN : SPRINT );
|
||
|
return true;
|
||
|
} else if ( ltgMoveTime > botWorld->gameLocalInfo.time || ( botDistToPlayerSqr > Square( 900.0f ) && playerDistToGoalSqr > botDistToGoalSqr ) ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 98 ) {
|
||
|
}
|
||
|
Bot_LookAtLocation( player.viewOrigin, SMOOTH_TURN );
|
||
|
|
||
|
if ( ltgMoveTime == 0 ) {
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1500;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ltgPauseTime > botWorld->gameLocalInfo.time ) {
|
||
|
Bot_LookAtLocation( player.viewOrigin, SMOOTH_TURN );
|
||
|
botUcmd->botCmds.actorIsBriefingPlayer = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botDistToGoalSqr > Square( 500.0f ) || botInfo->onLadder ) { // is 500 too close, or too far?
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// botMoveFlags_t botMoveFlags = RUN;
|
||
|
|
||
|
botMoveFlags_t botMoveFlags = SPRINT;
|
||
|
|
||
|
if ( playerDistToGoalSqr < botDistToGoalSqr ) {
|
||
|
botMoveFlags = SPRINT;
|
||
|
}
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
|
||
|
// if ( botDistToPlayerSqr > Square( 500.0f ) && playerDistToGoalSqr > botDistToGoalSqr ) {
|
||
|
// botMoveFlags = RUN;
|
||
|
// } // else if ( ( botDistToPlayerSqr < Square( 350.0f ) && player.xySpeed >= RUNNING_SPEED ) || playerDistToGoalSqr < botDistToGoalSqr ) {
|
||
|
// botMoveFlags = SPRINT;
|
||
|
// }
|
||
|
|
||
|
Bot_MoveAlongPath( botMoveFlags );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//mal: from here on down, we've reached the goal, and are merely defending the player while he does his work....
|
||
|
|
||
|
int playerAttacker = CheckClientAttacker( ltgTarget, 3 ); //mal: if our friend is under attack, move towards him to protect him.
|
||
|
|
||
|
if ( playerAttacker != -1 && botDistToPlayerSqr > Square( 75.0f ) ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ltgReached == false ) {
|
||
|
ltgReached = true;
|
||
|
ltgTimer = botWorld->gameLocalInfo.time + 5000;
|
||
|
}
|
||
|
|
||
|
if ( ltgTimer > botWorld->gameLocalInfo.time ) {
|
||
|
Bot_LookAtEntity( ltgTarget, SMOOTH_TURN ); //look at our friend when first reach goal
|
||
|
} else if ( botThreadData.random.RandomInt( 100 ) > 94 ) {
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_ActorGiveFinalBriefingToPlayer
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_ActorGiveFinalBriefingToPlayer() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_ActorGiveFinalBriefingToPlayer;
|
||
|
|
||
|
ltgType = ACTOR_GOAL;
|
||
|
|
||
|
lastAINode = "Last Actor Goal";
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgCounter = botWorld->gameLocalInfo.time + botThreadData.actorMissionInfo.goalPauseTime;
|
||
|
|
||
|
ltgPauseTime = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_ActorGiveFinalBriefingToPlayer
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_ActorGiveFinalBriefingToPlayer() {
|
||
|
if ( !ClientIsValid( ltgTarget, ltgTargetSpawnID ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
botUcmd->botCmds.actorSurrenderStatus = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( ltgCounter > botWorld->gameLocalInfo.time ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 94 ) {
|
||
|
idVec3 vec;
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const clientInfo_t& player = botWorld->clientInfo[ ltgTarget ];
|
||
|
|
||
|
if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) {
|
||
|
Bot_ExitAINode();
|
||
|
botUcmd->botCmds.actorSurrenderStatus = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( player.inLimbo || ( !player.hasGroundContact && player.invulnerableEndTime > botWorld->gameLocalInfo.time ) ) { //mal: if our player gets killed, don't do anything til he respawns.
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 95 ) {
|
||
|
idVec3 vec;
|
||
|
Bot_RandomLook( vec );
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN );
|
||
|
}
|
||
|
|
||
|
ltgReached = false;
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, CROUCH, NULLMOVETYPE );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = player.origin - botInfo->origin;
|
||
|
vec.z = 0.0f;
|
||
|
float botDistToPlayerSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( !ltgReached ) {
|
||
|
if ( botDistToPlayerSqr > Square( 150.0f ) || botInfo->onLadder ) {
|
||
|
Bot_SetupMove( vec3_zero, ltgTarget, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( SPRINT );
|
||
|
return true;
|
||
|
} else {
|
||
|
ltgReached = true;
|
||
|
botUcmd->botCmds.actorBriefedPlayer = true; //mal: let the game know we've briefed the player, so we don't do it again.
|
||
|
ltgPauseTime = botWorld->gameLocalInfo.time + ( 1000 * botThreadData.actorMissionInfo.chatPauseTime );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ltgPauseTime > botWorld->gameLocalInfo.time ) {
|
||
|
Bot_LookAtLocation( player.viewOrigin, SMOOTH_TURN );
|
||
|
botUcmd->botCmds.actorIsBriefingPlayer = true;
|
||
|
if ( botDistToPlayerSqr < Square( 100.0f ) ) { // too close, back up some!
|
||
|
if ( Bot_CanMove( BACK, 75.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE );
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
botUcmd->botCmds.actorSurrenderStatus = true;
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_ThirdEyeCameraGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_ThirdEyeCameraGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_ThirdEyeCameraGoal;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + 60000;
|
||
|
|
||
|
ltgType = THIRD_EYE_GOAL;
|
||
|
|
||
|
lastAINode = "Third Eye Goal";
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgPauseTime = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_ThirdEyeCameraGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_ThirdEyeCameraGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( actionNumInsideDanger == actionNum ) { //mal: if our action is encased inside a danger ( landmine ), and we're not destroying it, must not be able to, so leave.
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !ClassWeaponCharged( THIRD_EYE ) || botInfo->isDisguised ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( botThreadData.botActions[ actionNum ]->radius ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !ltgReached ) {
|
||
|
ltgReached = true;
|
||
|
ltgPauseTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
}
|
||
|
|
||
|
botIdealWeapNum = THIRD_EYE;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
ignoreWeapChange = true;
|
||
|
|
||
|
Bot_LookAtLocation( botThreadData.botActions[ actionNum ]->actionBBox.GetCenter(), SMOOTH_TURN );
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == THIRD_EYE && ltgPauseTime < botWorld->gameLocalInfo.time ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_ProtectCharge
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_ProtectCharge() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_ProtectCharge;
|
||
|
|
||
|
ltgTime = botWorld->gameLocalInfo.time + 40000;
|
||
|
|
||
|
ltgType = PROTECT_CHARGE;
|
||
|
|
||
|
lastAINode = "Protect Charge";
|
||
|
|
||
|
ltgDist = 300.0f + ( ( float ) botThreadData.random.RandomInt( 500 ) );
|
||
|
|
||
|
ltgPosture = ( botThreadData.random.RandomInt( 100 ) > 50 ) ? CROUCH : WALK;
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgUseMine = true;
|
||
|
|
||
|
ltgChat = false; //mal: track whether or not we should use airstrike on charge.
|
||
|
|
||
|
if ( botInfo->classType == FIELDOPS ) {
|
||
|
plantedChargeInfo_t charge;
|
||
|
|
||
|
if ( FindChargeBySpawnID( ltgTargetSpawnID, charge ) ) {
|
||
|
if ( LocationVis2Sky( charge.origin ) ) {
|
||
|
ltgChat = true;
|
||
|
ltgDist = 900.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_ProtectCharge
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_ProtectCharge() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
ignorePlantedChargeTime = botWorld->gameLocalInfo.time + IGNORE_PLANTED_CHARGE_TIME;
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
plantedChargeInfo_t charge;
|
||
|
|
||
|
if ( !FindChargeBySpawnID( ltgTargetSpawnID, charge ) ) {
|
||
|
ignorePlantedChargeTime = botWorld->gameLocalInfo.time + IGNORE_PLANTED_CHARGE_TIME;
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool isVisible = false;
|
||
|
idVec3 vec = charge.origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
int enemyEngsInArea = ClientsInArea( botNum, charge.origin, 300.0f, ( botInfo->team == GDF ) ? STROGG : GDF, ENGINEER, false, false, false, false, false );
|
||
|
|
||
|
if ( enemyEngsInArea > 0 ) { //mal: do this in case we aren't visible to the charge we're protecting, and we don't just sit there and wait while its defused.
|
||
|
ltgDist = 150.0f;
|
||
|
}
|
||
|
|
||
|
trace_t tr;
|
||
|
botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, charge.origin, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum ) );
|
||
|
|
||
|
if ( tr.fraction == 1.0f || tr.c.entityNum == charge.entNum ) {
|
||
|
isVisible = true;
|
||
|
}
|
||
|
|
||
|
if ( distSqr > Square( ltgDist ) || botInfo->onLadder || botInfo->inWater || !isVisible ) {
|
||
|
|
||
|
Bot_SetupMove( charge.origin, -1, ACTION_NULL, charge.areaNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
ignorePlantedChargeTime = botWorld->gameLocalInfo.time + IGNORE_PLANTED_CHARGE_TIME;
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( charge.origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( botInfo->classType == ENGINEER && botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY && ltgUseMine && botInfo->abilities.selfArmingMines ) {
|
||
|
if ( !TeamMineInArea( charge.origin, 300.0f ) ) {
|
||
|
if ( ltgReached == false ) { //mal: store off our camp location for later.
|
||
|
ltgOrigin = botInfo->origin;
|
||
|
ltgReached = true;
|
||
|
}
|
||
|
|
||
|
if ( distSqr > Square( 100.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( charge.origin, -1, ACTION_NULL, charge.areaNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
ltgUseMine = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Bot_LookAtLocation( charge.origin, INSTANT_TURN );
|
||
|
ignoreWeapChange = true;
|
||
|
botIdealWeapSlot = NO_WEAPON;
|
||
|
botIdealWeapNum = LANDMINE;
|
||
|
|
||
|
if ( botInfo->weapInfo.weapon == LANDMINE ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
|
||
|
botUcmd->botCmds.attack = true;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
vec = ltgOrigin - botInfo->origin;
|
||
|
if ( vec.LengthSqr() > Square( 75.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( ltgOrigin, -1, ACTION_NULL );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
ignorePlantedChargeTime = botWorld->gameLocalInfo.time + IGNORE_PLANTED_CHARGE_TIME;
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Bot_MoveAlongPath( RUN );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
} else if ( botInfo->classType == FIELDOPS && botWorld->gameLocalInfo.botSkill > BOT_SKILL_EASY && ltgChat && ClassWeaponCharged( AIRCAN ) ) {
|
||
|
|
||
|
int enemiesInArea = ClientsInArea( botNum, charge.origin, 1200.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false );
|
||
|
|
||
|
if ( enemiesInArea > 0 ) {
|
||
|
if ( LocationVis2Sky( botInfo->origin ) ) {
|
||
|
ignoreWeapChange = true;
|
||
|
Bot_UseCannister( AIRCAN, charge.origin );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 93 ) {
|
||
|
idVec3 vec;
|
||
|
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, ltgPosture, NULLMOVETYPE );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::Enter_LTG_PatrolDeliverGoal
|
||
|
|
||
|
The bot wants to roam to the deliver action, just to make sure its clear and hold it for the carrier.
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::Enter_LTG_PatrolDeliverGoal() {
|
||
|
|
||
|
LTG_AI_SUB_NODE = &idBotAI::LTG_PatrolDeliverGoal;
|
||
|
|
||
|
ltgType = PATROL_DELIVER_GOAL;
|
||
|
|
||
|
stayInPosition = false;
|
||
|
|
||
|
//mal: higher skill bots are more likely to respond to heard sounds, and go after the person making them, then lower skilled bots.
|
||
|
if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) {
|
||
|
stayInPosition = true;
|
||
|
} else if ( botThreadData.GetBotSkill() == BOT_SKILL_NORMAL ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 30 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 70 ) {
|
||
|
stayInPosition = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ltgReached = false;
|
||
|
|
||
|
ltgMoveTime = 0;
|
||
|
|
||
|
ltgTryMoveCounter = 0;
|
||
|
|
||
|
lastAINode = "Patrol Deliver Goal";
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
idBotAI_LTG::LTG_PatrolDeliverGoal
|
||
|
================
|
||
|
*/
|
||
|
bool idBotAI::LTG_PatrolDeliverGoal() {
|
||
|
if ( ltgTime < botWorld->gameLocalInfo.time ) { //mal: times up - leave!
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !Bot_CheckActionIsValid( actionNum ) ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( !botThreadData.botActions[ actionNum ]->active ) { //mal: action got turned off
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int matesInArea = ClientsInArea( botNum, botThreadData.botActions[ actionNum ]->GetActionOrigin(), PATROL_DELIVER_TEST_DIST, botInfo->team, NOCLASS, false, false, false, true, true );
|
||
|
|
||
|
if ( matesInArea > MAX_PATROL_DELIVER_CLIENTS ) {
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
} //mal: enough clients there camping
|
||
|
|
||
|
if ( ltgMoveTime < botWorld->gameLocalInfo.time ) {
|
||
|
ltgTryMoveCounter = 0;
|
||
|
}
|
||
|
|
||
|
idVec3 vec = botThreadData.botActions[ actionNum ]->origin - botInfo->origin;
|
||
|
float distSqr = vec.LengthSqr();
|
||
|
|
||
|
if ( distSqr > Square( botThreadData.botActions[ actionNum ]->radius + 75.0f ) || botInfo->onLadder ) {
|
||
|
|
||
|
Bot_SetupMove( vec3_zero, -1, actionNum );
|
||
|
|
||
|
if ( MoveIsInvalid() ) {
|
||
|
Bot_IgnoreAction( actionNum, ACTION_IGNORE_TIME ); //mal: no valid path to this action for some reason - ignore it for a while
|
||
|
Bot_ExitAINode();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ltgReached = false;
|
||
|
Bot_MoveAlongPath( ( distSqr > Square( 100.0f ) && enemy == -1 ) ? Bot_ShouldStrafeJump( botThreadData.botActions[ actionNum ]->origin ) : RUN );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
reachedPatrolPointTime = botWorld->gameLocalInfo.time;
|
||
|
|
||
|
int blockedClient = Bot_CheckBlockingOtherClients( -1, 75.0f );
|
||
|
botMoveFlags_t defaultMove = ( botInfo->posture == IS_CROUCHED ) ? CROUCH : WALK;
|
||
|
|
||
|
if ( blockedClient != -1 && ltgTryMoveCounter < MAX_MOVE_ATTEMPTS ) {
|
||
|
ltgTryMoveCounter++;
|
||
|
if ( Bot_CanMove( BACK, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( RIGHT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
} else if ( Bot_CanMove( LEFT, 100.0f, true ) ) {
|
||
|
Bot_MoveToGoal( botCanMoveGoal, vec3_zero, defaultMove, NULLMOVETYPE );
|
||
|
}
|
||
|
|
||
|
Bot_LookAtEntity( blockedClient, SMOOTH_TURN );
|
||
|
ltgReached = false;
|
||
|
ltgMoveTime = botWorld->gameLocalInfo.time + 1000;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !ClientIsValid( heardClient, -1 ) ) {
|
||
|
if ( botThreadData.random.RandomInt( 100 ) > 90 || ltgReached == false ) {
|
||
|
idVec3 vec;
|
||
|
if ( Bot_RandomLook( vec ) ) {
|
||
|
Bot_LookAtLocation( vec, SMOOTH_TURN ); //randomly look around, for enemies and whatnot.
|
||
|
ltgReached = true; //mal: we reached our goal, and am going to camp now. ONLY after we've looked around a bit.
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
Bot_MoveToGoal( vec3_zero, vec3_zero, CROUCH, NULLMOVETYPE ); //mal: crouch down, waiting for our enemy....
|
||
|
Bot_LookAtLocation( botWorld->clientInfo[ heardClient ].origin, SMOOTH_TURN ); //we hear someone - look at where they're at.
|
||
|
ltgReached = false; //mal: as long as we hear someone, keep this updated.
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|