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