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