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