// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "../ContentMask.h" #include "BotThreadData.h" #include "BotAI_Main.h" /* ================== idBotAI::GetVehicleInfo Returns all the info about a particular vehicle. ================== */ void idBotAI::GetVehicleInfo( int entNum, proxyInfo_t& vehicleInfo ) const { vehicleInfo.entNum = 0; for( int i = 0; i < MAX_VEHICLES; i++ ) { if ( botWorld->vehicleInfo[ i ].entNum != entNum ) { continue; } vehicleInfo = botWorld->vehicleInfo[ i ]; break; } if ( vehicleInfo.entNum == 0 ) { // assert( false ); } } /* ================== idBotAI::GetVehicleInfo Returns all the info about a particular vehicle. ================== */ const proxyInfo_t *idBotAI::GetBotVehicleInfo( int entNum ) const { for( int i = 0; i < MAX_VEHICLES; i++ ) { if ( botWorld->vehicleInfo[ i ].entNum != entNum ) { continue; } return &botWorld->vehicleInfo[ i ]; } return NULL; } /* ================== idBotAI::VehicleIsValid Checks to make sure the vehicle in question is still valid. ================== */ bool idBotAI::VehicleIsValid( int entNum, bool skipSpeedCheck, bool addDriverCheck ) { proxyInfo_t vehicleInfo; GetVehicleInfo( entNum, vehicleInfo ); if ( vehicleInfo.entNum == 0 ) { return false; } if ( !vehicleInfo.hasGroundContact && vehicleInfo.type != DESECRATOR && !vehicleInfo.inWater ) { //mal: desecrator NEVER has groundcontact return false; } if ( !vehicleInfo.hasFreeSeat ) { return false; } if ( vehicleInfo.flags & PERSONAL ) { return false; } if ( vehicleInfo.isBoobyTrapped && vehicleInfo.type != MCP ) { return false; } if ( !vehicleInfo.inPlayZone ) { return false; } if ( !VehicleHasGunnerSeatOpen( entNum ) ) { if ( addDriverCheck == false ) { return false; } else { if ( vehicleInfo.driverEntNum != -1 ) { return false; } } } if ( vehicleInfo.inWater && !( vehicleInfo.flags & WATER ) ) { return false; } if ( vehicleInfo.isFlipped ) { return false; } if ( vehicleInfo.damagedPartsCount > 0 && botInfo->classType != ENGINEER ) { return false; } if ( !skipSpeedCheck ) { if ( vehicleInfo.xyspeed > WALKING_SPEED ) { return false; } } if ( vehicleInfo.areaNum == 0 ) { return false; } return true; } /* ================== idBotAI::FindClosestVehicle Returns the entity number of the closest vehicle to the bot, matching vehicleType, within a certain range. ================== */ int idBotAI::FindClosestVehicle( float range, const idVec3& org, const playerVehicleTypes_t vehicleType, int vehicleFlags, int vehicleIgnoreFlags, bool emptyOnly ) { int entNum = -1; int i; float closest = idMath::INFINITY; float dist; idVec3 vec; if ( botInfo->isDisguised ) { return -1; } if ( vehicleIgnoreFlags == -1 ) { return -1; } if ( !botWorld->gameLocalInfo.botsUseVehicles && vehicleType != MCP ) { return -1; } //mal_hack: these maps weren't originally setup for boats... if ( botWorld->gameLocalInfo.gameMap == ISLAND ) { range = 3500.0f; } else if ( botWorld->gameLocalInfo.gameMap == VALLEY ) { range = 4500.0f; } else if ( botWorld->gameLocalInfo.gameMap == VOLCANO ) { range = 7000.0f; } for( i = 0; i < MAX_VEHICLES; i++ ) { proxyInfo_t vehicle = botWorld->vehicleInfo[ i ]; if ( vehicle.entNum == 0 ) { continue; } if ( vehicle.team != botInfo->team ) { continue; } if ( vehicle.flags & PERSONAL && !( vehicleFlags & PERSONAL ) && vehicleFlags != 0 ) { //mal: have to specify personal vehicles continue; } if ( vehicle.inWater && !( vehicle.flags & WATER ) ) { //mal: someone drove it into the ocean. continue; } if ( vehicle.areaNumVehicle == 0 || vehicle.areaNum == 0 ) { continue; } if ( vehicle.isFlipped ) { //mal: its on its back. continue; } if ( vehicle.isBoobyTrapped && vehicle.type != MCP ) { continue; } if ( vehicle.type == PLATYPUS && !vehicle.neverDriven ) { continue; } if ( !vehicle.hasGroundContact && vehicle.type != DESECRATOR && vehicle.type != MCP && !vehicle.inWater ) { continue; } if ( vehicle.wheelsAreOnGround != 1.0f && vehicle.type != MCP ) { continue; } if ( vehicle.type == ICARUS && ignoreIcarusTime > botWorld->gameLocalInfo.time ) { continue; } if ( vehicleIgnoreFlags > 0 ) { if ( !botWorld->gameLocalInfo.debugPersonalVehicles ) { if ( vehicle.flags & vehicleIgnoreFlags ) { continue; } } } if ( VehicleIsIgnored( vehicle.entNum ) ) { continue; } if ( vehicle.damagedPartsCount > 0 && botInfo->classType != ENGINEER ) { //mal: dont get a vehicle that has a missing wheel, unless we can fix it! continue; } if ( vehicle.type == MCP && botWorld->gameLocalInfo.heroMode && TeamHasHuman( botInfo->team ) ) { continue; } if ( vehicle.isOwned && vehicle.type != MCP && vehicle.ownerNum != botNum ) { continue; } if ( vehicle.type == MCP && vehicleType != MCP ) { //mal: sorry - have to pick the MCP by name. continue; } if ( vehicle.spawnID == lastVehicleSpawnID && lastVehicleTime + MINIMUM_VEHICLE_IGNORE_TIME > botWorld->gameLocalInfo.time ) { //mal: dont jump in and out of the same vehicle. continue; } if ( vehicle.xyspeed > ( RUNNING_SPEED - 50.0f ) && !vehicle.isEmpty ) { //mal: slow enough we could run and catch it. continue; } //mal: dont worrry about vehicles moving, unless its empty ( and just coasting along ). if ( emptyOnly ) { if ( vehicle.type == MCP ) { if ( vehicle.driverEntNum != -1 ) { continue; } } else { if ( !vehicle.isEmpty ) { continue; } } } else { if ( !vehicle.hasFreeSeat ) { continue; } } if ( !vehicle.inPlayZone ) { if ( botInfo->inPlayZone ) { //mal: vehicles out of bounds are just a hazard to us - ignore them. continue; //mal: unless we're out of bounds too - then they're our ticket out of here! } } if ( vehicleType != NULL_VEHICLE ) { //mal: are we looking for a specific vehicle..... if ( vehicle.type != vehicleType ) { continue; } } else if ( vehicleFlags != NULL_VEHICLE_FLAGS ) { //mal: or just a general class of vehicles if ( !( vehicle.flags & vehicleFlags ) ) { if ( vehicle.type != PLATYPUS || ( botWorld->gameLocalInfo.gameMap != VALLEY && botWorld->gameLocalInfo.gameMap != ISLAND ) ) { //mal_HACK: these maps had boat support added late... continue; } } } //mal: we wont defer to a human if the vehicle is owned by us. We WILL still wait for him when we jump in, so he can have the chance to ride with us. if ( vehicle.type != MCP && ( !vehicle.isOwned || vehicle.ownerNum != botNum ) ) { if ( TeamHumanNearLocation( botInfo->team, vehicle.origin, HUMAN_OWN_VEHICLE_DIST ) ) { //mal: theres a human nearby, he might want this vehicle so defer to him. continue; } } vec = vehicle.origin - org; dist = vec.LengthSqr(); if ( dist > Square( range ) ) { continue; } idVec3 vehicleOrigin = vehicle.origin; vehicleOrigin.z += 32.0f; //mal: move it up a bit for safety. int travelTime; if ( !Bot_LocationIsReachable( ( botVehicleInfo != NULL ) ? true : false, vehicleOrigin, travelTime ) ) { continue; } if ( dist < closest ) { entNum = vehicle.entNum; closest = dist; } } return entNum; } /* ================== idBotAI::Bot_ExitVehicle ================== */ void idBotAI::Bot_ExitVehicle( bool ignoreMCP ) { if ( botVehicleInfo != NULL && botVehicleInfo->type == MCP && ignoreMCP ) { return; } if ( botWorld->gameLocalInfo.botsStayInVehicles ) { //mal: someone wants me to stay in here for debugging purposes. return; } if ( botVehicleInfo != NULL ) { if ( ( botInfo->classType != ENGINEER || vLTGType != V_STOP_VEHICLE ) && botVehicleInfo->driverEntNum != botNum && ClientIsValid( botVehicleInfo->driverEntNum, -1 ) && botInfo->proxyInfo.weapon != NULL_VEHICLE_WEAPON ) { //mal: if we're riding along with a human, never leave the vehicle - its frustrating for the human, unless we have no weapon to use. const clientInfo_t& player = botWorld->clientInfo[ botVehicleInfo->driverEntNum ]; if ( !player.isBot ) { if ( Bot_WithObjShouldLeaveVehicle() ) { //mal: if the bot has the obj - they should leave if the player stops the vehicle. goto leaveThisVehicle; } vLTGTarget = botVehicleInfo->driverEntNum; vLTGTargetSpawnID = player.spawnID; vLTGTime = ( ClientHasObj( botNum ) ) ? botWorld->gameLocalInfo.time + 10000 : botWorld->gameLocalInfo.time + BOT_INFINITY; //mal: if have obj, do constant checks as to whether or not we should leave. V_ROOT_AI_NODE = &idBotAI::Run_VLTG_Node; V_LTG_AI_SUB_NODE = &idBotAI::Enter_VLTG_RideWithMate; return; } } } leaveThisVehicle: if ( botVehicleInfo != NULL ) { lastVehicleSpawnID = botVehicleInfo->spawnID; lastVehicleTime = botWorld->gameLocalInfo.time; if ( botVehicleInfo->type == ICARUS ) { ignoreIcarusTime = botWorld->gameLocalInfo.time + IGNORE_ICARUS_TIME; } } botUcmd->botCmds.exitVehicle = true; botUcmd->moveType = FULL_STOP; botExitTime = botWorld->gameLocalInfo.time + BOT_THINK_DELAY_TIME; botPathFailedCounter = 0; vehicleAINodeSwitch.nodeSwitchCount = 0; vehicleAINodeSwitch.nodeSwitchTime = 0; } /* ================== idBotAI::Bot_FindParkingSpotAoundLocaction ================== */ bool idBotAI::Bot_FindParkingSpotAoundLocaction( idVec3 &loc ) { float checkDist = 500.0f; idAngles ang; idVec3 end; end = loc; ang = end.ToAngles(); ang[ PITCH ] = 0.0f; end += ( checkDist * ang.ToMat3()[ 0 ] ); if ( !botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, NULL_AREANUM, loc, end ) ) { end = loc; ang[ YAW ] += 90.0f; end += ( checkDist * ang.ToMat3()[ 0 ] ); if ( !botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, NULL_AREANUM, loc, end ) ) { end = loc; ang[ YAW ] += 90.0f; end += ( checkDist * ang.ToMat3()[ 0 ] ); if ( !botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, NULL_AREANUM, loc, end ) ) { end = loc; ang[ YAW ] += 90.0f; end += ( checkDist * ang.ToMat3()[ 0 ] ); if ( !botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, NULL_AREANUM, loc, end ) ) { return false; //mal: no clear parking spot found, so just use the default location. } } } } loc = end; //mal: found a clear parking spot. return true; } /* ================== idBotAI::InAirVehicleGunSights ================== */ bool idBotAI::InAirVehicleGunSights( int vehicleNum, const idVec3 &org ) { proxyInfo_t vehicleInfo; GetVehicleInfo( vehicleNum, vehicleInfo ); if ( vehicleInfo.entNum == 0 ) { return false; } idVec3 dir = org - vehicleInfo.origin; dir.NormalizeFast(); if ( dir * vehicleInfo.axis[ 0 ] > 0.3f ) { return true;//mal: have someone in front of us.. } return false; } /* ================== idBotAI::InFrontOfVehicle ================== */ bool idBotAI::InFrontOfVehicle( int vehicleNum, const idVec3 &org, bool precise, float preciseValue ) { proxyInfo_t vehicleInfo; GetVehicleInfo( vehicleNum, vehicleInfo ); if ( vehicleInfo.entNum == 0 ) { return false; } idVec3 dir = org - vehicleInfo.origin; float dotCheck = 0.0f; if ( precise ) { dir.NormalizeFast(); dotCheck = preciseValue; } if ( dir * vehicleInfo.axis[ 0 ] > dotCheck ) { return true;//mal: have someone in front of us.. } return false; } /* ================== idBotAI::Bot_PickBestVehiclePosition See what the best seat in this ride is. Will be called when the bots not in combat ( will detect if a gunner's seat opens, or if our driver bails ) ================== */ void idBotAI::Bot_PickBestVehiclePosition() { if ( !botVehicleInfo->hasFreeSeat ) { return; } if ( botVehicleInfo->driverEntNum == botNum ) { return; } //mal: we're in the best position on this ride. if ( botVehicleInfo->driverEntNum == -1 ) { botUcmd->botCmds.becomeDriver = true; } if ( botInfo->proxyInfo.weapon != MINIGUN && botInfo->proxyInfo.weapon != LAW ) { //mal: if we're not already a gunner, check to see if a seat is open. if ( VehicleHasGunnerSeatOpen( botVehicleInfo->entNum ) ) { botUcmd->botCmds.becomeGunner = true; } else { if ( botVehicleInfo->type == BADGER ) { if ( botInfo->proxyInfo.weapon == NULL_VEHICLE_WEAPON ) { //mal: sitting on the bumper lets us use our SMG at least.... botUcmd->botCmds.activate = true; } } } } } /* ================ idBotAI::Bot_ExitVehicleAINode Vechile specific "Exit AI Node" ================ */ void idBotAI::Bot_ExitVehicleAINode( bool resetStack ) { lastActionNum = actionNum; Bot_ResetState( false, resetStack ); ResetRandomLook(); botIdealWeapSlot = GUN; botIdealWeapNum = NULL_WEAP; lastAINode = "Exiting Vehicle AI Node"; } /* ================ idBotAI::Bot_VehicleCanMove ================ */ bool idBotAI::Bot_VehicleCanMove( const moveDirections_t direction, float gUnits, bool copyEndPos ) { idVec3 end = botVehicleInfo->origin; switch ( direction ) { case FORWARD: end += ( gUnits * botVehicleInfo->axis[ 0 ] ); break; case BACK: end += ( -gUnits * botVehicleInfo->axis[ 0 ] ); break; case LEFT: end += ( -gUnits * ( botVehicleInfo->axis[ 1 ] * -1 ) ); break; case RIGHT: end += ( gUnits * ( botVehicleInfo->axis[ 1 ] * -1 ) ); break; } if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugLine( colorGreen, botVehicleInfo->origin, end, 16 ); } if ( botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, botInfo->areaNumVehicle, botVehicleInfo->aasVehicleOrigin, end ) ) { botCanMoveGoal = end; return true; } return false; } /* ================ idBotAI::Bot_SetupVehicleMove Sets up the bot's path goal while its in a vehicle. ================ */ void idBotAI::Bot_SetupVehicleMove( const idVec3 &org, int clientNum, int actionNumber, bool ignoreNodes ) { if ( vehiclePauseTime > botWorld->gameLocalInfo.time ) { botAAS.hasPath = true; vLTGTime += 50; nodeTimeOut += 50; return; } bool moveIsClear = false; int areaNum = 0; int i; float height = -idMath::INFINITY; aasTrace_t tr; idVec3 end; idVec3 goalOrigin; idBounds bbox = botAAS.aas->GetSettings()->boundingBox; obstacles.ClearObstacles(); bool botShouldMoveCautiously = BuildObstacleList( true, true ); if ( botVehicleInfo->type == ICARUS ) { Bot_SetupIcarusMove( org, clientNum, actionNumber ); return; } else if ( botVehicleInfo->type > ICARUS ) { //mal: icarus will use normal path move const int MAX_POINTS = 256; idVec3 pointsList[ MAX_POINTS ]; end = botInfo->viewOrigin; end.z += 64.0f; end += ( 4096.0f * botVehicleInfo->axis[ 0 ] ); botAAS.hasPath = true; botAAS.hasReachedVehicleNodeGoal = false; aasTraceHeight_t traceHeight; traceHeight.maxPoints = MAX_POINTS; traceHeight.numPoints = 0; traceHeight.points = pointsList; botAAS.aas->TraceHeight( traceHeight, botInfo->viewOrigin, end ); for( i = 0; i < traceHeight.numPoints; i++ ) { if ( traceHeight.points[ i ].z > height ) { height = traceHeight.points[ i ].z; if ( botThreadData.AllowDebugData() ) { if ( bot_showPath.GetInteger() == botNum ) { gameRenderWorld->DebugLine( colorGreen, traceHeight.points[ i ], traceHeight.points[ i ] + idVec3( 0.0f, 0.0f, 128.0f ) ); if ( i > 0 ) { gameRenderWorld->DebugLine( colorLtBlue, traceHeight.points[ i - 1 ], traceHeight.points[ i ] ); } } } if ( height >= botWorld->gameLocalInfo.maxVehicleHeight - 128.0f ) { botAAS.hasPath = false; } } } if ( botInfo->origin.z < height ) { botUcmd->botCmds.isBlocked = true; } if ( clientNum != -1 ) { botAAS.path.moveGoal = botWorld->clientInfo[ clientNum ].origin; } else if ( actionNumber != -1 ) { botAAS.path.moveGoal = botThreadData.botActions[ actionNumber ]->origin; } else { botAAS.path.moveGoal = org; } botAAS.path.moveGoal.z += height; idObstacleAvoidance::obstaclePath_t path; botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botVehicleInfo->bbox, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botVehicleInfo->origin, botAAS.path.moveGoal, path ); botAAS.path.moveGoal = path.seekPos; botAAS.obstacleNum = path.firstObstacle; if ( botAAS.obstacleNum != -1 ) { botUcmd->botCmds.isBlocked = true; } botAAS.path.viewGoal = botAAS.path.moveGoal; idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin; if ( idMath::Fabs( viewDelta.z ) < 100.0f ) { botAAS.path.viewGoal.z = botInfo->viewOrigin.z; } } else { //mal: must be a ground transport of some kind int travelFlags, walkTravelFlags; float radius = 64.0f; if ( botInfo->team == GDF ) { travelFlags = TFL_VALID_GDF; walkTravelFlags = TFL_VALID_WALK_GDF; } else { travelFlags = TFL_VALID_STROGG; walkTravelFlags = TFL_VALID_WALK_STROGG; } if ( clientNum != -1 ) { end = botWorld->clientInfo[ clientNum ].aasVehicleOrigin; } else if ( actionNumber != -1 ) { end = botThreadData.botActions[ actionNumber ]->GetActionOrigin(); radius = botThreadData.botActions[ actionNumber ]->GetRadius(); } else { end = org; } bool resetGoal = true; if ( !ignoreNodes ) { botAAS.hasReachedVehicleNodeGoal = Bot_CheckVehicleNodePath( end, goalOrigin, resetGoal ); } else { botAAS.hasReachedVehicleNodeGoal = false; } if ( resetGoal ) { badMoveTime = 500; botUcmd->specialMoveType = SKIP_MOVE; return; } areaNum = botAAS.aas->PointReachableAreaNum( goalOrigin, bbox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() ); if ( botVehicleInfo->areaNumVehicle != areaNum ) { botAAS.aas->PushPointIntoArea( areaNum, goalOrigin ); } idObstacleAvoidance::obstaclePath_t path; botAAS.hasPath = botAAS.aas->WalkPathToGoal( botAAS.path, botVehicleInfo->areaNumVehicle, botVehicleInfo->aasVehicleOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags ); if ( botThreadData.AllowDebugData() ) { if ( bot_showPath.GetInteger() == botNum ) { botAAS.aas->ShowWalkPath( botVehicleInfo->areaNumVehicle, botVehicleInfo->aasVehicleOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags ); gameRenderWorld->DebugCircle( colorGreen, end, idVec3( 0, 0, 1 ), radius, 32 ); end.z += 8.0f; gameRenderWorld->DebugCircle( colorRed, end, idVec3( 0, 0, 1 ), radius, 32 ); end.z += 8.0f; gameRenderWorld->DebugCircle( colorGreen, end, idVec3( 0, 0, 1 ), radius, 32 ); end.z += 8.0f; gameRenderWorld->DebugCircle( colorRed, end, idVec3( 0, 0, 1 ), radius, 32 ); } } //mal: experimented with a larger bbox for vehicle, now experiment with normal bbox again. float halfVehicleLength = botVehicleInfo->bbox[1][0]; idVec3 vehicleOrg = botVehicleInfo->origin; vehicleOrg += ( halfVehicleLength * botVehicleInfo->axis[ 0 ] ); /* if ( botVehicleInfo->isAirborneVehicle ) { //mal: do this AFTER calculate vehicleOrg. halfVehicleLength *= 1.5f; } else { halfVehicleLength *= 1.25f; } idBounds vehicleBounds; vehicleBounds[0][0] = vehicleBounds[0][1] = -halfVehicleLength; vehicleBounds[1][0] = vehicleBounds[1][1] = halfVehicleLength; vehicleBounds[0][2] = botVehicleInfo->bbox[0][2]; vehicleBounds[1][2] = botVehicleInfo->bbox[1][2]; */ botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botVehicleInfo->bbox /*vehicleBounds*/, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, vehicleOrg, botAAS.path.moveGoal, path ); botAAS.path.moveGoal = path.seekPos; botAAS.obstacleNum = path.firstObstacle; if ( botAAS.obstacleNum != -1 && botVehiclePathList.Num() > 0 ) { idVec3 origin; idBox obstacleBox; bool foundEnt = FindEntityByEntNum( botAAS.obstacleNum, origin, obstacleBox ); if ( foundEnt ) { if ( obstacleBox.ContainsPoint( botVehiclePathList[ botVehiclePathList.Num() - 1 ].node->origin ) ) { botVehiclePathList.SetNum( botVehiclePathList.Num() - 1 ); } } } if ( botVehicleInfo->canRotateInPlace && path.originalSeekPos != goalOrigin && botUcmd->botCmds.isBlocked == true ) { //mal: have to set this here, AFTER we run the path finding, but before we do the "moveIsClear" checks. botUcmd->botCmds.isBlocked = false; } if ( botShouldMoveCautiously && botVehicleInfo->type != GOLIATH ) { //mal: goliath looks nasty when it tries to move slow. botUcmd->specialMoveType = SLOWMOVE; } moveIsClear = Bot_CheckMoveIsClear( botAAS.path.moveGoal ); if ( botVehiclePathList.Num() > 0 && botVehicleInfo->canRotateInPlace == false && newPathTime > botWorld->gameLocalInfo.time && botVehicleInfo->type != PLATYPUS ) { //mal: if the first node on our path is directly behind us, back up a bit to reach it. idVec3 dir = botVehiclePathList[ botVehiclePathList.Num() - 1 ].node->origin - botVehicleInfo->origin; float nodeDist = dir.LengthSqr(); dir.NormalizeFast(); if ( -dir * botVehicleInfo->axis[ 0 ] > 0.5f && nodeDist > Square( botVehiclePathList[ botVehiclePathList.Num() - 1 ].node->radius ) ) { botUcmd->specialMoveType = REVERSEMOVE; } } if ( !moveIsClear ) { framesVehicleStuck++; int temp = framesVehicleStuck; if ( framesVehicleStuck > 10 ) { Bot_ExitVehicleAINode( true ); framesVehicleStuck = temp; } else if ( framesVehicleStuck > 150 ) { //mal: we've been stuck for too long, bail out and do something else! Bot_ExitVehicleAINode( true ); Bot_ExitVehicle(); Bot_IgnoreVehicle( botVehicleInfo->entNum, 15000 ); } } else { framesVehicleStuck = 0; } if ( moveIsClear && botInfo->team == GDF && botVehicleInfo->type != MCP && botUcmd->specialMoveType != REVERSEMOVE ) { if ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum != -1 && botWorld->botGoalInfo.mapHasMCPGoal ) { proxyInfo_t mcpInfo; GetVehicleInfo( botWorld->botGoalInfo.botGoal_MCP_VehicleNum, mcpInfo ); if ( InFrontOfVehicle( botVehicleInfo->entNum, mcpInfo.origin ) ) { idVec3 vec = mcpInfo.origin - botVehicleInfo->origin; if ( vec.LengthSqr() < Square( botAAS.aas->GetSettings()->obstaclePVSRadius * 2.0f ) ) { botUcmd->specialMoveType = SLOWMOVE; } } } } if ( path.firstObstacle != -1 && botWorld->gameLocalInfo.botsCanDecayObstacles ) { proxyInfo_t vehicle; GetVehicleInfo( path.firstObstacle, vehicle ); if ( vehicle.entNum != 0 && vehicle.type != MCP && vehicle.isEmpty && vehicle.xyspeed == 0.0f && InFrontOfVehicle( botVehicleInfo->entNum, vehicle.origin ) && !vehicle.neverDriven ) { int humansInArea = ClientsInArea( botNum, botInfo->origin, 1000.0f, vehicle.team, NOCLASS, false, false , false, true, true, true ); if ( humansInArea == 0 && OtherBotsWantVehicle( vehicle ) == false ) { if ( vehicle.spawnID != vehicleObstacleSpawnID ) { vehicleObstacleSpawnID = vehicle.spawnID; vehicleObstacleTime = botWorld->gameLocalInfo.time; } else if ( vehicleObstacleTime + 5000 < botWorld->gameLocalInfo.time ) { botUcmd->decayObstacleSpawnID = vehicle.spawnID; vehicleObstacleSpawnID = -1; vehicleObstacleTime = -1; } } } } /* if ( path.firstObstacle != -1 ) { botAAS.blockedByObstacleCounterInVehicle++; } else { botAAS.blockedByObstacleCounterInVehicle = 0; } */ if ( !botWorld->gameLocalInfo.inWarmup && botThreadData.random.RandomInt( 100 ) > 98 ) { if ( path.firstObstacle > -1 && path.firstObstacle < MAX_CLIENTS ) { const clientInfo_t& blockingClient = botWorld->clientInfo[ path.firstObstacle ]; if ( blockingClient.team == botInfo->team && !blockingClient.isBot ) { //mal: ONLY say move if both the bot and the client in question are not just spawning in! if ( blockingClient.invulnerableEndTime < botWorld->gameLocalInfo.time && botInfo->invulnerableEndTime < botWorld->gameLocalInfo.time ) { end = blockingClient.origin - botInfo->origin; if ( end.LengthSqr() < Square( 1500.0f ) ) { botUcmd->desiredChat = MOVE; botUcmd->botCmds.honkHorn = true; } } } } } //mal: play with the origin just a bit, so that its more at eye level. Gets us realistic view goals on the cheap. botAAS.path.viewGoal = botAAS.path.moveGoal; idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin; if ( viewDelta.z < -100.f || viewDelta.z > 100.f ) { botAAS.path.viewGoal.z = botInfo->viewOrigin.z; } } } /* ================ idBotAI::Bot_SetupVehicleQuickMove A fast version of SetupMove, that just has the bot blindly move towards its goal pos, only doing dynamic obstacle avoidance checks. ================ */ void idBotAI::Bot_SetupVehicleQuickMove( const idVec3 &org, bool largePlayerBBox ) { idVec3 tempOrigin; obstacles.ClearObstacles(); BuildObstacleList( largePlayerBBox, true ); idObstacleAvoidance::obstaclePath_t path; botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botVehicleInfo->bbox, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botVehicleInfo->origin, org, path ); botAAS.path.moveGoal = path.seekPos; if ( path.firstObstacle > -1 && path.firstObstacle < MAX_CLIENTS && botThreadData.random.RandomInt( 100 ) > 98 ) { if ( botWorld->clientInfo[ path.firstObstacle ].team == botInfo->team ) { tempOrigin = botWorld->clientInfo[ path.firstObstacle ].origin - botInfo->origin; if ( tempOrigin.LengthSqr() < Square( 1500.0f ) ) { botUcmd->desiredChat = MOVE; botUcmd->botCmds.honkHorn = true; } } } botAAS.obstacleNum = path.firstObstacle; botAAS.hasPath = path.hasValidPath; //mal: safety check - let the bot know if its blind wanderings just isn't working out. //mal: play with the origin just a bit, so that its more at eye level. Gets us realistic view goals on the cheap. tempOrigin = botAAS.path.moveGoal - botInfo->viewOrigin; botAAS.path.viewGoal = botAAS.path.moveGoal; if ( tempOrigin[ 2 ] > -100.0f && tempOrigin[ 2 ] < 100.0f ) { botAAS.path.viewGoal[ 2 ] -= tempOrigin[ 2 ]; } } /* ================ idBotAI::Bot_CheckMoveIsClear This is going to be one big, nasty function. You've been warned. ================ */ bool idBotAI::Bot_CheckMoveIsClear( idVec3& goalOrigin ) { bool forwardClear = false; float vehicleLength; float distToGoal; moveDirections_t moveDir; idVec3 v; idVec3 temp; idVec3 vehicleOrg; idVec3 forwardVehicleAxis; idVec3 rightVehicleAxis; v = botVehicleInfo->bbox[1] - botVehicleInfo->bbox[0]; vehicleLength = v[ 0 ]; moveDir = Bot_DirectionToLocation( goalOrigin, true ); v = goalOrigin - botVehicleInfo->origin; distToGoal = v.LengthSqr(); vehicleOrg = botVehicleInfo->origin + ( botVehicleInfo->axis * botVehicleInfo->bbox.GetCenter() ); forwardVehicleAxis = botVehicleInfo->axis[ 0 ]; forwardVehicleAxis.z = 0.0f; forwardVehicleAxis.NormalizeFast(); rightVehicleAxis = botVehicleInfo->axis[ 1 ]; rightVehicleAxis.z = 0.0f; rightVehicleAxis.NormalizeFast(); v = vehicleOrg; v += ( ( vehicleLength * 3.5f ) * forwardVehicleAxis ); bool moveIsClear = botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, botInfo->areaNumVehicle, vehicleOrg, v ); if ( !moveIsClear && !botVehicleInfo->canRotateInPlace ) { botUcmd->specialMoveType = SLOWMOVE; //mal: if in a tight area, dont go gunning it. } v = vehicleOrg; v += ( ( vehicleLength * 0.50f ) * forwardVehicleAxis ); if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugLine( colorYellow, vehicleOrg, v ); gameRenderWorld->DebugLine( colorRed, vehicleOrg, vehicleOrg + idVec3( 0, 0, 128 ) ); gameRenderWorld->DebugLine( colorGreen, goalOrigin, goalOrigin + idVec3( 0, 0, 256 ) ); } moveIsClear = botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, botInfo->areaNumVehicle, vehicleOrg, v ); bool isBlocked = false; temp = botVehicleInfo->aasVehicleOrigin - botVehicleInfo->origin; if ( botThreadData.AllowDebugData() ) { float tempFloat = temp.LengthFast(); gameRenderWorld->DrawText( va( "Dist = %.0f", tempFloat ), botVehicleInfo->origin + idVec3( 0, 0, 256 ), .5, colorWhite, mat3_identity ); } if ( temp.LengthSqr() > Square( vehicleLength / 2.f ) ) { isBlocked = true; } if ( botAAS.obstacleNum != -1 && botVehicleInfo->type != MCP ) { //mal: the obstacle avoidance should get us out of most situations, but if we're too close, it might fail. The MCP should just roll over all obstacles. idVec3 origin; idBox bbox; bool foundEnt = FindEntityByEntNum( botAAS.obstacleNum, origin, bbox ); if ( foundEnt ) { idVec3 vec = origin - vehicleOrg; if ( vec.LengthSqr() < Square( 1200.0f ) ) { //mal: far enough away to ignore.... idBox vehicleBBox = idBox( botVehicleInfo->bbox, botVehicleInfo->origin, botVehicleInfo->axis ); vehicleBBox.ExpandSelf( NORMAL_BOX_EXPAND ); if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugBox( colorYellow, vehicleBBox ); gameRenderWorld->DebugBox( colorBrown, bbox ); } if ( bbox.IntersectsBox( vehicleBBox ) && InFrontOfVehicle( botInfo->proxyInfo.entNum, origin ) ) { if ( botAAS.obstacleNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) { proxyInfo_t mcpInfo; GetVehicleInfo( botAAS.obstacleNum, mcpInfo ); if ( mcpInfo.entNum != 0 ) { if ( !mcpInfo.isImmobilized && mcpInfo.driverEntNum != -1 && !InFrontOfVehicle( mcpInfo.entNum, botVehicleInfo->origin ) ) { if ( vehiclePauseTime < botWorld->gameLocalInfo.time ) { vehiclePauseTime = botWorld->gameLocalInfo.time + 1500; return true; } } else { isBlocked = true; } } } else { isBlocked = true; } } } } } else if ( botVehicleInfo->type != MCP ) { float halfVehicleLength = botVehicleInfo->bbox[1][0]; idVec3 vehicleOrg = botVehicleInfo->origin + ( botVehicleInfo->axis * botVehicleInfo->bbox.GetCenter() ); vehicleOrg += ( halfVehicleLength * botVehicleInfo->axis[ 0 ] ); trace_t tr; idVec3 end = vehicleOrg; end += ( 128.0f * botVehicleInfo->axis[ 0 ] ); botThreadData.clip->Translation( CLIP_DEBUG_PARMS tr, vehicleOrg, end, botThreadData.GetVehicleTestBounds( botVehicleInfo->type ), botVehicleInfo->axis, MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX | MASK_VEHICLESOLID, GetGameEntity( botVehicleInfo->entNum ) ); if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugBounds( colorWhite, botThreadData.GetVehicleTestBounds( botVehicleInfo->type )->GetBounds(), vehicleOrg, botVehicleInfo->axis ); } if ( tr.fraction < 1.0f ) { isBlocked = true; if ( botThreadData.AllowDebugData() ) { if ( bot_debugGroundVehicles.GetInteger() == botNum ) { gameLocal.Printf("Bumper Blocked!\n"); } } } } if ( botVehicleInfo->wheelsAreOnGround != 1.f && !botVehicleInfo->inWater ) { vehicleWheelsAreOffGroundCounter++; } else { vehicleWheelsAreOffGroundCounter = 0; } if ( vehicleWheelsAreOffGroundCounter > 250 ) { //mal: bots stuck - get outta here! return false; } if ( ( botVehicleInfo->axis[ 2 ] * botAAS.aas->GetSettings()->invGravityDir ) < 0.80f ) { //mal: because of the physics, the vehicle could roll up a wall. Detect if thats happened, and backup if possible. 36 degrees == 0.80. isBlocked = true; } if ( vehicleWheelsAreOffGroundCounter > 30 ) { isBlocked = true; } if ( botVehicleInfo->type == PLATYPUS ) { isBlocked = false; moveIsClear = true; } if ( ( moveIsClear && !isBlocked ) && botIsBlockedTime < botWorld->gameLocalInfo.time ) { botIsBlocked = 0; botIsBlockedTime = 0; } else { botIsBlocked++; if ( botIsBlockedTime == 0 ) { botIsBlockedTime = botWorld->gameLocalInfo.time + 1200; } } if ( botIsBlocked < 30 ) { //mal: have a small grace period before we consider ourselves blocked. WAS 20 return true; } else { if ( botVehicleInfo->type == GOLIATH || botVehicleInfo->type == TITAN || botVehicleInfo->type == DESECRATOR || botVehicleInfo->type == MCP ) { botUcmd->botCmds.isBlocked = true; return true; } //mal: its NOT clear to move forward, see how far back we can move v = vehicleOrg; v += ( -( vehicleLength ) * forwardVehicleAxis ); if ( botThreadData.AllowDebugData() ) { if ( bot_debugGroundVehicles.GetInteger() == botNum ) { gameLocal.Printf("Blocked!\n"); gameRenderWorld->DebugLine( colorLtBlue, vehicleOrg, v ); } } int travelFlags = ( botInfo->team == GDF ) ? TFL_VALID_GDF : TFL_VALID_STROGG; int areaNum = botAAS.aas->PointReachableAreaNum( vehicleOrg, botAAS.aas->GetSettings()->boundingBox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() ); aasTraceFloor_t tr; travelFlags &= ~TFL_WALKOFFLEDGE; botAAS.aas->TraceFloor( tr, vehicleOrg, areaNum, v, travelFlags ); if ( moveDir == BACK || moveDir == FORWARD ) { goalOrigin = tr.endpos; botUcmd->specialMoveType = REVERSEMOVE; return true; } else { if ( tr.fraction >= 1.0f ) { //mal: its clear to move backward temp = v; if ( moveDir == LEFT ) { v += ( ( vehicleLength * 0.50f ) * ( rightVehicleAxis * -1 ) ); //mal: try the right of the vehicle. } else if ( moveDir == RIGHT ) { v += ( ( -vehicleLength * 0.50f ) * ( rightVehicleAxis * -1 ) ); //mal: try the left of the vehicle. } if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugLine( colorGreen, temp, v ); } moveIsClear = botThreadData.Nav_IsDirectPath( AAS_VEHICLE, botInfo->team, botInfo->areaNumVehicle, vehicleOrg, v ); if ( moveIsClear ) { goalOrigin = v; botUcmd->specialMoveType = REVERSEMOVE; return true; } } } } v = vehicleOrg; v += ( -( vehicleLength ) * forwardVehicleAxis ); goalOrigin = v; botUcmd->specialMoveType = REVERSEMOVE; botUcmd->botCmds.isBlocked = true; return false; } /* ================ idBotAI::Bot_CheckVehicleNodePath When the bots in a vehicle just moving to a non-combat goal, they'll use the vehicle node system to find a nice looking path. They'll still use the aas to get from node to node, but the nodes help define the roads, and the "drivable" areas better. ================ */ bool idBotAI::Bot_CheckVehicleNodePath( const idVec3& goalOrigin, idVec3& pathPoint, bool& resetGoal ) { idVec3 botOrigin = botVehicleInfo->origin; idMat3 botAxis = botVehicleInfo->axis; resetGoal = false; // check for reaching the current node if ( botVehiclePathList.Num() > 0 ) { idBotNode::botLink_t* curLink = &botVehiclePathList[ botVehiclePathList.Num() - 1 ]; float curDist = ( botOrigin - curLink->node->origin ).ToVec2().Length(); if ( botVehiclePathList.Num() > 1 && InFrontOfVehicle( botInfo->proxyInfo.entNum, botVehiclePathList[ botVehiclePathList.Num() - 2 ].node->origin ) && Bot_DirectionToLocation( curLink->node->origin, true ) == BACK ) { //mal: if we passed our node, forget it and go to next. botVehiclePathList.SetNum( botVehiclePathList.Num() - 1 ); curLink = &botVehiclePathList[ botVehiclePathList.Num() - 1 ]; curDist = ( botOrigin - curLink->node->origin ).ToVec2().Length(); } else if ( curDist < 500.0f ) { // check to see if we should start slowing down if ( botVehiclePathList.Num() > 1 ) { idVec3 vToNext; if ( botVehicleInfo->canRotateInPlace ) { vToNext = curLink->node->origin - botVehicleInfo->origin; } else { vToNext = botVehiclePathList[ botVehiclePathList.Num() - 2 ].node->origin - curLink->node->origin; } vToNext.Normalize(); float angleCheck = vToNext * botAxis[ 0 ]; if ( botVehicleInfo->canRotateInPlace && botAAS.obstacleNum == -1 ) { if ( angleCheck < 0.7f ) { botUcmd->botCmds.isBlocked = true; } } else { if ( angleCheck < 0.7f ) { botUcmd->specialMoveType = SLOWMOVE; } } } } else { if ( botVehicleInfo->canRotateInPlace && botAAS.obstacleNum == -1 ) { idVec3 vToNext; vToNext = curLink->node->origin - botVehicleInfo->origin; vToNext.Normalize(); float angleCheck = vToNext * botAxis[ 0 ]; if ( angleCheck < 0.7f ) { botUcmd->botCmds.isBlocked = true; } } else if ( botVehicleInfo->type == BADGER || botVehicleInfo->type == HOG ) { idVec3 vToNext; vToNext = curLink->node->origin - botVehicleInfo->origin; vToNext.Normalize(); float angleCheck = vToNext * botAxis[ 0 ]; if ( angleCheck < 0.7f ) { botUcmd->specialMoveType = SLOWMOVE; } } } float nodeRadius = ( botVehicleInfo->type == GOLIATH ) ? 150.0f : curLink->node->radius; if ( curDist < nodeRadius ) { botVehiclePathList.SetNum( botVehiclePathList.Num() - 1 ); nodeTimeOut = botWorld->gameLocalInfo.time + VEHICLE_NODE_TIMEOUT; } else { if ( nodeTimeOut < botWorld->gameLocalInfo.time ) { Bot_ExitVehicleAINode( true ); pathPoint = botVehicleInfo->origin; vehicleReverseTime = botWorld->gameLocalInfo.time + 700; //mal: just in case we got stuck, back up a bit. return false; } } if ( botVehiclePathList.Num() == 0 ) { pathPoint = goalOrigin; return true; } pathPoint = botVehiclePathList[ botVehiclePathList.Num() - 1 ].node->origin; return false; } int nodeAttempts = 1; idBotNode* ignoreNode = ( pathNodeTimer.ignorePathNodeTime > botWorld->gameLocalInfo.time ) ? pathNodeTimer.ignorePathNode : NULL; // We don't have a path, try to create one idBotNode* ourNode = botThreadData.botVehicleNodes.GetNearestNode( botAxis, botOrigin, botInfo->team, FORWARD, true, true, true, ignoreNode, botVehicleInfo->flags ); if ( ourNode == NULL ) { ourNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, botOrigin, botInfo->team, BACK, true, true, true, ignoreNode, botVehicleInfo->flags ); nodeAttempts++; } if ( ourNode == NULL ) { ourNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, botOrigin, botInfo->team, RIGHT, true, true, true, ignoreNode, botVehicleInfo->flags ); nodeAttempts++; } if ( ourNode == NULL ) { ourNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, botOrigin, botInfo->team, LEFT, true, true, true, ignoreNode, botVehicleInfo->flags ); nodeAttempts++; } if ( ourNode == NULL ) { ourNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, botOrigin, botInfo->team, NULL_DIR, false, true, false, ignoreNode, botVehicleInfo->flags ); //try again, with even less restraints. nodeAttempts++; } if ( ourNode != NULL ) { pathNodeTimer.ignorePathNodeTime = IGNORE_PATH_NODE_TIME; pathNodeTimer.ignorePathNode = ourNode; } if ( botThreadData.AllowDebugData() ) { botThreadData.Printf("Bot Client %i tried %i times to find a close node\n", botNum, nodeAttempts ); } idBotNode* goalNode = botThreadData.botVehicleNodes.GetNearestNode( mat3_identity, goalOrigin, botInfo->team, NULL_DIR, false, true, false, NULL, botVehicleInfo->flags ); // always assume the goal node is reachable if ( ourNode == NULL || goalNode == NULL ) { botThreadData.Warning( "Could not find path. NULL node" ); pathPoint = goalOrigin; resetGoal = true; return false; //mal: just use the aas to find us a path. } if ( goalNode == ourNode ) { if ( botVehicleInfo->canRotateInPlace ) { idVec3 vToNext; vToNext = ourNode->origin - botVehicleInfo->origin; float distSqr = vToNext.LengthSqr(); vToNext.Normalize(); float angleCheck = vToNext * botAxis[ 0 ]; if ( angleCheck < 0.7f && distSqr > Square( ourNode->radius ) ) { botUcmd->botCmds.isBlocked = true; pathPoint = goalOrigin; return false; } } pathPoint = goalOrigin; return true; } //mal: reached our goal. botThreadData.botVehicleNodes.CreateNodePath( botInfo, ourNode, goalNode, botVehiclePathList, botVehicleInfo->flags ); if ( botVehiclePathList.Num() == 0 ) { if ( ourNode != NULL && goalNode != NULL ) { botThreadData.Warning( "Could not find path from %d to node %d", ourNode->num, goalNode->num ); } else { botThreadData.Warning( "Could not find path. NULL node" ); } pathPoint = goalOrigin; resetGoal = true; return false; //mal: just use the aas to find a way to reach our goal. } else { pathPoint = botVehiclePathList[ botVehiclePathList.Num() - 1 ].node->origin; nodeTimeOut = botWorld->gameLocalInfo.time + VEHICLE_NODE_TIMEOUT; newPathTime = botWorld->gameLocalInfo.time + 1000; botPathFailedCounter = 0; } if ( botThreadData.AllowDebugData() ) { idVec3 blah = goalOrigin; for ( int i = 0; i < botVehiclePathList.Num(); i++ ) { gameRenderWorld->DebugArrow( colorBlue, blah, botVehiclePathList[i].node->origin, 1024 ); blah = botVehiclePathList[i].node->origin; } gameRenderWorld->DebugArrow( colorRed, botOrigin, botAAS.path.moveGoal, 1024 ); } return false; } /* ================== idBotAI::FindEntityByEntNum With threading, there is no way for me to search thru the game's entity lists to find out what entity may be blocking the bot, so we'll search thru the entities we track internally. ================== */ bool idBotAI::FindEntityByEntNum( int entNum, idVec3& origin, idBox& bbox ) { bool foundEnt = false; if ( entNum < MAX_CLIENTS ) { //mal: that was easy - just loop thru the clients for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i != entNum ) { continue; } const clientInfo_t& clientInfo = botWorld->clientInfo[ i ]; origin = clientInfo.origin; bbox = idBox( clientInfo.localBounds, clientInfo.origin, clientInfo.bodyAxis ); foundEnt = true; break; } } else if ( entNum < MAX_GENTITIES ) { for( int i = 0; i < MAX_VEHICLES; i++ ) { const proxyInfo_t& vehicleInfo = botWorld->vehicleInfo[ i ]; if ( vehicleInfo.entNum != entNum ) { continue; } origin = vehicleInfo.origin; bbox = idBox( vehicleInfo.bbox, vehicleInfo.origin, vehicleInfo.axis ); foundEnt = true; break; } if ( !foundEnt ) { for( int i = 0; i < MAX_DEPLOYABLES; i++ ) { const deployableInfo_t& deployableInfo = botWorld->deployableInfo[ i ]; if ( deployableInfo.entNum != entNum ) { continue; } origin = deployableInfo.origin; bbox = idBox( deployableInfo.bbox, deployableInfo.origin, deployableInfo.axis ); foundEnt = true; break; } } if ( !foundEnt ) { for( int i = 0; i < MAX_CLIENTS; i++ ) { const clientInfo_t& clientInfo = botWorld->clientInfo[ i ]; if ( clientInfo.supplyCrate.entNum != entNum ) { continue; } origin = clientInfo.supplyCrate.origin; bbox = idBox( clientInfo.supplyCrate.bbox, clientInfo.supplyCrate.origin, mat3_identity ); foundEnt = true; break; } } } else { for( int i = 0; i < botThreadData.botObstacles.Num(); i++ ) { if ( botThreadData.botObstacles[ i ]->num != entNum ) { continue; } origin = botThreadData.botObstacles[ i ]->bbox.GetCenter(); bbox = botThreadData.botObstacles[ i ]->bbox; foundEnt = true; break; } } if ( !foundEnt ) { botThreadData.Warning( "Bot %i can't find entity %i in \"FindEntityByEntNum\"!!", botNum, entNum ); //mal: argh! return false; } return true; } /* ================== idBotAI::Bot_IsInHeavyAttackVehicle Some vehicles ( tanks or attack aircraft ) should never be left unless the bot is critical to the mission. ================== */ bool idBotAI::Bot_IsInHeavyAttackVehicle( bool ignoreGroundVehicles ) { if ( botVehicleInfo == NULL ) { return false; } if ( botVehicleInfo->type == MCP ) { return false; } if ( ignoreGroundVehicles == true ) { if ( !( botVehicleInfo->flags & AIR ) ) { return false; } } else { if ( !( botVehicleInfo->flags & ARMOR ) && !( botVehicleInfo->flags & AIR ) || ( botVehicleInfo->flags & PERSONAL ) || botVehicleInfo->type == BUFFALO ) { return false; } } return true; } /* ================ idBotAI::Bot_SetupIcarusMove Sets up the bot's path goal when in an icarus. ================ */ void idBotAI::Bot_SetupIcarusMove( const idVec3 &org, int clientNum, int actionNumber ) { int areaNum; idVec3 goalOrigin; if ( botAAS.aas == NULL ) { return; } idBounds bbox = botAAS.aas->GetSettings()->boundingBox; idObstacleAvoidance::obstaclePath_t path; bool canBoost = false; if ( clientNum != -1 ) { areaNum = botWorld->clientInfo[ clientNum ].areaNumVehicle; goalOrigin = botWorld->clientInfo[ clientNum ].aasVehicleOrigin; } else if ( actionNumber != -1 ) { areaNum = botThreadData.botActions[ actionNumber ]->areaNumVehicle; goalOrigin = botThreadData.botActions[ actionNumber ]->origin; } else { areaNum = botAAS.aas->PointReachableAreaNum( org, bbox, AAS_AREA_REACHABLE_WALK, TravelFlagInvalidForTeam() ); goalOrigin = org; } if ( botInfo->hasGroundContact ) { //mal: when on the ground, the icarus is just like a player movement wise. isBoosting = false; int travelFlags, walkTravelFlags; if ( botInfo->team == GDF ) { travelFlags = TFL_VALID_GDF; walkTravelFlags = TFL_VALID_WALK_GDF; } else { travelFlags = TFL_VALID_STROGG; walkTravelFlags = TFL_VALID_WALK_STROGG; } if ( botInfo->areaNumVehicle != areaNum ) { botAAS.aas->PushPointIntoArea( areaNum, goalOrigin ); } botAAS.hasPath = botAAS.aas->WalkPathToGoal( botAAS.path, botInfo->areaNumVehicle, botInfo->aasVehicleOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags ); if ( botInfo->proxyInfo.boostCharge > 0.90f ) { if ( botAAS.aas->ExtendHopPathToGoal( botAAS.path, botInfo->areaNumVehicle, botInfo->aasVehicleOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags, idAASHopPathParms() ) ) { canBoost = true; } if ( botThreadData.AllowDebugData() ) { if ( bot_showPath.GetInteger() == botNum ) { botAAS.aas->ShowHopPath( botInfo->areaNumVehicle, botInfo->aasVehicleOrigin, areaNum, goalOrigin, travelFlags, walkTravelFlags, idAASHopPathParms() ); gameRenderWorld->DebugCircle( colorGreen, goalOrigin, idVec3( 0, 0, 1 ), 32, 32 ); } } } botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botInfo->localBounds, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botInfo->aasVehicleOrigin, botAAS.path.moveGoal, path ); if ( !botAAS.hasClearPath ) { canBoost = true; //mal: try to avoid obstacles by flying around them. } botAAS.obstacleNum = path.firstObstacle; if ( botAAS.obstacleNum != -1 ) { idVec3 origin; idBox bbox; bool foundEnt = FindEntityByEntNum( botAAS.obstacleNum, origin, bbox ); if ( foundEnt ) { idVec3 vec = origin - botVehicleInfo->origin; if ( vec.LengthSqr() < Square( 500.0f ) ) { //mal: far enough away to ignore.... idBox vehicleBBox = idBox( botVehicleInfo->bbox, botVehicleInfo->origin, botVehicleInfo->axis ); vehicleBBox.ExpandSelf( NORMAL_BOX_EXPAND ); if ( botThreadData.AllowDebugData() ) { gameRenderWorld->DebugBox( colorYellow, vehicleBBox ); gameRenderWorld->DebugBox( colorBrown, bbox ); } if ( bbox.IntersectsBox( vehicleBBox ) && InFrontOfVehicle( botInfo->proxyInfo.entNum, origin ) ) { canBoost = true; } } } } if ( canBoost ) { botUcmd->specialMoveType = ICARUS_BOOST; hopMoveGoal = botAAS.path.moveGoal; isBoosting = true; } botAAS.path.moveGoal = path.seekPos; if ( path.firstObstacle != -1 ) { botAAS.path.type = PATHTYPE_WALK; } } else if ( isBoosting ) { //mal: we're airborne, so now we handle like an air vehicle.... const int MAX_POINTS = 256; float height = -idMath::INFINITY; idVec3 pointsList[ MAX_POINTS ]; idVec3 end = botInfo->viewOrigin; end.z += 64.0f; end += ( 2048.0f * botVehicleInfo->axis[ 0 ] ); botAAS.hasPath = true; botAAS.hasReachedVehicleNodeGoal = false; aasTraceHeight_t traceHeight; traceHeight.maxPoints = MAX_POINTS; traceHeight.numPoints = 0; traceHeight.points = pointsList; botAAS.aas->TraceHeight( traceHeight, botInfo->viewOrigin, end ); for( int i = 0; i < traceHeight.numPoints; i++ ) { if ( traceHeight.points[ i ].z > height ) { height = traceHeight.points[ i ].z; if ( height >= botInfo->origin.z + idAASHopPathParms().maxHeight ) { botAAS.hasPath = false; } } } idVec3 vec = hopMoveGoal - botInfo->origin; vec.z = 0.0f; float goalDistSqr = vec.LengthSqr(); if ( height < botInfo->origin.z + idAASHopPathParms().maxHeight && botInfo->proxyInfo.boostCharge > 0.0f && goalDistSqr > Square( 1024.0f ) && botAAS.hasPath ) { botUcmd->specialMoveType = ICARUS_BOOST; } else { isBoosting = false; } botAAS.path.moveGoal = hopMoveGoal; botAAS.path.moveGoal.z += height; idObstacleAvoidance::obstaclePath_t path; botAAS.hasClearPath = obstacles.FindPathAroundObstacles( botVehicleInfo->bbox, botAAS.aas->GetSettings()->obstaclePVSRadius, botAAS.aas, botVehicleInfo->origin, botAAS.path.moveGoal, path ); botAAS.path.moveGoal = path.seekPos; botAAS.obstacleNum = path.firstObstacle; if ( botAAS.obstacleNum != -1 && botInfo->proxyInfo.boostCharge > 0.0f ) { //mal: fly over obstacles? botUcmd->specialMoveType = ICARUS_BOOST; isBoosting = true; } botAAS.path.viewGoal = botAAS.path.moveGoal; idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin; if ( idMath::Fabs( viewDelta.z ) < 100.0f ) { botAAS.path.viewGoal.z = botInfo->viewOrigin.z; } } if ( !botWorld->gameLocalInfo.inWarmup && botThreadData.random.RandomInt( 100 ) > 98 ) { if ( path.firstObstacle > -1 && path.firstObstacle < MAX_CLIENTS ) { if ( botWorld->clientInfo[ path.firstObstacle ].team == botInfo->team && !botWorld->clientInfo[ path.firstObstacle ].isBot ) { //mal: ONLY say move if both the bot and the client in question are not just spawning in! if ( botWorld->clientInfo[ path.firstObstacle ].invulnerableEndTime < botWorld->gameLocalInfo.time && botInfo->invulnerableEndTime < botWorld->gameLocalInfo.time ) { idVec3 tempOrigin = botWorld->clientInfo[ path.firstObstacle ].origin - botInfo->origin; if ( tempOrigin.LengthSqr() < Square( 50.0f ) ) { botUcmd->desiredChat = MOVE; } } } } } //mal: play with the origin just a bit, so that its more at eye level. Gets us realistic view goals on the cheap. botAAS.path.viewGoal = botAAS.path.moveGoal; idVec3 viewDelta = botAAS.path.moveGoal - botInfo->viewOrigin; if ( idMath::Fabs( viewDelta.z ) < 100.0f ) { botAAS.path.viewGoal.z = botInfo->viewOrigin.z; } } /* ================== idBotAI::Bot_VehicleIsUnderAVTAttack Checks to see if an AVT out there in the world is targeting us, in which case we'll attack it. ================== */ int idBotAI::Bot_VehicleIsUnderAVTAttack() { if ( botVehicleInfo == NULL ) { assert( false ); return false; } int deployableEnemyNum = -1; for( int i = 0; i < MAX_DEPLOYABLES; i++ ) { const deployableInfo_t& deployable = botWorld->deployableInfo[ i ]; if ( deployable.entNum == 0 ) { continue; } if ( deployable.health == 0 && deployable.maxHealth == 0 ) { continue; } if ( !deployable.inPlace ) { continue; } if ( deployable.team == botInfo->team ) { continue; } if ( deployable.type != AVT ) { continue; } if ( deployable.enemyEntNum != botNum && deployable.enemyEntNum != botVehicleInfo->entNum ) { continue; } idVec3 dangerOrigin = deployable.origin; //mal: they can lock onto us before they can really see us, which causes some issues for the bots. dangerOrigin.z += ( deployable.bbox[ 1 ][ 2 ] - deployable.bbox[ 0 ][ 2 ] ) * 0.95f; trace_t tr; botThreadData.clip->TracePointExt( CLIP_DEBUG_PARMS tr, botInfo->viewOrigin, dangerOrigin, MASK_SHOT_BOUNDINGBOX | MASK_VEHICLESOLID | CONTENTS_FORCEFIELD, GetGameEntity( botNum ), GetGameEntity( botVehicleInfo->entNum ) ); if ( tr.fraction < 1.0f && tr.c.entityNum != deployable.entNum ) { continue; } deployableEnemyNum = deployable.entNum; break; } return deployableEnemyNum; } /* ================== idBotAI::Bot_VehicleLTGIsAvailable ================== */ bool idBotAI::Bot_VehicleLTGIsAvailable( int clientNum, int actionNumber, const bot_Vehicle_LTG_Types_t goalType, int minNumClients ) { int numClients = 0; for( int 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! } if ( botThreadData.bots[ i ] == NULL ) { continue; } const clientInfo_t& playerInfo = botWorld->clientInfo[ i ]; if ( playerInfo.health <= 0 ) { continue; } if ( playerInfo.team != botInfo->team ) { continue; } if ( playerInfo.isBot != true ) { continue; } if ( botThreadData.bots[ i ] == NULL ) { continue; } if ( botThreadData.bots[ i ]->GetAIState() != VLTG ) { continue; } if ( botThreadData.bots[ i ]->GetVehicleLTGType() != goalType ) { continue; } if ( clientNum != -1 ) { if ( botThreadData.bots[ i ]->GetVehicleLTGTarget() != clientNum ) { continue; } } if ( actionNumber != ACTION_NULL ) { if ( botThreadData.bots[ i ]->GetActionNum() != actionNumber ) { continue; } } numClients++; if ( minNumClients == 1 ) { break; } } if ( numClients >= minNumClients ) { return false; } else { return true; } } /* ================== idBotAI::Bot_CheckHumanRequestingTransport ================== */ bool idBotAI::Bot_CheckHumanRequestingTransport() { if ( botVehicleInfo->type >= ICARUS && !botVehicleInfo->hasGroundContact ) { return false; } if ( botVehicleInfo->flags & PERSONAL ) { return false; } if ( !botVehicleInfo->hasFreeSeat ) { return false; } if ( vehicleSurrenderTime > botWorld->gameLocalInfo.time ) { //mal: already have someone we're waiting on. botMoveTypes_t moveType = ( botVehicleInfo->type > ICARUS ) ? LAND : FULL_STOP; Bot_MoveToGoal( vec3_zero, vec3_zero, NULLMOVEFLAG, moveType ); Bot_LookAtEntity( vehicleSurrenderClient, SMOOTH_TURN ); if ( !vehicleSurrenderChatSent ) { Bot_AddDelayedChat( botNum, NEED_LIFT, 1 ); vehicleSurrenderChatSent = true; botUcmd->botCmds.honkHorn = true; } return true; } bool hasEscortRequest = false; for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( !ClientIsValid( i, -1 ) ) { continue; } if ( i == botNum ) { continue; } if ( ClientIsIgnored( i ) ) { continue; } const clientInfo_t &player = botWorld->clientInfo[ i ]; if ( player.isBot ) { continue; } if ( player.team != botInfo->team ) { continue; } if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { continue; } if ( player.pickupRequestTime + 5000 < botWorld->gameLocalInfo.time ) { continue; } idVec3 vec = player.origin - botInfo->origin; float ourDistSqr = vec.LengthSqr(); if ( player.pickupTargetSpawnID != -1 ) { if ( player.pickupTargetSpawnID != botVehicleInfo->spawnID ) { continue; } } else { for( int j = 0; j < MAX_CLIENTS; j++ ) { //mal: check to make sure the bot is the closest vehicle to the player. if ( !ClientIsValid( j, -1 ) ) { continue; } if ( j == botNum ) { continue; } const clientInfo_t& bot = botWorld->clientInfo[ j ]; if ( !bot.isBot ) { continue; } if ( bot.team != botInfo->team ) { continue; } if ( bot.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) { continue; } vec = player.origin - bot.origin; float distSqr = vec.LengthSqr(); if ( distSqr > Square( MAX_RIDE_DIST ) ) { continue; } proxyInfo_t vehicle; GetVehicleInfo( bot.proxyInfo.entNum, vehicle ); if ( vehicle.driverEntNum != j ) { continue; } if ( !vehicle.hasFreeSeat || ( vehicle.type >= ICARUS && !vehicle.hasGroundContact ) || vehicle.flags & PERSONAL ) { continue; } if ( distSqr < ourDistSqr ) { //mal: someones closer to this guy then us, so just let him give the player a ride break; } } } if ( ourDistSqr > Square( MAX_RIDE_DIST ) ) { Bot_AddDelayedChat( botNum, CMD_DECLINED, 1 ); Bot_IgnoreClient( i, REQUEST_CONSIDER_TIME ); continue; } vehicleSurrenderTime = botWorld->gameLocalInfo.time + 10000; vehicleSurrenderChatSent = false; vehicleSurrenderClient = i; hasEscortRequest = true; break; } return hasEscortRequest; } /* ================== idBotAI::Bot_CheckIfClientHasRideWaiting ================== */ bool idBotAI::Bot_CheckIfClientHasRideWaiting( int clientNum ) { bool hasRide = false; for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( !ClientIsValid( i, -1 ) ) { continue; } if ( i == botNum ) { continue; } const clientInfo_t &player = botWorld->clientInfo[ i ]; if ( !player.isBot ) { continue; } if ( player.team != botInfo->team ) { continue; } if ( botThreadData.bots[ i ] == NULL ) { continue; } if ( botThreadData.bots[ i ]->GetVehicleSurrenderTime() < botWorld->gameLocalInfo.time ) { continue; } if ( botThreadData.bots[ i ]->GetVehicleSurrenderClient() != clientNum ) { continue; } hasRide = true; break; } return hasRide; } /* ================== idBotAI::VehiclesInArea ================== */ int idBotAI::VehiclesInArea( int ignoreClientNum, const idVec3 &org, float range, int team, bool vis2Sky, int ignoreVehicleNum, bool humanOnly ) { int clients = 0; for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i == ignoreClientNum ) { // dont scan the client who started this continue; } if ( !ClientIsValid( i, -1 ) ) { continue; //mal: no valid client in this client slot! } const clientInfo_t& playerInfo = botWorld->clientInfo[ i ]; if ( !playerInfo.inGame ) { continue; } if ( playerInfo.health <= 0 ) { //mal: dont count dead clients! continue; } if ( humanOnly ) { if ( playerInfo.isBot ) { continue; } } if ( playerInfo.proxyInfo.entNum == CLIENT_HAS_NO_VEHICLE ) { continue; } if ( playerInfo.proxyInfo.entNum == ignoreVehicleNum ) { continue; } if ( vis2Sky ) { if ( !LocationVis2Sky( playerInfo.origin ) ) { continue; } } if ( team != NOTEAM ) { if ( playerInfo.team != team ) { continue; } } idVec3 vec = playerInfo.origin - org; if ( vec.LengthSqr() > Square( range ) ) { continue; } clients++; } return clients; } /* ================== idBotAI::IcarusCombatMove ================== */ void idBotAI::IcarusCombatMove() { float evadeDist = 550.0f; const clientInfo_t& playerInfo = botWorld->clientInfo[ enemy ]; idVec3 vec = playerInfo.origin - botInfo->origin; vec.z = 0.0f; float distToEnemySqr = vec.LengthSqr(); if ( AIStack.stackActionNum != ACTION_NULL && AIStack.isPriority ) { idVec3 vec = botThreadData.botActions[ AIStack.stackActionNum ]->origin - botInfo->origin; vec.z = 0.0f; if ( vec.LengthSqr() > Square( evadeDist ) ) { Bot_SetupVehicleMove( vec3_zero, -1, AIStack.stackActionNum ); if ( MoveIsInvalid() && botVehicleInfo->hasGroundContact ) { //mal: move is failing, jump out when touch down, and fight normally. Bot_ExitVehicle(); return; } Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, RUN, NULLMOVETYPE ); Bot_LookAtLocation( botAAS.path.viewGoal, SMOOTH_TURN ); } else { if ( botVehicleInfo->hasGroundContact ) { //mal: reached our priority goal, now jump out so we can complete it ( hopefully ). Bot_ExitVehicle(); return; } } } else { moveDirections_t dirToTarget = Bot_DirectionToLocation( playerInfo.origin, false ); idVec3 goalOrigin = playerInfo.origin; if ( distToEnemySqr < Square( 2500.0f ) ) { if ( dirToTarget == FORWARD || dirToTarget == LEFT ) { //mal: move to the right of our enemy goalOrigin += ( 1500.0f * playerInfo.viewAxis[ 1 ] * -1 ); } else { goalOrigin += ( -1500.0f * playerInfo.viewAxis[ 1 ] * -1 ); //mal: move to the left of our enemy } } Bot_SetupVehicleMove( goalOrigin, -1, ACTION_NULL ); if ( MoveIsInvalid() && botVehicleInfo->hasGroundContact ) { //mal: move is failing, jump out when touch down, and fight normally. Bot_ExitVehicle(); return; } Bot_MoveToGoal( botAAS.path.moveGoal, vec3_zero, RUN, NULLMOVETYPE ); Bot_LookAtLocation( playerInfo.origin, SMOOTH_TURN ); } if ( distToEnemySqr < Square( 1500.0f ) && InFrontOfVehicle( botVehicleInfo->entNum, playerInfo.origin ) && !botVehicleInfo->hasGroundContact ) { botUcmd->botCmds.attack = true; } } /* ================ idBotAI::HumanVehicleOwnerNearby ================ */ bool idBotAI::HumanVehicleOwnerNearby( const playerTeamTypes_t playerTeam, const idVec3 &loc, float range, int vehicleSpawnID ) { bool humanOwner = false; for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { continue; } if ( !ClientIsValid( i, -1 ) ) { continue; } const clientInfo_t& playerInfo = botWorld->clientInfo[ i ]; if ( playerInfo.team != playerTeam ) { continue; } if ( playerInfo.health <= 0 ) { continue; } if ( playerInfo.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { //mal: this player is already in a vehicle, dont worry about him continue; } if ( playerInfo.lastOwnedVehicleSpawnID != vehicleSpawnID ) { //mal: the player was never in this vehicle, so dont worry about him continue; } if ( playerInfo.isBot ) { continue; } if ( playerInfo.lastOwnedVehicleTime + MAX_OWN_VEHICLE_TIME < botWorld->gameLocalInfo.time ) { //mal: after a certain amount of time, we don't care anymore. continue; } if ( playerInfo.xySpeed >= RUNNING_SPEED && !InFrontOfClient( i, loc ) ) { //mal: player is running away from this vehicle, so don't worry about him. continue; } idVec3 vec = playerInfo.origin - loc; if ( vec.LengthSqr() > Square( range ) ) { continue; } humanOwner = true; break; } return humanOwner; } /* ================ idBotAI::Bot_CheckFriendlyEngineerIsNearbyOurVehicle ================ */ bool idBotAI::Bot_CheckFriendlyEngineerIsNearbyOurVehicle() { bool friendNearby = false; if ( botVehicleInfo == false ) { return false; } if ( botVehicleInfo->health == botVehicleInfo->maxHealth ) { return false; } if ( enemy != -1 ) { return false; } for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { continue; } if ( !ClientIsValid( i, -1 ) ) { continue; } const clientInfo_t& player = botWorld->clientInfo[ i ]; if ( player.team != botInfo->team ) { continue; } if ( player.health <= 0 ) { continue; } if ( player.classType != ENGINEER ) { continue; } if ( player.isBot ) { if ( botThreadData.bots[ i ] == NULL ) { continue; } if ( botThreadData.bots[ i ]->GetNBGType() != FIX_VEHICLE || botThreadData.bots[ i ]->GetAIState() != NBG ) { continue; } if ( botThreadData.bots[ i ]->GetNBGTarget() != botVehicleInfo->entNum ) { continue; } friendNearby = true; break; } else { if ( player.weapInfo.weapon != PLIERS ) { continue; } if ( !InFrontOfClient( i, botVehicleInfo->origin ) ) { continue; } idVec3 vec = botVehicleInfo->origin - player.origin; if ( vec.LengthSqr() > Square( MAX_REPAIR_DIST ) ) { continue; } friendNearby = true; break; } } return friendNearby; } /* ================ idBotAI::Bot_CheckForHumanNearByWhoMayWantRide ================ */ bool idBotAI::Bot_CheckForHumanNearByWhoMayWantRide() { bool friendNearby = false; if ( enemy != -1 ) { return false; } if ( botVehicleInfo == NULL ) { return false; } if ( botVehicleInfo->type == ICARUS && !botVehicleInfo->hasGroundContact ) { return false; } for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { continue; } if ( !ClientIsValid( i, -1 ) ) { continue; } const clientInfo_t& player = botWorld->clientInfo[ i ]; if ( player.team != botInfo->team ) { continue; } if ( player.health <= 0 ) { continue; } if ( player.isBot ) { continue; } if ( player.proxyInfo.entNum != CLIENT_HAS_NO_VEHICLE ) { continue; } if ( player.xySpeed < RUNNING_SPEED ) { continue; } if ( !InFrontOfClient( i, botVehicleInfo->origin, true, 0.95f ) ) { continue; } idVec3 vec = botVehicleInfo->origin - player.origin; if ( vec.LengthSqr() > Square( MAX_CONSIDER_HUMAN_FOR_RIDE_DIST ) ) { continue; } friendNearby = true; break; } return friendNearby; } /* ================ idBotAI::Bot_GetVehicleGunnerClientNum ================ */ int idBotAI::Bot_GetVehicleGunnerClientNum( int vehicleEntNum ) { int clientNum = -1; proxyInfo_t vehicleInfo; botVehicleWeaponInfo_t weaponType = MINIGUN; GetVehicleInfo( vehicleEntNum, vehicleInfo ); if ( vehicleInfo.type == TROJAN ) { weaponType = LAW; } for( int i = 0; i < MAX_CLIENTS; i++ ) { if ( i == botNum ) { continue; } if ( !ClientIsValid( i, -1 ) ) { continue; } const clientInfo_t &playerInfo = botWorld->clientInfo[ i ]; if ( playerInfo.proxyInfo.entNum != vehicleEntNum ) { continue; } if ( playerInfo.proxyInfo.weapon != weaponType ) { continue; } clientNum = i; break; } return clientNum; } /* ================ idBotAI::VehicleGoalsExistForVehicle ================ */ bool idBotAI::VehicleGoalsExistForVehicle( const proxyInfo_t& vehicleInfo ) { bool goalExists = false; 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( vehicleInfo.team ) != ACTION_VEHICLE_ROAM && botThreadData.botActions[ i ]->GetObjForTeam( vehicleInfo.team ) != ACTION_VEHICLE_CAMP ) { continue; } if ( !( botThreadData.botActions[ i ]->GetActionVehicleFlags( vehicleInfo.team ) & vehicleInfo.flags ) && botThreadData.botActions[ i ]->GetActionVehicleFlags( vehicleInfo.team ) != 0 ) { continue; } goalExists = true; break; } return goalExists; } /* ================ idBotAI::Bot_WithObjShouldLeaveVehicle ================ */ bool idBotAI::Bot_WithObjShouldLeaveVehicle() { float vehicleExitSpeed = ( botVehicleInfo->isAirborneVehicle ) ? 1024.0f : WALKING_SPEED; if ( botVehicleInfo->type == BUFFALO ) { vehicleExitSpeed = BASE_VEHICLE_SPEED; } if ( ClientHasObj( botNum ) && botVehicleInfo->xyspeed < vehicleExitSpeed && ClientIsCloseToDeliverObj( botNum, 3000.0f ) ) { //mal: if the bot has the obj - they should leave if the player stops the vehicle near the obj. return true; } return false; } /* ================ idBotAI::Bot_CheckForDeployableTargetsWhileVehicleGunner ================ */ int idBotAI::Bot_CheckForDeployableTargetsWhileVehicleGunner() { if ( botInfo->proxyInfo.weapon != MINIGUN && botInfo->proxyInfo.weapon != LAW ) { return -1; } if ( botVehicleInfo == NULL ) { return -1; } bool hasMCP = ( botWorld->botGoalInfo.botGoal_MCP_VehicleNum != -1 && botWorld->botGoalInfo.mapHasMCPGoal ) ? true : false; int targetNum = -1; float closest = idMath::INFINITY; bool botActionValid = false; int botActionNum; idVec3 botActionOrg; if ( botInfo->team == GDF ) { botActionNum = botWorld->botGoalInfo.team_GDF_PrimaryAction; } else { botActionNum = botWorld->botGoalInfo.team_STROGG_PrimaryAction; } if ( botActionNum > -1 && botActionNum < botThreadData.botActions.Num() ) { botActionValid = true; botActionOrg = botThreadData.botActions[ botActionNum ]->GetActionOrigin(); } for( int i = 0; i < MAX_DEPLOYABLES; i++ ) { const deployableInfo_t& deployable = botWorld->deployableInfo[ i ]; if ( deployable.entNum == 0 ) { continue; } if ( deployable.team == botInfo->team ) { continue; } if ( DeployableIsIgnored( deployable.entNum ) ) { continue; } if ( deployable.health <= 0 ) { continue; } if ( deployable.ownerClientNum == -1 ) { //mal: dont attack static, map based deployables. continue; } if ( deployable.health < ( deployable.maxHealth / DEPLOYABLE_DISABLED_PERCENT ) ) { continue; } if ( !deployable.inPlace ) { continue; } if ( botWorld->gameLocalInfo.gameMap == SLIPGATE ) { //mal: need some special love for this unique map. if ( !Bot_CheckLocationIsOnSameSideOfSlipGate( deployable.origin ) ) { continue; } } bool isTargetingUs = ( deployable.enemyEntNum == botNum || ( botVehicleInfo != NULL && deployable.enemyEntNum == botVehicleInfo->entNum ) ) ? true : false; idVec3 vec = deployable.origin - botInfo->origin; float distSqr = vec.LengthSqr(); if ( distSqr > Square( GUNNER_ATTACK_DEPLOYABLE_DIST ) ) { continue; } //mal: set some kind of priority depending on the deployable type. if ( deployable.type == AVT ) { if ( isTargetingUs ) { distSqr = 1.0f; } else if ( hasMCP ) { if ( deployable.enemyEntNum == botWorld->botGoalInfo.botGoal_MCP_VehicleNum ) { distSqr = 5.0f; } } else if ( botActionValid ) { vec = botActionOrg - deployable.origin; if ( vec.LengthSqr() < Square( BOT_ATTACK_DEPLOYABLE_RANGE ) ) { distSqr -= Square( 1000.0f ); } } } else if ( deployable.type == APT ) { //mal: focus more on APTs that are guarding the obj we're trying to attack. if ( botActionValid ) { vec = botActionOrg - deployable.origin; if ( vec.LengthSqr() < Square( BOT_ATTACK_DEPLOYABLE_RANGE ) ) { distSqr -= Square( 1000.0f ); } } } if ( distSqr < closest ) { vec = deployable.origin; vec.z += ( deployable.bbox[ 1 ][ 2 ] - deployable.bbox[ 0 ][ 2 ] ) * Bot_GetDeployableOffSet( deployable.type ); if ( !Bot_CheckLocationIsVisible( vec, deployable.entNum, botVehicleInfo->entNum ) ) { continue; } targetNum = deployable.entNum; closest = distSqr; } } return targetNum; } /* ================ idBotAI::Bot_AttackDeployableTargetsWhileVehicleGunner ================ */ void idBotAI::Bot_AttackDeployableTargetsWhileVehicleGunner( int deployableTargetToKill ) { if ( deployableTargetToKill != vLTGDeployableTarget ) { vLTGDeployableTargetAttackTime = botWorld->gameLocalInfo.time; vLTGDeployableTarget = deployableTargetToKill; } deployableInfo_t deployable; if ( GetDeployableInfo( false, deployableTargetToKill, deployable ) ) { if ( vLTGDeployableTargetAttackTime + 5000 < botWorld->gameLocalInfo.time && ( botInfo->lastAttackedEntity != deployable.entNum || botInfo->lastAttackedEntityTime + 5000 < botWorld->gameLocalInfo.time ) ) { Bot_IgnoreDeployable( deployableTargetToKill, 1000 ); //mal: update often, incase player moves into a better position. return; } idVec3 deployableTargetOrg = deployable.origin; deployableTargetOrg.z += ( deployable.bbox[ 1 ][ 2 ] - deployable.bbox[ 0 ][ 2 ] ) * Bot_GetDeployableOffSet( deployable.type ); Bot_LookAtLocation( deployableTargetOrg, SMOOTH_TURN, true ); if ( InFrontOfClient( botNum, deployableTargetOrg, true, 0.95f ) ) { botUcmd->botCmds.attack = true; } } } /* ================ idBotAI::Bot_WhoIsCriticalForCurrentObjShouldLeaveVehicle ================ */ bool idBotAI::Bot_WhoIsCriticalForCurrentObjShouldLeaveVehicle() { float vehicleExitSpeed = ( botVehicleInfo->isAirborneVehicle ) ? 1024.0f : WALKING_SPEED; if ( botVehicleInfo->type == BUFFALO ) { vehicleExitSpeed = BASE_VEHICLE_SPEED; } if ( Client_IsCriticalForCurrentObj( botNum, 3000.0f ) && botVehicleInfo->xyspeed < vehicleExitSpeed ) { //mal: if the bot is critical for the obj - they should leave if the player stops the vehicle near the obj. return true; } return false; }