// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "BotThreadData.h" #include "BotAI_Main.h" /* ================ idEntity *GetGameEntity A thread safe way for us to get a pointer to an entity - used for traces and collision detections. NOTE: we can't actually access any of the entity's info in the Bot AI, just the pointer. If you need certain info, add it to the "botThreadData" client/entity info arrays. ================ */ idEntity *GetGameEntity( int entityNumber ) { if ( entityNumber == -1 ) { return NULL; } return gameLocal.entities[ entityNumber ]; } /* ================ idBotAI::idBotAI ================ */ idBotAI::idBotAI() { ClearBotState(); } /* ================ idBotAI::~idBotAI ================ */ idBotAI::~idBotAI() { } /* ================ idBotAI::Think This is the start of the bot's thinking process, called every game frame by the server. ================ */ void idBotAI::Think() { botInfo = &botThreadData.GetBotWorldState()->clientInfo[ botNum ]; botUcmd = &botThreadData.GetBotOutputState()->botOutput[ botNum ]; botWorld = botThreadData.GetBotWorldState(); botVehicleInfo = GetBotVehicleInfo( botInfo->proxyInfo.entNum ); BotAI_ResetUcmd(); if ( !BotAI_CheckThinkState() ) { return; } BotAI_UpdateStateInfo(); RunDebugChecks(); UpdateAAS(); if ( botVehicleInfo != NULL ) { VThink(); //mal: if bot is in a vehicle, run vehicle specific AI. return; } if ( ROOT_AI_NODE == NULL ) { Bot_ResetState( false, false ); ROOT_AI_NODE = &idBotAI::Run_LTG_Node; } if ( botAAS.blockedByObstacleCounterOnFoot > MAX_FRAMES_BLOCKED_BY_OBSTACLE_ON_FOOT ) { Bot_ResetState( true, true ); botAAS.blockedByObstacleCounterOnFoot = 0; ROOT_AI_NODE = &idBotAI::Run_LTG_Node; } CallFuncPtr( ROOT_AI_NODE ); //mal: run the bot's current think node Bot_Input(); CheckBotStuckState(); } /* ================ idBotAI::ResetUcmd Clears out the Bot's user cmd structure at the start of every frame, so that we can build a fresh one each bot think. ================ */ void idBotAI::BotAI_ResetUcmd () { Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, NULLMOVETYPE ); //mal: clear out the move state at first! // botUcmd->viewType = VIEW_ORIGIN; botUcmd->turnType = SMOOTH_TURN; botUcmd->viewEntityNum = -1; botUcmd->actionEntityNum = -1; botUcmd->actionEntitySpawnID = -1; botUcmd->botCmds.ackJustSpawned = false; botUcmd->botCmds.ackReset = false; botUcmd->botCmds.activate = false; botUcmd->botCmds.ackSeatChange = false; botUcmd->botCmds.activateHeld = false; botUcmd->botCmds.altAttackOff = false; botUcmd->botCmds.honkHorn = false; botUcmd->botCmds.isBlocked = false; botUcmd->botCmds.launchDecoys = false; botUcmd->botCmds.launchDecoysNow = false; botUcmd->botCmds.altAttackOn = false; botUcmd->botCmds.godMode = false; botUcmd->botCmds.attack = false; botUcmd->botCmds.reload = false; botUcmd->botCmds.enterVehicle = false; botUcmd->botCmds.exitVehicle = false; botUcmd->botCmds.zoom = false; botUcmd->botCmds.lookDown = false; botUcmd->botCmds.lookUp = false; botUcmd->botCmds.constantFire = false; botUcmd->botCmds.throwNade = false; botUcmd->botCmds.hasNoGoals = false; botUcmd->botCmds.switchVehicleWeap = false; botUcmd->botCmds.dropDisguise = false; botUcmd->botCmds.shoveClient = false; botUcmd->botCmds.droppingSupplyCrate = false; botUcmd->botCmds.destroySupplyCrate = false; botUcmd->botCmds.hasMedicInFOV = false; botUcmd->botCmds.becomeDriver = false; botUcmd->botCmds.becomeGunner = false; botUcmd->botCmds.enterSiegeMode = false; botUcmd->botCmds.exitSiegeMode = false; botUcmd->desiredChat = NULL_CHAT; botUcmd->desiredChatForced = false; botUcmd->botCmds.useTankAim = false; botUcmd->botCmds.altFire = false; botUcmd->deployInfo.deployableType = NULL_DEPLOYABLE; botUcmd->deployInfo.location.Zero(); botUcmd->deployInfo.actionNumber = ACTION_NULL; botUcmd->ackKillForClient = -1; botUcmd->ackRepairForClient = -1; isStrafeJumping = false; ignoreWeapChange = false; botAAS.triedToMoveThisFrame = false; weaponLocked = false; botUcmd->botCmds.launchPacks = false; botUcmd->botCmds.switchAwayFromSniperRifle = false; botUcmd->botCmds.topHat = false; botUcmd->tkReviveTime = 0; botUcmd->decayObstacleSpawnID = -1; botUcmd->botCmds.actorBriefedPlayer = false; botUcmd->botCmds.actorSurrenderStatus = false; botUcmd->botCmds.actorIsBriefingPlayer = false; botUcmd->botCmds.actorHasEnteredActorNode = false; botUcmd->specialMoveType = NULLMOVETYPE; } /* ================ idBotAI::BotAI_UpdateStateInfo Updates some important info that the bots will need every frame. ================ */ void idBotAI::BotAI_UpdateStateInfo () { const_cast( botInfo->friendsInArea ) = ClientsInArea( botNum, botInfo->origin, 1200.0f, botInfo->team, NOCLASS, false, false, false, true, false ); const_cast( botInfo->enemiesInArea ) = ClientsInArea( botNum, botInfo->origin, 1500.0f, ( botInfo->team == GDF ) ? STROGG : GDF, NOCLASS, false, false, false, false, false ); } /* ================ idBotAI::Bot_Input A housekeeping function, called at the end of every bot think. Sends the bot's user cmds to the server, updates bots cmd timers, etc. ================ */ void idBotAI::Bot_Input() { //mal_TODO: add more stuff here as needed! if ( aiState == LTG && !botWorld->gameLocalInfo.inWarmup ) { botUcmd->botCmds.reload = true; } //mal: just auto-reload in LTG. if ( weaponLocked ) { botUcmd->idealWeaponSlot = NO_WEAPON; botUcmd->idealWeaponNum = NULL_WEAP; } else { botUcmd->idealWeaponSlot = botIdealWeapSlot; botUcmd->idealWeaponNum = botIdealWeapNum; } if ( !hasGreetedServer ) { if ( botThreadData.random.RandomInt( 100 ) > 70 ) { Bot_AddDelayedChat( botNum, HELLO, botThreadData.random.RandomInt( 3 ) ); //mal: say a nice hello to the folks on the server. } hasGreetedServer = true; //mal: never do this again. } Bot_CheckDelayedChats(); } /* ================ idBotAI::Bot_ResetState Totally resets the bot's AI states and important values. Used in the case where the bot needs to "forget" what it was doing, and needs to find a new goal, and on every spawn into the world (and revive). ================ */ void idBotAI::Bot_ResetState( bool resetEnemy, bool resetStack ) { if ( resetEnemy ) { Bot_ResetEnemy(); } if ( resetStack ) { Bot_ClearAIStack(); } classAbilityDelay = -1; wantsVehicle = false; chasingEnemy = false; ignoreSpyTime = 0; vehicleGunnerTime = 0; vehicleDriverTime = 0; vehicleUpdateTime = 0; supplySelfTime = 0; resetEnemy = false; shotIsBlockedCounter = 0; vehicleEnemyWasInheritedFromFootCombat = false; nodeTimeOut = 0; newPathTime = 0; combatMoveTime = -1; combatMoveFailedCount = 0; //mal: save off what the last action we did was, so we can make sure we don't repeat ourselves! actionNum = ACTION_NULL; ignoreDangersTime = 0; useNadeOnEnemy = false; // does this bot want to use a nade on its enemy? useAircanOnEnemy = false; ResetRandomLook(); framesVehicleStuck = 0; isStrafeJumping = false; stayInPosition = false; badMoveTime = 0; testFireShot = false; ResetRandomLook(); routeNode = NULL; vehicleNode = NULL; skipStrafeJumpTime = 0; pistolTime = 0; COMBAT_MOVEMENT_STATE = NULL; VEHICLE_COMBAT_MOVEMENT_STATE = NULL; VEHICLE_COMBAT_AI_SUB_NODE = NULL; botIdealWeapSlot = GUN; botIdealWeapNum = NULL_WEAP; aiState = LTG; nbgTargetType = NOTYPE; ltgUseVehicle = false; botExitTime = 0; ROOT_AI_NODE = NULL; V_ROOT_AI_NODE = NULL; V_LTG_AI_SUB_NODE = NULL; NBG_AI_SUB_NODE = NULL; LTG_AI_SUB_NODE = NULL; COMBAT_AI_SUB_NODE = NULL; combatNBGType = NO_COMBAT_TYPE; botVehiclePathList.Clear(); } /* ================ idBotAI::Bot_ClearAIStack ================ */ void idBotAI::Bot_ClearAIStack() { AIStack.STACK_AI_NODE = false; AIStack.VEHICLE_STACK_AI_NODE = false; AIStack.stackClientNum = -1; AIStack.stackClientSpawnID = -1; AIStack.stackEntNum = -1; AIStack.stackTimeLimit = -1; AIStack.stackActionNum = ACTION_NULL; AIStack.stackLTGType = NO_LTG; AIStack.vehicleStackLTGType = NO_VEHICLE_LTG; AIStack.isPriority = false; AIStack.useVehicle = false; AIStack.routeNode = NULL; } /* ================ idBotAI::Bot_ClearVehicleOffAIStack ================ */ void idBotAI::Bot_ClearVehicleOffAIStack() { AIStack.useVehicle = false; } /* ================ idBotAI::PushAINodeOntoStack ================ */ void idBotAI::PushAINodeOntoStack( int clientNum, int entNum, int actionNumber, int timeLimit, bool isPriority, bool useVehicle, bool useRoute ) { AIStack.STACK_AI_NODE = LTG_AI_SUB_NODE; AIStack.VEHICLE_STACK_AI_NODE = V_LTG_AI_SUB_NODE; AIStack.stackLTGType = ltgType; AIStack.vehicleStackLTGType = vLTGType; if ( ClientIsValid( clientNum, -1 ) ) { AIStack.stackClientNum = clientNum; AIStack.stackClientSpawnID = botWorld->clientInfo[ clientNum ].spawnID; } AIStack.stackEntNum = entNum; AIStack.stackTimeLimit = botWorld->gameLocalInfo.time + timeLimit; AIStack.stackActionNum = actionNumber; AIStack.isPriority = isPriority; AIStack.useVehicle = useVehicle; if ( useRoute ) { if ( routeNode != NULL ) { AIStack.routeNode = routeNode; } else { assert( false ); } } else { AIStack.routeNode = NULL; } } /* ================ idBotAI::PopAINodeOffStack ================ */ bool idBotAI::PopAINodeOffStack() { if ( AIStack.STACK_AI_NODE == false ) { return false; } LTG_AI_SUB_NODE = AIStack.STACK_AI_NODE; V_LTG_AI_SUB_NODE = AIStack.VEHICLE_STACK_AI_NODE; if ( AIStack.stackClientNum != -1 ) { ltgTarget = AIStack.stackClientNum; ltgTargetSpawnID = AIStack.stackClientSpawnID; } else if ( AIStack.stackEntNum != -1 ) { ltgTarget = AIStack.stackEntNum; } else { ltgTarget = -1; } ltgUseVehicle = AIStack.useVehicle; routeNode = AIStack.routeNode; ltgTime = botWorld->gameLocalInfo.time + AIStack.stackTimeLimit; actionNum = AIStack.stackActionNum; if ( AIStack.stackLTGType != FOLLOW_TEAMMATE && AIStack.stackLTGType != FOLLOW_TEAMMATE_BY_REQUEST ) { Bot_ClearAIStack(); //mal: only use this once - else bot may get stuck in loop - UNLESS following mate! } return true; } /* ================ idBotAI::Bot_ResetEnemy ================ */ void idBotAI::Bot_ResetEnemy() { numVisEnemies = 0; enemy = -1; fastAwareness = false; timeTilAttackEnemy = -1; enemyInfo.enemyDist = -1; enemyInfo.enemyLastVisTime = -1; enemyInfo.enemyVisible = false; enemyInfo.enemy_LS_Pos = vec3_zero; COMBAT_AI_SUB_NODE = NULL; VEHICLE_COMBAT_AI_SUB_NODE = NULL; hammerTime = false; hammerVehicle = false; hammerLocation = vec3_zero; COMBAT_MOVEMENT_STATE = NULL; VEHICLE_COMBAT_MOVEMENT_STATE = NULL; combatMoveType = COMBAT_MOVE_NULL; combatMoveTime = -1; combatMoveFailedCount = 0; chasingEnemy = false; chaseEnemy = false; vehicleGunnerTime = 0; vehicleDriverTime = 0; vehicleUpdateTime = 0; shotIsBlockedCounter = 0; resetEnemy = false; } /* ================ idBotAI::UpdateBotsInformation Anytime the bot spawns fresh into the world, start with a clean slate. This could be setup to detect if the bot changes its weapon/class/team, so that we can support that sort of thing in the scripts. ================ */ void idBotAI::UpdateBotsInformation( const playerClassTypes_t playerClass, const playerTeamTypes_t playerTeam, bool botInit ) { if ( !botInit ) { Bot_ResetState( true, true ); } //mal: dont do this if haven't had time to get all the bot's info setup yet! if ( playerClass == NOCLASS || playerTeam == NOTEAM ) { return; } LTG_CHECK_FOR_CLASS_NBG = NULL; NBG_CHECK_FOR_CLASS_NBG = NULL; //mal: check what kind of class this bot is, and set its short term goal checks accordingly.... if ( playerClass == MEDIC ) { LTG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_MedicCheckNBGState_InLTG; NBG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_MedicCheckNBGState_InNBG; } if ( playerClass == FIELDOPS ) { //mal: strogg medics will do the same job as Fops. LTG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_FopsCheckNBGState; NBG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_FopsCheckNBGState; } if ( playerClass == ENGINEER ) { LTG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_EngCheckNBGState; NBG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_EngCheckNBGState; } if ( playerClass == COVERTOPS ) { LTG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_CovertOpsCheckNBGState; NBG_CHECK_FOR_CLASS_NBG = &idBotAI::Bot_CovertOpsCheckNBGState; } //mal_TODO: add short term goal checks for all the classes (at least the ones who need them) - also setup team specific stuff here! if ( !botInit ) { //must be run very last! botUcmd->botCmds.ackJustSpawned = true; } } /* =============== idBotAI::InitAAS =============== */ void idBotAI::InitAAS() { botAAS.aas = botThreadData.GetAAS( AAS_PLAYER ); //mal: start out with player AAS at spawn botAAS.aasType = AAS_PLAYER; if ( !botAAS.aas ) { return; // give some kind of error msg here? } } /* =============== idBotAI::Spawn just setups all of the bots basic vars/strucs with default values. =============== */ void idBotAI::Spawn( int playerClass, int playerTeam ) { InitAAS(); ClearBotUcmd( botNum ); playerTeamTypes_t botTeam = ( playerTeamTypes_t ) playerTeam; playerClassTypes_t botClass = ( playerClassTypes_t ) playerClass; UpdateBotsInformation( botClass, botTeam, true ); } /* ================ idBotAI::CheckBotStuckState ================ */ void idBotAI::CheckBotStuckState() { if ( !botInfo->hasGroundContact && botInfo->invulnerableEndTime > botWorld->gameLocalInfo.time ) { framesStuck = 0; framesFlipped = 0; vehicleFramesStuck = 0; overallFramesStuck = 0; return; //mal: if we're spawning in, dont count a lack of a valid AAS area against us } if ( stuckRandomMoveTime > botWorld->gameLocalInfo.time ) { BotAI_ResetUcmd(); Bot_MoveToGoal( vec3_zero, vec3_zero, SPRINT, RANDOM_DIR_JUMP ); return; } if ( !botAAS.triedToMoveThisFrame ) { botAAS.blockedByObstacleCounterOnFoot = 0; } if ( botInfo->areaNum == 0 ) { noAASCounter++; if ( noAASCounter >= 100 ) { if ( botVehicleInfo == NULL ) { botUcmd->botCmds.suicide = true; noAASCounter = 0; } else { Bot_ExitVehicle(); } } return; } else { noAASCounter = 0; } if ( moveErrorCounter.moveErrorTime < botWorld->gameLocalInfo.time ) { moveErrorCounter.moveErrorTime = botWorld->gameLocalInfo.time + 5000; if ( moveErrorCounter.moveErrorCount >= MAX_MOVE_ERRORS ) { moveErrorCounter.moveErrorCount = 0; botUcmd->botCmds.suicide = true; } else { moveErrorCounter.moveErrorCount = 0; } } if ( botVehicleInfo == NULL ) { idVec3 vec = botInfo->origin - botInfo->oldOrigin; if ( vec.LengthSqr() < Square( 10.0f ) && botInfo->isTryingToMove ) { framesStuck++; // if ( framesStuck > MAX_FRAMES_BLOCKED_BY_OBSTACLE_ON_FOOT ) { // botAAS.blockedByObstacleCounterOnFoot = MAX_FRAMES_BLOCKED_BY_OBSTACLE_ON_FOOT + 1; // framesStuck = 0; // stuckRandomMoveTime = botWorld->gameLocalInfo.time + 500; // overallFramesStuck++; // } if ( framesStuck >= 300 ) { stuckRandomMoveTime = botWorld->gameLocalInfo.time + 500; framesStuck = 0; overallFramesStuck++; } if ( overallFramesStuck > 6 && botWorld->gameLocalInfo.botsCanSuicide ) { botUcmd->botCmds.suicide = true; } return; } } framesStuck = 0; overallFramesStuck = 0; if ( botVehicleInfo != NULL ) { if ( botVehicleInfo->isFlipped ) { framesFlipped++; if ( framesFlipped >= 100 ) { //mal: give us time to recover, just in case we can. Bot_ExitVehicle( false); } return; } if ( botVehicleInfo->isCareening && botVehicleInfo->isAirborneVehicle ) { //mal: out NOW! This thing is lost! Bot_ExitVehicle(); } if ( botVehicleInfo->inWater && !( botVehicleInfo->flags & WATER ) ) { Bot_ExitVehicle( false ); } } framesFlipped = 0; /* if ( botVehicleInfo != NULL ) { //mal: this sucks. idVec3 vec = botInfo->origin - botInfo->oldOrigin; vec.z = 0.0f; if ( vec.LengthSqr() < Square( 10.0f ) && botInfo->isTryingToMove ) { vehicleFramesStuck++; if ( vehicleFramesStuck >= 300 ) { vehicleReverseTime = botWorld->gameLocalInfo.time + 900; botUcmd->specialMoveType = REVERSEMOVE; vehicleFramesStuck = 0; } } else { vehicleFramesStuck = 0; } } */ } /* ================ idBotAI::RunDebugChecks ================ */ void idBotAI::RunDebugChecks() { SetupDebugHud(); if ( botThreadData.AllowDebugData() ) { if ( bot_breakPoint.GetBool() ) { bot_breakPoint.SetBool( false ); assert( false ); // stop everything! } } } /* =============== idBotAI::ResetBotsAI just resets the AI, but keeps any enemies the bot has! =============== */ void idBotAI::ResetBotsAI( bool critical ) { if ( !critical ) { if ( aiState == LTG && ( ltgType == FOLLOW_TEAMMATE || ltgType == FOLLOW_TEAMMATE_BY_REQUEST ) && Client_IsCriticalForCurrentObj( ltgTarget, -1 ) ) { botUcmd->botCmds.ackReset = true; return; } } //mal: if we're escorting someone doing the obj, DON'T reset our AI state! bool fastEnemyAwareness = false; if ( enemy != -1 ) { fastEnemyAwareness = true; } Bot_ResetState( true, true ); botUcmd->botCmds.ackReset = true; fastAwareness = fastEnemyAwareness; //mal: if we has an enemy when we reset, we'll re-aquire them very quickly. lastCheckActionTime = 0; //mal: we won't ignore a 2nd plant msg right after the first one. } /* =============== idBotAI::Bot_AddDelayedChat sets a chat for the bot to use later in time. Not recommended to be for too long ( 5 - 10 seconds tops ). Its possible a chat will be dropped, which is fine because a bot's chats aren't considered to be too critical. Has been extended, so that ANY bot can have their chat state set in the botThread. =============== */ void idBotAI::Bot_AddDelayedChat( int botClientNum, const botChatTypes_t chatType, int delayTimeInSecs, bool forceChat ) { bool skipChat = false; for( int i = 0; i < MAX_DELAYED_CHATS; i++ ) { if ( botThreadData.bots[ botClientNum ]->delayedChats[ i ].delayTime != 0 && botThreadData.bots[ botClientNum ]->delayedChats[ i ].delayTime > botWorld->gameLocalInfo.time ) { continue; } //mal: this chat is currently tagged inuse, so find another. for( int j = 0; j < MAX_DELAYED_CHATS; j++ ) { if ( botThreadData.bots[ botClientNum ]->delayedChats[ j ].delayTime == 0 || botThreadData.bots[ botClientNum ]->delayedChats[ i ].delayTime < botWorld->gameLocalInfo.time ) { continue; } if ( botThreadData.bots[ botClientNum ]->delayedChats[ j ].chat == chatType ) { skipChat = true; break; } } if ( skipChat ) { break; } botThreadData.bots[ botClientNum ]->delayedChats[ i ].chat = chatType; botThreadData.bots[ botClientNum ]->delayedChats[ i ].delayTime = ( delayTimeInSecs * 1000 ) + botWorld->gameLocalInfo.time; botThreadData.bots[ botClientNum ]->delayedChats[ i ].forceChat = forceChat; break; } } /* =============== idBotAI::Bot_CheckDelayedChats Checks to see if there are any delayed chats ready to play. =============== */ void idBotAI::Bot_CheckDelayedChats() { bool hasChat = false; int i; idVec3 vec; for( i = 0; i < MAX_DELAYED_CHATS; i++ ) { if ( delayedChats[ i ].delayTime == 0 || delayedChats[ i ].delayTime > botWorld->gameLocalInfo.time ) { continue; } //mal: this chat is currently tagged released or not ready to be sent, so find another. botUcmd->desiredChat = delayedChats[ i ].chat; botUcmd->desiredChatForced = delayedChats[ i ].forceChat; delayedChats[ i ].delayTime = 0; hasChat = true; break; } //mal: if we dont have anything important to say, lets see if we should say "Your Welcome!" to someone who thanked us. if ( hasChat == false && botInfo->health > 0 && botInfo->lastChatTime[ YOURWELCOME ] < botWorld->gameLocalInfo.time ) { if ( ( botInfo->classType == MEDIC || botInfo->classType == FIELDOPS ) && aiState == LTG || aiState == NBG ) { for( i = 0; i < MAX_CLIENTS; i++ ) { if ( !ClientIsValid( i, -1 ) ) { continue; } if ( i == botNum ) { continue; } const clientInfo_t& playerInfo = botThreadData.GetBotWorldState()->clientInfo[ i ]; if ( playerInfo.health <= 0 ) { //mal: seems retared to say "Bitte Sehr!" to someones whos' dead! continue; } if ( playerInfo.myHero != botNum ) { continue; } if ( playerInfo.lastThanksTime + 5000 < botWorld->gameLocalInfo.time || playerInfo.lastThanksTime + 1500 > botWorld->gameLocalInfo.time ) { continue; } vec = playerInfo.origin - botInfo->origin; if ( vec.LengthSqr() > Square( 900.0f ) ) { continue; } botUcmd->desiredChat = YOURWELCOME; } } } } /* =============== idBotAI::UpdateAAS NOTE: The icarus is the only vehicle that will use the player AAS. =============== */ void idBotAI::UpdateAAS() { if ( botVehicleInfo != NULL ) { if ( botAAS.aasType != AAS_VEHICLE ) { botAAS.aas = botThreadData.GetAAS( AAS_VEHICLE ); if ( botAAS.aas == NULL ) { botAAS.aas = botThreadData.GetAAS( AAS_PLAYER ); assert( false ); //mal: this should NEVER happen! } botAAS.aasType = AAS_VEHICLE; } } else { if ( botAAS.aasType != AAS_PLAYER ) { botAAS.aas = botThreadData.GetAAS( AAS_PLAYER ); botAAS.aasType = AAS_PLAYER; } } botAAS.blockedByObstacleCounterOnFoot = 0; botAAS.blockedByObstacleCounterInVehicle = 0; } /* =============== idBotAI::BotAI_CheckThinkState Should the bot think this frame? =============== */ bool idBotAI::BotAI_CheckThinkState() { if ( botWorld->gameLocalInfo.botsPaused ) { return false; } if ( botWorld->gameLocalInfo.botsSleep ) { return false; } if ( botInfo->team == NOTEAM || botInfo->classType == NOCLASS ) { if ( botThreadData.AllowDebugData() ) { // assert( false ); } return false; } if ( botThreadData.AllowDebugData() ) { if ( bot_skipThinkClient.GetInteger() == botNum ) { botUcmd->botCmds.godMode = true; return false; } if ( bot_godMode.GetInteger() == botNum ) { botUcmd->botCmds.godMode = true; } } if ( botInfo->isTeleporting ) { return false; } if ( botAAS.aas == NULL ) { if ( botThreadData.random.RandomInt( 100 ) > 98 ) { botUcmd->botCmds.hasNoGoals = true; //mal: pass a warning to the player to let them know we have no goals } return false; } if ( !botInfo->hasGroundContact && botInfo->invulnerableEndTime > botWorld->gameLocalInfo.time ) { return false; } //mal: dont start thinking until we've actually touched the ground after spawning in. if ( botInfo->justSpawned ) { UpdateBotsInformation( botInfo->classType, botInfo->team, false ); return false; //mal: dont think til we have some team/class stuff to think about! } if ( botWorld->gameLocalInfo.debugBotWeapons ) { botUcmd->botCmds.lookDown = true; botUcmd->idealWeaponSlot = GUN; botUcmd->botCmds.attack = true; if ( !botInfo->weapInfo.primaryWeapHasAmmo && !botInfo->weapInfo.sideArmHasAmmo && !botInfo->justSpawned ) { botUcmd->botCmds.suicide = true; } Bot_MoveToGoal( botInfo->origin + idVec3( 5.0f, 5.0f, 0.0f ), vec3_zero, RUN, ( botThreadData.random.RandomInt( 100 ) > 80 ) ? RANDOM_JUMP : NULLMOVETYPE ); return false; } if ( botInfo->resetState > 0 ) { ResetBotsAI( ( botInfo->resetState == MINOR_RESET_EVENT ) ? false : true ); return false; } return true; } /* =============== idBotAI::ClearBotState =============== */ void idBotAI::ClearBotState() { nextRemoved = NULL; crateGoodTime = 0; LTG_CHECK_FOR_CLASS_NBG = NULL; NBG_CHECK_FOR_CLASS_NBG = NULL; COMBAT_MOVEMENT_STATE = NULL; ROOT_AI_NODE = NULL; V_ROOT_AI_NODE = NULL; NBG_AI_SUB_NODE = NULL; LTG_AI_SUB_NODE = NULL; V_LTG_AI_SUB_NODE = NULL; COMBAT_AI_SUB_NODE = NULL; VEHICLE_COMBAT_MOVEMENT_STATE = NULL; VEHICLE_COMBAT_AI_SUB_NODE = NULL; noAASCounter = 0; vehicleEnemyWasInheritedFromFootCombat = false; wantsVehicle = false; ignoreNewEnemiesTime = 0; ignoreNewEnemiesWhileInVehicleTime = 0; botTeleporterAttempts = 0; botPathFailedCounter = 0; botInfo = NULL; botUcmd = NULL; botWorld = NULL; botVehicleInfo = NULL; AIStack.STACK_AI_NODE = NULL; AIStack.stackClientNum = -1; AIStack.stackClientSpawnID = -1; AIStack.stackEntNum = -1; AIStack.stackTimeLimit = -1; skipStrafeJumpTime = 0; pistolTime = 0; hopMoveGoal = vec3_zero; ignoreMCPRouteAction = false; turretDangerExists = false; turretDangerEntNum = -1; warmupActionMoveGoal = ACTION_NULL; warmupUseActionMoveGoalTime = 0; stuckRandomMoveTime = 0; lastVehicleSpawnID = 0; lastVehicleTime = 0; actionNumInsideDanger = ACTION_NULL; lastNearbySafeActionToMoveTowardActionNum = ACTION_NULL; shotIsBlockedCounter = 0; hasGreetedServer = false; reachedPatrolPointTime = 0; strafeToAvoidDangerTime = 0; strafeToAvoidDangerDir = NULL_DIR; timeOnTarget = 0; nextObjChargeCheckTime = 0; routeNode = NULL; vehicleNode = NULL; weaponLocked = false; vehicleSurrenderTime = 0; vehicleSurrenderClient = -1; vehicleSurrenderChatSent = false; vehicleCheckForPossibleHumanPassengerChatTime = 0; weapSwitchTime = 0; randomLookDelay = 0; resetEnemy = false; botWantsVehicleBackTime = 0; checkRoadKillTime = 0; ignoreIcarusTime = 0; pathNodeTimer.ignorePathNode = NULL; pathNodeTimer.ignorePathNodeTime = 0; pathNodeTimer.ignorePathNodeUpdateTime = 0; vehicleGunnerTime = 0; vehicleDriverTime = 0; vehicleUpdateTime = 0; memset( &botAAS, 0, sizeof( botAAS ) ); memset( &enemyInfo, 0, sizeof( enemyInfo ) ); memset( ignoreClients, 0, sizeof( ignoreClients ) ); memset( ignoreEnemies, 0, sizeof( ignoreEnemies ) ); memset( ignoreBodies, 0, sizeof( ignoreBodies ) ); memset( currentDangers, 0, sizeof( currentDangers ) ); memset( ignoreActions, 0, sizeof( ignoreActions ) ); memset( ignoreItems, 0, sizeof( ignoreItems ) ); memset( ¤tArtyDanger, 0, sizeof( currentArtyDanger ) ); memset( delayedChats, 0, sizeof( delayedChats ) ); randomLookOrg.Zero(); botExitTime = 0; botIsBlocked = 0; botIsBlockedTime = 0; vehicleWheelsAreOffGroundCounter = 0.0f; ignoreDangersTime = 0; lastGrenadeTime = 0; fdaUpdateTime = 0; selfShockTime = 0; useNadeOnEnemy = false; useAircanOnEnemy = false; safeGrenade = false; chaseEnemy = false; timeTilAttackEnemy = -1; ignoreSpyTime = 0; framesStuck = 0; framesVehicleStuck = 0; framesFlipped = 0; randomLookRange = 0.0f; isStrafeJumping = false; fastAwareness = false; skipStrafeJumpTime = 0; badMoveTime = 0; vehiclePauseTime = 0; vehicleReverseTime = 0; chasingEnemy = false; testFireShot = false; isBoosting = false; chaseEnemyTime = 0; stayInPosition = false; actionNum = ACTION_NULL; lastActionNum = ACTION_NULL; tacticalActionNum = ACTION_NULL; tacticalActionTime = -1; lastCheckActionTime = -1; supplySelfTime = 0; bugForSuppliesDelay = 0; tacticalActionTime = 0; botNum = -1; aiState = LTG; botCanMoveGoal.Zero(); botBackStabMoveGoal.Zero(); actionNum = 0; ltgType = NO_LTG; ltgTarget = 0; ltgTime = 0; ltgReached = false; ltgTimer = 0; ltgTargetType = NOTYPE; ltgUseVehicle = false; rocketTime = 0; nbgType = NO_NBG; nbgTarget = 0; nbgTime = 0; nbgReached = false; nbgTimer = 0; nbgTargetType = NOTYPE; combatNBGTarget = 0; combatNBGTime = 0; combatNBGReached = false; combatNBGType = NO_COMBAT_TYPE; combatMoveFlag = NULLMOVEFLAG; bot_FS_Enemy_Pos.Zero(); bot_LS_Enemy_Pos.Zero(); numVisEnemies = 0; enemy = -1; heardClient = -1; nextMissionUpdateTime = 0; nodeTimeOut = 0; newPathTime = 0; hammerTime = false; hammerLocation = vec3_zero; hammerVehicle = false; combatMoveDir = FORWARD; combatMoveType = COMBAT_MOVE_NULL; combatMoveTime = -1; combatMoveFailedCount = 0; gunTargetEntNum = 0; classAbilityDelay = 0; bugForSuppliesDelay = 0; vehicleObstacleSpawnID = 0; vehicleObstacleTime = 0; combatDangerExists = false; lefty = false; enemy = -1; combatMoveTime = -1; ResetRandomLook(); botIdealWeapSlot = GUN; botIdealWeapNum = NULL_WEAP; badMoveTime = 0; testFireShot = false; vehicleAINodeSwitch.nodeSwitchCount = 0; vehicleAINodeSwitch.nodeSwitchTime = 0; moveErrorCounter.moveErrorTime = 0; moveErrorCounter.moveErrorCount = 0; // FIXME: initialize all variables lastAINode = "NO NODE"; lastMoveNode = "NO NODE"; botVehiclePathList.Clear(); #ifdef _DEBUG debugVar1 = -1; debugVar2 = -1; #endif } /* =============== idBotAI::ClearBotUcmd =============== */ void idBotAI::ClearBotUcmd( int clientNum ) { botAIOutput_t &output = botThreadData.GetBotOutputState()->botOutput[ clientNum ]; output.botCmds.ackJustSpawned = false; output.botCmds.ackReset = false; output.turnType = INSTANT_TURN; output.desiredChat = NULL_CHAT; output.desiredChatForced = false; output.deployInfo.deployableType = NULL_DEPLOYABLE; output.deployInfo.location.Zero(); output.deployInfo.actionNumber = ACTION_NULL; output.viewType = VIEW_ORIGIN; output.viewEntityNum = -1; output.actionEntitySpawnID = -1; output.botCmds.activate = false; output.botCmds.activateHeld = false; output.botCmds.ackSeatChange = false; output.botCmds.honkHorn = false; output.botCmds.isBlocked = false; output.botCmds.launchDecoys = false; output.botCmds.launchDecoysNow = false; output.botCmds.altAttackOff = false; output.botCmds.altAttackOn = false; output.botCmds.attack = false; output.botCmds.reload = false; output.botCmds.enterVehicle = false; output.botCmds.zoom = false; output.botCmds.lookDown = false; output.botCmds.lookUp = false; output.botCmds.constantFire = false; output.botCmds.throwNade = false; output.botCmds.suicide = false; output.botCmds.hasNoGoals = false; output.botCmds.dropDisguise = false; output.botCmds.shoveClient = false; output.botCmds.hasMedicInFOV = false; output.botCmds.enterSiegeMode = false; output.botCmds.exitSiegeMode = false; output.moveFlag = NULLMOVEFLAG; output.specialMoveType = NULLMOVETYPE; output.moveType = NULLMOVETYPE; output.moveGoal = vec3_zero; output.moveGoal2 = vec3_zero; output.moveViewAngles = ang_zero; output.moveViewOrigin = vec3_zero; output.idealWeaponNum = NULL_WEAP; output.idealWeaponSlot = GUN; output.ackKillForClient = -1; output.ackRepairForClient = -1; output.tkReviveTime = 0; output.decayObstacleSpawnID = -1; output.botCmds.launchPacks = false; output.botCmds.topHat = false; }