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