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