|
|
1.1 root 1: /*
2: ==============================================================================
3:
4: black widow
5:
6: ==============================================================================
7: */
8:
9: // self->timestamp used to prevent rapid fire of railgun
10: // self->plat2flags used for fire count (flashes)
11: // self->monsterinfo.pausetime used for timing of blaster shots
12:
13: #include "g_local.h"
14: #include "m_widow.h"
15:
16: #define NUM_STALKERS_SPAWNED 6 // max # of stalkers she can spawn
17:
18: #define RAIL_TIME 3
19: #define BLASTER_TIME 2
20: #define BLASTER2_DAMAGE 10
21: #define WIDOW_RAIL_DAMAGE 50
22:
23: #define DRAWBBOX NULL
24: #define SHOWME showme
25:
26: void BossExplode (edict_t *self);
27:
28: qboolean infront (edict_t *self, edict_t *other);
29:
30: static int sound_pain1;
31: static int sound_pain2;
32: static int sound_pain3;
33: static int sound_death;
34: static int sound_search1;
35: static int sound_rail;
36:
37: static unsigned long shotsfired;
38:
39: static vec3_t spawnpoints[] = {
40: {30, 100, 16},
41: {30, -100, 16}
42: };
43:
44: static vec3_t beameffects[] = {
45: {12.58, -43.71, 68.88},
46: {3.43, 58.72, 68.41}
47: };
48:
49: static float sweep_angles[] = {
50: // 32.0, 26.0, 20.0, 11.5, 3.0, -8.0, -13.0, -27.0, -41.0
51: 32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0
52: };
53:
54: vec3_t stalker_mins = {-28, -28, -18};
55: vec3_t stalker_maxs = {28, 28, 18};
56:
57: unsigned int widow_damage_multiplier;
58:
59: void widow_run (edict_t *self);
60: void widow_stand (edict_t *self);
61: void widow_dead (edict_t *self);
62: void widow_attack (edict_t *self);
63: void widow_attack_blaster (edict_t *self);
64: void widow_reattack_blaster (edict_t *self);
65: void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
66:
67: void widow_start_spawn (edict_t *self);
68: void widow_done_spawn (edict_t *self);
69: void widow_spawn_check (edict_t *self);
70: void widow_prep_spawn (edict_t *self);
71: void widow_attack_rail (edict_t *self);
72:
73: void widow_start_run_5 (edict_t *self);
74: void widow_start_run_10 (edict_t *self);
75: void widow_start_run_12 (edict_t *self);
76:
77: void WidowCalcSlots (edict_t *self);
78:
79: void drawbbox (edict_t *self);
80:
81: void showme (edict_t *self)
82: {
83: gi.dprintf ("frame %d\n", self->s.frame);
84: }
85:
86: void widow_search (edict_t *self)
87: {
88: if (random() < 0.5)
89: gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
90: }
91:
92: void widow_sight (edict_t *self, edict_t *other)
93: {
94: self->monsterinfo.pausetime = 0;
95: if ((g_showlogic) && (g_showlogic->value))
96: gi.dprintf ("widow: found target!\n");
97: }
98:
99: mmove_t widow_move_attack_post_blaster;
100: mmove_t widow_move_attack_post_blaster_r;
101: mmove_t widow_move_attack_post_blaster_l;
102: mmove_t widow_move_attack_blaster;
103:
104: float target_angle (edict_t *self)
105: {
106: vec3_t target;
107: float enemy_yaw;
108:
109: VectorSubtract (self->s.origin, self->enemy->s.origin, target);
110: enemy_yaw = self->s.angles[YAW] - vectoyaw2(target);
111: if (enemy_yaw < 0)
112: enemy_yaw += 360.0;
113:
114: // this gets me 0 degrees = forward
115: enemy_yaw -= 180.0;
116: // positive is to right, negative to left
117:
118: return enemy_yaw;
119: }
120:
121: int WidowTorso (edict_t *self)
122: {
123: float enemy_yaw;
124:
125: enemy_yaw = target_angle (self);
126:
127: // if ((g_showlogic) && (g_showlogic->value))
128: // gi.dprintf ("%2.2f -> ", enemy_yaw);
129:
130: if (enemy_yaw >= 105)
131: {
132: self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r;
133: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
134: return 0;
135: }
136:
137: if (enemy_yaw <= -75.0)
138: {
139: self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l;
140: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
141: return 0;
142: }
143:
144: if (enemy_yaw >= 95)
145: return FRAME_fired03;
146: else if (enemy_yaw >= 85)
147: return FRAME_fired04;
148: else if (enemy_yaw >= 75)
149: return FRAME_fired05;
150: else if (enemy_yaw >= 65)
151: return FRAME_fired06;
152: else if (enemy_yaw >= 55)
153: return FRAME_fired07;
154: else if (enemy_yaw >= 45)
155: return FRAME_fired08;
156: else if (enemy_yaw >= 35)
157: return FRAME_fired09;
158: else if (enemy_yaw >= 25)
159: return FRAME_fired10;
160: else if (enemy_yaw >= 15)
161: return FRAME_fired11;
162: else if (enemy_yaw >= 5)
163: return FRAME_fired12;
164: else if (enemy_yaw >= -5)
165: return FRAME_fired13;
166: else if (enemy_yaw >= -15)
167: return FRAME_fired14;
168: else if (enemy_yaw >= -25)
169: return FRAME_fired15;
170: else if (enemy_yaw >= -35)
171: return FRAME_fired16;
172: else if (enemy_yaw >= -45)
173: return FRAME_fired17;
174: else if (enemy_yaw >= -55)
175: return FRAME_fired18;
176: else if (enemy_yaw >= -65)
177: return FRAME_fired19;
178: else if (enemy_yaw >= -75)
179: return FRAME_fired20;
180: /*
181: if (fabs(enemy_yaw) < 11.25)
182: return FRAME_fired03;
183: else if (fabs(enemy_yaw) > 56.25)
184: {
185: self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
186: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
187: return;
188: }
189: else if ((enemy_yaw >= 11.25) && (enemy_yaw < 33.75))
190: return FRAME_fired04;
191: else if (enemy_yaw >= 33.75)
192: return FRAME_fired05;
193: else if ((enemy_yaw <= -11.25) && (enemy_yaw > -33.75))
194: return FRAME_fired06;
195: else if (enemy_yaw <= -33.75)
196: return FRAME_fired07;
197: */
198: }
199:
200: #define VARIANCE 15.0
201:
202: void WidowBlaster (edict_t *self)
203: {
204: vec3_t forward, right, target, vec, targ_angles;
205: vec3_t start;
206: int flashnum;
207: int effect;
208:
209: if (!self->enemy)
210: return;
211:
212: // FIXME - this is not an int
213: shotsfired++;
214: if (!(shotsfired % 4))
215: effect = EF_BLASTER;
216: else
217: effect = 0;
218:
219: AngleVectors (self->s.angles, forward, right, NULL);
220: if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13))
221: {
222: // sweep
223: flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05;
224: G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
225: VectorSubtract (self->enemy->s.origin, start, target);
226: vectoangles2 (target, targ_angles);
227:
228: VectorCopy (self->s.angles, vec);
229:
230: vec[PITCH] += targ_angles[PITCH];
231: vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW_BLASTER_SWEEP1];
232:
233: AngleVectors (vec, forward, NULL, NULL);
234: monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
235:
236: /* if (self->s.frame == FRAME_spawn13)
237: {
238: VectorMA (start, 1024, forward, debugend);
239:
240: gi.WriteByte (svc_temp_entity);
241: gi.WriteByte (TE_DEBUGTRAIL);
242: gi.WritePosition (start);
243: gi.WritePosition (debugend);
244: gi.multicast (start, MULTICAST_ALL);
245:
246: drawbbox (self);
247: self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING;
248: }
249: */
250: }
251: else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20))
252: {
253: vec3_t angles;
254: float aim_angle, target_angle;
255: float error;
256:
257: self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
258:
259: self->monsterinfo.nextframe = WidowTorso (self);
260:
261: if (!self->monsterinfo.nextframe)
262: self->monsterinfo.nextframe = self->s.frame;
263:
264: // if ((g_showlogic) && (g_showlogic->value))
265: // gi.dprintf ("%d\n", self->monsterinfo.nextframe);
266:
267: if (self->s.frame == FRAME_fired02a)
268: flashnum = MZ2_WIDOW_BLASTER_0;
269: else
270: flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03;
271:
272: G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
273:
274: PredictAim (self->enemy, start, 1000, true, ((random()*0.1)-0.05), forward, NULL);
275:
276: // clamp it to within 10 degrees of the aiming angle (where she's facing)
277: vectoangles2 (forward, angles);
278: // give me 100 -> -70
279: aim_angle = 100 - (10*(flashnum-MZ2_WIDOW_BLASTER_100));
280: if (aim_angle <= 0)
281: aim_angle += 360;
282: target_angle = self->s.angles[YAW] - angles[YAW];
283: if (target_angle <= 0)
284: target_angle += 360;
285:
286: error = aim_angle - target_angle;
287:
288: // positive error is to entity's left, aka positive direction in engine
289: // unfortunately, I decided that for the aim_angle, positive was right. *sigh*
290: if (error > VARIANCE)
291: {
292: // if ((g_showlogic) && (g_showlogic->value))
293: // gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
294: angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE;
295: // if ((g_showlogic) && (g_showlogic->value))
296: // {
297: // if (angles[YAW] <= 0)
298: // angles[YAW] += 360;
299: // gi.dprintf (" %2.2f\n", angles[YAW]);
300: // }
301: AngleVectors (angles, forward, NULL, NULL);
302: }
303: else if (error < -VARIANCE)
304: {
305: // if ((g_showlogic) && (g_showlogic->value))
306: // gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
307: angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE;
308: // if ((g_showlogic) && (g_showlogic->value))
309: // {
310: // if (angles[YAW] <= 0)
311: // angles[YAW] += 360;
312: // gi.dprintf (" %2.2f\n", angles[YAW]);
313: // }
314: AngleVectors (angles, forward, NULL, NULL);
315: }
316: // gi.dprintf ("%2.2f - %2.2f - %2.2f - %2.2f\n", aim_angle, self->s.angles[YAW] - angles[YAW], target_angle, error);
317: // gi.dprintf ("%2.2f - %2.2f - %2.2f\n", angles[YAW], aim_angle, self->s.angles[YAW]);
318:
319: /*
320: if (self->s.frame == FRAME_fired20)
321: {
322: VectorMA (start, 512, forward, debugend);
323: gi.WriteByte (svc_temp_entity);
324: gi.WriteByte (TE_DEBUGTRAIL);
325: gi.WritePosition (start);
326: gi.WritePosition (forward);
327: gi.multicast (start, MULTICAST_ALL);
328:
329: drawbbox (self);
330: self->monsterinfo.aiflags |= AI_HOLD_FRAME;
331: self->monsterinfo.nextframe = FRAME_fired20;
332: self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
333: }
334: */
335: /*
336: if (!(self->plat2flags % 3))
337: effect = EF_HYPERBLASTER;
338: else
339: effect = 0;
340: self->plat2flags ++;
341: */
342: monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
343: }
344: else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08))
345: {
346: flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01;
347: G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
348:
349: VectorSubtract (self->enemy->s.origin, start, target);
350: target[2] += self->enemy->viewheight;
351:
352: monster_fire_blaster2 (self, start, target, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
353: }
354: else
355: {
356: if ((g_showlogic) && (g_showlogic->value))
357: gi.dprintf ("widow: firing on non-fire frame!\n");
358: }
359: }
360:
361: void WidowSpawn (edict_t *self)
362: {
363: vec3_t f, r, u, offset, startpoint, spawnpoint;
364: edict_t *ent, *designated_enemy;
365: int i;
366:
367: AngleVectors (self->s.angles, f, r, u);
368:
369: for (i=0; i < 2; i++)
370: {
371: VectorCopy (spawnpoints[i], offset);
372:
373: G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
374:
375: if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
376: {
377: ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256);
378:
379: if (!ent)
380: continue;
381:
382: self->monsterinfo.monster_used++;
383: ent->monsterinfo.commander = self;
384: if ((g_showlogic) && (g_showlogic->value))
385: gi.dprintf ("widow: post-spawn : %d slots left out of %d\n", SELF_SLOTS_LEFT, self->monsterinfo.monster_slots);
386:
387: ent->nextthink = level.time;
388: ent->think (ent);
389:
390: ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
391:
392: if (!(coop && coop->value))
393: {
394: designated_enemy = self->enemy;
395: }
396: else
397: {
398: designated_enemy = PickCoopTarget(ent);
399: if (designated_enemy)
400: {
401: // try to avoid using my enemy
402: if (designated_enemy == self->enemy)
403: {
404: designated_enemy = PickCoopTarget(ent);
405: if (designated_enemy)
406: {
407: if ((g_showlogic) && (g_showlogic->value))
408: {
409: gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
410: if (designated_enemy->client)
411: gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
412: else
413: gi.dprintf ("NOT A CLIENT\n");
414: }
415: }
416: else
417: {
418: if ((g_showlogic) && (g_showlogic->value))
419: gi.dprintf ("pick coop failed, using my current enemy\n");
420: designated_enemy = self->enemy;
421: }
422: }
423: }
424: else
425: {
426: if ((g_showlogic) && (g_showlogic->value))
427: gi.dprintf ("pick coop failed, using my current enemy\n");
428: designated_enemy = self->enemy;
429: }
430: }
431:
432: if ((designated_enemy->inuse) && (designated_enemy->health > 0))
433: {
434: ent->enemy = designated_enemy;
435: FoundTarget (ent);
436: ent->monsterinfo.attack(ent);
437: }
438: }
439: }
440: }
441:
442: void widow_spawn_check (edict_t *self)
443: {
444: WidowBlaster(self);
445: WidowSpawn (self);
446: }
447:
448: void widow_ready_spawn (edict_t *self)
449: {
450: vec3_t f, r, u, offset, startpoint, spawnpoint;
451: int i;
452:
453: WidowBlaster(self);
454: AngleVectors (self->s.angles, f, r, u);
455:
456: for (i=0; i < 2; i++)
457: {
458: VectorCopy (spawnpoints[i], offset);
459: G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
460: if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
461: {
462: SpawnGrow_Spawn (spawnpoint, 1);
463: }
464: }
465: }
466:
467: mframe_t widow_frames_stand [] =
468: {
469: ai_stand, 0, NULL,
470: ai_stand, 0, NULL,
471: ai_stand, 0, NULL,
472: ai_stand, 0, NULL,
473: ai_stand, 0, NULL,
474: ai_stand, 0, NULL,
475: ai_stand, 0, NULL,
476: ai_stand, 0, NULL,
477: ai_stand, 0, NULL,
478: ai_stand, 0, NULL,
479: ai_stand, 0, NULL
480: };
481: mmove_t widow_move_stand = {FRAME_idle01, FRAME_idle11, widow_frames_stand, NULL};
482:
483: mframe_t widow_frames_walk [] =
484: {
485: // hand generated numbers
486: /*
487: ai_run, 6, NULL,
488: ai_run, 3, NULL,
489: ai_run, 3, NULL,
490: ai_run, 3, NULL,
491: ai_run, 4, NULL, //5
492: ai_run, 4, NULL,
493: ai_run, 4, NULL,
494: ai_run, 4.5, NULL,
495: ai_run, 3, NULL,
496: ai_run, 5, NULL, //10
497: ai_run, 8, NULL,
498: ai_run, 8, NULL,
499: ai_run, 6.5, NULL
500: */
501: // auto generated numbers
502: ai_walk, 2.79, NULL,
503: ai_walk, 2.77, NULL,
504: ai_walk, 3.53, NULL,
505: ai_walk, 3.97, NULL,
506: ai_walk, 4.13, NULL, //5
507: ai_walk, 4.09, NULL,
508: ai_walk, 3.84, NULL,
509: ai_walk, 3.62, NULL,
510: ai_walk, 3.29, NULL,
511: ai_walk, 6.08, NULL, //10
512: ai_walk, 6.94, NULL,
513: ai_walk, 5.73, NULL,
514: ai_walk, 2.85, NULL
515: };
516: mmove_t widow_move_walk = {FRAME_walk01, FRAME_walk13, widow_frames_walk, NULL};
517:
518:
519: mframe_t widow_frames_run [] =
520: {
521: ai_run, 2.79, NULL,
522: ai_run, 2.77, NULL,
523: ai_run, 3.53, NULL,
524: ai_run, 3.97, NULL,
525: ai_run, 4.13, NULL, //5
526: ai_run, 4.09, NULL,
527: ai_run, 3.84, NULL,
528: ai_run, 3.62, NULL,
529: ai_run, 3.29, NULL,
530: ai_run, 6.08, NULL, //10
531: ai_run, 6.94, NULL,
532: ai_run, 5.73, NULL,
533: ai_run, 2.85, NULL
534: };
535: mmove_t widow_move_run = {FRAME_walk01, FRAME_walk13, widow_frames_run, NULL};
536:
537: mframe_t widow_frames_run_attack [] =
538: {
539: ai_charge, 13, WidowBlaster,
540: ai_charge, 11.72, WidowBlaster,
541: ai_charge, 18.04, WidowBlaster,
542: ai_charge, 14.58, WidowBlaster,
543: ai_charge, 13, WidowBlaster, //5
544: ai_charge, 12.12, WidowBlaster,
545: ai_charge, 19.63, WidowBlaster,
546: ai_charge, 11.37, WidowBlaster
547: };
548: mmove_t widow_move_run_attack = {FRAME_run01, FRAME_run08, widow_frames_run_attack, widow_run};
549:
550:
551: //
552: // These three allow specific entry into the run sequence
553: //
554:
555: void widow_start_run_5 (edict_t *self)
556: {
557: self->monsterinfo.currentmove = &widow_move_run;
558: self->monsterinfo.nextframe = FRAME_walk05;
559: }
560:
561: void widow_start_run_10 (edict_t *self)
562: {
563: self->monsterinfo.currentmove = &widow_move_run;
564: self->monsterinfo.nextframe = FRAME_walk10;
565: }
566:
567: void widow_start_run_12 (edict_t *self)
568: {
569: self->monsterinfo.currentmove = &widow_move_run;
570: self->monsterinfo.nextframe = FRAME_walk12;
571: }
572:
573:
574: mframe_t widow_frames_attack_pre_blaster [] =
575: {
576: ai_charge, 0, NULL,
577: ai_charge, 0, NULL,
578: ai_charge, 0, widow_attack_blaster
579: };
580: mmove_t widow_move_attack_pre_blaster = {FRAME_fired01, FRAME_fired02a, widow_frames_attack_pre_blaster, NULL};
581:
582: // Loop this
583: mframe_t widow_frames_attack_blaster [] =
584: {
585: ai_charge, 0, widow_reattack_blaster, // straight ahead
586: ai_charge, 0, widow_reattack_blaster, // 100 degrees right
587: ai_charge, 0, widow_reattack_blaster,
588: ai_charge, 0, widow_reattack_blaster,
589: ai_charge, 0, widow_reattack_blaster,
590: ai_charge, 0, widow_reattack_blaster,
591: ai_charge, 0, widow_reattack_blaster, // 50 degrees right
592: ai_charge, 0, widow_reattack_blaster,
593: ai_charge, 0, widow_reattack_blaster,
594: ai_charge, 0, widow_reattack_blaster,
595: ai_charge, 0, widow_reattack_blaster,
596: ai_charge, 0, widow_reattack_blaster, // straight
597: ai_charge, 0, widow_reattack_blaster,
598: ai_charge, 0, widow_reattack_blaster,
599: ai_charge, 0, widow_reattack_blaster,
600: ai_charge, 0, widow_reattack_blaster,
601: ai_charge, 0, widow_reattack_blaster, // 50 degrees left
602: ai_charge, 0, widow_reattack_blaster,
603: ai_charge, 0, widow_reattack_blaster // 70 degrees left
604: };
605: mmove_t widow_move_attack_blaster = {FRAME_fired02a, FRAME_fired20, widow_frames_attack_blaster, NULL};
606:
607: mframe_t widow_frames_attack_post_blaster [] =
608: {
609: ai_charge, 0, NULL,
610: ai_charge, 0, NULL
611: };
612: mmove_t widow_move_attack_post_blaster = {FRAME_fired21, FRAME_fired22, widow_frames_attack_post_blaster, widow_run};
613:
614: mframe_t widow_frames_attack_post_blaster_r [] =
615: {
616: ai_charge, -2, NULL,
617: ai_charge, -10, NULL,
618: ai_charge, -2, NULL,
619: ai_charge, 0, NULL,
620: ai_charge, 0, widow_start_run_12
621: };
622: mmove_t widow_move_attack_post_blaster_r = {FRAME_transa01, FRAME_transa05, widow_frames_attack_post_blaster_r, NULL};
623:
624: mframe_t widow_frames_attack_post_blaster_l [] =
625: {
626: ai_charge, 0, NULL,
627: ai_charge, 14, NULL,
628: ai_charge, -2, NULL,
629: ai_charge, 10, NULL,
630: ai_charge, 10, widow_start_run_12
631: };
632: mmove_t widow_move_attack_post_blaster_l = {FRAME_transb01, FRAME_transb05, widow_frames_attack_post_blaster_l, NULL};
633:
634: mmove_t widow_move_attack_rail;
635: mmove_t widow_move_attack_rail_l;
636: mmove_t widow_move_attack_rail_r;
637:
638: void WidowRail (edict_t *self)
639: {
640: vec3_t start;
641: vec3_t dir;
642: vec3_t forward, right;
643: int flash;
644:
645: // gi.dprintf ("railing!\n");
646: AngleVectors (self->s.angles, forward, right, NULL);
647:
648: if (self->monsterinfo.currentmove == &widow_move_attack_rail)
649: flash = MZ2_WIDOW_RAIL;
650: else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l)
651: {
652: flash = MZ2_WIDOW_RAIL_LEFT;
653: }
654: else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r)
655: {
656: flash = MZ2_WIDOW_RAIL_RIGHT;
657: }
658:
659: G_ProjectSource (self->s.origin, monster_flash_offset[flash], forward, right, start);
660:
661: // calc direction to where we targeted
662: VectorSubtract (self->pos1, start, dir);
663: VectorNormalize (dir);
664:
665: monster_fire_railgun (self, start, dir, WIDOW_RAIL_DAMAGE*widow_damage_multiplier, 100, flash);
666: self->timestamp = level.time + RAIL_TIME;
667: }
668:
669: void WidowSaveLoc (edict_t *self)
670: {
671: VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot
672: self->pos1[2] += self->enemy->viewheight;
673: };
674:
675: void widow_start_rail (edict_t *self)
676: {
677: self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
678: }
679:
680: void widow_rail_done (edict_t *self)
681: {
682: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
683: }
684:
685: mframe_t widow_frames_attack_pre_rail [] =
686: {
687: ai_charge, 0, widow_start_rail,
688: ai_charge, 0, NULL,
689: ai_charge, 0, NULL,
690: ai_charge, 0, widow_attack_rail
691: };
692: mmove_t widow_move_attack_pre_rail = {FRAME_transc01, FRAME_transc04, widow_frames_attack_pre_rail, NULL};
693:
694: mframe_t widow_frames_attack_rail [] =
695: {
696: ai_charge, 0, NULL,
697: ai_charge, 0, NULL,
698: ai_charge, 0, WidowSaveLoc,
699: ai_charge, -10, WidowRail,
700: ai_charge, 0, NULL,
701: ai_charge, 0, NULL,
702: ai_charge, 0, NULL,
703: ai_charge, 0, NULL,
704: ai_charge, 0, widow_rail_done
705: };
706: mmove_t widow_move_attack_rail = {FRAME_firea01, FRAME_firea09, widow_frames_attack_rail, widow_run};
707:
708: mframe_t widow_frames_attack_rail_r [] =
709: {
710: ai_charge, 0, NULL,
711: ai_charge, 0, NULL,
712: ai_charge, 0, WidowSaveLoc,
713: ai_charge, -10, WidowRail,
714: ai_charge, 0, NULL,
715: ai_charge, 0, NULL,
716: ai_charge, 0, NULL,
717: ai_charge, 0, NULL,
718: ai_charge, 0, widow_rail_done
719: };
720: mmove_t widow_move_attack_rail_r = {FRAME_fireb01, FRAME_fireb09, widow_frames_attack_rail_r, widow_run};
721:
722: mframe_t widow_frames_attack_rail_l [] =
723: {
724: ai_charge, 0, NULL,
725: ai_charge, 0, NULL,
726: ai_charge, 0, WidowSaveLoc,
727: ai_charge, -10, WidowRail,
728: ai_charge, 0, NULL,
729: ai_charge, 0, NULL,
730: ai_charge, 0, NULL,
731: ai_charge, 0, NULL,
732: ai_charge, 0, widow_rail_done
733: };
734: mmove_t widow_move_attack_rail_l = {FRAME_firec01, FRAME_firec09, widow_frames_attack_rail_l, widow_run};
735:
736: void widow_attack_rail (edict_t *self)
737: {
738: float enemy_angle;
739: // gi.dprintf ("going to the rail!\n");
740:
741: enemy_angle = target_angle (self);
742:
743: if (enemy_angle < -15)
744: self->monsterinfo.currentmove = &widow_move_attack_rail_l;
745: else if (enemy_angle > 15)
746: self->monsterinfo.currentmove = &widow_move_attack_rail_r;
747: else
748: self->monsterinfo.currentmove = &widow_move_attack_rail;
749: }
750:
751: void widow_start_spawn (edict_t *self)
752: {
753: self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
754: }
755:
756: void widow_done_spawn (edict_t *self)
757: {
758: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
759: }
760:
761: mframe_t widow_frames_spawn [] =
762: {
763: ai_charge, 0, NULL, //1
764: ai_charge, 0, NULL,
765: ai_charge, 0, NULL,
766: ai_charge, 0, widow_start_spawn,
767: ai_charge, 0, NULL, //5
768: ai_charge, 0, WidowBlaster, //6
769: ai_charge, 0, widow_ready_spawn, //7
770: ai_charge, 0, WidowBlaster,
771: ai_charge, 0, WidowBlaster, //9
772: ai_charge, 0, widow_spawn_check,
773: ai_charge, 0, WidowBlaster, //11
774: ai_charge, 0, WidowBlaster,
775: ai_charge, 0, WidowBlaster, //13
776: ai_charge, 0, NULL,
777: ai_charge, 0, NULL,
778: ai_charge, 0, NULL,
779: ai_charge, 0, NULL,
780: ai_charge, 0, widow_done_spawn
781: };
782: mmove_t widow_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow_frames_spawn, widow_run};
783:
784: mframe_t widow_frames_pain_heavy [] =
785: {
786: ai_move, 0, NULL,
787: ai_move, 0, NULL,
788: ai_move, 0, NULL,
789: ai_move, 0, NULL,
790: ai_move, 0, NULL,
791: ai_move, 0, NULL,
792: ai_move, 0, NULL,
793: ai_move, 0, NULL,
794: ai_move, 0, NULL,
795: ai_move, 0, NULL,
796: ai_move, 0, NULL,
797: ai_move, 0, NULL,
798: ai_move, 0, NULL
799: };
800: mmove_t widow_move_pain_heavy = {FRAME_pain01, FRAME_pain13, widow_frames_pain_heavy, widow_run};
801:
802: mframe_t widow_frames_pain_light [] =
803: {
804: ai_move, 0, NULL,
805: ai_move, 0, NULL,
806: ai_move, 0, NULL
807: };
808: mmove_t widow_move_pain_light = {FRAME_pain201, FRAME_pain203, widow_frames_pain_light, widow_run};
809:
810: void spawn_out_start (edict_t *self)
811: {
812: vec3_t startpoint,f,r,u;
813: self->wait = level.time + 2.0;
814:
815: // gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
816: AngleVectors (self->s.angles, f, r, u);
817:
818: G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
819: gi.WriteByte (svc_temp_entity);
820: gi.WriteByte (TE_WIDOWBEAMOUT);
821: gi.WriteShort (20001);
822: gi.WritePosition (startpoint);
823: gi.multicast (startpoint, MULTICAST_ALL);
824:
825: G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
826: gi.WriteByte (svc_temp_entity);
827: gi.WriteByte (TE_WIDOWBEAMOUT);
828: gi.WriteShort (20002);
829: gi.WritePosition (startpoint);
830: gi.multicast (startpoint, MULTICAST_ALL);
831: }
832:
833: void spawn_out_do (edict_t *self)
834: {
835: vec3_t startpoint,f,r,u;
836:
837: AngleVectors (self->s.angles, f, r, u);
838: G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
839: gi.WriteByte (svc_temp_entity);
840: gi.WriteByte (TE_WIDOWSPLASH);
841: gi.WritePosition (startpoint);
842: gi.multicast (startpoint, MULTICAST_ALL);
843:
844: G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
845: gi.WriteByte (svc_temp_entity);
846: gi.WriteByte (TE_WIDOWSPLASH);
847: gi.WritePosition (startpoint);
848: gi.multicast (startpoint, MULTICAST_ALL);
849:
850: VectorCopy (self->s.origin, startpoint);
851: startpoint[2] += 36;
852: gi.WriteByte (svc_temp_entity);
853: gi.WriteByte (TE_BOSSTPORT);
854: gi.WritePosition (startpoint);
855: gi.multicast (startpoint, MULTICAST_PVS);
856:
857: Widowlegs_Spawn (self->s.origin, self->s.angles);
858:
859: G_FreeEdict (self);
860: }
861:
862: mframe_t widow_frames_death [] =
863: {
864: ai_move, 0, NULL,
865: ai_move, 0, NULL,
866: ai_move, 0, NULL,
867: ai_move, 0, NULL,
868: ai_move, 0, NULL, //5
869: ai_move, 0, NULL,
870: ai_move, 0, NULL,
871: ai_move, 0, NULL,
872: ai_move, 0, NULL,
873: ai_move, 0, spawn_out_start, //10
874: ai_move, 0, NULL,
875: ai_move, 0, NULL,
876: ai_move, 0, NULL,
877: ai_move, 0, NULL,
878: ai_move, 0, NULL, //15
879: ai_move, 0, NULL,
880: ai_move, 0, NULL,
881: ai_move, 0, NULL,
882: ai_move, 0, NULL,
883: ai_move, 0, NULL, //20
884: ai_move, 0, NULL,
885: ai_move, 0, NULL,
886: ai_move, 0, NULL,
887: ai_move, 0, NULL,
888: ai_move, 0, NULL, //25
889: ai_move, 0, NULL,
890: ai_move, 0, NULL,
891: ai_move, 0, NULL,
892: ai_move, 0, NULL,
893: ai_move, 0, NULL, //30
894: ai_move, 0, spawn_out_do
895: };
896: mmove_t widow_move_death = {FRAME_death01, FRAME_death31, widow_frames_death, NULL};
897:
898: void widow_attack_kick (edict_t *self)
899: {
900: vec3_t aim;
901:
902: // VectorSet (aim, MELEE_DISTANCE, 0, 4);
903: VectorSet (aim, 100, 0, 4);
904: if (self->enemy->groundentity)
905: fire_hit (self, aim, (50 + (rand() % 6)), 500);
906: else // not as much kick if they're in the air .. makes it harder to land on her head
907: fire_hit (self, aim, (50 + (rand() % 6)), 250);
908:
909: }
910:
911: mframe_t widow_frames_attack_kick [] =
912: {
913: ai_move, 0, NULL,
914: ai_move, 0, NULL,
915: ai_move, 0, NULL,
916: ai_move, 0, widow_attack_kick,
917: ai_move, 0, NULL, // 5
918: ai_move, 0, NULL,
919: ai_move, 0, NULL,
920: ai_move, 0, NULL
921: };
922:
923: mmove_t widow_move_attack_kick = {FRAME_kick01, FRAME_kick08, widow_frames_attack_kick, widow_run};
924:
925: void widow_stand (edict_t *self)
926: {
927: // gi.dprintf ("widow stand\n");
928: self->monsterinfo.currentmove = &widow_move_stand;
929: }
930:
931: void widow_run (edict_t *self)
932: {
933: self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
934:
935: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
936: self->monsterinfo.currentmove = &widow_move_stand;
937: else
938: self->monsterinfo.currentmove = &widow_move_run;
939: }
940:
941: void widow_walk (edict_t *self)
942: {
943: self->monsterinfo.currentmove = &widow_move_walk;
944: }
945:
946: void widow_attack (edict_t *self)
947: {
948: float luck;
949: qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false;
950:
951: if (self->monsterinfo.aiflags & AI_BLOCKED)
952: {
953: blocked = true;
954: self->monsterinfo.aiflags &= ~AI_BLOCKED;
955: }
956:
957: if (self->monsterinfo.aiflags & AI_TARGET_ANGER)
958: {
959: anger = true;
960: self->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
961: }
962:
963: if ((!self->enemy) || (!self->enemy->inuse))
964: return;
965:
966: if (self->bad_area)
967: {
968: if ((random() < 0.1) || (level.time < self->timestamp))
969: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
970: else
971: {
972: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
973: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
974: }
975: return;
976: }
977:
978: // frames FRAME_walk13, FRAME_walk01, FRAME_walk02, FRAME_walk03 are rail gun start frames
979: // frames FRAME_walk09, FRAME_walk10, FRAME_walk11, FRAME_walk12 are spawn & blaster start frames
980:
981: if ((self->s.frame == FRAME_walk13) || ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03)))
982: rail_frames = true;
983:
984: if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12))
985: blaster_frames = true;
986:
987: WidowCalcSlots(self);
988:
989: // if we can't see the target, spawn stuff regardless of frame
990: if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
991: {
992: if ((g_showlogic) && (g_showlogic->value))
993: gi.dprintf ("attacking blind!\n");
994: self->monsterinfo.currentmove = &widow_move_spawn;
995: return;
996: }
997:
998: // accept bias towards spawning regardless of frame
999: if (blocked && (SELF_SLOTS_LEFT >= 2))
1000: {
1001: self->monsterinfo.currentmove = &widow_move_spawn;
1002: return;
1003: }
1004:
1005: if ((realrange(self, self->enemy) > 300) && (!anger) && (random() < 0.5) && (!blocked))
1006: {
1007: self->monsterinfo.currentmove = &widow_move_run_attack;
1008: return;
1009: }
1010:
1011: if (blaster_frames)
1012: {
1013: // gi.dprintf ("blaster frame %2.2f <= %2.2f\n", self->monsterinfo.pausetime + BLASTER_TIME, level.time);
1014: if (SELF_SLOTS_LEFT >= 2)
1015: {
1016: self->monsterinfo.currentmove = &widow_move_spawn;
1017: return;
1018: }
1019: else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)
1020: {
1021: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1022: return;
1023: }
1024: }
1025:
1026: if (rail_frames)
1027: {
1028: // gi.dprintf ("rail frame %2.2f - %2.2f\n", level.time, self->timestamp);
1029: if (!(level.time < self->timestamp))
1030: {
1031: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1032: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1033: }
1034: }
1035:
1036: if ((rail_frames) || (blaster_frames))
1037: return;
1038:
1039: // if ((g_showlogic) && (g_showlogic->value))
1040: // gi.dprintf ("widow: unknown start frame, picking randomly\n");
1041:
1042: luck = random();
1043: if (SELF_SLOTS_LEFT >= 2)
1044: {
1045: if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time))
1046: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1047: else if ((luck <= 0.7) && !(level.time < self->timestamp))
1048: {
1049: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1050: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1051: }
1052: else
1053: self->monsterinfo.currentmove = &widow_move_spawn;
1054: }
1055: else
1056: {
1057: if (level.time < self->timestamp)
1058: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1059: else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime))
1060: {
1061: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1062: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1063: }
1064: else // holdout to blaster
1065: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1066: }
1067: }
1068: /*
1069: void widow_attack (edict_t *self)
1070: {
1071: float range, luck;
1072:
1073: // gi.dprintf ("widow attack\n");
1074:
1075: if ((!self->enemy) || (!self->enemy->inuse))
1076: return;
1077:
1078: if (self->bad_area)
1079: {
1080: if ((random() < 0.1) || (level.time < self->timestamp))
1081: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1082: else
1083: {
1084: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1085: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1086: }
1087: return;
1088: }
1089:
1090: // if we can't see the target, spawn stuff
1091: if ((self->monsterinfo.attack_state == AS_BLIND) && (blaster_frames))
1092: {
1093: self->monsterinfo.currentmove = &widow_move_spawn;
1094: return;
1095: }
1096:
1097: range = realrange (self, self->enemy);
1098:
1099: if (range < 600)
1100: {
1101: luck = random();
1102: if (SLOTS_LEFT >= 2)
1103: {
1104: if (luck <= 0.40)
1105: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1106: else if ((luck <= 0.7) && !(level.time < self->timestamp))
1107: {
1108: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1109: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1110: }
1111: else
1112: self->monsterinfo.currentmove = &widow_move_spawn;
1113: }
1114: else
1115: {
1116: if ((luck <= 0.50) || (level.time < self->timestamp))
1117: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1118: else
1119: {
1120: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1121: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1122: }
1123: }
1124: }
1125: else
1126: {
1127: luck = random();
1128: if (SLOTS_LEFT >= 2)
1129: {
1130: if (luck < 0.3)
1131: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1132: else if ((luck < 0.65) || (level.time < self->timestamp))
1133: self->monsterinfo.currentmove = &widow_move_spawn;
1134: else
1135: {
1136: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1137: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1138: }
1139: }
1140: else
1141: {
1142: if ((luck < 0.45) || (level.time < self->timestamp))
1143: self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1144: else
1145: {
1146: gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1147: self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1148: }
1149: }
1150: }
1151: }
1152: */
1153: void widow_attack_blaster (edict_t *self)
1154: {
1155: self->monsterinfo.pausetime = level.time + 1.0 + (2.0*random());
1156: // self->monsterinfo.pausetime = level.time + 100;
1157: // self->plat2flags = 0;
1158: self->monsterinfo.currentmove = &widow_move_attack_blaster;
1159: self->monsterinfo.nextframe = WidowTorso (self);
1160: }
1161:
1162: void widow_reattack_blaster (edict_t *self)
1163: {
1164: WidowBlaster(self);
1165:
1166: if ((g_showlogic) && (g_showlogic->value))
1167: {
1168: if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l)
1169: gi.dprintf ("pulling left!\n");
1170: if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r)
1171: gi.dprintf ("pulling right!\n");
1172: }
1173:
1174: // self->monsterinfo.currentmove = &widow_move_attack_blaster;
1175: // self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
1176: // return;
1177: // if WidowBlaster bailed us out of the frames, just bail
1178: if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) ||
1179: (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r))
1180: return;
1181:
1182: // if we're not done with the attack, don't leave the sequence
1183: if (self->monsterinfo.pausetime >= level.time)
1184: return;
1185:
1186: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1187:
1188: self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1189: }
1190: /*
1191: if ( infront(self, self->enemy) )
1192: if (random() <= 0.5)
1193: if ((random() < 0.7) || (SLOTS_LEFT <= 1))
1194: self->monsterinfo.currentmove = &widow_move_attack_blaster;
1195: else
1196: self->monsterinfo.currentmove = &widow_move_spawn;
1197: else
1198: self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1199: else
1200: self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1201: }
1202: */
1203:
1204:
1205: void widow_pain (edict_t *self, edict_t *other, float kick, int damage)
1206: {
1207: if (self->health < (self->max_health / 2))
1208: self->s.skinnum = 1;
1209:
1210: if (skill->value == 3)
1211: return; // no pain anims in nightmare
1212:
1213: if (level.time < self->pain_debounce_time)
1214: return;
1215:
1216: if (self->monsterinfo.pausetime == 100000000)
1217: self->monsterinfo.pausetime = 0;
1218:
1219: self->pain_debounce_time = level.time + 3;
1220:
1221: if (damage < 15)
1222: {
1223: gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
1224: }
1225: else if (damage < 75)
1226: {
1227: if ((skill->value < 3) && (random() < (0.6 - (0.2*((float)skill->value)))))
1228: {
1229: self->monsterinfo.currentmove = &widow_move_pain_light;
1230: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1231: }
1232: gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
1233: }
1234: else
1235: {
1236: if ((skill->value < 3) && (random() < (0.75 - (0.1*((float)skill->value)))))
1237: {
1238: self->monsterinfo.currentmove = &widow_move_pain_heavy;
1239: self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1240: }
1241: gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
1242: }
1243: }
1244:
1245: void widow_dead (edict_t *self)
1246: {
1247: VectorSet (self->mins, -56, -56, 0);
1248: VectorSet (self->maxs, 56, 56, 80);
1249: self->movetype = MOVETYPE_TOSS;
1250: self->svflags |= SVF_DEADMONSTER;
1251: self->nextthink = 0;
1252: gi.linkentity (self);
1253: }
1254:
1255: void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1256: {
1257: self->deadflag = DEAD_DEAD;
1258: self->takedamage = DAMAGE_NO;
1259: self->count = 0;
1260: self->monsterinfo.quad_framenum = 0;
1261: self->monsterinfo.double_framenum = 0;
1262: self->monsterinfo.invincible_framenum = 0;
1263: self->monsterinfo.currentmove = &widow_move_death;
1264: }
1265:
1266: void widow_melee (edict_t *self)
1267: {
1268: // monster_done_dodge (self);
1269: self->monsterinfo.currentmove = &widow_move_attack_kick;
1270: }
1271:
1272: void WidowGoinQuad (edict_t *self, float framenum)
1273: {
1274: self->monsterinfo.quad_framenum = framenum;
1275: widow_damage_multiplier = 4;
1276: }
1277:
1278: void WidowDouble (edict_t *self, float framenum)
1279: {
1280: self->monsterinfo.double_framenum = framenum;
1281: widow_damage_multiplier = 2;
1282: }
1283:
1284: void WidowPent (edict_t *self, float framenum)
1285: {
1286: self->monsterinfo.invincible_framenum = framenum;
1287: }
1288:
1289: void WidowPowerArmor (edict_t *self)
1290: {
1291: self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
1292: // I don't like this, but it works
1293: if (self->monsterinfo.power_armor_power <= 0)
1294: self->monsterinfo.power_armor_power += 250 * skill->value;
1295: }
1296:
1297: void WidowRespondPowerup (edict_t *self, edict_t *other)
1298: {
1299: if (other->s.effects & EF_QUAD)
1300: {
1301: if (skill->value == 1)
1302: WidowDouble (self, other->client->quad_framenum);
1303: else if (skill->value == 2)
1304: WidowGoinQuad (self, other->client->quad_framenum);
1305: else if (skill->value == 3)
1306: {
1307: WidowGoinQuad (self, other->client->quad_framenum);
1308: WidowPowerArmor (self);
1309: }
1310: }
1311: else if (other->s.effects & EF_DOUBLE)
1312: {
1313: if (skill->value == 2)
1314: WidowDouble (self, other->client->double_framenum);
1315: else if (skill->value == 3)
1316: {
1317: WidowDouble (self, other->client->double_framenum);
1318: WidowPowerArmor (self);
1319: }
1320: }
1321: else
1322: widow_damage_multiplier = 1;
1323:
1324: if (other->s.effects & EF_PENT)
1325: {
1326: if (skill->value == 1)
1327: WidowPowerArmor (self);
1328: else if (skill->value == 2)
1329: WidowPent (self, other->client->invincible_framenum);
1330: else if (skill->value == 3)
1331: {
1332: WidowPent (self, other->client->invincible_framenum);
1333: WidowPowerArmor (self);
1334: }
1335: }
1336: }
1337:
1338: void WidowPowerups (edict_t *self)
1339: {
1340: int player;
1341: edict_t *ent;
1342:
1343: if (!(coop && coop->value))
1344: {
1345: WidowRespondPowerup (self, self->enemy);
1346: }
1347: else
1348: {
1349: // in coop, check for pents, then quads, then doubles
1350: for (player = 1; player <= game.maxclients; player++)
1351: {
1352: ent = &g_edicts[player];
1353: if (!ent->inuse)
1354: continue;
1355: if (!ent->client)
1356: continue;
1357: if (ent->s.effects & EF_PENT)
1358: {
1359: WidowRespondPowerup (self, ent);
1360: return;
1361: }
1362: }
1363:
1364: for (player = 1; player <= game.maxclients; player++)
1365: {
1366: ent = &g_edicts[player];
1367: if (!ent->inuse)
1368: continue;
1369: if (!ent->client)
1370: continue;
1371: if (ent->s.effects & EF_QUAD)
1372: {
1373: WidowRespondPowerup (self, ent);
1374: return;
1375: }
1376: }
1377:
1378: for (player = 1; player <= game.maxclients; player++)
1379: {
1380: ent = &g_edicts[player];
1381: if (!ent->inuse)
1382: continue;
1383: if (!ent->client)
1384: continue;
1385: if (ent->s.effects & EF_DOUBLE)
1386: {
1387: WidowRespondPowerup (self, ent);
1388: return;
1389: }
1390: }
1391: }
1392: }
1393:
1394: qboolean Widow_CheckAttack (edict_t *self)
1395: {
1396: vec3_t spot1, spot2;
1397: vec3_t temp;
1398: float chance;
1399: trace_t tr;
1400: qboolean enemy_infront;
1401: int enemy_range;
1402: float enemy_yaw;
1403: float real_enemy_range;
1404:
1405: if (!self->enemy)
1406: return false;
1407:
1408: WidowPowerups(self);
1409:
1410: if (self->monsterinfo.currentmove == &widow_move_run)
1411: {
1412: // if we're in run, make sure we're in a good frame for attacking before doing anything else
1413: // frames 1,2,3,9,10,11,13 good to fire
1414: switch (self->s.frame)
1415: {
1416: case FRAME_walk04:
1417: case FRAME_walk05:
1418: case FRAME_walk06:
1419: case FRAME_walk07:
1420: case FRAME_walk08:
1421: case FRAME_walk12:
1422: {
1423: // if ((g_showlogic) && (g_showlogic->value))
1424: // gi.dprintf ("Not in good walk frame (%d), not attacking\n", (self->s.frame - FRAME_walk01+1));
1425: return false;
1426: }
1427: default:
1428: break;
1429: }
1430: }
1431:
1432: // give a LARGE bias to spawning things when we have room
1433: // use AI_BLOCKED as a signal to attack to spawn
1434: if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150))
1435: {
1436: self->monsterinfo.aiflags |= AI_BLOCKED;
1437: self->monsterinfo.attack_state = AS_MISSILE;
1438: return true;
1439: }
1440:
1441: if (self->enemy->health > 0)
1442: {
1443: // see if any entities are in the way of the shot
1444: VectorCopy (self->s.origin, spot1);
1445: spot1[2] += self->viewheight;
1446: VectorCopy (self->enemy->s.origin, spot2);
1447: spot2[2] += self->enemy->viewheight;
1448:
1449: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
1450:
1451: // do we have a clear shot?
1452: if (tr.ent != self->enemy)
1453: {
1454: // go ahead and spawn stuff if we're mad a a client
1455: if (self->enemy->client && SELF_SLOTS_LEFT >= 2)
1456: {
1457: self->monsterinfo.attack_state = AS_BLIND;
1458: return true;
1459: }
1460:
1461: // PGM - we want them to go ahead and shoot at info_notnulls if they can.
1462: if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
1463: return false;
1464: }
1465: }
1466:
1467: enemy_infront = infront(self, self->enemy);
1468:
1469: enemy_range = range(self, self->enemy);
1470: VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
1471: enemy_yaw = vectoyaw2(temp);
1472:
1473: self->ideal_yaw = enemy_yaw;
1474:
1475: real_enemy_range = realrange (self, self->enemy);
1476:
1477: if (g_showlogic->value)
1478: gi.dprintf ("range = %2.2f\n", real_enemy_range);
1479:
1480: // melee attack
1481: // if (enemy_range == RANGE_MELEE)
1482: if (real_enemy_range <= (MELEE_DISTANCE+20))
1483: {
1484: // don't always melee in easy mode
1485: if (skill->value == 0 && (rand()&3) )
1486: return false;
1487: if (self->monsterinfo.melee)
1488: self->monsterinfo.attack_state = AS_MELEE;
1489: else
1490: self->monsterinfo.attack_state = AS_MISSILE;
1491: return true;
1492: }
1493:
1494: if (level.time < self->monsterinfo.attack_finished)
1495: return false;
1496:
1497: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
1498: {
1499: chance = 0.4;
1500: }
1501: else if (enemy_range == RANGE_MELEE)
1502: {
1503: chance = 0.8;
1504: }
1505: else if (enemy_range == RANGE_NEAR)
1506: {
1507: chance = 0.7;
1508: }
1509: else if (enemy_range == RANGE_MID)
1510: {
1511: chance = 0.6;
1512: }
1513: else if (enemy_range == RANGE_FAR)
1514: {
1515: chance = 0.5;
1516: }
1517:
1518: // PGM - go ahead and shoot every time if it's a info_notnull
1519: if ((random () < chance) || (self->enemy->solid == SOLID_NOT))
1520: {
1521: self->monsterinfo.attack_state = AS_MISSILE;
1522: return true;
1523: }
1524:
1525: return false;
1526: }
1527:
1528: qboolean widow_blocked (edict_t *self, float dist)
1529: {
1530: // if we get blocked while we're in our run/attack mode, turn on a meaningless (in this context)AI flag,
1531: // and call attack to get a new attack sequence. make sure to turn it off when we're done.
1532: //
1533: // I'm using AI_TARGET_ANGER for this purpose
1534:
1535: if (self->monsterinfo.currentmove == &widow_move_run_attack)
1536: {
1537: self->monsterinfo.aiflags |= AI_TARGET_ANGER;
1538: if (self->monsterinfo.checkattack(self))
1539: self->monsterinfo.attack(self);
1540: else
1541: self->monsterinfo.run(self);
1542: return true;
1543: }
1544:
1545: if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
1546: return true;
1547:
1548: /*
1549: if(blocked_checkjump (self, dist, 192, 40))
1550: {
1551: infantry_jump(self);
1552: return true;
1553: }
1554:
1555: if(blocked_checkplat (self, dist))
1556: return true;
1557: */
1558: return false;
1559: }
1560:
1561: void WidowCalcSlots (edict_t *self)
1562: {
1563: int old_slots;
1564:
1565: old_slots = self->monsterinfo.monster_slots;
1566:
1567: switch ((int)skill->value)
1568: {
1569: case 0:
1570: case 1:
1571: self->monsterinfo.monster_slots = 3;
1572: break;
1573: case 2:
1574: self->monsterinfo.monster_slots = 4;
1575: break;
1576: case 3:
1577: self->monsterinfo.monster_slots = 6;
1578: break;
1579: default:
1580: self->monsterinfo.monster_slots = 3;
1581: break;
1582: }
1583: if (coop->value)
1584: {
1585: self->monsterinfo.monster_slots = min (8, self->monsterinfo.monster_slots + (2*(CountPlayers()-1)));
1586: }
1587: if ((g_showlogic) && (g_showlogic->value) && (old_slots != self->monsterinfo.monster_slots))
1588: gi.dprintf ("number of slots changed from %d to %d\n", old_slots, self->monsterinfo.monster_slots);
1589: }
1590:
1591: void WidowPrecache ()
1592: {
1593: // cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs
1594: gi.soundindex ("parasite/parpain1.wav");
1595: gi.soundindex ("parasite/parpain2.wav");
1596: gi.soundindex ("parasite/pardeth1.wav");
1597: gi.soundindex ("parasite/paratck1.wav");
1598: gi.soundindex ("parasite/parsght1.wav");
1599: gi.soundindex ("infantry/melee2.wav");
1600:
1601: gi.soundindex ("tank/tnkatck3.wav");
1602: gi.modelindex ("models/proj/laser2/tris.md2");
1603:
1604: gi.modelindex ("models/monsters/stalker/tris.md2");
1605: gi.modelindex ("models/items/spawngro2/tris.md2");
1606: gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
1607: gi.modelindex ("models/objects/gibs/gear/tris.md2");
1608: gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2");
1609: gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2");
1610: gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2");
1611: gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2");
1612: gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2");
1613: gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2");
1614: gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2");
1615: gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2");
1616: gi.modelindex ("models/monsters/legs/tris.md2");
1617:
1618: gi.soundindex ("misc/bigtele.wav");
1619: }
1620:
1621:
1622: /*QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight
1623: */
1624: void SP_monster_widow (edict_t *self)
1625: {
1626: if (deathmatch->value)
1627: {
1628: G_FreeEdict (self);
1629: return;
1630: }
1631:
1632: sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav");
1633: sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav");
1634: sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav");
1635: sound_death = gi.soundindex ("weapons/bfg__f1y.wav");
1636: sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
1637: sound_rail = gi.soundindex ("gladiator/railgun.wav");
1638:
1639: self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
1640:
1641: self->movetype = MOVETYPE_STEP;
1642: self->solid = SOLID_BBOX;
1643: self->s.modelindex = gi.modelindex ("models/monsters/blackwidow/tris.md2");
1644: VectorSet (self->mins, -40, -40, 0);
1645: VectorSet (self->maxs, 40, 40, 144);
1646:
1647: self->health = 2000 + 1000*(skill->value);
1648: if (coop->value)
1649: self->health += 500*(skill->value);
1650: // self->health = 1;
1651: self->gib_health = -5000;
1652: self->mass = 1500;
1653: /*
1654: if (skill->value == 2)
1655: {
1656: self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
1657: self->monsterinfo.power_armor_power = 250;
1658: }
1659: else */if (skill->value == 3)
1660: {
1661: self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
1662: self->monsterinfo.power_armor_power = 500;
1663: }
1664:
1665: self->yaw_speed = 30;
1666:
1667: self->flags |= FL_IMMUNE_LASER;
1668: self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
1669:
1670: self->pain = widow_pain;
1671: self->die = widow_die;
1672:
1673: self->monsterinfo.melee = widow_melee;
1674: self->monsterinfo.stand = widow_stand;
1675: self->monsterinfo.walk = widow_walk;
1676: self->monsterinfo.run = widow_run;
1677: self->monsterinfo.attack = widow_attack;
1678: self->monsterinfo.search = widow_search;
1679: self->monsterinfo.checkattack = Widow_CheckAttack;
1680: self->monsterinfo.sight = widow_sight;
1681:
1682: self->monsterinfo.blocked = widow_blocked;
1683:
1684: gi.linkentity (self);
1685:
1686: self->monsterinfo.currentmove = &widow_move_stand;
1687: self->monsterinfo.scale = MODEL_SCALE;
1688:
1689: WidowPrecache();
1690: WidowCalcSlots(self);
1691: widow_damage_multiplier = 1;
1692:
1693: walkmonster_start (self);
1694: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.