// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "../../game/ContentMask.h" #include "BotThreadData.h" #include "BotAI_Main.h" /* ================ idBotAI::Bot_FindLongTermGoal ================ */ void idBotAI::Bot_FindLongTermGoal() { bool botHasObj = ClientHasObj( botNum ); if ( Bot_CheckForDroppedObjGoals() ) { //mal: first, check for dropped objs, and what we should do about them. return; } if ( Bot_CheckMapScripts() ) { //mal: next, check for any map specific goals the bots should be mindful of. return; } if ( botWorld->botGoalInfo.mapHasMCPGoal && botInfo->team == GDF && !botHasObj ) { if ( Bot_CheckMCPGoals() ) { return; } } if ( botInfo->classType == ENGINEER && !botHasObj ) { //mal: check eng's goals if ( Bot_CheckForEngineerGoals() ) { return; } } if ( botInfo->classType == SOLDIER && !botHasObj ) { //mal: check soldier's goals if ( Bot_CheckForSoldierGoals() ) { return; } } if ( botInfo->classType == FIELDOPS && !botHasObj ) { //mal: check Fops's goals - only have 1 goal. if ( Bot_CheckForFieldOpsGoals() ) { return; } } if ( botInfo->classType == COVERTOPS && !botHasObj ) { //mal: check covert's goals if ( Bot_CheckForCovertGoals() ) { return; } } if ( !botHasObj ) { //mal: check for escort goals. Any class may call this if ( Bot_CheckForEscortGoals() ) { return; } } if ( Bot_CheckForTeamGoals() ) { return; } else { if ( botVehicleInfo != NULL ) { if ( Bot_CheckForVehicleFallBackGoals() ) { return; } } else { if ( Bot_CheckForFallBackGoals() ) { return; } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; //mal: this should never run anymore - unless there is no goals on the map for the bot to do. LTG_AI_SUB_NODE = &idBotAI::LTG_ErrorThink; } } /* ================ idBotAI::Bot_Check_NBG_Goals A team neutral short term goal check. ================ */ bool idBotAI::Bot_Check_NBG_Goals() { bool hasGoal = false; //mal: start having no goal hasGoal = Bot_CheckSelfState(); if ( hasGoal ) { return true; } hasGoal = Bot_IsNearDroppedObj(); if ( hasGoal ) { return true; } hasGoal = Bot_IsNearTeamGoalUnderAttack(); if ( hasGoal ) { return true; } if ( aiState == LTG ) { if ( LTG_CHECK_FOR_CLASS_NBG != NULL ) { hasGoal = CallFuncPtr( LTG_CHECK_FOR_CLASS_NBG ); //mal: check for any valid short term goals for this bot. } if ( !hasGoal ) { hasGoal = Bot_CheckForHumanWantingEscort(); } } else { if ( NBG_CHECK_FOR_CLASS_NBG != NULL ) { hasGoal = CallFuncPtr( NBG_CHECK_FOR_CLASS_NBG ); //mal: check for any valid short term goals for this bot. } if ( !hasGoal ) { Bot_CheckForHumanWantingEscort(); } hasGoal = true; //mal: dont want to check for spawnhosts, or any other NBG, if we're in the process of one. } if ( hasGoal ) { return true; } hasGoal = Bot_IsNearForwardSpawnToGrab(); if ( hasGoal ) { return true; } if ( botInfo->team == STROGG ) { hasGoal = Bot_CheckForSpawnHostToGrab(); } if ( hasGoal ) { return true; } return false; } /* ================ idBotAI::Bot_CheckSelfState A bot self check, where they decide if they should request a medic, or Fops or look for one to bugger for supplies, or if a high ranking strogg - balance their stroyent. ================ */ bool idBotAI::Bot_CheckSelfState() { if ( aiState == NBG && ( nbgType != CAMP ) ) { return false; } if ( Client_IsCriticalForCurrentObj( botNum, 1500.0f ) || ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum, 1500.0f ) ) ) { //mal: dont bother with chasing medics if really close to their primary obj - just do it! return false; } if ( botInfo->health < botInfo->maxHealth ) { if ( Bot_FindCloseSupplyPack( true, false ) ) { return true; } if ( Bot_IsInvestigatingTeamObj() || Bot_IsDefendingTeamCharge() ) { return false; } if ( botInfo->classType != MEDIC ) { if ( Bot_FindCloseSupplyTeammate( true ) ) { //mal: see if we can bugger someone for supplies! return true; } } } if ( Bot_IsInvestigatingTeamObj() || Bot_IsDefendingTeamCharge() ) { return false; } if ( botInfo->weapInfo.primaryWeapNeedsAmmo == true || ( botInfo->weapInfo.hasNadeAmmo == false && botInfo->isDisguised == false ) ) { bool nadesOnly = ( botInfo->weapInfo.primaryWeapNeedsAmmo == false ) ? true : false; if ( Bot_FindCloseSupplyPack( false, nadesOnly ) ) { return true; } if ( ( botInfo->team == GDF && botInfo->classType != FIELDOPS ) || ( botInfo->team == STROGG && botInfo->classType != MEDIC ) && botInfo->weapInfo.primaryWeapNeedsAmmo != false ) { //mal: wont bugger for nade ammo if ( Bot_FindCloseSupplyTeammate( false ) ) { //mal: see if we can bugger someone for supplies! return true; } } } return false; // also - if strogg, if have low health, but lots of ammo (or vice versa) balance stroylent to compensate as a short term fix! } /* ================ idBotAI::Bot_CheckClassState A class specific check for goals that the bot can do while on the move to a LTG, goals that dont require a AI Node change (medic healing self, covert throwing smoke, etc). Medics are the only class that will call this during a NBG. ================ */ void idBotAI::Bot_CheckClassState() { Bot_CheckMountedGPMGState(); if ( botInfo->classType == MEDIC || ( botInfo->classType == FIELDOPS && botInfo->team == GDF ) ) { if ( Bot_CheckSelfSupply() ) { return; // exit out of here if bot is busy healing self. } } if ( botInfo->classType == MEDIC && aiState == LTG ) { Bot_CheckAdrenaline( vec3_zero ); } if ( aiState == NBG && botInfo->classType == COVERTOPS ) { if ( nbgType == DEFENSE_CAMP || nbgType == CAMP || nbgType == SNIPE || nbgType == GRAB_SUPPLIES || nbgType == BUG_FOR_SUPPLIES || nbgType == AVOID_DANGER || nbgType == INVESTIGATE_CAMP ) { Bot_CheckCovertToolState(); } } if ( aiState != LTG ) { return; } if ( botInfo->isActor ) { return; } if ( Bot_CheckForTacticalAction() != -1 ) { Bot_RunTacticalAction(); } else if ( botInfo->classType == COVERTOPS ) { Bot_CheckCovertToolState(); } else if ( botInfo->classType == FIELDOPS && botInfo->team == STROGG ) { idVec3 turret_location; if ( Bot_IsUnderAttackByAPT( turret_location ) && ClassWeaponCharged( SHIELD_GUN ) ) { Bot_LookAtLocation( turret_location, FAST_TURN ); botIdealWeapNum = SHIELD_GUN; botIdealWeapSlot = NO_WEAPON; if ( botInfo->weapInfo.weapon == SHIELD_GUN ) { botUcmd->botCmds.attack = true; } } } else if ( botInfo->classType == MEDIC && botInfo->team == GDF ) { Bot_CheckHealthCrateState(); } //mal: GDF FOps will give ammo in the spawn. Strogg dont need this because they start out with SO much ammo. NOT needed anymore as now GDF start with max ammo. /* if ( botInfo->spawnTime + SPAWN_CONSIDER_TIME > botWorld->gameLocalInfo.time && !botInfo->revived && ClassWeaponCharged( AMMO_PACK ) ) { if ( botInfo->classType == FIELDOPS && botInfo->team == GDF ) { if ( botInfo->xySpeed > WALKING_SPEED ) { if ( botInfo->backPedalTime > botWorld->gameLocalInfo.time ) { botUcmd->viewType = VIEW_REVERSE; } else { botUcmd->botCmds.lookUp = true; } botIdealWeapNum = AMMO_PACK; botIdealWeapSlot = NO_WEAPON; botUcmd->moveFlag = RUN; if ( botInfo->weapInfo.weapon == AMMO_PACK ) { botUcmd->botCmds.attack = true; } } } } */ if ( botInfo->team == GDF && botInfo->classType == FIELDOPS && botInfo->spawnTime + SPAWN_CONSIDER_TIME > botWorld->gameLocalInfo.time && botInfo->revived && ClassWeaponCharged( AMMO_PACK ) && ClientIsValid( botInfo->mySavior, -1 ) && enemy == -1 && !ClientIsIgnored( botInfo->mySavior ) ) { nbgTarget = botInfo->mySavior; nbgTargetType = REARM_REVIVE; nbgTargetSpawnID = botWorld->clientInfo[ botInfo->mySavior ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; nbgType = SUPPLY_TEAMMATE; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; } //mal: be a good teammate and supply some ammo to the guy who revived us, as long as we're not in combat! } /* ================ idBotAI::Bot_CovertOpsCheckNBGState A covert specific short term goal check. Works for both teams. Only run when bot is in active pursuit of a long term goal ================ */ bool idBotAI::Bot_CovertOpsCheckNBGState() { //mal: first, setup some defaults. int clientNum; float range = MEDIC_RANGE; if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP ) ) { //mal: when doing short term goals, only grab uniforms if camping. return false; } if ( botInfo->isActor ) { return false; } //mal: add any exceptions to those defaults here if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) { //mal_NOTE: coverts will only follow a teammate if they're not disguised. range = MEDIC_RANGE_BUSY; } if ( Client_IsCriticalForCurrentObj( botNum, BODY_IGNORE_RANGE ) ) { //mal: if bot is close to its critical obj, forget about grabbing uniforms! return false; } if ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum ) ) { return false; } clientNum = Bot_CovertCheckForUniforms( range ); if ( clientNum != -1 ) { nbgTarget = clientNum; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_StealUniform; return true; } if ( Client_IsCriticalForCurrentObj( botNum, -1.0f ) ) { //mal: if bot has a critical obj, leave! return false; } if ( aiState == LTG && ( ltgType == HACK_GOAL || ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) { //mal_TODO: add other exceptions here return false; } clientNum = Bot_CovertCheckForVictims( range * 2.0f ); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_HuntVictim; return true; } clientNum = Bot_CheckForNearbyTeammateWhoCouldUseSmoke(); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SmokeTeammate; return true; } return false; } /* ================ idBotAI::Bot_MedicCheckNBGState_InLTG A medic specific short term goal check. Works for both teams. Only run when bot is in active pursuit of a long term goal ================ */ bool idBotAI::Bot_MedicCheckNBGState_InLTG () { int clientNum = -1; int escortClientNum = -1; int healthRange = 150; //mal: will heal anyone not at full health float range = MEDIC_RANGE; if ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST || botInfo->isActor ) { escortClientNum = ltgTarget; range = MEDIC_RANGE_BUSY; healthRange = 80; //mal: if already busy, less inclined to heal others } if ( ClientHasObj( botNum ) ) { if ( ClientIsCloseToDeliverObj( botNum ) ) { return false; } if ( botInfo->team == GDF ) { range = MEDIC_RANGE_CARRIER; } else { range = 0.0f; } } clientNum = Bot_MedicCheckForDeadMate( -1, escortClientNum, range ); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; //mal: bit of a hack, but a self correcting one if the bot isn't able to revive. aiState = NBG; nbgType = REVIVE_TEAMMATE; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_ReviveTeammate; if ( Bot_IsInvestigatingTeamObj() ) { lastCheckActionTime = 0; } return true; } if ( Bot_IsInvestigatingTeamObj() ) { return false; } if ( ClientHasObj( botNum ) ) { range = MEDIC_RANGE_CARRIER; healthRange = 50; } clientNum = Bot_MedicCheckForWoundedMate( -1, escortClientNum, range, healthRange ); if ( clientNum != -1 ) { if ( botWorld->clientInfo[ clientNum ].lastChatTime[ HEAL_ME ] + 5000 > botWorld->gameLocalInfo.time ) { nbgTargetType = HEAL_REQUESTED; } else { nbgTargetType = HEAL; } //mal: bit of a hack, but a self correcting one if the bot isn't able to heal. aiState = NBG; nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; if ( ClientCanBeTKRevived( clientNum ) ) { nbgType = TK_REVIVE_TEAMMATE; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_TKReviveTeammate; } else { nbgType = SUPPLY_TEAMMATE; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; } return true; } if ( ClientHasObj( botNum ) ) { return false; } if ( botInfo->isActor ) { return false; } if ( botInfo->team == STROGG ) { //mal: the sinister strogg will look for helpless GDF to turn into unwilling hosts < cue evile laughter > clientNum = Bot_CheckForNeedyTeammates( range ); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; nbgTargetType = REARM; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; return true; } clientNum = Bot_StroggCheckForGDFBodies( range ); //mal_TODO: is this the best range? if ( clientNum != -1 ) { nbgTarget = clientNum; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_CreateSpawnHost; return true; } } if ( botInfo->team == GDF ) { clientNum = Bot_CheckForSpawnHostsToDestroy( range, nbgChat ); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetType = BODY; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_DestroySpawnHost; return true; } } return false; } /* ================ idBotAI::Bot_MedicCheckNBGState_InNBG A medic specific short term goal check. Works for both teams. Only run when bot is in active pursuit of a short term goal ================ */ bool idBotAI::Bot_MedicCheckNBGState_InNBG () { if ( ClientHasObj( botNum ) ) { //mal: no multi-tasking when we're the carrier! return false; } if ( botInfo->isActor ) { return false; } //mal: first, setup some defaults. bool isBusy = ( nbgType == REVIVE_TEAMMATE || nbgType == TK_REVIVE_TEAMMATE ); int clientNum; int targetClientNum = -1; int healthRange = 150; //mal: will heal anyone not at full health float range = MEDIC_RANGE; //mal: add any exceptions to those defaults here if ( isBusy || nbgType == SUPPLY_TEAMMATE ) { if ( Client_IsCriticalForCurrentObj( nbgTarget, -1.0f ) && nbgType != SUPPLY_TEAMMATE ) { //mal: sorry guys - critical teammates get priority! return false; } range = MEDIC_RANGE_BUSY; targetClientNum = nbgTarget; healthRange = 60; //mal: if already busy, less inclined to heal others } clientNum = Bot_MedicCheckForDeadMate( targetClientNum, -1, range ); if ( clientNum != targetClientNum && clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_ReviveTeammate; return true; } if ( isBusy || ( Client_IsCriticalForCurrentObj( nbgTarget, -1.0f ) && nbgType == SUPPLY_TEAMMATE ) ) { //mal: won't bother healing ppl if we're on our way to revive someone! return false; } clientNum = Bot_MedicCheckForWoundedMate( targetClientNum, -1, range, healthRange ); if ( clientNum != targetClientNum && clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; nbgTargetType = HEAL; if ( ClientCanBeTKRevived( clientNum ) ) { NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_TKReviveTeammate; } else { NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; } return true; } if ( ( nbgType == CAMP || nbgType == DEFENSE_CAMP ) && botInfo->team == STROGG ) { //mal: if we're just standing around, camping, look to resupply needy teammates. clientNum = Bot_CheckForNeedyTeammates( range ); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; nbgTargetType = REARM; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; return true; } clientNum = Bot_StroggCheckForGDFBodies( range ); //mal_TODO: is this the best range? if ( clientNum != -1 ) { nbgTarget = clientNum; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_CreateSpawnHost; return true; } } return false; } /* ================ idBotAI::Bot_MedicCheckNBGState_InCombat ================ */ bool idBotAI::Bot_MedicCheckNBGState_InCombat() { if ( botInfo->classType != MEDIC ) { return false; } #ifndef STROGG_INSTANT_REVIVE if ( botInfo->team == STROGG ) { return false; } #endif if ( combatNBGType == COMBAT_REVIVE_MATE ) { //mal: dont bother with this if already doing it! return false; } float reviveRange = ( botWorld->gameLocalInfo.gameIsBotMatch ) ? 1500.0f : 900.0f; int clientNum = Bot_MedicCheckForDeadMateDuringCombat( botWorld->clientInfo[ enemy ].origin, reviveRange ); if ( clientNum != -1 ) { COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_ReviveTeammate; combatNBGTarget = clientNum; combatNBGType = COMBAT_REVIVE_MATE; combatNBGTime = botWorld->gameLocalInfo.time + 15000; //mal: 15 seconds to make this happen! combatNBGReached = false; return true; } return false; } /* ================ idBotAI::Bot_FopsCheckNBGState A GDF Fops specific short term goal check. Only run when bot is in active pursuit of a long term goal, unless hes camping. ================ */ bool idBotAI::Bot_FopsCheckNBGState() { int clientNum = -1; if ( botInfo->team == GDF ) { float range = MEDIC_RANGE; ///mal: first, setup some defaults. //mal: add any exceptions to those defaults here if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) { range = MEDIC_RANGE_BUSY; } if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP ) ) { return false; } if ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum ) ) { return false; } clientNum = Bot_CheckForNeedyTeammates( range ); if ( clientNum != -1 ) { if ( botWorld->clientInfo[ clientNum ].lastChatTime[ REARM_ME ] + 5000 > botWorld->gameLocalInfo.time ) { nbgTargetType = REARM_REQUESTED; } else { nbgTargetType = REARM; } //mal: bit of a hack, but a self correcting one if the bot isn't able to supply. aiState = NBG; nbgType = SUPPLY_TEAMMATE; nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_SupplyTeammate; return true; } } else { if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) { return false; } if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP ) ) { return false; } if ( ClientHasObj( botNum ) && ClientIsCloseToDeliverObj( botNum ) ) { return false; } if ( botInfo->isActor ) { return false; } clientNum = Bot_HasTeammateWhoCouldUseShieldCoverNearby(); if ( clientNum != -1 ) { nbgTarget = clientNum; nbgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_ShieldTeammate; return true; } } return false; } /* ================ idBotAI::Bot_EngCheckNBGState An Engineer specific short term goal check. Only run when bot is in active pursuit of a long term goal, unless hes camping. Here, the bot will decide to repair vehicles and deployables. ================ */ bool idBotAI::Bot_EngCheckNBGState() { //mal: first, setup some defaults. int proxyNum = -1; float range = MEDIC_RANGE_REQUEST; //mal: add any exceptions to those defaults here if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) ) { range = MEDIC_RANGE_BUSY; } if ( ClientHasObj( botNum ) ) { return false; } if ( botInfo->isActor ) { return false; } if ( aiState == NBG && ( nbgType != CAMP && nbgType != DEFENSE_CAMP ) ) { return false; } if ( aiState == LTG && ltgType == DEFUSE_GOAL ) { //mal: defusing bombs has to take priority! return false; } bool chatRequested = false; proxyNum = Bot_CheckForNeedyVehicles( range, chatRequested ); if ( proxyNum != -1 ) { nbgChat = false; if ( chatRequested ) { nbgChat = true; } nbgTarget = proxyNum; nbgTargetType = VEHICLE; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_FixProxyEntity; return true; } proxyNum = Bot_CheckForNeedyDeployables( range ); if ( proxyNum != -1 ) { if ( DeployableIsMarkedForRepair( proxyNum, true ) ) { Bot_AddDelayedChat( botNum, ACKNOWLEDGE_YES, 2 ); } nbgTarget = proxyNum; nbgTargetType = DEPLOYABLE; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_FixDeployable; return true; } return false; } /* ================ idBotAI::Bot_CheckForNearbyVehicleToGrab ================ */ void idBotAI::Bot_CheckForNearbyVehicleToGrab() { int vehicleNum; if ( combatNBGType == COMBAT_GRAB_VEHICLE ) { //mal: dont bother with this if already doing it! return; } if ( !botWorld->gameLocalInfo.botsUseVehicles ) { return; } vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, GROUND | AIR, PERSONAL, true ); if ( vehicleNum != -1 ) { int travelTime; proxyInfo_t vehicle; GetVehicleInfo( vehicleNum, vehicle ); if ( Bot_LocationIsReachable( false, vehicle.origin, travelTime ) && travelTime < ( Bot_ApproxTravelTimeToLocation( botInfo->origin, vehicle.origin, false ) * TRAVEL_TIME_MULTIPLY ) ) { COMBAT_AI_SUB_NODE = &idBotAI::Enter_COMBAT_Foot_GrabVehicle; combatNBGTarget = vehicleNum; combatNBGType = COMBAT_GRAB_VEHICLE; combatNBGTime = botWorld->gameLocalInfo.time + 15000; //mal: 15 seconds to make this happen! } } } /* ================ idBotAI::Bot_CheckMapScripts This function can do map specific overrides, if needed, or if debugging. ================ */ bool idBotAI::Bot_CheckMapScripts() { if ( botWorld->gameLocalInfo.botFollowPlayer > 0 ) { ltgTarget = 0; //mal: client 0 - since this is just a test, I can be evile like this! ltgType = FOLLOW_TEAMMATE; ltgTargetSpawnID = botWorld->clientInfo[ 0 ].spawnID; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FollowMate; return true; } if ( botThreadData.AllowDebugData() ) { if ( bot_debugActionGoalNumber.GetInteger() > 0 && bot_debugActionGoalNumber.GetInteger() < botThreadData.botActions.Num() ) { actionNum = bot_debugActionGoalNumber.GetInteger(); ltgUseVehicle = true; ltgType = CAMP_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_CampGoal; return true; } } if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( botWorld->gameLocalInfo.gameMap == SLIPGATE ) { if ( botVehicleInfo != NULL && botVehicleInfo->isAirborneVehicle && botVehicleInfo->origin.x < SLIPGATE_DIVIDING_PLANE_X_VALUE ) { //mal: if in air vehicle, on desert side of the map, check where the MCP is. proxyInfo_t mcpInfo; GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcpInfo ); if ( mcpInfo.entNum == 0 ) { return false; } if ( mcpInfo.origin.x < SLIPGATE_DIVIDING_PLANE_X_VALUE ) { return false; } Bot_ExitVehicle(); // vLTGOrigin = SLIPGATE_ORIGIN; // V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; // V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelToGoalOrigin; //mal: just no time to work on this at the 11th hour. } // return true; } if ( botInfo->isActor ) { if ( botThreadData.actorMissionInfo.targetClientNum != -1 && botThreadData.actorMissionInfo.actionNumber != ACTION_NULL ) { actionNum = botThreadData.actorMissionInfo.actionNumber; ltgTarget = botThreadData.actorMissionInfo.targetClientNum; ltgTargetSpawnID = botWorld->clientInfo[ botThreadData.actorMissionInfo.targetClientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_ActorEscortPlayerToGoal; return true; } if ( botThreadData.actorMissionInfo.targetClientNum != -1 && botThreadData.actorMissionInfo.playerIsOnFinalMission && botThreadData.actorMissionInfo.playerNeedsFinalBriefing ) { ltgTarget = botThreadData.actorMissionInfo.targetClientNum; ltgTargetSpawnID = botWorld->clientInfo[ botThreadData.actorMissionInfo.targetClientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_ActorGiveFinalBriefingToPlayer; return true; } } return false; } /* ================ idBotAI::ClientHasObj ================ */ bool idBotAI::ClientHasObj( int clientNum ) { bool hasObj = false; int i; for( i = 0; i < MAX_CARRYABLES; i++ ) { if ( botWorld->botGoalInfo.carryableObjs[ i ].entNum == 0 ) { continue; } if ( botWorld->botGoalInfo.carryableObjs[ i ].carrierEntNum != clientNum ) { continue; } hasObj = true; break; } return hasObj; } /* ================ idBotAI::Bot_CheckForTeamGoals ================ */ bool idBotAI::Bot_CheckForTeamGoals() { ltgUseVehicle = false; bool mcpGoal = false; int botDeployableTarget = -1; int botCamperTarget = -1; int i, j, matesInArea, botClass, validClasses; int spawnGoal = -1; //mal: grabbing the spawn point. Theres only 1 spawn goal at a time to worry about. int stealSpawnGoal = -1; int deliverGoal = -1; int mcpActionNum = ACTION_NULL; // float dist; // float closest = idMath::INFINITY; proxyInfo_t mcpVehicleInfo; playerTeamTypes_t teamFilter = botInfo->team; playerTeamTypes_t covertTeamFilter = botInfo->team; idVec3 vec; idList< int > campGoals; idList< int > priorityCampGoals; idList< int > gunCampGoals; idList< int > roamGoals; idList< int > vRoamGoals; idList< int > vCampGoals; idList< int > investigateGoals; //mal: these are goals that have the bot investigate their current obj ( ex: if you plant, a non-eng may decide to "investigate" the plant site ). idList< int > stealGoals; idList< int > vehicleGrabGoals; if ( botInfo->classType == SOLDIER ) { botClass = 1; } else if ( botInfo->classType == MEDIC ) { botClass = 2; } else if ( botInfo->classType == ENGINEER ) { botClass = 4; } else if ( botInfo->classType == FIELDOPS ) { botClass = 8; } else { botClass = 16; // COVERT OPS } if ( botInfo->isDisguised ) { //mal: if the bots disguised, will do the enemys' camp/roam goals. covertTeamFilter = ( botInfo->team == GDF ) ? STROGG : GDF; } if ( botWorld->botGoalInfo.mapHasMCPGoal && botWorld->gameLocalInfo.heroMode == false ) { GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcpVehicleInfo ); if ( mcpVehicleInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { if ( !mcpVehicleInfo.isImmobilized && ( mcpVehicleInfo.driverEntNum == -1 || mcpVehicleInfo.driverEntNum == botNum || botInfo->proxyInfo.entNum == mcpVehicleInfo.entNum ) ) { if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( botWorld->gameLocalInfo.botsDoObjsInTrainingMode && ( !TeamHasHuman( botInfo->team ) || ( botWorld->botGoalInfo.mapHasMCPGoalTime + ( botWorld->gameLocalInfo.botTrainingModeObjDelayTime * 1000 ) ) < botWorld->gameLocalInfo.time ) ) { mcpGoal = true; } } else { if ( !Bot_IsInHeavyAttackVehicle() ) { mcpGoal = true; } } } } } 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( teamFilter ) == ACTION_STEAL ) { if ( ClientHasObj( botNum ) ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } if ( botThreadData.botActions[ i ]->GetActionObjState() == false ) { //mal: obj is already stolen! continue; } if ( botWorld->gameLocalInfo.inWarmup ) { continue; } stealGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DELIVER ) { //mal: only 1 deliver point on a map at a time. if ( !ClientHasObj( botNum ) ) { if ( botThreadData.random.RandomInt( 100 ) < 25 ) { continue; } if ( reachedPatrolPointTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: did this recently, so do something else for a while. continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, PATROL_DELIVER_GOAL, MAX_PATROL_DELIVER_CLIENTS ) ) { //mal: if just patrolling around the obj, don't need too many there. continue; } if ( botInfo->isDisguised ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } idVec3 vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( 3500.0f ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), PATROL_DELIVER_TEST_DIST, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > MAX_PATROL_DELIVER_CLIENTS ) { continue; } //mal: already somebody there, so lets pick a different one to camp. } if ( botWorld->gameLocalInfo.inWarmup ) { continue; } deliverGoal = i; continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_MCP_OUTPOST ) { if ( botInfo->team == STROGG ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( !mcpGoal ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum == -1 ) { continue; } if ( !botWorld->botGoalInfo.mapHasMCPGoal ) { continue; } proxyInfo_t mcp; GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcp ); if ( mcp.entNum == 0 ) { continue; } if ( mcp.isDeployed ) { continue; } mcpActionNum = i; continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_CAMP || botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_DEFENSE_CAMP ) { bool isPriority = ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_DEFENSE_CAMP ) ? true : false; if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( !botInfo->weapInfo.primaryWeapHasAmmo ) { //mal: no ammo - dont camp. continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } if ( !botInfo->isDisguised ) { validClasses = botThreadData.botActions[ i ]->GetValidClasses(); if ( validClasses != 0 ) { if ( !( validClasses & botClass ) ) { //mal: give the LD's the ability to limit a camp position to a certain class. continue; } } } if ( botThreadData.botActions[ i ]->GetActionWeapType() != -1 ) { if ( botInfo->weapInfo.primaryWeapon != botThreadData.botActions[ i ]->GetActionWeapType() ) { //mal: give LD's ability to limit this camp to a certain weapon. continue; } } if ( isPriority ) { if ( !Bot_LTGIsAvailable( -1, i, DEFENSE_CAMP_GOAL, 1 ) ) { continue; } } else { if ( !Bot_LTGIsAvailable( -1, i, CAMP_GOAL, 1 ) ) { continue; } //mal: some bot is already on the way to camp there - no need for us to do so as well. } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), ( isPriority ) ? DEFENSE_CAMP_MATE_RANGE : 150.0f, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } //mal: already somebody there, so lets pick a different one to camp. if ( botInfo->isDisguised ) { if ( !botThreadData.botActions[ i ]->disguiseSafe ) { //mal: not a good one for a disguised covert. continue; } int enemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, true, true ); if ( enemiesInArea > 0 ) { continue; } //mal: if bot is disguised, don't camp if lots of the enemy are around - that raises the chances they'll spot us. } if ( isPriority ) { if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } priorityCampGoals.Append( i ); } else { campGoals.Append( i ); } continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_ROAM ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } if ( botInfo->isDisguised ) { if ( !botThreadData.botActions[ i ]->disguiseSafe ) { //mal: not a good one for a disguised covert. continue; } } else { validClasses = botThreadData.botActions[ i ]->GetValidClasses(); if ( validClasses != 0 ) { if ( !( validClasses & botClass ) ) { //mal: give the LD's the ability to limit a roam position to a certain class. continue; } } } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_MG_NEST || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_MG_NEST_BUILD ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( Bot_WantsVehicle() ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botVehicleInfo != NULL ) { continue; } if ( botThreadData.botActions[ i ]->GetActionState() == ACTION_STATE_GUN_DESTROYED ) { continue; } if ( botInfo->isDisguised ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, MG_CAMP_GOAL, 1 ) ) { continue; } //mal: some bot is already on the way to grab that gun - no need for us to do so as well. matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 350.0f, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } //mal: already somebody there, so lets pick a different one to grab. int badGuysInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 3000.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, true, false ); if ( badGuysInArea == 0 ) { continue; } //mal: noone around this gun to attack, so just ignore it. gunCampGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_FORWARD_SPAWN ) { if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team ) { //mal: it already belongs to us. continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botWorld->botGoalInfo.isTrainingMap ) { if ( !botThreadData.actorMissionInfo.forwardSpawnIsAllowed ) { continue; } } else if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( Bot_CheckForHumanInteractingWithEntity( botThreadData.botActions[ i ]->GetActionSpawnControllerEntNum() ) == true ) { continue; } if ( !ActionIsActiveForTrainingMode( i, botWorld->gameLocalInfo.botTrainingModeObjDelayTime ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. continue; } if ( !TeamHasHuman( botInfo->team ) ) { //mal: dont keep frustrating the human player by taking our spawn back too quick, in case he took it from us. if ( botThreadData.random.RandomInt( 100 ) > TAKE_SPAWN_IN_DEMO_MODE_CHANCE ) { continue; } } } if ( !Bot_LTGIsAvailable( -1, i, FDA_GOAL, 2 ) ) { continue; } //mal: some bot(s) is already on the way to grab that spawn - no need for us to do so as well. matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 350.0f, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } //mal: already somebody there, so lets pick a different one to grab. spawnGoal = i; continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_VEHICLE_GRAB ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botVehicleInfo != NULL ) { continue; } if ( botInfo->spawnTime + 5000 < botWorld->gameLocalInfo.time ) { continue; } validClasses = botThreadData.botActions[ i ]->GetValidClasses(); if ( validClasses != 0 ) { if ( !( validClasses & botClass ) ) { //mal: give the LD's the ability to limit a camp position to a certain class. continue; } } if ( !Bot_LTGIsAvailable( -1, i, GRAB_VEHICLE_GOAL, 1 ) ) { continue; } //mal: some bot is already on the way to grab that vehicle - no need for us to do so as well. matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 500.0f, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } //mal: already somebody there, so lets pick a different one to grab. if ( FindClosestVehicle( MAX_VEHICLE_RANGE, botThreadData.botActions[ i ]->GetActionOrigin(), NULL_VEHICLE, botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ), PERSONAL | AIR_TRANSPORT, true ) == -1 ) { continue; } vehicleGrabGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DENY_SPAWNPOINT ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team || botThreadData.botActions[ i ]->GetTeamOwner() == NOTEAM ) { //mal: it already belongs to us. continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botWorld->botGoalInfo.isTrainingMap ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( Bot_CheckForHumanInteractingWithEntity( botThreadData.botActions[ i ]->GetActionSpawnControllerEntNum() ) == true ) { continue; } } if ( !Bot_LTGIsAvailable( -1, i, STEAL_SPAWN_GOAL, 1 ) ) { continue; } //mal: some bot is already on the way to grab that spawn - no need for us to do so as well. matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 350.0f, botInfo->team, NOCLASS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } //mal: already somebody there, so lets pick a different one to grab. stealSpawnGoal = i; continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_VEHICLE_ROAM ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( botInfo->isDisguised ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botVehicleInfo == NULL ) { if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) > EASY_MODE_CHANCE_WILL_BUILD_DEPLOYABLE_OR_USE_VEHICLE ) { continue; } if ( Bot_CheckThereIsHeavyVehicleInUseAlready() ) { continue; } } } if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->flags & PERSONAL ) { continue; } if ( !( botVehicleInfo->flags & botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) ) && botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) != 0 ) { continue; } //mal: the vehicle we're in cant handle doing this action. Bots want to stay in their current vehicle unless they MUST exit it. } else { if ( FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ), PERSONAL | AIR_TRANSPORT, true ) == -1 ) { continue; } } vRoamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_VEHICLE_CAMP ) { if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( botInfo->isDisguised ) { continue; } if ( botVehicleInfo == NULL ) { if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) > EASY_MODE_CHANCE_WILL_BUILD_DEPLOYABLE_OR_USE_VEHICLE ) { continue; } if ( Bot_CheckThereIsHeavyVehicleInUseAlready() ) { continue; } } } if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->flags & PERSONAL ) { continue; } if ( !( botVehicleInfo->flags & botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) ) && botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) != 0 ) { continue; } //mal: the vehicle we're in cant handle doing this action. Bots want to stay in their current vehicle unless they MUST exit it. } else { if ( FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ), PERSONAL | AIR_TRANSPORT, true ) == -1 ) { continue; } } vCampGoals.Append( i ); continue; } //mal_TODO: add more invesigate type actions! if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DEFUSE || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_PREVENT_BUILD || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_PREVENT_HACK ) { if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && !botWorld->botGoalInfo.gameIsOnFinalObjective ) { //mal: dont worry about the obj much in training mode, unless its the final obj... continue; } if ( botWorld->gameLocalInfo.gameIsBotMatch && !TeamHasHuman( botInfo->team ) && botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY && botThreadData.random.RandomInt( 100 ) > 25 ) { //mal: very small chance will respond to actions being done in easy mode botmatch continue; } if ( lastCheckActionTime > botWorld->gameLocalInfo.time ) { //mal: don't repeat ourselves continue; } if ( botThreadData.botActions[ i ]->GetActionState() == ACTION_STATE_NORMAL ) { //mal: noones done anything to this action yet. continue; } if ( !botThreadData.botActions[ i ]->ActionIsPriority() ) { //mal: not worth worrying about. continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; float distSqr = vec.LengthSqr(); if ( distSqr > Square( 6000.0f ) ) { //mal: its too far away, we'll never make it in time! continue; } if ( distSqr > Square( 3000.0f ) ) { //mal: if fairly close, go to action no matter what! if ( !Bot_LTGIsAvailable( -1, i, INVESTIGATE_ACTION, 3 ) ) { //mal: 3 bots already on their way - ignore this action. continue; } } if ( botVehicleInfo != NULL ) { //mal: if we're in a vehicle, ignore this goal if we can't reach it. if ( botVehicleInfo->type > ICARUS || botVehicleInfo->type == GOLIATH ) { continue; } int travelTime; if ( !Bot_LocationIsReachable( true, botThreadData.botActions[ i ]->GetActionOrigin(), travelTime ) ) { continue; } } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, botInfo->team, NOCLASS, false, false, false, false, true ); if ( matesInArea > MIN_NUM_INVESTIGATE_CLIENTS ) { continue; } //mal: already some ppl there, so lets do something else. investigateGoals.Append( i ); continue; } } if ( !Bot_WantsVehicle() || botVehicleInfo != NULL ) { botDeployableTarget = Bot_HasDeployableTargetGoals( false ); } botCamperTarget = Bot_CheckForGrieferTargetGoals(); if ( roamGoals.Num() == 0 && campGoals.Num() == 0 && spawnGoal == -1 && stealSpawnGoal == -1 && investigateGoals.Num() == 0 && vRoamGoals.Num() == 0 && vCampGoals.Num() == 0 && mcpGoal == false && stealGoals.Num() == 0 && deliverGoal == -1 && botDeployableTarget != -1 && priorityCampGoals.Num() == 0 && vehicleGrabGoals.Num() == 0 && gunCampGoals.Num() == 0 && botCamperTarget == -1 ) { //TODO: add more conditions here as create them! if ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum != -1 && botWorld->botGoalInfo.botGoal_MCP_VehicleNum == botInfo->proxyInfo.entNum ) { Bot_ExitVehicle( false ); } return false; } //mal: if have docs, consider getting them to goal first! if ( deliverGoal != -1 ) { actionNum = deliverGoal; if ( ClientHasObj( botNum ) ) { ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DELIVER_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeliverGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } else { aiState = LTG; ltgType = PATROL_DELIVER_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 60000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_PatrolDeliverGoal; ltgUseVehicle = false; routeNode = NULL; PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } } //mal: next, consider whether or not to go to an action of ours thats under attack. if ( investigateGoals.Num() > 0 ) { if ( investigateGoals.Num() == 1 ) { actionNum = investigateGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( investigateGoals.Num() ); actionNum = investigateGoals[ j ]; } ltgTime = botWorld->gameLocalInfo.time + 45000; lastCheckActionTime = botWorld->gameLocalInfo.time + 20000; //mal: dont do this again for a while. aiState = LTG; ltgType = INVESTIGATE_ACTION; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_InvestigateGoal; return true; } //mal: will always consider steal goals next if ( stealGoals.Num() > 0 ) { if ( stealGoals.Num() == 1 ) { actionNum = stealGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( stealGoals.Num() ); actionNum = stealGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = STEAL_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_StealGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } //mal: next, MCP goals. if ( mcpGoal && mcpActionNum != ACTION_NULL ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->type == MCP ) { actionNum = mcpActionNum; vLTGType = V_DRIVE_MCP; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_DriveMCPToGoal; return true; } } else { if ( Bot_LTGIsAvailable( -1, ACTION_NULL, DRIVE_MCP, 2 ) ) { //mal: couple bots already on their way - ignore the MCP. vLTGType = V_DRIVE_MCP; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_DriveMCPToGoal; ltgTarget = botWorld->botGoalInfo.botGoal_MCP_VehicleNum; ltgType = DRIVE_MCP; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; actionNum = mcpActionNum; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_UseVehicle; return true; } } } bool enemyNearOurGoal = ClientsNearObj( ( botInfo->team == GDF ) ? STROGG : GDF ); bool needDefenseAtOurObj = ( priorityCampGoals.Num() > 0 ) ? Bot_CheckNeedClientsOnDefense() : false; if ( priorityCampGoals.Num() > 0 && ( botThreadData.random.RandomInt( 100 ) > 50 || enemyNearOurGoal || needDefenseAtOurObj ) ) { if ( priorityCampGoals.Num() == 1 ) { actionNum = priorityCampGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( priorityCampGoals.Num() ); actionNum = priorityCampGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DEFENSE_CAMP_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 60000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_CampGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } //mal: killing people who are harassing us takes precedence over deployables and roaming/camping... if ( botCamperTarget != -1 ) { if ( botVehicleInfo == NULL ) { int closestVehicle = FindClosestVehicle( HUNT_MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, ARMOR | AIR, PERSONAL | AIR_TRANSPORT, true ); if ( closestVehicle != -1 ) { //mal: if we want to kill a camper, and theres a heavy vehicle around - take it instead of hoofing it. ltgTime = botWorld->gameLocalInfo.time + 60000; ltgTarget = closestVehicle; ltgType = ENTER_VEHICLE_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_EnterVehicleGoal; return true; } aiState = LTG; ltgType = HUNT_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_HuntGoal; ltgTarget = botCamperTarget; ltgTargetSpawnID = botWorld->clientInfo[ botCamperTarget ].spawnID; return true; } else { vLTGTarget = botCamperTarget; vLTGTargetSpawnID = botWorld->clientInfo[ botCamperTarget ].spawnID; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_HuntGoal; return true; } } //mal: killing deployables takes precedence over just roaming/camping around the map. if ( botDeployableTarget != -1 ) { if ( DeployableIsMarkedForDeath( botDeployableTarget, true ) ) { Bot_AddDelayedChat( botNum, ACKNOWLEDGE_YES, 2 ); } if ( botVehicleInfo == NULL ) { int closestVehicle = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, ARMOR | AIR, PERSONAL | AIR_TRANSPORT, true ); if ( closestVehicle != -1 ) { //mal: if we want to kill a deployable, and theres a heavy vehicle around - take it instead of hoofing it. ltgTime = botWorld->gameLocalInfo.time + 60000; ltgTarget = closestVehicle; ltgType = ENTER_VEHICLE_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_EnterVehicleGoal; return true; } ltgTarget = botDeployableTarget; ltgType = DESTROY_DEPLOYABLE_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DestroyDeployable; return true; } else { vLTGTarget = botDeployableTarget; if ( botVehicleInfo->isAirborneVehicle ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_AircraftDestroyDeployable; } else { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_GroundVehicleDestroyDeployable; } return true; } } int randVehicleUse = ( Bot_WantsVehicle() ) ? 100 : 60; //mal: we'll take vehicle based roams/camps first. The bot will prefer vehicle goals if already started one. Else, its a tossup whether he picks one or not. if ( vCampGoals.Num() > 0 || vRoamGoals.Num() > 0 ) { if ( !ClientHasVehicleInWorld( botNum, MAX_VEHICLE_RANGE ) ) { if ( botThreadData.random.RandomInt( 100 ) > randVehicleUse ) { if ( campGoals.Num() > 0 || roamGoals.Num() > 0 ) { //mal: make sure we ALWAYS have something to do before we ditch vehicle goals. vCampGoals.SetNum( 0, false ); //mal: quick and dirty way of wiping out any record of there being goals of this type vRoamGoals.SetNum( 0, false ); } } } else { if ( vCampGoals.Num() > 0 && vRoamGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 50 ) { vCampGoals.SetNum( 0, false ); //mal: quick and dirty way of wiping out any record of there being goals of this type } else { vRoamGoals.SetNum( 0, false ); } } } } if ( vRoamGoals.Num() > 0 ) { if ( vRoamGoals.Num() == 1 ) { actionNum = vRoamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( vRoamGoals.Num() ); actionNum = vRoamGoals[ j ]; } if ( botVehicleInfo == NULL ) { ltgType = ENTER_HEAVY_VEHICLE; ltgTime = botWorld->gameLocalInfo.time + 30000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_UseVehicle; //mal: get a vehicle first PushAINodeOntoStack( -1, -1, actionNum, DEFAULT_LTG_TIME, false, true ); } V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; return true; } if ( vCampGoals.Num() > 0 ) { if ( vCampGoals.Num() == 1 ) { actionNum = vCampGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( vCampGoals.Num() ); actionNum = vCampGoals[ j ]; } if ( botVehicleInfo == NULL ) { ltgType = ENTER_HEAVY_VEHICLE; ltgTime = botWorld->gameLocalInfo.time + 30000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_UseVehicle; //mal: get a vehicle first PushAINodeOntoStack( -1, -1, actionNum, DEFAULT_LTG_TIME, false, true ); } V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_CampGoal; return true; } //mal: next decide if should grab forward spawn. if ( spawnGoal != -1 ) { idVec3 distToSpawn = botThreadData.botActions[ spawnGoal ]->GetActionOrigin() - botInfo->origin; int randChance; if ( distToSpawn.LengthSqr() < Square( 3500.0f ) ) { //mal: if we're close to the spawn, why not just go grab it? randChance = 0; } else { randChance = 50; } if ( botThreadData.random.RandomInt( 100 ) > ( ( botInfo->isDisguised ) ? 20 : randChance ) ) { //mal: higher chance we'll do this when disguised. actionNum = spawnGoal; ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = FDA_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 120000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_SpawnPointGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } } if ( stealSpawnGoal != -1 ) { //mal: deny our enemy his spawn point idVec3 distToSpawn = botThreadData.botActions[ stealSpawnGoal ]->GetActionOrigin() - botInfo->origin; int randChance; if ( distToSpawn.LengthSqr() < Square( 1500.0f ) ) { //mal: if we're close to the spawn, why not just go grab it? randChance = 0; } else { randChance = 70; } if ( botThreadData.random.RandomInt( 100 ) > ( ( botInfo->isDisguised ) ? 10 : randChance ) ) { //mal: higher chance we'll do this when disguised. actionNum = stealSpawnGoal; ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = STEAL_SPAWN_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 120000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_SpawnPointGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } } if ( vehicleGrabGoals.Num() > 0 /* && botThreadData.random.RandomInt( 100 ) > 60 */ ) { if ( vehicleGrabGoals.Num() == 1 ) { actionNum = vehicleGrabGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( vehicleGrabGoals.Num() ); actionNum = vehicleGrabGoals[ j ]; } ltgType = GRAB_VEHICLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + 120000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_UseVehicle; //mal: get a vehicle first PushAINodeOntoStack( -1, -1, actionNum, DEFAULT_LTG_TIME, false, true ); return true; } if ( gunCampGoals.Num() > 0 && botThreadData.random.RandomInt( 100 ) > 70 ) { if ( gunCampGoals.Num() == 1 ) { actionNum = gunCampGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( gunCampGoals.Num() ); actionNum = gunCampGoals[ j ]; } ltgType = MG_CAMP_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_MG_CampGoal; return true; } //mal: next, decide if we should camp or roam, randomly. if ( campGoals.Num() > 0 && roamGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 50 ) { campGoals.SetNum( 0, false ); //mal: quick and dirty way of wiping out any record of there being goals of this type } else { roamGoals.SetNum( 0, false ); } } if ( campGoals.Num() > 0 ) { if ( campGoals.Num() == 1 ) { actionNum = campGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( campGoals.Num() ); actionNum = campGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = CAMP_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_CampGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } if ( roamGoals.Num() > 0 ) { if ( roamGoals.Num() == 1 ) { actionNum = roamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( roamGoals.Num() ); actionNum = roamGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RoamGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } return false; } /* ================== idBotAI::FindCloseSupplyPack Look for a close ammo/health/supply pack. As a courtesy, medics/Fops won't call this function, so they won't steal each others packs. ================== */ bool idBotAI::Bot_FindCloseSupplyPack( bool healthOnly, bool grenadesOnly ) { bool isCrate = false; int i, j; int entNum = -1; int matesInArea; float closest = idMath::INFINITY; float dist; trace_t tr; idVec3 vec; idVec3 otherOrg; for( i = 0; i < MAX_CLIENTS; i++ ) { if ( botWorld->clientInfo[ i ].team != botInfo->team ) { continue; //mal: dont try to grab enemies packs } //mal: first, check for crates. if ( botWorld->clientInfo[ i ].supplyCrate.entNum != 0 && !ItemIsIgnored( botWorld->clientInfo[ i ].supplyCrate.entNum ) ) { if ( botWorld->clientInfo[ i ].supplyCrate.areaNum != 0 ) { vec = botWorld->clientInfo[ i ].supplyCrate.origin - botInfo->origin; dist = vec.LengthSqr(); int travelTime; if ( dist < Square( ITEM_RANGE ) && Bot_LocationIsReachable( false, botWorld->clientInfo[ i ].supplyCrate.origin, travelTime ) ) { //mal: will go out of our way for crates - just because they're more visible and useful to us. closest = dist; entNum = botWorld->clientInfo[ i ].supplyCrate.entNum; isCrate = true; } } } #ifdef PACKS_HAVE_NO_NADES if ( grenadesOnly ) { continue; } #endif for( j = 0; j < MAX_ITEMS; j++ ) { if ( botWorld->clientInfo[ i ].packs[ j ].entNum == 0 ) { continue; } const supplyPackInfo_t& supplyPack = botWorld->clientInfo[ i ].packs[ j ]; if ( ItemIsIgnored( supplyPack.entNum ) ) { continue; } if ( supplyPack.xySpeed > 0.0f && i != botNum ) { //mal: if the pack is moving somehow, ignore, unless its ours continue; } if ( supplyPack.areaNum == 0 ) { //mal: its not in a valid AAS area, ignore! continue; } if ( supplyPack.inWater ) { continue; } if ( !supplyPack.available ) { continue; } if ( !supplyPack.inPlayZone ) { continue; } if ( botInfo->team == GDF ) { if ( healthOnly && botWorld->clientInfo[ i ].classType != MEDIC ) { continue; } if ( !healthOnly && botWorld->clientInfo[ i ].classType != FIELDOPS ) { continue; } } //mal: dont steal packs other classes want to use. If noones around it, its fair game! if ( ( botInfo->classType == MEDIC && healthOnly ) || ( botInfo->classType == FIELDOPS && !healthOnly ) ) { matesInArea = ClientsInArea( botNum, supplyPack.origin, 250.0f, botInfo->team, NOCLASS, false, false, false, false, true ); if ( matesInArea > 0 ) { continue; } } vec = supplyPack.origin - botInfo->origin; dist = vec.LengthFast(); if ( dist > ITEM_RANGE ) { continue; } if ( i != botNum || botInfo->xySpeed > 0.0f ) { if ( !InFrontOfClient( botNum, supplyPack.origin ) ) { continue; } } //mal: if its not in front of us, we don't see it, unless its our pack! otherOrg = supplyPack.origin; otherOrg.z += ITEM_PACK_OFFSET; //mal: raise it up a bit so we can "see" it on uneven terrain. botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, otherOrg, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum )); if ( tr.fraction < 1.0f && tr.c.entityNum != supplyPack.entNum ) { continue; } int travelTime; if ( !Bot_LocationIsReachable( false, otherOrg, travelTime ) ) { continue; } if ( dist < closest ) { closest = dist; entNum = supplyPack.entNum; } } } if ( entNum != -1 ) { nbgTarget = entNum; if ( isCrate ) { nbgTargetType = SUPPLY_CRATE; } else { if ( healthOnly == true ) { nbgTargetType = HEAL; } else { nbgTargetType = REARM; } } ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_GetSupplies; return true; } return false; } /* ================== idBotAI::Bot_FindCloseSupplyTeammate Look for a close mate to bug for supplies. ================== */ bool idBotAI::Bot_FindCloseSupplyTeammate( bool buggerForHealth ) { int i, busyClient; float bestDist = idMath::INFINITY; float dist; trace_t tr; idVec3 vec; if ( enemy != -1 ) { //mal: dont do this if aware of an enemy. return false; } if ( botThreadData.GetBotSkill() == BOT_SKILL_EASY ) { //mal: stupid bot! You aren't smart enough to look for supplies. return false; } //mal: first, check to see if a bot is trying to supply me - we'll stop and let him if someone is trying, UNLESS we're important and on the way to goal - he can just follow us. if ( !Bot_NBGIsAvailable( botNum, ACTION_NULL, SUPPLY_TEAMMATE, busyClient ) && !Client_IsCriticalForCurrentObj( botNum, 2500.0f ) ) { nbgTargetType = NOTYPE; nbgTarget = busyClient; nbgTargetSpawnID = botWorld->clientInfo[ busyClient ].spawnID; ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_BugForSupplies; return true; } if ( buggerForHealth ) { if ( botInfo->health > 30 ) { //mal: got to be in REALLY bad shape to bug for supplies. return false; } } else { if ( botInfo->weapInfo.primaryWeapNeedsAmmo == false ) { return false; } } if ( bugForSuppliesDelay > botWorld->gameLocalInfo.time ) { return false; } if ( botThreadData.random.RandomInt( 100 ) > 70 ) { //mal: randomly decide if we should or shouldn't - if not, dont come here again for a while. bugForSuppliesDelay = botWorld->gameLocalInfo.time + 10000; return false; } busyClient = -1; //mal: noone cares about our pain! Lets find someone to bug into caring! :-P for( i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { continue; } if ( !ClientIsValid( i, -1 ) ) { continue; } const clientInfo_t& playerInfo = botWorld->clientInfo[ i ]; if ( playerInfo.health <= 0 ) { continue; } if ( botInfo->team == GDF ) { if ( buggerForHealth == true ) { if ( playerInfo.classType != MEDIC ) { continue; } } else { if ( playerInfo.classType != FIELDOPS ) { continue; } } } else { if ( playerInfo.classType != MEDIC ) { //mal: strogg go to the medic for everything. continue; } } if ( playerInfo.areaNum == 0 ) { continue; } if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { continue; } if ( playerInfo.team != botInfo->team ) { continue; } if ( playerInfo.isActor ) { continue; } vec = playerInfo.origin - botInfo->origin; dist = vec.LengthFast(); if ( dist > 700.0f ) { continue; } botThreadData.clip->TracePoint( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, playerInfo.viewOrigin, BOT_VISIBILITY_TRACE_MASK, GetGameEntity( botNum )); if ( tr.fraction < 1.0f && tr.c.entityNum != i ) { //mal: have to be able to see the mate to chase him for supplies. continue; } int travelTime; if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) { continue; } if ( dist < bestDist ) { //mal: find the closest one. bestDist = dist; busyClient = i; } } if ( busyClient != -1 ) { nbgTarget = busyClient; nbgTargetSpawnID = botWorld->clientInfo[ busyClient ].spawnID; if ( buggerForHealth == true ) { nbgTargetType = HEAL; } else { nbgTargetType = REARM; } ROOT_AI_NODE = &idBotAI::Run_NBG_Node; NBG_AI_SUB_NODE = &idBotAI::Enter_NBG_BugForSupplies; bugForSuppliesDelay = botWorld->gameLocalInfo.time + 30000; //mal: dont do this again for at LEAST 30 seconds. return true; } return false; } /* ================== idBotAI::Bot_FindDeadWhileInVehicle Look for a close dead mate to revive while we're in a vehicle. ================== */ void idBotAI::Bot_FindDeadWhileInVehicle() { int clientNum = -1; if ( ( botVehicleInfo->isAirborneVehicle && botVehicleInfo->type != ICARUS ) || botVehicleInfo->inWater ) { //mal_FIXME: do we really want the bots to ignore you if they're in a boat??! return; } if ( aiState == VLTG && vLTGType == V_FOLLOW_TEAMMATE ) { //mal: bots won't jump out of vehicles to get to you if they're escorting return; } if ( Bot_IsInHeavyAttackVehicle() ) { //mal: dont do this if in an attack aircraft or tank. return; } if ( botVehicleInfo->driverEntNum != botNum ) { //mal: nothing we can do if we can't control the vehicle. return; } if ( botVehicleInfo->type == MCP ) { return; } clientNum = Bot_MedicCheckForDeadMate( -1, -1, 2500.0f ); if ( clientNum != -1 ) { vLTGTarget = clientNum; vLTGTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_GotoReviveMate; } } /* ================ idBotAI::Bot_CheckForSoldierGoals A soldier specific long term goal check. ================ */ bool idBotAI::Bot_CheckForSoldierGoals() { int i, j, num; float dist; float closest = idMath::INFINITY; proxyInfo_t vehicleInfo; idVec3 vec; idList< int > plantGoals; idList< int > trainingModeRoamGoals; if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { return false; } if ( botWorld->gameLocalInfo.inWarmup ) { return false; } 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_HE_CHARGE ) { if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { trainingModeRoamGoals.Append( i ); continue; } if ( TeamHumanNearLocation( botInfo->team, vec3_zero, -1.0f, false, SOLDIER, false ) && TeamHumanMissionIsObjective() && botThreadData.botActions[ i ]->ActionIsPriority() ) { trainingModeRoamGoals.Append( i ); continue; } int timeDelay = ( botThreadData.botActions[ i ]->ActionIsPriority() ) ? botWorld->gameLocalInfo.botTrainingModeObjDelayTime : 60; //mal: some plants are primary objs, so wait longer. if ( !ActionIsActiveForTrainingMode( i, timeDelay ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. trainingModeRoamGoals.Append( i ); continue; } } if ( ActionIsIgnored( i ) ) { continue; } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( ClientHasChargeInWorld( botNum, true, ACTION_NULL ) ) { //mal: this client has already planted on this action, so ignore it! continue; } if ( botThreadData.botActions[ i ]->ActionIsPriority() == false ) { //mal: secondary actions are not critical. Only 1 bot needs to worry about them. if ( !Bot_LTGIsAvailable( -1, i, PLANT_GOAL, 1 ) ) { continue; } int busyClient; if ( !Bot_NBGIsAvailable( -1, i, PLANT_BOMB, busyClient ) ) { continue; } } plantGoals.Append( i ); continue; } } if ( plantGoals.Num() == 0 && trainingModeRoamGoals.Num() == 0 ) { return false; } //mal: plant actions ALWAYS come first! if ( plantGoals.Num() > 0 ) { if ( plantGoals.Num() == 1 ) { actionNum = plantGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick j = botThreadData.random.RandomInt( plantGoals.Num() ); actionNum = plantGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < plantGoals.Num(); i++ ) { vec = botThreadData.botActions[ plantGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = plantGoals[ i ]; } } idList< int > linkedActionList; FindLinkedActionsForAction( num, plantGoals, linkedActionList ); if ( linkedActionList.Num() == 0 ) { actionNum = num; } else { j = botThreadData.random.RandomInt( linkedActionList.Num() ); actionNum = linkedActionList[ j ]; } } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = PLANT_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it! ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_PlantGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( trainingModeRoamGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 50 ) { if ( trainingModeRoamGoals.Num() == 1 ) { actionNum = trainingModeRoamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( trainingModeRoamGoals.Num() ); actionNum = trainingModeRoamGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RoamGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } } return false; //mal: no soldier goals, find a non-class specific goal to do. } /* ================ idBotAI::Bot_CheckForEngineerGoals A engineer specific long term goal check. ================ */ bool idBotAI::Bot_CheckForEngineerGoals() { bool mcpGoal = false; bool doDeployableGoals = !Bot_HasWorkingDeployable( true ); int minorBuildGoal = -1; int i, j, num; int matesInArea; float dist; float closest = idMath::INFINITY; proxyInfo_t vehicleInfo; idVec3 vec; idList< int > defuseGoals; idList< int > buildGoals; idList< int > mineGoals; idList< int > deployableGoals; idList< int > priorityMineGoals; idList< int > priorityDeployGoals; idList< int > mgNestGoals; // both repair and build. idList< int > trainingModeRoamGoals; if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( botWorld->botGoalInfo.mapHasMCPGoal && botInfo->team == GDF ) { GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, vehicleInfo ); if ( vehicleInfo.entNum != 0 ) { if ( vehicleInfo.health < ( vehicleInfo.maxHealth / 2 ) && vehicleInfo.xyspeed < WALKING_SPEED ) { mcpGoal = true; } } } 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_DEFUSE ) { if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { continue; } if ( TeamHumanNearLocation( botInfo->team, botThreadData.botActions[ i ]->GetActionOrigin(), CLOSE_TO_GOAL_RANGE, false, ENGINEER ) ) { continue; } } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( !botThreadData.botActions[ i ]->ArmedChargesInsideActionBBox( -1 ) ) { continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( 8000.0f ) ) { //mal: its too far away, we'll never make it in time! continue; } defuseGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_MAJOR_OBJ_BUILD ) { if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { trainingModeRoamGoals.Append( i ); continue; } if ( TeamHumanNearLocation( botInfo->team, vec3_zero, -1.0f, false, ENGINEER, false ) && TeamHumanMissionIsObjective() ) { trainingModeRoamGoals.Append( i ); continue; } if ( !ActionIsActiveForTrainingMode( i, botWorld->gameLocalInfo.botTrainingModeObjDelayTime ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. trainingModeRoamGoals.Append( i ); continue; } } if ( ActionIsIgnored( i ) ) { continue; } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( botThreadData.botActions[ i ]->ActionIsPriority() == false ) { //mal: secondary actions are not critical. Only 1 bot needs to worry about them. if ( !Bot_LTGIsAvailable( -1, i, BUILD_GOAL, 1 ) ) { continue; } int busyClient; if ( !Bot_NBGIsAvailable( -1, i, BUILD, busyClient ) ) { continue; } } buildGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DROP_DEPLOYABLE ) { if ( !doDeployableGoals ) { continue; } if ( i == botThreadData.ignoreActionNumber ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botInfo->deployDelayTime > botWorld->gameLocalInfo.time ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, DROP_DEPLOYABLE_GOAL, 1 ) ) { continue; } if ( Bot_GetDeployableTypeForAction( i ) == NULL_DEPLOYABLE ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) > EASY_MODE_CHANCE_WILL_BUILD_DEPLOYABLE_OR_USE_VEHICLE ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == APT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == AVT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) ) { continue; } if ( ( botThreadData.botActions[ i ]->GetDeployableType() & APT ) && ( botThreadData.botActions[ i ]->GetDeployableType() & AVT ) ) { if ( Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } } } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { //mal: in training mode, dont have too many deployables on the ground, as they can overwhelm the player.... if ( botThreadData.botActions[ i ]->GetDeployableType() == APT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == AVT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) ) { continue; } if ( ( botThreadData.botActions[ i ]->GetDeployableType() & APT ) && ( botThreadData.botActions[ i ]->GetDeployableType() & AVT ) ) { if ( Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } } if ( !Bot_LTGIsAvailable( -1, ACTION_NULL, DROP_DEPLOYABLE_GOAL, 1 ) ) { continue; } } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( DEPLOYABLE_GOAL_DIST ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, botInfo->team, ENGINEER, false, false, false, false, true, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } if ( DeployableAtAction( i, true ) ) { continue; } deployableGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_MG_NEST_BUILD || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_MG_NEST ) { if ( ActionIsIgnored( i ) ) { continue; } if ( lastActionNum == i ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { continue; } if ( TeamHumanNearLocation( botInfo->team, vec3_zero, -1.0f, false, ENGINEER, false ) ) { continue; } if ( !ActionIsActiveForTrainingMode( i, 60 ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. trainingModeRoamGoals.Append( i ); continue; } } if ( Bot_WantsVehicle() ) { continue; } if ( botThreadData.botActions[ i ]->GetActionState() == ACTION_STATE_GUN_READY ) { //mal: someone else fixed it. continue; } if ( botVehicleInfo != NULL ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, MG_REPAIR_GOAL, 1 ) ) { continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( MG_REPAIR_MAX_DIST ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, botInfo->team, ENGINEER, false, false, false, false, true, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } mgNestGoals.Append( i ); } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DROP_PRIORITY_DEPLOYABLE ) { if ( !doDeployableGoals ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( i == botThreadData.ignoreActionNumber ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botInfo->deployDelayTime > botWorld->gameLocalInfo.time ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, DROP_PRIORITY_DEPLOYABLE_GOAL, 1 ) ) { continue; } if ( Bot_GetDeployableTypeForAction( i ) == NULL_DEPLOYABLE ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) > EASY_MODE_CHANCE_WILL_BUILD_DEPLOYABLE_OR_USE_VEHICLE ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == APT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == AVT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) ) { continue; } if ( ( botThreadData.botActions[ i ]->GetDeployableType() & APT ) && ( botThreadData.botActions[ i ]->GetDeployableType() & AVT ) ) { if ( Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } } } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { //mal: in training mode, dont have too many deployables on the ground, as they can overwhelm the player.... if ( botThreadData.botActions[ i ]->GetDeployableType() == APT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } if ( botThreadData.botActions[ i ]->GetDeployableType() == AVT && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) ) { continue; } if ( ( botThreadData.botActions[ i ]->GetDeployableType() & APT ) && ( botThreadData.botActions[ i ]->GetDeployableType() & AVT ) ) { if ( Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, AVT, vec3_zero, -1.0f ) && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, APT, vec3_zero, -1.0f ) ) { continue; } } if ( !Bot_LTGIsAvailable( -1, ACTION_NULL, DROP_PRIORITY_DEPLOYABLE_GOAL, 1 ) ) { continue; } } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( DEPLOYABLE_GOAL_DIST ) ) { //mal: goal should be back at base, if too far away, then dont drop one here. continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 300.0f, botInfo->team, ENGINEER, false, false, false, false, true, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } if ( DeployableAtAction( i, true ) ) { continue; } priorityDeployGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_MINOR_OBJ_BUILD ) { if ( ActionIsIgnored( i ) ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { continue; } if ( TeamHumanNearLocation( botInfo->team, vec3_zero, -1.0f, false, ENGINEER, false ) ) { continue; } if ( !ActionIsActiveForTrainingMode( i, 60 ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. trainingModeRoamGoals.Append( i ); continue; } } if ( Bot_WantsVehicle() ) { continue; } minorBuildGoal = i; continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_LANDMINE ) { if ( ActionIsIgnored( i ) ) { continue; } if ( !botWorld->gameLocalInfo.botsUseMines ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( NumPlayerMines() >= MAX_MINES ) { //mal: player has no available mines continue; } if ( botWorld->botGoalInfo.isTrainingMap ) { if ( TeamMineInArea( botThreadData.botActions[ i ]->GetActionOrigin(), 1024.0f ) ) { continue; } } if ( !Bot_LTGIsAvailable( -1, i, MINE_GOAL, 1 ) ) { continue; } if ( !botWorld->gameLocalInfo.teamMineInfo[ botInfo->team ].isPriority ) { if ( !Bot_LTGIsAvailable( -1, ACTION_NULL, MINE_GOAL, 1 ) ) { continue; } } if ( botThreadData.botActions[ i ]->ArmedMinesInsideActionBBox() ) { //mal: someone already planted a mine here continue; } // if ( TeamMineInArea( botThreadData.botActions[ i ]->GetActionOrigin(), MAX_LANDMINE_DIST ) ) { // continue; // } mineGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DEFENSE_MINE ) { //mal: special mine with high priority. if ( ActionIsIgnored( i ) ) { continue; } if ( !botWorld->gameLocalInfo.botsUseMines ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botWorld->botGoalInfo.isTrainingMap ) { if ( TeamMineInArea( botThreadData.botActions[ i ]->GetActionOrigin(), 1024.0f ) ) { continue; } } if ( NumPlayerMines() >= MAX_MINES ) { //mal: player has no available mines continue; } if ( !Bot_LTGIsAvailable( -1, i, PRIORITY_MINE_GOAL, 1 ) ) { continue; } if ( botThreadData.botActions[ i ]->ArmedMinesInsideActionBBox() ) { //mal: someone already planted a mine here continue; } // if ( TeamMineInArea( botThreadData.botActions[ i ]->GetActionOrigin(), MAX_LANDMINE_DIST ) ) { // continue; // } priorityMineGoals.Append( i ); continue; } } if ( defuseGoals.Num() == 0 && buildGoals.Num() == 0 && mineGoals.Num() == 0 && mcpGoal == false && minorBuildGoal == -1 && deployableGoals.Num() == 0 && priorityMineGoals.Num() == 0 && mgNestGoals.Num() == 0 && trainingModeRoamGoals.Num() == 0 ) { return false; } //mal: will always consider defuse goals first! if ( defuseGoals.Num() > 0 ) { if ( defuseGoals.Num() == 1 ) { actionNum = defuseGoals[ 0 ]; //mal: easy enough... } else { closest = idMath::INFINITY; for( i = 0; i < defuseGoals.Num(); i++ ) { //mal: find the closest defuse goal. vec = botThreadData.botActions[ defuseGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = defuseGoals[ i ]; } } actionNum = num; } if ( botVehicleInfo != NULL ) { Bot_ExitVehicle(); } ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it! ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DefuseGoal; return true; } //mal: MCP goals come next if ( mcpGoal ) { GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, vehicleInfo ); if ( vehicleInfo.entNum != 0 ) { if ( botVehicleInfo != NULL && botVehicleInfo->driverEntNum == botNum && botVehicleInfo->type != MCP ) { vLTGOrigin = vehicleInfo.origin; vLTGTargetType = MCP_GOAL; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelToGoalOrigin; } else { Bot_ExitVehicle(); } ltgTarget = botWorld->botGoalInfo.botGoal_MCP_VehicleNum; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FixMCP; //mal: dont need to stack this goal return true; } } if ( priorityDeployGoals.Num() > 0 ) { if ( priorityDeployGoals.Num() == 1 ) { actionNum = priorityDeployGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( priorityDeployGoals.Num() ); actionNum = priorityDeployGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < priorityDeployGoals.Num(); i++ ) { vec = botThreadData.botActions[ priorityDeployGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = priorityDeployGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DROP_PRIORITY_DEPLOYABLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeployableGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( priorityMineGoals.Num() > 0 ) { if ( priorityMineGoals.Num() == 1 ) { actionNum = priorityMineGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( priorityMineGoals.Num() ); actionNum = priorityMineGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < priorityMineGoals.Num(); i++ ) { vec = botThreadData.botActions[ priorityMineGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = priorityMineGoals[ i ]; } } actionNum = num; } } ltgType = PRIORITY_MINE_GOAL; ltgTime = botWorld->gameLocalInfo.time + 30000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_MineGoal; Bot_FindRouteToCurrentGoal(); PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } //mal: we have both a major and minor build available. Sometimes, we'll decide to do the minor if its closer, just for fun //mal: we'll only do this if the minor build is a secondary action - else it won't get done unless we have NOTHING else to do. if ( minorBuildGoal > -1 && buildGoals.Num() > 0 ) { if ( minorBuildGoal == ( ( botInfo->team == GDF ) ? botWorld->botGoalInfo.team_GDF_SecondaryAction : botWorld->botGoalInfo.team_STROGG_SecondaryAction ) ) { if ( botThreadData.random.RandomInt( 100 ) > 70 ) { vec = botThreadData.botActions[ minorBuildGoal ]->GetActionOrigin() - botInfo->origin; float dist1 = vec.LengthSqr(); vec = botThreadData.botActions[ buildGoals[ 0 ] ]->GetActionOrigin() - botInfo->origin; float dist2 = vec.LengthSqr(); if ( dist1 < dist2 ) { //mal: it has to be closer to us then the primary build goal, else its no good. buildGoals.SetNum( 0, false ); } } } } //mal: major build goals come next if ( buildGoals.Num() > 0 ) { if ( buildGoals.Num() == 1 ) { actionNum = buildGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( buildGoals.Num() ); actionNum = buildGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. closest = idMath::INFINITY; for( i = 0; i < buildGoals.Num(); i++ ) { vec = botThreadData.botActions[ buildGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = buildGoals[ i ]; } } idList< int > linkedActionList; FindLinkedActionsForAction( num, buildGoals, linkedActionList ); if ( linkedActionList.Num() == 0 ) { actionNum = num; } else { j = botThreadData.random.RandomInt( linkedActionList.Num() ); actionNum = linkedActionList[ j ]; } } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = BUILD_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it! ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_BuildGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } //mal: dropping a deployable comes next. if ( deployableGoals.Num() > 0 ) { if ( deployableGoals.Num() == 1 ) { actionNum = deployableGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( deployableGoals.Num() ); actionNum = deployableGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < deployableGoals.Num(); i++ ) { vec = botThreadData.botActions[ deployableGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = deployableGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DROP_DEPLOYABLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeployableGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( mineGoals.Num() > 0 && mgNestGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 60 ) { mineGoals.SetNum( 0, false ); minorBuildGoal = -1; //mal: clear this out too - we just want them to build the MG nest at this point. } } //mal: next, consider planting mines. if ( mineGoals.Num() > 0 ) { if ( mineGoals.Num() == 1 ) { actionNum = mineGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( mineGoals.Num() ); actionNum = mineGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < mineGoals.Num(); i++ ) { vec = botThreadData.botActions[ mineGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = mineGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = MINE_GOAL; ltgTime = botWorld->gameLocalInfo.time + 30000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_MineGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( minorBuildGoal > -1 ) { //mal: these get considered next to last. This is towers/MG nests/etc. actionNum = minorBuildGoal; ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = MINOR_BUILD_GOAL; ltgTime = botWorld->gameLocalInfo.time + 60000; //mal: do this for a minute. ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_BuildGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( mgNestGoals.Num() > 0 ) { //mal: these are last. if ( mgNestGoals.Num() == 1 ) { actionNum = mgNestGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( mgNestGoals.Num() ); actionNum = mgNestGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < mgNestGoals.Num(); i++ ) { vec = botThreadData.botActions[ mgNestGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = mgNestGoals[ i ]; } } actionNum = num; } } ltgType = MG_REPAIR_GOAL; ltgTime = botWorld->gameLocalInfo.time + 60000; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RepairGunGoal; Bot_FindRouteToCurrentGoal(); PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, false, ( routeNode != NULL ) ? true : false ); return true; } if ( trainingModeRoamGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 50 ) { if ( trainingModeRoamGoals.Num() == 1 ) { actionNum = trainingModeRoamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( trainingModeRoamGoals.Num() ); actionNum = trainingModeRoamGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RoamGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } } return false; //mal: else, find a normal non-class specific goal to do. } /* ================ idBotAI::Bot_CheckForCovertGoals A covert ops specific long term goal check. ================ */ bool idBotAI::Bot_CheckForCovertGoals() { bool doDeployableGoals = ( !Bot_HasWorkingDeployable( true ) && botInfo->deployDelayTime < botWorld->gameLocalInfo.time && botInfo->deployChargeUsed == 0 ); int i, j, num, matesInArea; int thirdEyeCameraAction = ACTION_NULL; float dist; float closest = idMath::INFINITY; proxyInfo_t vehicleInfo; idVec3 vec; idList< int > hackGoals; idList< int > sniperGoals; idList< int > deployableGoals; idList< int > hiveGoals; idList< int > trainingModeRoamGoals; if ( botWorld->gameLocalInfo.inWarmup ) { return false; } for( i = 0; i < botThreadData.botActions.Num(); i++ ) { if ( !botThreadData.botActions[ i ]->ActionIsActive() ) { continue; } if ( !botThreadData.botActions[ i ]->ActionIsValid() ) { continue; } if ( botWorld->botGoalInfo.attackingTeam == STROGG && botInfo->team == GDF && ( thirdEyeCameraAction == ACTION_NULL ) && !botInfo->isDisguised ) { if ( botThreadData.botActions[ i ]->GetObjForTeam( STROGG ) == ACTION_HACK || botThreadData.botActions[ i ]->GetObjForTeam( STROGG ) == ACTION_MAJOR_OBJ_BUILD || botThreadData.botActions[ i ]->GetObjForTeam( STROGG ) == ACTION_HE_CHARGE ) { if ( botThreadData.botActions[ i ]->ActionIsPriority() && !ActionIsIgnored( i ) ) { if ( ClassWeaponCharged( THIRD_EYE ) && botInfo->weapInfo.covertToolInfo.entNum == 0 ) { thirdEyeCameraAction = i; } } } } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_HACK ) { if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { trainingModeRoamGoals.Append( i ); continue; } if ( TeamHumanNearLocation( botInfo->team, vec3_zero, -1.0f, false, COVERTOPS, false ) && TeamHumanMissionIsObjective() ) { trainingModeRoamGoals.Append( i ); continue; } if ( !ActionIsActiveForTrainingMode( i, botWorld->gameLocalInfo.botTrainingModeObjDelayTime ) ) { //mal: wait a while before we try to complete the goal, to give the player time to decide what he wants to do. trainingModeRoamGoals.Append( i ); continue; } } if ( ActionIsIgnored( i ) ) { continue; } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } if ( botThreadData.botActions[ i ]->ActionIsPriority() == false ) { //mal: secondary actions are not critical. Only 1 bot needs to worry about them. if ( !Bot_LTGIsAvailable( -1, i, HACK_GOAL, 1 ) ) { continue; } int busyClient; if ( !Bot_NBGIsAvailable( -1, i, HACK, busyClient ) ) { continue; } } if ( botWorld->gameLocalInfo.gameMap == VOLCANO && botInfo->team == STROGG ) { //mal: map specific hack - make sure hack obj is reachable before go for it. int travelTime; if ( !Bot_LocationIsReachable( false, botThreadData.botActions[ i ]->GetActionOrigin(), travelTime ) ) { continue; } } hackGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_FLYER_HIVE_LAUNCH ) { if ( ActionIsIgnored( i ) ) { continue; } if ( botWorld->gameLocalInfo.botsIgnoreGoals ) { continue; } if ( botWorld->gameLocalInfo.heroMode != false && TeamHasHuman( botInfo->team ) ) { //mal: the human has decided to complete the maps goals. continue; } if ( !Bot_LTGIsAvailable( -1, i, FLYER_HIVE_GOAL, 1 ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 500.0f, botInfo->team, COVERTOPS, false, false, false, false, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } hiveGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DROP_DEPLOYABLE || botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DROP_PRIORITY_DEPLOYABLE ) { if ( !doDeployableGoals ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botInfo->deployDelayTime > botWorld->gameLocalInfo.time ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botInfo->isDisguised ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, DROP_DEPLOYABLE_GOAL, 1 ) ) { continue; } if ( Bot_GetDeployableTypeForAction( i ) == NULL_DEPLOYABLE ) { continue; } if ( !( botThreadData.botActions[ i ]->GetDeployableType() & RADAR ) ) { continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( 12000.0f /*DEPLOYABLE_GOAL_DIST*/ ) ) { //mal: goal should be back at base, if too far away, then dont drop one here. continue; } if ( Bot_CheckTeamHasDeployableTypeNearAction( botInfo->team, RADAR, i, DEFAULT_DEPLOYABLE_COVERAGE_RANGE ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 700.0f, botInfo->team, COVERTOPS, false, false, false, false, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } if ( DeployableAtAction( i, true ) ) { continue; } deployableGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_SNIPE ) { if ( ActionIsIgnored( i ) ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botInfo->isDisguised ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( botInfo->weapInfo.primaryWeapon != SNIPERRIFLE ) { //mal_TODO: is this a good choice? continue; } if ( !botInfo->weapInfo.primaryWeapHasAmmo ) { //mal: no ammo - dont camp. continue; } if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 150.0f, botInfo->team, COVERTOPS, false, false, false, true, true ); if ( matesInArea > 0 ) { continue; } sniperGoals.Append( i ); continue; } } int botDeployableTarget = Bot_HasDeployableTargetGoals( true ); //mal: only hacks if in disguise. if ( hackGoals.Num() == 0 && sniperGoals.Num() == 0 && deployableGoals.Num() == 0 && hiveGoals.Num() == 0 && botDeployableTarget == -1 && trainingModeRoamGoals.Num() == 0 && thirdEyeCameraAction == ACTION_NULL ) { return false; } if ( deployableGoals.Num() > 0 && Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, RADAR, botInfo->origin, -1.0f ) ) { //mal: if no radar on the map - we NEED to get one down! if ( deployableGoals.Num() == 1 ) { actionNum = deployableGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( deployableGoals.Num() ); actionNum = deployableGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < deployableGoals.Num(); i++ ) { vec = botThreadData.botActions[ deployableGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = deployableGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DROP_DEPLOYABLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeployableGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( thirdEyeCameraAction != ACTION_NULL && botThreadData.random.RandomInt( 100 ) > 50 ) { //mal: sometimes, lets just drop the 3rd eye before we do anything else. actionNum = thirdEyeCameraAction; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_ThirdEyeCameraGoal; return true; } //mal: will always consider hack goals first! if ( hackGoals.Num() > 0 ) { if ( hackGoals.Num() == 1 ) { actionNum = hackGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. There really shouldn't ever be more then 1 hack goal, but just in case.... j = botThreadData.random.RandomInt( hackGoals.Num() ); actionNum = hackGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < hackGoals.Num(); i++ ) { vec = botThreadData.botActions[ hackGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = hackGoals[ i ]; } } idList< int > linkedActionList; FindLinkedActionsForAction( num, hackGoals, linkedActionList ); if ( linkedActionList.Num() == 0 ) { actionNum = num; } else { j = botThreadData.random.RandomInt( linkedActionList.Num() ); actionNum = linkedActionList[ j ]; } } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = HACK_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it! ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_HackGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } //mal: consider hive goals next if ( hiveGoals.Num() > 0 ) { if ( hiveGoals.Num() == 1 ) { actionNum = hiveGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. There really shouldn't ever be more then 1 hive goal, but just in case.... j = botThreadData.random.RandomInt( hiveGoals.Num() ); actionNum = hiveGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < hiveGoals.Num(); i++ ) { vec = botThreadData.botActions[ hiveGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = hiveGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = FLYER_HIVE_GOAL; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: do this forever, until we die or complete it! ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FlyerHiveGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( deployableGoals.Num() > 0 ) { if ( deployableGoals.Num() == 1 ) { actionNum = deployableGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( deployableGoals.Num() ); actionNum = deployableGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < deployableGoals.Num(); i++ ) { vec = botThreadData.botActions[ deployableGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = deployableGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DROP_DEPLOYABLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeployableGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( botDeployableTarget != -1 ) { ltgTarget = botDeployableTarget; ltgType = DESTROY_DEPLOYABLE_GOAL; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_HackDeployableGoal; return true; } //mal: next, consider sniping. if ( sniperGoals.Num() > 0 ) { if ( sniperGoals.Num() == 1 ) { actionNum = sniperGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( sniperGoals.Num() ); actionNum = sniperGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < sniperGoals.Num(); i++ ) { vec = botThreadData.botActions[ sniperGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = sniperGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = SNIPE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_SnipeGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( trainingModeRoamGoals.Num() > 0 ) { if ( botThreadData.random.RandomInt( 100 ) > 50 ) { if ( trainingModeRoamGoals.Num() == 1 ) { actionNum = trainingModeRoamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( trainingModeRoamGoals.Num() ); actionNum = trainingModeRoamGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RoamGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } } if ( thirdEyeCameraAction != ACTION_NULL ) { actionNum = thirdEyeCameraAction; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_ThirdEyeCameraGoal; return true; } return false; //mal: else, find a normal non-class specific goal to do. } /* ================ idBotAI::Bot_CheckForEscortGoals A non-class specific long term goal check for escorting important teammates. ================ */ bool idBotAI::Bot_CheckForEscortGoals() { int i; int clientNum = -1; int allowedEscorts = 1; //mal: normally, only 1 bot might escort you. In hero/demo mode, upto 3 will follow you. float dist; float closest = idMath::INFINITY; playerClassTypes_t criticalClass = ( botInfo->team == GDF ) ? botWorld->botGoalInfo.team_GDF_criticalClass : botWorld->botGoalInfo.team_STROGG_criticalClass; proxyInfo_t vehicleInfo; idVec3 vec; if ( botVehicleInfo != NULL ) { return false; } if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( Bot_WantsVehicle() ) { return false; } if ( botInfo->isDisguised ) { //mal: looks silly to be in disguise, escorting someone return false; } if ( botInfo->weapInfo.primaryWeapon == ROCKET || botInfo->weapInfo.primaryWeapon == SNIPERRIFLE ) { //mal: we're not good at escorting.. return false; } if ( botWorld->botGoalInfo.isTrainingMap && ( !botThreadData.actorMissionInfo.playerIsOnFinalMission || botThreadData.actorMissionInfo.playerNeedsFinalBriefing ) ) { return false; } if ( botInfo->classType == criticalClass && botWorld->gameLocalInfo.heroMode == false ) { //mal: we're too important to escort others. return false; } if ( botInfo->classType == MEDIC && Bot_HasTeamWoundedInArea( false ) && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO ) { return false; } if ( botWorld->botGoalInfo.attackingTeam == botInfo->team ) { if ( botWorld->gameLocalInfo.heroMode || botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { allowedEscorts = 2; } } else { if ( botWorld->gameLocalInfo.heroMode == false && botWorld->gameLocalInfo.botSkill != BOT_SKILL_DEMO && botWorld->gameLocalInfo.botSkill != BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) < 50 ) { return false; } } else { allowedEscorts = 2; }//mal: if we're not excluded from doing major goals, we'll randomly decide to not do this. If we are excluded, then we'll always want to escort critical teammates! } for( i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { //mal: dont scan ourselves. continue; } if ( !ClientIsValid( i, -1 ) ) { continue; //mal: no valid client in this client slot! } const clientInfo_t& playerInfo = botWorld->clientInfo[ i ]; if ( playerInfo.health <= 0 ) { continue; } if ( playerInfo.team != botInfo->team ) { continue; } if ( playerInfo.isDisguised ) { continue; } if ( playerInfo.isBot ) { //mal: dont follow a bot thats following someone else. if ( botThreadData.bots[ i ] == NULL ) { continue; } if ( botThreadData.bots[ i ]->GetAIState() == LTG && ( botThreadData.bots[ i ]->GetLTGType() == FOLLOW_TEAMMATE || botThreadData.bots[ i ]->GetLTGType() == FOLLOW_TEAMMATE_BY_REQUEST ) ) { continue; } } vec = playerInfo.origin - botInfo->origin; dist = vec.LengthSqr(); int tempAllowedEscorts = allowedEscorts; float maxEscortDist = 1900.0f; if ( ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO || botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) && !playerInfo.isBot ) { maxEscortDist *= 3.5f; tempAllowedEscorts = 3; } if ( dist > Square( maxEscortDist ) ) { //mal: they're too far away for us to start following! continue; } if ( !Client_IsCriticalForCurrentObj( i, -1.0f ) && !ClientHasObj( i ) && playerInfo.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) { //mal: this bozo isn't worth following! Check if he has a vehicle. continue; } if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { GetVehicleInfo( playerInfo.proxyInfo.entNum, vehicleInfo ); //mal: we may decide to ride along in his vehicle to cause trouble if he has a gunner seat open. if ( vehicleInfo.driverEntNum != i ) { continue; } if ( !VehicleIsValid( vehicleInfo.entNum ) ) { //mal: make sure it has a seat open, and is easy to reach. continue; } if ( VehicleIsIgnored( vehicleInfo.entNum ) ) { continue; } if ( vehicleInfo.type == ANANSI || vehicleInfo.type == HORNET ) { //mal: no point being a gunner in these vehicles. continue; } } idList< int > busyClients; int botsFollowingThisClient = Bot_NumClientsDoingLTGGoal( i, ACTION_NULL, FOLLOW_TEAMMATE, busyClients ); botsFollowingThisClient += Bot_NumClientsDoingLTGGoal( i, ACTION_NULL, FOLLOW_TEAMMATE_BY_REQUEST, busyClients ); if ( botsFollowingThisClient >= tempAllowedEscorts ) { continue; } int travelTime; if ( !Bot_LocationIsReachable( false, playerInfo.origin, travelTime ) ) { continue; } if ( dist < closest ) { //mal: pick the closest client to escort. closest = dist; clientNum = i; } } if ( clientNum != -1 ) { aiState = LTG; ltgTarget = clientNum; ltgType = FOLLOW_TEAMMATE; ltgTargetSpawnID = botWorld->clientInfo[ clientNum ].spawnID; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FollowMate; return true; } return false; //mal: else, find a normal non-class specific goal to do. } /* ================ idBotAI::Bot_CheckForFieldOpsGoals A FieldOps specific long term goal check. They only have one primary LTG - to rain fire support down on you! ================ */ bool idBotAI::Bot_CheckForFieldOpsGoals() { bool doFireSupportGoals = true; bool doDeployableGoals = !Bot_HasWorkingDeployable( true ); int i, j, num; int randNum; int matesInArea; int numFOps = botThreadData.GetNumClassOnTeam( botInfo->team, botInfo->classType ); float dist; float closest = idMath::INFINITY; proxyInfo_t vehicleInfo; idVec3 vec; idList< int > fireSupportGoals; idList< int > deployableGoals; if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( numFOps == 1 ) { randNum = 70; } else if ( numFOps == 2 ) { randNum = 50; } else { randNum = 30; } if ( botThreadData.random.RandomInt( 100 ) > randNum ) { //mal: will randomly decide to do this based on how many FOps we have on the team already. doFireSupportGoals = false; } 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_FIRESUPPORT ) { if ( !doFireSupportGoals ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( ActionIsIgnored( i ) ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( i == lastActionNum ) { //mal: don't repeat ourselves continue; } if ( botThreadData.botActions[ i ]->actionTargets[ 0 ].inuse == false ) { if ( botThreadData.AllowDebugData() ) { botThreadData.Printf("^1Warning: Fire Support Action %s has no target!\n", botThreadData.botActions[ i ]->name.c_str() ); } continue; } if ( !Bot_HasWorkingDeployable() ) { continue; } if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } idVec3 targetOrg = botThreadData.botActions[ i ]->actionTargets[ 0 ].origin; if ( Bot_EnemyAITInArea( targetOrg ) ) { continue; } if ( ClientHasFireSupportInWorld( botNum ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, FIRESUPPORT_CAMP_GOAL, 1 ) ) { //mal: some bot is already doing this. continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 350.0f, botInfo->team, FIELDOPS, false, false, false, false, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } fireSupportGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_DROP_DEPLOYABLE ) { if ( !doDeployableGoals ) { continue; } if ( Bot_IsInHeavyAttackVehicle() ) { continue; } if ( Bot_WantsVehicle() ) { continue; } if ( botInfo->deployDelayTime > botWorld->gameLocalInfo.time ) { continue; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_EASY ) { if ( botThreadData.random.RandomInt( 100 ) > EASY_MODE_CHANCE_WILL_BUILD_DEPLOYABLE_OR_USE_VEHICLE ) { continue; } if ( Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, NUKE, vec3_zero, -1.0f ) || Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, ARTILLERY, vec3_zero, -1.0f ) || Bot_CheckTeamHasDeployableTypeNearLocation( botInfo->team, ROCKET_ARTILLERY, vec3_zero, -1.0f ) ) { continue; } } if ( ActionIsIgnored( i ) ) { continue; } if ( !Bot_LTGIsAvailable( -1, i, DROP_DEPLOYABLE_GOAL, 1 ) ) { continue; } if ( !( botThreadData.botActions[ i ]->GetDeployableType() & NUKE ) && !( botThreadData.botActions[ i ]->GetDeployableType() & ARTILLERY ) && !( botThreadData.botActions[ i ]->GetDeployableType() & ROCKET_ARTILLERY ) ) { continue; } if ( Bot_GetDeployableTypeForAction( i ) == NULL_DEPLOYABLE ) { continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( DEPLOYABLE_GOAL_DIST ) ) { //mal: goal should be back at base, if too far away, then dont drop one here. continue; } matesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 700.0f, botInfo->team, FIELDOPS, false, false, false, false, true ); if ( matesInArea > 0 ) { //mal: theres prolly a human already there doing this, no need to double team it. continue; } if ( DeployableAtAction( i, true ) ) { continue; } deployableGoals.Append( i ); continue; } } if ( fireSupportGoals.Num() == 0 && deployableGoals.Num() == 0 ) { return false; } if ( deployableGoals.Num() > 0 ) { if ( deployableGoals.Num() == 1 ) { actionNum = deployableGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( deployableGoals.Num() ); actionNum = deployableGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < deployableGoals.Num(); i++ ) { vec = botThreadData.botActions[ deployableGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthSqr(); if ( dist < closest ) { closest = dist; num = deployableGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = DROP_DEPLOYABLE_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_DeployableGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } if ( fireSupportGoals.Num() > 0 ) { if ( fireSupportGoals.Num() == 1 ) { actionNum = fireSupportGoals[ 0 ]; //mal: easy enough... } else { if ( botInfo->spawnTime + 5000 > botWorld->gameLocalInfo.time ) { //mal: if we're just born, randomly pick. j = botThreadData.random.RandomInt( fireSupportGoals.Num() ); actionNum = fireSupportGoals[ j ]; } else { //mal: else sort thru the list and find the closest one. for( i = 0; i < fireSupportGoals.Num(); i++ ) { vec = botThreadData.botActions[ fireSupportGoals[ i ] ]->GetActionOrigin() - botInfo->origin; dist = vec.LengthFast(); if ( dist < closest ) { closest = dist; num = fireSupportGoals[ i ]; } } actionNum = num; } } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ltgType = FIRESUPPORT_CAMP_GOAL; ltgTime = botWorld->gameLocalInfo.time + DEFAULT_LTG_TIME; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_FireSupportGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } return false; } /* ================ idBotAI::Bot_CheckForTacticalAction This works in two parts - first we check if we're already in the process of doing a tactical action, and whether or not its still valid. If not, then we check if we should do a tactical action - what what kind of action that should be, based on our class and situation. ================ */ int idBotAI::Bot_CheckForTacticalAction() { botActionGoals_t actionFilter1 = ACTION_NULL; botActionGoals_t actionFilter2 = ACTION_NULL; //mal: currently, all classes use this for the grenade. botActionGoals_t actionFilter3 = ACTION_NULL; botActionGoals_t actionType; idBox playerBox; if ( tacticalActionIgnoreTime > botWorld->gameLocalInfo.time ) { return -1; } if ( tacticalActionPauseTime > botWorld->gameLocalInfo.time ) { return TACTICAL_PAUSE_ACTION; } if ( botInfo->classType == FIELDOPS ) { if ( tacticalActionTime > botWorld->gameLocalInfo.time ) { if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_AIRCAN_HINT ) { if ( !ClassWeaponCharged( AIRCAN ) ) { return -1; } } if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_SHIELD_HINT ) { if ( !ClassWeaponCharged( SHIELD_GUN ) ) { return -1; } if ( botInfo->deviceChargeUsed > tacticalDeviceCharge ) { tacticalActionIgnoreTime = botWorld->gameLocalInfo.time + 1000; return -1; } } } if ( ClassWeaponCharged( AIRCAN ) ) { actionFilter1 = ACTION_AIRCAN_HINT; } if ( botInfo->team == STROGG && ClassWeaponCharged( SHIELD_GUN ) ) { actionFilter3 = ACTION_SHIELD_HINT; } } if ( botInfo->classType == COVERTOPS ) { if ( tacticalActionTime > botWorld->gameLocalInfo.time ) { if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_SMOKE_HINT ) { if ( !ClassWeaponCharged( SMOKE_NADE ) ) { return -1; } } if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_THIRDEYE_HINT ) { if ( !ClassWeaponCharged( THIRD_EYE ) ) { return -1; } } if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_FLYER_HIVE_HINT ) { if ( botInfo->weapInfo.covertToolInfo.entNum == 0 && tacticalActionTimer > 0 ) { return -1; } if ( ( tacticalActionTime - botWorld->gameLocalInfo.time ) < 200 ) { botUcmd->botCmds.attack = true; return -1; } } if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_TELEPORTER_HINT ) { if ( !ClassWeaponCharged( TELEPORTER ) && tacticalActionTimer2 < botWorld->gameLocalInfo.time ) { botIdealWeapNum = TELEPORTER; botIdealWeapSlot = NO_WEAPON; if ( botInfo->weapInfo.weapon == TELEPORTER ) { botUcmd->botCmds.attack = true; return -1; } } } } if ( botInfo->isDisguised == false && botInfo->team == GDF && ClassWeaponCharged( SMOKE_NADE ) ) { actionFilter1 = ACTION_SMOKE_HINT; } if ( botInfo->isDisguised == false && botInfo->team == GDF && ClassWeaponCharged( THIRD_EYE ) && botInfo->weapInfo.covertToolInfo.entNum == 0 ) { actionFilter3 = ACTION_THIRDEYE_HINT; } if ( botInfo->isDisguised == false && botInfo->team == STROGG && ClassWeaponCharged( TELEPORTER ) && !ClientHasObj( botNum ) ) { actionFilter1 = ACTION_TELEPORTER_HINT; } if ( botInfo->isDisguised == false && botInfo->team == STROGG && ClassWeaponCharged( FLYER_HIVE ) && !ClientHasObj( botNum ) && !Client_IsCriticalForCurrentObj( botNum, -1.0f ) && ClientsInArea( botNum, botInfo->origin, 350.0f, STROGG, COVERTOPS, false, false, false, true, true ) <= 1 ) { actionFilter3 = ACTION_FLYER_HIVE_HINT; } } if ( botInfo->classType == MEDIC ) { if ( tacticalActionTime > botWorld->gameLocalInfo.time ) { if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_SUPPLY_HINT ) { if ( !ClassWeaponCharged( SUPPLY_MARKER ) ) { return -1; } } } if ( botInfo->supplyCrate.entNum == 0 && botInfo->team == GDF && ClassWeaponCharged( SUPPLY_MARKER ) && !Bot_CheckIfHealthCrateInArea( 1024.0f ) ) { actionFilter1 = ACTION_SUPPLY_HINT; } } if ( tacticalActionTime > botWorld->gameLocalInfo.time ) { if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) == ACTION_NADE_HINT ) { if ( lastGrenadeTime + 5000 > botWorld->gameLocalInfo.time || botInfo->weapInfo.hasNadeAmmo == false && tacticalActionPauseTime < botWorld->gameLocalInfo.time ) { tacticalActionPauseTime = botWorld->gameLocalInfo.time + 3000; tacticalActionTime = tacticalActionPauseTime; return TACTICAL_PAUSE_ACTION; } } } if ( lastGrenadeTime + 10000 < botWorld->gameLocalInfo.time ) { if ( botInfo->weapInfo.hasNadeAmmo != false ) { actionFilter2 = ACTION_NADE_HINT; } } if ( tacticalActionTime > botWorld->gameLocalInfo.time ) { return tacticalActionNum; } if ( actionFilter1 == ACTION_NULL && actionFilter2 == ACTION_NULL && actionFilter3 == ACTION_NULL ) { return -1; } tacticalActionNum = ACTION_NULL; for( int i = 0; i < botThreadData.botActions.Num(); i++ ) { if ( !botThreadData.botActions[ i ]->ActionIsActive() ) { //mal: if action is turned off, ignore continue; } if ( !botThreadData.botActions[ i ]->ActionIsValid() ) { continue; } actionType = botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ); if ( ( actionFilter1 == ACTION_NULL || actionType != actionFilter1 ) && ( actionFilter2 == ACTION_NULL || actionType != actionFilter2 ) && ( actionFilter3 == ACTION_NULL || actionType != actionFilter3 ) ) { continue; } playerBox = idBox( botInfo->localBounds, botInfo->origin, botInfo->bodyAxis ); if ( !botThreadData.botActions[ i ]->actionBBox.IntersectsBox( playerBox ) ) { continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_AIRCAN_HINT ) { int enemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 1700.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false ); int friendsInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 1700.0f, botInfo->team , NOCLASS, false, false, false, false, false ); bool deployablesInArea = Bot_CheckTeamHasDeployableTypeNearAction( ( botInfo->team == GDF ) ? STROGG : GDF, NULL_DEPLOYABLE, i, 1700.0f ); if ( ( enemiesInArea == 0 && deployablesInArea == false ) || friendsInArea > 1 ) { //mal: no target of value in this area, or too many friendlies, so dont bother. continue; } } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_NADE_HINT ) { int enemiesInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 500.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false ); int friendsInArea = ClientsInArea( botNum, botThreadData.botActions[ i ]->GetActionOrigin(), 500.0f, botInfo->team , NOCLASS, false, false, false, false, false ); bool deployablesInArea = Bot_CheckTeamHasDeployableTypeNearAction( ( botInfo->team == GDF ) ? STROGG : GDF, NULL_DEPLOYABLE, i, 500.0f ); if ( ( enemiesInArea == 0 && deployablesInArea == false ) || friendsInArea > 1 ) { //mal: no target of value in this area, or too many friendlies, so dont bother. continue; } } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_SUPPLY_HINT ) { //make sure no crates in area. bool dropCrate = true; for( int k = 0; k < MAX_CLIENTS; k++ ) { const clientInfo_t& player = botWorld->clientInfo[ k ]; if ( player.supplyCrate.entNum == 0 ) { continue; } idVec3 vec = player.supplyCrate.origin - botThreadData.botActions[ i ]->GetActionOrigin(); if ( vec.LengthSqr() < Square( ITEM_RANGE * 2.0f ) ) { dropCrate = false; break; } } if ( !dropCrate ) { continue; } } tacticalActionNum = i; tacticalActionTimer = 0; tacticalActionTimer2 = 0; tacticalActionOrigin = vec3_zero; tacticalDeviceCharge = botInfo->deviceChargeUsed; tacticalActionIgnoreTime = 0; tacticalActionPauseTime = 0; if ( botThreadData.botActions[ tacticalActionNum ]->GetObjForTeam( botInfo->team ) != ACTION_FLYER_HIVE_HINT ) { tacticalActionTime = botWorld->gameLocalInfo.time + TACTICAL_ACTION_TIME; ltgTime += TACTICAL_ACTION_TIME; //mal: give us some extra time to complete our long term goal, while we pause for a sec for this tactical one. } else { tacticalActionTime = botWorld->gameLocalInfo.time + TACTICAL_HIVE_TIME; ltgTime += TACTICAL_HIVE_TIME; //mal: give us some extra time to complete our long term goal, while we pause for a sec for this tactical one. } break; } //mal_FIXME: add more tactical actions for more classes! return tacticalActionNum; } /* ================ idBotAI::Bot_CheckForDroppedObjGoals ================ */ bool idBotAI::Bot_CheckForDroppedObjGoals() { if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( ClientHasObj( botNum ) ) { return false; } if ( Bot_WantsVehicle() ) { return false; } if ( botInfo->isActor ) { return false; } int bestObj = -1; int vehicleNum; float closest = idMath::INFINITY; for( int i = 0; i < MAX_CARRYABLES; i++ ) { if ( !botWorld->botGoalInfo.carryableObjs[ i ].onGround || botWorld->botGoalInfo.carryableObjs[ i ].entNum == 0 ) { continue; } if ( botWorld->botGoalInfo.carryableObjs[ i ].areaNum == 0 ) { continue; } idVec3 vec = botWorld->botGoalInfo.carryableObjs[ i ].origin - botInfo->origin; float dist = vec.LengthSqr(); if ( dist > Square( BOT_RECOVER_OBJ_RANGE ) ) { continue; } if ( dist < closest ) { bestObj = i; closest = dist; } } if ( bestObj == -1 ) { return false; } ltgTarget = bestObj; idVec3 vec = botWorld->botGoalInfo.carryableObjs[ ltgTarget ].origin - botInfo->origin; if ( vec.LengthSqr() > Square( 3000.0f ) && botWorld->gameLocalInfo.botsUseVehicles ) { vehicleNum = FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, GROUND | AIR, NULL_VEHICLE_FLAGS, true ); if ( vehicleNum != -1 ) { ltgUseVehicle = true; } else { ltgUseVehicle = false; } } else { ltgUseVehicle = false; } if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_TravelGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } if ( botWorld->botGoalInfo.carryableObjs[ ltgTarget ].ownerTeam == botInfo->team ) { ltgType = RECOVER_GOAL; } else { ltgType = STEAL_GOAL; } ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RecoverDroppedGoal; if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, ltgTarget, ACTION_NULL, ltgTime, true, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); return true; } /* ================ idBotAI::Bot_CheckMCPGoals ================ */ bool idBotAI::Bot_CheckMCPGoals() { if ( botVehicleInfo != NULL && botVehicleInfo->type != MCP ) { return false; } if ( botWorld->gameLocalInfo.heroMode && TeamHasHuman( botInfo->team ) ) { return false; } if ( Bot_WantsVehicle() ) { return false; } if ( botWorld->gameLocalInfo.inWarmup ) { return false; } if ( !botWorld->botGoalInfo.mapHasMCPGoal ) { return false; } if ( Bot_IsInHeavyAttackVehicle() ) { return false; } proxyInfo_t mcp; GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcp ); if ( mcp.entNum == 0 ) { return false; } if ( mcp.isDeployed ) { return false; } if ( botWorld->gameLocalInfo.botSkill == BOT_SKILL_DEMO && mcp.driverEntNum != botNum ) { if ( !botWorld->gameLocalInfo.botsDoObjsInTrainingMode ) { return false; } if ( TeamHasHuman( botInfo->team ) && ( botWorld->botGoalInfo.mapHasMCPGoalTime + ( botWorld->gameLocalInfo.botTrainingModeObjDelayTime * 1000 ) ) > botWorld->gameLocalInfo.time ) { return false; } } if ( mcp.isImmobilized ) { return false; } if ( mcp.driverEntNum != -1 && mcp.driverEntNum != botNum ) { return false; } idVec3 vec = mcp.origin - botInfo->origin; if ( vec.LengthSqr() > Square( MAX_MCP_ATTRACT_DIST ) ) { return false; } int mcpActionNum = -1; for( int i = 0; i < botThreadData.botActions.Num(); i++ ) { if ( !botThreadData.botActions[ i ]->ActionIsActive() ) { continue; } if ( !botThreadData.botActions[ i ]->ActionIsValid() ) { continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) != ACTION_MCP_OUTPOST ) { continue; } mcpActionNum = i; break; } if ( mcpActionNum == -1 ) { return false; } ignoreMCPRouteAction = false; if ( botVehicleInfo != NULL && botVehicleInfo->actionRouteNumber != ACTION_NULL && !ActionIsIgnored( botVehicleInfo->actionRouteNumber ) ) { //mal: check to see if we should go to a MCP route goal first, to give us varied paths. idVec3 vec = botThreadData.botActions[ botVehicleInfo->actionRouteNumber ]->GetActionOrigin() - botInfo->origin; if ( vec.LengthSqr() > Square( botThreadData.botActions[ botVehicleInfo->actionRouteNumber ]->GetRadius() ) ) { vec = botThreadData.botActions[ mcpActionNum ]->GetActionOrigin() - botInfo->origin; float ourDistToOutPost = vec.LengthSqr(); vec = botThreadData.botActions[ botVehicleInfo->actionRouteNumber ]->GetActionOrigin() - botThreadData.botActions[ mcpActionNum ]->GetActionOrigin(); float routeDistToOutPost = vec.LengthSqr(); if ( routeDistToOutPost < ourDistToOutPost ) { actionNum = botVehicleInfo->actionRouteNumber; vLTGType = V_DRIVE_MCP_ROUTE; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_DriveMCPToRouteGoal; return true; } } } if ( botVehicleInfo != NULL ) { ignoreMCPRouteAction = true; actionNum = mcpActionNum; vLTGType = V_DRIVE_MCP; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_DriveMCPToGoal; return true; } else { vLTGType = V_DRIVE_MCP; V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_DriveMCPToGoal; ltgTarget = botWorld->botGoalInfo.botGoal_MCP_VehicleNum; ltgType = DRIVE_MCP; ltgTime = botWorld->gameLocalInfo.time + BOT_INFINITY; actionNum = mcpActionNum; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_UseVehicle; return true; } return false; } /* ================ idBotAI::Bot_CheckForVehicleFallBackGoals Sometimes theres nothing available for the bots to do. So go thru and find something productive. ================ */ bool idBotAI::Bot_CheckForVehicleFallBackGoals() { if ( botThreadData.AllowDebugData() ) { botThreadData.Printf("Bot Vehicle: %i used a fallback goal!\n", botNum ); } idList< int > vehicleRoamGoals; for( int i = 0; i < botThreadData.botActions.Num(); i++ ) { if ( !botThreadData.botActions[ i ]->ActionIsActive() ) { continue; } if ( !botThreadData.botActions[ i ]->ActionIsValid() ) { continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_VEHICLE_ROAM ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->flags & PERSONAL ) { continue; } if ( !( botVehicleInfo->flags & botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) ) && botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) != 0 ) { continue; } //mal: the vehicle we're in cant handle doing this action. Bots want to stay in their current vehicle unless they MUST exit it. } else { if ( FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ), PERSONAL | AIR_TRANSPORT, true ) == -1 ) { continue; } } vehicleRoamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( botInfo->team ) == ACTION_VEHICLE_CAMP ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->flags & PERSONAL ) { continue; } if ( !( botVehicleInfo->flags & botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) ) && botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ) != 0 ) { continue; } //mal: the vehicle we're in cant handle doing this action. Bots want to stay in their current vehicle unless they MUST exit it. } else { if ( FindClosestVehicle( MAX_VEHICLE_RANGE, botInfo->origin, NULL_VEHICLE, botThreadData.botActions[ i ]->GetActionVehicleFlags( botInfo->team ), PERSONAL | AIR_TRANSPORT, true ) == -1 ) { continue; } } vehicleRoamGoals.Append( i ); continue; } } if ( vehicleRoamGoals.Num() > 0 ) { if ( vehicleRoamGoals.Num() == 1 ) { actionNum = vehicleRoamGoals[ 0 ]; } else { int j = botThreadData.random.RandomInt( vehicleRoamGoals.Num() ); actionNum = vehicleRoamGoals[ j ]; } V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_CampGoal; return true; } return false; } /* ================ idBotAI::Bot_CheckForFallBackGoals Sometimes theres nothing available for the bots to do. So go thru and find something productive. ================ */ bool idBotAI::Bot_CheckForFallBackGoals() { ltgUseVehicle = false; int j; playerTeamTypes_t teamFilter = botInfo->team; playerTeamTypes_t covertTeamFilter = botInfo->team; idVec3 vec; idList< int > roamGoals; if ( botThreadData.AllowDebugData() ) { botThreadData.Printf("Bot Client: %i used a fallback goal!\n", botNum ); } if ( botInfo->isDisguised ) { //mal: if the bots disguised, will do the enemys' camp/roam goals. covertTeamFilter = ( botInfo->team == GDF ) ? STROGG : GDF; } for( int i = 0; i < botThreadData.botActions.Num(); i++ ) { if ( !botThreadData.botActions[ i ]->ActionIsActive() ) { continue; } if ( !botThreadData.botActions[ i ]->ActionIsValid() ) { continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DELIVER ) { //mal: only 1 deliver point on a map at a time. if ( botWorld->gameLocalInfo.inWarmup ) { continue; } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_CAMP || botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_DEFENSE_CAMP ) { bool isPriority = ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_DEFENSE_CAMP ) ? true : false; if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } if ( botInfo->isDisguised ) { if ( !botThreadData.botActions[ i ]->disguiseSafe ) { //mal: not a good one for a disguised covert. continue; } } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( covertTeamFilter ) == ACTION_ROAM ) { if ( !Bot_MeetsVehicleRequirementsForAction( i ) ) { continue; } if ( botInfo->isDisguised ) { if ( !botThreadData.botActions[ i ]->disguiseSafe ) { //mal: not a good one for a disguised covert. continue; } } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_FORWARD_SPAWN ) { if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team ) { //mal: it already belongs to us. continue; } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DENY_SPAWNPOINT ) { if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( botThreadData.botActions[ i ]->GetTeamOwner() == botInfo->team || botThreadData.botActions[ i ]->GetTeamOwner() == NOTEAM ) { //mal: it already belongs to us. continue; } roamGoals.Append( i ); continue; } if ( botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_DEFUSE || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_PREVENT_BUILD || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_PREVENT_HACK || botThreadData.botActions[ i ]->GetObjForTeam( teamFilter ) == ACTION_PREVENT_STEAL ) { if ( botWorld->gameLocalInfo.inWarmup ) { continue; } if ( !botThreadData.botActions[ i ]->ActionIsPriority() ) { //mal: not worth worrying about. continue; } vec = botThreadData.botActions[ i ]->GetActionOrigin() - botInfo->origin; float distSqr = vec.LengthSqr(); if ( distSqr > Square( 6000.0f ) ) { //mal: its too far away, we'll never make it in time! continue; } roamGoals.Append( i ); continue; } } if ( roamGoals.Num() == 0 ) { //mal: if this happens, then we are totally hosed, or theres no goals on the map at all. if ( botThreadData.AllowDebugData() ) { botThreadData.Printf("^1No goals available for bot %i!!!\n", botNum ); } return false; } if ( roamGoals.Num() > 0 ) { if ( roamGoals.Num() == 1 ) { actionNum = roamGoals[ 0 ]; } else { j = botThreadData.random.RandomInt( roamGoals.Num() ); actionNum = roamGoals[ j ]; } ltgUseVehicle = Bot_ShouldUseVehicleForAction( actionNum, true ); if ( ltgUseVehicle ) { if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->driverEntNum == botNum ) { V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RoamGoal; } else { Bot_ExitVehicle(); } } } else { if ( botVehicleInfo != NULL ) { //mal: if we're close to the goal, or theres some other reason not to use a vehicle for this goal, then exit our vehicle Bot_ExitVehicle(); } } ROOT_AI_NODE = &idBotAI::Run_LTG_Node; ltgTime = botWorld->gameLocalInfo.time + 30000; LTG_AI_SUB_NODE = &idBotAI::Enter_LTG_RoamGoal; if ( !botInfo->isDisguised ) { if ( !ltgUseVehicle ) { Bot_FindRouteToCurrentGoal(); } PushAINodeOntoStack( -1, -1, actionNum, ltgTime, false, ltgUseVehicle, ( routeNode != NULL ) ? true : false ); } return true; } return false; }