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