123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056 |
- // These utilities are meant for strictly non-player, non-team NPCs.
- // These functions are in their own file because they are only intended
- // for use with NPCs who's logic has been overriden from the original
- // AI code, and who's code resides in files with the AI_ prefix.
- // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
- #include "g_headers.h"
- #include "b_local.h"
- #include "g_nav.h"
- #include "g_navigator.h"
- #define MAX_RADIUS_ENTS 128
- #define DEFAULT_RADIUS 45
- //extern CNavigator navigator;
- extern cvar_t *d_noGroupAI;
- qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member );
- /*
- -------------------------
- AI_GetGroupSize
- -------------------------
- */
- int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid )
- {
- gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
- vec3_t mins, maxs;
- int numEnts, realCount = 0;
- //Setup the bbox to search in
- for ( int i = 0; i < 3; i++ )
- {
- mins[i] = origin[i] - radius;
- maxs[i] = origin[i] + radius;
- }
- //Get the number of entities in a given space
- numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
- //Cull this list
- for ( int j = 0; j < numEnts; j++ )
- {
- //Validate clients
- if ( radiusEnts[ j ]->client == NULL )
- continue;
- //Skip the requested avoid ent if present
- if ( ( avoid != NULL ) && ( radiusEnts[ j ] == avoid ) )
- continue;
- //Must be on the same team
- if ( radiusEnts[ j ]->client->playerTeam != playerTeam )
- continue;
- //Must be alive
- if ( radiusEnts[ j ]->health <= 0 )
- continue;
- realCount++;
- }
- return realCount;
- }
- //Overload
- int AI_GetGroupSize( gentity_t *ent, int radius )
- {
- if ( ( ent == NULL ) || ( ent->client == NULL ) )
- return -1;
- return AI_GetGroupSize( ent->currentOrigin, radius, ent->client->playerTeam, ent );
- }
- void AI_SetClosestBuddy( AIGroupInfo_t *group )
- {
- int i, j;
- int dist, bestDist;
- for ( i = 0; i < group->numGroup; i++ )
- {
- group->member[i].closestBuddy = ENTITYNUM_NONE;
- bestDist = Q3_INFINITE;
- for ( j = 0; j < group->numGroup; j++ )
- {
- dist = DistanceSquared( g_entities[group->member[i].number].currentOrigin, g_entities[group->member[j].number].currentOrigin );
- if ( dist < bestDist )
- {
- bestDist = dist;
- group->member[i].closestBuddy = group->member[j].number;
- }
- }
- }
- }
- void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group )
- {
- AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS];
- int i, j, k;
- qboolean sort = qfalse;
- if ( group->enemy != NULL )
- {//FIXME: just use enemy->waypoint?
- group->enemyWP = NAV::GetNearestNode(group->enemy);
- }
- else
- {
- group->enemyWP = WAYPOINT_NONE;
- }
- for ( i = 0; i < group->numGroup; i++ )
- {
- if ( group->enemyWP == WAYPOINT_NONE )
- {//FIXME: just use member->waypoint?
- group->member[i].waypoint = WAYPOINT_NONE;
- group->member[i].pathCostToEnemy = Q3_INFINITE;
- }
- else
- {//FIXME: just use member->waypoint?
- group->member[i].waypoint = NAV::GetNearestNode(group->enemy);
- if ( group->member[i].waypoint != WAYPOINT_NONE )
- {
- group->member[i].pathCostToEnemy = NAV::EstimateCostToGoal( group->member[i].waypoint, group->enemyWP );
- //at least one of us has a path, so do sorting
- sort = qtrue;
- }
- else
- {
- group->member[i].pathCostToEnemy = Q3_INFINITE;
- }
- }
- }
- //Now sort
- if ( sort )
- {
- //initialize bestMembers data
- for ( j = 0; j < group->numGroup; j++ )
- {
- bestMembers[j].number = ENTITYNUM_NONE;
- }
- for ( i = 0; i < group->numGroup; i++ )
- {
- for ( j = 0; j < group->numGroup; j++ )
- {
- if ( bestMembers[j].number != ENTITYNUM_NONE )
- {//slot occupied
- if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy )
- {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here
- for ( k = group->numGroup; k > j; k++ )
- {
- memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) );
- }
- memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
- break;
- }
- }
- else
- {//slot unoccupied, reached end of list, throw self in here
- memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
- break;
- }
- }
- }
- //Okay, now bestMembers is a sorted list, just copy it into group->members
- for ( i = 0; i < group->numGroup; i++ )
- {
- memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) );
- }
- }
- }
- qboolean AI_FindSelfInPreviousGroup( gentity_t *self )
- {//go through other groups made this frame and see if any of those contain me already
- int i, j;
- for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
- {
- if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL )
- {//check this one
- for ( j = 0; j < level.groups[i].numGroup; j++ )
- {
- if ( level.groups[i].member[j].number == self->s.number )
- {
- self->NPC->group = &level.groups[i];
- return qtrue;
- }
- }
- }
- }
- return qfalse;
- }
- void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member )
- {
- //okay, you know what? Check this damn group and make sure we're not already in here!
- for ( int i = 0; i < group->numGroup; i++ )
- {
- if ( group->member[i].number == member->s.number )
- {//already in here
- break;
- }
- }
- if ( i < group->numGroup )
- {//found him in group already
- }
- else
- {//add him in
- group->member[group->numGroup++].number = member->s.number;
- group->numState[member->NPC->squadState]++;
- }
- if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) )
- {//keep track of highest rank
- group->commander = member;
- }
- member->NPC->group = group;
- }
- qboolean AI_TryJoinPreviousGroup( gentity_t *self )
- {//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in!
- int i;
- for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
- {
- if ( level.groups[i].numGroup
- && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1)
- //&& level.groups[i].enemy != NULL
- && level.groups[i].enemy == self->enemy )
- {//has members, not full and has my enemy
- if ( AI_ValidateGroupMember( &level.groups[i], self ) )
- {//I am a valid member for this group
- AI_InsertGroupMember( &level.groups[i], self );
- return qtrue;
- }
- }
- }
- return qfalse;
- }
- qboolean AI_GetNextEmptyGroup( gentity_t *self )
- {
- if ( AI_FindSelfInPreviousGroup( self ) )
- {//already in one, no need to make a new one
- return qfalse;
- }
- if ( AI_TryJoinPreviousGroup( self ) )
- {//try to just put us in one that already exists
- return qfalse;
- }
-
- //okay, make a whole new one, then
- for ( int i = 0; i < MAX_FRAME_GROUPS; i++ )
- {
- if ( !level.groups[i].numGroup )
- {//make a new one
- self->NPC->group = &level.groups[i];
- return qtrue;
- }
- }
- //if ( i >= MAX_FRAME_GROUPS )
- {//WTF? Out of groups!
- self->NPC->group = NULL;
- return qfalse;
- }
- }
- qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member )
- {
- if ( !group )
- {
- return qfalse;
- }
- vec3_t center;
- if ( group->commander )
- {
- VectorCopy( group->commander->currentOrigin, center );
- }
- else
- {//hmm, just pick the first member
- if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD )
- {
- return qfalse;
- }
- VectorCopy( g_entities[group->member[0].number].currentOrigin, center );
- }
- //FIXME: maybe it should be based on the center of the mass of the group, not the commander?
- if ( DistanceSquared( center, member->currentOrigin ) > 147456/*384*384*/ )
- {
- return qfalse;
- }
- if ( !gi.inPVS( member->currentOrigin, center ) )
- {//not within PVS of the group enemy
- return qfalse;
- }
- return qtrue;
- }
- qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member )
- {
- //Validate ents
- if ( member == NULL )
- return qfalse;
- //Validate clients
- if ( member->client == NULL )
- return qfalse;
- //Validate NPCs
- if ( member->NPC == NULL )
- return qfalse;
- //must be aware
- if ( member->NPC->confusionTime > level.time )
- return qfalse;
- //must be allowed to join groups
- if ( member->NPC->scriptFlags&SCF_NO_GROUPS )
- return qfalse;
- //Must not be in another group
- if ( member->NPC->group != NULL && member->NPC->group != group )
- {//FIXME: if that group's enemy is mine, why not absorb that group into mine?
- return qfalse;
- }
- //Must be alive
- if ( member->health <= 0 )
- return qfalse;
- //can't be in an emplaced gun
- if( member->s.eFlags & EF_LOCKED_TO_WEAPON )
- return qfalse;
- if( member->s.eFlags & EF_HELD_BY_RANCOR )
- return qfalse;
- if( member->s.eFlags & EF_HELD_BY_SAND_CREATURE )
- return qfalse;
- if( member->s.eFlags & EF_HELD_BY_WAMPA )
- return qfalse;
- //Must be on the same team
- if ( member->client->playerTeam != group->team )
- return qfalse;
- if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon )
- member->client->ps.weapon == WP_THERMAL ||
- member->client->ps.weapon == WP_DISRUPTOR ||
- member->client->ps.weapon == WP_EMPLACED_GUN ||
- member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast
- member->client->ps.weapon == WP_MELEE ||
- member->client->ps.weapon == WP_TURRET || // turret guns
- member->client->ps.weapon == WP_ATST_MAIN ||
- member->client->ps.weapon == WP_ATST_SIDE ||
- member->client->ps.weapon == WP_TIE_FIGHTER )
- {//not really a squad-type guy
- return qfalse;
- }
- if ( member->client->NPC_class == CLASS_ATST ||
- member->client->NPC_class == CLASS_PROBE ||
- member->client->NPC_class == CLASS_SEEKER ||
- member->client->NPC_class == CLASS_REMOTE ||
- member->client->NPC_class == CLASS_SENTRY ||
- member->client->NPC_class == CLASS_INTERROGATOR ||
- member->client->NPC_class == CLASS_MINEMONSTER ||
- member->client->NPC_class == CLASS_HOWLER ||
- member->client->NPC_class == CLASS_RANCOR ||
- member->client->NPC_class == CLASS_MARK1 ||
- member->client->NPC_class == CLASS_MARK2 )
- {//these kinds of enemies don't actually use this group AI
- return qfalse;
- }
- //should have same enemy
- if ( member->enemy != group->enemy )
- {
- if ( member->enemy != NULL )
- {//he's fighting someone else, leave him out
- return qfalse;
- }
- if ( !gi.inPVS( member->currentOrigin, group->enemy->currentOrigin ) )
- {//not within PVS of the group enemy
- return qfalse;
- }
- }
- else if ( group->enemy == NULL )
- {//if the group is a patrol group, only take those within the room and radius
- if ( !AI_ValidateNoEnemyGroupMember( group, member ) )
- {
- return qfalse;
- }
- }
- //must be actually in combat mode
- if ( !TIMER_Done( member, "interrogating" ) )
- return qfalse;
- //FIXME: need to have a route to enemy and/or clear shot?
- return qtrue;
- }
- /*
- -------------------------
- AI_GetGroup
- -------------------------
- */
- //#define MAX_WAITERS 128
- void AI_GetGroup( gentity_t *self )
- {
- int i;
- gentity_t *member;//, *waiter;
- //int waiters[MAX_WAITERS];
- if ( !self || !self->NPC )
- {
- return;
- }
- if ( d_noGroupAI->integer )
- {
- self->NPC->group = NULL;
- return;
- }
- if ( !self->client )
- {
- self->NPC->group = NULL;
- return;
- }
- if ( self->NPC->scriptFlags&SCF_NO_GROUPS )
- {
- self->NPC->group = NULL;
- return;
- }
- if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 )))
- {
- self->NPC->group = NULL;
- return;
- }
- if ( !AI_GetNextEmptyGroup( self ) )
- {//either no more groups left or we're already in a group built earlier
- return;
- }
- //create a new one
- memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) );
- self->NPC->group->enemy = self->enemy;
- self->NPC->group->team = self->client->playerTeam;
- self->NPC->group->processed = qfalse;
- self->NPC->group->commander = self;
- self->NPC->group->memberValidateTime = level.time + 2000;
- self->NPC->group->activeMemberNum = 0;
- if ( self->NPC->group->enemy )
- {
- self->NPC->group->lastSeenEnemyTime = level.time;
- self->NPC->group->lastClearShotTime = level.time;
- VectorCopy( self->NPC->group->enemy->currentOrigin, self->NPC->group->enemyLastSeenPos );
- }
- // for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++)
- for ( i = 0; i < globals.num_entities ; i++)
- {
- if(!PInUse(i))
- continue;
- member = &g_entities[i];
- if ( !AI_ValidateGroupMember( self->NPC->group, member ) )
- {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
- continue;
- }
-
- //store it
- AI_InsertGroupMember( self->NPC->group, member );
- if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) )
- {//full
- break;
- }
- }
- /*
- //now go through waiters and see if any should join the group
- //NOTE: Some should hang back and probably not attack, so we can ambush
- //NOTE: only do this if calling for reinforcements?
- for ( i = 0; i < numWaiters; i++ )
- {
- waiter = &g_entities[waiters[i]];
-
- for ( j = 0; j < self->NPC->group->numGroup; j++ )
- {
- member = &g_entities[self->NPC->group->member[j];
- if ( gi.inPVS( waiter->currentOrigin, member->currentOrigin ) )
- {//this waiter is within PVS of a current member
- }
- }
- }
- */
- if ( self->NPC->group->numGroup <= 0 )
- {//none in group
- self->NPC->group = NULL;
- return;
- }
- AI_SortGroupByPathCostToEnemy( self->NPC->group );
- AI_SetClosestBuddy( self->NPC->group );
- }
- void AI_SetNewGroupCommander( AIGroupInfo_t *group )
- {
- gentity_t *member = NULL;
- group->commander = NULL;
- for ( int i = 0; i < group->numGroup; i++ )
- {
- member = &g_entities[group->member[i].number];
- if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) )
- {//keep track of highest rank
- group->commander = member;
- }
- }
- }
- void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum )
- {
- if ( group->commander && group->commander->s.number == group->member[memberNum].number )
- {
- group->commander = NULL;
- }
- if ( g_entities[group->member[memberNum].number].NPC )
- {
- g_entities[group->member[memberNum].number].NPC->group = NULL;
- }
- for ( int i = memberNum; i < (group->numGroup-1); i++ )
- {
- memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) );
- }
- if ( memberNum < group->activeMemberNum )
- {
- group->activeMemberNum--;
- if ( group->activeMemberNum < 0 )
- {
- group->activeMemberNum = 0;
- }
- }
- group->numGroup--;
- if ( group->numGroup < 0 )
- {
- group->numGroup = 0;
- }
- AI_SetNewGroupCommander( group );
- }
- void AI_DeleteSelfFromGroup( gentity_t *self )
- {
- //FIXME: if killed, keep track of how many in group killed? To affect morale?
- for ( int i = 0; i < self->NPC->group->numGroup; i++ )
- {
- if ( self->NPC->group->member[i].number == self->s.number )
- {
- AI_DeleteGroupMember( self->NPC->group, i );
- return;
- }
- }
- }
- extern void ST_AggressionAdjust( gentity_t *self, int change );
- extern void ST_MarkToCover( gentity_t *self );
- extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime );
- void AI_GroupMemberKilled( gentity_t *self )
- {
- /* AIGroupInfo_t *group = self->NPC->group;
- gentity_t *member;
- qboolean noflee = qfalse;
- if ( !group )
- {//what group?
- return;
- }
- if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN )
- {//I'm not an officer, let's not really care for now
- return;
- }
- //temporarily drop group morale for a few seconds
- group->moraleAdjust -= self->NPC->rank;
- //go through and drop aggression on my teammates (more cover, worse aim)
- for ( int i = 0; i < group->numGroup; i++ )
- {
- member = &g_entities[group->member[i].number];
- if ( member == self )
- {
- continue;
- }
- if ( member->NPC->rank > RANK_ENSIGN )
- {//officers do not panic
- noflee = qtrue;
- }
- else
- {
- ST_AggressionAdjust( member, -1 );
- member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy
- }
- }
- //okay, if I'm the group commander, make everyone else flee
- if ( group->commander != self )
- {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me?
- return;
- }
- //now see if there is another of sufficient rank to keep them from fleeing
- if ( !noflee )
- {
- self->NPC->group->speechDebounceTime = 0;
- for ( int i = 0; i < group->numGroup; i++ )
- {
- member = &g_entities[group->member[i].number];
- if ( member == self )
- {
- continue;
- }
- if ( member->NPC->rank < RANK_ENSIGN )
- {//grunt
- if ( group->enemy && DistanceSquared( member->currentOrigin, group->enemy->currentOrigin ) < 65536 )//256*256
- {//those close to enemy run away!
- ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
- }
- else if ( DistanceSquared( member->currentOrigin, self->currentOrigin ) < 65536 )
- {//those close to me run away!
- ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
- }
- else
- {//else, maybe just a random chance
- if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank )
- {//lower rank they are, higher rank I am, more likely they are to flee
- ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
- }
- else
- {
- ST_MarkToCover( member );
- }
- }
- member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
- }
- member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
- }
- }*/
- }
- void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot )
- {
- if ( !group )
- {
- return;
- }
- group->lastSeenEnemyTime = level.time;
- VectorCopy( spot, group->enemyLastSeenPos );
- }
- void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group )
- {
- if ( !group )
- {
- return;
- }
- group->lastClearShotTime = level.time;
- }
- void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState )
- {
- if ( !group )
- {
- member->NPC->squadState = newSquadState;
- return;
- }
- for ( int i = 0; i < group->numGroup; i++ )
- {
- if ( group->member[i].number == member->s.number )
- {
- group->numState[member->NPC->squadState]--;
- member->NPC->squadState = newSquadState;
- group->numState[member->NPC->squadState]++;
- return;
- }
- }
- }
- qboolean AI_RefreshGroup( AIGroupInfo_t *group )
- {
- gentity_t *member;
- int i;//, j;
- //see if we should merge with another group
- for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
- {
- if ( &level.groups[i] == group )
- {
- break;
- }
- else
- {
- if ( level.groups[i].enemy == group->enemy )
- {//2 groups with same enemy
- if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) )
- {//combining the members would fit in one group
- qboolean deleteWhenDone = qtrue;
- //combine the members of mine into theirs
- for ( int j = 0; j < group->numGroup; j++ )
- {
- member = &g_entities[group->member[j].number];
- if ( level.groups[i].enemy == NULL )
- {//special case for groups without enemies, must be in range
- if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) )
- {
- deleteWhenDone = qfalse;
- continue;
- }
- }
- //remove this member from this group
- AI_DeleteGroupMember( group, j );
- //keep marker at same place since we deleted this guy and shifted everyone up one
- j--;
- //add them to the earlier group
- AI_InsertGroupMember( &level.groups[i], member );
- }
- //return and delete this group
- if ( deleteWhenDone )
- {
- return qfalse;
- }
- }
- }
- }
- }
- //clear numStates
- for ( i = 0; i < NUM_SQUAD_STATES; i++ )
- {
- group->numState[i] = 0;
- }
- //go through group and validate each membership
- group->commander = NULL;
- for ( i = 0; i < group->numGroup; i++ )
- {
- /*
- //this checks for duplicate copies of one member in a group
- for ( j = 0; j < group->numGroup; j++ )
- {
- if ( i != j )
- {
- if ( group->member[i].number == group->member[j].number )
- {
- break;
- }
- }
- }
- if ( j < group->numGroup )
- {//found a dupe!
- 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 );
- AI_DeleteGroupMember( group, i );
- i--;
- continue;
- }
- */
- member = &g_entities[group->member[i].number];
- //Must be alive
- if ( member->health <= 0 )
- {
- AI_DeleteGroupMember( group, i );
- //keep marker at same place since we deleted this guy and shifted everyone up one
- i--;
- }
- else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) )
- {
- //remove this one from the group
- AI_DeleteGroupMember( group, i );
- //keep marker at same place since we deleted this guy and shifted everyone up one
- i--;
- }
- else
- {//membership is valid
- //keep track of squadStates
- group->numState[member->NPC->squadState]++;
- if ( !group->commander || member->NPC->rank > group->commander->NPC->rank )
- {//keep track of highest rank
- group->commander = member;
- }
- }
- }
- if ( group->memberValidateTime < level.time )
- {
- group->memberValidateTime = level.time + Q_irand( 500, 2500 );
- }
- //Now add any new guys as long as we're not full
- /*
- for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++)
- {
- if ( !AI_ValidateGroupMember( group, member ) )
- {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
- continue;
- }
- if ( member->NPC->group == group )
- {//DOH, already in our group
- continue;
- }
- //store it
- AI_InsertGroupMember( group, member );
- }
- */
- //calc the morale of this group
- group->morale = group->moraleAdjust;
- for ( i = 0; i < group->numGroup; i++ )
- {
- member = &g_entities[group->member[i].number];
- if ( member->NPC->rank < RANK_ENSIGN )
- {//grunts
- group->morale++;
- }
- else
- {
- group->morale += member->NPC->rank;
- }
- if ( group->commander && debugNPCAI->integer )
- {
- G_DebugLine( group->commander->currentOrigin, member->currentOrigin, FRAMETIME, 0x00ff00ff, qtrue );
- }
- }
- if ( group->enemy )
- {//modify morale based on enemy health and weapon
- if ( group->enemy->health < 10 )
- {
- group->morale += 10;
- }
- else if ( group->enemy->health < 25 )
- {
- group->morale += 5;
- }
- else if ( group->enemy->health < 50 )
- {
- group->morale += 2;
- }
- switch( group->enemy->s.weapon )
- {
- case WP_SABER:
- group->morale -= 5;
- break;
- case WP_BRYAR_PISTOL:
- case WP_BLASTER_PISTOL:
- group->morale += 3;
- break;
- case WP_DISRUPTOR:
- group->morale += 2;
- break;
- case WP_REPEATER:
- group->morale -= 1;
- break;
- case WP_FLECHETTE:
- group->morale -= 2;
- break;
- case WP_ROCKET_LAUNCHER:
- group->morale -= 10;
- break;
- case WP_CONCUSSION:
- group->morale -= 12;
- break;
- case WP_THERMAL:
- group->morale -= 5;
- break;
- case WP_TRIP_MINE:
- group->morale -= 3;
- break;
- case WP_DET_PACK:
- group->morale -= 10;
- break;
- case WP_MELEE: // Any ol' melee attack
- group->morale += 20;
- break;
- case WP_STUN_BATON:
- group->morale += 10;
- break;
- case WP_EMPLACED_GUN:
- group->morale -= 8;
- break;
- case WP_ATST_MAIN:
- group->morale -= 8;
- break;
- case WP_ATST_SIDE:
- group->morale -= 20;
- break;
- }
- }
- if ( group->moraleDebounce < level.time )
- {//slowly degrade whatever moraleAdjusters we may have
- if ( group->moraleAdjust > 0 )
- {
- group->moraleAdjust--;
- }
- else if ( group->moraleAdjust < 0 )
- {
- group->moraleAdjust++;
- }
- group->moraleDebounce = level.time + 1000;//FIXME: define?
- }
- //mark this group as not having been run this frame
- group->processed = qfalse;
- return (group->numGroup>0);
- }
- void AI_UpdateGroups( void )
- {
- if ( d_noGroupAI->integer )
- {
- return;
- }
- //Clear all Groups
- for ( int i = 0; i < MAX_FRAME_GROUPS; i++ )
- {
- if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL ||
- {
- memset( &level.groups[i], 0, sizeof( level.groups[i] ) );
- }
- }
- }
- qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum )
- {
- if ( !group )
- {
- return qfalse;
- }
- for ( int i = 0; i < group->numGroup; i++ )
- {
- if ( group->member[i].number == entNum )
- {
- return qtrue;
- }
- }
- return qfalse;
- }
- //Overload
- /*
- void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius )
- {
- if ( ent->client == NULL )
- return;
- vec3_t temp, angles;
- //FIXME: This is specialized code.. move?
- if ( ent->enemy )
- {
- VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, temp );
- VectorNormalize( temp ); //FIXME: Needed?
- vectoangles( temp, angles );
- }
- else
- {
- VectorCopy( ent->currentAngles, angles );
- }
- AI_GetGroup( group, ent->currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy );
- }
- */
- /*
- -------------------------
- AI_DistributeAttack
- -------------------------
- */
- #define MAX_RADIUS_ENTS 128
- gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold )
- {
- //Don't take new targets
- if ( NPC->svFlags & SVF_LOCKEDENEMY )
- return enemy;
- int numSurrounding = AI_GetGroupSize( enemy->currentOrigin, 48, team, attacker );
- //First, see if we should look for the player
- if ( enemy != &g_entities[0] )
- {
- int aroundPlayer = AI_GetGroupSize( g_entities[0].currentOrigin, 48, team, attacker );
- //See if we're above our threshold
- if ( aroundPlayer < threshold )
- {
- return &g_entities[0];
- }
- }
- //See if our current enemy is still ok
- if ( numSurrounding < threshold )
- return enemy;
- //Otherwise we need to take a new enemy if possible
- vec3_t mins, maxs;
- //Setup the bbox to search in
- for ( int i = 0; i < 3; i++ )
- {
- mins[i] = enemy->currentOrigin[i] - 512;
- maxs[i] = enemy->currentOrigin[i] + 512;
- }
- //Get the number of entities in a given space
- gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
- int numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
- //Cull this list
- for ( int j = 0; j < numEnts; j++ )
- {
- //Validate clients
- if ( radiusEnts[ j ]->client == NULL )
- continue;
- //Skip the requested avoid ent if present
- if ( ( radiusEnts[ j ] == enemy ) )
- continue;
- //Must be on the same team
- if ( radiusEnts[ j ]->client->playerTeam != enemy->client->playerTeam )
- continue;
- //Must be alive
- if ( radiusEnts[ j ]->health <= 0 )
- continue;
- //Must not be overwhelmed
- if ( AI_GetGroupSize( radiusEnts[j]->currentOrigin, 48, team, attacker ) > threshold )
- continue;
- return radiusEnts[j];
- }
- return NULL;
- }
|