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 /*
21 ==============================================================================
22 
23 MEDIC
24 
25 ==============================================================================
26 */
27 
28 #include "g_local.h"
29 #include "m_medic.h"
30 
31 qboolean visible (edict_t *self, edict_t *other);
32 
33 
34 static int	sound_idle1;
35 static int	sound_pain1;
36 static int	sound_pain2;
37 static int	sound_die;
38 static int	sound_sight;
39 static int	sound_search;
40 static int	sound_hook_launch;
41 static int	sound_hook_hit;
42 static int	sound_hook_heal;
43 static int	sound_hook_retract;
44 
45 
medic_FindDeadMonster(edict_t * self)46 edict_t *medic_FindDeadMonster (edict_t *self)
47 {
48 	edict_t	*ent = NULL;
49 	edict_t	*best = NULL;
50 
51 	while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
52 	{
53 		if (ent == self)
54 			continue;
55 		if (!(ent->svflags & SVF_MONSTER))
56 			continue;
57 		if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
58 			continue;
59 		if (ent->owner)
60 			continue;
61 		if (ent->health > 0)
62 			continue;
63 		if (ent->nextthink)
64 			continue;
65 		if (!visible(self, ent))
66 			continue;
67 		if (!best)
68 		{
69 			best = ent;
70 			continue;
71 		}
72 		if (ent->max_health <= best->max_health)
73 			continue;
74 		best = ent;
75 	}
76 
77 	return best;
78 }
79 
medic_idle(edict_t * self)80 void medic_idle (edict_t *self)
81 {
82 	edict_t	*ent;
83 
84 	gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
85 
86 	ent = medic_FindDeadMonster(self);
87 	if (ent)
88 	{
89 		self->enemy = ent;
90 		self->enemy->owner = self;
91 		self->monsterinfo.aiflags |= AI_MEDIC;
92 		FoundTarget (self);
93 	}
94 }
95 
medic_search(edict_t * self)96 void medic_search (edict_t *self)
97 {
98 	edict_t	*ent;
99 
100 	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
101 
102 	if (!self->oldenemy)
103 	{
104 		ent = medic_FindDeadMonster(self);
105 		if (ent)
106 		{
107 			self->oldenemy = self->enemy;
108 			self->enemy = ent;
109 			self->enemy->owner = self;
110 			self->monsterinfo.aiflags |= AI_MEDIC;
111 			FoundTarget (self);
112 		}
113 	}
114 }
115 
medic_sight(edict_t * self,edict_t * other)116 void medic_sight (edict_t *self, edict_t *other)
117 {
118 	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
119 }
120 
121 
122 mframe_t medic_frames_stand [] =
123 {
124 	{ai_stand, 0, medic_idle},
125 	{ai_stand, 0, NULL},
126 	{ai_stand, 0, NULL},
127 	{ai_stand, 0, NULL},
128 	{ai_stand, 0, NULL},
129 	{ai_stand, 0, NULL},
130 	{ai_stand, 0, NULL},
131 	{ai_stand, 0, NULL},
132 	{ai_stand, 0, NULL},
133 	{ai_stand, 0, NULL},
134 	{ai_stand, 0, NULL},
135 	{ai_stand, 0, NULL},
136 	{ai_stand, 0, NULL},
137 	{ai_stand, 0, NULL},
138 	{ai_stand, 0, NULL},
139 	{ai_stand, 0, NULL},
140 	{ai_stand, 0, NULL},
141 	{ai_stand, 0, NULL},
142 	{ai_stand, 0, NULL},
143 	{ai_stand, 0, NULL},
144 	{ai_stand, 0, NULL},
145 	{ai_stand, 0, NULL},
146 	{ai_stand, 0, NULL},
147 	{ai_stand, 0, NULL},
148 	{ai_stand, 0, NULL},
149 	{ai_stand, 0, NULL},
150 	{ai_stand, 0, NULL},
151 	{ai_stand, 0, NULL},
152 	{ai_stand, 0, NULL},
153 	{ai_stand, 0, NULL},
154 	{ai_stand, 0, NULL},
155 	{ai_stand, 0, NULL},
156 	{ai_stand, 0, NULL},
157 	{ai_stand, 0, NULL},
158 	{ai_stand, 0, NULL},
159 	{ai_stand, 0, NULL},
160 	{ai_stand, 0, NULL},
161 	{ai_stand, 0, NULL},
162 	{ai_stand, 0, NULL},
163 	{ai_stand, 0, NULL},
164 	{ai_stand, 0, NULL},
165 	{ai_stand, 0, NULL},
166 	{ai_stand, 0, NULL},
167 	{ai_stand, 0, NULL},
168 	{ai_stand, 0, NULL},
169 	{ai_stand, 0, NULL},
170 	{ai_stand, 0, NULL},
171 	{ai_stand, 0, NULL},
172 	{ai_stand, 0, NULL},
173 	{ai_stand, 0, NULL},
174 	{ai_stand, 0, NULL},
175 	{ai_stand, 0, NULL},
176 	{ai_stand, 0, NULL},
177 	{ai_stand, 0, NULL},
178 	{ai_stand, 0, NULL},
179 	{ai_stand, 0, NULL},
180 	{ai_stand, 0, NULL},
181 	{ai_stand, 0, NULL},
182 	{ai_stand, 0, NULL},
183 	{ai_stand, 0, NULL},
184 	{ai_stand, 0, NULL},
185 	{ai_stand, 0, NULL},
186 	{ai_stand, 0, NULL},
187 	{ai_stand, 0, NULL},
188 	{ai_stand, 0, NULL},
189 	{ai_stand, 0, NULL},
190 	{ai_stand, 0, NULL},
191 	{ai_stand, 0, NULL},
192 	{ai_stand, 0, NULL},
193 	{ai_stand, 0, NULL},
194 	{ai_stand, 0, NULL},
195 	{ai_stand, 0, NULL},
196 	{ai_stand, 0, NULL},
197 	{ai_stand, 0, NULL},
198 	{ai_stand, 0, NULL},
199 	{ai_stand, 0, NULL},
200 	{ai_stand, 0, NULL},
201 	{ai_stand, 0, NULL},
202 	{ai_stand, 0, NULL},
203 	{ai_stand, 0, NULL},
204 	{ai_stand, 0, NULL},
205 	{ai_stand, 0, NULL},
206 	{ai_stand, 0, NULL},
207 	{ai_stand, 0, NULL},
208 	{ai_stand, 0, NULL},
209 	{ai_stand, 0, NULL},
210 	{ai_stand, 0, NULL},
211 	{ai_stand, 0, NULL},
212 	{ai_stand, 0, NULL},
213 	{ai_stand, 0, NULL},
214 
215 };
216 mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL};
217 
medic_stand(edict_t * self)218 void medic_stand (edict_t *self)
219 {
220 	self->monsterinfo.currentmove = &medic_move_stand;
221 }
222 
223 
224 mframe_t medic_frames_walk [] =
225 {
226 	{ai_walk, 6.2,	NULL},
227 	{ai_walk, 18.1,  NULL},
228 	{ai_walk, 1,		NULL},
229 	{ai_walk, 9,		NULL},
230 	{ai_walk, 10,	NULL},
231 	{ai_walk, 9,		NULL},
232 	{ai_walk, 11,	NULL},
233 	{ai_walk, 11.6,  NULL},
234 	{ai_walk, 2,		NULL},
235 	{ai_walk, 9.9,	NULL},
236 	{ai_walk, 14,	NULL},
237 	{ai_walk, 9.3,	NULL}
238 };
239 mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL};
240 
medic_walk(edict_t * self)241 void medic_walk (edict_t *self)
242 {
243 	self->monsterinfo.currentmove = &medic_move_walk;
244 }
245 
246 
247 mframe_t medic_frames_run [] =
248 {
249 	{ai_run, 18,		NULL},
250 	{ai_run, 22.5,	NULL},
251 	{ai_run, 25.4,	NULL},
252 	{ai_run, 23.4,	NULL},
253 	{ai_run, 24,		NULL},
254 	{ai_run, 35.6,	NULL}
255 
256 };
257 mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL};
258 
medic_run(edict_t * self)259 void medic_run (edict_t *self)
260 {
261 	if (!(self->monsterinfo.aiflags & AI_MEDIC))
262 	{
263 		edict_t	*ent;
264 
265 		ent = medic_FindDeadMonster(self);
266 		if (ent)
267 		{
268 			self->oldenemy = self->enemy;
269 			self->enemy = ent;
270 			self->enemy->owner = self;
271 			self->monsterinfo.aiflags |= AI_MEDIC;
272 			FoundTarget (self);
273 			return;
274 		}
275 	}
276 
277 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
278 		self->monsterinfo.currentmove = &medic_move_stand;
279 	else
280 		self->monsterinfo.currentmove = &medic_move_run;
281 }
282 
283 
284 mframe_t medic_frames_pain1 [] =
285 {
286 	{ai_move, 0, NULL},
287 	{ai_move, 0, NULL},
288 	{ai_move, 0, NULL},
289 	{ai_move, 0, NULL},
290 	{ai_move, 0, NULL},
291 	{ai_move, 0, NULL},
292 	{ai_move, 0, NULL},
293 	{ai_move, 0, NULL}
294 };
295 mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run};
296 
297 mframe_t medic_frames_pain2 [] =
298 {
299 	{ai_move, 0, NULL},
300 	{ai_move, 0, NULL},
301 	{ai_move, 0, NULL},
302 	{ai_move, 0, NULL},
303 	{ai_move, 0, NULL},
304 	{ai_move, 0, NULL},
305 	{ai_move, 0, NULL},
306 	{ai_move, 0, NULL},
307 	{ai_move, 0, NULL},
308 	{ai_move, 0, NULL},
309 	{ai_move, 0, NULL},
310 	{ai_move, 0, NULL},
311 	{ai_move, 0, NULL},
312 	{ai_move, 0, NULL},
313 	{ai_move, 0, NULL}
314 };
315 mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run};
316 
medic_pain(edict_t * self,edict_t * other,float kick,int damage)317 void medic_pain (edict_t *self, edict_t *other, float kick, int damage)
318 {
319 	if (self->health < (self->max_health / 2))
320 		self->s.skinnum = 1;
321 
322 	if (level.time < self->pain_debounce_time)
323 		return;
324 
325 	self->pain_debounce_time = level.time + 3;
326 
327 	if (skill->value == 3)
328 		return;		// no pain anims in nightmare
329 
330 	if (random() < 0.5)
331 	{
332 		self->monsterinfo.currentmove = &medic_move_pain1;
333 		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
334 	}
335 	else
336 	{
337 		self->monsterinfo.currentmove = &medic_move_pain2;
338 		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
339 	}
340 }
341 
medic_fire_blaster(edict_t * self)342 void medic_fire_blaster (edict_t *self)
343 {
344 	vec3_t	start;
345 	vec3_t	forward, right;
346 	vec3_t	end;
347 	vec3_t	dir;
348 	int		effect;
349 
350 	if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
351 		effect = EF_BLASTER;
352 	else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28))
353 		effect = EF_HYPERBLASTER;
354 	else
355 		effect = 0;
356 
357 	AngleVectors (self->s.angles, forward, right, NULL);
358 	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start);
359 
360 	VectorCopy (self->enemy->s.origin, end);
361 	end[2] += self->enemy->viewheight;
362 	VectorSubtract (end, start, dir);
363 
364 	monster_fire_blaster (self, start, dir, 2, 1000, MZ2_MEDIC_BLASTER_1, effect);
365 }
366 
367 
medic_dead(edict_t * self)368 void medic_dead (edict_t *self)
369 {
370 	VectorSet (self->mins, -16, -16, -24);
371 	VectorSet (self->maxs, 16, 16, -8);
372 	self->movetype = MOVETYPE_TOSS;
373 	self->svflags |= SVF_DEADMONSTER;
374 	self->nextthink = 0;
375 	gi.linkentity (self);
376 }
377 
378 mframe_t medic_frames_death [] =
379 {
380 	{ai_move, 0, NULL},
381 	{ai_move, 0, NULL},
382 	{ai_move, 0, NULL},
383 	{ai_move, 0, NULL},
384 	{ai_move, 0, NULL},
385 	{ai_move, 0, NULL},
386 	{ai_move, 0, NULL},
387 	{ai_move, 0, NULL},
388 	{ai_move, 0, NULL},
389 	{ai_move, 0, NULL},
390 	{ai_move, 0, NULL},
391 	{ai_move, 0, NULL},
392 	{ai_move, 0, NULL},
393 	{ai_move, 0, NULL},
394 	{ai_move, 0, NULL},
395 	{ai_move, 0, NULL},
396 	{ai_move, 0, NULL},
397 	{ai_move, 0, NULL},
398 	{ai_move, 0, NULL},
399 	{ai_move, 0, NULL},
400 	{ai_move, 0, NULL},
401 	{ai_move, 0, NULL},
402 	{ai_move, 0, NULL},
403 	{ai_move, 0, NULL},
404 	{ai_move, 0, NULL},
405 	{ai_move, 0, NULL},
406 	{ai_move, 0, NULL},
407 	{ai_move, 0, NULL},
408 	{ai_move, 0, NULL},
409 	{ai_move, 0, NULL}
410 };
411 mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead};
412 
medic_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)413 void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
414 {
415 	int		n;
416 
417 	// if we had a pending patient, free him up for another medic
418 	if ((self->enemy) && (self->enemy->owner == self))
419 		self->enemy->owner = NULL;
420 
421 // check for gib
422 	if (self->health <= self->gib_health)
423 	{
424 		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
425 		for (n= 0; n < 2; n++)
426 			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
427 		for (n= 0; n < 4; n++)
428 			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
429 		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
430 		self->deadflag = DEAD_DEAD;
431 		return;
432 	}
433 
434 	if (self->deadflag == DEAD_DEAD)
435 		return;
436 
437 // regular death
438 	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
439 	self->deadflag = DEAD_DEAD;
440 	self->takedamage = DAMAGE_YES;
441 
442 	self->monsterinfo.currentmove = &medic_move_death;
443 }
444 
445 
medic_duck_down(edict_t * self)446 void medic_duck_down (edict_t *self)
447 {
448 	if (self->monsterinfo.aiflags & AI_DUCKED)
449 		return;
450 	self->monsterinfo.aiflags |= AI_DUCKED;
451 	self->maxs[2] -= 32;
452 	self->takedamage = DAMAGE_YES;
453 	self->monsterinfo.pausetime = level.time + 1;
454 	gi.linkentity (self);
455 }
456 
medic_duck_hold(edict_t * self)457 void medic_duck_hold (edict_t *self)
458 {
459 	if (level.time >= self->monsterinfo.pausetime)
460 		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
461 	else
462 		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
463 }
464 
medic_duck_up(edict_t * self)465 void medic_duck_up (edict_t *self)
466 {
467 	self->monsterinfo.aiflags &= ~AI_DUCKED;
468 	self->maxs[2] += 32;
469 	self->takedamage = DAMAGE_AIM;
470 	gi.linkentity (self);
471 }
472 
473 mframe_t medic_frames_duck [] =
474 {
475 	{ai_move, -1,	NULL},
476 	{ai_move, -1,	NULL},
477 	{ai_move, -1,	medic_duck_down},
478 	{ai_move, -1,	medic_duck_hold},
479 	{ai_move, -1,	NULL},
480 	{ai_move, -1,	NULL},
481 	{ai_move, -1,	medic_duck_up},
482 	{ai_move, -1,	NULL},
483 	{ai_move, -1,	NULL},
484 	{ai_move, -1,	NULL},
485 	{ai_move, -1,	NULL},
486 	{ai_move, -1,	NULL},
487 	{ai_move, -1,	NULL},
488 	{ai_move, -1,	NULL},
489 	{ai_move, -1,	NULL},
490 	{ai_move, -1,	NULL}
491 };
492 mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run};
493 
medic_dodge(edict_t * self,edict_t * attacker,float eta)494 void medic_dodge (edict_t *self, edict_t *attacker, float eta)
495 {
496 	if (random() > 0.25)
497 		return;
498 
499 	if (!self->enemy)
500 		self->enemy = attacker;
501 
502 	self->monsterinfo.currentmove = &medic_move_duck;
503 }
504 
505 mframe_t medic_frames_attackHyperBlaster [] =
506 {
507 	{ai_charge, 0,	NULL},
508 	{ai_charge, 0,	NULL},
509 	{ai_charge, 0,	NULL},
510 	{ai_charge, 0,	NULL},
511 	{ai_charge, 0,	medic_fire_blaster},
512 	{ai_charge, 0,	medic_fire_blaster},
513 	{ai_charge, 0,	medic_fire_blaster},
514 	{ai_charge, 0,	medic_fire_blaster},
515 	{ai_charge, 0,	medic_fire_blaster},
516 	{ai_charge, 0,	medic_fire_blaster},
517 	{ai_charge, 0,	medic_fire_blaster},
518 	{ai_charge, 0,	medic_fire_blaster},
519 	{ai_charge, 0,	medic_fire_blaster},
520 	{ai_charge, 0,	medic_fire_blaster},
521 	{ai_charge, 0,	medic_fire_blaster},
522 	{ai_charge, 0,	medic_fire_blaster}
523 };
524 mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run};
525 
526 
medic_continue(edict_t * self)527 void medic_continue (edict_t *self)
528 {
529 	if (visible (self, self->enemy) )
530 		if (random() <= 0.95)
531 			self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
532 }
533 
534 
535 mframe_t medic_frames_attackBlaster [] =
536 {
537 	{ai_charge, 0,	NULL},
538 	{ai_charge, 5,	NULL},
539 	{ai_charge, 5,	NULL},
540 	{ai_charge, 3,	NULL},
541 	{ai_charge, 2,	NULL},
542 	{ai_charge, 0,	NULL},
543 	{ai_charge, 0,	NULL},
544 	{ai_charge, 0,	NULL},
545 	{ai_charge, 0,	medic_fire_blaster},
546 	{ai_charge, 0,	NULL},
547 	{ai_charge, 0,	NULL},
548 	{ai_charge, 0,	medic_fire_blaster},
549 	{ai_charge, 0,	NULL},
550 	{ai_charge, 0,	medic_continue}	// Change to medic_continue... Else, go to frame 32
551 };
552 mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run};
553 
554 
medic_hook_launch(edict_t * self)555 void medic_hook_launch (edict_t *self)
556 {
557 	gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
558 }
559 
560 void ED_CallSpawn (edict_t *ent);
561 
562 static vec3_t	medic_cable_offsets[] =
563 {
564 	{45.0,  -9.2, 15.5},
565 	{48.4,  -9.7, 15.2},
566 	{47.8,  -9.8, 15.8},
567 	{47.3,  -9.3, 14.3},
568 	{45.4, -10.1, 13.1},
569 	{41.9, -12.7, 12.0},
570 	{37.8, -15.8, 11.2},
571 	{34.3, -18.4, 10.7},
572 	{32.7, -19.7, 10.4},
573 	{32.7, -19.7, 10.4}
574 };
575 
medic_cable_attack(edict_t * self)576 void medic_cable_attack (edict_t *self)
577 {
578 	vec3_t	offset, start, end, f, r;
579 	trace_t	tr;
580 	vec3_t	dir, angles;
581 	float	distance;
582 
583 	if (!self->enemy->inuse)
584 		return;
585 
586 	AngleVectors (self->s.angles, f, r, NULL);
587 	VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
588 	G_ProjectSource (self->s.origin, offset, f, r, start);
589 
590 	// check for max distance
591 	VectorSubtract (start, self->enemy->s.origin, dir);
592 	distance = VectorLength(dir);
593 	if (distance > 256)
594 		return;
595 
596 	// check for min/max pitch
597 	vectoangles (dir, angles);
598 	if (angles[0] < -180)
599 		angles[0] += 360;
600 	if (fabs(angles[0]) > 45)
601 		return;
602 
603 	tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SHOT);
604 	if (tr.fraction != 1.0 && tr.ent != self->enemy)
605 		return;
606 
607 	if (self->s.frame == FRAME_attack43)
608 	{
609 		gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
610 		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
611 	}
612 	else if (self->s.frame == FRAME_attack50)
613 	{
614 		self->enemy->spawnflags = 0;
615 		self->enemy->monsterinfo.aiflags = 0;
616 		self->enemy->target = NULL;
617 		self->enemy->targetname = NULL;
618 		self->enemy->combattarget = NULL;
619 		self->enemy->deathtarget = NULL;
620 		self->enemy->owner = self;
621 		ED_CallSpawn (self->enemy);
622 		self->enemy->owner = NULL;
623 		if (self->enemy->think)
624 		{
625 			self->enemy->nextthink = level.time;
626 			self->enemy->think (self->enemy);
627 		}
628 		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
629 		if (self->oldenemy && self->oldenemy->client)
630 		{
631 			self->enemy->enemy = self->oldenemy;
632 			FoundTarget (self->enemy);
633 		}
634 	}
635 	else
636 	{
637 		if (self->s.frame == FRAME_attack44)
638 			gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
639 	}
640 
641 	// adjust start for beam origin being in middle of a segment
642 	VectorMA (start, 8, f, start);
643 
644 	// adjust end z for end spot since the monster is currently dead
645 	VectorCopy (self->enemy->s.origin, end);
646 	end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
647 
648 	gi.WriteByte (svc_temp_entity);
649 	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
650 	gi.WriteShort (self - g_edicts);
651 	gi.WritePosition (start);
652 	gi.WritePosition (end);
653 	gi.multicast (self->s.origin, MULTICAST_PVS);
654 }
655 
medic_hook_retract(edict_t * self)656 void medic_hook_retract (edict_t *self)
657 {
658 	gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
659 	self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
660 }
661 
662 mframe_t medic_frames_attackCable [] =
663 {
664 	{ai_move, 2,		NULL},
665 	{ai_move, 3,		NULL},
666 	{ai_move, 5,		NULL},
667 	{ai_move, 4.4,	NULL},
668 	{ai_charge, 4.7,	NULL},
669 	{ai_charge, 5,	NULL},
670 	{ai_charge, 6,	NULL},
671 	{ai_charge, 4,	NULL},
672 	{ai_charge, 0,	NULL},
673 	{ai_move, 0,		medic_hook_launch},
674 	{ai_move, 0,		medic_cable_attack},
675 	{ai_move, 0,		medic_cable_attack},
676 	{ai_move, 0,		medic_cable_attack},
677 	{ai_move, 0,		medic_cable_attack},
678 	{ai_move, 0,		medic_cable_attack},
679 	{ai_move, 0,		medic_cable_attack},
680 	{ai_move, 0,		medic_cable_attack},
681 	{ai_move, 0,		medic_cable_attack},
682 	{ai_move, 0,		medic_cable_attack},
683 	{ai_move, -15,	medic_hook_retract},
684 	{ai_move, -1.5,	NULL},
685 	{ai_move, -1.2,	NULL},
686 	{ai_move, -3,	NULL},
687 	{ai_move, -2,	NULL},
688 	{ai_move, 0.3,	NULL},
689 	{ai_move, 0.7,	NULL},
690 	{ai_move, 1.2,	NULL},
691 	{ai_move, 1.3,	NULL}
692 };
693 mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run};
694 
695 
medic_attack(edict_t * self)696 void medic_attack(edict_t *self)
697 {
698 	if (self->monsterinfo.aiflags & AI_MEDIC)
699 		self->monsterinfo.currentmove = &medic_move_attackCable;
700 	else
701 		self->monsterinfo.currentmove = &medic_move_attackBlaster;
702 }
703 
medic_checkattack(edict_t * self)704 qboolean medic_checkattack (edict_t *self)
705 {
706 	if (self->monsterinfo.aiflags & AI_MEDIC)
707 	{
708 		medic_attack(self);
709 		return true;
710 	}
711 
712 	return M_CheckAttack (self);
713 }
714 
715 
716 /*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
717 */
SP_monster_medic(edict_t * self)718 void SP_monster_medic (edict_t *self)
719 {
720 	if (deathmatch->value)
721 	{
722 		G_FreeEdict (self);
723 		return;
724 	}
725 
726 	sound_idle1 = gi.soundindex ("medic/idle.wav");
727 	sound_pain1 = gi.soundindex ("medic/medpain1.wav");
728 	sound_pain2 = gi.soundindex ("medic/medpain2.wav");
729 	sound_die = gi.soundindex ("medic/meddeth1.wav");
730 	sound_sight = gi.soundindex ("medic/medsght1.wav");
731 	sound_search = gi.soundindex ("medic/medsrch1.wav");
732 	sound_hook_launch = gi.soundindex ("medic/medatck2.wav");
733 	sound_hook_hit = gi.soundindex ("medic/medatck3.wav");
734 	sound_hook_heal = gi.soundindex ("medic/medatck4.wav");
735 	sound_hook_retract = gi.soundindex ("medic/medatck5.wav");
736 
737 	gi.soundindex ("medic/medatck1.wav");
738 
739 	self->movetype = MOVETYPE_STEP;
740 	self->solid = SOLID_BBOX;
741 	self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2");
742 	VectorSet (self->mins, -24, -24, -24);
743 	VectorSet (self->maxs, 24, 24, 32);
744 
745 	self->health = 300;
746 	self->gib_health = -130;
747 	self->mass = 400;
748 
749 	self->pain = medic_pain;
750 	self->die = medic_die;
751 
752 	self->monsterinfo.stand = medic_stand;
753 	self->monsterinfo.walk = medic_walk;
754 	self->monsterinfo.run = medic_run;
755 	self->monsterinfo.dodge = medic_dodge;
756 	self->monsterinfo.attack = medic_attack;
757 	self->monsterinfo.melee = NULL;
758 	self->monsterinfo.sight = medic_sight;
759 	self->monsterinfo.idle = medic_idle;
760 	self->monsterinfo.search = medic_search;
761 	self->monsterinfo.checkattack = medic_checkattack;
762 
763 	gi.linkentity (self);
764 
765 	self->monsterinfo.currentmove = &medic_move_stand;
766 	self->monsterinfo.scale = MODEL_SCALE;
767 
768 	walkmonster_start (self);
769 }
770