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_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); }
//#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 ); }