|
|
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.