|
|
1.1 ! root 1: /* ! 2: ============================================================================== ! 3: ! 4: stalker ! 5: ! 6: ============================================================================== ! 7: */ ! 8: ! 9: #include "g_local.h" ! 10: #include "m_stalker.h" ! 11: #include <float.h> ! 12: ! 13: static int sound_pain1; ! 14: static int sound_pain2; ! 15: static int sound_die; ! 16: static int sound_launch; ! 17: static int sound_sight; ! 18: static int sound_punch_hit; ! 19: ! 20: int stalker_do_pounce(edict_t *self, vec3_t dest); ! 21: void stalker_stand (edict_t *self); ! 22: void stalker_run (edict_t *self); ! 23: void stalker_walk (edict_t *self); ! 24: void stalker_jump (edict_t *self); ! 25: void stalker_dodge_jump (edict_t *self); ! 26: void stalker_swing_check_l (edict_t *self); ! 27: void stalker_swing_check_r (edict_t *self); ! 28: void stalker_swing_attack (edict_t *self); ! 29: void stalker_jump_straightup (edict_t *self); ! 30: void stalker_jump_wait_land (edict_t *self); ! 31: void stalker_false_death (edict_t *self); ! 32: void stalker_false_death_start (edict_t *self); ! 33: qboolean stalker_ok_to_transition (edict_t *self); ! 34: ! 35: #define STALKER_ON_CEILING(ent) ( ent->gravityVector[2] > 0 ? 1 : 0 ) ! 36: ! 37: //extern qboolean SV_StepDirection (edict_t *ent, float yaw, float dist); ! 38: extern qboolean SV_PointCloseEnough (edict_t *ent, vec3_t goal, float dist); ! 39: extern void drawbbox(edict_t *self); ! 40: ! 41: //========================= ! 42: //========================= ! 43: qboolean stalker_ok_to_transition (edict_t *self) ! 44: { ! 45: trace_t trace; ! 46: vec3_t pt, start; ! 47: float max_dist; ! 48: float margin; ! 49: float end_height; ! 50: ! 51: if(STALKER_ON_CEILING(self)) ! 52: { ! 53: max_dist = -384; ! 54: margin = self->mins[2] - 8; ! 55: } ! 56: else ! 57: { ! 58: max_dist = 180; ! 59: margin = self->maxs[2] + 8; ! 60: } ! 61: ! 62: VectorCopy(self->s.origin, pt); ! 63: pt[2] += max_dist; ! 64: trace = gi.trace (self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID); ! 65: if(trace.fraction == 1.0 || ! 66: !(trace.contents & CONTENTS_SOLID) || ! 67: (trace.ent != world)) ! 68: { ! 69: if(STALKER_ON_CEILING(self)) ! 70: { ! 71: if(trace.plane.normal[2] < 0.9) ! 72: return false; ! 73: } ! 74: else ! 75: { ! 76: if(trace.plane.normal[2] > -0.9) ! 77: return false; ! 78: } ! 79: } ! 80: // gi.dprintf("stalker_check_pt: main check ok\n"); ! 81: ! 82: end_height = trace.endpos[2]; ! 83: ! 84: // check the four corners, tracing only to the endpoint of the center trace (vertically). ! 85: pt[0] = self->absmin[0]; ! 86: pt[1] = self->absmin[1]; ! 87: pt[2] = trace.endpos[2] + margin; // give a little margin of error to allow slight inclines ! 88: VectorCopy(pt, start); ! 89: start[2] = self->s.origin[2]; ! 90: trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); ! 91: if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) ! 92: { ! 93: // gi.dprintf("stalker_check_pt: absmin/absmin failed\n"); ! 94: return false; ! 95: } ! 96: if(abs(end_height + margin - trace.endpos[2]) > 8) ! 97: return false; ! 98: ! 99: pt[0] = self->absmax[0]; ! 100: pt[1] = self->absmin[1]; ! 101: VectorCopy(pt, start); ! 102: start[2] = self->s.origin[2]; ! 103: trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); ! 104: if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) ! 105: { ! 106: // gi.dprintf("stalker_check_pt: absmax/absmin failed\n"); ! 107: return false; ! 108: } ! 109: if(abs(end_height + margin - trace.endpos[2]) > 8) ! 110: return false; ! 111: ! 112: pt[0] = self->absmax[0]; ! 113: pt[1] = self->absmax[1]; ! 114: VectorCopy(pt, start); ! 115: start[2] = self->s.origin[2]; ! 116: trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); ! 117: if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) ! 118: { ! 119: // gi.dprintf("stalker_check_pt: absmax/absmax failed\n"); ! 120: return false; ! 121: } ! 122: if(abs(end_height + margin - trace.endpos[2]) > 8) ! 123: return false; ! 124: ! 125: pt[0] = self->absmin[0]; ! 126: pt[1] = self->absmax[1]; ! 127: VectorCopy(pt, start); ! 128: start[2] = self->s.origin[2]; ! 129: trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); ! 130: if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) ! 131: { ! 132: // gi.dprintf("stalker_check_pt: absmin/absmax failed\n"); ! 133: return false; ! 134: } ! 135: if(abs(end_height + margin - trace.endpos[2]) > 8) ! 136: return false; ! 137: ! 138: return true; ! 139: } ! 140: ! 141: //========================= ! 142: //========================= ! 143: void stalker_sight (edict_t *self, edict_t *other) ! 144: { ! 145: gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); ! 146: } ! 147: ! 148: // ****************** ! 149: // IDLE ! 150: // ****************** ! 151: ! 152: mframe_t stalker_frames_idle [] = ! 153: { ! 154: ai_stand, 0, NULL, ! 155: ai_stand, 0, NULL, ! 156: ai_stand, 0, NULL, ! 157: ai_stand, 0, NULL, ! 158: ai_stand, 0, NULL, ! 159: ! 160: ai_stand, 0, NULL, ! 161: ai_stand, 0, NULL, ! 162: ai_stand, 0, NULL, ! 163: ai_stand, 0, NULL, ! 164: ai_stand, 0, NULL, ! 165: ! 166: ai_stand, 0, NULL, ! 167: ai_stand, 0, NULL, ! 168: ai_stand, 0, NULL, ! 169: ai_stand, 0, NULL, ! 170: ai_stand, 0, NULL, ! 171: ! 172: ai_stand, 0, NULL, ! 173: ai_stand, 0, NULL, ! 174: ai_stand, 0, NULL, ! 175: ai_stand, 0, NULL, ! 176: ai_stand, 0, NULL, ! 177: ! 178: ai_stand, 0, NULL ! 179: }; ! 180: mmove_t stalker_move_idle = {FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand}; ! 181: ! 182: void stalker_idle (edict_t *self) ! 183: { ! 184: self->monsterinfo.currentmove = &stalker_move_idle; ! 185: } ! 186: ! 187: // ****************** ! 188: // STAND ! 189: // ****************** ! 190: ! 191: mframe_t stalker_frames_stand [] = ! 192: { ! 193: ai_stand, 0, NULL, ! 194: ai_stand, 0, NULL, ! 195: ai_stand, 0, NULL, ! 196: ai_stand, 0, NULL, ! 197: ai_stand, 0, NULL, ! 198: ! 199: ai_stand, 0, NULL, ! 200: ai_stand, 0, NULL, ! 201: ai_stand, 0, NULL, ! 202: ai_stand, 0, NULL, ! 203: ai_stand, 0, NULL, ! 204: ! 205: ai_stand, 0, NULL, ! 206: ai_stand, 0, NULL, ! 207: ai_stand, 0, NULL, ! 208: ai_stand, 0, NULL, ! 209: ai_stand, 0, NULL, ! 210: ! 211: ai_stand, 0, NULL, ! 212: ai_stand, 0, NULL, ! 213: ai_stand, 0, NULL, ! 214: ai_stand, 0, NULL, ! 215: ai_stand, 0, NULL, ! 216: ! 217: ai_stand, 0, NULL ! 218: }; ! 219: mmove_t stalker_move_stand = {FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand}; ! 220: ! 221: void stalker_stand (edict_t *self) ! 222: { ! 223: self->monsterinfo.currentmove = &stalker_move_stand; ! 224: } ! 225: ! 226: // ****************** ! 227: // RUN ! 228: // ****************** ! 229: ! 230: mframe_t stalker_frames_run [] = ! 231: { ! 232: ai_run, 13, NULL, ! 233: ai_run, 17, NULL, ! 234: ai_run, 21, NULL, ! 235: ai_run, 18, NULL ! 236: ! 237: /* ai_run, 15, NULL, ! 238: ai_run, 20, NULL, ! 239: ai_run, 18, NULL, ! 240: ai_run, 14, NULL*/ ! 241: }; ! 242: mmove_t stalker_move_run = {FRAME_run01, FRAME_run04, stalker_frames_run, NULL}; ! 243: ! 244: void stalker_run (edict_t *self) ! 245: { ! 246: // gi.dprintf("stalker_run %5.1f\n", level.time); ! 247: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 248: self->monsterinfo.currentmove = &stalker_move_stand; ! 249: else ! 250: self->monsterinfo.currentmove = &stalker_move_run; ! 251: } ! 252: ! 253: // ****************** ! 254: // WALK ! 255: // ****************** ! 256: ! 257: mframe_t stalker_frames_walk [] = ! 258: { ! 259: ai_walk, 4, NULL, ! 260: ai_walk, 6, NULL, ! 261: ai_walk, 8, NULL, ! 262: ai_walk, 5, NULL, ! 263: ! 264: ai_walk, 4, NULL, ! 265: ai_walk, 6, NULL, ! 266: ai_walk, 8, NULL, ! 267: ai_walk, 4, NULL ! 268: }; ! 269: mmove_t stalker_move_walk = {FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk}; ! 270: ! 271: void stalker_walk (edict_t *self) ! 272: { ! 273: // gi.dprintf("stalker_walk\n"); ! 274: self->monsterinfo.currentmove = &stalker_move_walk; ! 275: } ! 276: ! 277: // ****************** ! 278: // false death ! 279: // ****************** ! 280: mframe_t stalker_frames_reactivate [] = ! 281: { ! 282: ai_move, 0, NULL, ! 283: ai_move, 0, NULL, ! 284: ai_move, 0, NULL, ! 285: ai_move, 0, NULL ! 286: }; ! 287: mmove_t stalker_move_false_death_end = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run }; ! 288: ! 289: void stalker_reactivate (edict_t *self) ! 290: { ! 291: self->monsterinfo.aiflags &= ~AI_STAND_GROUND; ! 292: self->monsterinfo.currentmove = &stalker_move_false_death_end; ! 293: } ! 294: ! 295: void stalker_heal (edict_t *self) ! 296: { ! 297: if(skill->value == 2) ! 298: self->health+=2; ! 299: else if(skill->value == 3) ! 300: self->health+=3; ! 301: else ! 302: self->health++; ! 303: ! 304: // gi.dprintf("stalker_heal: %d\n", self->health); ! 305: ! 306: if(self->health > (self->max_health/2)) ! 307: self->s.skinnum = 0; ! 308: ! 309: if(self->health >= self->max_health) ! 310: { ! 311: self->health = self->max_health; ! 312: stalker_reactivate(self); ! 313: } ! 314: } ! 315: ! 316: mframe_t stalker_frames_false_death [] = ! 317: { ! 318: ai_move, 0, stalker_heal, ! 319: ai_move, 0, stalker_heal, ! 320: ai_move, 0, stalker_heal, ! 321: ai_move, 0, stalker_heal, ! 322: ai_move, 0, stalker_heal, ! 323: ! 324: ai_move, 0, stalker_heal, ! 325: ai_move, 0, stalker_heal, ! 326: ai_move, 0, stalker_heal, ! 327: ai_move, 0, stalker_heal, ! 328: ai_move, 0, stalker_heal ! 329: }; ! 330: mmove_t stalker_move_false_death = {FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death}; ! 331: ! 332: void stalker_false_death (edict_t *self) ! 333: { ! 334: self->monsterinfo.currentmove = &stalker_move_false_death; ! 335: } ! 336: ! 337: mframe_t stalker_frames_false_death_start [] = ! 338: { ! 339: ai_move, 0, NULL, ! 340: ai_move, 0, NULL, ! 341: ai_move, 0, NULL, ! 342: ai_move, 0, NULL, ! 343: ai_move, 0, NULL, ! 344: ! 345: ai_move, 0, NULL, ! 346: ai_move, 0, NULL, ! 347: ai_move, 0, NULL, ! 348: ai_move, 0, NULL, ! 349: }; ! 350: mmove_t stalker_move_false_death_start = {FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death}; ! 351: ! 352: void stalker_false_death_start (edict_t *self) ! 353: { ! 354: self->s.angles[2] = 0; ! 355: VectorSet(self->gravityVector, 0, 0, -1); ! 356: ! 357: self->monsterinfo.aiflags |= AI_STAND_GROUND; ! 358: self->monsterinfo.currentmove = &stalker_move_false_death_start; ! 359: } ! 360: ! 361: ! 362: // ****************** ! 363: // PAIN ! 364: // ****************** ! 365: ! 366: mframe_t stalker_frames_pain [] = ! 367: { ! 368: ai_move, 0, NULL, ! 369: ai_move, 0, NULL, ! 370: ai_move, 0, NULL, ! 371: ai_move, 0, NULL ! 372: }; ! 373: mmove_t stalker_move_pain = {FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run}; ! 374: ! 375: void stalker_pain (edict_t *self, edict_t *other, float kick, int damage) ! 376: { ! 377: if (self->deadflag == DEAD_DEAD) ! 378: return; ! 379: ! 380: if (self->health < (self->max_health / 2)) ! 381: { ! 382: self->s.skinnum = 1; ! 383: } ! 384: ! 385: if (skill->value == 3) ! 386: return; // no pain anims in nightmare ! 387: ! 388: // if (self->monsterinfo.aiflags & AI_DODGING) ! 389: // monster_done_dodge (self); ! 390: ! 391: if (self->groundentity == NULL) ! 392: return; ! 393: ! 394: // if we're reactivating or false dying, ignore the pain. ! 395: if (self->monsterinfo.currentmove == &stalker_move_false_death_end || ! 396: self->monsterinfo.currentmove == &stalker_move_false_death_start ) ! 397: return; ! 398: ! 399: if (self->monsterinfo.currentmove == &stalker_move_false_death) ! 400: { ! 401: stalker_reactivate(self); ! 402: return; ! 403: } ! 404: ! 405: if ((self->health > 0) && (self->health < (self->max_health / 4))) ! 406: { ! 407: if(random() < (0.2 * skill->value)) ! 408: { ! 409: if( !STALKER_ON_CEILING(self) || stalker_ok_to_transition(self) ) ! 410: { ! 411: // gi.dprintf("starting false death sequence\n"); ! 412: stalker_false_death_start(self); ! 413: return; ! 414: } ! 415: } ! 416: } ! 417: ! 418: if (level.time < self->pain_debounce_time) ! 419: return; ! 420: ! 421: self->pain_debounce_time = level.time + 3; ! 422: ! 423: // gi.dprintf("stalker_pain\n"); ! 424: if (damage > 10) // don't react unless the damage was significant ! 425: { ! 426: // stalker should dodge jump periodically to help avoid damage. ! 427: if(self->groundentity && (random() < 0.5)) ! 428: stalker_dodge_jump(self); ! 429: else ! 430: self->monsterinfo.currentmove = &stalker_move_pain; ! 431: ! 432: if (random() < 0.5) ! 433: gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); ! 434: else ! 435: gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); ! 436: } ! 437: } ! 438: ! 439: ! 440: // ****************** ! 441: // STALKER ATTACK ! 442: // ****************** ! 443: ! 444: //extern qboolean infront (edict_t *self, edict_t *other); ! 445: ! 446: void stalker_shoot_attack (edict_t *self) ! 447: { ! 448: vec3_t offset, start, f, r, dir; ! 449: vec3_t end; ! 450: float time, dist; ! 451: trace_t trace; ! 452: ! 453: if(!has_valid_enemy(self)) ! 454: return; ! 455: ! 456: if(self->groundentity && random() < 0.33) ! 457: { ! 458: VectorSubtract (self->enemy->s.origin, self->s.origin, dir); ! 459: dist = VectorLength (dir); ! 460: ! 461: if((dist > 256) || (random() < 0.5)) ! 462: stalker_do_pounce(self, self->enemy->s.origin); ! 463: else ! 464: stalker_jump_straightup (self); ! 465: } ! 466: ! 467: // FIXME -- keep this but use a custom one ! 468: // if (!infront(self, self->enemy)) ! 469: // return; ! 470: ! 471: AngleVectors (self->s.angles, f, r, NULL); ! 472: VectorSet (offset, 24, 0, 6); ! 473: G_ProjectSource (self->s.origin, offset, f, r, start); ! 474: ! 475: VectorSubtract(self->enemy->s.origin, start, dir); ! 476: if(random() < (0.20 + 0.1 * skill->value)) ! 477: { ! 478: dist = VectorLength(dir); ! 479: time = dist / 1000; ! 480: VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end); ! 481: VectorSubtract(end, start, dir); ! 482: } ! 483: else ! 484: VectorCopy(self->enemy->s.origin, end); ! 485: ! 486: trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); ! 487: if(trace.ent == self->enemy || trace.ent == world) ! 488: monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER); ! 489: // else ! 490: // gi.dprintf("blocked by entity %s\n", trace.ent->classname); ! 491: } ! 492: ! 493: void stalker_shoot_attack2 (edict_t *self) ! 494: { ! 495: // if (random() < (0.4+(float)skill->value)) ! 496: // stalker_shoot_attack (self); ! 497: ! 498: if (random() < (0.4 + (0.1 * (float)skill->value))) ! 499: stalker_shoot_attack (self); ! 500: } ! 501: ! 502: mframe_t stalker_frames_shoot [] = ! 503: { ! 504: ai_charge, 13, NULL, ! 505: ai_charge, 17, stalker_shoot_attack, ! 506: ai_charge, 21, NULL, ! 507: ai_charge, 18, stalker_shoot_attack2 ! 508: }; ! 509: mmove_t stalker_move_shoot = {FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run}; ! 510: ! 511: void stalker_attack_ranged (edict_t *self) ! 512: { ! 513: if(!has_valid_enemy(self)) ! 514: return; ! 515: ! 516: // PMM - circle strafe stuff ! 517: if (random() > (1.0 - (0.5/(float)(skill->value)))) ! 518: { ! 519: self->monsterinfo.attack_state = AS_STRAIGHT; ! 520: } ! 521: else ! 522: { ! 523: if (random () <= 0.5) // switch directions ! 524: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; ! 525: self->monsterinfo.attack_state = AS_SLIDING; ! 526: } ! 527: self->monsterinfo.currentmove = &stalker_move_shoot; ! 528: } ! 529: ! 530: // ****************** ! 531: // close combat ! 532: // ****************** ! 533: ! 534: void stalker_swing_attack (edict_t *self) ! 535: { ! 536: vec3_t aim; ! 537: ! 538: VectorSet (aim, MELEE_DISTANCE, 0, 0); ! 539: if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) ! 540: gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); ! 541: } ! 542: ! 543: mframe_t stalker_frames_swing_l [] = ! 544: { ! 545: ai_charge, 2, NULL, ! 546: ai_charge, 4, NULL, ! 547: ai_charge, 6, NULL, ! 548: ai_charge, 10, NULL, ! 549: ! 550: ai_charge, 5, stalker_swing_attack, ! 551: ai_charge, 5, NULL, ! 552: ai_charge, 5, NULL, ! 553: ai_charge, 5, NULL // stalker_swing_check_l ! 554: }; ! 555: mmove_t stalker_move_swing_l = {FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run}; ! 556: ! 557: mframe_t stalker_frames_swing_r [] = ! 558: { ! 559: ai_charge, 4, NULL, ! 560: ai_charge, 6, NULL, ! 561: ai_charge, 6, stalker_swing_attack, ! 562: ai_charge, 10, NULL, ! 563: ai_charge, 5, NULL // stalker_swing_check_r ! 564: }; ! 565: mmove_t stalker_move_swing_r = {FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run}; ! 566: ! 567: void stalker_attack_melee (edict_t *self) ! 568: { ! 569: if(!has_valid_enemy(self)) ! 570: return; ! 571: ! 572: if(random() < 0.5) ! 573: { ! 574: self->monsterinfo.currentmove = &stalker_move_swing_l; ! 575: } ! 576: else ! 577: { ! 578: self->monsterinfo.currentmove = &stalker_move_swing_r; ! 579: } ! 580: } ! 581: ! 582: ! 583: // ****************** ! 584: // POUNCE ! 585: // ****************** ! 586: ! 587: #define PI 3.14159 ! 588: #define RAD2DEG(x) (x * (float)180.0 / (float)PI) ! 589: #define DEG2RAD(x) (x * (float)PI / (float)180.0) ! 590: #define FAUX_GRAVITY 800.0 ! 591: ! 592: // ==================== ! 593: // ==================== ! 594: void calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles) ! 595: { ! 596: float distV, distH; ! 597: float one, cosU; ! 598: float l, U; ! 599: vec3_t dist; ! 600: ! 601: VectorSubtract(end, start, dist); ! 602: distH = (float)sqrt(dist[0]*dist[0] + dist[1]*dist[1]); ! 603: distV = dist[2]; ! 604: if(distV < 0) ! 605: distV = 0 - distV; ! 606: ! 607: if(distV) ! 608: { ! 609: l = (float) sqrt(distH*distH + distV*distV); ! 610: U = (float) atan(distV / distH); ! 611: if(dist[2] > 0) ! 612: U = (float)0.0 - U; ! 613: ! 614: angles[2] = 0.0; ! 615: ! 616: cosU = (float)cos(U); ! 617: one = l * FAUX_GRAVITY * (cosU * cosU); ! 618: one = one / (velocity * velocity); ! 619: one = one - (float)sin(U); ! 620: // one = ((l * FAUX_GRAVITY * (cosU * cosU)) / (velocity * velocity)) - (float)sin(U); ! 621: angles[0] = (float)asin(one); ! 622: if(_isnan(angles[0])) ! 623: angles[2] = 1.0; ! 624: angles[1] = (float)PI - angles[0]; ! 625: if(_isnan(angles[1])) ! 626: angles[2] = 1.0; ! 627: ! 628: angles[0] = RAD2DEG ( (angles[0] - U) / 2.0 ); ! 629: angles[1] = RAD2DEG ( (angles[1] - U) / 2.0 ); ! 630: } ! 631: else ! 632: { ! 633: l = (float) sqrt(distH*distH + distV*distV); ! 634: ! 635: angles[2] = 0.0; ! 636: ! 637: one = l * FAUX_GRAVITY; ! 638: one = one / (velocity * velocity); ! 639: angles[0] = (float)asin(one); ! 640: if(_isnan(angles[0])) ! 641: angles[2] = 1.0; ! 642: angles[1] = (float)PI - angles[0]; ! 643: if(_isnan(angles[1])) ! 644: angles[2] = 1.0; ! 645: ! 646: angles[0] = RAD2DEG ( (angles[0]) / 2.0 ); ! 647: angles[1] = RAD2DEG ( (angles[1]) / 2.0 ); ! 648: } ! 649: } ! 650: ! 651: // ==================== ! 652: // ==================== ! 653: int stalker_check_lz (edict_t *self, edict_t *target, vec3_t dest) ! 654: { ! 655: vec3_t jumpLZ; ! 656: ! 657: if( (gi.pointcontents (dest) & MASK_WATER) || (target->waterlevel)) ! 658: { ! 659: // gi.dprintf ("you won't make me jump in water!\n"); ! 660: return false; ! 661: } ! 662: ! 663: if( !target->groundentity ) ! 664: { ! 665: // gi.dprintf( "I'll wait until you land..\n"); ! 666: return false; ! 667: } ! 668: ! 669: // check under the player's four corners ! 670: // if they're not solid, bail. ! 671: jumpLZ[0] = self->enemy->mins[0]; ! 672: jumpLZ[1] = self->enemy->mins[1]; ! 673: jumpLZ[2] = self->enemy->mins[2] - 0.25; ! 674: if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) ! 675: return false; ! 676: ! 677: jumpLZ[0] = self->enemy->maxs[0]; ! 678: jumpLZ[1] = self->enemy->mins[1]; ! 679: if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) ! 680: return false; ! 681: ! 682: jumpLZ[0] = self->enemy->maxs[0]; ! 683: jumpLZ[1] = self->enemy->maxs[1]; ! 684: if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) ! 685: return false; ! 686: ! 687: jumpLZ[0] = self->enemy->mins[0]; ! 688: jumpLZ[1] = self->enemy->maxs[1]; ! 689: if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) ! 690: return false; ! 691: ! 692: return true; ! 693: } ! 694: ! 695: // ==================== ! 696: // ==================== ! 697: int stalker_do_pounce(edict_t *self, vec3_t dest) ! 698: { ! 699: vec3_t forward, right; ! 700: vec3_t dist; ! 701: vec_t length; ! 702: vec3_t jumpAngles; ! 703: vec3_t jumpLZ; ! 704: float velocity = 400.1; ! 705: trace_t trace; ! 706: int preferHighJump; ! 707: ! 708: // don't pounce when we're on the ceiling ! 709: if(STALKER_ON_CEILING(self)) ! 710: return false; ! 711: ! 712: if(!stalker_check_lz (self, self->enemy, dest)) ! 713: return false; ! 714: ! 715: VectorSubtract(dest, self->s.origin, dist); ! 716: ! 717: // make sure we're pointing in that direction 15deg margin of error. ! 718: vectoangles2 (dist, jumpAngles); ! 719: if(abs(jumpAngles[YAW] - self->s.angles[YAW]) > 45) ! 720: return false; // not facing the player... ! 721: ! 722: self->ideal_yaw = jumpAngles[YAW]; ! 723: M_ChangeYaw(self); ! 724: ! 725: length = VectorLength(dist); ! 726: if(length > 450) ! 727: return false; // can't jump that far... ! 728: ! 729: VectorCopy(dest, jumpLZ); ! 730: ! 731: preferHighJump = 0; ! 732: ! 733: // if we're having to jump up a distance, jump a little too high to compensate. ! 734: if(dist[2] >= 32.0) ! 735: { ! 736: preferHighJump = 1; ! 737: jumpLZ[2] += 32; ! 738: } ! 739: ! 740: trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, dest, self, MASK_MONSTERSOLID); ! 741: if((trace.fraction < 1) && (trace.ent != self->enemy)) ! 742: { ! 743: // gi.dprintf("prefer high jump angle\n"); ! 744: preferHighJump = 1; ! 745: } ! 746: ! 747: // find a valid angle/velocity combination ! 748: while(velocity <= 800) ! 749: { ! 750: calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles); ! 751: if((!_isnan(jumpAngles[0])) || (!_isnan(jumpAngles[1]))) ! 752: break; ! 753: ! 754: velocity+=200; ! 755: }; ! 756: ! 757: if(!preferHighJump && (!_isnan(jumpAngles[0])) ) ! 758: { ! 759: AngleVectors (self->s.angles, forward, right, NULL); ! 760: VectorNormalize ( forward ) ; ! 761: ! 762: VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity); ! 763: self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME); ! 764: // gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", ! 765: // jumpAngles[0], jumpAngles[1], jumpAngles[0], ! 766: // self->velocity[0], self->velocity[1], self->velocity[2]); ! 767: return 1; ! 768: } ! 769: ! 770: if(!_isnan(jumpAngles[1])) ! 771: { ! 772: AngleVectors (self->s.angles, forward, right, NULL); ! 773: VectorNormalize ( forward ) ; ! 774: ! 775: VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity); ! 776: self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME); ! 777: // gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", ! 778: // jumpAngles[0], jumpAngles[1], jumpAngles[1], ! 779: // self->velocity[0], self->velocity[1], self->velocity[2]); ! 780: return 1; ! 781: } ! 782: ! 783: // gi.dprintf(" nan\n"); ! 784: return 0; ! 785: } ! 786: ! 787: // ****************** ! 788: // DODGE ! 789: // ****************** ! 790: ! 791: //=================== ! 792: // stalker_jump_straightup ! 793: //=================== ! 794: void stalker_jump_straightup (edict_t *self) ! 795: { ! 796: if (self->deadflag == DEAD_DEAD) ! 797: return; ! 798: ! 799: if(STALKER_ON_CEILING(self)) ! 800: { ! 801: if(stalker_ok_to_transition(self)) ! 802: { ! 803: // gi.dprintf("falling off ceiling %d\n", self->health); ! 804: self->gravityVector[2] = -1; ! 805: self->s.angles[2] += 180.0; ! 806: if(self->s.angles[2] > 360.0) ! 807: self->s.angles[2] -= 360.0; ! 808: self->groundentity = NULL; ! 809: } ! 810: } ! 811: else if(self->groundentity) // make sure we're standing on SOMETHING... ! 812: { ! 813: self->velocity[0] += ((random() * 10) - 5); ! 814: self->velocity[1] += ((random() * 10) - 5); ! 815: self->velocity[2] += -400 * self->gravityVector[2]; ! 816: if(stalker_ok_to_transition(self)) ! 817: { ! 818: // gi.dprintf("falling TO ceiling %d\n", self->health); ! 819: self->gravityVector[2] = 1; ! 820: self->s.angles[2] = 180.0; ! 821: self->groundentity = NULL; ! 822: } ! 823: } ! 824: } ! 825: ! 826: mframe_t stalker_frames_jump_straightup [] = ! 827: { ! 828: ai_move, 1, stalker_jump_straightup, ! 829: ai_move, 1, stalker_jump_wait_land, ! 830: ai_move, -1, NULL, ! 831: ai_move, -1, NULL ! 832: }; ! 833: ! 834: mmove_t stalker_move_jump_straightup = {FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run}; ! 835: ! 836: //=================== ! 837: // stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without ! 838: // faking the inputs to stalker_dodge ! 839: //=================== ! 840: void stalker_dodge_jump (edict_t *self) ! 841: { ! 842: self->monsterinfo.currentmove = &stalker_move_jump_straightup; ! 843: } ! 844: ! 845: mframe_t stalker_frames_dodge_run [] = ! 846: { ! 847: ai_run, 13, NULL, ! 848: ai_run, 17, NULL, ! 849: ai_run, 21, NULL, ! 850: ai_run, 18, monster_done_dodge ! 851: }; ! 852: mmove_t stalker_move_dodge_run = {FRAME_run01, FRAME_run04, stalker_frames_dodge_run, NULL}; ! 853: ! 854: void stalker_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) ! 855: { ! 856: if (!self->groundentity || self->health <= 0) ! 857: return; ! 858: ! 859: if (!self->enemy) ! 860: { ! 861: self->enemy = attacker; ! 862: FoundTarget(self); ! 863: return; ! 864: } ! 865: ! 866: // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was ! 867: // seeing numbers like 13 and 14) ! 868: if ((eta < 0.1) || (eta > 5)) ! 869: return; ! 870: ! 871: // this will override the foundtarget call of stalker_run ! 872: stalker_dodge_jump(self); ! 873: } ! 874: ! 875: ! 876: // ****************** ! 877: // Jump onto / off of things ! 878: // ****************** ! 879: ! 880: //=================== ! 881: //=================== ! 882: void stalker_jump_down (edict_t *self) ! 883: { ! 884: vec3_t forward,up; ! 885: ! 886: AngleVectors (self->s.angles, forward, NULL, up); ! 887: VectorMA(self->velocity, 100, forward, self->velocity); ! 888: VectorMA(self->velocity, 300, up, self->velocity); ! 889: } ! 890: ! 891: //=================== ! 892: //=================== ! 893: void stalker_jump_up (edict_t *self) ! 894: { ! 895: vec3_t forward,up; ! 896: ! 897: AngleVectors (self->s.angles, forward, NULL, up); ! 898: VectorMA(self->velocity, 200, forward, self->velocity); ! 899: VectorMA(self->velocity, 450, up, self->velocity); ! 900: } ! 901: ! 902: //=================== ! 903: //=================== ! 904: void stalker_jump_wait_land (edict_t *self) ! 905: { ! 906: if ((random() < (0.3 + (0.1*(float)(skill->value)))) && (level.time >= self->monsterinfo.attack_finished)) ! 907: { ! 908: self->monsterinfo.attack_finished = level.time + 0.3; ! 909: stalker_shoot_attack(self); ! 910: } ! 911: ! 912: if(self->groundentity == NULL) ! 913: { ! 914: self->gravity = 1.3; ! 915: self->monsterinfo.nextframe = self->s.frame; ! 916: } ! 917: else ! 918: { ! 919: self->gravity = 1; ! 920: self->monsterinfo.nextframe = self->s.frame + 1; ! 921: } ! 922: } ! 923: ! 924: mframe_t stalker_frames_jump_up [] = ! 925: { ! 926: ai_move, -8, NULL, ! 927: ai_move, -8, NULL, ! 928: ai_move, -8, NULL, ! 929: ai_move, -8, NULL, ! 930: ! 931: ai_move, 0, stalker_jump_up, ! 932: ai_move, 0, stalker_jump_wait_land, ! 933: ai_move, 0, NULL ! 934: }; ! 935: mmove_t stalker_move_jump_up = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run }; ! 936: ! 937: mframe_t stalker_frames_jump_down [] = ! 938: { ! 939: ai_move, 0, NULL, ! 940: ai_move, 0, NULL, ! 941: ai_move, 0, NULL, ! 942: ai_move, 0, NULL, ! 943: ! 944: ai_move, 0, stalker_jump_down, ! 945: ai_move, 0, stalker_jump_wait_land, ! 946: ai_move, 0, NULL ! 947: }; ! 948: mmove_t stalker_move_jump_down = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run }; ! 949: ! 950: //============ ! 951: // stalker_jump - this is only used for jumping onto or off of things. for dodge jumping, ! 952: // use stalker_dodge_jump ! 953: //============ ! 954: void stalker_jump (edict_t *self) ! 955: { ! 956: if(!self->enemy) ! 957: return; ! 958: ! 959: if(self->enemy->s.origin[2] >= self->s.origin[2]) ! 960: { ! 961: // gi.dprintf("stalker_jump_up\n"); ! 962: self->monsterinfo.currentmove = &stalker_move_jump_up; ! 963: } ! 964: else ! 965: { ! 966: // gi.dprintf("stalker_jump_down\n"); ! 967: self->monsterinfo.currentmove = &stalker_move_jump_down; ! 968: } ! 969: } ! 970: ! 971: ! 972: // ****************** ! 973: // Blocked ! 974: // ****************** ! 975: ! 976: qboolean stalker_blocked (edict_t *self, float dist) ! 977: { ! 978: qboolean onCeiling; ! 979: ! 980: // gi.dprintf("stalker_blocked\n"); ! 981: if(!has_valid_enemy(self)) ! 982: return false; ! 983: ! 984: onCeiling = false; ! 985: if(self->gravityVector[2] > 0) ! 986: onCeiling = true; ! 987: ! 988: if(!onCeiling) ! 989: { ! 990: if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) ! 991: { ! 992: // gi.dprintf("blocked: shooting\n"); ! 993: return true; ! 994: } ! 995: ! 996: if(visible (self, self->enemy)) ! 997: { ! 998: // gi.dprintf("blocked: jumping at player!\n"); ! 999: stalker_do_pounce(self, self->enemy->s.origin); ! 1000: return true; ! 1001: } ! 1002: ! 1003: if(blocked_checkjump (self, dist, 256, 68)) ! 1004: { ! 1005: // gi.dprintf("blocked: jumping up/down\n"); ! 1006: stalker_jump (self); ! 1007: return true; ! 1008: } ! 1009: ! 1010: if(blocked_checkplat (self, dist)) ! 1011: return true; ! 1012: } ! 1013: else ! 1014: { ! 1015: if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) ! 1016: { ! 1017: // gi.dprintf("blocked: shooting\n"); ! 1018: return true; ! 1019: } ! 1020: else if(stalker_ok_to_transition(self)) ! 1021: { ! 1022: self->gravityVector[2] = -1; ! 1023: self->s.angles[2] += 180.0; ! 1024: if(self->s.angles[2] > 360.0) ! 1025: self->s.angles[2] -= 360.0; ! 1026: self->groundentity = NULL; ! 1027: ! 1028: // gi.dprintf("falling off ceiling\n"); ! 1029: return true; ! 1030: } ! 1031: // else ! 1032: // gi.dprintf("Not OK to fall!\n"); ! 1033: } ! 1034: ! 1035: return false; ! 1036: } ! 1037: ! 1038: // ****************** ! 1039: // Death ! 1040: // ****************** ! 1041: ! 1042: void stalker_dead (edict_t *self) ! 1043: { ! 1044: VectorSet (self->mins, -28, -28, -18); ! 1045: VectorSet (self->maxs, 28, 28, -4); ! 1046: self->movetype = MOVETYPE_TOSS; ! 1047: self->svflags |= SVF_DEADMONSTER; ! 1048: self->nextthink = 0; ! 1049: gi.linkentity (self); ! 1050: // drawbbox(self); ! 1051: } ! 1052: ! 1053: mframe_t stalker_frames_death [] = ! 1054: { ! 1055: ai_move, 0, NULL, ! 1056: ai_move, -5, NULL, ! 1057: ai_move, -10, NULL, ! 1058: ai_move, -20, NULL, ! 1059: ! 1060: ai_move, -10, NULL, ! 1061: ai_move, -10, NULL, ! 1062: ai_move, -5, NULL, ! 1063: ai_move, -5, NULL, ! 1064: ! 1065: ai_move, 0, NULL ! 1066: }; ! 1067: mmove_t stalker_move_death = {FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead}; ! 1068: ! 1069: void stalker_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) ! 1070: { ! 1071: int n; ! 1072: ! 1073: // gi.dprintf("stalker_die: %d\n", self->health); ! 1074: ! 1075: // dude bit it, make him fall! ! 1076: self->movetype = MOVETYPE_TOSS; ! 1077: self->s.angles[2] = 0; ! 1078: VectorSet(self->gravityVector, 0, 0, -1); ! 1079: ! 1080: // check for gib ! 1081: if (self->health <= self->gib_health) ! 1082: { ! 1083: gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); ! 1084: for (n= 0; n < 2; n++) ! 1085: ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); ! 1086: for (n= 0; n < 4; n++) ! 1087: ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); ! 1088: ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); ! 1089: self->deadflag = DEAD_DEAD; ! 1090: return; ! 1091: } ! 1092: ! 1093: if (self->deadflag == DEAD_DEAD) ! 1094: return; ! 1095: ! 1096: // regular death ! 1097: gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); ! 1098: self->deadflag = DEAD_DEAD; ! 1099: self->takedamage = DAMAGE_YES; ! 1100: self->monsterinfo.currentmove = &stalker_move_death; ! 1101: } ! 1102: ! 1103: ! 1104: // ****************** ! 1105: // SPAWN ! 1106: // ****************** ! 1107: ! 1108: /*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof ! 1109: Spider Monster ! 1110: ! 1111: ONROOF - Monster starts sticking to the roof. ! 1112: */ ! 1113: void SP_monster_stalker (edict_t *self) ! 1114: { ! 1115: if (deathmatch->value) ! 1116: { ! 1117: G_FreeEdict (self); ! 1118: return; ! 1119: } ! 1120: ! 1121: // FIXME - I want my own sounds! ! 1122: sound_pain1 = gi.soundindex ("parasite/parpain1.wav"); ! 1123: sound_pain2 = gi.soundindex ("parasite/parpain2.wav"); ! 1124: sound_die = gi.soundindex ("parasite/pardeth1.wav"); ! 1125: sound_launch = gi.soundindex("parasite/paratck1.wav"); ! 1126: sound_sight = gi.soundindex("parasite/parsght1.wav"); ! 1127: sound_punch_hit = gi.soundindex ("infantry/melee2.wav"); ! 1128: ! 1129: // PMM - precache bolt2 ! 1130: gi.modelindex ("models/proj/laser2/tris.md2"); ! 1131: ! 1132: self->s.modelindex = gi.modelindex ("models/monsters/stalker/tris.md2"); ! 1133: VectorSet (self->mins, -28, -28, -18); ! 1134: VectorSet (self->maxs, 28, 28, 18); ! 1135: self->movetype = MOVETYPE_STEP; ! 1136: self->solid = SOLID_BBOX; ! 1137: ! 1138: self->health = 250; ! 1139: self->gib_health = -50; // FIXME ! 1140: self->mass = 250; ! 1141: ! 1142: self->pain = stalker_pain; ! 1143: self->die = stalker_die; ! 1144: ! 1145: self->monsterinfo.stand = stalker_stand; ! 1146: self->monsterinfo.walk = stalker_walk; ! 1147: self->monsterinfo.run = stalker_run; ! 1148: self->monsterinfo.attack = stalker_attack_ranged; ! 1149: self->monsterinfo.sight = stalker_sight; ! 1150: self->monsterinfo.idle = stalker_idle; ! 1151: self->monsterinfo.dodge = stalker_dodge; ! 1152: self->monsterinfo.blocked = stalker_blocked; ! 1153: self->monsterinfo.melee = stalker_attack_melee; ! 1154: ! 1155: gi.linkentity (self); ! 1156: ! 1157: self->monsterinfo.currentmove = &stalker_move_stand; ! 1158: self->monsterinfo.scale = MODEL_SCALE; ! 1159: ! 1160: self->monsterinfo.aiflags |= AI_WALK_WALLS; ! 1161: ! 1162: if(self->spawnflags & 8) ! 1163: { ! 1164: self->s.angles[2] = 180; ! 1165: self->gravityVector[2] = 1; ! 1166: } ! 1167: ! 1168: walkmonster_start (self); ! 1169: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.