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