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