/*
===========================================================================
Wolfenstein: Enemy Territory GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Wolfenstein: Enemy Territory GPL Source Code (Wolf ET Source Code).
Wolf ET Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Wolf ET Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Wolf ET Source Code. If not, see .
In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
/*****************************************************************************
* name: ai_dmgoal_mp.c
*
* desc: Wolf bot AI
*
*
*****************************************************************************/
//
// MULTIPLAYER GOAL AI
//
#include "../game/g_local.h"
#include "../game/botlib.h"
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_char.h"
#include "../game/be_ai_chat.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../game/be_ai_weap.h"
#include "../botai/botai.h"
//
#include "ai_main.h"
#include "ai_team.h"
#include "ai_dmq3.h"
#include "ai_cmd.h"
//
#include "ai_dmnet_mp.h"
#include "ai_dmgoal_mp.h"
static bot_goal_t target;
/*
==================
BotMP_CheckClassActions()
==================
*/
qboolean BotMP_CheckClassActions( bot_state_t *bs ) {
qboolean hasLeader;
//
// if carrying flag, screw others
if ( BotCarryingFlag( bs->client ) && bs->enemy > -1 ) {
return qfalse;
}
// if we are following a carrier, or a non-bot, then stay close
hasLeader = qfalse;
if ( bs->leader > -1 ) {
if ( BotCarryingFlag( bs->leader ) || !( g_entities[bs->leader].r.svFlags & SVF_BOT ) ) {
hasLeader = qtrue;
}
}
/* if( bs->script.frameFlags & BSFFL_MOVETOTARGET ) {
hasLeader = qtrue; // dont go so far if we're trying to get to a marker
}*/
if ( bs->ainode == AINode_MP_MoveToAutonomyRange ) {
hasLeader = qtrue; // dont go too far off course
}
if ( BotClass_MedicCheckRevives( bs, ( hasLeader ? 300 : 600 ), &target, qtrue ) ) {
if ( target.entitynum != bs->target_goal.entitynum || bs->ainode != AINode_MP_MedicRevive ) {
bs->target_goal = target;
AIEnter_MP_MedicRevive( bs );
}
return qtrue;
}
if ( BotCarryingFlag( bs->client ) ) {
return qfalse; // only revive if we have flag (Gordon: or skip other checks if we have flag to make more sense...)
}
if ( BotClass_MedicCheckGiveHealth( bs, ( hasLeader ? 200 : 800 ), &target ) ) {
if ( target.entitynum != bs->target_goal.entitynum || bs->ainode != AINode_MP_MedicGiveHealth ) {
bs->target_goal = target;
AIEnter_MP_MedicGiveHealth( bs );
}
return qtrue;
}
if ( BotClass_LtCheckGiveAmmo( bs, ( hasLeader ? 200 : 800 ), &target ) ) {
if ( target.entitynum != bs->target_goal.entitynum || bs->ainode != AINode_MP_GiveAmmo ) {
bs->target_goal = target;
AIEnter_MP_GiveAmmo( bs );
}
return qtrue;
}
return qfalse;
}
/*
===================
BotMP_CheckEmergencyGoals
===================
*/
qboolean BotMP_CheckEmergencyGoals( bot_state_t *bs ) {
gentity_t *trav, *flag;
int i, t;
int list[32], numList;
int oldest = 0, oldestTime, oldIgnoreTime, areanum, numTeammates;
float dist, bestDist;
vec3_t center, brushPos, vec;
trace_t tr;
//
if ( bs->last_checkemergencytargets > level.time - 300 ) {
return qfalse;
}
bs->last_checkemergencytargets = level.time + rand() % 200;
//
oldIgnoreTime = bs->ignore_specialgoal_time;
bs->ignore_specialgoal_time = level.time + 2000;
numTeammates = BotNumTeamMates( bs, NULL, 0 );
// check for voice chats
BotCheckVoiceChats( bs );
// if a bot finds itself in a void, it should use DrirectMove() to get out of it
if ( !bs->areanum ) {
if ( bs->ainode == AINode_MP_NavigateFromVoid ) {
return qfalse;
}
AIEnter_MP_NavigateFromVoid( bs );
return qtrue;
}
// DEBUG MODE: bot_debug = 11
trap_Cvar_Update( &bot_debug );
if ( g_cheats.integer && bot_debug.integer == BOT_DEBUG_FOLLOW_PLAYER ) {
bs->leader = 0;
if ( BotGoalForEntity( bs, bs->leader, &target, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_DefendTarget( bs );
return qtrue;
} else {
bs->leader = -1;
}
// DEBUG MODE: bot_debug = 12 (dont look for goals)
} else if ( g_cheats.integer && bot_debug.integer == BOT_DEBUG_FOLLOW_PLAYER + 1 ) {
return qfalse;
}
if ( !BotIsPOW( bs ) ) {
// check for being outside autonomy range within reasonable cause
if ( BotCheckMovementAutonomy( bs, &bs->target_goal ) ) {
if ( bs->target_goal.entitynum == bs->leader && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
if ( bs->ainode == AINode_MP_MoveToAutonomyRange ) {
return qfalse;
}
// get back to it
AIEnter_MP_MoveToAutonomyRange( bs );
return qtrue;
}
// check for dropped flag
if ( BotFindDroppedFlag( &trav ) ) {
// if we are the owner and we just dropped it, then ignore it
if ( !( trav->r.ownerNum == bs->client && trav->botIgnoreTime > level.time ) ) {
// if we are already going for it
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse; //keep going for it
}
// GO GET IT
// do we have a route to the enemy flag?
if ( BotGoalForEntity( NULL, trav->s.number, &target, BGU_HIGH ) ) {
target.flags = GFL_NOSLOWAPPROACH;
//
if ( VectorDistanceSquared( target.origin, bs->origin ) < ( 1600 * 1600 ) || trap_InPVS( target.origin, bs->origin ) ) {
if ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl ) ) {
bs->target_goal = target;
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
//if (g_cheats.integer)
// G_Printf( "WARNING: dropped objective is unreachable\n" );
}
}
}
}
} else {
return qtrue;
}
// if we have a visible & damagable script_mover target, then we should try to blow it up
if ( ( trav = BotGetVisibleDamagableScriptMover( bs ) ) ) {
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_AttackTarget ) {
return qfalse;
}
if ( BotGoalForEntity( bs, trav->s.number, &target, BGU_MAXIMUM ) ) {
bs->target_goal = target;
AIEnter_MP_AttackTarget( bs );
return qtrue;
}
}
// if a checkpoint is nearby, touch it
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_CHECKPOINT ) ) ) {
// if the opposition team controls this checkpoint, or it hasnt been captured yet
if ( trav->count == ( bs->sess.sessionTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS ) || ( trav->count < 0 ) ) {
// if we can see it
VectorAdd( trav->r.absmin, trav->r.absmax, center );
VectorScale( center, 0.5, center );
if ( VectorDistanceSquared( center, bs->origin ) > ( 1024 * 1024 ) ) {
continue;
}
center[0] = trav->r.absmax[0];
center[1] = trav->r.absmax[1];
VectorSubtract( trav->r.absmax, trav->r.absmin, vec );
vec[2] = 0;
VectorNormalize( vec );
VectorAdd( center, vec, brushPos );
if ( !trap_InPVS( brushPos, bs->origin ) ) {
continue;
}
// GO GET IT
// do we have a route to the enemy flag?
if ( BotGoalForEntity( bs, trav->s.number, &target, BGU_MEDIUM ) ) {
// if we are already going for it
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse; //keep going for it
}
target.flags = GFL_NOSLOWAPPROACH;
//
if ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl ) ) {
bs->target_goal = target;
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
}
}
}
// in GT_WOLF mode on defense, if the flag is not at base, then all units should help get it back
if ( level.captureFlagMode ) {
//
if ( bs->sess.sessionTeam != level.attackingTeam ) {
//
if ( !BotFlagAtBase( bs->sess.sessionTeam, &flag ) ) {
//
// if we are pursuing the flag carrier, then continue
if ( bs->enemy >= 0 && BotCarryingFlag( bs->enemy ) ) {
return qfalse;
}
//
// go to the transmission point?
trav = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY );
if ( !trav ) {
trav = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE );
}
if ( trav ) {
//
if ( BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) < (int)ceil( 0.5 * numTeammates ) ) {
//
// if we are already heading for it, then continue
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
//
VectorAdd( trav->r.absmin, trav->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( trav->r.absmin, trav->r.absmax, list, 32 );
if ( numList ) {
oldestTime = -1;
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
if ( bestDist > 0 && oldestTime < 4000 && bestDist < 2000 ) {
BotClearGoal( &target );
// use this as the goal origin
VectorCopy( center, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = trav->s.number;
target.flags = GFL_NOSLOWAPPROACH;
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
// if we are already heading there, continue
if ( bs->target_goal.entitynum && bs->target_goal.entitynum == trav->s.number ) {
return qfalse;
}
//
bs->target_goal = target;
BotFindSparseDefendArea( bs, &bs->target_goal, qtrue );
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
}/* else { // go for the document room
BotFlagAtBase(bs->sess.sessionTeam, &flag);
//
// do we have a route to the flag?
BotClearGoal(&target);
target.entitynum = flag->s.number;
VectorCopy( flag->r.currentOrigin, center );
center[2] += 30;
target.areanum = trap_AAS_PointAreaNum(center);
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( flag->r.mins, target.mins );
VectorCopy( flag->r.maxs, target.maxs );
VectorCopy( flag->r.currentOrigin, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, target.areanum, bs->tfl);
if (t) {
// if we are already heading there, continue
if (bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_TouchTarget) return qfalse;
bs->target_goal = target;
BotFindSparseDefendArea(bs, &bs->target_goal);
AIEnter_MP_DefendTarget(bs);
return qtrue;
}
}*/
}
// keep sending some troops to the flag carrier, so we have the most direct route covered
flag = BotGetEnemyFlagCarrier( bs );
if ( flag && ( VectorDistanceSquared( flag->r.currentOrigin, bs->eye ) < ( 1024 * 1024 ) || trap_InPVS( flag->r.currentOrigin, bs->eye ) || BotNumTeamMatesWithTarget( bs, flag->s.number, NULL, 0 ) < 1 ) ) {
// HACK, err... lets just take a snapshot of where they are now, and go down there
bs->enemyvisible_time = trap_AAS_Time();
//update the reachability area and origin if possible
areanum = BotPointAreaNum( flag->s.number, flag->r.currentOrigin );
if ( areanum && trap_AAS_AreaReachability( areanum ) && trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, areanum, bs->tfl ) ) {
VectorCopy( flag->r.currentOrigin, bs->lastenemyorigin );
bs->lastenemyareanum = areanum;
//
if ( BotGoalForEntity( bs, flag->s.number, &target, BGU_HIGH ) ) {
bs->enemy = flag->s.number;
AIEnter_MP_Battle_Chase( bs );
bs->chase_time += 20.0;
return qtrue;
}
}
}
}
} else // if we are close to the flag, get it!
{
// is the enemy flag at base?
if ( ( ( bs->leader == -1 ) || !trap_InPVS( bs->origin, g_entities[bs->leader].r.currentOrigin ) || ( VectorDistanceSquared( bs->origin, g_entities[bs->leader].r.currentOrigin ) > ( MAX_BOTLEADER_DIST * MAX_BOTLEADER_DIST ) ) )
&& ( !BotCarryingFlag( bs->client ) )
&& ( BotFlagAtBase( level.attackingTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS, &flag ) == qtrue ) ) {
qboolean getflag = qtrue;
//
// check special circumstances in which we should not go for the flag
if ( bs->ainode == AINode_MP_MedicRevive || bs->ainode == AINode_MP_MedicGiveHealth ) {
// if we are close to them, continue
if ( trap_InPVS( bs->origin, g_entities[bs->target_goal.entitynum].r.currentOrigin ) &&
VectorDistanceSquared( bs->origin, g_entities[bs->target_goal.entitynum].r.currentOrigin ) < ( 512 * 512 ) ) {
getflag = qfalse;
}
}
//
if ( getflag ) {
// GO GET IT
// do we have a route to the enemy flag?
if ( BotGoalForEntity( bs, flag->s.number, &target, BGU_MEDIUM ) ) {
target.flags = GFL_NOSLOWAPPROACH;
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( t ) {
if ( trap_InPVS( bs->origin, target.origin ) ) {
t = t / 2 - 300;
}
if ( t <= 0 ) {
t = 1;
}
if ( t < 700 ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
}
}
}
}
}
}
}
// check for medic trying to give us health
// NOTE: check that we havent just spawned, since this can cause congestion at the start
if ( ( g_entities[bs->client].client->respawnTime < level.time - 12000 ) && g_entities[bs->client].awaitingHelpTime > level.time + 200 ) {
if ( bs->enemy < 0 && bs->stand_time < trap_AAS_Time() && !BotCarryingFlag( bs->client ) ) {
AIEnter_MP_Stand( bs );
bs->stand_time = trap_AAS_Time() + 1.0;
return qtrue;
}
}
// if we are a medic, we should always revive nearby players
if ( BotClass_MedicCheckRevives( bs, 300, &target, qtrue ) ) {
if ( !BotCarryingFlag( bs->client ) || ( ( bs->enemy == -1 ) && ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->target_goal.areanum, bs->tfl ) > 600 ) ) ) {
if ( target.entitynum == bs->target_goal.entitynum && bs->ainode != AINode_MP_MedicRevive ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_MedicRevive( bs );
return qtrue;
}
}
// PANZER TARGETS
if ( ( COM_BitCheck( bs->cur_ps.weapons, WP_PANZERFAUST ) ) ) {
if ( ( BotWeaponWantScale( bs, WP_PANZERFAUST ) > 0 ) ) {
team_t team = bs->sess.sessionTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS;
// shoot towards gun emplacements after spawning in
i = 0;
trav = NULL;
numList = 0;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_MG42 ) ) ) {
if ( ( trav->aiInactive && ( 1 << team ) ) ) {
continue;
}
if ( trav->s.powerups != STATE_DEFAULT ) {
continue;
}
if ( trav->health <= 0 ) {
continue;
}
// make sure we can see it
VectorCopy( trav->r.currentOrigin, center );
center[2] += 12;
if ( !trap_InPVS( bs->eye, center ) ) {
continue;
}
trap_Trace( &tr, bs->eye, NULL, NULL, center, bs->client, MASK_MISSILESHOT );
if ( tr.fraction < 1.0 && tr.entityNum != trav->s.number ) {
continue;
}
//
list[numList++] = trav->s.number;
i++;
}
//
if ( i ) {
// pick a random mg42
t = rand() % i;
trav = BotGetEntity( list[t] );
// trav is the mg42
trav->missionLevel = level.time + 10000;
//
target.entitynum = trav->s.number;
VectorCopy( trav->r.currentOrigin, center );
center[2] += 12;
VectorCopy( center, target.origin );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
bs->target_goal = target;
AIEnter_MP_PanzerTarget( bs );
return qtrue;
}
}
}
}
// defense engineers should look for dynamite placed nearby objectives to defuse
{
int list[10], numList;
//
if ( ( numList = BotGetTargetDynamite( list, 10, NULL ) ) ) {
for ( i = 0; i < numList; i++ ) {
trav = BotGetEntity( list[i] );
if ( !trav ) {
continue;
}
// Gordon: WTH is this for?
if ( bs->sess.playerType != PC_ENGINEER ) {
if ( BotCanSnipe( bs, qtrue ) ) {
if ( level.explosiveTargets[bs->sess.sessionTeam == TEAM_AXIS ? 0 : 1] != BotGetTargetExplosives( bs->sess.sessionTeam, NULL, 0, qfalse )
|| BotNumTeamMatesWithTarget( bs, list[i], NULL, 0 ) >= (int)( floor( 0.3 * numTeammates / numList ) ) ) {
continue;
}
}
}
// if this dynamite has been laid by our team, and we are an engineer, no need to defend it
if ( bs->sess.playerType == PC_ENGINEER && ( ( trav->s.teamNum % 4 ) == bs->sess.sessionTeam ) ) {
continue;
}
//if (trav->missionLevel > level.time) continue;
if ( BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) > (int)floor( (float)( numTeammates / numList ) ) ) {
continue;
}
VectorAdd( trav->r.absmin, trav->r.absmax, center );
VectorScale( center, 0.5, center );
if ( ( areanum = BotPointAreaNum( trav->s.number, center ) ) ) {
//if (areanum = BotReachableBBoxAreaNum( bs, trav->r.absmin, trav->r.absmax )) {
if ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, areanum, bs->tfl ) ) {
// make this our goal
BotClearGoal( &target );
target.entitynum = trav->s.number;
target.areanum = areanum;
VectorCopy( trav->r.mins, target.mins );
VectorCopy( trav->r.maxs, target.maxs );
VectorCopy( center, target.origin );
//
// if we are an engineer, and this is hostile dynamite, we should defuse it, otherwise defend it
if ( bs->sess.playerType == PC_ENGINEER && ( ( trav->s.teamNum % 4 ) != bs->sess.sessionTeam ) ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
// if we are already heading there, continue
if ( bs->target_goal.entitynum && bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_DisarmDynamite ) {
return qfalse;
}
bs->target_goal = target;
trav->missionLevel = level.time + 5000;
AIEnter_MP_DisarmDynamite( bs );
return qtrue;
}
// only "defend" the dynamite if they are yet to break through any of the defenses
} else if ( ( BotNumTeamMatesWithTargetAndCloser( bs, target.entitynum, target.areanum, NULL, 0, -1 ) < (int)( ceil( 0.3 * numTeammates / numList ) ) ) ) {
// if we are already heading there, continue
if ( bs->target_goal.entitynum && bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
// just "defend" it
bs->target_goal = target;
BotFindSparseDefendArea( bs, &bs->target_goal, qtrue );
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
}
}
}
//
// look for healing/giving ammo to others
if ( BotMP_CheckClassActions( bs ) ) {
return qtrue;
}
//
bs->ignore_specialgoal_time = oldIgnoreTime;
return qfalse;
}
int botgoalPriorities_Standard[BFG_NUMBOTGOALTYPES] = {
0, // BFG_FOLLOW_LEADER
-1, // BFG_CONSTRUCT
2, // BFG_TRIGGER
-1, // BFG_DESTRUCTION_EXPLOSIVE
-1, // BFG_DESTRUCTION_BUILDING
-1, // BFG_MG42_REPAIR
-1, // BFG_MINE
1, // BFG_ATTRACTOR
-1, // BFG_SNIPERSPOT
2, // BFG_MG42
-1, // BFG_SCANFORMINES
-1, // BFG_DESTRUCTION_SATCHEL
};
int botgoalPriorities_Engineer[BFG_NUMBOTGOALTYPES] = {
1, // BFG_FOLLOW_LEADER
5, // BFG_CONSTRUCT
3, // BFG_TRIGGER
5, // BFG_DESTRUCTION_EXPLOSIVE
5, // BFG_DESTRUCTION_BUILDING
4, // BFG_MG42_REPAIR
4, // BFG_MINE
2, // BFG_ATTRACTOR
-1, // BFG_SNIPERSPOT
0, // BFG_MG42
-1, // BFG_SCANFORMINES
-1, // BFG_DESTRUCTION_SATCHEL
};
int botgoalPriorities_CovertOps[BFG_NUMBOTGOALTYPES] = {
1, // BFG_FOLLOW_LEADER
-1, // BFG_CONSTRUCT
3, // BFG_TRIGGER
-1, // BFG_DESTRUCTION_EXPLOSIVE
-1, // BFG_DESTRUCTION_BUILDING
-1, // BFG_MG42_REPAIR
-1, // BFG_MINE
2, // BFG_ATTRACTOR
4, // BFG_SNIPERSPOT
0, // BFG_MG42
4, // BFG_SCANFORMINES
4, // BFG_DESTRUCTION_SATCHEL
};
int *botgoalPriorities_Class[NUM_PLAYER_CLASSES] = {
botgoalPriorities_Standard, // PC_SOLDIER
botgoalPriorities_Standard, // PC_MEDIC
botgoalPriorities_Engineer, // PC_ENGINEER
botgoalPriorities_Standard, // PC_FIELDOPS
botgoalPriorities_CovertOps // PC_COVERTOPS
};
int botgoalMaxCloser[BFG_NUMBOTGOALTYPES] = {
2, // BFG_FOLLOW_LEADER
1, // BFG_CONSTRUCT
2, // BFG_TRIGGER
1, // BFG_DESTRUCTION_EXPLOSIVE
1, // BFG_DESTRUCTION_BUILDING
0, // BFG_MG42_REPAIR
0, // BFG_MINE
2, // BFG_ATTRACTOR
0, // BFG_SNIPERSPOT
0, // BFG_MG42
0, // BFG_SCANFORMINES
0, // BFG_DESTRUCTION_SATCHEL
};
typedef struct botgoalFind_s {
gentity_t* ent;
botgoalFindType_t type;
qboolean ignore;
int priority;
int defaultPriority;
int numPlayers;
} botgoalFind_t;
#define MAX_FIND_BOTGOALS 32
#define BFG_CHECKMAX if ( goalNum >= maxGoals ) { return maxGoals; }
#define BFG_CHECKDISABLED( ent,bs ) ent->aiInactive & ( 1 << bs->sess.sessionTeam )
int BotMP_FindGoal_BuildGoalList( bot_state_t* bs, botgoalFind_t* pGoals, int maxGoals ) {
int goalNum = 0;
int leader, numTargets;
int k, c;
int tlist[10];
gentity_t *ent, *trav;
BFG_CHECKMAX;
leader = BotGetLeader( bs, ( bs->sess.playerType == PC_SOLDIER ) );
if ( leader != -1 && g_entities[leader].r.svFlags & SVF_BOT ) {
pGoals[goalNum].type = BFG_FOLLOW_LEADER;
pGoals[goalNum].ent = &g_entities[leader];
pGoals[goalNum].priority = g_entities[leader].goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
// trigger_multiples
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_TRIGGER_MULTIPLE ) ) ) {
// is it disabled?
if ( BFG_CHECKDISABLED( trav, bs ) ) {
continue;
}
// is it active?
if ( !trav->r.linked || ( trav->nextthink > level.time + 1000 ) ) {
continue; // if it's thinking, then it's not active (doesnt respond to touch)
}
pGoals[goalNum].type = BFG_TRIGGER;
pGoals[goalNum].ent = trav;
pGoals[goalNum].priority = trav->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
if ( bs->sess.playerType == PC_ENGINEER ) {
// Constructibles
// find a constructible
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
// is it disabled?
if ( BFG_CHECKDISABLED( trav, bs ) ) {
continue;
}
// find the constructible
ent = G_ConstructionForTeam( trav, bs->sess.sessionTeam );
if ( !ent ) {
continue;
}
if ( !BotIsConstructible( bs->sess.sessionTeam, trav->s.number ) ) {
continue;
}
pGoals[goalNum].type = BFG_CONSTRUCT;
pGoals[goalNum].ent = trav;
pGoals[goalNum].priority = trav->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
// check for dynamite
// look for things to blow up
if ( BotWeaponCharged( bs, WP_DYNAMITE ) ) {
numTargets = BotGetTargetExplosives( bs->sess.sessionTeam, tlist, 10, qfalse );
for ( k = 0; k < numTargets; k++ ) {
// see if this is a worthy goal
ent = &g_entities[tlist[k]];
if ( ent->s.eType == ET_OID_TRIGGER ) {
pGoals[goalNum].type = BFG_DESTRUCTION_EXPLOSIVE;
if ( ent->target_ent ) {
pGoals[goalNum].priority = ent->target_ent->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
} else {
// Gordon: this shouldn't happen really....
pGoals[goalNum].priority = ent->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
}
} else {
pGoals[goalNum].type = BFG_DESTRUCTION_BUILDING;
pGoals[goalNum].priority = ent->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
}
pGoals[goalNum].ent = ent;
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
}
// look for a broken MG42 that isnt already being fixed
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_MG42 ) ) ) {
// is it disabled?
if ( BFG_CHECKDISABLED( trav, bs ) ) {
continue;
}
if ( trav->health <= 0 ) {
if ( trav->melee ) {
ent = trav->melee;
if ( ( ent->botIgnoreTime < level.time ) ) {
pGoals[goalNum].type = BFG_MG42_REPAIR;
pGoals[goalNum].ent = ent;
pGoals[goalNum].priority = trav->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
}
}
}
// PLANT LAND MINES
if ( G_CountTeamLandmines( bs->sess.sessionTeam ) < MAX_TEAM_LANDMINES ) {
// look for a land mine area that isn't full
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_BOT_LANDMINE_AREA ) ) ) {
// is it disabled?
if ( BFG_CHECKDISABLED( trav, bs ) ) {
continue;
}
// is it for us?
if ( ( trav->spawnflags & 1 ) && ( bs->sess.sessionTeam != TEAM_AXIS ) ) {
continue;
} else if ( ( trav->spawnflags & 2 ) && ( bs->sess.sessionTeam != TEAM_ALLIES ) ) {
continue;
}
// has it got enough landmines?
if ( trav->count2 >= trav->count ) {
continue;
}
pGoals[goalNum].type = BFG_MINE;
pGoals[goalNum].ent = trav;
pGoals[goalNum].priority = trav->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
}
}
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_BOT_ATTRACTOR ) ) ) {
if ( BFG_CHECKDISABLED( trav, bs ) ) {
continue;
}
pGoals[goalNum].type = BFG_ATTRACTOR;
pGoals[goalNum].ent = trav;
pGoals[goalNum].priority = trav->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
// SNIPER SPOT
if ( BotCanSnipe( bs, qtrue ) ) {
c = BotBestSniperSpot( bs );
if ( c != -1 ) {
pGoals[goalNum].type = BFG_SNIPERSPOT;
pGoals[goalNum].ent = &g_entities[c];
pGoals[goalNum].priority = g_entities[c].goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
}
if ( bs->sess.playerType == PC_COVERTOPS ) {
c = BotBestLandmineSpotingSpot( bs );
if ( c != -1 ) {
pGoals[goalNum].type = BFG_SCANFORMINES;
pGoals[goalNum].ent = &g_entities[c];
pGoals[goalNum].priority = g_entities[c].goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
numTargets = BotGetTargetsForSatchelCharge( bs->sess.sessionTeam, tlist, 10, qfalse );
for ( k = 0; k < numTargets; k++ ) {
// see if this is a worthy goal
ent = &g_entities[tlist[k]];
pGoals[goalNum].type = BFG_DESTRUCTION_SATCHEL;
pGoals[goalNum].priority = ent->goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].ent = ent;
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
}
// MG42
c = BotBestMG42Spot( bs, qfalse );
if ( c != -1 ) {
pGoals[goalNum].type = BFG_MG42;
pGoals[goalNum].ent = &g_entities[c];
pGoals[goalNum].priority = g_entities[c].goalPriority[bs->sess.sessionTeam - TEAM_AXIS];
pGoals[goalNum].defaultPriority = botgoalPriorities_Class[bs->sess.playerType][pGoals[goalNum].type];
goalNum++;
BFG_CHECKMAX;
}
return goalNum;
}
int QDECL BotMP_FindGoals_Sort_Standard( const void *a, const void *b ) {
botgoalFind_t* bg1 = (botgoalFind_t*)a;
botgoalFind_t* bg2 = (botgoalFind_t*)b;
if ( botgoalPriorities_Standard[bg1->type] > botgoalPriorities_Standard[bg2->type] ) {
return -1;
} else if ( botgoalPriorities_Standard[bg2->type] > botgoalPriorities_Standard[bg1->type] ) {
return 1;
}
if ( bg1->priority > bg2->priority ) {
return -1;
} else if ( bg2->priority > bg1->priority ) {
return 1;
}
return 0;
}
int QDECL BotMP_FindGoals_Sort_Engineer( const void *a, const void *b ) {
botgoalFind_t* bg1 = (botgoalFind_t*)a;
botgoalFind_t* bg2 = (botgoalFind_t*)b;
if ( botgoalPriorities_Engineer[bg1->type] > botgoalPriorities_Engineer[bg2->type] ) {
return -1;
} else if ( botgoalPriorities_Engineer[bg2->type] > botgoalPriorities_Engineer[bg1->type] ) {
return 1;
}
if ( bg1->priority > bg2->priority ) {
return -1;
} else if ( bg2->priority > bg1->priority ) {
return 1;
}
return 0;
}
int QDECL BotMP_FindGoals_Sort_CovertOps( const void *a, const void *b ) {
botgoalFind_t* bg1 = (botgoalFind_t*)a;
botgoalFind_t* bg2 = (botgoalFind_t*)b;
if ( botgoalPriorities_CovertOps[bg1->type] > botgoalPriorities_CovertOps[bg2->type] ) {
return -1;
} else if ( botgoalPriorities_CovertOps[bg2->type] > botgoalPriorities_CovertOps[bg1->type] ) {
return 1;
}
if ( bg1->priority > bg2->priority ) {
return -1;
} else if ( bg2->priority > bg1->priority ) {
return 1;
}
return 0;
}
typedef int QDECL sortFunc ( const void *a, const void *b );
sortFunc* botmp_sortFuncs[NUM_PLAYER_CLASSES] = {
BotMP_FindGoals_Sort_Standard, // PC_SOLDIER
BotMP_FindGoals_Sort_Standard, // PC_MEDIC
BotMP_FindGoals_Sort_Engineer, // PC_ENGINEER
BotMP_FindGoals_Sort_Standard, // PC_FIELDOPS
BotMP_FindGoals_Sort_CovertOps // PC_COVERTOPS
};
int BotMP_FindGoal_ClassForGoalType( botgoalFindType_t type ) {
switch ( type ) {
case BFG_CONSTRUCT:
case BFG_DESTRUCTION_EXPLOSIVE:
case BFG_DESTRUCTION_BUILDING:
case BFG_MG42_REPAIR:
case BFG_MINE:
return PC_ENGINEER;
case BFG_SNIPERSPOT:
case BFG_SCANFORMINES:
case BFG_DESTRUCTION_SATCHEL:
return PC_COVERTOPS;
default:
break;
}
return -1;
}
#define BFG_RETURN_SUCCESS return BPG_NEWGOAL
#define BFG_RETURN_FAILURE return BPG_FAILED
#define BFG_RETURN_ALREADY return BPG_ALREADYDOING
#define BFG_RETURN_FAILURE_AND_IGNORE bg->ignore = qtrue; BFG_RETURN_FAILURE
#define BFG_GOAL_ENTITYNUM bg->ent - g_entities
typedef enum botMPpg_e {
BPG_FAILED,
BPG_NEWGOAL,
BPG_ALREADYDOING,
} botMPpg_t;
botMPpg_t BotMP_FindGoal_ProcessGoal( bot_state_t* bs, botgoalFind_t* bg, bot_goal_t* target_goal ) {
int t, i;
vec3_t mins, maxs;
vec3_t center, brushPos, vec, end;
vec_t dist;
int list[32];
int numList;
trace_t tr;
weapon_t weap;
switch ( bg->type ) {
case BFG_FOLLOW_LEADER:
// we have a leader, follow them
if ( BotGoalForEntity( bs, BFG_GOAL_ENTITYNUM, target_goal, BGU_LOW ) ) {
target_goal->flags = GFL_LEADER;
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( t && t <= MAX_BOTLEADER_TRAVEL ) {
BotFindSparseDefendArea( bs, target_goal, qtrue );
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_DefendTarget ) {
BFG_RETURN_ALREADY;
}
BFG_RETURN_SUCCESS;
}
}
bs->leader = -1;
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_CONSTRUCT:
if ( !BotGetReachableEntityArea( bs, BFG_GOAL_ENTITYNUM, target_goal ) ) {
BFG_RETURN_FAILURE_AND_IGNORE; // not reachable
}
if ( bs->target_goal.entitynum == BFG_GOAL_ENTITYNUM && bs->ainode == AINode_MP_ConstructibleTarget ) {
BFG_RETURN_ALREADY; // already going for it
}
// Gordon: if this is a new goal, check pliers are half-full before heading off (optimist ;))
if ( bs->cur_ps.classWeaponTime > ( level.time - ( level.engineerChargeTime[bs->sess.sessionTeam - 1] * 0.5f ) ) ) {
BFG_RETURN_FAILURE_AND_IGNORE;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( !t ) {
BFG_RETURN_FAILURE_AND_IGNORE; // failed for whatever reason
}
BFG_RETURN_SUCCESS;
case BFG_TRIGGER:
if ( !BotGetReachableEntityArea( bs, BFG_GOAL_ENTITYNUM, target_goal ) ) {
BFG_RETURN_FAILURE_AND_IGNORE; // not reachable
}
if ( !BotGoalWithinMovementAutonomy( bs, target_goal, BGU_LOW ) ) {
BFG_RETURN_FAILURE_AND_IGNORE; // outside autonomy range
}
// if the current destination is not touching the brush, abort
VectorAdd( target_goal->origin, bs->cur_ps.mins, mins );
VectorAdd( target_goal->origin, bs->cur_ps.maxs, maxs );
if ( !trap_EntityContactCapsule( mins, maxs, bg->ent ) ) {
BFG_RETURN_FAILURE_AND_IGNORE;
}
// get the travel time
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( !t ) {
BFG_RETURN_FAILURE_AND_IGNORE;
}
// is it for us only?
if ( ( ( bg->ent->spawnflags & 1 ) && ( bs->sess.sessionTeam == TEAM_AXIS ) ) || ( ( bg->ent->spawnflags & 2 ) && ( bs->sess.sessionTeam == TEAM_ALLIES ) ) ) {
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_TouchTarget ) {
BFG_RETURN_ALREADY;
}
BFG_RETURN_SUCCESS;
} else { // defend it
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_DefendTarget ) {
BFG_RETURN_ALREADY;
}
BFG_RETURN_SUCCESS;
}
case BFG_DESTRUCTION_EXPLOSIVE:
// calc center
VectorAdd( bg->ent->r.absmin, bg->ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( bg->ent->r.absmin, bg->ent->r.absmax, list, 32 );
if ( numList ) {
int oldestTime = -1;
int bestDist = -1;
int oldest = 0;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 && t < 400 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
if ( oldestTime > 0 ) {
// now trace from this area towards the func_explosive
trap_AAS_AreaCenter( oldest, center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, oldest, bs->tfl );
if ( t ) {
bg->ent->lastHintCheckTime = level.time + 5000;
// use this as the goal origin
VectorCopy( center, target_goal->origin );
VectorCopy( bs->cur_ps.mins, target_goal->mins );
VectorCopy( bs->cur_ps.maxs, target_goal->maxs );
target_goal->areanum = oldest;
target_goal->entitynum = BFG_GOAL_ENTITYNUM;
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_DynamiteTarget ) {
BFG_RETURN_ALREADY;
}
BFG_RETURN_SUCCESS;
}
}
}
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_DESTRUCTION_BUILDING:
case BFG_DESTRUCTION_SATCHEL:
if ( bg->type == BFG_DESTRUCTION_BUILDING ) {
weap = WP_DYNAMITE;
} else {
weap = WP_SATCHEL;
}
VectorAdd( bg->ent->r.absmin, bg->ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
VectorCopy( bg->ent->r.absmin, mins );
VectorCopy( bg->ent->r.absmax, maxs );
for ( i = 0; i < 2; i++ ) {
mins[i] -= G_GetWeaponDamage( weap ) * 0.2f;
maxs[i] += G_GetWeaponDamage( weap ) * 0.2f;
}
// find the best goal area
numList = trap_AAS_BBoxAreas( mins, maxs, list, 32 );
if ( numList ) {
int oldestTime = -1;
int bestDist = -1;
int oldest = 0;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
// RF, make sure this is within range
trap_AAS_AreaCenter( list[i], center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
VectorSubtract( center, brushPos, vec );
G_AdjustedDamageVec( bg->ent, center, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( G_GetWeaponDamage( weap ) ) ) && CanDamage( bg->ent, center ) ) {
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t ) {
oldestTime = t;
oldest = list[i];
bestDist = t;
}
}
}
if ( oldestTime != -1 ) {
// now trace from this area towards the func_explosive
trap_AAS_AreaCenter( oldest, center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, oldest, bs->tfl );
if ( t ) {
bg->ent->lastHintCheckTime = level.time + 5000;
// use this as the goal origin
VectorCopy( center, target_goal->origin );
VectorCopy( bs->cur_ps.mins, target_goal->mins );
VectorCopy( bs->cur_ps.maxs, target_goal->maxs );
target_goal->areanum = oldest;
target_goal->entitynum = BFG_GOAL_ENTITYNUM;
if ( BotGoalWithinMovementAutonomy( bs, target_goal, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == target_goal->entitynum ) {
if ( bs->ainode == AINode_MP_DynamiteTarget ) {
BFG_RETURN_ALREADY;
}
// Gordon: FIXME: insert satchel node here
/* if( bs->ainode == AINode_MP_DynamiteTarget ) {
BFG_RETURN_ALREADY;
}*/
}
BFG_RETURN_SUCCESS;
}
BFG_RETURN_SUCCESS;
}
}
}
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_MG42_REPAIR:
if ( BotGoalForEntity( bs, BFG_GOAL_ENTITYNUM, target_goal, BGU_LOW ) ) {
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( t < 500 ) {
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_FixMG42 ) {
BFG_RETURN_ALREADY;
}
// go fix it!!
BFG_RETURN_SUCCESS;
}
}
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_MINE:
if ( !BotGoalForEntity( bs, BFG_GOAL_ENTITYNUM, target_goal, BGU_LOW ) ) {
BFG_RETURN_FAILURE_AND_IGNORE;
}
if ( bs->target_goal.entitynum == BFG_GOAL_ENTITYNUM && bs->ainode == AINode_MP_PlantMine ) {
BFG_RETURN_ALREADY;
}
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( !t ) {
BFG_RETURN_FAILURE_AND_IGNORE;
}
// find a spot inside the area to plant the mine
i = 0;
do {
VectorAdd( BotGetOrigin( BFG_GOAL_ENTITYNUM ), bg->ent->pos3, vec );
vec[0] += crandom() * bg->ent->r.absmax[0] * 0.25;
vec[1] += crandom() * bg->ent->r.absmax[1] * 0.25;
target_goal->areanum = BotPointAreaNum( bs->client, vec );
VectorCopy( vec, target_goal->origin );
} while ( ( ++i < 5 ) && ( ( target_goal->areanum < 0 ) || !PointInBounds( vec, bg->ent->r.absmin, bg->ent->r.absmax ) ) );
if ( i < 5 ) { // we found a valid spot
BFG_RETURN_SUCCESS;
}
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_ATTRACTOR:
if ( BotGoalForEntity( bs, BFG_GOAL_ENTITYNUM, target_goal, BGU_MAXIMUM ) ) {
// get the distance, halve it if there is noone one going for this attractor
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target_goal->areanum, bs->tfl );
if ( t ) {
BotFindSparseDefendArea( bs, target_goal, qtrue );
if ( bs->target_goal.entitynum == target_goal->entitynum && bs->ainode == AINode_MP_DefendTarget ) {
BFG_RETURN_ALREADY;
}
BFG_RETURN_SUCCESS;
}
}
BFG_RETURN_FAILURE_AND_IGNORE;
case BFG_SNIPERSPOT:
if ( bs->target_goal.entitynum == BFG_GOAL_ENTITYNUM && bs->ainode == AINode_MP_SniperSpot ) {
BFG_RETURN_ALREADY;
}
target_goal->entitynum = BFG_GOAL_ENTITYNUM;
VectorCopy( bg->ent->s.origin, target_goal->origin );
target_goal->areanum = BotPointAreaNum( -1, target_goal->origin );
VectorCopy( bs->cur_ps.mins, target_goal->mins );
VectorCopy( bs->cur_ps.maxs, target_goal->maxs );
BFG_RETURN_SUCCESS;
case BFG_SCANFORMINES:
if ( bs->target_goal.entitynum == BFG_GOAL_ENTITYNUM && bs->ainode == AINode_MP_ScanForLandmines ) {
BFG_RETURN_ALREADY;
}
target_goal->entitynum = BFG_GOAL_ENTITYNUM;
VectorCopy( bg->ent->s.origin, target_goal->origin );
target_goal->areanum = BotPointAreaNum( -1, target_goal->origin );
VectorCopy( bs->cur_ps.mins, target_goal->mins );
VectorCopy( bs->cur_ps.maxs, target_goal->maxs );
BFG_RETURN_SUCCESS;
case BFG_MG42:
if ( ( bs->target_goal.entitynum == BFG_GOAL_ENTITYNUM ) && ( bs->ainode == AINode_MP_MG42Mount || bs->ainode == AINode_MP_MG42Scan ) ) {
BFG_RETURN_ALREADY;
}
target_goal->entitynum = BFG_GOAL_ENTITYNUM;
VectorCopy( bg->ent->s.origin, target_goal->origin );
target_goal->areanum = BotPointAreaNum( -1, target_goal->origin );
VectorCopy( bs->cur_ps.mins, target_goal->mins );
VectorCopy( bs->cur_ps.maxs, target_goal->maxs );
BFG_RETURN_SUCCESS;
default:
break;
}
BFG_RETURN_FAILURE;
}
void BotMP_FindGoal_PostProcessGoal( bot_state_t* bs, botgoalFind_t* bg, bot_goal_t* goal ) {
bs->target_goal = *goal;
switch ( bg->type ) {
case BFG_FOLLOW_LEADER:
bs->leader = goal->entitynum;
AIEnter_MP_DefendTarget( bs );
break;
case BFG_CONSTRUCT:
AIEnter_MP_ConstructibleTarget( bs );
break;
case BFG_TRIGGER:
if ( ( ( bg->ent->spawnflags & 1 ) && ( bs->sess.sessionTeam == TEAM_AXIS ) ) || ( ( bg->ent->spawnflags & 2 ) && ( bs->sess.sessionTeam == TEAM_ALLIES ) ) ) {
AIEnter_MP_TouchTarget( bs );
} else {
AIEnter_MP_DefendTarget( bs );
}
break;
case BFG_DESTRUCTION_EXPLOSIVE:
case BFG_DESTRUCTION_BUILDING:
AIEnter_MP_DynamiteTarget( bs );
break;
case BFG_DESTRUCTION_SATCHEL:
AIEnter_MP_SatchelChargeTarget( bs );
break;
case BFG_MG42_REPAIR:
AIEnter_MP_FixMG42( bs );
break;
case BFG_MINE:
AIEnter_MP_PlantMine( bs );
break;
case BFG_ATTRACTOR:
AIEnter_MP_DefendTarget( bs );
break;
case BFG_SNIPERSPOT:
AIEnter_MP_SniperSpot( bs );
break;
case BFG_SCANFORMINES:
AIEnter_MP_ScanForLandmines( bs );
break;
case BFG_MG42:
AIEnter_MP_MG42Mount( bs );
break;
default:
break;
}
}
qboolean BotMP_AlreadyDoing_FastOut( bot_state_t *bs, botgoalFind_t *bg ) {
// return qtrue if the given goal, which we are already doing, is ok to continue doing, without looking for other goals
switch ( bg->type ) {
case BFG_SNIPERSPOT:
case BFG_MG42:
case BFG_SCANFORMINES:
return qtrue;
case BFG_FOLLOW_LEADER:
case BFG_CONSTRUCT:
case BFG_TRIGGER:
case BFG_DESTRUCTION_EXPLOSIVE:
case BFG_DESTRUCTION_BUILDING:
case BFG_DESTRUCTION_SATCHEL:
case BFG_MG42_REPAIR:
case BFG_MINE:
case BFG_ATTRACTOR:
default:
return qfalse;
}
}
qboolean BotMP_FindGoal_New( bot_state_t* bs ) {
int numGoals, i, numCloser;
botgoalFind_t findGoals[MAX_FIND_BOTGOALS];
botMPpg_t res, bestGoal_result = 0;
bot_goal_t goal, bestGoal;
int bestGoal_numCloser, bestGoal_InList;
#ifdef _DEBUG
// int t = trap_Milliseconds();
#endif // _DEBUG
if ( bs->last_findspecialgoals > level.time - 1600 ) {
return qfalse;
}
bs->last_findspecialgoals = level.time + rand() % 400;
if ( bs->ignore_specialgoal_time > level.time ) {
return qfalse;
}
numGoals = BotMP_FindGoal_BuildGoalList( bs, findGoals, MAX_FIND_BOTGOALS );
if ( !numGoals ) {
// Gordon: didnt find any goals :(
#ifdef _DEBUG
/* if(bot_profile.integer == 1) {
t = trap_Milliseconds() - t;
G_Printf( "Findgoal: %s: %i\n", BG_ClassnameForNumber( bs->sess.playerType ), t );
}*/
#endif // _DEBUG
return qfalse;
}
for ( i = 0; i < numGoals; i++ ) {
if ( findGoals[i].defaultPriority == -1 ) {
findGoals[i].ignore = qtrue;
} else {
findGoals[i].ignore = qfalse;
}
}
qsort( findGoals, numGoals, sizeof( botgoalFind_t ), botmp_sortFuncs[bs->cur_ps.stats[STAT_PLAYER_CLASS]] );
bestGoal_numCloser = -1;
bestGoal_InList = -1;
for ( i = 0; i < numGoals; i++ ) {
if ( findGoals[i].ignore ) {
continue;
}
#ifdef _DEBUG
/* if(bot_profile.integer == 1) {
G_Printf( "Processing goaltype %i\n", findGoals[i].type );
}*/
#endif // _DEBUG
res = BotMP_FindGoal_ProcessGoal( bs, &findGoals[i], &goal );
switch ( res ) {
case BPG_FAILED:
continue;
case BPG_ALREADYDOING: // we should check for range also if we are already doing it, otherwise they may all get stuck at the same goal
if ( BotMP_AlreadyDoing_FastOut( bs, &findGoals[i] ) ) {
#ifdef _DEBUG
/* if(bot_profile.integer == 1) {
t = trap_Milliseconds() - t;
G_Printf( "Findgoal: %s: %i\n", BG_ClassnameForNumber( bs->sess.playerType ), t );
}*/
#endif // _DEBUG
return qfalse;
}
// else, drop down so we do a numCloser check
case BPG_NEWGOAL:
// Possible optimization here: record the travel time within each bot's AI node, so we can use that time inside BotNumTeamMatesWithTargetAndCloser() rather than calculating the time for all bots
findGoals[i].numPlayers = numCloser = BotNumTeamMatesWithTargetAndCloser( bs, goal.entitynum, goal.areanum, NULL, 0, BotMP_FindGoal_ClassForGoalType( findGoals[i].type ) );
if ( bestGoal_numCloser >= 0 ) {
// First, check against maxCloser lookup
if ( numCloser > botgoalMaxCloser[findGoals[i].type] ) {
continue;
}
// TODO: scale it down according to priority (?)
if ( numCloser > bestGoal_numCloser ) {
continue;
}
// if this has the same numCloser, but a lower priority, then skip it
if ( numCloser == bestGoal_numCloser ) {
// if the default priority is lower, ignore
if ( findGoals[i].defaultPriority < findGoals[bestGoal_InList].defaultPriority ) {
continue;
}
// if the default priority is the same, check script
if ( ( findGoals[i].defaultPriority == findGoals[bestGoal_InList].defaultPriority ) &&
( findGoals[i].priority <= findGoals[bestGoal_InList].priority ) ) {
continue;
}
}
}
bestGoal = goal;
bestGoal_result = res;
bestGoal_numCloser = numCloser;
bestGoal_InList = i;
break;
}
}
#ifdef _DEBUG
/* if(bot_profile.integer == 1) {
t = trap_Milliseconds() - t;
G_Printf( "Findgoal: %s: %i\n", BG_ClassnameForNumber( bs->sess.playerType ), t );
}*/
#endif // _DEBUG
if ( bestGoal_numCloser >= 0 ) {
switch ( bestGoal_result ) {
case BPG_NEWGOAL:
BotMP_FindGoal_PostProcessGoal( bs, &findGoals[bestGoal_InList], &bestGoal );
return qtrue;
case BPG_ALREADYDOING:
return qfalse;
default:
break;
}
}
return qfalse;
}
/*
===================
BotMP_FindGoal
===================
*/
qboolean BotMP_FindGoal( bot_state_t *bs ) {
int i, k, t = 0, c;
int oldest = -1, oldestTime = -1, closestTime = -1;
int tlist[10], numTargets;
int teamlist[64], numTeammates;
float dist, bestDist = -1, targetTime = 0, f = 0.0;
gentity_t *ent, *trav;
trace_t tr;
vec3_t center, end, brushPos, vec;
qboolean gotTarget, defendTarget = qfalse;
bot_goal_t bestTarget, secondary;
aas_altroutegoal_t altroutegoals[40];
vec3_t mins, maxs;
int list[MAX_CLIENTS], numList;
float ourDist, *distances;
int targetPriority = 0, bestPriority = 0, bestGoalPriority = 0;
if ( bs->last_findspecialgoals > level.time - 1600 ) {
return qfalse;
}
bs->last_findspecialgoals = level.time + rand() % 400;
if ( bs->ignore_specialgoal_time > level.time ) {
return qfalse;
}
BotClearGoal( &target );
BotClearGoal( &secondary );
numTeammates = BotNumTeamMates( bs, teamlist, 64 );
gotTarget = qfalse;
// LEADER
//
// look for a (better?) leader
bs->leader = BotGetLeader( bs, ( bs->sess.playerType == PC_SOLDIER ) );
if ( bs->leader > -1 ) {
// we have a leader, follow them
if ( BotGoalForEntity( bs, bs->leader, &secondary, BGU_LOW ) || !( g_entities[bs->leader].r.svFlags & SVF_BOT ) ) {
secondary.flags = GFL_LEADER;
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, secondary.areanum, bs->tfl );
if ( !t || t > MAX_BOTLEADER_TRAVEL ) {
bs->leader = -1;
} else {
targetTime = t;
gotTarget = qtrue;
defendTarget = qtrue;
bestTarget = secondary;
}
} else {
bs->leader = -1;
}
}
// IMPORTANT CONSTRUCTIBLES
if ( bs->sess.playerType == PC_ENGINEER ) {
int pass;
//
for ( pass = 0; pass < 2; pass++ ) {
// find a constructible
trav = NULL;
ent = NULL;
closestTime = 0;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
// find the constructible
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
// is it not of highest priority?
if ( trav->goalPriority[bs->sess.sessionTeam - 1] < 9 ) {
continue;
}
ent = trav->target_ent;
if ( !ent ) {
continue;
}
if ( bs->sess.sessionTeam == TEAM_ALLIES ) {
if ( ent->spawnflags & AXIS_CONSTRUCTIBLE ) {
if ( ent->chain ) {
ent = ent->chain;
} else {
continue;
}
}
} else if ( bs->sess.sessionTeam == TEAM_AXIS ) {
if ( ent->spawnflags & ALLIED_CONSTRUCTIBLE ) {
if ( ent->chain ) {
ent = ent->chain;
} else {
continue;
}
}
}
if ( ent->s.eType != ET_CONSTRUCTIBLE ) {
continue;
}
//
if ( !BotIsConstructible( bs->sess.sessionTeam, trav->s.number ) ) {
continue;
}
if ( ( pass == 0 ) && BotNumTeamMatesWithTargetByClass( bs, trav->s.number, NULL, 0, PC_ENGINEER ) ) {
continue; // someone else is going for it
}
if ( !BotGetReachableEntityArea( bs, trav->s.number, &target ) ) {
continue; // not reachable
}
//
// we can build it
//
// if we are already heading for this goal, stay with it, otherwise look only for closest targets
//
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_ConstructibleTarget ) {
return qfalse;
}
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( closestTime && t > closestTime ) {
continue;
}
//
// if our weapon wont be ready in time
//if (bs->cur_ps.classWeaponTime + t*10 < (level.time - level.lieutenantChargeTime[bs->sess.sessionTeam-1])) continue;
//
bestTarget = target;
closestTime = t;
}
//
if ( closestTime ) {
bs->target_goal = bestTarget;
AIEnter_MP_ConstructibleTarget( bs );
return qtrue;
}
}
}
// IMPORTANT TRIGGER MULTIPLE
{
// find a trigger_multiple
trav = NULL;
ent = NULL;
closestTime = 0;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_TRIGGER_MULTIPLE ) ) ) {
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
// is it active?
if ( !trav->r.linked || ( trav->nextthink > level.time + 1000 ) ) {
continue; // if it's thinking, then it's not active (doesnt respond to touch)
}
// is it not of highest priority?
if ( trav->goalPriority[bs->sess.sessionTeam - 1] < 9 ) {
continue;
}
//
if ( !BotGetReachableEntityArea( bs, trav->s.number, &target ) ) {
continue; // not reachable
}
if ( !BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
continue; // outside autonomy range
}
// if the current destination is not touching the brush, abort
VectorAdd( target.origin, bs->cur_ps.mins, mins );
VectorAdd( target.origin, bs->cur_ps.maxs, maxs );
if ( !trap_EntityContactCapsule( mins, maxs, trav ) ) {
continue;
}
//
// get the travel time
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( !t ) {
continue;
}
//
targetPriority = 2;
//
// if there are too many going for this goal, then ignore it if we are the furthest away
if ( ( numList = BotNumTeamMatesWithTargetAndCloser( bs, trav->s.number, target.areanum, list, MAX_CLIENTS, -1 ) ) > (int)floor( 0.3 * numTeammates ) ) {
distances = BotSortPlayersByTraveltime( BotGetArea( trav->s.number ), list, numList );
if ( distances[numList - 1] < (float)t ) {
// we are the furthest
targetPriority = 1;
}
}
// only go for it if our current best goal is of lesser priority
if ( targetPriority < bestPriority ) {
continue;
}
//
// we should touch it
//
if ( ( targetPriority <= bestPriority ) && ( closestTime && t > closestTime ) ) {
continue;
}
//
bestTarget = target;
closestTime = t;
bestPriority = targetPriority;
}
//
if ( closestTime ) {
trav = &g_entities[bestTarget.entitynum];
// is it for us only?
if ( ( ( trav->spawnflags & 1 ) && ( bs->sess.sessionTeam == TEAM_AXIS ) ) ||
( ( trav->spawnflags & 2 ) && ( bs->sess.sessionTeam == TEAM_ALLIES ) ) ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_TouchTarget( bs );
return qtrue;
} else { // defend it
if ( targetPriority > 1 ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_DefendTarget( bs );
return qtrue;
} else {
targetTime = closestTime;
gotTarget = -1;
defendTarget = qtrue;
}
}
}
}
// CONSTRUCTIBLES
if ( bs->sess.playerType == PC_ENGINEER ) {
int pass;
//
for ( pass = 0; pass < 2; pass++ ) {
// find a constructible
trav = NULL;
ent = NULL;
closestTime = 0;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
// find the constructible
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
ent = trav->target_ent;
if ( !ent ) {
continue;
}
if ( bs->sess.sessionTeam == TEAM_ALLIES ) {
if ( ent->spawnflags & AXIS_CONSTRUCTIBLE ) {
if ( ent->chain ) {
ent = ent->chain;
} else {
continue;
}
}
} else if ( bs->sess.sessionTeam == TEAM_AXIS ) {
if ( ent->spawnflags & ALLIED_CONSTRUCTIBLE ) {
if ( ent->chain ) {
ent = ent->chain;
} else {
continue;
}
}
}
if ( ent->s.eType != ET_CONSTRUCTIBLE ) {
continue;
}
//
if ( !BotIsConstructible( bs->sess.sessionTeam, trav->s.number ) ) {
continue;
}
if ( BotNumTeamMatesWithTargetByClass( bs, trav->s.number, NULL, 0, PC_ENGINEER ) > pass ) {
continue; // someone else is going for it
}
if ( !BotGetReachableEntityArea( bs, trav->s.number, &target ) ) {
continue; // not reachable
}
if ( bestGoalPriority > trav->goalPriority[bs->sess.sessionTeam - 1] ) {
continue;
}
//
// we can build it
//
// if we are already heading for this goal, stay with it, otherwise look only for closest targets
//
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_ConstructibleTarget ) {
return qfalse;
}
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( bestGoalPriority == trav->goalPriority[bs->sess.sessionTeam - 1] ) {
// only check the distance if the priorities are the same
if ( closestTime && t > closestTime ) {
continue;
}
}
//
// if our weapon wont be ready in time
//if (bs->cur_ps.classWeaponTime + t*10 < (level.time - level.lieutenantChargeTime[bs->sess.sessionTeam-1])) continue;
//
bestTarget = target;
closestTime = t;
bestGoalPriority = trav->goalPriority[bs->sess.sessionTeam - 1];
}
//
if ( closestTime ) {
bs->target_goal = bestTarget;
AIEnter_MP_ConstructibleTarget( bs );
return qtrue;
}
}
}
// check for dynamite
if ( bs->sess.playerType == PC_ENGINEER ) {
// look for things to blow up
numTargets = BotGetTargetExplosives( bs->sess.sessionTeam, tlist, 10, qfalse );
if ( numTargets ) {
closestTime = -1;
// pick the closest target
for ( k = 0; k < numTargets; k++ ) {
//
oldest = tlist[k];
//
// see if this is a worthy goal
ent = BotGetEntity( oldest );
// is it disabled?
// Gordon: forest script was setup to use the entity itself, NOT the toi (toi for building, entity itself for destruction)
// if (ent->aiInactive & (1<sess.sessionTeam)) continue;
// if (ent->parent && (ent->parent->aiInactive & (1<sess.sessionTeam))) continue;
// is the target disabled?
// if (ent->target_ent && (ent->target_ent->aiInactive & (1<sess.sessionTeam))) continue;
//
if ( ( numList = BotNumTeamMatesWithTarget( bs, oldest, list, 10 ) ) ) {
continue;
}
//
if ( ent->s.eType == ET_OID_TRIGGER ) {
VectorAdd( ent->r.absmin, ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( ent->r.absmin, ent->r.absmax, list, 32 );
if ( numList ) {
oldestTime = -1;
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 && ( !gotTarget || t < 400 ) ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
if ( oldestTime > 0 ) {
// now trace from this area towards the func_explosive
trap_AAS_AreaCenter( oldest, center );
//trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, brushPos, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
//VectorCopy( tr.endpos, center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
//center[2] += 24;
//oldest = trap_AAS_PointAreaNum(center);
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, oldest, bs->tfl );
if ( t && ( closestTime < 0 || closestTime > t ) ) {
ent->lastHintCheckTime = level.time + 5000;
// use this as the goal origin
VectorCopy( center, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = ent->s.number;
//
closestTime = t;
}
}
}
} else {
VectorAdd( ent->r.absmin, ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
VectorCopy( ent->r.absmin, mins );
VectorCopy( ent->r.absmax, maxs );
for ( i = 0; i < 2; i++ ) {
mins[i] -= G_GetWeaponDamage( WP_DYNAMITE ) * 0.25f;
maxs[i] += G_GetWeaponDamage( WP_DYNAMITE ) * 0.25f;
}
// find the best goal area
numList = trap_AAS_BBoxAreas( mins, maxs, list, 32 );
if ( numList ) {
oldestTime = -1;
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
// RF, make sure this is within range
trap_AAS_AreaCenter( list[i], center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
VectorSubtract( center, brushPos, vec );
G_AdjustedDamageVec( ent, center, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( G_GetWeaponDamage( WP_DYNAMITE ) ) ) && CanDamage( ent, center ) ) {
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 && ( !gotTarget || t < 400 ) ) {
if ( bestDist < 0 || t < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = t;
}
}
}
}
if ( oldestTime > 0 ) {
// now trace from this area towards the func_explosive
trap_AAS_AreaCenter( oldest, center );
//trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, brushPos, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
//VectorCopy( tr.endpos, center );
VectorCopy( center, end );
end[2] -= 512;
trap_Trace( &tr, center, bs->cur_ps.mins, bs->cur_ps.maxs, end, -1, MASK_PLAYERSOLID & ~CONTENTS_BODY );
VectorCopy( tr.endpos, center );
//center[2] += 24;
//oldest = trap_AAS_PointAreaNum(center);
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, oldest, bs->tfl );
if ( t && ( closestTime < 0 || closestTime > t ) ) {
ent->lastHintCheckTime = level.time + 5000;
// use this as the goal origin
VectorCopy( center, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = ent->s.number;
//
closestTime = t;
}
}
}
}
}
// if we have a closestTime, we have a target!
if ( closestTime > 0 ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DynamiteTarget ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_DynamiteTarget( bs );
return qtrue;
}
}
}
}
// BROKEN MG42
if ( bs->sess.playerType == PC_ENGINEER ) {
// look for a broken MG42 that isnt already being fixed
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_MG42 ) ) ) {
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
if ( trav->health <= 0 ) {
if ( trav->melee ) {
ent = trav->melee;
if ( ( ent->aiTeam == bs->sess.sessionTeam ) && ( ent->botIgnoreTime < level.time ) && ( BotNumTeamMatesWithTargetByClass( bs, trav->s.number, NULL, -1, PC_ENGINEER ) == 0 ) ) {
// go fix it?
if ( BotGoalForEntity( bs, ent->s.number, &target, BGU_LOW ) ) {
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
//
if ( t < 500 && ( !gotTarget || t < 100 ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_FixMG42 ) {
return qfalse;
}
// go fix it!!
bs->target_goal = target;
AIEnter_MP_FixMG42( bs );
return qtrue;
}
}
}
}
}
}
}
// PLANT LAND MINES
if ( bs->sess.playerType == PC_ENGINEER && ( G_CountTeamLandmines( bs->sess.sessionTeam ) < MAX_TEAM_LANDMINES ) ) {
// look for a land mine area that isn't full
closestTime = 0;
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_BOT_LANDMINE_AREA ) ) ) {
// is it disabled?
//if (trav->aiInactive & (1<sess.sessionTeam)) continue; // FIXME: djbob needs to add the etype of the landmine hint to the filter
// is it for us?
if ( ( trav->spawnflags & 1 ) && ( bs->sess.sessionTeam != TEAM_AXIS ) ) {
continue;
}
if ( ( trav->spawnflags & 2 ) && ( bs->sess.sessionTeam != TEAM_ALLIES ) ) {
continue;
}
// has it got enough landmines?
if ( trav->count2 >= trav->count ) {
continue;
}
// already someone else heading for it?
if ( BotNumTeamMatesWithTargetByClass( bs, trav->s.number, NULL, 0, PC_ENGINEER ) ) {
continue; // someone else is going for it
}
// valid goal towards it?
if ( !BotGoalForEntity( bs, trav->s.number, &target, BGU_LOW ) ) {
continue;
}
//
// if we are already heading for this goal, stay with it, otherwise look only for closest targets
//
if ( bs->target_goal.entitynum == trav->s.number && bs->ainode == AINode_MP_PlantMine ) {
return qfalse;
}
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( !t ) {
continue;
}
if ( closestTime && t > closestTime ) {
continue;
}
//
bestTarget = target;
closestTime = t;
}
//
if ( closestTime ) {
// find a spot inside the area to plant the mine
trav = &g_entities[bestTarget.entitynum];
i = 0;
do {
VectorAdd( BotGetOrigin( trav->s.number ), trav->pos3, vec );
vec[0] += crandom() * trav->r.absmax[0] * 0.25;
vec[1] += crandom() * trav->r.absmax[1] * 0.25;
//
bestTarget.areanum = BotPointAreaNum( bs->client, vec );
VectorCopy( vec, bestTarget.origin );
} while ( ( ++i < 5 ) && ( ( bestTarget.areanum < 0 ) || !PointInBounds( vec, trav->r.absmin, trav->r.absmax ) ) );
//
if ( i < 5 ) { // we found a valid spot
bs->target_goal = bestTarget;
AIEnter_MP_PlantMine( bs );
return qtrue;
}
}
}
// TRIGGER MULTIPLE
{
// find a trigger_multiple
trav = NULL;
ent = NULL;
closestTime = 0;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_TRIGGER_MULTIPLE ) ) ) {
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
// is it active?
if ( !trav->r.linked || ( trav->nextthink > level.time + 1000 ) ) {
continue; // if it's thinking, then it's not active (doesnt respond to touch)
}
//
if ( !BotGetReachableEntityArea( bs, trav->s.number, &target ) ) {
continue; // not reachable
}
if ( !BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
continue; // outside autonomy range
}
// if the current destination is not touching the brush, abort
VectorAdd( target.origin, bs->cur_ps.mins, mins );
VectorAdd( target.origin, bs->cur_ps.maxs, maxs );
if ( !trap_EntityContactCapsule( mins, maxs, trav ) ) {
continue;
}
//
// get the travel time
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( !t ) {
continue;
}
//
targetPriority = 2;
//
// if there are too many going for this goal, then ignore it if we are the furthest away
if ( ( numList = BotNumTeamMatesWithTargetAndCloser( bs, trav->s.number, target.areanum, list, MAX_CLIENTS, -1 ) ) > (int)floor( 0.3 * numTeammates ) ) {
distances = BotSortPlayersByTraveltime( BotGetArea( trav->s.number ), list, numList );
if ( distances[numList - 1] < (float)t ) {
// we are the furthest
targetPriority = 1;
// also consider other goals
t += 5000;
}
}
// only go for it if our current best goal is of lesser priority
if ( targetPriority < bestPriority ) {
continue;
}
//
// we should touch it
//
if ( ( targetPriority <= bestPriority ) && ( closestTime && t > closestTime ) ) {
continue;
}
//
bestTarget = target;
closestTime = t;
bestPriority = targetPriority;
}
//
if ( closestTime ) {
trav = &g_entities[bestTarget.entitynum];
// is it for us only?
if ( ( ( trav->spawnflags & 1 ) && ( bs->sess.sessionTeam == TEAM_AXIS ) ) ||
( ( trav->spawnflags & 2 ) && ( bs->sess.sessionTeam == TEAM_ALLIES ) ) ) {
if ( targetPriority > 1 ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_TouchTarget( bs );
return qtrue;
} else { // enough people going for it, so only go for it if there's nothing else to do
targetTime = closestTime;
gotTarget = -1;
defendTarget = qfalse;
}
} else { // defend it
if ( targetPriority > 1 ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_DefendTarget( bs );
return qtrue;
} else {
targetTime = closestTime;
gotTarget = -1;
defendTarget = qtrue;
}
}
}
}
//================================================================================================
// SECONDARY TARGETS()
// LEADER
if ( !gotTarget ) {
if ( bs->leader > -1 ) {
if ( bs->lead_time > trap_AAS_Time() ) {
// we must follow them
goto secondaryTarget;
}
}
}
// CHECKPOINTS
if ( !gotTarget ) {
// Defend checkpoints if they are owned by us. Make sure we disperse troops amongst checkpoints, and actively
// pursue checkpoints not owned by us more aggressively.
t = 0;
gotTarget = qfalse;
// count the checkpoints
i = 0;
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_CHECKPOINT ) ) ) {
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
i++;
}
if ( i ) {
f = 1.0f / i;
}
//if (level.captureFlagMode) {
// c = (int)ceil(0.35*f*numTeammates);
//} else {
c = (int)ceil( f * numTeammates );
//}
// see if we should get/defend one
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_CHECKPOINT ) ) ) {
// is it disabled?
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
// if the opposition team controls this checkpoint, or it hasnt been captured yet
if ( trav->count == ( bs->sess.sessionTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS ) || ( trav->count < 0 ) ) {
if ( BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) < c ) {
// GO GET IT
// do we have a route to the enemy flag?
secondary.entitynum = trav->s.number;
VectorAdd( trav->r.absmin, trav->r.absmax, center );
VectorScale( center, 0.5, center );
secondary.areanum = BotReachableBBoxAreaNum( bs, trav->r.absmin, trav->r.absmax );
if ( !secondary.areanum ) {
secondary.areanum = BotPointAreaNum( -1, center );
}
//secondary.areanum = trap_AAS_PointAreaNum(center);
secondary.flags = GFL_NOSLOWAPPROACH;
VectorCopy( trav->r.mins, secondary.mins );
VectorCopy( trav->r.maxs, secondary.maxs );
VectorCopy( center, secondary.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, secondary.areanum, bs->tfl );
if ( t && ( !gotTarget || t < targetTime ) ) {
targetTime = t;
gotTarget = -1; // use this if nothing else is around
defendTarget = qfalse;
bestTarget = secondary;
if ( t < 500 && BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) < 1 ) {
if ( BotGoalWithinMovementAutonomy( bs, &bestTarget, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
}
}
}
} else {
// defend the checkpoint?
if ( !level.captureFlagMode && BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) < ( c > 1 ? c / 2 : c ) ) {
// GO GET IT
// do we have a route to the enemy flag?
secondary.entitynum = trav->s.number;
VectorAdd( trav->r.absmin, trav->r.absmax, center );
VectorScale( center, 0.5, center );
//VectorCopy( trav->r.currentOrigin, center );
//center[2] += 30;
secondary.areanum = trap_AAS_PointAreaNum( center );
if ( !secondary.areanum ) {
secondary.areanum = BotPointAreaNum( -1, center );
}
secondary.flags = GFL_NOSLOWAPPROACH;
VectorCopy( trav->r.mins, secondary.mins );
VectorCopy( trav->r.maxs, secondary.maxs );
VectorCopy( trav->r.currentOrigin, secondary.origin );
//
// don't worry about our flag as much
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, secondary.areanum, bs->tfl );
if ( !t ) {
t = BotFindSparseDefendArea( bs, &secondary, qfalse );
}
if ( t ) {
t = 200 + 2 * t;
t += bs->client * 100; // random element to spread bots out
if ( !gotTarget || t < targetTime ) {
if ( BotGoalWithinMovementAutonomy( bs, &secondary, BGU_LOW ) ) {
targetTime = t;
gotTarget = -1;
defendTarget = qtrue;
bestTarget = secondary;
}
}
}
}
}
}
}
// ATTRACTORS
if ( !gotTarget ) {
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_BOT_ATTRACTOR ) ) ) {
if ( trav->aiInactive & ( 1 << bs->sess.sessionTeam ) ) {
continue;
}
if ( BotGoalForEntity( bs, trav->s.number, &target, BGU_MAXIMUM ) ) {
// get the distance, halve it if there is noone one going for this attractor
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) == 0 ) {
t /= 2;
}
if ( !gotTarget || ( t < targetTime ) ) {
targetTime = t;
gotTarget = -1;
defendTarget = qtrue;
bestTarget = target;
}
}
}
}
// OBJECTIVE LOCATIONS
// defend target objectives (both teams should try and "occupy" objectives)
/* if (gotTarget != qtrue) {
qboolean useObjective = qfalse;
// count the objectives
i = 0;
c = 999;
oldest = -1;
trav = NULL;
while ( trav = BotFindNextStaticEntity(trav, BOTSTATICENTITY_OBJECTIVE_INFO) ) {
// is it disabled?
if (trav->aiInactive & (1<sess.sessionTeam)) continue;
//
// only guard objectives that are attached to constructibles or destructibles
//
useObjective = qfalse;
//
if (trav->target_ent) {
if (trav->target_ent->s.eType == ET_CONSTRUCTIBLE) {
// has it been built?
// all constructibles are considered important, whether built or not, unless they are disabled in scripting
//if (BotIsConstructible( bs->sess.sessionTeam, trav->s.number )) {
useObjective = qtrue;
//}
} else if (trav->target_ent->s.eType == ET_EXPLOSIVE) {
// is it still valid?
if (trav->target_ent->r.linked) {
useObjective = qtrue;
}
}
}
//
if (!useObjective) continue; // ignore this objective
//
i++;
if ((k = BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 )) < c) {
if (!BotGetReachableEntityArea( bs, trav->s.number, &secondary )) continue; // not reachable
c = k;
oldest = trav->s.number;
}
}
if (i) {
f = 1.0f / i;
}
trav = NULL;
while (trav = BotFindNextStaticEntity(trav, BOTSTATICENTITY_OBJECTIVE_INFO)) {
// is it disabled?
if (trav->aiInactive & (1<sess.sessionTeam)) continue;
//
if (BotNumTeamMatesWithTarget( bs, trav->s.number, NULL, 0 ) >= (int)ceil(f*numTeammates)) {
if (bestPriority >= 2) continue; // we already have an important goal
targetPriority = 1;
} else {
targetPriority = 2;
}
//
// only guard objectives that are attached to constructibles or destructibles
//
useObjective = qfalse;
//
if (trav->target_ent) {
if (trav->target_ent->s.eType == ET_CONSTRUCTIBLE) {
// has it been built?
//if (BotIsConstructible( bs->sess.sessionTeam, trav->s.number )) {
useObjective = qtrue;
//}
} else if (trav->target_ent->s.eType == ET_EXPLOSIVE) {
// is it still valid?
if (trav->target_ent->r.linked) {
useObjective = qtrue;
}
}
//
if (!useObjective) continue; // ignore this objective
//
// GO GET IT
// do we have a route to the objective?
if (!BotGetReachableEntityArea( bs, trav->s.number, &secondary )) continue; // not reachable
//
t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, secondary.areanum, bs->tfl);
if (t) {
if (!gotTarget || (targetPriority > bestPriority) || (oldest != -1 ? trav->s.number == oldest : t < targetTime)) {
if (BotGoalWithinMovementAutonomy( bs, &secondary, BGU_LOW )) {
targetTime = t;
gotTarget = qtrue;
defendTarget = qtrue;
bestTarget = secondary;
bestPriority = targetPriority;
}
}
}
}
}
}*/
//================================================================================================
// PRIMARY TARGETS()
// FLAGS
if ( level.captureFlagMode ) {
if ( bs->sess.sessionTeam == level.attackingTeam ) {
// ATTACKING
// do we have the flag?
if ( BotCarryingFlag( bs->client ) ) { // we have it!!
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY );
if ( !ent ) {
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE );
}
if ( ent ) {
vec3_t bestAreaCenter;
//
VectorAdd( ent->r.absmin, ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( ent->r.absmin, ent->r.absmax, list, 32 );
if ( numList ) {
oldestTime = -1;
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
VectorCopy( center, bestAreaCenter );
}
}
}
}
if ( bestDist > 0 ) {
if ( bs->target_goal.entitynum == ent->s.number && bs->ainode == AINode_MP_TouchTarget
&& bs->target_goal.areanum == oldest ) {
return qfalse;
}
//
BotClearGoal( &target );
// use this as the goal origin
VectorCopy( bestAreaCenter, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = ent->s.number;
target.flags = GFL_NOSLOWAPPROACH;
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
//
bs->target_goal = target;
//
// if we are close to the static flag, then look for an alternate route
BotFlagAtBase( level.attackingTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS, &ent );
if ( ent && VectorDistanceSquared( bs->origin, ent->r.currentOrigin ) < ( 384 * 384 ) ) {
// check for an alternate route
if ( ( t > 1000 ) && ( numList = trap_AAS_AlternativeRouteGoals( bs->origin, bestAreaCenter, bs->tfl, altroutegoals, 40, 0 ) ) ) {
//pick one at random
i = rand() % numList;
//make this the altrouetgoal
bs->alt_goal = bs->target_goal;
trap_AAS_AreaWaypoint( altroutegoals[i].areanum, bs->alt_goal.origin );
bs->alt_goal.areanum = trap_AAS_PointAreaNum( bs->alt_goal.origin );
bs->alt_goal.number = level.time;
bs->target_goal.number = level.time;
}
}
//
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
} else {
// can't get to the destination point
if ( bs->ainode == AINode_MP_Stand ) {
return qfalse;
}
AIEnter_MP_Stand( bs );
return qfalse;
}
}
}
// SNIPER AI
// note: snipers only operate if flag is at base
if ( BotCanSnipe( bs, qtrue ) ) {
// already sniping
if ( bs->ainode == AINode_MP_SniperSpot ) {
return qfalse;
}
//
c = BotBestSniperSpot( bs );
if ( c > -1 ) {
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_SniperSpot( bs );
return qtrue;
}
}
}
// MG42 AI
// note: mg42 only operate if flag is at base
if ( bs->sess.playerType == PC_SOLDIER ) {
//
c = BotBestMG42Spot( bs, !gotTarget );
if ( c > -1 ) {
// are we already mounting it?
if ( ( bs->target_goal.entitynum == c ) && ( bs->ainode == AINode_MP_MG42Mount || bs->ainode == AINode_MP_MG42Scan ) ) {
return qfalse;
}
//
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_MG42Mount( bs );
return qtrue;
}
}
}
// is the enemy flag at base?
if ( BotFlagAtBase( level.attackingTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS, &ent ) == qtrue ) {
// GO GET IT
// do we have a route to the enemy flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->r.currentOrigin, center );
center[2] += 30;
target.areanum = trap_AAS_PointAreaNum( center );
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( ent->r.mins, target.mins );
VectorCopy( ent->r.maxs, target.maxs );
VectorCopy( center, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( t && ( ( t < 500 ) || !gotTarget || defendTarget || ( ( t / 2 ) < targetTime ) ) ) {
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
//
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = target;
//
// check for an alternate route
if ( t > 600 && ( numList = trap_AAS_AlternativeRouteGoals( bs->origin, bs->target_goal.origin, bs->tfl, altroutegoals, 40, 0 ) ) ) {
//pick one at random
i = rand() % numList;
//make this the altrouetgoal
bs->alt_goal = bs->target_goal;
trap_AAS_AreaWaypoint( altroutegoals[i].areanum, bs->alt_goal.origin );
bs->alt_goal.areanum = trap_AAS_PointAreaNum( bs->alt_goal.origin );
bs->alt_goal.number = level.time;
bs->target_goal.number = level.time;
}
//
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
}
} else {
qboolean protect = qtrue;
// who has it?
BotClearGoal( &target );
target.entitynum = BotGetTeamFlagCarrier( bs );
//
if ( ( target.entitynum > -1 && target.entitynum != bs->client ) &&
( VectorDistanceSquared( g_entities[target.entitynum].r.currentOrigin, bs->origin ) < ( 1600 * 1600 ) ) /*&&
(trap_InPVS(g_entities[target.entitynum].r.currentOrigin, bs->origin))*/) {
// TODO: if there are too many defenders, and we are the furthest away, stop defending
// !!!
if ( ( numList = BotNumTeamMatesWithTarget( bs, target.entitynum, list, MAX_CLIENTS ) ) >= BOT_FLAG_CARRIER_DEFENDERS ) {
ourDist = VectorDistanceSquared( bs->origin, g_entities[target.entitynum].r.currentOrigin );
//if (!trap_InPVS( bs->origin, g_entities[target.entitynum].r.currentOrigin )) ourDist += 2048;
distances = BotSortPlayersByDistance( g_entities[target.entitynum].r.currentOrigin, list, numList );
if ( distances[numList - 1] < ourDist ) {
// we are the furthest
protect = qfalse;
}
}
if ( protect ) {
// protect the carrier
ent = BotGetEntity( target.entitynum );
VectorCopy( ent->r.currentOrigin, center );
target.areanum = BotPointAreaNum( target.entitynum, center );
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( ent->r.mins, target.mins );
VectorCopy( ent->r.maxs, target.maxs );
VectorCopy( ent->r.currentOrigin, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( !t ) {
t = BotFindSparseDefendArea( bs, &target, qfalse );
}
if ( t && ( !gotTarget || defendTarget || t < targetTime ) ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
BotFindSparseDefendArea( bs, &bs->target_goal, qtrue );
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
}
// head to the flag destination?
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY );
if ( !ent ) {
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE );
}
if ( target.entitynum != bs->client && ent && ( BotNumTeamMatesWithTarget( bs, ent->s.number, NULL, 0 ) < (int)ceil( 0.3 * numTeammates ) ) ) {
VectorAdd( ent->r.absmin, ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( ent->r.absmin, ent->r.absmax, list, 32 );
if ( numList ) {
oldestTime = -1;
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
}
if ( bestDist > 0 ) {
BotClearGoal( &target );
// use this as the goal origin
VectorCopy( center, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = ent->s.number;
target.flags = GFL_NOSLOWAPPROACH;
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == ent->s.number && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
BotFindSparseDefendArea( bs, &bs->target_goal, qtrue );
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
// HEAD FOR IT, THEN WAIT FOR IT
BotFlagAtBase( level.attackingTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS, &ent );
// do we have a route to the enemy flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->r.currentOrigin, center );
center[2] += 30;
target.areanum = trap_AAS_PointAreaNum( center );
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( ent->r.mins, target.mins );
VectorCopy( ent->r.maxs, target.maxs );
VectorCopy( ent->r.currentOrigin, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( !t ) {
t = BotFindSparseDefendArea( bs, &target, qfalse );
}
if ( t && ( !gotTarget || defendTarget || t < targetTime ) ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
BotFindSparseDefendArea( bs, &bs->target_goal, qtrue );
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
} else { // ================================================================
// DEFENDING()
// SNIPER AI
// note: snipers only operate if flag is at base
if ( BotCanSnipe( bs, qtrue ) ) {
// already sniping
if ( bs->ainode == AINode_MP_SniperSpot ) {
return qfalse;
}
c = BotBestSniperSpot( bs );
if ( c > -1 ) {
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_SniperSpot( bs );
return qtrue;
}
}
}
// MG42 AI
// note: mg42 only operate if flag is at base
if ( ( bs->sess.playerType == PC_SOLDIER ) && ( BotFlagAtBase( bs->sess.sessionTeam, &ent ) == qtrue ) ) {
// already sniping
if ( bs->ainode == AINode_MP_MG42Mount || bs->ainode == AINode_MP_MG42Scan ) {
return qfalse;
}
//
c = BotBestMG42Spot( bs, !gotTarget );
if ( c > -1 ) {
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_MG42Mount( bs );
return qtrue;
}
}
}
if ( !gotTarget || !level.explosiveTargets[bs->sess.sessionTeam == TEAM_AXIS ? 0 : 1] || ( level.explosiveTargets[bs->sess.sessionTeam == TEAM_AXIS ? 0 : 1] - BotGetTargetExplosives( bs->sess.sessionTeam, NULL, 0, qtrue ) > 0 ) ) {
// at least one target has been breached, so start defending objective
// defend the objective!
if ( BotFlagAtBase( bs->sess.sessionTeam, &ent ) == qtrue ) {
if ( !gotTarget || BotNumTeamMatesWithTarget( bs, ent->s.number, NULL, 0 ) < (int)ceil( 0.4 * numTeammates ) ) {
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->r.currentOrigin, center );
center[2] += 30;
target.areanum = trap_AAS_PointAreaNum( center );
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( ent->r.mins, target.mins );
VectorCopy( ent->r.maxs, target.maxs );
VectorCopy( ent->r.currentOrigin, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( t && ( !gotTarget || defendTarget || t < targetTime ) ) {
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
//
BotFindSparseDefendArea( bs, &target, qtrue );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
// we are on defense
BotVoiceChatAfterIdleTime( bs->client, "OnDefense", SAY_TEAM, 1000, qfalse, 10000 + rand() % 5000, qfalse );
bs->target_goal = target;
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
}
} else { // defend the enemy destination
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY );
if ( !ent ) {
ent = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE );
}
if ( ent /*&& (BotNumTeamMatesWithTarget( bs, ent->s.number, NULL, 0 ) < (int)ceil(0.5*numTeammates))*/ ) {
VectorAdd( ent->r.absmin, ent->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
bestDist = -1;
// find the best goal area
numList = trap_AAS_BBoxAreas( ent->r.absmin, ent->r.absmax, list, 32 );
if ( numList ) {
oldestTime = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, list[i], bs->tfl );
if ( t > 0 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
}
if ( bestDist > 0 ) {
BotClearGoal( &target );
// use this as the goal origin
VectorCopy( center, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
target.areanum = oldest;
target.entitynum = ent->s.number;
target.flags = GFL_NOSLOWAPPROACH;
//
BotFindSparseDefendArea( bs, &target, qtrue );
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
if ( bs->target_goal.entitynum == ent->s.number && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
//
// go to the flag room
BotFlagAtBase( bs->sess.sessionTeam, &ent );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->r.currentOrigin, center );
center[2] += 30;
target.areanum = trap_AAS_PointAreaNum( center );
target.flags = GFL_NOSLOWAPPROACH;
VectorCopy( ent->r.mins, target.mins );
VectorCopy( ent->r.maxs, target.maxs );
VectorCopy( ent->r.currentOrigin, target.origin );
//
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, target.areanum, bs->tfl );
if ( t && ( !gotTarget || defendTarget || t < targetTime ) ) {
BotFindSparseDefendArea( bs, &target, qtrue );
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_MEDIUM ) ) {
if ( bs->target_goal.entitynum == target.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = target;
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
}
}
}
}
} else { // NOT CTF MODE
// SNIPER AI
// note: snipers only operate if flag is at base
if ( BotCanSnipe( bs, qtrue ) ) {
// already sniping
if ( bs->ainode == AINode_MP_SniperSpot ) {
return qfalse;
}
//
c = BotBestSniperSpot( bs );
if ( c > -1 ) {
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_SniperSpot( bs );
return qtrue;
}
}
}
// MG42 AI
// note: mg42 only operate if flag is at base
if ( qtrue ) { //bs->sess.playerType == PC_SOLDIER) {
// already sniping
if ( bs->ainode == AINode_MP_MG42Mount || bs->ainode == AINode_MP_MG42Scan ) {
return qfalse;
}
//
c = BotBestMG42Spot( bs, !gotTarget );
if ( c > -1 ) {
ent = BotGetEntity( c );
// do we have a route to the flag?
BotClearGoal( &target );
target.entitynum = ent->s.number;
VectorCopy( ent->s.origin, target.origin );
target.areanum = BotPointAreaNum( -1, target.origin );
VectorCopy( bs->cur_ps.mins, target.mins );
VectorCopy( bs->cur_ps.maxs, target.maxs );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
bs->target_goal = target;
AIEnter_MP_MG42Mount( bs );
return qtrue;
}
}
}
}
//================================================================================================
secondaryTarget:
// if we found a secondary target (checkpoint), go for it
if ( gotTarget ) {
if ( defendTarget ) {
BotFindSparseDefendArea( bs, &bestTarget, qtrue );
if ( BotGoalWithinMovementAutonomy( bs, &bestTarget, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_DefendTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_DefendTarget( bs );
return qtrue;
}
} else {
if ( BotGoalWithinMovementAutonomy( bs, &bestTarget, BGU_LOW ) ) {
if ( bs->target_goal.entitynum == bestTarget.entitynum && bs->ainode == AINode_MP_TouchTarget ) {
return qfalse;
}
bs->target_goal = bestTarget;
AIEnter_MP_TouchTarget( bs );
return qtrue;
}
}
}
return qfalse;
}