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