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