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

602 lines
17 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "BotThreadData.h"
#include "BotAI_Main.h"
/*
================
idBotAI::Run_LTG_Node
This is the bot's long term goal thinking.
================
*/
bool idBotAI::Run_LTG_Node() {
//mal: first, lets check if we should be running a different node....
if ( botWorld->gameLocalInfo.inWarmup && botWorld->gameLocalInfo.botsSillyWarmup != false ) {
ROOT_AI_NODE = &idBotAI::Run_Warmup_Node;
return false;
}
if ( botWorld->gameLocalInfo.inEndGame ) {
ROOT_AI_NODE = &idBotAI::Run_Intermission_Node;
return false;
}
if ( ClientIsDead( botNum ) ) {
ROOT_AI_NODE = &idBotAI::Enter_Run_Dead_Node;
return false;
}
if ( timeTilAttackEnemy != -1 && timeTilAttackEnemy < botWorld->gameLocalInfo.time && enemy != -1 ) {
Bot_SetTimeOnTarget( false );
ROOT_AI_NODE = &idBotAI::Run_Combat_Node;
return false;
}
if ( LTG_AI_SUB_NODE == NULL ) {
if ( !PopAINodeOffStack() ) {
Bot_FindLongTermGoal();
assert( LTG_AI_SUB_NODE != NULL );
}
}
aiState = LTG;
CallFuncPtr( LTG_AI_SUB_NODE ); //mal: run the bot's current Long Term Goal think node
Bot_Check_NBG_Goals();
Bot_CheckWeapon();
Bot_CheckClassState(); //mal: check if we should do some kind of class based action on the move!
Bot_CheckForDangers( false );
if ( enemy <= -1 ) {
Bot_FindEnemy( -1 );
}
return true;
}
/*
================
idBotAI::Run_NBG_Node
This is the bot's short term goal thinking.
================
*/
bool idBotAI::Run_NBG_Node() {
//mal: first, lets check if we should be running a different node....
if ( botWorld->gameLocalInfo.inWarmup && botWorld->gameLocalInfo.botsSillyWarmup != false ) {
ROOT_AI_NODE = &idBotAI::Run_Warmup_Node;
return false;
}
if ( botWorld->gameLocalInfo.inEndGame ) {
ROOT_AI_NODE = &idBotAI::Run_Intermission_Node;
return false;
}
if ( ClientIsDead( botNum ) ) {
ROOT_AI_NODE = &idBotAI::Enter_Run_Dead_Node;
return false;
}
if ( timeTilAttackEnemy != -1 && timeTilAttackEnemy < botWorld->gameLocalInfo.time && enemy != -1 ) {
ROOT_AI_NODE = &idBotAI::Run_Combat_Node;
return false;
}
if ( NBG_AI_SUB_NODE == false ) { //mal: if we have no NBG sub node, we're done here and need to reset and find a LTG next frame
Bot_ResetState( false, false );
return false;
}
aiState = NBG;
Bot_CheckClassState(); //mal: we run this early in NBG, so if medic is reviving, he'll switch weap as needed
CallFuncPtr( NBG_AI_SUB_NODE ); //mal: run the bot's current short term goal think node
Bot_Check_NBG_Goals();
Bot_CheckForDangers( false );
if ( enemy <= -1 ) {
Bot_FindEnemy( -1 );
}
return true;
}
/*
================
idBotAI::Run_Combat_Node
This is the bot's combat thinking.
================
*/
bool idBotAI::Run_Combat_Node() {
bool needNewEnemy = false;
bool haveClientToHaze = false;
//mal: first, lets check if we should be running a different node....
if ( botWorld->gameLocalInfo.inWarmup && botWorld->gameLocalInfo.botsSillyWarmup != false ) {
ROOT_AI_NODE = &idBotAI::Run_Warmup_Node;
return false;
}
if ( botWorld->gameLocalInfo.inEndGame ) {
ROOT_AI_NODE = &idBotAI::Run_Intermission_Node;
return false;
}
if ( ClientIsDead( botNum ) ) {
ROOT_AI_NODE = &idBotAI::Enter_Run_Dead_Node;
return false;
}
if ( botInfo->isDisguised ) {
botUcmd->botCmds.dropDisguise = true;
botIdealWeapSlot = RESET_WEAPON;
return true; //mal: wait til we drop uniform to attack
}
if ( !ClientIsValid( enemy, enemySpawnID ) ) { //mal: enemy disconnected/got kicked/ etc
needNewEnemy = true;
}
if ( !EnemyValid() ) {
needNewEnemy = true;
}
if ( ClientIsDead( enemy ) ) {
needNewEnemy = true;
haveClientToHaze = true;
}
if ( resetEnemy ) {
Bot_ResetEnemy();
needNewEnemy = true;
}
if ( needNewEnemy ) {
if ( !Bot_FindEnemy( enemy ) ) {
if ( haveClientToHaze ) {
if ( !Bot_PickPostCombatGoal() ) {
Bot_ResetState( true, false );
}
} else {
Bot_ResetState( true, false );
}
}
return false; //mal: no matter what we decide, leave this node for a frame.
}
int vehicleNum = botWorld->clientInfo[ enemy ].proxyInfo.entNum;
needNewEnemy = false;
if ( vehicleNum != CLIENT_HAS_NO_VEHICLE ) { //mal: dont fight pointless battles with vehicles if we're outgunned.
proxyInfo_t vehicleInfo;
GetVehicleInfo( vehicleNum, vehicleInfo );
if ( vehicleInfo.type == MCP && vehicleInfo.isImmobilized && vehicleInfo.driverEntNum == enemy ) {
needNewEnemy = true;
} else if ( ( vehicleInfo.type == GOLIATH || vehicleInfo.type == TITAN || vehicleInfo.type == DESECRATOR ) && vehicleInfo.health > ( vehicleInfo.maxHealth / 3 ) ) {
bool canNadeAttack = ( !botInfo->weapInfo.hasNadeAmmo || enemyInfo.enemyDist > GRENADE_ATTACK_DIST ) ? false : true;
if ( botInfo->classType == FIELDOPS ) {
if ( enemyInfo.enemyDist > GRENADE_ATTACK_DIST ) {
if ( !Bot_HasWorkingDeployable() || !ClassWeaponCharged( AIRCAN ) || Bot_EnemyAITInArea( vehicleInfo.origin ) ) {
needNewEnemy = true;
}
} else {
if ( !canNadeAttack && !ClassWeaponCharged( AIRCAN ) && !botInfo->weapInfo.primaryWeapHasAmmo ) {
needNewEnemy = true;
}
}
} else if ( botInfo->classType == COVERTOPS ) {
if ( !canNadeAttack && ( botInfo->weapInfo.primaryWeapon != SNIPERRIFLE || !botInfo->weapInfo.primaryWeapHasAmmo ) ) {
needNewEnemy = true;
}
} else if ( botInfo->classType == SOLDIER ) {
if ( !canNadeAttack && ( botInfo->weapInfo.primaryWeapon != ROCKET || !botInfo->weapInfo.primaryWeapHasAmmo ) ) {
needNewEnemy = true;
}
} else {
if ( ( !canNadeAttack && enemyInfo.enemyDist > INFANTRY_ATTACK_HEAVY_DIST ) && !botInfo->weapInfo.primaryWeapHasAmmo ) {
needNewEnemy = true;
}
}
}
}
if ( needNewEnemy ) {
if ( !Bot_FindEnemy( enemy ) ) {
Bot_ResetState( true, false );
return false;
}
}
aiState = COMBAT;
UpdateEnemyInfo();
if ( !enemyInfo.enemyVisible && enemyInfo.enemyLastVisTime + 5000 < botWorld->gameLocalInfo.time && !chasingEnemy ) {
if ( !Bot_FindEnemy( enemy ) ) {
Bot_ResetState( true, false );
}
return false;
}
Bot_CheckForDangers( true );
if ( turretDangerExists && turretDangerEntNum != -1 && combatNBGType != COMBAT_ATTACK_TURRET && enemyInfo.enemyDist > 500.0f && Bot_HasExplosives( true, true ) && !Client_IsCriticalForCurrentObj( botNum, BOT_WILL_ATTACK_TURRETS_IN_COMBAT_DIST ) ) {
combatNBGTarget = turretDangerEntNum;
COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_AttackTurret;
}
if ( COMBAT_AI_SUB_NODE == NULL ) {
Bot_CheckCurrentStateForCombat();
}
CallFuncPtr( COMBAT_AI_SUB_NODE ); //mal: run the bot's current combat think node
Bot_FindBetterEnemy(); //mal: the last thing to run
return true;
}
/*
================
idBotAI::Run_Intermission_Node
This node is run at game end. Bots jeer/cheer based on who won. can also just say silly things for fun.
================
*/
bool idBotAI::Run_Intermission_Node() {
int randChance = ( botWorld->gameLocalInfo.numClients > 12 ) ? 97 : 70;
if ( !botWorld->gameLocalInfo.inEndGame ) {
Bot_ResetState( true, true );
return false;
}
if ( botWorld->gameLocalInfo.winningTeam == NOTEAM ) { //mal: may take a bit to get updated. Ignore til its ready.
return true;
}
if ( botThreadData.random.RandomInt( 100 ) < randChance ) {
return true;
}
if ( botThreadData.random.RandomInt( 100 ) > 98 ) {
if ( botWorld->gameLocalInfo.winningTeam == botInfo->team ) {
Bot_AddDelayedChat( botNum, ENDGAME_WIN, 1 ); //I'm a winnah!!!1
} else {
Bot_AddDelayedChat( botNum, ENDGAME_LOSE, 1 ); //OMG H4X!!1
}
}
return true;
}
/*
================
idBotAI::Run_Warmup_Node
This is run while the game is in warmup mode. In warmup, the bots just
screw around and shoot each other, do other wacky stuff.
================
*/
bool idBotAI::Run_Warmup_Node() {
float attackDist = 3500.0f; //mal: the range of the bot's "awareness" during warmup
float dist;
idVec3 vec;
if ( !botWorld->gameLocalInfo.inWarmup || botWorld->gameLocalInfo.botsSillyWarmup == false ) {
Bot_ResetState( true, true );
return false;
}
if ( botWorld->gameLocalInfo.inEndGame ) {
ROOT_AI_NODE = &idBotAI::Run_Intermission_Node;
return false;
}
if ( enemy == -1 ) { //mal: if we have no enemy, lets look for one! NOTE: if a bot is killed, he keeps his enemy, to get revenge!
int i, j;
if ( !botInfo->hasGroundContact && !botInfo->hasJumped && botInfo->invulnerableEndTime > botWorld->gameLocalInfo.time ) {
return false; //mal: if we're dropping in (and not jumping around) dont pick targets yet (just spawned).
}
//mal: don't ALWAYS pick on the human! So lets just pick a random client and fight them.
if ( botThreadData.random.RandomInt( 100 ) > 75 ) {
j = 0;
} else {
j = botThreadData.random.RandomInt( botWorld->gameLocalInfo.numClients );
}
COMBAT_MOVEMENT_STATE = NULL;
for ( i = j; i < MAX_CLIENTS; i++ ) {
if ( !ClientIsValid( i, -1 ) ) {
continue; //mal: no valid client in this client slot!
}
if ( i == botNum ) { //mal: don't target yourself!
continue;
}
if ( EnemyIsIgnored( i ) ) {
continue; //mal: dont try to fight someone we've flagged to ignore for whatever reason!
}
const clientInfo_t& playerInfo = botWorld->clientInfo[ i ];
if ( playerInfo.isNoTarget ) {
continue;
} //mal: dont target clients that have notarget set - this is useful for debugging, etc.
if ( playerInfo.health <= 0 ) {
continue;
}
if ( !playerInfo.hasGroundContact && !playerInfo.hasJumped && playerInfo.invulnerableEndTime > botWorld->gameLocalInfo.time ) {
continue; //mal: dont shoot them when they're in the air!
}
vec = playerInfo.viewOrigin - botInfo->viewOrigin;
dist = vec.LengthFast();
if ( dist > attackDist ) { //mal: too far away - ignore!
continue;
}
enemy = i; //mal: just pick the first guy we come across
enemySpawnID = botWorld->clientInfo[ i ].spawnID;
botIdealWeapNum = NULL_WEAP;
if ( botThreadData.random.RandomInt( 100 ) > 50 || dist > 2500.0f ) { //mal: lets have a little fun - sometimes we'll try to knife our enemy!
botIdealWeapSlot = GUN;
} else {
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
botIdealWeapSlot = MELEE;
} else {
if ( botInfo->weapInfo.hasNadeAmmo ) {
botIdealWeapSlot = NADE;
} else {
botIdealWeapSlot = MELEE;
}
}
}
warmupUseActionMoveGoalTime = 0;
if ( botThreadData.random.RandomInt( 100 ) > 75 ) {
warmupActionMoveGoal = Bot_FindNearbySafeActionToMoveToward( botInfo->origin, 1024.0f, true );
if ( warmupActionMoveGoal != ACTION_NULL ) {
warmupUseActionMoveGoalTime = botWorld->gameLocalInfo.time + WARMUP_ACTION_TIME;
}
}
return true;
}
return true; //mal: dont do anything til next pass, whether found someone or not.
}
if ( warmupUseActionMoveGoalTime > botWorld->gameLocalInfo.time ) { //mal: don't always attack, sometimes move away from the spawn, and attack from a diff position
Bot_SetupMove( vec3_zero, -1, warmupActionMoveGoal );
if ( MoveIsInvalid() ) {
warmupUseActionMoveGoalTime = 0;
return true;
}
Bot_MoveAlongPath( SPRINT );
if ( botInfo->backPedalTime > botWorld->gameLocalInfo.time ) { //mal: add a bit of variety...
botUcmd->viewType = VIEW_REVERSE;
}
return true;
}
if ( !botWorld->clientInfo[ enemy ].inGame ) {
enemy = -1; //mal: our enemy disconnected, got kicked, etc. get another one
return true;
}
if ( ClientIsDead( enemy ) ) {
enemy = -1; //mal: our enemy is dead, get another one!
return true;
}
vec = botWorld->clientInfo[ enemy ].origin - botInfo->origin;
dist = vec.LengthFast();
if ( dist > attackDist ) { //mal: hes too far away (maybe flying), so forget him!
enemy = -1;
return true;
}
if ( botIdealWeapSlot == MELEE && dist > 2500.0f ) {
botIdealWeapSlot = GUN;
}
if ( botThreadData.random.RandomInt( 100 ) > 98 && botThreadData.random.RandomInt( 100 ) > 50 ) {
if ( botInfo->classType == FIELDOPS && botInfo->fireSupportChargedUsed == 0 ) {
botIdealWeapNum = AIRCAN;
botIdealWeapSlot = NO_WEAPON;
}
}
UpdateEnemyInfo();
if ( !ClientIsVisibleToBot( enemy, false, false ) ) {
Bot_SetupMove(vec3_zero, enemy, ACTION_NULL );
if ( MoveIsInvalid() ) { //mal: cant find a valid path - ignore this client for a while!
Bot_IgnoreEnemy( enemy, ENEMY_IGNORE_TIME );
Bot_ResetEnemy();
}
Bot_MoveAlongPath( SPRINT );
return true;
}
COMBAT_AI_SUB_NODE = &idBotAI::COMBAT_Foot_AttackEnemy;
if ( botIdealWeapSlot == MELEE ) {
COMBAT_MOVEMENT_STATE = &idBotAI::Knife_Attack_Movement;
}
bool result = CallFuncPtr( COMBAT_AI_SUB_NODE ); //mal: run the bot's current combat think node
if ( botInfo->weapInfo.weapon == AIRCAN || botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) {
if ( !botInfo->weapInfo.hasNadeAmmo && ( botInfo->weapInfo.weapon == GRENADE || botInfo->weapInfo.weapon == EMP ) ) {
botIdealWeapSlot = GUN;
return false;
}
if ( botInfo->weapInfo.weapon == AIRCAN && botInfo->fireSupportChargedUsed > 0 ) {
botIdealWeapSlot = GUN;
return false;
}
combatMoveFailedCount = 0;
COMBAT_MOVEMENT_STATE = &idBotAI::Crazy_Jump_Attack_Movement;
if ( botThreadData.random.RandomInt( 100 ) > 95 ) {
botUcmd->botCmds.attack = false;
}
}
if ( result == false ) {
Bot_IgnoreEnemy( enemy, ENEMY_IGNORE_TIME );
Bot_ResetEnemy();
}
if ( botThreadData.random.RandomInt( 100 ) > 98 && botThreadData.random.RandomInt( 100 ) > 95 ) {
botUcmd->desiredChat = WARMUP_TAUNT;
}
return true;
}
/*
================
idBotAI::Enter_Run_Dead_Node
This node is run if the bot is killed/dead/waiting for a medic.
================
*/
bool idBotAI::Enter_Run_Dead_Node() {
int attackerNum;
attackerNum = botWorld->clientInfo[ botNum ].lastAttacker;
Bot_ResetState( true, false ); //mal: clear out the bot's state, but keep any important goals on the stack in case he gets revived.
memset( delayedChats, 0, sizeof( delayedChats ) );
ROOT_AI_NODE = &idBotAI::Run_Dead_Node;
if ( attackerNum > -1 && attackerNum < MAX_CLIENTS ) {
if ( botWorld->clientInfo[ attackerNum ].team != botInfo->team ) {
if ( botWorld->clientInfo[ attackerNum ].isDisguised ) {
if ( botThreadData.random.RandomInt( 100 ) > 50 ) {
if ( botWorld->clientInfo[ attackerNum ].disguisedClient == botNum ) {
Bot_AddDelayedChat( botNum, ENEMY_DISGUISED_AS_ME, 3 );
} else {
Bot_AddDelayedChat( botNum, ENEMY_DISGUISED, 3 );
}
}
} else if ( !botWorld->clientInfo[ attackerNum ].isBot && botThreadData.random.RandomInt( 100 ) > 85 && botThreadData.random.RandomInt( 100 ) > 50 ) {
Bot_AddDelayedChat( botNum, KILLED_TAUNT, 2 );
}
}
}
lastAINode = "Dead Think";
return true;
}
/*
================
idBotAI::Run_Dead_Node
This node is run if the bot is killed/dead/waiting for a medic.
================
*/
bool idBotAI::Run_Dead_Node() {
int medicsInArea;
if ( botWorld->gameLocalInfo.inWarmup && botWorld->gameLocalInfo.botsSillyWarmup != false ) {
ROOT_AI_NODE = &idBotAI::Run_Warmup_Node;
return false;
}
if ( botWorld->gameLocalInfo.inEndGame ) {
ROOT_AI_NODE = &idBotAI::Run_Intermission_Node;
return false;
}
if ( botInfo->health > 0 ) {
if ( botInfo->revived ) {
Bot_ResetState( true, false ); //mal: got revived - get back into the action doing whatever we were doing before.
} else {
Bot_ResetState( true, true ); //mal: if respawned, wipe everything and start from scratch.
lastActionNum = ACTION_NULL;
}
return false;
}
if ( botInfo->inLimbo ) { //mal: if we're in limbo, just exit out of here for now.
return true;
}
botThreadData.GetBotOutputState()->botOutput[ botNum ].botCmds.hasMedicInFOV = Bot_CheckHasVisibleMedicNearby(); //mal: we'll use this later to decide if we should tap out, or wait.
if ( botThreadData.random.RandomInt( 100 ) > 98 && botThreadData.random.RandomInt( 100 ) > 50 ) {
medicsInArea = ClientsInArea( botNum, botInfo->origin, 700.0f, botInfo->team, MEDIC, false, false, false, false, true, true );
if ( medicsInArea > 0 && botThreadData.random.RandomInt( 100 ) > 98 ) { //mal: no point in begging for a medic, if theres none around ( or they're all dead ).
botUcmd->desiredChat = REVIVE_ME;
}
}
return true;
}