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 qBool FindTarget (edict_t *self);
25 extern cVar_t	*maxclients;
26 
27 qBool ai_checkattack (edict_t *self, float dist);
28 
29 qBool	enemy_vis;
30 qBool	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 			Vec3Subtract (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 	Vec3Subtract (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 	Vec3Subtract (self->s.origin, other->s.origin, v);
270 	len = Vec3Length (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 qBool visible (edict_t *self, edict_t *other)
288 {
289 	vec3_t	spot1;
290 	vec3_t	spot2;
291 	trace_t	trace;
292 
293 	Vec3Copy (self->s.origin, spot1);
294 	spot1[2] += self->viewheight;
295 	Vec3Copy (other->s.origin, spot2);
296 	spot2[2] += other->viewheight;
297 	trace = gi.trace (spot1, vec3Origin, vec3Origin, spot2, self, MASK_OPAQUE);
298 
299 	if (trace.fraction == 1.0)
300 		return qTrue;
301 	return qFalse;
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 qBool infront (edict_t *self, edict_t *other)
313 {
314 	vec3_t	vec;
315 	float	dot;
316 	vec3_t	forward;
317 
318 	Angles_Vectors (self->s.angles, forward, NULL, NULL);
319 	Vec3Subtract (other->s.origin, self->s.origin, vec);
320 	VectorNormalizef (vec, vec);
321 	dot = DotProduct (vec, forward);
322 
323 	if (dot > 0.3)
324 		return qTrue;
325 	return qFalse;
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 	Vec3Subtract (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 	Vec3Copy(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 qTrue 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 qBool FindTarget (edict_t *self)
408 {
409 	edict_t		*client;
410 	qBool	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 qFalse;
419 		}
420 
421 		//FIXME look for monsters?
422 		return qFalse;
423 	}
424 
425 	// if we're going to a combat point, just proceed
426 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
427 		return qFalse;
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 = qFalse;
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 qFalse;
443 		}
444 	}
445 	else if (level.sound_entity_framenum >= (level.framenum - 1))
446 	{
447 		client = level.sound_entity;
448 		heardit = qTrue;
449 	}
450 	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
451 	{
452 		client = level.sound2_entity;
453 		heardit = qTrue;
454 	}
455 	else
456 	{
457 		client = level.sight_client;
458 		if (!client)
459 			return qFalse;	// no clients to get mad at
460 	}
461 
462 	// if the entity went away, forget it
463 	if (!client->inUse)
464 		return qFalse;
465 
466 	if (client == self->enemy)
467 		return qTrue;	// JDC false;
468 
469 	if (client->client)
470 	{
471 		if (client->flags & FL_NOTARGET)
472 			return qFalse;
473 	}
474 	else if (client->svFlags & SVF_MONSTER)
475 	{
476 		if (!client->enemy)
477 			return qFalse;
478 		if (client->enemy->flags & FL_NOTARGET)
479 			return qFalse;
480 	}
481 	else if (heardit)
482 	{
483 		if (client->owner->flags & FL_NOTARGET)
484 			return qFalse;
485 	}
486 	else
487 		return qFalse;
488 
489 	if (!heardit)
490 	{
491 		r = range (self, client);
492 
493 		if (r == RANGE_FAR)
494 			return qFalse;
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 qFalse;
501 
502 		if (!visible (self, client))
503 		{
504 			return qFalse;
505 		}
506 
507 		if (r == RANGE_NEAR)
508 		{
509 			if (client->show_hostile < level.time && !infront (self, client))
510 			{
511 				return qFalse;
512 			}
513 		}
514 		else if (r == RANGE_MID)
515 		{
516 			if (!infront (self, client))
517 			{
518 				return qFalse;
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 qFalse;
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 qFalse;
547 		}
548 		else
549 		{
550 			if (!gi.inPHS(self->s.origin, client->s.origin))
551 				return qFalse;
552 		}
553 
554 		Vec3Subtract (client->s.origin, self->s.origin, temp);
555 
556 		if (Vec3Length(temp) > 1000)	// too far to hear
557 		{
558 			return qFalse;
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 qFalse;
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 qTrue;
583 }
584 
585 
586 //=============================================================================
587 
588 /*
589 ============
590 FacingIdeal
591 
592 ============
593 */
FacingIdeal(edict_t * self)594 qBool FacingIdeal(edict_t *self)
595 {
596 	float	delta;
597 
598 	delta = AngleModf (self->s.angles[YAW] - self->ideal_yaw);
599 	if (delta > 45 && delta < 315)
600 		return qFalse;
601 	return qTrue;
602 }
603 
604 
605 //=============================================================================
606 
M_CheckAttack(edict_t * self)607 qBool 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 		Vec3Copy (self->s.origin, spot1);
617 		spot1[2] += self->viewheight;
618 		Vec3Copy (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 qFalse;
626 	}
627 
628 	// melee attack
629 	if (enemy_range == RANGE_MELEE)
630 	{
631 		// don't always melee in easy mode
632 		if (skill->floatVal == 0 && (rand()&3) )
633 			return qFalse;
634 		if (self->monsterinfo.melee)
635 			self->monsterinfo.attack_state = AS_MELEE;
636 		else
637 			self->monsterinfo.attack_state = AS_MISSILE;
638 		return qTrue;
639 	}
640 
641 // missile attack
642 	if (!self->monsterinfo.attack)
643 		return qFalse;
644 
645 	if (level.time < self->monsterinfo.attack_finished)
646 		return qFalse;
647 
648 	if (enemy_range == RANGE_FAR)
649 		return qFalse;
650 
651 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
652 	{
653 		chance = 0.4f;
654 	}
655 	else if (enemy_range == RANGE_MELEE)
656 	{
657 		chance = 0.2f;
658 	}
659 	else if (enemy_range == RANGE_NEAR)
660 	{
661 		chance = 0.1f;
662 	}
663 	else if (enemy_range == RANGE_MID)
664 	{
665 		chance = 0.02f;
666 	}
667 	else
668 	{
669 		return qFalse;
670 	}
671 
672 	if (skill->floatVal == 0)
673 		chance *= 0.5;
674 	else if (skill->floatVal >= 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 qTrue;
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 qFalse;
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 qBool ai_checkattack (edict_t *self, float dist)
772 {
773 	vec3_t		temp;
774 	qBool	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 qFalse;
781 
782 		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
783 		{
784 			if ((level.time - self->enemy->teleport_time) > 5.0)
785 			{
786 				if (self->goalentity == self->enemy)
787 					if (self->movetarget)
788 						self->goalentity = self->movetarget;
789 					else
790 						self->goalentity = NULL;
791 				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
792 				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
793 					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
794 			}
795 			else
796 			{
797 				self->show_hostile = level.time + 1;
798 				return qFalse;
799 			}
800 		}
801 	}
802 
803 	enemy_vis = qFalse;
804 
805 // see if the enemy is dead
806 	hesDeadJim = qFalse;
807 	if ((!self->enemy) || (!self->enemy->inUse))
808 	{
809 		hesDeadJim = qTrue;
810 	}
811 	else if (self->monsterinfo.aiflags & AI_MEDIC)
812 	{
813 		if (self->enemy->health > 0)
814 		{
815 			hesDeadJim = qTrue;
816 			self->monsterinfo.aiflags &= ~AI_MEDIC;
817 		}
818 	}
819 	else
820 	{
821 		if (self->monsterinfo.aiflags & AI_BRUTAL)
822 		{
823 			if (self->enemy->health <= -80)
824 				hesDeadJim = qTrue;
825 		}
826 		else
827 		{
828 			if (self->enemy->health <= 0)
829 				hesDeadJim = qTrue;
830 		}
831 	}
832 
833 	if (hesDeadJim)
834 	{
835 		self->enemy = NULL;
836 	// FIXME: look all around for other targets
837 		if (self->oldenemy && self->oldenemy->health > 0)
838 		{
839 			self->enemy = self->oldenemy;
840 			self->oldenemy = NULL;
841 			HuntTarget (self);
842 		}
843 		else
844 		{
845 			if (self->movetarget)
846 			{
847 				self->goalentity = self->movetarget;
848 				self->monsterinfo.walk (self);
849 			}
850 			else
851 			{
852 				// we need the pausetime otherwise the stand code
853 				// will just revert to walking with no target and
854 				// the monsters will wonder around aimlessly trying
855 				// to hunt the world entity
856 				self->monsterinfo.pausetime = level.time + 100000000;
857 				self->monsterinfo.stand (self);
858 			}
859 			return qTrue;
860 		}
861 	}
862 
863 	self->show_hostile = level.time + 1;		// wake up other monsters
864 
865 // check knowledge of enemy
866 	enemy_vis = visible(self, self->enemy);
867 	if (enemy_vis)
868 	{
869 		self->monsterinfo.search_time = level.time + 5;
870 		Vec3Copy (self->enemy->s.origin, self->monsterinfo.last_sighting);
871 	}
872 
873 // look for other coop players here
874 //	if (coop && self->monsterinfo.search_time < level.time)
875 //	{
876 //		if (FindTarget (self))
877 //			return qTrue;
878 //	}
879 
880 	enemy_infront = infront(self, self->enemy);
881 	enemy_range = range(self, self->enemy);
882 	Vec3Subtract (self->enemy->s.origin, self->s.origin, temp);
883 	enemy_yaw = VecToYaw(temp);
884 
885 
886 	// JDC self->ideal_yaw = enemy_yaw;
887 
888 	if (self->monsterinfo.attack_state == AS_MISSILE)
889 	{
890 		ai_run_missile (self);
891 		return qTrue;
892 	}
893 	if (self->monsterinfo.attack_state == AS_MELEE)
894 	{
895 		ai_run_melee (self);
896 		return qTrue;
897 	}
898 
899 	// if enemy is not currently visible, we will never attack
900 	if (!enemy_vis)
901 		return qFalse;
902 
903 	return self->monsterinfo.checkattack (self);
904 }
905 
906 
907 /*
908 =============
909 ai_run
910 
911 The monster has an enemy it is trying to kill
912 =============
913 */
ai_run(edict_t * self,float dist)914 void ai_run (edict_t *self, float dist)
915 {
916 	vec3_t		v;
917 	edict_t		*tempgoal;
918 	edict_t		*save;
919 	qBool	new;
920 	edict_t		*marker;
921 	float		d1, d2;
922 	trace_t		tr;
923 	vec3_t		v_forward, v_right;
924 	float		left, center, right;
925 	vec3_t		left_target, right_target;
926 
927 	// if we're going to a combat point, just proceed
928 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
929 	{
930 		M_MoveToGoal (self, dist);
931 		return;
932 	}
933 
934 	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
935 	{
936 		Vec3Subtract (self->s.origin, self->enemy->s.origin, v);
937 		if (Vec3Length(v) < 64)
938 		{
939 			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
940 			self->monsterinfo.stand (self);
941 			return;
942 		}
943 
944 		M_MoveToGoal (self, dist);
945 
946 		if (!FindTarget (self))
947 			return;
948 	}
949 
950 	if (ai_checkattack (self, dist))
951 		return;
952 
953 	if (self->monsterinfo.attack_state == AS_SLIDING)
954 	{
955 		ai_run_slide (self, dist);
956 		return;
957 	}
958 
959 	if (enemy_vis)
960 	{
961 //		if (self.aiflags & AI_LOST_SIGHT)
962 //			dprint("regained sight\n");
963 		M_MoveToGoal (self, dist);
964 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
965 		Vec3Copy (self->enemy->s.origin, self->monsterinfo.last_sighting);
966 		self->monsterinfo.trail_time = level.time;
967 		return;
968 	}
969 
970 	// coop will change to another enemy if visible
971 	if (coop->floatVal)
972 	{	// FIXME: insane guys get mad with this, which causes crashes!
973 		if (FindTarget (self))
974 			return;
975 	}
976 
977 	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
978 	{
979 		M_MoveToGoal (self, dist);
980 		self->monsterinfo.search_time = 0;
981 //		dprint("search timeout\n");
982 		return;
983 	}
984 
985 	save = self->goalentity;
986 	tempgoal = G_Spawn();
987 	self->goalentity = tempgoal;
988 
989 	new = qFalse;
990 
991 	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
992 	{
993 		// just lost sight of the player, decide where to go first
994 //		dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
995 		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
996 		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
997 		new = qTrue;
998 	}
999 
1000 	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1001 	{
1002 		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1003 //		dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
1004 
1005 		// give ourself more time since we got this far
1006 		self->monsterinfo.search_time = level.time + 5;
1007 
1008 		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1009 		{
1010 //			dprint("was temp goal; retrying original\n");
1011 			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1012 			marker = NULL;
1013 			Vec3Copy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1014 			new = qTrue;
1015 		}
1016 		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1017 		{
1018 			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1019 			marker = PlayerTrail_PickFirst (self);
1020 		}
1021 		else
1022 		{
1023 			marker = PlayerTrail_PickNext (self);
1024 		}
1025 
1026 		if (marker)
1027 		{
1028 			Vec3Copy (marker->s.origin, self->monsterinfo.last_sighting);
1029 			self->monsterinfo.trail_time = marker->timestamp;
1030 			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1031 //			dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1032 
1033 //			debug_drawline(self.origin, self.last_sighting, 52);
1034 			new = qTrue;
1035 		}
1036 	}
1037 
1038 	Vec3Subtract (self->s.origin, self->monsterinfo.last_sighting, v);
1039 	d1 = Vec3Length(v);
1040 	if (d1 <= dist)
1041 	{
1042 		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1043 		dist = d1;
1044 	}
1045 
1046 	Vec3Copy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1047 
1048 	if (new)
1049 	{
1050 //		gi.dprintf("checking for course correction\n");
1051 
1052 		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1053 		if (tr.fraction < 1)
1054 		{
1055 			Vec3Subtract (self->goalentity->s.origin, self->s.origin, v);
1056 			d1 = Vec3Length(v);
1057 			center = tr.fraction;
1058 			d2 = d1 * ((center+1)/2);
1059 			self->s.angles[YAW] = self->ideal_yaw = VecToYaw(v);
1060 			Angles_Vectors(self->s.angles, v_forward, v_right, NULL);
1061 
1062 			Vec3Set (v, d2, -16, 0);
1063 			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1064 			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1065 			left = tr.fraction;
1066 
1067 			Vec3Set (v, d2, 16, 0);
1068 			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1069 			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1070 			right = tr.fraction;
1071 
1072 			center = (d1*center)/d2;
1073 			if (left >= center && left > right)
1074 			{
1075 				if (left < 1)
1076 				{
1077 					Vec3Set (v, d2 * left * 0.5, -16, 0);
1078 					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1079 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1080 				}
1081 				Vec3Copy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1082 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1083 				Vec3Copy (left_target, self->goalentity->s.origin);
1084 				Vec3Copy (left_target, self->monsterinfo.last_sighting);
1085 				Vec3Subtract (self->goalentity->s.origin, self->s.origin, v);
1086 				self->s.angles[YAW] = self->ideal_yaw = VecToYaw(v);
1087 //				gi.dprintf("adjusted left\n");
1088 //				debug_drawline(self.origin, self.last_sighting, 152);
1089 			}
1090 			else if (right >= center && right > left)
1091 			{
1092 				if (right < 1) {
1093 					Vec3Set (v, d2 * right * 0.5, 16, 0);
1094 					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1095 //					gi.dprintf("incomplete path, go part way and adjust again\n");
1096 				}
1097 				Vec3Copy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1098 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1099 				Vec3Copy (right_target, self->goalentity->s.origin);
1100 				Vec3Copy (right_target, self->monsterinfo.last_sighting);
1101 				Vec3Subtract (self->goalentity->s.origin, self->s.origin, v);
1102 				self->s.angles[YAW] = self->ideal_yaw = VecToYaw(v);
1103 //				gi.dprintf("adjusted right\n");
1104 //				debug_drawline(self.origin, self.last_sighting, 152);
1105 			}
1106 		}
1107 //		else gi.dprintf("course was fine\n");
1108 	}
1109 
1110 	M_MoveToGoal (self, dist);
1111 
1112 	G_FreeEdict(tempgoal);
1113 
1114 	if (self)
1115 		self->goalentity = save;
1116 }
1117