1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // g_ai.c
21 
22 #include "g_local.h"
23 
24 qboolean FindTarget (edict_t *self);
25 extern cvar_t	*maxclients;
26 
27 qboolean ai_checkattack (edict_t *self, float dist);
28 
29 qboolean	enemy_vis;
30 qboolean	enemy_infront;
31 int			enemy_range;
32 float		enemy_yaw;
33 
34 //============================================================================
35 
36 
37 /*
38 =================
39 AI_SetSightClient
40 
41 Called once each frame to set level.sight_client to the
42 player to be checked for in findtarget.
43 
44 If all clients are either dead or in notarget, sight_client
45 will be null.
46 
47 In coop games, sight_client will cycle between the clients.
48 =================
49 */
AI_SetSightClient(void)50 void AI_SetSightClient (void)
51 {
52 	edict_t	*ent;
53 	int		start, check;
54 
55 	if (level.sight_client == NULL)
56 		start = 1;
57 	else
58 		start = level.sight_client - g_edicts;
59 
60 	check = start;
61 	while (1)
62 	{
63 		check++;
64 		if (check > game.maxclients)
65 			check = 1;
66 		ent = &g_edicts[check];
67 		if (ent->inuse
68 			&& ent->health > 0
69 			&& !(ent->flags & FL_NOTARGET) )
70 		{
71 			level.sight_client = ent;
72 			return;		// got one
73 		}
74 		if (check == start)
75 		{
76 			level.sight_client = NULL;
77 			return;		// nobody to see
78 		}
79 	}
80 }
81 
82 //============================================================================
83 
84 /*
85 =============
86 ai_move
87 
88 Move the specified distance at current facing.
89 This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
90 ==============
91 */
ai_move(edict_t * self,float dist)92 void ai_move (edict_t *self, float dist)
93 {
94 	M_walkmove (self, self->s.angles[YAW], dist);
95 }
96 
97 
98 /*
99 =============
100 ai_stand
101 
102 Used for standing around and looking for players
103 Distance is for slight position adjustments needed by the animations
104 ==============
105 */
ai_stand(edict_t * self,float dist)106 void ai_stand (edict_t *self, float dist)
107 {
108 	vec3_t	v;
109 
110 	if (dist)
111 		M_walkmove (self, self->s.angles[YAW], dist);
112 
113 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
114 	{
115 		if (self->enemy)
116 		{
117 			VectorSubtract (self->enemy->s.origin, self->s.origin, v);
118 			self->ideal_yaw = vectoyaw(v);
119 			if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
120 			{
121 				self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
122 				self->monsterinfo.run (self);
123 			}
124 			M_ChangeYaw (self);
125 			ai_checkattack (self, 0);
126 		}
127 		else
128 			FindTarget (self);
129 		return;
130 	}
131 
132 	if (FindTarget (self))
133 		return;
134 
135 	if (level.time > self->monsterinfo.pausetime)
136 	{
137 		self->monsterinfo.walk (self);
138 		return;
139 	}
140 
141 	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
142 	{
143 		if (self->monsterinfo.idle_time)
144 		{
145 			self->monsterinfo.idle (self);
146 			self->monsterinfo.idle_time = level.time + 15 + random() * 15;
147 		}
148 		else
149 		{
150 			self->monsterinfo.idle_time = level.time + random() * 15;
151 		}
152 	}
153 }
154 
155 
156 /*
157 =============
158 ai_walk
159 
160 The monster is walking it's beat
161 =============
162 */
ai_walk(edict_t * self,float dist)163 void ai_walk (edict_t *self, float dist)
164 {
165 	M_MoveToGoal (self, dist);
166 
167 	// check for noticing a player
168 	if (FindTarget (self))
169 		return;
170 
171 	if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
172 	{
173 		if (self->monsterinfo.idle_time)
174 		{
175 			self->monsterinfo.search (self);
176 			self->monsterinfo.idle_time = level.time + 15 + random() * 15;
177 		}
178 		else
179 		{
180 			self->monsterinfo.idle_time = level.time + random() * 15;
181 		}
182 	}
183 }
184 
185 
186 /*
187 =============
188 ai_charge
189 
190 Turns towards target and advances
191 Use this call with a distnace of 0 to replace ai_face
192 ==============
193 */
ai_charge(edict_t * self,float dist)194 void ai_charge (edict_t *self, float dist)
195 {
196 	vec3_t	v;
197 
198 	VectorSubtract (self->enemy->s.origin, self->s.origin, v);
199 	self->ideal_yaw = vectoyaw(v);
200 	M_ChangeYaw (self);
201 
202 	if (dist)
203 		M_walkmove (self, self->s.angles[YAW], dist);
204 }
205 
206 
207 /*
208 =============
209 ai_turn
210 
211 don't move, but turn towards ideal_yaw
212 Distance is for slight position adjustments needed by the animations
213 =============
214 */
ai_turn(edict_t * self,float dist)215 void ai_turn (edict_t *self, float dist)
216 {
217 	if (dist)
218 		M_walkmove (self, self->s.angles[YAW], dist);
219 
220 	if (FindTarget (self))
221 		return;
222 
223 	M_ChangeYaw (self);
224 }
225 
226 
227 /*
228 
229 .enemy
230 Will be world if not currently angry at anyone.
231 
232 .movetarget
233 The next path spot to walk toward.  If .enemy, ignore .movetarget.
234 When an enemy is killed, the monster will try to return to it's path.
235 
236 .hunt_time
237 Set to time + something when the player is in sight, but movement straight for
238 him is blocked.  This causes the monster to use wall following code for
239 movement direction instead of sighting on the player.
240 
241 .ideal_yaw
242 A yaw angle of the intended direction, which will be turned towards at up
243 to 45 deg / state.  If the enemy is in view and hunt_time is not active,
244 this will be the exact line towards the enemy.
245 
246 .pausetime
247 A monster will leave it's stand state and head towards it's .movetarget when
248 time > .pausetime.
249 
250 walkmove(angle, speed) primitive is all or nothing
251 */
252 
253 /*
254 =============
255 range
256 
257 returns the range catagorization of an entity reletive to self
258 0	melee range, will become hostile even if back is turned
259 1	visibility and infront, or visibility and show hostile
260 2	infront and show hostile
261 3	only triggered by damage
262 =============
263 */
range(edict_t * self,edict_t * other)264 int range (edict_t *self, edict_t *other)
265 {
266 	vec3_t	v;
267 	float	len;
268 
269 	VectorSubtract (self->s.origin, other->s.origin, v);
270 	len = VectorLength (v);
271 	if (len < MELEE_DISTANCE)
272 		return RANGE_MELEE;
273 	if (len < 500)
274 		return RANGE_NEAR;
275 	if (len < 1000)
276 		return RANGE_MID;
277 	return RANGE_FAR;
278 }
279 
280 /*
281 =============
282 visible
283 
284 returns 1 if the entity is visible to self, even if not infront ()
285 =============
286 */
visible(edict_t * self,edict_t * other)287 qboolean visible (edict_t *self, edict_t *other)
288 {
289 	vec3_t	spot1;
290 	vec3_t	spot2;
291 	trace_t	trace;
292 
293 	VectorCopy (self->s.origin, spot1);
294 	spot1[2] += self->viewheight;
295 	VectorCopy (other->s.origin, spot2);
296 	spot2[2] += other->viewheight;
297 	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
298 
299 	if (trace.fraction == 1.0)
300 		return true;
301 	return false;
302 }
303 
304 
305 /*
306 =============
307 infront
308 
309 returns 1 if the entity is in front (in sight) of self
310 =============
311 */
infront(edict_t * self,edict_t * other)312 qboolean infront (edict_t *self, edict_t *other)
313 {
314 	vec3_t	vec;
315 	float	dot;
316 	vec3_t	forward;
317 
318 	AngleVectors (self->s.angles, forward, NULL, NULL);
319 	VectorSubtract (other->s.origin, self->s.origin, vec);
320 	VectorNormalize (vec);
321 	dot = DotProduct (vec, forward);
322 
323 	if (dot > 0.3)
324 		return true;
325 	return false;
326 }
327 
328 
329 //============================================================================
330 
HuntTarget(edict_t * self)331 void HuntTarget (edict_t *self)
332 {
333 	vec3_t	vec;
334 
335 	self->goalentity = self->enemy;
336 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
337 		self->monsterinfo.stand (self);
338 	else
339 		self->monsterinfo.run (self);
340 	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
341 	self->ideal_yaw = vectoyaw(vec);
342 	// wait a while before first attack
343 	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
344 		AttackFinished (self, 1);
345 }
346 
FoundTarget(edict_t * self)347 void FoundTarget (edict_t *self)
348 {
349 	// let other monsters see this monster for a while
350 	if (self->enemy->client)
351 	{
352 		level.sight_entity = self;
353 		level.sight_entity_framenum = level.framenum;
354 		level.sight_entity->light_level = 128;
355 	}
356 
357 	self->show_hostile = level.time + 1;		// wake up other monsters
358 
359 	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
360 	self->monsterinfo.trail_time = level.time;
361 
362 	if (!self->combattarget)
363 	{
364 		HuntTarget (self);
365 		return;
366 	}
367 
368 	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
369 	if (!self->movetarget)
370 	{
371 		self->goalentity = self->movetarget = self->enemy;
372 		HuntTarget (self);
373 		gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
374 		return;
375 	}
376 
377 	// clear out our combattarget, these are a one shot deal
378 	self->combattarget = NULL;
379 	self->monsterinfo.aiflags |= AI_COMBAT_POINT;
380 
381 	// clear the targetname, that point is ours!
382 	self->movetarget->targetname = NULL;
383 	self->monsterinfo.pausetime = 0;
384 
385 	// run for it
386 	self->monsterinfo.run (self);
387 }
388 
389 
390 /*
391 ===========
392 FindTarget
393 
394 Self is currently not attacking anything, so try to find a target
395 
396 Returns TRUE if an enemy was sighted
397 
398 When a player fires a missile, the point of impact becomes a fakeplayer so
399 that monsters that see the impact will respond as if they had seen the
400 player.
401 
402 To avoid spending too much time, only a single client (or fakeclient) is
403 checked each frame.  This means multi player games will have slightly
404 slower noticing monsters.
405 ============
406 */
FindTarget(edict_t * self)407 qboolean FindTarget (edict_t *self)
408 {
409 	edict_t		*client;
410 	qboolean	heardit;
411 	int			r;
412 
413 	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
414 	{
415 		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
416 		{
417 			if (strcmp(self->goalentity->classname, "target_actor") == 0)
418 				return false;
419 		}
420 
421 		//FIXME look for monsters?
422 		return false;
423 	}
424 
425 	// if we're going to a combat point, just proceed
426 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
427 		return false;
428 
429 // if the first spawnflag bit is set, the monster will only wake up on
430 // really seeing the player, not another monster getting angry or hearing
431 // something
432 
433 // revised behavior so they will wake up if they "see" a player make a noise
434 // but not weapon impact/explosion noises
435 
436 	heardit = false;
437 	if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
438 	{
439 		client = level.sight_entity;
440 		if (client->enemy == self->enemy)
441 		{
442 			return false;
443 		}
444 	}
445 	else if (level.sound_entity_framenum >= (level.framenum - 1))
446 	{
447 		client = level.sound_entity;
448 		heardit = true;
449 	}
450 	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
451 	{
452 		client = level.sound2_entity;
453 		heardit = true;
454 	}
455 	else
456 	{
457 		client = level.sight_client;
458 		if (!client)
459 			return false;	// no clients to get mad at
460 	}
461 
462 	// if the entity went away, forget it
463 	if (!client->inuse)
464 		return false;
465 
466 	if (client == self->enemy)
467 		return true;	// JDC false;
468 
469 	if (client->client)
470 	{
471 		if (client->flags & FL_NOTARGET)
472 			return false;
473 	}
474 	else if (client->svflags & SVF_MONSTER)
475 	{
476 		if (!client->enemy)
477 			return false;
478 		if (client->enemy->flags & FL_NOTARGET)
479 			return false;
480 	}
481 	else if (heardit)
482 	{
483 		if (client->owner->flags & FL_NOTARGET)
484 			return false;
485 	}
486 	else
487 		return false;
488 
489 	if (!heardit)
490 	{
491 		r = range (self, client);
492 
493 		if (r == RANGE_FAR)
494 			return false;
495 
496 // this is where we would check invisibility
497 
498 		// is client in an spot too dark to be seen?
499 		if (client->light_level <= 5)
500 			return false;
501 
502 		if (!visible (self, client))
503 		{
504 			return false;
505 		}
506 
507 		if (r == RANGE_NEAR)
508 		{
509 			if (client->show_hostile < level.time && !infront (self, client))
510 			{
511 				return false;
512 			}
513 		}
514 		else if (r == RANGE_MID)
515 		{
516 			if (!infront (self, client))
517 			{
518 				return false;
519 			}
520 		}
521 
522 		self->enemy = client;
523 
524 		if (strcmp(self->enemy->classname, "player_noise") != 0)
525 		{
526 			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
527 
528 			if (!self->enemy->client)
529 			{
530 				self->enemy = self->enemy->enemy;
531 				if (!self->enemy->client)
532 				{
533 					self->enemy = NULL;
534 					return false;
535 				}
536 			}
537 		}
538 	}
539 	else	// heardit
540 	{
541 		vec3_t	temp;
542 
543 		if (self->spawnflags & 1)
544 		{
545 			if (!visible (self, client))
546 				return false;
547 		}
548 		else
549 		{
550 			if (!gi.inPHS(self->s.origin, client->s.origin))
551 				return false;
552 		}
553 
554 		VectorSubtract (client->s.origin, self->s.origin, temp);
555 
556 		if (VectorLength(temp) > 1000)	// too far to hear
557 		{
558 			return false;
559 		}
560 
561 		// check area portals - if they are different and not connected then we can't hear it
562 		if (client->areanum != self->areanum)
563 			if (!gi.AreasConnected(self->areanum, client->areanum))
564 				return false;
565 
566 		self->ideal_yaw = vectoyaw(temp);
567 		M_ChangeYaw (self);
568 
569 		// hunt the sound for a bit; hopefully find the real player
570 		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
571 		self->enemy = client;
572 	}
573 
574 //
575 // got one
576 //
577 	FoundTarget (self);
578 
579 	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
580 		self->monsterinfo.sight (self, self->enemy);
581 
582 	return true;
583 }
584 
585 
586 //=============================================================================
587 
588 /*
589 ============
590 FacingIdeal
591 
592 ============
593 */
FacingIdeal(edict_t * self)594 qboolean FacingIdeal(edict_t *self)
595 {
596 	float	delta;
597 
598 	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
599 	if (delta > 45 && delta < 315)
600 		return false;
601 	return true;
602 }
603 
604 
605 //=============================================================================
606 
M_CheckAttack(edict_t * self)607 qboolean M_CheckAttack (edict_t *self)
608 {
609 	vec3_t	spot1, spot2;
610 	float	chance;
611 	trace_t	tr;
612 
613 	if (self->enemy->health > 0)
614 	{
615 	// see if any entities are in the way of the shot
616 		VectorCopy (self->s.origin, spot1);
617 		spot1[2] += self->viewheight;
618 		VectorCopy (self->enemy->s.origin, spot2);
619 		spot2[2] += self->enemy->viewheight;
620 
621 		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
622 
623 		// do we have a clear shot?
624 		if (tr.ent != self->enemy)
625 			return false;
626 	}
627 
628 	// melee attack
629 	if (enemy_range == RANGE_MELEE)
630 	{
631 		// don't always melee in easy mode
632 		if (skill->value == 0 && (rand()&3) )
633 			return false;
634 		if (self->monsterinfo.melee)
635 			self->monsterinfo.attack_state = AS_MELEE;
636 		else
637 			self->monsterinfo.attack_state = AS_MISSILE;
638 		return true;
639 	}
640 
641 // missile attack
642 	if (!self->monsterinfo.attack)
643 		return false;
644 
645 	if (level.time < self->monsterinfo.attack_finished)
646 		return false;
647 
648 	if (enemy_range == RANGE_FAR)
649 		return false;
650 
651 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
652 	{
653 		chance = 0.4;
654 	}
655 	else if (enemy_range == RANGE_MELEE)
656 	{
657 		chance = 0.2;
658 	}
659 	else if (enemy_range == RANGE_NEAR)
660 	{
661 		chance = 0.1;
662 	}
663 	else if (enemy_range == RANGE_MID)
664 	{
665 		chance = 0.02;
666 	}
667 	else
668 	{
669 		return false;
670 	}
671 
672 	if (skill->value == 0)
673 		chance *= 0.5;
674 	else if (skill->value >= 2)
675 		chance *= 2;
676 
677 	if (random () < chance)
678 	{
679 		self->monsterinfo.attack_state = AS_MISSILE;
680 		self->monsterinfo.attack_finished = level.time + 2*random();
681 		return true;
682 	}
683 
684 	if (self->flags & FL_FLY)
685 	{
686 		if (random() < 0.3)
687 			self->monsterinfo.attack_state = AS_SLIDING;
688 		else
689 			self->monsterinfo.attack_state = AS_STRAIGHT;
690 	}
691 
692 	return false;
693 }
694 
695 
696 /*
697 =============
698 ai_run_melee
699 
700 Turn and close until within an angle to launch a melee attack
701 =============
702 */
ai_run_melee(edict_t * self)703 void ai_run_melee(edict_t *self)
704 {
705 	self->ideal_yaw = enemy_yaw;
706 	M_ChangeYaw (self);
707 
708 	if (FacingIdeal(self))
709 	{
710 		self->monsterinfo.melee (self);
711 		self->monsterinfo.attack_state = AS_STRAIGHT;
712 	}
713 }
714 
715 
716 /*
717 =============
718 ai_run_missile
719 
720 Turn in place until within an angle to launch a missile attack
721 =============
722 */
ai_run_missile(edict_t * self)723 void ai_run_missile(edict_t *self)
724 {
725 	self->ideal_yaw = enemy_yaw;
726 	M_ChangeYaw (self);
727 
728 	if (FacingIdeal(self))
729 	{
730 		self->monsterinfo.attack (self);
731 		self->monsterinfo.attack_state = AS_STRAIGHT;
732 	}
733 };
734 
735 
736 /*
737 =============
738 ai_run_slide
739 
740 Strafe sideways, but stay at aproximately the same range
741 =============
742 */
ai_run_slide(edict_t * self,float distance)743 void ai_run_slide(edict_t *self, float distance)
744 {
745 	float	ofs;
746 
747 	self->ideal_yaw = enemy_yaw;
748 	M_ChangeYaw (self);
749 
750 	if (self->monsterinfo.lefty)
751 		ofs = 90;
752 	else
753 		ofs = -90;
754 
755 	if (M_walkmove (self, self->ideal_yaw + ofs, distance))
756 		return;
757 
758 	self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
759 	M_walkmove (self, self->ideal_yaw - ofs, distance);
760 }
761 
762 
763 /*
764 =============
765 ai_checkattack
766 
767 Decides if we're going to attack or do something else
768 used by ai_run and ai_stand
769 =============
770 */
ai_checkattack(edict_t * self,float dist)771 qboolean ai_checkattack (edict_t *self, float dist)
772 {
773 	vec3_t		temp;
774 	qboolean	hesDeadJim;
775 
776 // this causes monsters to run blindly to the combat point w/o firing
777 	if (self->goalentity)
778 	{
779 		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
780 			return false;
781 
782 		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
783 		{
784 			if ((level.time - self->enemy->teleport_time) > 5.0)
785 			{
786 				if (self->goalentity == self->enemy)
787 					if (self->movetarget)
788 						self->goalentity = self->movetarget;
789 					else
790 						self->goalentity = NULL;
791 				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
792 				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
793 					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
794 			}
795 			else
796 			{
797 				self->show_hostile = level.time + 1;
798 				return false;
799 			}
800 		}
801 	}
802 
803 	enemy_vis = false;
804 
805 // see if the enemy is dead
806 	hesDeadJim = false;
807 	if ((!self->enemy) || (!self->enemy->inuse))
808 	{
809 		hesDeadJim = true;
810 	}
811 	else if (self->monsterinfo.aiflags & AI_MEDIC)
812 	{
813 		if (self->enemy->health > 0)
814 		{
815 			hesDeadJim = true;
816 			self->monsterinfo.aiflags &= ~AI_MEDIC;
817 		}
818 	}
819 	else
820 	{
821 		if (self->monsterinfo.aiflags & AI_BRUTAL)
822 		{
823 			if (self->enemy->health <= -80)
824 				hesDeadJim = true;
825 		}
826 		else
827 		{
828 			if (self->enemy->health <= 0)
829 				hesDeadJim = true;
830 		}
831 	}
832 
833 	if (hesDeadJim)
834 	{
835 		self->enemy = NULL;
836 	// FIXME: look all around for other targets
837 		if (self->oldenemy && self->oldenemy->health > 0)
838 		{
839 			self->enemy = self->oldenemy;
840 			self->oldenemy = NULL;
841 			HuntTarget (self);
842 		}
843 		else
844 		{
845 			if (self->movetarget)
846 			{
847 				self->goalentity = self->movetarget;
848 				self->monsterinfo.walk (self);
849 			}
850 			else
851 			{
852 				// we need the pausetime otherwise the stand code
853 				// will just revert to walking with no target and
854 				// the monsters will wonder around aimlessly trying
855 				// to hunt the world entity
856 				self->monsterinfo.pausetime = level.time + 100000000;
857 				self->monsterinfo.stand (self);
858 			}
859 			return true;
860 		}
861 	}
862 
863 	self->show_hostile = level.time + 1;		// wake up other monsters
864 
865 // check knowledge of enemy
866 	enemy_vis = visible(self, self->enemy);
867 	if (enemy_vis)
868 	{
869 		self->monsterinfo.search_time = level.time + 5;
870 		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
871 	}
872 
873 // look for other coop players here
874 //	if (coop && self->monsterinfo.search_time < level.time)
875 //	{
876 //		if (FindTarget (self))
877 //			return true;
878 //	}
879 
880 	enemy_infront = infront(self, self->enemy);
881 	enemy_range = range(self, self->enemy);
882 	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
883 	enemy_yaw = vectoyaw(temp);
884 
885 
886 	// JDC self->ideal_yaw = enemy_yaw;
887 
888 	if (self->monsterinfo.attack_state == AS_MISSILE)
889 	{
890 		ai_run_missile (self);
891 		return true;
892 	}
893 	if (self->monsterinfo.attack_state == AS_MELEE)
894 	{
895 		ai_run_melee (self);
896 		return true;
897 	}
898 
899 	// if enemy is not currently visible, we will never attack
900 	if (!enemy_vis)
901 		return false;
902 
903 	return self->monsterinfo.checkattack (self);
904 }
905 
906 
907 /*
908 =============
909 ai_run
910 
911 The monster has an enemy it is trying to kill
912 =============
913 */
ai_run(edict_t * self,float dist)914 void ai_run (edict_t *self, float dist)
915 {
916 	vec3_t		v;
917 	edict_t		*tempgoal;
918 	edict_t		*save;
919 	qboolean	new;
920 	edict_t		*marker;
921 	float		d1, d2;
922 	trace_t		tr;
923 	vec3_t		v_forward, v_right;
924 	float		left, center, right;
925 	vec3_t		left_target, right_target;
926 
927 	// if we're going to a combat point, just proceed
928 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
929 	{
930 		M_MoveToGoal (self, dist);
931 		return;
932 	}
933 
934 	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
935 	{
936 		VectorSubtract (self->s.origin, self->enemy->s.origin, v);
937 		if (VectorLength(v) < 64)
938 		{
939 			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
940 			self->monsterinfo.stand (self);
941 			return;
942 		}
943 
944 		M_MoveToGoal (self, dist);
945 
946 		if (!FindTarget (self))
947 			return;
948 	}
949 
950 	if (ai_checkattack (self, dist))
951 		return;
952 
953 	if (self->monsterinfo.attack_state == AS_SLIDING)
954 	{
955 		ai_run_slide (self, dist);
956 		return;
957 	}
958 
959 	if (enemy_vis)
960 	{
961 //		if (self.aiflags & AI_LOST_SIGHT)
962 //			dprint("regained sight\n");
963 		M_MoveToGoal (self, dist);
964 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
965 		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
966 		self->monsterinfo.trail_time = level.time;
967 		return;
968 	}
969 
970 	// coop will change to another enemy if visible
971 	if (coop->value)
972 	{	// FIXME: insane guys get mad with this, which causes crashes!
973 		if (FindTarget (self))
974 			return;
975 	}
976 
977 	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
978 	{
979 		M_MoveToGoal (self, dist);
980 		self->monsterinfo.search_time = 0;
981 //		dprint("search timeout\n");
982 		return;
983 	}
984 
985 	save = self->goalentity;
986 	tempgoal = G_Spawn();
987 	self->goalentity = tempgoal;
988 
989 	new = false;
990 
991 	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
992 	{
993 		// just lost sight of the player, decide where to go first
994 //		dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
995 		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
996 		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
997 		new = true;
998 	}
999 
1000 	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1001 	{
1002 		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1003 //		dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
1004 
1005 		// give ourself more time since we got this far
1006 		self->monsterinfo.search_time = level.time + 5;
1007 
1008 		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1009 		{
1010 //			dprint("was temp goal; retrying original\n");
1011 			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1012 			marker = NULL;
1013 			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1014 			new = true;
1015 		}
1016 		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1017 		{
1018 			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1019 			marker = PlayerTrail_PickFirst (self);
1020 		}
1021 		else
1022 		{
1023 			marker = PlayerTrail_PickNext (self);
1024 		}
1025 
1026 		if (marker)
1027 		{
1028 			VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1029 			self->monsterinfo.trail_time = marker->timestamp;
1030 			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1031 //			dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1032 
1033 //			debug_drawline(self.origin, self.last_sighting, 52);
1034 			new = true;
1035 		}
1036 	}
1037 
1038 	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1039 	d1 = VectorLength(v);
1040 	if (d1 <= dist)
1041 	{
1042 		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1043 		dist = d1;
1044 	}
1045 
1046 	VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1047 
1048 	if (new)
1049 	{
1050 //		gi.dprintf("checking for course correction\n");
1051 
1052 		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1053 		if (tr.fraction < 1)
1054 		{
1055 			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1056 			d1 = VectorLength(v);
1057 			center = tr.fraction;
1058 			d2 = d1 * ((center+1)/2);
1059 			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1060 			AngleVectors(self->s.angles, v_forward, v_right, NULL);
1061 
1062 			VectorSet(v, d2, -16, 0);
1063 			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1064 			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1065 			left = tr.fraction;
1066 
1067 			VectorSet(v, d2, 16, 0);
1068 			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1069 			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1070 			right = tr.fraction;
1071 
1072 			center = (d1*center)/d2;
1073 			if (left >= center && left > right)
1074 			{
1075 				if (left < 1)
1076 				{
1077 					VectorSet(v, d2 * left * 0.5, -16, 0);
1078 					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1079 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1080 				}
1081 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1082 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1083 				VectorCopy (left_target, self->goalentity->s.origin);
1084 				VectorCopy (left_target, self->monsterinfo.last_sighting);
1085 				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1086 				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1087 //				gi.dprintf("adjusted left\n");
1088 //				debug_drawline(self.origin, self.last_sighting, 152);
1089 			}
1090 			else if (right >= center && right > left)
1091 			{
1092 				if (right < 1)
1093 				{
1094 					VectorSet(v, d2 * right * 0.5, 16, 0);
1095 					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1096 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1097 				}
1098 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1099 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1100 				VectorCopy (right_target, self->goalentity->s.origin);
1101 				VectorCopy (right_target, self->monsterinfo.last_sighting);
1102 				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1103 				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1104 //				gi.dprintf("adjusted right\n");
1105 //				debug_drawline(self.origin, self.last_sighting, 152);
1106 			}
1107 		}
1108 //		else gi.dprintf("course was fine\n");
1109 	}
1110 
1111 	M_MoveToGoal (self, dist);
1112 
1113 	G_FreeEdict(tempgoal);
1114 
1115 	if (self)
1116 		self->goalentity = save;
1117 }
1118