1 /*
2 ==============================================================================
3 
4 MEDIC
5 
6 ==============================================================================
7 */
8 
9 #include "g_local.h"
10 #include "m_medic.h"
11 
12 #define	MEDIC_MIN_DISTANCE	32
13 #define MEDIC_MAX_HEAL_DISTANCE	400
14 #define	MEDIC_TRY_TIME		10.0
15 
16 // FIXME -
17 //
18 // owner moved to monsterinfo.healer instead
19 //
20 // For some reason, the healed monsters are rarely ending up in the floor
21 //
22 // 5/15/1998 I think I fixed these, keep an eye on them
23 
24 qboolean visible (edict_t *self, edict_t *other);
25 void M_SetEffects (edict_t *ent);
26 qboolean FindTarget (edict_t *self);
27 void HuntTarget (edict_t *self);
28 void FoundTarget (edict_t *self);
29 char *ED_NewString (char *string);
30 void spawngrow_think (edict_t *self);
31 void SpawnGrow_Spawn (vec3_t startpos, int size);
32 void ED_CallSpawn (edict_t *ent);
33 void M_FliesOff (edict_t *self);
34 void M_FliesOn (edict_t *self);
35 
36 
37 static int	sound_idle1;
38 static int	sound_pain1;
39 static int	sound_pain2;
40 static int	sound_die;
41 static int	sound_sight;
42 static int	sound_search;
43 static int	sound_hook_launch;
44 static int	sound_hook_hit;
45 static int	sound_hook_heal;
46 static int	sound_hook_retract;
47 
48 // PMM - commander sounds
49 static int	commander_sound_idle1;
50 static int	commander_sound_pain1;
51 static int	commander_sound_pain2;
52 static int	commander_sound_die;
53 static int	commander_sound_sight;
54 static int	commander_sound_search;
55 static int	commander_sound_hook_launch;
56 static int	commander_sound_hook_hit;
57 static int	commander_sound_hook_heal;
58 static int	commander_sound_hook_retract;
59 static int	commander_sound_spawn;
60 
61 char * reinforcements[] = {
62 	{"monster_soldier_light"},	// 0
63 	{"monster_soldier"},		// 1
64 	{"monster_soldier_ss"},		// 2
65 	{"monster_infantry"},		// 3
66 	{"monster_gunner"},			// 4
67 //	{"monster_chick"},			// 4
68 	{"monster_medic"},			// 5
69 	{"monster_gladiator"}		// 6
70 };
71 
72 vec3_t reinforcement_mins[] = {
73 	{-16, -16, -24},
74 	{-16, -16, -24},
75 	{-16, -16, -24},
76 	{-16, -16, -24},
77 	{-16, -16, -24},
78 	{-16, -16, -24},
79 	{-32, -32, -24}
80 };
81 
82 vec3_t reinforcement_maxs[] = {
83 	{16, 16, 32},
84 	{16, 16, 32},
85 	{16, 16, 32},
86 	{16, 16, 32},
87 	{16, 16, 32},
88 	{16, 16, 32},
89 	{32, 32, 64}
90 };
91 
92 vec3_t reinforcement_position[] = {
93 	{80, 0, 0},
94 	{40, 60, 0},
95 	{40, -60, 0},
96 	{0, 80, 0},
97 	{0, -80, 0}
98 };
99 
cleanupHeal(edict_t * self,qboolean change_frame)100 void cleanupHeal (edict_t *self, qboolean change_frame)
101 {
102 	// clean up target, if we have one and it's legit
103 	if (self->enemy && self->enemy->inuse)
104 	{
105 		self->enemy->monsterinfo.healer = NULL;
106 		self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
107 		self->enemy->takedamage = DAMAGE_YES;
108 		M_SetEffects (self->enemy);
109 	}
110 
111 	if (change_frame)
112 		self->monsterinfo.nextframe = FRAME_attack52;
113 }
114 
abortHeal(edict_t * self,qboolean change_frame,qboolean gib,qboolean mark)115 void abortHeal (edict_t *self, qboolean change_frame, qboolean gib, qboolean mark)
116 {
117 	int hurt;
118 	static vec3_t	pain_normal = { 0, 0, 1 };
119 
120 	// clean up target
121 	cleanupHeal (self, change_frame);
122 	// gib em!
123 	if ((mark) && (self->enemy) && (self->enemy->inuse))
124 	{
125 //		if ((g_showlogic) && (g_showlogic->value))
126 //			gi.dprintf ("%s - marking target as bad\n", self->classname);
127 		// if the first badMedic slot is filled by a medic, skip it and use the second one
128 		if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse)
129 			&& (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)) )
130 		{
131 			self->enemy->monsterinfo.badMedic2 = self;
132 		}
133 		else
134 		{
135 			self->enemy->monsterinfo.badMedic1 = self;
136 		}
137 	}
138 	if ((gib) && (self->enemy) && (self->enemy->inuse))
139 	{
140 //		if ((g_showlogic) && (g_showlogic->value))
141 //			gi.dprintf ("%s - gibbing bad heal target", self->classname);
142 
143 		if(self->enemy->gib_health)
144 			hurt = - self->enemy->gib_health;
145 		else
146 			hurt = 500;
147 
148 		T_Damage (self->enemy, self, self, vec3_origin, self->enemy->s.origin,
149 					pain_normal, hurt, 0, 0, MOD_UNKNOWN);
150 	}
151 	// clean up self
152 
153 	self->monsterinfo.aiflags &= ~AI_MEDIC;
154 	if ((self->oldenemy) && (self->oldenemy->inuse))
155 		self->enemy = self->oldenemy;
156 	else
157 		self->enemy = NULL;
158 
159 	self->monsterinfo.medicTries = 0;
160 }
161 
canReach(edict_t * self,edict_t * other)162 qboolean canReach (edict_t *self, edict_t *other)
163 {
164 	vec3_t	spot1;
165 	vec3_t	spot2;
166 	trace_t	trace;
167 
168 	VectorCopy (self->s.origin, spot1);
169 	spot1[2] += self->viewheight;
170 	VectorCopy (other->s.origin, spot2);
171 	spot2[2] += other->viewheight;
172 	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_SHOT|MASK_WATER);
173 
174 	if (trace.fraction == 1.0 || trace.ent == other)		// PGM
175 		return true;
176 	return false;
177 }
178 
medic_FindDeadMonster(edict_t * self)179 edict_t *medic_FindDeadMonster (edict_t *self)
180 {
181 	float	radius;
182 	edict_t	*ent = NULL;
183 	edict_t	*best = NULL;
184 
185 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
186 		radius = MEDIC_MAX_HEAL_DISTANCE;
187 	else
188 		radius = 1024;
189 
190 	while ((ent = findradius(ent, self->s.origin, radius)) != NULL)
191 	{
192 		if (ent == self)
193 			continue;
194 		if (!(ent->svflags & SVF_MONSTER))
195 			continue;
196 		if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
197 			continue;
198 		// check to make sure we haven't bailed on this guy already
199 		if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self))
200 			continue;
201 		if (ent->monsterinfo.healer)
202 			// FIXME - this is correcting a bug that is somewhere else
203 			// if the healer is a monster, and it's in medic mode .. continue .. otherwise
204 			//   we will override the healer, if it passes all the other tests
205 			if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) &&
206 				(ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC))
207 				continue;
208 		if (ent->health > 0)
209 			continue;
210 		if ((ent->nextthink) && !((ent->think == M_FliesOn) || (ent->think == M_FliesOff)))
211 			continue;
212 		if (!visible(self, ent))
213 //		if (!canReach(self, ent))
214 			continue;
215 		if (!strncmp(ent->classname, "player", 6))		 // stop it from trying to heal player_noise entities
216 			continue;
217 		// FIXME - there's got to be a better way ..
218 		// make sure we don't spawn people right on top of us
219 		if (realrange(self, ent) <= MEDIC_MIN_DISTANCE)
220 			continue;
221 		if (!best)
222 		{
223 			best = ent;
224 			continue;
225 		}
226 		if (ent->max_health <= best->max_health)
227 			continue;
228 		best = ent;
229 	}
230 
231 	if (best)
232 		self->timestamp = level.time + MEDIC_TRY_TIME;
233 
234 	return best;
235 }
236 
medic_idle(edict_t * self)237 void medic_idle (edict_t *self)
238 {
239 	edict_t	*ent;
240 
241 	// PMM - commander sounds
242 	if (self->mass == 400)
243 		gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
244 	else
245 		gi.sound (self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0);
246 
247 
248 	if (!self->oldenemy)
249 	{
250 		ent = medic_FindDeadMonster(self);
251 		if (ent)
252 		{
253 			self->oldenemy = self->enemy;
254 			self->enemy = ent;
255 			self->enemy->monsterinfo.healer = self;
256 			self->monsterinfo.aiflags |= AI_MEDIC;
257 			FoundTarget (self);
258 		}
259 	}
260 }
261 
medic_search(edict_t * self)262 void medic_search (edict_t *self)
263 {
264 	edict_t	*ent;
265 
266 	// PMM - commander sounds
267 	if (self->mass == 400)
268 		gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
269 	else
270 		gi.sound (self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0);
271 
272 	if (!self->oldenemy)
273 	{
274 		ent = medic_FindDeadMonster(self);
275 		if (ent)
276 		{
277 			self->oldenemy = self->enemy;
278 			self->enemy = ent;
279 			self->enemy->monsterinfo.healer = self;
280 			self->monsterinfo.aiflags |= AI_MEDIC;
281 			FoundTarget (self);
282 		}
283 	}
284 }
285 
medic_sight(edict_t * self,edict_t * other)286 void medic_sight (edict_t *self, edict_t *other)
287 {
288 	// PMM - commander sounds
289 	if (self->mass == 400)
290 		gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
291 	else
292 		gi.sound (self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0);
293 }
294 
295 
296 mframe_t medic_frames_stand [] =
297 {
298 	ai_stand, 0, medic_idle,
299 	ai_stand, 0, NULL,
300 	ai_stand, 0, NULL,
301 	ai_stand, 0, NULL,
302 	ai_stand, 0, NULL,
303 	ai_stand, 0, NULL,
304 	ai_stand, 0, NULL,
305 	ai_stand, 0, NULL,
306 	ai_stand, 0, NULL,
307 	ai_stand, 0, NULL,
308 	ai_stand, 0, NULL,
309 	ai_stand, 0, NULL,
310 	ai_stand, 0, NULL,
311 	ai_stand, 0, NULL,
312 	ai_stand, 0, NULL,
313 	ai_stand, 0, NULL,
314 	ai_stand, 0, NULL,
315 	ai_stand, 0, NULL,
316 	ai_stand, 0, NULL,
317 	ai_stand, 0, NULL,
318 	ai_stand, 0, NULL,
319 	ai_stand, 0, NULL,
320 	ai_stand, 0, NULL,
321 	ai_stand, 0, NULL,
322 	ai_stand, 0, NULL,
323 	ai_stand, 0, NULL,
324 	ai_stand, 0, NULL,
325 	ai_stand, 0, NULL,
326 	ai_stand, 0, NULL,
327 	ai_stand, 0, NULL,
328 	ai_stand, 0, NULL,
329 	ai_stand, 0, NULL,
330 	ai_stand, 0, NULL,
331 	ai_stand, 0, NULL,
332 	ai_stand, 0, NULL,
333 	ai_stand, 0, NULL,
334 	ai_stand, 0, NULL,
335 	ai_stand, 0, NULL,
336 	ai_stand, 0, NULL,
337 	ai_stand, 0, NULL,
338 	ai_stand, 0, NULL,
339 	ai_stand, 0, NULL,
340 	ai_stand, 0, NULL,
341 	ai_stand, 0, NULL,
342 	ai_stand, 0, NULL,
343 	ai_stand, 0, NULL,
344 	ai_stand, 0, NULL,
345 	ai_stand, 0, NULL,
346 	ai_stand, 0, NULL,
347 	ai_stand, 0, NULL,
348 	ai_stand, 0, NULL,
349 	ai_stand, 0, NULL,
350 	ai_stand, 0, NULL,
351 	ai_stand, 0, NULL,
352 	ai_stand, 0, NULL,
353 	ai_stand, 0, NULL,
354 	ai_stand, 0, NULL,
355 	ai_stand, 0, NULL,
356 	ai_stand, 0, NULL,
357 	ai_stand, 0, NULL,
358 	ai_stand, 0, NULL,
359 	ai_stand, 0, NULL,
360 	ai_stand, 0, NULL,
361 	ai_stand, 0, NULL,
362 	ai_stand, 0, NULL,
363 	ai_stand, 0, NULL,
364 	ai_stand, 0, NULL,
365 	ai_stand, 0, NULL,
366 	ai_stand, 0, NULL,
367 	ai_stand, 0, NULL,
368 	ai_stand, 0, NULL,
369 	ai_stand, 0, NULL,
370 	ai_stand, 0, NULL,
371 	ai_stand, 0, NULL,
372 	ai_stand, 0, NULL,
373 	ai_stand, 0, NULL,
374 	ai_stand, 0, NULL,
375 	ai_stand, 0, NULL,
376 	ai_stand, 0, NULL,
377 	ai_stand, 0, NULL,
378 	ai_stand, 0, NULL,
379 	ai_stand, 0, NULL,
380 	ai_stand, 0, NULL,
381 	ai_stand, 0, NULL,
382 	ai_stand, 0, NULL,
383 	ai_stand, 0, NULL,
384 	ai_stand, 0, NULL,
385 	ai_stand, 0, NULL,
386 	ai_stand, 0, NULL,
387 	ai_stand, 0, NULL,
388 
389 };
390 mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL};
391 
medic_stand(edict_t * self)392 void medic_stand (edict_t *self)
393 {
394 	self->monsterinfo.currentmove = &medic_move_stand;
395 }
396 
397 
398 mframe_t medic_frames_walk [] =
399 {
400 	ai_walk, 6.2,	NULL,
401 	ai_walk, 18.1,  NULL,
402 	ai_walk, 1,		NULL,
403 	ai_walk, 9,		NULL,
404 	ai_walk, 10,	NULL,
405 	ai_walk, 9,		NULL,
406 	ai_walk, 11,	NULL,
407 	ai_walk, 11.6,  NULL,
408 	ai_walk, 2,		NULL,
409 	ai_walk, 9.9,	NULL,
410 	ai_walk, 14,	NULL,
411 	ai_walk, 9.3,	NULL
412 };
413 mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL};
414 
medic_walk(edict_t * self)415 void medic_walk (edict_t *self)
416 {
417 	self->monsterinfo.currentmove = &medic_move_walk;
418 }
419 
420 
421 mframe_t medic_frames_run [] =
422 {
423 	ai_run, 18,		NULL,
424 	ai_run, 22.5,	NULL,
425 	ai_run, 25.4,	monster_done_dodge,
426 	ai_run, 23.4,	NULL,
427 	ai_run, 24,		NULL,
428 	ai_run, 35.6,	NULL		//pmm
429 
430 };
431 mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL};
432 
medic_run(edict_t * self)433 void medic_run (edict_t *self)
434 {
435 	monster_done_dodge (self);
436 	if (!(self->monsterinfo.aiflags & AI_MEDIC))
437 	{
438 		edict_t	*ent;
439 
440 		ent = medic_FindDeadMonster(self);
441 		if (ent)
442 		{
443 			self->oldenemy = self->enemy;
444 			self->enemy = ent;
445 			self->enemy->monsterinfo.healer = self;
446 			self->monsterinfo.aiflags |= AI_MEDIC;
447 			FoundTarget (self);
448 			return;
449 		}
450 	}
451 //	else if (!canReach(self, self->enemy))
452 //	{
453 //		abortHeal (self, 0);
454 //	}
455 
456 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
457 		self->monsterinfo.currentmove = &medic_move_stand;
458 	else
459 		self->monsterinfo.currentmove = &medic_move_run;
460 }
461 
462 
463 mframe_t medic_frames_pain1 [] =
464 {
465 	ai_move, 0, NULL,
466 	ai_move, 0, NULL,
467 	ai_move, 0, NULL,
468 	ai_move, 0, NULL,
469 	ai_move, 0, NULL,
470 	ai_move, 0, NULL,
471 	ai_move, 0, NULL,
472 	ai_move, 0, NULL
473 };
474 mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run};
475 
476 mframe_t medic_frames_pain2 [] =
477 {
478 	ai_move, 0, NULL,
479 	ai_move, 0, NULL,
480 	ai_move, 0, NULL,
481 	ai_move, 0, NULL,
482 	ai_move, 0, NULL,
483 	ai_move, 0, NULL,
484 	ai_move, 0, NULL,
485 	ai_move, 0, NULL,
486 	ai_move, 0, NULL,
487 	ai_move, 0, NULL,
488 	ai_move, 0, NULL,
489 	ai_move, 0, NULL,
490 	ai_move, 0, NULL,
491 	ai_move, 0, NULL,
492 	ai_move, 0, NULL
493 };
494 mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run};
495 
medic_pain(edict_t * self,edict_t * other,float kick,int damage)496 void medic_pain (edict_t *self, edict_t *other, float kick, int damage)
497 {
498 	monster_done_dodge (self);
499 
500 	if ((self->health < (self->max_health / 2)))
501 		if (self->mass > 400)
502 			self->s.skinnum = 3;
503 		else
504 			self->s.skinnum = 1;
505 
506 	if (level.time < self->pain_debounce_time)
507 		return;
508 
509 	self->pain_debounce_time = level.time + 3;
510 
511 	if (skill->value == 3)
512 		return;		// no pain anims in nightmare
513 
514 	// if we're healing someone, we ignore pain
515 	if (self->monsterinfo.aiflags & AI_MEDIC)
516 		return;
517 
518 	if (self->mass > 400)
519 	{
520 		if (damage < 35)
521 		{
522 			gi.sound (self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0);
523 			return;
524 		}
525 
526 		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
527 		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
528 
529 		gi.sound (self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0);
530 
531 		if (random() < (min(((float)damage * 0.005), 0.5)))		// no more than 50% chance of big pain
532 			self->monsterinfo.currentmove = &medic_move_pain2;
533 		else
534 			self->monsterinfo.currentmove = &medic_move_pain1;
535 	}
536 	else if (random() < 0.5)
537 	{
538 		self->monsterinfo.currentmove = &medic_move_pain1;
539 		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
540 	}
541 	else
542 	{
543 		self->monsterinfo.currentmove = &medic_move_pain2;
544 		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
545 	}
546 	// PMM - clear duck flag
547 	if (self->monsterinfo.aiflags & AI_DUCKED)
548 		monster_duck_up(self);
549 }
550 
medic_fire_blaster(edict_t * self)551 void medic_fire_blaster (edict_t *self)
552 {
553 	vec3_t	start;
554 	vec3_t	forward, right;
555 	vec3_t	end;
556 	vec3_t	dir;
557 	int		effect;
558 	int		damage = 2;
559 
560 	// paranoia checking
561 	if (!(self->enemy && self->enemy->inuse))
562 		return;
563 
564 	if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
565 		effect = EF_BLASTER;
566 	else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28))
567 		effect = EF_HYPERBLASTER;
568 	else
569 		effect = 0;
570 
571 	AngleVectors (self->s.angles, forward, right, NULL);
572 	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start);
573 
574 	VectorCopy (self->enemy->s.origin, end);
575 	end[2] += self->enemy->viewheight;
576 	VectorSubtract (end, start, dir);
577 
578 	if (!strcmp(self->enemy->classname, "tesla"))
579 		damage = 3;
580 
581 	// medic commander shoots blaster2
582 	if (self->mass > 400)
583 		monster_fire_blaster2 (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_2, effect);
584 	else
585 		monster_fire_blaster (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_1, effect);
586 }
587 
medic_dead(edict_t * self)588 void medic_dead (edict_t *self)
589 {
590 	VectorSet (self->mins, -16, -16, -24);
591 	VectorSet (self->maxs, 16, 16, -8);
592 	self->movetype = MOVETYPE_TOSS;
593 	self->svflags |= SVF_DEADMONSTER;
594 	self->nextthink = 0;
595 	gi.linkentity (self);
596 }
597 
598 mframe_t medic_frames_death [] =
599 {
600 	ai_move, 0, NULL,
601 	ai_move, 0, NULL,
602 	ai_move, 0, NULL,
603 	ai_move, 0, NULL,
604 	ai_move, 0, NULL,
605 	ai_move, 0, NULL,
606 	ai_move, 0, NULL,
607 	ai_move, 0, NULL,
608 	ai_move, 0, NULL,
609 	ai_move, 0, NULL,
610 	ai_move, 0, NULL,
611 	ai_move, 0, NULL,
612 	ai_move, 0, NULL,
613 	ai_move, 0, NULL,
614 	ai_move, 0, NULL,
615 	ai_move, 0, NULL,
616 	ai_move, 0, NULL,
617 	ai_move, 0, NULL,
618 	ai_move, 0, NULL,
619 	ai_move, 0, NULL,
620 	ai_move, 0, NULL,
621 	ai_move, 0, NULL,
622 	ai_move, 0, NULL,
623 	ai_move, 0, NULL,
624 	ai_move, 0, NULL,
625 	ai_move, 0, NULL,
626 	ai_move, 0, NULL,
627 	ai_move, 0, NULL,
628 	ai_move, 0, NULL,
629 	ai_move, 0, NULL
630 };
631 mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead};
632 
medic_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)633 void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
634 {
635 	int		n;
636 
637 	// if we had a pending patient, he was already freed up in Killed
638 
639 // check for gib
640 	if (self->health <= self->gib_health)
641 	{
642 		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
643 		for (n= 0; n < 2; n++)
644 			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
645 		for (n= 0; n < 4; n++)
646 			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
647 		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
648 		self->deadflag = DEAD_DEAD;
649 		return;
650 	}
651 
652 	if (self->deadflag == DEAD_DEAD)
653 		return;
654 
655 // regular death
656 	//	PMM
657 	if (self->mass == 400)
658 		gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
659 	else
660 		gi.sound (self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0);
661 	//
662 	self->deadflag = DEAD_DEAD;
663 	self->takedamage = DAMAGE_YES;
664 
665 	self->monsterinfo.currentmove = &medic_move_death;
666 }
667 
668 mframe_t medic_frames_duck [] =
669 {
670 	ai_move, -1,	NULL,
671 	ai_move, -1,	NULL,
672 	ai_move, -1,	monster_duck_down,
673 	ai_move, -1,	monster_duck_hold,
674 	ai_move, -1,	NULL,
675 	ai_move, -1,	NULL,
676 	ai_move, -1,	NULL,		// PMM - duck up used to be here
677 	ai_move, -1,	NULL,
678 	ai_move, -1,	NULL,
679 	ai_move, -1,	NULL,
680 	ai_move, -1,	NULL,
681 	ai_move, -1,	NULL,
682 	ai_move, -1,	NULL,
683 	ai_move, -1,	monster_duck_up,
684 	ai_move, -1,	NULL,
685 	ai_move, -1,	NULL
686 };
687 mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run};
688 
689 // PMM -- moved dodge code to after attack code so I can reference attack frames
690 
691 mframe_t medic_frames_attackHyperBlaster [] =
692 {
693 	ai_charge, 0,	NULL,
694 	ai_charge, 0,	NULL,
695 	ai_charge, 0,	NULL,
696 	ai_charge, 0,	NULL,
697 	ai_charge, 0,	medic_fire_blaster,
698 	ai_charge, 0,	medic_fire_blaster,
699 	ai_charge, 0,	medic_fire_blaster,
700 	ai_charge, 0,	medic_fire_blaster,
701 	ai_charge, 0,	medic_fire_blaster,
702 	ai_charge, 0,	medic_fire_blaster,
703 	ai_charge, 0,	medic_fire_blaster,
704 	ai_charge, 0,	medic_fire_blaster,
705 	ai_charge, 0,	medic_fire_blaster,
706 	ai_charge, 0,	medic_fire_blaster,
707 	ai_charge, 0,	medic_fire_blaster,
708 	ai_charge, 0,	medic_fire_blaster
709 };
710 mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run};
711 
712 
medic_continue(edict_t * self)713 void medic_continue (edict_t *self)
714 {
715 	if (visible (self, self->enemy) )
716 		if (random() <= 0.95)
717 			self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
718 }
719 
720 
721 mframe_t medic_frames_attackBlaster [] =
722 {
723 	ai_charge, 0,	NULL,
724 	ai_charge, 5,	NULL,
725 	ai_charge, 5,	NULL,
726 	ai_charge, 3,	NULL,
727 	ai_charge, 2,	NULL,
728 	ai_charge, 0,	NULL,
729 	ai_charge, 0,	NULL,
730 	ai_charge, 0,	NULL,
731 	ai_charge, 0,	medic_fire_blaster,
732 	ai_charge, 0,	NULL,
733 	ai_charge, 0,	NULL,
734 	ai_charge, 0,	medic_fire_blaster,
735 	ai_charge, 0,	NULL,
736 	ai_charge, 0,	medic_continue	// Change to medic_continue... Else, go to frame 32
737 };
738 mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run};
739 
740 
medic_hook_launch(edict_t * self)741 void medic_hook_launch (edict_t *self)
742 {
743 	// PMM - commander sounds
744 	if (self->mass == 400)
745 		gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
746 	else
747 		gi.sound (self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0);
748 }
749 
750 static vec3_t	medic_cable_offsets[] =
751 {
752 	45.0,  -9.2, 15.5,
753 	48.4,  -9.7, 15.2,
754 	47.8,  -9.8, 15.8,
755 	47.3,  -9.3, 14.3,
756 	45.4, -10.1, 13.1,
757 	41.9, -12.7, 12.0,
758 	37.8, -15.8, 11.2,
759 	34.3, -18.4, 10.7,
760 	32.7, -19.7, 10.4,
761 	32.7, -19.7, 10.4
762 };
763 
medic_cable_attack(edict_t * self)764 void medic_cable_attack (edict_t *self)
765 {
766 	vec3_t	offset, start, end, f, r;
767 	trace_t	tr;
768 	vec3_t	dir;
769 //	vec3_t  angles;
770 	float	distance;
771 
772 	if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->s.effects & EF_GIB))
773 	{
774 //		if ((g_showlogic) && (g_showlogic->value))
775 //			gi.dprintf ("medic_commander - aborting heal due to target's disappearance\n");
776 		abortHeal (self, true, false, false);
777 		return;
778 	}
779 
780 	// see if our enemy has changed to a client, or our target has more than 0 health,
781 	// abort it .. we got switched to someone else due to damage
782 	if ((self->enemy->client) || (self->enemy->health > 0))
783 	{
784 		abortHeal (self, true, false, false);
785 		return;
786 	}
787 	AngleVectors (self->s.angles, f, r, NULL);
788 	VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
789 	G_ProjectSource (self->s.origin, offset, f, r, start);
790 
791 	// check for max distance
792 	// not needed, done in checkattack
793 	// check for min distance
794 	VectorSubtract (start, self->enemy->s.origin, dir);
795 	distance = VectorLength(dir);
796 	if (distance < MEDIC_MIN_DISTANCE)
797 	{
798 //		if ((g_showlogic) && (g_showlogic->value))
799 //			gi.dprintf ("medic_commander - aborting heal due to proximity to target ");
800 		abortHeal (self, true, true, false );
801 		return;
802 	}
803 //	if ((g_showlogic)&&(g_showlogic->value))
804 //		gi.dprintf ("distance to target is %f\n", distance);
805 //	if (distance > 300)
806 //		return;
807 
808 	// check for min/max pitch
809 	// PMM -- took out since it doesn't look bad when it fails
810 //	vectoangles (dir, angles);
811 //	if (angles[0] < -180)
812 //		angles[0] += 360;
813 //	if (fabs(angles[0]) > 45)
814 //	{
815 //		if ((g_showlogic) && (g_showlogic->value))
816 //			gi.dprintf ("medic_commander - aborting heal due to bad angle\n");
817 //		abortHeal(self);
818 //		return;
819 //	}
820 
821 	tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID);
822 	if (tr.fraction != 1.0 && tr.ent != self->enemy)
823 	{
824 		if (tr.ent == world)
825 		{
826 			// give up on second try
827 			if (self->monsterinfo.medicTries > 1)
828 			{
829 				abortHeal (self, true, false, true);
830 				return;
831 			}
832 			self->monsterinfo.medicTries++;
833 			cleanupHeal (self, 1);
834 			return;
835 		}
836 //		if ((g_showlogic) && (g_showlogic->value))
837 //			gi.dprintf ("medic_commander - aborting heal due to beamus interruptus\n");
838 		abortHeal (self, true, false, false);
839 		return;
840 	}
841 
842 	if (self->s.frame == FRAME_attack43)
843 	{
844 		// PMM - commander sounds
845 		if (self->mass == 400)
846 			gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
847 		else
848 			gi.sound (self->enemy, CHAN_AUTO, commander_sound_hook_hit, 1, ATTN_NORM, 0);
849 
850 		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
851 		self->enemy->takedamage = DAMAGE_NO;
852 		M_SetEffects (self->enemy);
853 	}
854 	else if (self->s.frame == FRAME_attack50)
855 	{
856 		vec3_t	maxs;
857 		self->enemy->spawnflags = 0;
858 		self->enemy->monsterinfo.aiflags = 0;
859 		self->enemy->target = NULL;
860 		self->enemy->targetname = NULL;
861 		self->enemy->combattarget = NULL;
862 		self->enemy->deathtarget = NULL;
863 		self->enemy->monsterinfo.healer = self;
864 
865 		VectorCopy (self->enemy->maxs, maxs);
866 		maxs[2] += 48;   // compensate for change when they die
867 
868 		tr = gi.trace (self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID);
869 		if (tr.startsolid || tr.allsolid)
870 		{
871 //			if ((g_showlogic) && (g_showlogic->value))
872 //				gi.dprintf ("Spawn point obstructed, aborting heal!\n");
873 			abortHeal (self, true, true, false);
874 			return;
875 		}
876 		else if (tr.ent != world)
877 		{
878 //			if ((g_showlogic) && (g_showlogic->value))
879 //				gi.dprintf("heal in entity %s\n", tr.ent->classname);
880 			abortHeal (self, true, true, false);
881 			return;
882 		}
883 /*		else if (tr.ent == world)
884 		{
885 			if ((g_showlogic) && (g_showlogic->value))
886 				gi.dprintf ("heal in world, aborting!\n");
887 			abortHeal (self, 1);
888 			return;
889 		}
890 */		else
891 		{
892 			self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
893 			ED_CallSpawn (self->enemy);
894 
895 			if (self->enemy->think)
896 			{
897 				self->enemy->nextthink = level.time;
898 				self->enemy->think (self->enemy);
899 			}
900 	//		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
901 			self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
902 			self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT;
903 			// turn off flies
904 			self->enemy->s.effects &= ~EF_FLIES;
905 			self->enemy->monsterinfo.healer = NULL;
906 
907 			if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0))
908 			{
909 //				if ((g_showlogic) && (g_showlogic->value))
910 //					gi.dprintf ("setting heal target's enemy to %s\n", self->oldenemy->classname);
911 				self->enemy->enemy = self->oldenemy;
912 //				HuntTarget (self->enemy);
913 				FoundTarget (self->enemy);
914 			}
915 			else
916 			{
917 //				if (g_showlogic && g_showlogic->value)
918 //					gi.dprintf ("no valid enemy to set!\n");
919 				self->enemy->enemy = NULL;
920 				if (!FindTarget (self->enemy))
921 				{
922 					// no valid enemy, so stop acting
923 					self->enemy->monsterinfo.pausetime = level.time + 100000000;
924 					self->enemy->monsterinfo.stand (self->enemy);
925 				}
926 				self->enemy = NULL;
927 				self->oldenemy = NULL;
928 				if (!FindTarget (self))
929 				{
930 					// no valid enemy, so stop acting
931 					self->monsterinfo.pausetime = level.time + 100000000;
932 					self->monsterinfo.stand (self);
933 					return;
934 				}
935 			}
936 		}
937 	}
938 	else
939 	{
940 		if (self->s.frame == FRAME_attack44)
941 			// PMM - medic commander sounds
942 			if (self->mass == 400)
943 				gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
944 			else
945 				gi.sound (self, CHAN_WEAPON, commander_sound_hook_heal, 1, ATTN_NORM, 0);
946 	}
947 
948 	// adjust start for beam origin being in middle of a segment
949 	VectorMA (start, 8, f, start);
950 
951 	// adjust end z for end spot since the monster is currently dead
952 	VectorCopy (self->enemy->s.origin, end);
953 	end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
954 
955 	gi.WriteByte (svc_temp_entity);
956 	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
957 	gi.WriteShort (self - g_edicts);
958 	gi.WritePosition (start);
959 	gi.WritePosition (end);
960 	gi.multicast (self->s.origin, MULTICAST_PVS);
961 }
962 
medic_hook_retract(edict_t * self)963 void medic_hook_retract (edict_t *self)
964 {
965 	if (self->mass == 400)
966 		gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
967 	else
968 		gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
969 
970 	self->monsterinfo.aiflags &= ~AI_MEDIC;
971 	if ((self->oldenemy) && (self->oldenemy->inuse))
972 		self->enemy = self->oldenemy;
973 	else
974 	{
975 		self->enemy = NULL;
976 		self->oldenemy = NULL;
977 		if (!FindTarget (self))
978 		{
979 			// no valid enemy, so stop acting
980 			self->monsterinfo.pausetime = level.time + 100000000;
981 			self->monsterinfo.stand (self);
982 			return;
983 		}
984 	}
985 }
986 
987 mframe_t medic_frames_attackCable [] =
988 {
989 // ROGUE - negated 36-40 so he scoots back from his target a little
990 // ROGUE - switched 33-36 to ai_charge
991 // ROGUE - changed frame 52 to 0 to compensate for changes in 36-40
992 	ai_charge, 2,		NULL,					//33
993 	ai_charge, 3,		NULL,
994 	ai_charge, 5,		NULL,
995 	ai_charge, -4.4,	NULL,					//36
996 	ai_charge, -4.7,	NULL,					//37
997 	ai_charge, -5,	NULL,
998 	ai_charge, -6,	NULL,
999 	ai_charge, -4,	NULL,					//40
1000 	ai_charge, 0,	NULL,
1001 	ai_move, 0,		medic_hook_launch,		//42
1002 	ai_move, 0,		medic_cable_attack,		//43
1003 	ai_move, 0,		medic_cable_attack,
1004 	ai_move, 0,		medic_cable_attack,
1005 	ai_move, 0,		medic_cable_attack,
1006 	ai_move, 0,		medic_cable_attack,
1007 	ai_move, 0,		medic_cable_attack,
1008 	ai_move, 0,		medic_cable_attack,
1009 	ai_move, 0,		medic_cable_attack,
1010 	ai_move, 0,		medic_cable_attack,		//51
1011 	ai_move, 0,		medic_hook_retract,		//52
1012 	ai_move, -1.5,	NULL,
1013 	ai_move, -1.2,	NULL,
1014 	ai_move, -3,	NULL,
1015 	ai_move, -2,	NULL,
1016 	ai_move, 0.3,	NULL,
1017 	ai_move, 0.7,	NULL,
1018 	ai_move, 1.2,	NULL,
1019 	ai_move, 1.3,	NULL					//60
1020 };
1021 mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run};
1022 
1023 
medic_start_spawn(edict_t * self)1024 void medic_start_spawn (edict_t *self)
1025 {
1026 	gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
1027 	self->monsterinfo.nextframe = FRAME_attack48;
1028 }
1029 
medic_determine_spawn(edict_t * self)1030 void medic_determine_spawn (edict_t *self)
1031 {
1032 	vec3_t	f, r, offset, startpoint, spawnpoint;
1033 	float	lucky;
1034 	int		summonStr;
1035 	int		count;
1036 	int		inc;
1037 	int		num_summoned; // should be 1, 3, or 5
1038 	int		num_success = 0;
1039 
1040 	lucky = random();
1041 	summonStr = skill->value;
1042 
1043 	// bell curve - 0 = 67%, 1 = 93%, 2 = 99% -- too steep
1044 	// this ends up with
1045 	// -3 = 5%
1046 	// -2 = 10%
1047 	// -1 = 15%
1048 	// 0  = 40%
1049 	// +1 = 15%
1050 	// +2 = 10%
1051 	// +3 = 5%
1052 	if (lucky < 0.05)
1053 		summonStr -= 3;
1054 	else if (lucky < 0.15)
1055 		summonStr -= 2;
1056 	else if (lucky < 0.3)
1057 		summonStr -= 1;
1058 	else if (lucky > 0.95)
1059 		summonStr += 3;
1060 	else if (lucky > 0.85)
1061 		summonStr += 2;
1062 	else if (lucky > 0.7)
1063 		summonStr += 1;
1064 
1065 	if (summonStr < 0)
1066 		summonStr = 0;
1067 
1068 	//FIXME - need to remember this, might as well use this int that isn't used for monsters
1069 	self->plat2flags = summonStr;
1070 	AngleVectors (self->s.angles, f, r, NULL);
1071 
1072 // this yields either 1, 3, or 5
1073 	if (summonStr)
1074 		num_summoned = (summonStr - 1) + (summonStr % 2);
1075 	else
1076 		num_summoned = 1;
1077 
1078 //	if ((g_showlogic) && (g_showlogic->value))
1079 //		gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
1080 
1081 	for (count = 0; count < num_summoned; count++)
1082 	{
1083 		inc = count + (count%2); // 0, 2, 2, 4, 4
1084 		VectorCopy (reinforcement_position[count], offset);
1085 
1086 		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
1087 		// a little off the ground
1088 		startpoint[2] += 10;
1089 
1090 		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
1091 		{
1092 			if (CheckGroundSpawnPoint(spawnpoint,
1093 				reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
1094 				256, -1))
1095 			{
1096 				num_success++;
1097 				// we found a spot, we're done here
1098 				count = num_summoned;
1099 			}
1100 //			else if ((g_showlogic) && (g_showlogic->value))
1101 //				gi.dprintf ("medic_commander: CheckGroundSpawnPoint says bad stuff down there!\n");
1102 		}
1103 	}
1104 
1105 	if (num_success == 0)
1106 	{
1107 		for (count = 0; count < num_summoned; count++)
1108 		{
1109 			inc = count + (count%2); // 0, 2, 2, 4, 4
1110 			VectorCopy (reinforcement_position[count], offset);
1111 
1112 			// check behind
1113 			offset[0] *= -1.0;
1114 			offset[1] *= -1.0;
1115 			G_ProjectSource (self->s.origin, offset, f, r, startpoint);
1116 			// a little off the ground
1117 			startpoint[2] += 10;
1118 
1119 			if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
1120 			{
1121 				if (CheckGroundSpawnPoint(spawnpoint,
1122 					reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
1123 					256, -1))
1124 				{
1125 					num_success++;
1126 					// we found a spot, we're done here
1127 					count = num_summoned;
1128 				}
1129 			}
1130 		}
1131 
1132 		if (num_success)
1133 		{
1134 			self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
1135 			self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180;
1136 			if (self->ideal_yaw > 360.0)
1137 				self->ideal_yaw -= 360.0;
1138 //			self->plat2flags *= -1;
1139 		}
1140 	}
1141 
1142 	if (num_success == 0)
1143 	{
1144 //		if ((g_showlogic) && (g_showlogic->value))
1145 //			gi.dprintf ("medic_commander: failed to find any spawn points, aborting!\n");
1146 		self->monsterinfo.nextframe = FRAME_attack53;
1147 	}
1148 }
1149 
medic_spawngrows(edict_t * self)1150 void medic_spawngrows (edict_t *self)
1151 {
1152 	vec3_t	f, r, offset, startpoint, spawnpoint;
1153 	int		summonStr;
1154 	int		count;
1155 	int		inc;
1156 	int		num_summoned; // should be 1, 3, or 5
1157 	int		num_success = 0;
1158 	float	current_yaw;
1159 	qboolean	behind = false;
1160 
1161 	// if we've been directed to turn around
1162 	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
1163 	{
1164 		current_yaw = anglemod(self->s.angles[YAW]);
1165 		if (fabs(current_yaw - self->ideal_yaw) > 0.1)
1166 		{
1167 			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
1168 			return;
1169 		}
1170 
1171 		// done turning around
1172 		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
1173 		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1174 	}
1175 
1176 //	if (self->plat2flags < 0)
1177 //	{
1178 //		summonStr = -1.0 * self->plat2flags;
1179 //		behind = true;
1180 //	}
1181 //	else
1182 		summonStr = self->plat2flags;
1183 
1184 	AngleVectors (self->s.angles, f, r, NULL);
1185 
1186 //	num_summoned = ((((summonStr-1)/2)+1)*2)-1;  // this yields either 1, 3, or 5
1187 	if (summonStr)
1188 		num_summoned = (summonStr - 1) + (summonStr % 2);
1189 	else
1190 		num_summoned = 1;
1191 
1192 	for (count = 0; count < num_summoned; count++)
1193 	{
1194 		inc = count + (count%2); // 0, 2, 2, 4, 4
1195 		VectorCopy (reinforcement_position[count], offset);
1196 
1197 		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
1198 		// a little off the ground
1199 		startpoint[2] += 10;
1200 
1201 		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
1202 		{
1203 			if (CheckGroundSpawnPoint(spawnpoint,
1204 				reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
1205 				256, -1))
1206 			{
1207 				num_success++;
1208 				if ((summonStr-inc) > 3)
1209 					SpawnGrow_Spawn (spawnpoint, 1);		// big monster
1210 				else
1211 					SpawnGrow_Spawn (spawnpoint, 0);		// normal size
1212 			}
1213 		}
1214 	}
1215 
1216 	if (num_success == 0)
1217 	{
1218 //		if ((g_showlogic) && (g_showlogic->value))
1219 //			gi.dprintf ("medic_commander: spawngrows bad, aborting!\n");
1220 		self->monsterinfo.nextframe = FRAME_attack53;
1221 	}
1222 }
1223 
medic_finish_spawn(edict_t * self)1224 void medic_finish_spawn (edict_t *self)
1225 {
1226 	edict_t *ent;
1227 	vec3_t	f, r, offset, startpoint, spawnpoint;
1228 	int		summonStr;
1229 	int		count;
1230 	int		inc;
1231 	int		num_summoned; // should be 1, 3, or 5
1232 	qboolean	behind = false;
1233 	edict_t	*designated_enemy;
1234 
1235 //	trace_t		tr;
1236 //	vec3_t mins, maxs;
1237 
1238 	// this is one bigger than the soldier's real mins .. just for paranoia's sake
1239 //	VectorSet (mins, -17, -17, -25);
1240 //	VectorSet (maxs, 17, 17, 33);
1241 
1242 	//FIXME - better place to store this info?
1243 	if (self->plat2flags < 0)
1244 	{
1245 		behind = true;
1246 		self->plat2flags *= -1;
1247 	}
1248 	summonStr = self->plat2flags;
1249 
1250 	AngleVectors (self->s.angles, f, r, NULL);
1251 
1252 //	num_summoned = ((((summonStr-1)/2)+1)*2)-1;  // this yields either 1, 3, or 5
1253 	if (summonStr)
1254 		num_summoned = (summonStr - 1) + (summonStr % 2);
1255 	else
1256 		num_summoned = 1;
1257 
1258 //	if ((g_showlogic) && (g_showlogic->value))
1259 //		gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
1260 
1261 	for (count = 0; count < num_summoned; count++)
1262 	{
1263 		inc = count + (count%2); // 0, 2, 2, 4, 4
1264 		VectorCopy (reinforcement_position[count], offset);
1265 
1266 		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
1267 
1268 		// a little off the ground
1269 		startpoint[2] += 10;
1270 
1271 		ent = NULL;
1272 		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
1273 		{
1274 			if (CheckSpawnPoint (spawnpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc]))
1275 				ent = CreateGroundMonster (spawnpoint, self->s.angles,
1276 					reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
1277 					reinforcements[summonStr-inc], 256);
1278 //			else if ((g_showlogic) && (g_showlogic->value))
1279 //				gi.dprintf ("CheckSpawnPoint failed volume check!\n");
1280 		}
1281 		else
1282 		{
1283 //			if ((g_showlogic) && (g_showlogic->value))
1284 //				gi.dprintf ("FindSpawnPoint failed to find a point!\n");
1285 		}
1286 
1287 		if (!ent)
1288 		{
1289 //			if ((g_showlogic) && (g_showlogic->value))
1290 //				gi.dprintf ("Spawn point obstructed for %s, aborting!\n", reinforcements[summonStr-inc]);
1291 			continue;
1292 		}
1293 
1294 //		gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
1295 
1296 		if (ent->think)
1297 		{
1298 			ent->nextthink = level.time;
1299 			ent->think (ent);
1300 		}
1301 
1302 		ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT|AI_SPAWNED_MEDIC_C;
1303 		ent->monsterinfo.commander = self;
1304 		self->monsterinfo.monster_slots--;
1305 //		if ((g_showlogic) && (g_showlogic->value))
1306 //			gi.dprintf ("medic_commander: %d slots remaining\n", self->monsterinfo.monster_slots);
1307 
1308 		if (self->monsterinfo.aiflags & AI_MEDIC)
1309 			designated_enemy = self->oldenemy;
1310 		else
1311 			designated_enemy = self->enemy;
1312 
1313 		if (coop && coop->value)
1314 		{
1315 			designated_enemy = PickCoopTarget(ent);
1316 			if (designated_enemy)
1317 			{
1318 				// try to avoid using my enemy
1319 				if (designated_enemy == self->enemy)
1320 				{
1321 					designated_enemy = PickCoopTarget(ent);
1322 					if (designated_enemy)
1323 					{
1324 //						if ((g_showlogic) && (g_showlogic->value))
1325 //						{
1326 //							gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
1327 //							if (designated_enemy->client)
1328 //								gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
1329 //							else
1330 //								gi.dprintf ("NOT A CLIENT\n");
1331 //						}
1332 					}
1333 					else
1334 					{
1335 //						if ((g_showlogic) && (g_showlogic->value))
1336 //							gi.dprintf ("pick coop failed, using my current enemy\n");
1337 						designated_enemy = self->enemy;
1338 					}
1339 				}
1340 			}
1341 			else
1342 			{
1343 //				if ((g_showlogic) && (g_showlogic->value))
1344 //					gi.dprintf ("pick coop failed, using my current enemy\n");
1345 				designated_enemy = self->enemy;
1346 			}
1347 		}
1348 
1349 		if ((designated_enemy) && (designated_enemy->inuse) && (designated_enemy->health > 0))
1350 		{
1351 			// fixme
1352 //			if ((g_showlogic) && (g_showlogic -> value))
1353 //				gi.dprintf  ("setting enemy to %s\n", designated_enemy->classname);
1354 			ent->enemy = designated_enemy;
1355 			FoundTarget (ent);
1356 		}
1357 		else
1358 		{
1359 			ent->enemy = NULL;
1360 			ent->monsterinfo.stand (ent);
1361 		}
1362 //		ent->s.event = EV_PLAYER_TELEPORT;
1363 	}
1364 }
1365 
1366 mframe_t medic_frames_callReinforcements [] =
1367 {
1368 	// ROGUE - 33-36 now ai_charge
1369 	ai_charge, 2,		NULL,					//33
1370 	ai_charge, 3,		NULL,
1371 	ai_charge, 5,		NULL,
1372 	ai_charge, 4.4,	NULL,					//36
1373 	ai_charge, 4.7,	NULL,
1374 	ai_charge, 5,	NULL,
1375 	ai_charge, 6,	NULL,
1376 	ai_charge, 4,	NULL,					//40
1377 	ai_charge, 0,	NULL,
1378 	ai_move, 0,		medic_start_spawn,		//42
1379 	ai_move, 0,		NULL,		//43 -- 43 through 47 are skipped
1380 	ai_move, 0,		NULL,
1381 	ai_move, 0,		NULL,
1382 	ai_move, 0,		NULL,
1383 	ai_move, 0,		NULL,
1384 	ai_move, 0,		medic_determine_spawn,		//48
1385 	ai_charge, 0,		medic_spawngrows,			//49
1386 	ai_move, 0,		NULL,		//50
1387 	ai_move, 0,		NULL,		//51
1388 	ai_move, -15,	medic_finish_spawn,		//52
1389 	ai_move, -1.5,	NULL,
1390 	ai_move, -1.2,	NULL,
1391 	ai_move, -3,	NULL,
1392 	ai_move, -2,	NULL,
1393 	ai_move, 0.3,	NULL,
1394 	ai_move, 0.7,	NULL,
1395 	ai_move, 1.2,	NULL,
1396 	ai_move, 1.3,	NULL					//60
1397 };
1398 mmove_t medic_move_callReinforcements = {FRAME_attack33, FRAME_attack60, medic_frames_callReinforcements, medic_run};
1399 
medic_attack(edict_t * self)1400 void medic_attack(edict_t *self)
1401 {
1402 	int		enemy_range;
1403 	float	r;
1404 
1405 	monster_done_dodge (self);
1406 
1407 	enemy_range = range(self, self->enemy);
1408 
1409 	// signal from checkattack to spawn
1410 	if (self->monsterinfo.aiflags & AI_BLOCKED)
1411 	{
1412 		self->monsterinfo.currentmove = &medic_move_callReinforcements;
1413 		self->monsterinfo.aiflags &= ~AI_BLOCKED;
1414 	}
1415 
1416 	r = random();
1417 	if (self->monsterinfo.aiflags & AI_MEDIC)
1418 	{
1419 		if ((self->mass > 400) && (r > 0.8) && (self->monsterinfo.monster_slots > 2))
1420 			self->monsterinfo.currentmove = &medic_move_callReinforcements;
1421 		else
1422 			self->monsterinfo.currentmove = &medic_move_attackCable;
1423 	}
1424 	else
1425 	{
1426 		if (self->monsterinfo.attack_state == AS_BLIND)
1427 		{
1428 			self->monsterinfo.currentmove = &medic_move_callReinforcements;
1429 			return;
1430 		}
1431 		if ((self->mass > 400) && (r > 0.2) && (enemy_range != RANGE_MELEE) && (self->monsterinfo.monster_slots > 2))
1432 			self->monsterinfo.currentmove = &medic_move_callReinforcements;
1433 		else
1434 			self->monsterinfo.currentmove = &medic_move_attackBlaster;
1435 	}
1436 }
1437 
medic_checkattack(edict_t * self)1438 qboolean medic_checkattack (edict_t *self)
1439 {
1440 	if (self->monsterinfo.aiflags & AI_MEDIC)
1441 	{
1442 		// if our target went away
1443 		if ((!self->enemy) || (!self->enemy->inuse))
1444 		{
1445 //			if (g_showlogic && g_showlogic->value)
1446 //				gi.dprintf ("aborting heal target due to gib\n");
1447 			abortHeal (self, true, false, false);
1448 			return false;
1449 		}
1450 
1451 		// if we ran out of time, give up
1452 		if (self->timestamp < level.time)
1453 		{
1454 //			if (g_showlogic && g_showlogic->value)
1455 //				gi.dprintf ("aborting heal target (%s) due to time\n", self->enemy->classname);
1456 			abortHeal (self, true, false, true);
1457 			self->timestamp = 0;
1458 			return false;
1459 		}
1460 
1461 		if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE+10)
1462 		{
1463 			medic_attack(self);
1464 			return true;
1465 		}
1466 		else
1467 		{
1468 			self->monsterinfo.attack_state = AS_STRAIGHT;
1469 			return false;
1470 		}
1471 	}
1472 
1473 	if (self->enemy->client && !visible (self, self->enemy) && (self->monsterinfo.monster_slots > 2))
1474 	{
1475 		self->monsterinfo.attack_state = AS_BLIND;
1476 		return true;
1477 	}
1478 
1479 	// give a LARGE bias to spawning things when we have room
1480 	// use AI_BLOCKED as a signal to attack to spawn
1481 	if ((random() < 0.8) && (self->monsterinfo.monster_slots > 5) && (realrange(self, self->enemy) > 150))
1482 	{
1483 		self->monsterinfo.aiflags |= AI_BLOCKED;
1484 		self->monsterinfo.attack_state = AS_MISSILE;
1485 		return true;
1486 	}
1487 
1488 	// ROGUE
1489 	// since his idle animation looks kinda bad in combat, if we're not in easy mode, always attack
1490 	// when he's on a combat point
1491 	if (skill->value > 0)
1492 		if (self->monsterinfo.aiflags & AI_STAND_GROUND)
1493 		{
1494 			self->monsterinfo.attack_state = AS_MISSILE;
1495 			return true;
1496 		}
1497 
1498 	return M_CheckAttack (self);
1499 }
1500 /*
1501 void medic_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
1502 {
1503 
1504 	if (random() > 0.25)
1505 		return;
1506 
1507 	if (!self->enemy)
1508 		self->enemy = attacker;
1509 
1510 	self->monsterinfo.currentmove = &medic_move_duck;
1511 
1512 //===========
1513 //PMM - rogue rewrite of dodge code.
1514 	float	r;
1515 	float	height;
1516 	int		shooting = 0;
1517 
1518 	// this needs to be before the AI_MEDIC check, because
1519 	//   a) if AI_MEDIC is set, we should have an enemy anyway and
1520 	//   b) FoundTarget calls medic_run, which can set AI_MEDIC
1521 	if (!self->enemy)
1522 	{
1523 		self->enemy = attacker;
1524 		FoundTarget (self);
1525 	}
1526 
1527 //	don't dodge if you're healing
1528 	if (self->monsterinfo.aiflags & AI_MEDIC)
1529 		return;
1530 
1531 	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
1532 	// seeing numbers like 13 and 14)
1533 	if ((eta < 0.1) || (eta > 5))
1534 		return;
1535 
1536 	r = random();
1537 	if (r > (0.25*((skill->value)+1)))
1538 		return;
1539 
1540 	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
1541 		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
1542 		(self->monsterinfo.currentmove == &medic_move_attackBlaster))
1543 	{
1544 		shooting = 1;
1545 	}
1546 	if (self->monsterinfo.aiflags & AI_DODGING)
1547 	{
1548 		height = self->absmax[2];
1549 	}
1550 	else
1551 	{
1552 		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
1553 	}
1554 
1555 	// check to see if it makes sense to duck
1556 	if (tr->endpos[2] <= height)
1557 	{
1558 		vec3_t right, diff;
1559 		if (shooting)
1560 		{
1561 			self->monsterinfo.attack_state = AS_SLIDING;
1562 			return;
1563 		}
1564 		AngleVectors (self->s.angles, NULL, right, NULL);
1565 		VectorSubtract (tr->endpos, self->s.origin, diff);
1566 		if (DotProduct (right, diff) < 0)
1567 		{
1568 			self->monsterinfo.lefty = 1;
1569 		}
1570 		// if it doesn't sense to duck, try to strafe away
1571 		monster_done_dodge (self);
1572 		self->monsterinfo.currentmove = &medic_move_run;
1573 		self->monsterinfo.attack_state = AS_SLIDING;
1574 		return;
1575 	}
1576 
1577 	if (skill->value == 0)
1578 	{
1579 		self->monsterinfo.currentmove = &medic_move_duck;
1580 		// PMM - stupid dodge
1581 		self->monsterinfo.duck_wait_time = level.time + eta + 1;
1582 		self->monsterinfo.aiflags |= AI_DODGING;
1583 		return;
1584 	}
1585 
1586 	if (!shooting)
1587 	{
1588 		self->monsterinfo.currentmove = &medic_move_duck;
1589 		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
1590 		self->monsterinfo.aiflags |= AI_DODGING;
1591 	}
1592 	return;
1593 
1594 //PMM
1595 //===========
1596 
1597 }
1598 */
MedicCommanderCache(void)1599 void MedicCommanderCache (void)
1600 {
1601 	edict_t	*newEnt;
1602 	int	modelidx;
1603 	int i;
1604 
1605 	//FIXME - better way to do this?  this is quick and dirty
1606 	for (i=0; i < 7; i++)
1607 	{
1608 		newEnt = G_Spawn();
1609 
1610 		VectorCopy(vec3_origin, newEnt->s.origin);
1611 		VectorCopy(vec3_origin, newEnt->s.angles);
1612 		newEnt->classname = ED_NewString (reinforcements[i]);
1613 
1614 		newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
1615 
1616 		ED_CallSpawn(newEnt);
1617 		// FIXME - could copy mins/maxs into reinforcements from here
1618 		G_FreeEdict (newEnt);
1619 	}
1620 
1621 	modelidx = gi.modelindex("models/items/spawngro/tris.md2");
1622 	modelidx = gi.modelindex("models/items/spawngro2/tris.md2");
1623 }
1624 
medic_duck(edict_t * self,float eta)1625 void medic_duck (edict_t *self, float eta)
1626 {
1627 //	don't dodge if you're healing
1628 	if (self->monsterinfo.aiflags & AI_MEDIC)
1629 		return;
1630 
1631 	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
1632 		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
1633 		(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
1634 		(self->monsterinfo.currentmove == &medic_move_callReinforcements))
1635 	{
1636 		// he ignores skill
1637 		self->monsterinfo.aiflags &= ~AI_DUCKED;
1638 		return;
1639 	}
1640 
1641 	if (skill->value == 0)
1642 		// PMM - stupid dodge
1643 		self->monsterinfo.duck_wait_time = level.time + eta + 1;
1644 	else
1645 		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
1646 
1647 	// has to be done immediately otherwise he can get stuck
1648 	monster_duck_down(self);
1649 
1650 	self->monsterinfo.nextframe = FRAME_duck1;
1651 	self->monsterinfo.currentmove = &medic_move_duck;
1652 	return;
1653 }
1654 
medic_sidestep(edict_t * self)1655 void medic_sidestep (edict_t *self)
1656 {
1657 	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
1658 		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
1659 		(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
1660 		(self->monsterinfo.currentmove == &medic_move_callReinforcements))
1661 	{
1662 		// if we're shooting, and not on easy, don't dodge
1663 		if (skill->value)
1664 		{
1665 			self->monsterinfo.aiflags &= ~AI_DODGING;
1666 			return;
1667 		}
1668 	}
1669 
1670 	if (self->monsterinfo.currentmove != &medic_move_run)
1671 		self->monsterinfo.currentmove = &medic_move_run;
1672 }
1673 
1674 //===========
1675 //PGM
medic_blocked(edict_t * self,float dist)1676 qboolean medic_blocked (edict_t *self, float dist)
1677 {
1678 	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
1679 		return true;
1680 
1681 	if(blocked_checkplat (self, dist))
1682 		return true;
1683 
1684 	return false;
1685 }
1686 //PGM
1687 //===========
1688 
1689 /*QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
1690 */
1691 /*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
1692 */
SP_monster_medic(edict_t * self)1693 void SP_monster_medic (edict_t *self)
1694 {
1695 	if (deathmatch->value)
1696 	{
1697 		G_FreeEdict (self);
1698 		return;
1699 	}
1700 	self->movetype = MOVETYPE_STEP;
1701 	self->solid = SOLID_BBOX;
1702 	self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2");
1703 	VectorSet (self->mins, -24, -24, -24);
1704 	VectorSet (self->maxs, 24, 24, 32);
1705 
1706 //PMM
1707 	if (strcmp(self->classname, "monster_medic_commander") == 0)
1708 	{
1709 		self->health = 600;			//	fixme
1710 		self->gib_health = -130;
1711 		self->mass = 600;
1712 		self->yaw_speed = 40; // default is 20
1713 		MedicCommanderCache();
1714 //		self->s.skinnum = 2;
1715 	}
1716 	else
1717 	{
1718 //PMM
1719 		self->health = 300;
1720 		self->gib_health = -130;
1721 		self->mass = 400;
1722 //		self->s.skinnum = 0;
1723 	}
1724 
1725 	self->pain = medic_pain;
1726 	self->die = medic_die;
1727 
1728 	self->monsterinfo.stand = medic_stand;
1729 	self->monsterinfo.walk = medic_walk;
1730 	self->monsterinfo.run = medic_run;
1731 	// pmm
1732 	self->monsterinfo.dodge = M_MonsterDodge;
1733 	self->monsterinfo.duck = medic_duck;
1734 	self->monsterinfo.unduck = monster_duck_up;
1735 	self->monsterinfo.sidestep = medic_sidestep;
1736 //	self->monsterinfo.dodge = medic_dodge;
1737 	// pmm
1738 	self->monsterinfo.attack = medic_attack;
1739 	self->monsterinfo.melee = NULL;
1740 	self->monsterinfo.sight = medic_sight;
1741 	self->monsterinfo.idle = medic_idle;
1742 	self->monsterinfo.search = medic_search;
1743 	self->monsterinfo.checkattack = medic_checkattack;
1744 	self->monsterinfo.blocked = medic_blocked;
1745 
1746 	gi.linkentity (self);
1747 
1748 	self->monsterinfo.currentmove = &medic_move_stand;
1749 	self->monsterinfo.scale = MODEL_SCALE;
1750 
1751 	walkmonster_start (self);
1752 
1753 	//PMM
1754 	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
1755 
1756 
1757 	if (self->mass > 400)
1758 	{
1759 		self->s.skinnum = 2;
1760 		if (skill->value == 0)
1761 			self->monsterinfo.monster_slots = 3;
1762 		else if (skill->value == 1)
1763 			self->monsterinfo.monster_slots = 4;
1764 		else if (skill->value == 2)
1765 			self->monsterinfo.monster_slots = 6;
1766 		else if (skill->value == 3)
1767 			self->monsterinfo.monster_slots = 6;
1768 		// commander sounds
1769 		commander_sound_idle1 = gi.soundindex ("medic_commander/medidle.wav");
1770 		commander_sound_pain1 = gi.soundindex ("medic_commander/medpain1.wav");
1771 		commander_sound_pain2 = gi.soundindex ("medic_commander/medpain2.wav");
1772 		commander_sound_die = gi.soundindex ("medic_commander/meddeth.wav");
1773 		commander_sound_sight = gi.soundindex ("medic_commander/medsght.wav");
1774 		commander_sound_search = gi.soundindex ("medic_commander/medsrch.wav");
1775 		commander_sound_hook_launch = gi.soundindex ("medic_commander/medatck2c.wav");
1776 		commander_sound_hook_hit = gi.soundindex ("medic_commander/medatck3a.wav");
1777 		commander_sound_hook_heal = gi.soundindex ("medic_commander/medatck4a.wav");
1778 		commander_sound_hook_retract = gi.soundindex ("medic_commander/medatck5a.wav");
1779 		commander_sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav");
1780 		gi.soundindex ("tank/tnkatck3.wav");
1781 	}
1782 	else
1783 	{
1784 		sound_idle1 = gi.soundindex ("medic/idle.wav");
1785 		sound_pain1 = gi.soundindex ("medic/medpain1.wav");
1786 		sound_pain2 = gi.soundindex ("medic/medpain2.wav");
1787 		sound_die = gi.soundindex ("medic/meddeth1.wav");
1788 		sound_sight = gi.soundindex ("medic/medsght1.wav");
1789 		sound_search = gi.soundindex ("medic/medsrch1.wav");
1790 		sound_hook_launch = gi.soundindex ("medic/medatck2.wav");
1791 		sound_hook_hit = gi.soundindex ("medic/medatck3.wav");
1792 		sound_hook_heal = gi.soundindex ("medic/medatck4.wav");
1793 		sound_hook_retract = gi.soundindex ("medic/medatck5.wav");
1794 		gi.soundindex ("medic/medatck1.wav");
1795 
1796 		self->s.skinnum = 0;
1797 	}
1798 	//pmm
1799 }
1800