Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_ai.c (25020B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 // g_ai.c
     21 
     22 #include "g_local.h"
     23 
     24 qboolean FindTarget (edict_t *self);
     25 extern cvar_t	*maxclients;
     26 
     27 qboolean ai_checkattack (edict_t *self, float dist);
     28 
     29 qboolean	enemy_vis;
     30 qboolean	enemy_infront;
     31 int			enemy_range;
     32 float		enemy_yaw;
     33 
     34 //============================================================================
     35 
     36 
     37 /*
     38 =================
     39 AI_SetSightClient
     40 
     41 Called once each frame to set level.sight_client to the
     42 player to be checked for in findtarget.
     43 
     44 If all clients are either dead or in notarget, sight_client
     45 will be null.
     46 
     47 In coop games, sight_client will cycle between the clients.
     48 =================
     49 */
     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 */
     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 */
    106 void ai_stand (edict_t *self, float dist)
    107 {
    108 	vec3_t	v;
    109 
    110 	if (dist)
    111 		M_walkmove (self, self->s.angles[YAW], dist);
    112 
    113 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
    114 	{
    115 		if (self->enemy)
    116 		{
    117 			VectorSubtract (self->enemy->s.origin, self->s.origin, v);
    118 			self->ideal_yaw = vectoyaw(v);
    119 			if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
    120 			{
    121 				self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
    122 				self->monsterinfo.run (self);
    123 			}
    124 			M_ChangeYaw (self);
    125 			ai_checkattack (self, 0);
    126 		}
    127 		else
    128 			FindTarget (self);
    129 		return;
    130 	}
    131 
    132 	if (FindTarget (self))
    133 		return;
    134 	
    135 	if (level.time > self->monsterinfo.pausetime)
    136 	{
    137 		self->monsterinfo.walk (self);
    138 		return;
    139 	}
    140 
    141 	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
    142 	{
    143 		if (self->monsterinfo.idle_time)
    144 		{
    145 			self->monsterinfo.idle (self);
    146 			self->monsterinfo.idle_time = level.time + 15 + random() * 15;
    147 		}
    148 		else
    149 		{
    150 			self->monsterinfo.idle_time = level.time + random() * 15;
    151 		}
    152 	}
    153 }
    154 
    155 
    156 /*
    157 =============
    158 ai_walk
    159 
    160 The monster is walking it's beat
    161 =============
    162 */
    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 */
    194 void ai_charge (edict_t *self, float dist)
    195 {
    196 	vec3_t	v;
    197 
    198 	VectorSubtract (self->enemy->s.origin, self->s.origin, v);
    199 	self->ideal_yaw = vectoyaw(v);
    200 	M_ChangeYaw (self);
    201 
    202 	if (dist)
    203 		M_walkmove (self, self->s.angles[YAW], dist);
    204 }
    205 
    206 
    207 /*
    208 =============
    209 ai_turn
    210 
    211 don't move, but turn towards ideal_yaw
    212 Distance is for slight position adjustments needed by the animations
    213 =============
    214 */
    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 */
    264 int range (edict_t *self, edict_t *other)
    265 {
    266 	vec3_t	v;
    267 	float	len;
    268 
    269 	VectorSubtract (self->s.origin, other->s.origin, v);
    270 	len = VectorLength (v);
    271 	if (len < MELEE_DISTANCE)
    272 		return RANGE_MELEE;
    273 	if (len < 500)
    274 		return RANGE_NEAR;
    275 	if (len < 1000)
    276 		return RANGE_MID;
    277 	return RANGE_FAR;
    278 }
    279 
    280 /*
    281 =============
    282 visible
    283 
    284 returns 1 if the entity is visible to self, even if not infront ()
    285 =============
    286 */
    287 qboolean visible (edict_t *self, edict_t *other)
    288 {
    289 	vec3_t	spot1;
    290 	vec3_t	spot2;
    291 	trace_t	trace;
    292 
    293 	VectorCopy (self->s.origin, spot1);
    294 	spot1[2] += self->viewheight;
    295 	VectorCopy (other->s.origin, spot2);
    296 	spot2[2] += other->viewheight;
    297 	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
    298 	
    299 	if (trace.fraction == 1.0)
    300 		return true;
    301 	return false;
    302 }
    303 
    304 
    305 /*
    306 =============
    307 infront
    308 
    309 returns 1 if the entity is in front (in sight) of self
    310 =============
    311 */
    312 qboolean infront (edict_t *self, edict_t *other)
    313 {
    314 	vec3_t	vec;
    315 	float	dot;
    316 	vec3_t	forward;
    317 	
    318 	AngleVectors (self->s.angles, forward, NULL, NULL);
    319 	VectorSubtract (other->s.origin, self->s.origin, vec);
    320 	VectorNormalize (vec);
    321 	dot = DotProduct (vec, forward);
    322 	
    323 	if (dot > 0.3)
    324 		return true;
    325 	return false;
    326 }
    327 
    328 
    329 //============================================================================
    330 
    331 void HuntTarget (edict_t *self)
    332 {
    333 	vec3_t	vec;
    334 
    335 	self->goalentity = self->enemy;
    336 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
    337 		self->monsterinfo.stand (self);
    338 	else
    339 		self->monsterinfo.run (self);
    340 	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
    341 	self->ideal_yaw = vectoyaw(vec);
    342 	// wait a while before first attack
    343 	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
    344 		AttackFinished (self, 1);
    345 }
    346 
    347 void FoundTarget (edict_t *self)
    348 {
    349 	// let other monsters see this monster for a while
    350 	if (self->enemy->client)
    351 	{
    352 		level.sight_entity = self;
    353 		level.sight_entity_framenum = level.framenum;
    354 		level.sight_entity->light_level = 128;
    355 	}
    356 
    357 	self->show_hostile = level.time + 1;		// wake up other monsters
    358 
    359 	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
    360 	self->monsterinfo.trail_time = level.time;
    361 
    362 	if (!self->combattarget)
    363 	{
    364 		HuntTarget (self);
    365 		return;
    366 	}
    367 
    368 	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
    369 	if (!self->movetarget)
    370 	{
    371 		self->goalentity = self->movetarget = self->enemy;
    372 		HuntTarget (self);
    373 		gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
    374 		return;
    375 	}
    376 
    377 	// clear out our combattarget, these are a one shot deal
    378 	self->combattarget = NULL;
    379 	self->monsterinfo.aiflags |= AI_COMBAT_POINT;
    380 
    381 	// clear the targetname, that point is ours!
    382 	self->movetarget->targetname = NULL;
    383 	self->monsterinfo.pausetime = 0;
    384 
    385 	// run for it
    386 	self->monsterinfo.run (self);
    387 }
    388 
    389 
    390 /*
    391 ===========
    392 FindTarget
    393 
    394 Self is currently not attacking anything, so try to find a target
    395 
    396 Returns TRUE if an enemy was sighted
    397 
    398 When a player fires a missile, the point of impact becomes a fakeplayer so
    399 that monsters that see the impact will respond as if they had seen the
    400 player.
    401 
    402 To avoid spending too much time, only a single client (or fakeclient) is
    403 checked each frame.  This means multi player games will have slightly
    404 slower noticing monsters.
    405 ============
    406 */
    407 qboolean FindTarget (edict_t *self)
    408 {
    409 	edict_t		*client;
    410 	qboolean	heardit;
    411 	int			r;
    412 
    413 	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
    414 	{
    415 		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
    416 		{
    417 			if (strcmp(self->goalentity->classname, "target_actor") == 0)
    418 				return false;
    419 		}
    420 
    421 		//FIXME look for monsters?
    422 		return false;
    423 	}
    424 
    425 	// if we're going to a combat point, just proceed
    426 	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
    427 		return false;
    428 
    429 // if the first spawnflag bit is set, the monster will only wake up on
    430 // really seeing the player, not another monster getting angry or hearing
    431 // something
    432 
    433 // revised behavior so they will wake up if they "see" a player make a noise
    434 // but not weapon impact/explosion noises
    435 
    436 	heardit = false;
    437 	if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
    438 	{
    439 		client = level.sight_entity;
    440 		if (client->enemy == self->enemy)
    441 		{
    442 			return false;
    443 		}
    444 	}
    445 	else if (level.sound_entity_framenum >= (level.framenum - 1))
    446 	{
    447 		client = level.sound_entity;
    448 		heardit = true;
    449 	}
    450 	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
    451 	{
    452 		client = level.sound2_entity;
    453 		heardit = true;
    454 	}
    455 	else
    456 	{
    457 		client = level.sight_client;
    458 		if (!client)
    459 			return false;	// no clients to get mad at
    460 	}
    461 
    462 	// if the entity went away, forget it
    463 	if (!client->inuse)
    464 		return false;
    465 
    466 	if (client == self->enemy)
    467 		return true;	// JDC false;
    468 
    469 	if (client->client)
    470 	{
    471 		if (client->flags & FL_NOTARGET)
    472 			return false;
    473 	}
    474 	else if (client->svflags & SVF_MONSTER)
    475 	{
    476 		if (!client->enemy)
    477 			return false;
    478 		if (client->enemy->flags & FL_NOTARGET)
    479 			return false;
    480 	}
    481 	else if (heardit)
    482 	{
    483 		if (client->owner->flags & FL_NOTARGET)
    484 			return false;
    485 	}
    486 	else
    487 		return false;
    488 
    489 	if (!heardit)
    490 	{
    491 		r = range (self, client);
    492 
    493 		if (r == RANGE_FAR)
    494 			return false;
    495 
    496 // this is where we would check invisibility
    497 
    498 		// is client in an spot too dark to be seen?
    499 		if (client->light_level <= 5)
    500 			return false;
    501 
    502 		if (!visible (self, client))
    503 		{
    504 			return false;
    505 		}
    506 
    507 		if (r == RANGE_NEAR)
    508 		{
    509 			if (client->show_hostile < level.time && !infront (self, client))
    510 			{
    511 				return false;
    512 			}
    513 		}
    514 		else if (r == RANGE_MID)
    515 		{
    516 			if (!infront (self, client))
    517 			{
    518 				return false;
    519 			}
    520 		}
    521 
    522 		self->enemy = client;
    523 
    524 		if (strcmp(self->enemy->classname, "player_noise") != 0)
    525 		{
    526 			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
    527 
    528 			if (!self->enemy->client)
    529 			{
    530 				self->enemy = self->enemy->enemy;
    531 				if (!self->enemy->client)
    532 				{
    533 					self->enemy = NULL;
    534 					return false;
    535 				}
    536 			}
    537 		}
    538 	}
    539 	else	// heardit
    540 	{
    541 		vec3_t	temp;
    542 
    543 		if (self->spawnflags & 1)
    544 		{
    545 			if (!visible (self, client))
    546 				return false;
    547 		}
    548 		else
    549 		{
    550 			if (!gi.inPHS(self->s.origin, client->s.origin))
    551 				return false;
    552 		}
    553 
    554 		VectorSubtract (client->s.origin, self->s.origin, temp);
    555 
    556 		if (VectorLength(temp) > 1000)	// too far to hear
    557 		{
    558 			return false;
    559 		}
    560 
    561 		// check area portals - if they are different and not connected then we can't hear it
    562 		if (client->areanum != self->areanum)
    563 			if (!gi.AreasConnected(self->areanum, client->areanum))
    564 				return false;
    565 
    566 		self->ideal_yaw = vectoyaw(temp);
    567 		M_ChangeYaw (self);
    568 
    569 		// hunt the sound for a bit; hopefully find the real player
    570 		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
    571 		self->enemy = client;
    572 	}
    573 
    574 //
    575 // got one
    576 //
    577 	FoundTarget (self);
    578 
    579 	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
    580 		self->monsterinfo.sight (self, self->enemy);
    581 
    582 	return true;
    583 }
    584 
    585 
    586 //=============================================================================
    587 
    588 /*
    589 ============
    590 FacingIdeal
    591 
    592 ============
    593 */
    594 qboolean FacingIdeal(edict_t *self)
    595 {
    596 	float	delta;
    597 
    598 	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
    599 	if (delta > 45 && delta < 315)
    600 		return false;
    601 	return true;
    602 }
    603 
    604 
    605 //=============================================================================
    606 
    607 qboolean M_CheckAttack (edict_t *self)
    608 {
    609 	vec3_t	spot1, spot2;
    610 	float	chance;
    611 	trace_t	tr;
    612 
    613 	if (self->enemy->health > 0)
    614 	{
    615 	// see if any entities are in the way of the shot
    616 		VectorCopy (self->s.origin, spot1);
    617 		spot1[2] += self->viewheight;
    618 		VectorCopy (self->enemy->s.origin, spot2);
    619 		spot2[2] += self->enemy->viewheight;
    620 
    621 		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
    622 
    623 		// do we have a clear shot?
    624 		if (tr.ent != self->enemy)
    625 			return false;
    626 	}
    627 	
    628 	// melee attack
    629 	if (enemy_range == RANGE_MELEE)
    630 	{
    631 		// don't always melee in easy mode
    632 		if (skill->value == 0 && (rand()&3) )
    633 			return false;
    634 		if (self->monsterinfo.melee)
    635 			self->monsterinfo.attack_state = AS_MELEE;
    636 		else
    637 			self->monsterinfo.attack_state = AS_MISSILE;
    638 		return true;
    639 	}
    640 	
    641 // missile attack
    642 	if (!self->monsterinfo.attack)
    643 		return false;
    644 		
    645 	if (level.time < self->monsterinfo.attack_finished)
    646 		return false;
    647 		
    648 	if (enemy_range == RANGE_FAR)
    649 		return false;
    650 
    651 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
    652 	{
    653 		chance = 0.4;
    654 	}
    655 	else if (enemy_range == RANGE_MELEE)
    656 	{
    657 		chance = 0.2;
    658 	}
    659 	else if (enemy_range == RANGE_NEAR)
    660 	{
    661 		chance = 0.1;
    662 	}
    663 	else if (enemy_range == RANGE_MID)
    664 	{
    665 		chance = 0.02;
    666 	}
    667 	else
    668 	{
    669 		return false;
    670 	}
    671 
    672 	if (skill->value == 0)
    673 		chance *= 0.5;
    674 	else if (skill->value >= 2)
    675 		chance *= 2;
    676 
    677 	if (random () < chance)
    678 	{
    679 		self->monsterinfo.attack_state = AS_MISSILE;
    680 		self->monsterinfo.attack_finished = level.time + 2*random();
    681 		return true;
    682 	}
    683 
    684 	if (self->flags & FL_FLY)
    685 	{
    686 		if (random() < 0.3)
    687 			self->monsterinfo.attack_state = AS_SLIDING;
    688 		else
    689 			self->monsterinfo.attack_state = AS_STRAIGHT;
    690 	}
    691 
    692 	return false;
    693 }
    694 
    695 
    696 /*
    697 =============
    698 ai_run_melee
    699 
    700 Turn and close until within an angle to launch a melee attack
    701 =============
    702 */
    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 */
    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 */
    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 */
    771 qboolean ai_checkattack (edict_t *self, float dist)
    772 {
    773 	vec3_t		temp;
    774 	qboolean	hesDeadJim;
    775 
    776 // this causes monsters to run blindly to the combat point w/o firing
    777 	if (self->goalentity)
    778 	{
    779 		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
    780 			return false;
    781 
    782 		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
    783 		{
    784 			if ((level.time - self->enemy->teleport_time) > 5.0)
    785 			{
    786 				if (self->goalentity == self->enemy)
    787 					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 false;
    799 			}
    800 		}
    801 	}
    802 
    803 	enemy_vis = false;
    804 
    805 // see if the enemy is dead
    806 	hesDeadJim = false;
    807 	if ((!self->enemy) || (!self->enemy->inuse))
    808 	{
    809 		hesDeadJim = true;
    810 	}
    811 	else if (self->monsterinfo.aiflags & AI_MEDIC)
    812 	{
    813 		if (self->enemy->health > 0)
    814 		{
    815 			hesDeadJim = true;
    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 = true;
    825 		}
    826 		else
    827 		{
    828 			if (self->enemy->health <= 0)
    829 				hesDeadJim = true;
    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 true;
    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 		VectorCopy (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 true;
    878 //	}
    879 
    880 	enemy_infront = infront(self, self->enemy);
    881 	enemy_range = range(self, self->enemy);
    882 	VectorSubtract (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 true;
    892 	}
    893 	if (self->monsterinfo.attack_state == AS_MELEE)
    894 	{
    895 		ai_run_melee (self);
    896 		return true;
    897 	}
    898 
    899 	// if enemy is not currently visible, we will never attack
    900 	if (!enemy_vis)
    901 		return false;
    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 */
    914 void ai_run (edict_t *self, float dist)
    915 {
    916 	vec3_t		v;
    917 	edict_t		*tempgoal;
    918 	edict_t		*save;
    919 	qboolean	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 		VectorSubtract (self->s.origin, self->enemy->s.origin, v);
    937 		if (VectorLength(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 		VectorCopy (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->value)
    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 = false;
    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 = true;
    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 			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
   1014 			new = true;
   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 			VectorCopy (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 = true;
   1035 		}
   1036 	}
   1037 
   1038 	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
   1039 	d1 = VectorLength(v);
   1040 	if (d1 <= dist)
   1041 	{
   1042 		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
   1043 		dist = d1;
   1044 	}
   1045 
   1046 	VectorCopy (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 			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
   1056 			d1 = VectorLength(v);
   1057 			center = tr.fraction;
   1058 			d2 = d1 * ((center+1)/2);
   1059 			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
   1060 			AngleVectors(self->s.angles, v_forward, v_right, NULL);
   1061 
   1062 			VectorSet(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 			VectorSet(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 					VectorSet(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 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
   1082 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
   1083 				VectorCopy (left_target, self->goalentity->s.origin);
   1084 				VectorCopy (left_target, self->monsterinfo.last_sighting);
   1085 				VectorSubtract (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 				{
   1094 					VectorSet(v, d2 * right * 0.5, 16, 0);
   1095 					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
   1096 //					gi.dprintf("incomplete path, go part way and adjust again\n");
   1097 				}
   1098 				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
   1099 				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
   1100 				VectorCopy (right_target, self->goalentity->s.origin);
   1101 				VectorCopy (right_target, self->monsterinfo.last_sighting);
   1102 				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
   1103 				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
   1104 //				gi.dprintf("adjusted right\n");
   1105 //				debug_drawline(self.origin, self.last_sighting, 152);
   1106 			}
   1107 		}
   1108 //		else gi.dprintf("course was fine\n");
   1109 	}
   1110 
   1111 	M_MoveToGoal (self, dist);
   1112 
   1113 	G_FreeEdict(tempgoal);
   1114 
   1115 	if (self)
   1116 		self->goalentity = save;
   1117 }