codemp/game/NPC_AI_Utils.c

Go to the documentation of this file.
00001 // These utilities are meant for strictly non-player, non-team NPCs.  
00002 // These functions are in their own file because they are only intended
00003 // for use with NPCs who's logic has been overriden from the original
00004 // AI code, and who's code resides in files with the AI_ prefix.
00005 
00006 #include "b_local.h"
00007 #include "g_nav.h"
00008 
00009 #define MAX_RADIUS_ENTS         128
00010 #define DEFAULT_RADIUS          45
00011 
00012 extern vmCvar_t         d_noGroupAI;
00013 qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member );
00014 
00015 extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
00016 
00017 /*
00018 -------------------------
00019 AI_GetGroupSize
00020 -------------------------
00021 */
00022 
00023 int     AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid )
00024 {
00025         int                     radiusEnts[ MAX_RADIUS_ENTS ];
00026         gentity_t       *check;
00027         vec3_t          mins, maxs;
00028         int                     numEnts, realCount = 0;
00029         int                     i;
00030         int                     j;
00031 
00032         //Setup the bbox to search in
00033         for ( i = 0; i < 3; i++ )
00034         {
00035                 mins[i] = origin[i] - radius;
00036                 maxs[i] = origin[i] + radius;
00037         }
00038 
00039         //Get the number of entities in a given space
00040         numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
00041 
00042         //Cull this list
00043         for ( j = 0; j < numEnts; j++ )
00044         {
00045                 check = &g_entities[radiusEnts[j]];
00046 
00047                 //Validate clients
00048                 if ( check->client == NULL )
00049                         continue;
00050 
00051                 //Skip the requested avoid ent if present
00052                 if ( ( avoid != NULL ) && ( check == avoid ) )
00053                         continue;
00054 
00055                 //Must be on the same team
00056                 if ( check->client->playerTeam != playerTeam )
00057                         continue;
00058 
00059                 //Must be alive
00060                 if ( check->health <= 0 )
00061                         continue;
00062 
00063                 realCount++;
00064         }
00065 
00066         return realCount;
00067 }
00068 
00069 //Overload
00070 
00071 int AI_GetGroupSize2( gentity_t *ent, int radius )
00072 {
00073         if ( ( ent == NULL ) || ( ent->client == NULL ) )
00074                 return -1;
00075 
00076         return AI_GetGroupSize( ent->r.currentOrigin, radius, ent->client->playerTeam, ent );
00077 }
00078 
00079 extern int NAV_FindClosestWaypointForPoint( gentity_t *ent, vec3_t point );
00080 int AI_ClosestGroupEntityNumToPoint( AIGroupInfo_t *group, vec3_t point )
00081 {
00082         int     markerWP = WAYPOINT_NONE;
00083         int     cost, bestCost = Q3_INFINITE;
00084         int     closest = ENTITYNUM_NONE;
00085         int i;
00086         
00087         if ( group == NULL || group->numGroup <= 0 )
00088         {
00089                 return ENTITYNUM_NONE;
00090         }
00091 
00092         markerWP = NAV_FindClosestWaypointForPoint( &g_entities[group->member[0].number], point );
00093 
00094         if ( markerWP == WAYPOINT_NONE )
00095         {
00096                 return ENTITYNUM_NONE;
00097         }
00098 
00099         for ( i = 0; i < group->numGroup; i++ )
00100         {
00101                 cost = trap_Nav_GetPathCost( group->member[i].waypoint, markerWP );
00102                 if ( cost < bestCost )
00103                 {
00104                         bestCost = cost;
00105                         closest = group->member[i].number;
00106                 }
00107         }
00108 
00109         return closest;
00110 }
00111 
00112 void AI_SetClosestBuddy( AIGroupInfo_t *group )
00113 {
00114         int     i, j;
00115         int     dist, bestDist;
00116 
00117         for ( i = 0; i < group->numGroup; i++ )
00118         {
00119                 group->member[i].closestBuddy = ENTITYNUM_NONE;
00120 
00121                 bestDist = Q3_INFINITE;
00122                 for ( j = 0; j < group->numGroup; j++ )
00123                 {
00124                         dist = DistanceSquared( g_entities[group->member[i].number].r.currentOrigin, g_entities[group->member[j].number].r.currentOrigin );
00125                         if ( dist < bestDist )
00126                         {
00127                                 bestDist = dist;
00128                                 group->member[i].closestBuddy = group->member[j].number;
00129                         }
00130                 }
00131         }
00132 }
00133 
00134 void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group )
00135 {
00136         AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS];
00137         int                             i, j, k;
00138         qboolean                sort = qfalse;
00139 
00140         if ( group->enemy != NULL )
00141         {//FIXME: just use enemy->waypoint?
00142                 group->enemyWP = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
00143         }
00144         else
00145         {
00146                 group->enemyWP = WAYPOINT_NONE;
00147         }
00148 
00149         for ( i = 0; i < group->numGroup; i++ )
00150         {
00151                 if ( group->enemyWP == WAYPOINT_NONE )
00152                 {//FIXME: just use member->waypoint?
00153                         group->member[i].waypoint = WAYPOINT_NONE;
00154                         group->member[i].pathCostToEnemy = Q3_INFINITE;
00155                 }
00156                 else
00157                 {//FIXME: just use member->waypoint?
00158                         group->member[i].waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
00159                         if ( group->member[i].waypoint != WAYPOINT_NONE )
00160                         {
00161                                 group->member[i].pathCostToEnemy = trap_Nav_GetPathCost( group->member[i].waypoint, group->enemyWP );
00162                                 //at least one of us has a path, so do sorting
00163                                 sort = qtrue;
00164                         }
00165                         else
00166                         {
00167                                 group->member[i].pathCostToEnemy = Q3_INFINITE;
00168                         }
00169                 }
00170         }
00171         //Now sort
00172         if ( sort )
00173         {
00174                 //initialize bestMembers data
00175                 for ( j = 0; j < group->numGroup; j++ )
00176                 {
00177                         bestMembers[j].number = ENTITYNUM_NONE;
00178                 }
00179 
00180                 for ( i = 0; i < group->numGroup; i++ )
00181                 {
00182                         for ( j = 0; j < group->numGroup; j++ )
00183                         {
00184                                 if ( bestMembers[j].number != ENTITYNUM_NONE )
00185                                 {//slot occupied
00186                                         if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy )
00187                                         {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here
00188                                                 for ( k = group->numGroup; k > j; k++ )
00189                                                 {
00190                                                         memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) );
00191                                                 }
00192                                                 memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
00193                                                 break;
00194                                         }
00195                                 }
00196                                 else
00197                                 {//slot unoccupied, reached end of list, throw self in here
00198                                         memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
00199                                         break;
00200                                 }
00201                         }
00202                 }
00203 
00204                 //Okay, now bestMembers is a sorted list, just copy it into group->members
00205                 for ( i = 0; i < group->numGroup; i++ )
00206                 {
00207                         memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) );
00208                 }
00209         }
00210 }
00211 
00212 qboolean AI_FindSelfInPreviousGroup( gentity_t *self )
00213 {//go through other groups made this frame and see if any of those contain me already
00214         int     i, j;
00215         for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
00216         {
00217                 if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL )
00218                 {//check this one
00219                         for ( j = 0; j < level.groups[i].numGroup; j++ )
00220                         {
00221                                 if ( level.groups[i].member[j].number == self->s.number )
00222                                 {
00223                                         self->NPC->group = &level.groups[i];
00224                                         return qtrue;
00225                                 }
00226                         }
00227                 }
00228         }
00229         return qfalse;
00230 }
00231 
00232 void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member )
00233 {
00234         int i;
00235 
00236         //okay, you know what?  Check this damn group and make sure we're not already in here!
00237         for ( i = 0; i < group->numGroup; i++ )
00238         {
00239                 if ( group->member[i].number == member->s.number )
00240                 {//already in here
00241                         break;
00242                 }
00243         }
00244         if ( i < group->numGroup )
00245         {//found him in group already
00246         }
00247         else
00248         {//add him in
00249                 group->member[group->numGroup++].number = member->s.number;
00250                 group->numState[member->NPC->squadState]++;
00251         }
00252         if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) )
00253         {//keep track of highest rank
00254                 group->commander = member;
00255         }
00256         member->NPC->group = group;
00257 }
00258 
00259 qboolean AI_TryJoinPreviousGroup( gentity_t *self )
00260 {//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in!
00261         int     i;
00262         for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
00263         {
00264                 if ( level.groups[i].numGroup 
00265                         && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1) 
00266                         //&& level.groups[i].enemy != NULL 
00267                         && level.groups[i].enemy == self->enemy )
00268                 {//has members, not full and has my enemy
00269                         if ( AI_ValidateGroupMember( &level.groups[i], self ) )
00270                         {//I am a valid member for this group
00271                                 AI_InsertGroupMember( &level.groups[i], self );
00272                                 return qtrue;
00273                         }
00274                 }
00275         }
00276         return qfalse;
00277 }
00278 
00279 qboolean AI_GetNextEmptyGroup( gentity_t *self )
00280 {
00281         int i;
00282 
00283         if ( AI_FindSelfInPreviousGroup( self ) )
00284         {//already in one, no need to make a new one
00285                 return qfalse;
00286         }
00287 
00288         if ( AI_TryJoinPreviousGroup( self ) )
00289         {//try to just put us in one that already exists
00290                 return qfalse;
00291         }
00292         
00293         //okay, make a whole new one, then
00294         for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
00295         {
00296                 if ( !level.groups[i].numGroup )
00297                 {//make a new one
00298                         self->NPC->group = &level.groups[i];
00299                         return qtrue;
00300                 }
00301         }
00302 
00303         //if ( i >= MAX_FRAME_GROUPS )
00304         {//WTF?  Out of groups!
00305                 self->NPC->group = NULL;
00306                 return qfalse;
00307         }
00308 }
00309 
00310 qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member )
00311 {
00312         vec3_t center;
00313 
00314         if ( !group )
00315         {
00316                 return qfalse;
00317         }
00318         if ( group->commander )
00319         {
00320                 VectorCopy( group->commander->r.currentOrigin, center );
00321         }
00322         else
00323         {//hmm, just pick the first member
00324                 if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD )
00325                 {
00326                         return qfalse;
00327                 }
00328                 VectorCopy( g_entities[group->member[0].number].r.currentOrigin, center );
00329         }
00330         //FIXME: maybe it should be based on the center of the mass of the group, not the commander?
00331         if ( DistanceSquared( center, member->r.currentOrigin ) > 147456/*384*384*/ )
00332         {
00333                 return qfalse;
00334         }
00335         if ( !trap_InPVS( member->r.currentOrigin, center ) )
00336         {//not within PVS of the group enemy
00337                 return qfalse;
00338         }
00339         return qtrue;
00340 }
00341 
00342 qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member )
00343 {
00344         //Validate ents
00345         if ( member == NULL )
00346                 return qfalse;
00347 
00348         //Validate clients
00349         if ( member->client == NULL )
00350                 return qfalse;
00351 
00352         //Validate NPCs
00353         if ( member->NPC == NULL )
00354                 return qfalse;
00355 
00356         //must be aware
00357         if ( member->NPC->confusionTime > level.time )
00358                 return qfalse;
00359 
00360         //must be allowed to join groups
00361         if ( member->NPC->scriptFlags&SCF_NO_GROUPS )
00362                 return qfalse;
00363 
00364         //Must not be in another group
00365         if ( member->NPC->group != NULL && member->NPC->group != group )
00366         {//FIXME: if that group's enemy is mine, why not absorb that group into mine?
00367                 return qfalse;
00368         }
00369 
00370         //Must be alive
00371         if ( member->health <= 0 )
00372                 return qfalse;
00373 
00374         //can't be in an emplaced gun
00375 //      if( member->s.eFlags & EF_LOCKED_TO_WEAPON )
00376 //              return qfalse;
00377         //rwwFIXMEFIXME: support this flag
00378 
00379         //Must be on the same team
00380         if ( member->client->playerTeam != group->team )
00381                 return qfalse;
00382 
00383         if ( member->client->ps.weapon == WP_SABER ||
00384                 member->client->ps.weapon == WP_THERMAL ||
00385                 member->client->ps.weapon == WP_DISRUPTOR ||
00386                 member->client->ps.weapon == WP_EMPLACED_GUN ||
00387 //              member->client->ps.weapon == WP_BOT_LASER ||            // Probe droid  - Laser blast
00388                 member->client->ps.weapon == WP_STUN_BATON ||
00389                 member->client->ps.weapon == WP_TURRET /*||                     // turret guns 
00390                 member->client->ps.weapon == WP_ATST_MAIN ||
00391                 member->client->ps.weapon == WP_ATST_SIDE ||
00392                 member->client->ps.weapon == WP_TIE_FIGHTER*/ )
00393         {//not really a squad-type guy
00394                 return qfalse;
00395         }
00396 
00397         if ( member->client->NPC_class == CLASS_ATST ||
00398                 member->client->NPC_class == CLASS_PROBE ||
00399                 member->client->NPC_class == CLASS_SEEKER ||
00400                 member->client->NPC_class == CLASS_REMOTE ||
00401                 member->client->NPC_class == CLASS_SENTRY ||
00402                 member->client->NPC_class == CLASS_INTERROGATOR ||
00403                 member->client->NPC_class == CLASS_MINEMONSTER ||
00404                 member->client->NPC_class == CLASS_HOWLER ||
00405                 member->client->NPC_class == CLASS_MARK1 ||
00406                 member->client->NPC_class == CLASS_MARK2 )
00407         {//these kinds of enemies don't actually use this group AI
00408                 return qfalse;
00409         }
00410 
00411         //should have same enemy
00412         if ( member->enemy != group->enemy )
00413         {
00414                 if ( member->enemy != NULL )
00415                 {//he's fighting someone else, leave him out
00416                         return qfalse;
00417                 }
00418                 if ( !trap_InPVS( member->r.currentOrigin, group->enemy->r.currentOrigin ) )
00419                 {//not within PVS of the group enemy
00420                         return qfalse;
00421                 }
00422         }
00423         else if ( group->enemy == NULL )
00424         {//if the group is a patrol group, only take those within the room and radius
00425                 if ( !AI_ValidateNoEnemyGroupMember( group, member ) )
00426                 {
00427                         return qfalse;
00428                 }
00429         }
00430         //must be actually in combat mode
00431         if ( !TIMER_Done( member, "interrogating" ) )
00432                 return qfalse;
00433         //FIXME: need to have a route to enemy and/or clear shot?
00434         return qtrue;
00435 }
00436 
00437 /*
00438 -------------------------
00439 AI_GetGroup
00440 -------------------------
00441 */
00442 //#define MAX_WAITERS   128
00443 void AI_GetGroup( gentity_t *self )
00444 {
00445         int     i;
00446         gentity_t       *member;//, *waiter;
00447         //int   waiters[MAX_WAITERS];
00448 
00449         if ( !self || !self->NPC )
00450         {
00451                 return;
00452         }
00453 
00454         if ( d_noGroupAI.integer )
00455         {
00456                 self->NPC->group = NULL;
00457                 return;
00458         }
00459 
00460         if ( !self->client )
00461         {
00462                 self->NPC->group = NULL;
00463                 return;
00464         }
00465 
00466         if ( self->NPC->scriptFlags&SCF_NO_GROUPS )
00467         {
00468                 self->NPC->group = NULL;
00469                 return;
00470         }
00471 
00472         if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 )))
00473         {
00474                 self->NPC->group = NULL;
00475                 return;
00476         }
00477 
00478         if ( !AI_GetNextEmptyGroup( self ) )
00479         {//either no more groups left or we're already in a group built earlier
00480                 return;
00481         }
00482 
00483         //create a new one
00484         memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) );
00485 
00486         self->NPC->group->enemy = self->enemy;
00487         self->NPC->group->team = self->client->playerTeam;
00488         self->NPC->group->processed = qfalse;
00489         self->NPC->group->commander = self;
00490         self->NPC->group->memberValidateTime = level.time + 2000;
00491         self->NPC->group->activeMemberNum = 0;
00492 
00493         if ( self->NPC->group->enemy )
00494         {
00495                 self->NPC->group->lastSeenEnemyTime = level.time;
00496                 self->NPC->group->lastClearShotTime = level.time;
00497                 VectorCopy( self->NPC->group->enemy->r.currentOrigin, self->NPC->group->enemyLastSeenPos );
00498         }
00499 
00500 //      for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++)
00501         for ( i = 0; i < level.num_entities ; i++)
00502         {
00503                 member = &g_entities[i];
00504 
00505                 if (!member->inuse)
00506                 {
00507                         continue;
00508                 }
00509 
00510                 if ( !AI_ValidateGroupMember( self->NPC->group, member ) )
00511                 {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
00512                         continue;
00513                 }
00514                 
00515                 //store it
00516                 AI_InsertGroupMember( self->NPC->group, member );
00517 
00518                 if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) )
00519                 {//full
00520                         break;
00521                 }
00522         }
00523 
00524         /*
00525         //now go through waiters and see if any should join the group
00526         //NOTE:  Some should hang back and probably not attack, so we can ambush
00527         //NOTE: only do this if calling for reinforcements?
00528         for ( i = 0; i < numWaiters; i++ )
00529         {
00530                 waiter = &g_entities[waiters[i]];
00531         
00532                 for ( j = 0; j < self->NPC->group->numGroup; j++ )
00533                 {
00534                         member = &g_entities[self->NPC->group->member[j];
00535 
00536                         if ( trap_InPVS( waiter->r.currentOrigin, member->r.currentOrigin ) )
00537                         {//this waiter is within PVS of a current member
00538                         }
00539                 }
00540         }
00541         */
00542 
00543         if ( self->NPC->group->numGroup <= 0 )
00544         {//none in group
00545                 self->NPC->group = NULL;
00546                 return;
00547         }
00548 
00549         AI_SortGroupByPathCostToEnemy( self->NPC->group );
00550         AI_SetClosestBuddy( self->NPC->group );
00551 }
00552 
00553 void AI_SetNewGroupCommander( AIGroupInfo_t *group )
00554 {
00555         gentity_t *member = NULL;
00556         int i;
00557 
00558         group->commander = NULL;
00559         for ( i = 0; i < group->numGroup; i++ )
00560         {
00561                 member = &g_entities[group->member[i].number];
00562 
00563                 if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) )
00564                 {//keep track of highest rank
00565                         group->commander = member;
00566                 }
00567         }
00568 }
00569 
00570 void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum )
00571 {
00572         int i;
00573 
00574         if ( group->commander && group->commander->s.number == group->member[memberNum].number )
00575         {
00576                 group->commander = NULL;
00577         }
00578         if ( g_entities[group->member[memberNum].number].NPC )
00579         {
00580                 g_entities[group->member[memberNum].number].NPC->group = NULL;
00581         }
00582         for ( i = memberNum; i < (group->numGroup-1); i++ )
00583         {
00584                 memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) );
00585         }
00586         if ( memberNum < group->activeMemberNum )
00587         {
00588                 group->activeMemberNum--;
00589                 if ( group->activeMemberNum < 0 )
00590                 {
00591                         group->activeMemberNum = 0;
00592                 }
00593         }
00594         group->numGroup--;
00595         if ( group->numGroup < 0 )
00596         {
00597                 group->numGroup = 0;
00598         }
00599         AI_SetNewGroupCommander( group );
00600 }
00601 
00602 void AI_DeleteSelfFromGroup( gentity_t *self )
00603 {
00604         int i;
00605 
00606         //FIXME: if killed, keep track of how many in group killed?  To affect morale?
00607         for ( i = 0; i < self->NPC->group->numGroup; i++ )
00608         {
00609                 if ( self->NPC->group->member[i].number == self->s.number )
00610                 {
00611                         AI_DeleteGroupMember( self->NPC->group, i );
00612                         return;
00613                 }
00614         }
00615 }
00616 
00617 extern void ST_AggressionAdjust( gentity_t *self, int change );
00618 extern void ST_MarkToCover( gentity_t *self );
00619 extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime );
00620 void AI_GroupMemberKilled( gentity_t *self )
00621 {
00622         AIGroupInfo_t *group = self->NPC->group;
00623         gentity_t       *member;
00624         qboolean        noflee = qfalse;
00625         int                     i;
00626 
00627         if ( !group )
00628         {//what group?
00629                 return;
00630         }
00631         if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN )
00632         {//I'm not an officer, let's not really care for now
00633                 return;
00634         }
00635         //temporarily drop group morale for a few seconds
00636         group->moraleAdjust -= self->NPC->rank;
00637         //go through and drop aggression on my teammates (more cover, worse aim)
00638         for ( i = 0; i < group->numGroup; i++ )
00639         {
00640                 member = &g_entities[group->member[i].number];
00641                 if ( member == self )
00642                 {
00643                         continue;
00644                 }
00645                 if ( member->NPC->rank > RANK_ENSIGN )
00646                 {//officers do not panic
00647                         noflee = qtrue;
00648                 }
00649                 else
00650                 {
00651                         ST_AggressionAdjust( member, -1 );
00652                         member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy
00653                 }
00654         }
00655         //okay, if I'm the group commander, make everyone else flee
00656         if ( group->commander != self )
00657         {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me?
00658                 return;
00659         }
00660         //now see if there is another of sufficient rank to keep them from fleeing
00661         if ( !noflee )
00662         {
00663                 self->NPC->group->speechDebounceTime = 0;
00664                 for ( i = 0; i < group->numGroup; i++ )
00665                 {
00666                         member = &g_entities[group->member[i].number];
00667                         if ( member == self )
00668                         {
00669                                 continue;
00670                         }
00671                         if ( member->NPC->rank < RANK_ENSIGN )
00672                         {//grunt
00673                                 if ( group->enemy && DistanceSquared( member->r.currentOrigin, group->enemy->r.currentOrigin ) < 65536/*256*256*/ )
00674                                 {//those close to enemy run away!
00675                                         ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
00676                                 }
00677                                 else if ( DistanceSquared( member->r.currentOrigin, self->r.currentOrigin ) < 65536/*256*256*/ )
00678                                 {//those close to me run away!
00679                                         ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
00680                                 }
00681                                 else
00682                                 {//else, maybe just a random chance
00683                                         if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank )
00684                                         {//lower rank they are, higher rank I am, more likely they are to flee
00685                                                 ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
00686                                         }
00687                                         else
00688                                         {
00689                                                 ST_MarkToCover( member );
00690                                         }
00691                                 }
00692                                 member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
00693                         }
00694                         member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
00695                 }
00696         }
00697 }
00698 
00699 void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot )
00700 {
00701         if ( !group )
00702         {
00703                 return;
00704         }
00705         group->lastSeenEnemyTime = level.time;
00706         VectorCopy( spot, group->enemyLastSeenPos );
00707 }
00708 
00709 void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group )
00710 {
00711         if ( !group )
00712         {
00713                 return;
00714         }
00715         group->lastClearShotTime = level.time;
00716 }
00717 
00718 void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState )
00719 {
00720         int i;
00721 
00722         if ( !group )
00723         {
00724                 member->NPC->squadState = newSquadState;
00725                 return;
00726         }
00727 
00728         for ( i = 0; i < group->numGroup; i++ )
00729         {
00730                 if ( group->member[i].number == member->s.number )
00731                 {
00732                         group->numState[member->NPC->squadState]--;
00733                         member->NPC->squadState = newSquadState;
00734                         group->numState[member->NPC->squadState]++;
00735                         return;
00736                 }
00737         }
00738 }
00739 
00740 qboolean AI_RefreshGroup( AIGroupInfo_t *group )
00741 {
00742         gentity_t       *member;
00743         int                     i;//, j;
00744 
00745         //see if we should merge with another group
00746         for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) 
00747         {
00748                 if ( &level.groups[i] == group )
00749                 {
00750                         break;
00751                 }
00752                 else
00753                 {
00754                         if ( level.groups[i].enemy == group->enemy )
00755                         {//2 groups with same enemy
00756                                 if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) )
00757                                 {//combining the members would fit in one group
00758                                         qboolean deleteWhenDone = qtrue;
00759                                         int j;
00760 
00761                                         //combine the members of mine into theirs
00762                                         for ( j = 0; j < group->numGroup; j++ )
00763                                         {
00764                                                 member = &g_entities[group->member[j].number];
00765                                                 if ( level.groups[i].enemy == NULL )
00766                                                 {//special case for groups without enemies, must be in range
00767                                                         if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) )
00768                                                         {
00769                                                                 deleteWhenDone = qfalse;
00770                                                                 continue;
00771                                                         }
00772                                                 }
00773                                                 //remove this member from this group
00774                                                 AI_DeleteGroupMember( group, j );
00775                                                 //keep marker at same place since we deleted this guy and shifted everyone up one
00776                                                 j--;
00777                                                 //add them to the earlier group
00778                                                 AI_InsertGroupMember( &level.groups[i], member );
00779                                         }
00780                                         //return and delete this group
00781                                         if ( deleteWhenDone )
00782                                         {
00783                                                 return qfalse;
00784                                         }
00785                                 }
00786                         }
00787                 }
00788         }
00789         //clear numStates
00790         for ( i = 0; i < NUM_SQUAD_STATES; i++ )
00791         {
00792                 group->numState[i] = 0;
00793         }
00794 
00795         //go through group and validate each membership
00796         group->commander = NULL;
00797         for ( i = 0; i < group->numGroup; i++ )
00798         {
00799                 /*
00800                 //this checks for duplicate copies of one member in a group
00801                 for ( j = 0; j < group->numGroup; j++ )
00802                 {
00803                         if ( i != j )
00804                         {
00805                                 if ( group->member[i].number == group->member[j].number )
00806                                 {
00807                                         break;
00808                                 }
00809                         }
00810                 }
00811                 if ( j < group->numGroup )
00812                 {//found a dupe!
00813                         gi.Printf( S_COLOR_RED"ERROR: member %s(%d) a duplicate group member!!!\n", g_entities[group->member[i].number].targetname, group->member[i].number );
00814                         AI_DeleteGroupMember( group, i );
00815                         i--;
00816                         continue;
00817                 }
00818                 */
00819                 member = &g_entities[group->member[i].number];
00820 
00821                 //Must be alive
00822                 if ( member->health <= 0 )
00823                 {
00824                         AI_DeleteGroupMember( group, i );
00825                         //keep marker at same place since we deleted this guy and shifted everyone up one
00826                         i--;
00827                 }
00828                 else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) )
00829                 {
00830                         //remove this one from the group
00831                         AI_DeleteGroupMember( group, i );
00832                         //keep marker at same place since we deleted this guy and shifted everyone up one
00833                         i--;
00834                 }
00835                 else
00836                 {//membership is valid
00837                         //keep track of squadStates
00838                         group->numState[member->NPC->squadState]++;
00839                         if ( !group->commander || member->NPC->rank > group->commander->NPC->rank )
00840                         {//keep track of highest rank
00841                                 group->commander = member;
00842                         }
00843                 }
00844         }
00845         if ( group->memberValidateTime < level.time )
00846         {
00847                 group->memberValidateTime = level.time + Q_irand( 500, 2500 );
00848         }
00849         //Now add any new guys as long as we're not full
00850         /*
00851         for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++)
00852         {
00853                 if ( !AI_ValidateGroupMember( group, member ) )
00854                 {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
00855                         continue;
00856                 }
00857                 if ( member->NPC->group == group )
00858                 {//DOH, already in our group
00859                         continue;
00860                 }
00861 
00862                 //store it
00863                 AI_InsertGroupMember( group, member );
00864         }
00865         */
00866 
00867         //calc the morale of this group
00868         group->morale = group->moraleAdjust;
00869         for ( i = 0; i < group->numGroup; i++ )
00870         {
00871                 member = &g_entities[group->member[i].number];
00872                 if ( member->NPC->rank < RANK_ENSIGN )
00873                 {//grunts
00874                         group->morale++;
00875                 }
00876                 else
00877                 {
00878                         group->morale += member->NPC->rank;
00879                 }
00880                 if ( group->commander && debugNPCAI.integer )
00881                 {
00882                         //G_DebugLine( group->commander->r.currentOrigin, member->r.currentOrigin, FRAMETIME, 0x00ff00ff, qtrue );
00883                         G_TestLine(group->commander->r.currentOrigin, member->r.currentOrigin, 0x00000ff, FRAMETIME);
00884                 }
00885         }
00886         if ( group->enemy )
00887         {//modify morale based on enemy health and weapon
00888                 if ( group->enemy->health < 10 )
00889                 {
00890                         group->morale += 10;
00891                 }
00892                 else if ( group->enemy->health < 25 )
00893                 {
00894                         group->morale += 5;
00895                 }
00896                 else if ( group->enemy->health < 50 )
00897                 {
00898                         group->morale += 2;
00899                 }
00900                 switch( group->enemy->s.weapon )
00901                 {
00902                 case WP_SABER:
00903                         group->morale -= 5;
00904                         break;
00905                 case WP_BRYAR_PISTOL:
00906                         group->morale += 3;
00907                         break;
00908                 case WP_DISRUPTOR:
00909                         group->morale += 2;
00910                         break;
00911                 case WP_REPEATER:
00912                         group->morale -= 1;
00913                         break;
00914                 case WP_FLECHETTE:
00915                         group->morale -= 2;
00916                         break;
00917                 case WP_ROCKET_LAUNCHER:
00918                         group->morale -= 10;
00919                         break;
00920                 case WP_THERMAL:
00921                         group->morale -= 5;
00922                         break;
00923                 case WP_TRIP_MINE:
00924                         group->morale -= 3;
00925                         break;
00926                 case WP_DET_PACK:
00927                         group->morale -= 10;
00928                         break;
00929 //              case WP_MELEE:                  // Any ol' melee attack
00930 //                      group->morale += 20;
00931 //                      break;
00932                 case WP_STUN_BATON:
00933                         group->morale += 10;
00934                         break;
00935                 case WP_EMPLACED_GUN:
00936                         group->morale -= 8;
00937                         break;
00938 //              case WP_ATST_MAIN:
00939 //                      group->morale -= 8;
00940 //                      break;
00941 //              case WP_ATST_SIDE:
00942 //                      group->morale -= 20;
00943 //                      break;
00944                 }
00945         }
00946         if ( group->moraleDebounce < level.time )
00947         {//slowly degrade whatever moraleAdjusters we may have
00948                 if ( group->moraleAdjust > 0 )
00949                 {
00950                         group->moraleAdjust--;
00951                 }
00952                 else if ( group->moraleAdjust < 0 )
00953                 {
00954                         group->moraleAdjust++;
00955                 }
00956                 group->moraleDebounce = level.time + 1000;//FIXME: define?
00957         }
00958         //mark this group as not having been run this frame
00959         group->processed = qfalse;
00960 
00961         return (group->numGroup>0);
00962 }
00963 
00964 void AI_UpdateGroups( void )
00965 {
00966         int i;
00967 
00968         if ( d_noGroupAI.integer )
00969         {
00970                 return;
00971         }
00972         //Clear all Groups
00973         for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) 
00974         {
00975                 if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL || 
00976                 {
00977                         memset( &level.groups[i], 0, sizeof( level.groups[i] ) );
00978                 }
00979         }
00980 }
00981 
00982 qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum )
00983 {
00984         int i;
00985 
00986         if ( !group )
00987         {
00988                 return qfalse;
00989         }
00990         for ( i = 0; i < group->numGroup; i++ )
00991         {
00992                 if ( group->member[i].number == entNum )
00993                 {
00994                         return qtrue;
00995                 }
00996         }
00997         return qfalse;
00998 }
00999 //Overload 
01000 
01001 /*
01002 void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius )
01003 {
01004         if ( ent->client == NULL )
01005                 return;
01006 
01007         vec3_t  temp, angles;
01008 
01009         //FIXME: This is specialized code.. move?
01010         if ( ent->enemy )
01011         {
01012                 VectorSubtract( ent->enemy->r.currentOrigin, ent->r.currentOrigin, temp );
01013                 VectorNormalize( temp );        //FIXME: Needed?
01014                 vectoangles( temp, angles );
01015         }
01016         else
01017         {
01018                 VectorCopy( ent->currentAngles, angles );
01019         }
01020 
01021         AI_GetGroup( group, ent->r.currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy );
01022 }
01023 */
01024 /*
01025 -------------------------
01026 AI_CheckEnemyCollision
01027 -------------------------
01028 */
01029 
01030 qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy )
01031 {
01032         navInfo_t       info;
01033 
01034         if ( ent == NULL )
01035                 return qfalse;
01036 
01037 //      if ( ent->svFlags & SVF_LOCKEDENEMY )
01038 //              return qfalse;
01039 
01040         NAV_GetLastMove( &info );
01041 
01042         //See if we've hit something
01043         if ( ( info.blocker ) && ( info.blocker != ent->enemy ) )
01044         {
01045                 if ( ( info.blocker->client ) && ( info.blocker->client->playerTeam == ent->client->enemyTeam ) )
01046                 {
01047                         if ( takeEnemy )
01048                                 G_SetEnemy( ent, info.blocker );
01049 
01050                         return qtrue;
01051                 }
01052         }
01053 
01054         return qfalse;
01055 }
01056 
01057 /*
01058 -------------------------
01059 AI_DistributeAttack
01060 -------------------------
01061 */
01062 
01063 #define MAX_RADIUS_ENTS         128
01064 
01065 gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold )
01066 {
01067         int                     radiusEnts[ MAX_RADIUS_ENTS ];
01068         gentity_t       *check;
01069         int                     numEnts;
01070         int                     numSurrounding;
01071         int                     i;
01072         int                     j;
01073         vec3_t          mins, maxs;
01074 
01075         //Don't take new targets
01076 //      if ( NPC->svFlags & SVF_LOCKEDENEMY )
01077 //              return enemy;
01078 
01079         numSurrounding = AI_GetGroupSize( enemy->r.currentOrigin, 48, team, attacker );
01080 
01081         //First, see if we should look for the player
01082         if ( enemy != &g_entities[0] )
01083         {
01084                 //rwwFIXMEFIXME: care about all clients not just 0
01085                 int     aroundPlayer = AI_GetGroupSize( g_entities[0].r.currentOrigin, 48, team, attacker );
01086 
01087                 //See if we're above our threshold
01088                 if ( aroundPlayer < threshold )
01089                 {
01090                         return &g_entities[0];
01091                 }
01092         }
01093 
01094         //See if our current enemy is still ok
01095         if ( numSurrounding < threshold )
01096                 return enemy;
01097 
01098         //Otherwise we need to take a new enemy if possible
01099 
01100         //Setup the bbox to search in
01101         for ( i = 0; i < 3; i++ )
01102         {
01103                 mins[i] = enemy->r.currentOrigin[i] - 512;
01104                 maxs[i] = enemy->r.currentOrigin[i] + 512;
01105         }
01106 
01107         //Get the number of entities in a given space
01108         numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
01109 
01110         //Cull this list
01111         for ( j = 0; j < numEnts; j++ )
01112         {
01113                 check = &g_entities[radiusEnts[j]];
01114 
01115                 //Validate clients
01116                 if ( check->client == NULL )
01117                         continue;
01118 
01119                 //Skip the requested avoid ent if present
01120                 if ( ( check == enemy ) )
01121                         continue;
01122 
01123                 //Must be on the same team
01124                 if ( check->client->playerTeam != enemy->client->playerTeam )
01125                         continue;
01126 
01127                 //Must be alive
01128                 if ( check->health <= 0 )
01129                         continue;
01130 
01131                 //Must not be overwhelmed
01132                 if ( AI_GetGroupSize( check->r.currentOrigin, 48, team, attacker ) > threshold )
01133                         continue;
01134 
01135                 return check;
01136         }
01137 
01138         return NULL;
01139 }