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 				}
792 				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
793 				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
794 					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
795 			}
796 			else
797 			{
798 				self->show_hostile = level.time + 1;
799 				return false;
800 			}
801 		}
802 	}
803 
804 	enemy_vis = false;
805 
806 // see if the enemy is dead
807 	hesDeadJim = false;
808 	if ((!self->enemy) || (!self->enemy->inuse))
809 	{
810 		hesDeadJim = true;
811 	}
812 	else if (self->monsterinfo.aiflags & AI_MEDIC)
813 	{
814 		if (self->enemy->health > 0)
815 		{
816 			hesDeadJim = true;
817 			self->monsterinfo.aiflags &= ~AI_MEDIC;
818 		}
819 	}
820 	else
821 	{
822 		if (self->monsterinfo.aiflags & AI_BRUTAL)
823 		{
824 			if (self->enemy->health <= -80)
825 				hesDeadJim = true;
826 		}
827 		else
828 		{
829 			if (self->enemy->health <= 0)
830 				hesDeadJim = true;
831 		}
832 	}
833 
834 	if (hesDeadJim)
835 	{
836 		self->enemy = NULL;
837 	// FIXME: look all around for other targets
838 		if (self->oldenemy && self->oldenemy->health > 0)
839 		{
840 			self->enemy = self->oldenemy;
841 			self->oldenemy = NULL;
842 			HuntTarget (self);
843 		}
844 		else
845 		{
846 			if (self->movetarget)
847 			{
848 				self->goalentity = self->movetarget;
849 				self->monsterinfo.walk (self);
850 			}
851 			else
852 			{
853 				// we need the pausetime otherwise the stand code
854 				// will just revert to walking with no target and
855 				// the monsters will wonder around aimlessly trying
856 				// to hunt the world entity
857 				self->monsterinfo.pausetime = level.time + 100000000;
858 				self->monsterinfo.stand (self);
859 			}
860 			return true;
861 		}
862 	}
863 
864 	self->show_hostile = level.time + 1;		// wake up other monsters
865 
866 // check knowledge of enemy
867 	enemy_vis = visible(self, self->enemy);
868 	if (enemy_vis)
869 	{
870 		self->monsterinfo.search_time = level.time + 5;
871 		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
872 	}
873 
874 // look for other coop players here
875 //	if (coop && self->monsterinfo.search_time < level.time)
876 //	{
877 //		if (FindTarget (self))
878 //			return true;
879 //	}
880 
881 	enemy_infront = infront(self, self->enemy);
882 	enemy_range = range(self, self->enemy);
883 	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
884 	enemy_yaw = vectoyaw(temp);
885 
886 
887 	// JDC self->ideal_yaw = enemy_yaw;
888 
889 	if (self->monsterinfo.attack_state == AS_MISSILE)
890 	{
891 		ai_run_missile (self);
892 		return true;
893 	}
894 	if (self->monsterinfo.attack_state == AS_MELEE)
895 	{
896 		ai_run_melee (self);
897 		return true;
898 	}
899 
900 	// if enemy is not currently visible, we will never attack
901 	if (!enemy_vis)
902 		return false;
903 
904 	return self->monsterinfo.checkattack (self);
905 }
906 
907 
908 /*
909 =============
910 ai_run
911 
912 The monster has an enemy it is trying to kill
913 =============
914 */
ai_run(edict_t * self,float dist)915 void ai_run (edict_t *self, float dist)
916 {
917 	vec3_t		v;
918 	edict_t		*tempgoal;
919 	edict_t		*save;
920 	qboolean	new;
921 	edict_t		*marker;
922 	float		d1, d2;
923 	trace_t		tr;
924 	vec3_t		v_forward, v_right;
925 	float		left, center, right;
926 	vec3_t		left_target, right_target;
927 
928 	// if we're going to a combat point, just proceed
929 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
930 	{
931 		M_MoveToGoal (self, dist);
932 		return;
933 	}
934 
935 	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
936 	{
937 		VectorSubtract (self->s.origin, self->enemy->s.origin, v);
938 		if (VectorLength(v) < 64)
939 		{
940 			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
941 			self->monsterinfo.stand (self);
942 			return;
943 		}
944 
945 		M_MoveToGoal (self, dist);
946 
947 		if (!FindTarget (self))
948 			return;
949 	}
950 
951 	if (ai_checkattack (self, dist))
952 		return;
953 
954 	if (self->monsterinfo.attack_state == AS_SLIDING)
955 	{
956 		ai_run_slide (self, dist);
957 		return;
958 	}
959 
960 	if (enemy_vis)
961 	{
962 //		if (self.aiflags & AI_LOST_SIGHT)
963 //			dprint("regained sight\n");
964 		M_MoveToGoal (self, dist);
965 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
966 		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
967 		self->monsterinfo.trail_time = level.time;
968 		return;
969 	}
970 
971 	// coop will change to another enemy if visible
972 	if (coop->value)
973 	{	// FIXME: insane guys get mad with this, which causes crashes!
974 		if (FindTarget (self))
975 			return;
976 	}
977 
978 	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
979 	{
980 		M_MoveToGoal (self, dist);
981 		self->monsterinfo.search_time = 0;
982 //		dprint("search timeout\n");
983 		return;
984 	}
985 
986 	save = self->goalentity;
987 	tempgoal = G_Spawn();
988 	self->goalentity = tempgoal;
989 
990 	new = false;
991 
992 	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
993 	{
994 		// just lost sight of the player, decide where to go first
995 //		dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
996 		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
997 		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
998 		new = true;
999 	}
1000 
1001 	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1002 	{
1003 		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1004 //		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");
1005 
1006 		// give ourself more time since we got this far
1007 		self->monsterinfo.search_time = level.time + 5;
1008 
1009 		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1010 		{
1011 //			dprint("was temp goal; retrying original\n");
1012 			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1013 			marker = NULL;
1014 			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1015 			new = true;
1016 		}
1017 		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1018 		{
1019 			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1020 			marker = PlayerTrail_PickFirst (self);
1021 		}
1022 		else
1023 		{
1024 			marker = PlayerTrail_PickNext (self);
1025 		}
1026 
1027 		if (marker)
1028 		{
1029 			VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1030 			self->monsterinfo.trail_time = marker->timestamp;
1031 			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1032 //			dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1033 
1034 //			debug_drawline(self.origin, self.last_sighting, 52);
1035 			new = true;
1036 		}
1037 	}
1038 
1039 	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1040 	d1 = VectorLength(v);
1041 	if (d1 <= dist)
1042 	{
1043 		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1044 		dist = d1;
1045 	}
1046 
1047 	VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1048 
1049 	if (new)
1050 	{
1051 //		gi.dprintf("checking for course correction\n");
1052 
1053 		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1054 		if (tr.fraction < 1)
1055 		{
1056 			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1057 			d1 = VectorLength(v);
1058 			center = tr.fraction;
1059 			d2 = d1 * ((center+1)/2);
1060 			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1061 			AngleVectors(self->s.angles, v_forward, v_right, NULL);
1062 
1063 			VectorSet(v, d2, -16, 0);
1064 			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1065 			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1066 			left = tr.fraction;
1067 
1068 			VectorSet(v, d2, 16, 0);
1069 			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1070 			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1071 			right = tr.fraction;
1072 
1073 			center = (d1*center)/d2;
1074 			if (left >= center && left > right)
1075 			{
1076 				if (left < 1)
1077 				{
1078 					VectorSet(v, d2 * left * 0.5, -16, 0);
1079 					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1080 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1081 				}
1082 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1083 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1084 				VectorCopy (left_target, self->goalentity->s.origin);
1085 				VectorCopy (left_target, self->monsterinfo.last_sighting);
1086 				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1087 				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1088 //				gi.dprintf("adjusted left\n");
1089 //				debug_drawline(self.origin, self.last_sighting, 152);
1090 			}
1091 			else if (right >= center && right > left)
1092 			{
1093 				if (right < 1)
1094 				{
1095 					VectorSet(v, d2 * right * 0.5, 16, 0);
1096 					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1097 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1098 				}
1099 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1100 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1101 				VectorCopy (right_target, self->goalentity->s.origin);
1102 				VectorCopy (right_target, self->monsterinfo.last_sighting);
1103 				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1104 				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1105 //				gi.dprintf("adjusted right\n");
1106 //				debug_drawline(self.origin, self.last_sighting, 152);
1107 			}
1108 		}
1109 //		else gi.dprintf("course was fine\n");
1110 	}
1111 
1112 	M_MoveToGoal (self, dist);
1113 
1114 	G_FreeEdict(tempgoal);
1115 
1116 	if (self)
1117 		self->goalentity = save;
1118 }
1119