1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 // These utilities are meant for strictly non-player, non-team NPCs.
24 // These functions are in their own file because they are only intended
25 // for use with NPCs who's logic has been overriden from the original
26 // AI code, and who's code resides in files with the AI_ prefix.
27
28 #include "g_headers.h"
29
30 #include "b_local.h"
31 #include "g_nav.h"
32 #include "g_navigator.h"
33
34 #define MAX_RADIUS_ENTS 128
35 #define DEFAULT_RADIUS 45
36
37 extern CNavigator navigator;
38 extern cvar_t *d_noGroupAI;
39 qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member );
40
41 /*
42 -------------------------
43 AI_GetGroupSize
44 -------------------------
45 */
46
AI_GetGroupSize(vec3_t origin,int radius,team_t playerTeam,gentity_t * avoid)47 int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid )
48 {
49 gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
50 vec3_t mins, maxs;
51 int numEnts, realCount = 0;
52
53 //Setup the bbox to search in
54 for ( int i = 0; i < 3; i++ )
55 {
56 mins[i] = origin[i] - radius;
57 maxs[i] = origin[i] + radius;
58 }
59
60 //Get the number of entities in a given space
61 numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
62
63 //Cull this list
64 for ( int j = 0; j < numEnts; j++ )
65 {
66 //Validate clients
67 if ( radiusEnts[ j ]->client == NULL )
68 continue;
69
70 //Skip the requested avoid ent if present
71 if ( ( avoid != NULL ) && ( radiusEnts[ j ] == avoid ) )
72 continue;
73
74 //Must be on the same team
75 if ( radiusEnts[ j ]->client->playerTeam != playerTeam )
76 continue;
77
78 //Must be alive
79 if ( radiusEnts[ j ]->health <= 0 )
80 continue;
81
82 realCount++;
83 }
84
85 return realCount;
86 }
87
88 //Overload
89
AI_GetGroupSize(gentity_t * ent,int radius)90 int AI_GetGroupSize( gentity_t *ent, int radius )
91 {
92 if ( ( ent == NULL ) || ( ent->client == NULL ) )
93 return -1;
94
95 return AI_GetGroupSize( ent->currentOrigin, radius, ent->client->playerTeam, ent );
96 }
97
AI_SetClosestBuddy(AIGroupInfo_t * group)98 void AI_SetClosestBuddy( AIGroupInfo_t *group )
99 {
100 int i, j;
101 int dist, bestDist;
102
103 for ( i = 0; i < group->numGroup; i++ )
104 {
105 group->member[i].closestBuddy = ENTITYNUM_NONE;
106
107 bestDist = Q3_INFINITE;
108 for ( j = 0; j < group->numGroup; j++ )
109 {
110 dist = DistanceSquared( g_entities[group->member[i].number].currentOrigin, g_entities[group->member[j].number].currentOrigin );
111 if ( dist < bestDist )
112 {
113 bestDist = dist;
114 group->member[i].closestBuddy = group->member[j].number;
115 }
116 }
117 }
118 }
119
AI_SortGroupByPathCostToEnemy(AIGroupInfo_t * group)120 void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group )
121 {
122 AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS];
123 int i, j, k;
124 qboolean sort = qfalse;
125
126 if ( group->enemy != NULL )
127 {//FIXME: just use enemy->waypoint?
128 group->enemyWP = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
129 }
130 else
131 {
132 group->enemyWP = WAYPOINT_NONE;
133 }
134
135 for ( i = 0; i < group->numGroup; i++ )
136 {
137 if ( group->enemyWP == WAYPOINT_NONE )
138 {//FIXME: just use member->waypoint?
139 group->member[i].waypoint = WAYPOINT_NONE;
140 group->member[i].pathCostToEnemy = Q3_INFINITE;
141 }
142 else
143 {//FIXME: just use member->waypoint?
144 group->member[i].waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
145 if ( group->member[i].waypoint != WAYPOINT_NONE )
146 {
147 group->member[i].pathCostToEnemy = navigator.GetPathCost( group->member[i].waypoint, group->enemyWP );
148 //at least one of us has a path, so do sorting
149 sort = qtrue;
150 }
151 else
152 {
153 group->member[i].pathCostToEnemy = Q3_INFINITE;
154 }
155 }
156 }
157 //Now sort
158 if ( sort )
159 {
160 //initialize bestMembers data
161 for ( j = 0; j < group->numGroup; j++ )
162 {
163 bestMembers[j].number = ENTITYNUM_NONE;
164 }
165
166 for ( i = 0; i < group->numGroup; i++ )
167 {
168 for ( j = 0; j < group->numGroup; j++ )
169 {
170 if ( bestMembers[j].number != ENTITYNUM_NONE )
171 {//slot occupied
172 if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy )
173 {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here
174 for ( k = group->numGroup; k > j; k++ )
175 {
176 memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) );
177 }
178 memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
179 break;
180 }
181 }
182 else
183 {//slot unoccupied, reached end of list, throw self in here
184 memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) );
185 break;
186 }
187 }
188 }
189
190 //Okay, now bestMembers is a sorted list, just copy it into group->members
191 for ( i = 0; i < group->numGroup; i++ )
192 {
193 memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) );
194 }
195 }
196 }
197
AI_FindSelfInPreviousGroup(gentity_t * self)198 qboolean AI_FindSelfInPreviousGroup( gentity_t *self )
199 {//go through other groups made this frame and see if any of those contain me already
200 int i, j;
201 for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
202 {
203 if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL )
204 {//check this one
205 for ( j = 0; j < level.groups[i].numGroup; j++ )
206 {
207 if ( level.groups[i].member[j].number == self->s.number )
208 {
209 self->NPC->group = &level.groups[i];
210 return qtrue;
211 }
212 }
213 }
214 }
215 return qfalse;
216 }
217
AI_InsertGroupMember(AIGroupInfo_t * group,gentity_t * member)218 void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member )
219 {
220 //okay, you know what? Check this damn group and make sure we're not already in here!
221 int i;
222 for ( i = 0; i < group->numGroup; i++ )
223 {
224 if ( group->member[i].number == member->s.number )
225 {//already in here
226 break;
227 }
228 }
229 if ( i < group->numGroup )
230 {//found him in group already
231 }
232 else
233 {//add him in
234 group->member[group->numGroup++].number = member->s.number;
235 group->numState[member->NPC->squadState]++;
236 }
237 if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) )
238 {//keep track of highest rank
239 group->commander = member;
240 }
241 member->NPC->group = group;
242 }
243
AI_TryJoinPreviousGroup(gentity_t * self)244 qboolean AI_TryJoinPreviousGroup( gentity_t *self )
245 {//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in!
246 int i;
247 for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
248 {
249 if ( level.groups[i].numGroup
250 && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1)
251 //&& level.groups[i].enemy != NULL
252 && level.groups[i].enemy == self->enemy )
253 {//has members, not full and has my enemy
254 if ( AI_ValidateGroupMember( &level.groups[i], self ) )
255 {//I am a valid member for this group
256 AI_InsertGroupMember( &level.groups[i], self );
257 return qtrue;
258 }
259 }
260 }
261 return qfalse;
262 }
263
AI_GetNextEmptyGroup(gentity_t * self)264 qboolean AI_GetNextEmptyGroup( gentity_t *self )
265 {
266 if ( AI_FindSelfInPreviousGroup( self ) )
267 {//already in one, no need to make a new one
268 return qfalse;
269 }
270
271 if ( AI_TryJoinPreviousGroup( self ) )
272 {//try to just put us in one that already exists
273 return qfalse;
274 }
275
276 //okay, make a whole new one, then
277 for ( int i = 0; i < MAX_FRAME_GROUPS; i++ )
278 {
279 if ( !level.groups[i].numGroup )
280 {//make a new one
281 self->NPC->group = &level.groups[i];
282 return qtrue;
283 }
284 }
285
286 //if ( i >= MAX_FRAME_GROUPS )
287 {//WTF? Out of groups!
288 self->NPC->group = NULL;
289 return qfalse;
290 }
291 }
292
AI_ValidateNoEnemyGroupMember(AIGroupInfo_t * group,gentity_t * member)293 qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member )
294 {
295 if ( !group )
296 {
297 return qfalse;
298 }
299 vec3_t center;
300 if ( group->commander )
301 {
302 VectorCopy( group->commander->currentOrigin, center );
303 }
304 else
305 {//hmm, just pick the first member
306 if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD )
307 {
308 return qfalse;
309 }
310 VectorCopy( g_entities[group->member[0].number].currentOrigin, center );
311 }
312 //FIXME: maybe it should be based on the center of the mass of the group, not the commander?
313 if ( DistanceSquared( center, member->currentOrigin ) > 147456/*384*384*/ )
314 {
315 return qfalse;
316 }
317 if ( !gi.inPVS( member->currentOrigin, center ) )
318 {//not within PVS of the group enemy
319 return qfalse;
320 }
321 return qtrue;
322 }
323
AI_ValidateGroupMember(AIGroupInfo_t * group,gentity_t * member)324 qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member )
325 {
326 //Validate ents
327 if ( member == NULL )
328 return qfalse;
329
330 //Validate clients
331 if ( member->client == NULL )
332 return qfalse;
333
334 //Validate NPCs
335 if ( member->NPC == NULL )
336 return qfalse;
337
338 //must be aware
339 if ( member->NPC->confusionTime > level.time )
340 return qfalse;
341
342 //must be allowed to join groups
343 if ( member->NPC->scriptFlags&SCF_NO_GROUPS )
344 return qfalse;
345
346 //Must not be in another group
347 if ( member->NPC->group != NULL && member->NPC->group != group )
348 {//FIXME: if that group's enemy is mine, why not absorb that group into mine?
349 return qfalse;
350 }
351
352 //Must be alive
353 if ( member->health <= 0 )
354 return qfalse;
355
356 //can't be in an emplaced gun
357 if( member->s.eFlags & EF_LOCKED_TO_WEAPON )
358 return qfalse;
359
360 //Must be on the same team
361 if ( member->client->playerTeam != group->team )
362 return qfalse;
363
364 if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon )
365 member->client->ps.weapon == WP_THERMAL ||
366 member->client->ps.weapon == WP_DISRUPTOR ||
367 member->client->ps.weapon == WP_EMPLACED_GUN ||
368 member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast
369 member->client->ps.weapon == WP_MELEE ||
370 member->client->ps.weapon == WP_TURRET || // turret guns
371 member->client->ps.weapon == WP_ATST_MAIN ||
372 member->client->ps.weapon == WP_ATST_SIDE ||
373 member->client->ps.weapon == WP_TIE_FIGHTER )
374 {//not really a squad-type guy
375 return qfalse;
376 }
377
378 if ( member->client->NPC_class == CLASS_ATST ||
379 member->client->NPC_class == CLASS_PROBE ||
380 member->client->NPC_class == CLASS_SEEKER ||
381 member->client->NPC_class == CLASS_REMOTE ||
382 member->client->NPC_class == CLASS_SENTRY ||
383 member->client->NPC_class == CLASS_INTERROGATOR ||
384 member->client->NPC_class == CLASS_MINEMONSTER ||
385 member->client->NPC_class == CLASS_HOWLER ||
386 member->client->NPC_class == CLASS_MARK1 ||
387 member->client->NPC_class == CLASS_MARK2 )
388 {//these kinds of enemies don't actually use this group AI
389 return qfalse;
390 }
391
392 //should have same enemy
393 if ( member->enemy != group->enemy )
394 {
395 if ( member->enemy != NULL )
396 {//he's fighting someone else, leave him out
397 return qfalse;
398 }
399 if ( !gi.inPVS( member->currentOrigin, group->enemy->currentOrigin ) )
400 {//not within PVS of the group enemy
401 return qfalse;
402 }
403 }
404 else if ( group->enemy == NULL )
405 {//if the group is a patrol group, only take those within the room and radius
406 if ( !AI_ValidateNoEnemyGroupMember( group, member ) )
407 {
408 return qfalse;
409 }
410 }
411 //must be actually in combat mode
412 if ( !TIMER_Done( member, "interrogating" ) )
413 return qfalse;
414 //FIXME: need to have a route to enemy and/or clear shot?
415 return qtrue;
416 }
417
418 /*
419 -------------------------
420 AI_GetGroup
421 -------------------------
422 */
423 //#define MAX_WAITERS 128
AI_GetGroup(gentity_t * self)424 void AI_GetGroup( gentity_t *self )
425 {
426 int i;
427 gentity_t *member;//, *waiter;
428 //int waiters[MAX_WAITERS];
429
430 if ( !self || !self->NPC )
431 {
432 return;
433 }
434
435 if ( d_noGroupAI->integer )
436 {
437 self->NPC->group = NULL;
438 return;
439 }
440
441 if ( !self->client )
442 {
443 self->NPC->group = NULL;
444 return;
445 }
446
447 if ( self->NPC->scriptFlags&SCF_NO_GROUPS )
448 {
449 self->NPC->group = NULL;
450 return;
451 }
452
453 if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 )))
454 {
455 self->NPC->group = NULL;
456 return;
457 }
458
459 if ( !AI_GetNextEmptyGroup( self ) )
460 {//either no more groups left or we're already in a group built earlier
461 return;
462 }
463
464 //create a new one
465 memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) );
466
467 self->NPC->group->enemy = self->enemy;
468 self->NPC->group->team = self->client->playerTeam;
469 self->NPC->group->processed = qfalse;
470 self->NPC->group->commander = self;
471 self->NPC->group->memberValidateTime = level.time + 2000;
472 self->NPC->group->activeMemberNum = 0;
473
474 if ( self->NPC->group->enemy )
475 {
476 self->NPC->group->lastSeenEnemyTime = level.time;
477 self->NPC->group->lastClearShotTime = level.time;
478 VectorCopy( self->NPC->group->enemy->currentOrigin, self->NPC->group->enemyLastSeenPos );
479 }
480
481 // for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++)
482 for ( i = 0; i < globals.num_entities ; i++)
483 {
484 if(!PInUse(i))
485 continue;
486 member = &g_entities[i];
487
488 if ( !AI_ValidateGroupMember( self->NPC->group, member ) )
489 {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
490 continue;
491 }
492
493 //store it
494 AI_InsertGroupMember( self->NPC->group, member );
495
496 if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) )
497 {//full
498 break;
499 }
500 }
501
502 /*
503 //now go through waiters and see if any should join the group
504 //NOTE: Some should hang back and probably not attack, so we can ambush
505 //NOTE: only do this if calling for reinforcements?
506 for ( i = 0; i < numWaiters; i++ )
507 {
508 waiter = &g_entities[waiters[i]];
509
510 for ( j = 0; j < self->NPC->group->numGroup; j++ )
511 {
512 member = &g_entities[self->NPC->group->member[j];
513
514 if ( gi.inPVS( waiter->currentOrigin, member->currentOrigin ) )
515 {//this waiter is within PVS of a current member
516 }
517 }
518 }
519 */
520
521 if ( self->NPC->group->numGroup <= 0 )
522 {//none in group
523 self->NPC->group = NULL;
524 return;
525 }
526
527 AI_SortGroupByPathCostToEnemy( self->NPC->group );
528 AI_SetClosestBuddy( self->NPC->group );
529 }
530
AI_SetNewGroupCommander(AIGroupInfo_t * group)531 void AI_SetNewGroupCommander( AIGroupInfo_t *group )
532 {
533 gentity_t *member = NULL;
534 group->commander = NULL;
535 for ( int i = 0; i < group->numGroup; i++ )
536 {
537 member = &g_entities[group->member[i].number];
538
539 if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) )
540 {//keep track of highest rank
541 group->commander = member;
542 }
543 }
544 }
545
AI_DeleteGroupMember(AIGroupInfo_t * group,int memberNum)546 void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum )
547 {
548 if ( group->commander && group->commander->s.number == group->member[memberNum].number )
549 {
550 group->commander = NULL;
551 }
552 if ( g_entities[group->member[memberNum].number].NPC )
553 {
554 g_entities[group->member[memberNum].number].NPC->group = NULL;
555 }
556 for ( int i = memberNum; i < (group->numGroup-1); i++ )
557 {
558 memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) );
559 }
560 if ( memberNum < group->activeMemberNum )
561 {
562 group->activeMemberNum--;
563 if ( group->activeMemberNum < 0 )
564 {
565 group->activeMemberNum = 0;
566 }
567 }
568 group->numGroup--;
569 if ( group->numGroup < 0 )
570 {
571 group->numGroup = 0;
572 }
573 AI_SetNewGroupCommander( group );
574 }
575
AI_DeleteSelfFromGroup(gentity_t * self)576 void AI_DeleteSelfFromGroup( gentity_t *self )
577 {
578 //FIXME: if killed, keep track of how many in group killed? To affect morale?
579 for ( int i = 0; i < self->NPC->group->numGroup; i++ )
580 {
581 if ( self->NPC->group->member[i].number == self->s.number )
582 {
583 AI_DeleteGroupMember( self->NPC->group, i );
584 return;
585 }
586 }
587 }
588
589 extern void ST_AggressionAdjust( gentity_t *self, int change );
590 extern void ST_MarkToCover( gentity_t *self );
591 extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime );
AI_GroupMemberKilled(gentity_t * self)592 void AI_GroupMemberKilled( gentity_t *self )
593 {
594 AIGroupInfo_t *group = self->NPC->group;
595 gentity_t *member;
596 qboolean noflee = qfalse;
597
598 if ( !group )
599 {//what group?
600 return;
601 }
602 if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN )
603 {//I'm not an officer, let's not really care for now
604 return;
605 }
606 //temporarily drop group morale for a few seconds
607 group->moraleAdjust -= self->NPC->rank;
608 //go through and drop aggression on my teammates (more cover, worse aim)
609 for ( int i = 0; i < group->numGroup; i++ )
610 {
611 member = &g_entities[group->member[i].number];
612 if ( member == self )
613 {
614 continue;
615 }
616 if ( member->NPC->rank > RANK_ENSIGN )
617 {//officers do not panic
618 noflee = qtrue;
619 }
620 else
621 {
622 ST_AggressionAdjust( member, -1 );
623 member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy
624 }
625 }
626 //okay, if I'm the group commander, make everyone else flee
627 if ( group->commander != self )
628 {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me?
629 return;
630 }
631 //now see if there is another of sufficient rank to keep them from fleeing
632 if ( !noflee )
633 {
634 self->NPC->group->speechDebounceTime = 0;
635 for ( int i = 0; i < group->numGroup; i++ )
636 {
637 member = &g_entities[group->member[i].number];
638 if ( member == self )
639 {
640 continue;
641 }
642 if ( member->NPC->rank < RANK_ENSIGN )
643 {//grunt
644 if ( group->enemy && DistanceSquared( member->currentOrigin, group->enemy->currentOrigin ) < 65536/*256*256*/ )
645 {//those close to enemy run away!
646 ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
647 }
648 else if ( DistanceSquared( member->currentOrigin, self->currentOrigin ) < 65536/*256*256*/ )
649 {//those close to me run away!
650 ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
651 }
652 else
653 {//else, maybe just a random chance
654 if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank )
655 {//lower rank they are, higher rank I am, more likely they are to flee
656 ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
657 }
658 else
659 {
660 ST_MarkToCover( member );
661 }
662 }
663 member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
664 }
665 member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more
666 }
667 }
668 }
669
AI_GroupUpdateEnemyLastSeen(AIGroupInfo_t * group,vec3_t spot)670 void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot )
671 {
672 if ( !group )
673 {
674 return;
675 }
676 group->lastSeenEnemyTime = level.time;
677 VectorCopy( spot, group->enemyLastSeenPos );
678 }
679
AI_GroupUpdateClearShotTime(AIGroupInfo_t * group)680 void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group )
681 {
682 if ( !group )
683 {
684 return;
685 }
686 group->lastClearShotTime = level.time;
687 }
688
AI_GroupUpdateSquadstates(AIGroupInfo_t * group,gentity_t * member,int newSquadState)689 void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState )
690 {
691 if ( !group )
692 {
693 member->NPC->squadState = newSquadState;
694 return;
695 }
696
697 for ( int i = 0; i < group->numGroup; i++ )
698 {
699 if ( group->member[i].number == member->s.number )
700 {
701 group->numState[member->NPC->squadState]--;
702 member->NPC->squadState = newSquadState;
703 group->numState[member->NPC->squadState]++;
704 return;
705 }
706 }
707 }
708
AI_RefreshGroup(AIGroupInfo_t * group)709 qboolean AI_RefreshGroup( AIGroupInfo_t *group )
710 {
711 gentity_t *member;
712 int i;//, j;
713
714 //see if we should merge with another group
715 for ( i = 0; i < MAX_FRAME_GROUPS; i++ )
716 {
717 if ( &level.groups[i] == group )
718 {
719 break;
720 }
721 else
722 {
723 if ( level.groups[i].enemy == group->enemy )
724 {//2 groups with same enemy
725 if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) )
726 {//combining the members would fit in one group
727 qboolean deleteWhenDone = qtrue;
728 //combine the members of mine into theirs
729 for ( int j = 0; j < group->numGroup; j++ )
730 {
731 member = &g_entities[group->member[j].number];
732 if ( level.groups[i].enemy == NULL )
733 {//special case for groups without enemies, must be in range
734 if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) )
735 {
736 deleteWhenDone = qfalse;
737 continue;
738 }
739 }
740 //remove this member from this group
741 AI_DeleteGroupMember( group, j );
742 //keep marker at same place since we deleted this guy and shifted everyone up one
743 j--;
744 //add them to the earlier group
745 AI_InsertGroupMember( &level.groups[i], member );
746 }
747 //return and delete this group
748 if ( deleteWhenDone )
749 {
750 return qfalse;
751 }
752 }
753 }
754 }
755 }
756 //clear numStates
757 for ( i = 0; i < NUM_SQUAD_STATES; i++ )
758 {
759 group->numState[i] = 0;
760 }
761
762 //go through group and validate each membership
763 group->commander = NULL;
764 for ( i = 0; i < group->numGroup; i++ )
765 {
766 /*
767 //this checks for duplicate copies of one member in a group
768 for ( j = 0; j < group->numGroup; j++ )
769 {
770 if ( i != j )
771 {
772 if ( group->member[i].number == group->member[j].number )
773 {
774 break;
775 }
776 }
777 }
778 if ( j < group->numGroup )
779 {//found a dupe!
780 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 );
781 AI_DeleteGroupMember( group, i );
782 i--;
783 continue;
784 }
785 */
786 member = &g_entities[group->member[i].number];
787
788 //Must be alive
789 if ( member->health <= 0 )
790 {
791 AI_DeleteGroupMember( group, i );
792 //keep marker at same place since we deleted this guy and shifted everyone up one
793 i--;
794 }
795 else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) )
796 {
797 //remove this one from the group
798 AI_DeleteGroupMember( group, i );
799 //keep marker at same place since we deleted this guy and shifted everyone up one
800 i--;
801 }
802 else
803 {//membership is valid
804 //keep track of squadStates
805 group->numState[member->NPC->squadState]++;
806 if ( !group->commander || member->NPC->rank > group->commander->NPC->rank )
807 {//keep track of highest rank
808 group->commander = member;
809 }
810 }
811 }
812 if ( group->memberValidateTime < level.time )
813 {
814 group->memberValidateTime = level.time + Q_irand( 500, 2500 );
815 }
816 //Now add any new guys as long as we're not full
817 /*
818 for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++)
819 {
820 if ( !AI_ValidateGroupMember( group, member ) )
821 {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group
822 continue;
823 }
824 if ( member->NPC->group == group )
825 {//DOH, already in our group
826 continue;
827 }
828
829 //store it
830 AI_InsertGroupMember( group, member );
831 }
832 */
833
834 //calc the morale of this group
835 group->morale = group->moraleAdjust;
836 for ( i = 0; i < group->numGroup; i++ )
837 {
838 member = &g_entities[group->member[i].number];
839 if ( member->NPC->rank < RANK_ENSIGN )
840 {//grunts
841 group->morale++;
842 }
843 else
844 {
845 group->morale += member->NPC->rank;
846 }
847 if ( group->commander && debugNPCAI->integer )
848 {
849 G_DebugLine( group->commander->currentOrigin, member->currentOrigin, FRAMETIME, 0x00ff00ff, qtrue );
850 }
851 }
852 if ( group->enemy )
853 {//modify morale based on enemy health and weapon
854 if ( group->enemy->health < 10 )
855 {
856 group->morale += 10;
857 }
858 else if ( group->enemy->health < 25 )
859 {
860 group->morale += 5;
861 }
862 else if ( group->enemy->health < 50 )
863 {
864 group->morale += 2;
865 }
866 switch( group->enemy->s.weapon )
867 {
868 case WP_SABER:
869 group->morale -= 5;
870 break;
871 case WP_BRYAR_PISTOL:
872 group->morale += 3;
873 break;
874 case WP_DISRUPTOR:
875 group->morale += 2;
876 break;
877 case WP_REPEATER:
878 group->morale -= 1;
879 break;
880 case WP_FLECHETTE:
881 group->morale -= 2;
882 break;
883 case WP_ROCKET_LAUNCHER:
884 group->morale -= 10;
885 break;
886 case WP_THERMAL:
887 group->morale -= 5;
888 break;
889 case WP_TRIP_MINE:
890 group->morale -= 3;
891 break;
892 case WP_DET_PACK:
893 group->morale -= 10;
894 break;
895 case WP_MELEE: // Any ol' melee attack
896 group->morale += 20;
897 break;
898 case WP_STUN_BATON:
899 group->morale += 10;
900 break;
901 case WP_EMPLACED_GUN:
902 group->morale -= 8;
903 break;
904 case WP_ATST_MAIN:
905 group->morale -= 8;
906 break;
907 case WP_ATST_SIDE:
908 group->morale -= 20;
909 break;
910 }
911 }
912 if ( group->moraleDebounce < level.time )
913 {//slowly degrade whatever moraleAdjusters we may have
914 if ( group->moraleAdjust > 0 )
915 {
916 group->moraleAdjust--;
917 }
918 else if ( group->moraleAdjust < 0 )
919 {
920 group->moraleAdjust++;
921 }
922 group->moraleDebounce = level.time + 1000;//FIXME: define?
923 }
924 //mark this group as not having been run this frame
925 group->processed = qfalse;
926
927 return (qboolean)(group->numGroup > 0);
928 }
929
AI_UpdateGroups(void)930 void AI_UpdateGroups( void )
931 {
932 if ( d_noGroupAI->integer )
933 {
934 return;
935 }
936 //Clear all Groups
937 for ( int i = 0; i < MAX_FRAME_GROUPS; i++ )
938 {
939 if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL ||
940 {
941 memset( &level.groups[i], 0, sizeof( level.groups[i] ) );
942 }
943 }
944 }
945
AI_GroupContainsEntNum(AIGroupInfo_t * group,int entNum)946 qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum )
947 {
948 if ( !group )
949 {
950 return qfalse;
951 }
952 for ( int i = 0; i < group->numGroup; i++ )
953 {
954 if ( group->member[i].number == entNum )
955 {
956 return qtrue;
957 }
958 }
959 return qfalse;
960 }
961 //Overload
962
963 /*
964 void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius )
965 {
966 if ( ent->client == NULL )
967 return;
968
969 vec3_t temp, angles;
970
971 //FIXME: This is specialized code.. move?
972 if ( ent->enemy )
973 {
974 VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, temp );
975 VectorNormalize( temp ); //FIXME: Needed?
976 vectoangles( temp, angles );
977 }
978 else
979 {
980 VectorCopy( ent->currentAngles, angles );
981 }
982
983 AI_GetGroup( group, ent->currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy );
984 }
985 */
986 /*
987 -------------------------
988 AI_CheckEnemyCollision
989 -------------------------
990 */
991
AI_CheckEnemyCollision(gentity_t * ent,qboolean takeEnemy)992 qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy )
993 {
994 if ( ent == NULL )
995 return qfalse;
996
997 if ( ent->svFlags & SVF_LOCKEDENEMY )
998 return qfalse;
999
1000 navInfo_t info;
1001
1002 NAV_GetLastMove( info );
1003
1004 //See if we've hit something
1005 if ( ( info.blocker ) && ( info.blocker != ent->enemy ) )
1006 {
1007 if ( ( info.blocker->client ) && ( info.blocker->client->playerTeam == ent->client->enemyTeam ) )
1008 {
1009 if ( takeEnemy )
1010 G_SetEnemy( ent, info.blocker );
1011
1012 return qtrue;
1013 }
1014 }
1015
1016 return qfalse;
1017 }
1018
1019 /*
1020 -------------------------
1021 AI_DistributeAttack
1022 -------------------------
1023 */
1024
1025 #define MAX_RADIUS_ENTS 128
1026
AI_DistributeAttack(gentity_t * attacker,gentity_t * enemy,team_t team,int threshold)1027 gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold )
1028 {
1029 //Don't take new targets
1030 if ( NPC->svFlags & SVF_LOCKEDENEMY )
1031 return enemy;
1032
1033 int numSurrounding = AI_GetGroupSize( enemy->currentOrigin, 48, team, attacker );
1034
1035 //First, see if we should look for the player
1036 if ( enemy != &g_entities[0] )
1037 {
1038 int aroundPlayer = AI_GetGroupSize( g_entities[0].currentOrigin, 48, team, attacker );
1039
1040 //See if we're above our threshold
1041 if ( aroundPlayer < threshold )
1042 {
1043 return &g_entities[0];
1044 }
1045 }
1046
1047 //See if our current enemy is still ok
1048 if ( numSurrounding < threshold )
1049 return enemy;
1050
1051 //Otherwise we need to take a new enemy if possible
1052 vec3_t mins, maxs;
1053
1054 //Setup the bbox to search in
1055 for ( int i = 0; i < 3; i++ )
1056 {
1057 mins[i] = enemy->currentOrigin[i] - 512;
1058 maxs[i] = enemy->currentOrigin[i] + 512;
1059 }
1060
1061 //Get the number of entities in a given space
1062 gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
1063
1064 int numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
1065
1066 //Cull this list
1067 for ( int j = 0; j < numEnts; j++ )
1068 {
1069 //Validate clients
1070 if ( radiusEnts[ j ]->client == NULL )
1071 continue;
1072
1073 //Skip the requested avoid ent if present
1074 if ( radiusEnts[ j ] == enemy )
1075 continue;
1076
1077 //Must be on the same team
1078 if ( radiusEnts[ j ]->client->playerTeam != enemy->client->playerTeam )
1079 continue;
1080
1081 //Must be alive
1082 if ( radiusEnts[ j ]->health <= 0 )
1083 continue;
1084
1085 //Must not be overwhelmed
1086 if ( AI_GetGroupSize( radiusEnts[j]->currentOrigin, 48, team, attacker ) > threshold )
1087 continue;
1088
1089 return radiusEnts[j];
1090 }
1091
1092 return NULL;
1093 }
1094