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