|
|
1.1 ! root 1: /* ! 2: ============================================================================== ! 3: ! 4: TURRET ! 5: ! 6: ============================================================================== ! 7: */ ! 8: ! 9: #include "g_local.h" ! 10: #include "m_turret.h" ! 11: ! 12: #define SPAWN_BLASTER 0x0008 ! 13: #define SPAWN_MACHINEGUN 0x0010 ! 14: #define SPAWN_ROCKET 0x0020 ! 15: #define SPAWN_HEATBEAM 0x0040 ! 16: #define SPAWN_WEAPONCHOICE 0x0078 ! 17: #define SPAWN_INSTANT_WEAPON 0x0050 ! 18: #define SPAWN_WALL_UNIT 0x0080 ! 19: ! 20: extern qboolean FindTarget (edict_t *self); ! 21: ! 22: void turret_run (edict_t *self); ! 23: void TurretAim (edict_t *self); ! 24: void turret_sight (edict_t *self, edict_t *other); ! 25: void turret_search (edict_t *self); ! 26: void turret_stand (edict_t *self); ! 27: void turret_wake (edict_t *self); ! 28: void turret_ready_gun (edict_t *self); ! 29: void turret_run (edict_t *self); ! 30: ! 31: void turret_attack (edict_t *self); ! 32: mmove_t turret_move_fire; ! 33: mmove_t turret_move_fire_blind; ! 34: ! 35: ! 36: void TurretAim(edict_t *self) ! 37: { ! 38: vec3_t end, dir; ! 39: vec3_t ang; ! 40: float move, idealPitch, idealYaw, current, speed; ! 41: int orientation; ! 42: ! 43: // gi.dprintf("turret_aim: %d %d\n", self->s.frame, self->monsterinfo.nextframe); ! 44: ! 45: if(!self->enemy || self->enemy == world) ! 46: { ! 47: if(!FindTarget (self)) ! 48: return; ! 49: } ! 50: ! 51: // if turret is still in inactive mode, ready the gun, but don't aim ! 52: if(self->s.frame < FRAME_active01) ! 53: { ! 54: turret_ready_gun(self); ! 55: return; ! 56: } ! 57: // if turret is still readying, don't aim. ! 58: if(self->s.frame < FRAME_run01) ! 59: return; ! 60: ! 61: // PMM - blindfire aiming here ! 62: if (self->monsterinfo.currentmove == &turret_move_fire_blind) ! 63: { ! 64: VectorCopy(self->monsterinfo.blind_fire_target, end); ! 65: if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) ! 66: end[2] += self->enemy->viewheight + 10; ! 67: else ! 68: end[2] += self->enemy->mins[2] - 10; ! 69: } ! 70: else ! 71: { ! 72: VectorCopy(self->enemy->s.origin, end); ! 73: if (self->enemy->client) ! 74: end[2] += self->enemy->viewheight; ! 75: } ! 76: ! 77: VectorSubtract(end, self->s.origin, dir); ! 78: vectoangles2(dir, ang); ! 79: ! 80: // ! 81: // Clamp first ! 82: // ! 83: ! 84: idealPitch = ang[PITCH]; ! 85: idealYaw = ang[YAW]; ! 86: ! 87: orientation = self->offset[1]; ! 88: switch(orientation) ! 89: { ! 90: case -1: // up pitch: 0 to 90 ! 91: if(idealPitch < -90) ! 92: idealPitch += 360; ! 93: if(idealPitch > -5) ! 94: idealPitch = -5; ! 95: break; ! 96: case -2: // down pitch: -180 to -360 ! 97: if(idealPitch > -90) ! 98: idealPitch -= 360; ! 99: if(idealPitch < -355) ! 100: idealPitch = -355; ! 101: else if(idealPitch > -185) ! 102: idealPitch = -185; ! 103: break; ! 104: case 0: // +X pitch: 0 to -90, -270 to -360 (or 0 to 90) ! 105: //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); ! 106: if(idealPitch < -180) ! 107: idealPitch += 360; ! 108: ! 109: if(idealPitch > 85) ! 110: idealPitch = 85; ! 111: else if(idealPitch < -85) ! 112: idealPitch = -85; ! 113: ! 114: //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); ! 115: // yaw: 270 to 360, 0 to 90 ! 116: // yaw: -90 to 90 (270-360 == -90-0) ! 117: if(idealYaw > 180) ! 118: idealYaw -= 360; ! 119: if(idealYaw > 85) ! 120: idealYaw = 85; ! 121: else if(idealYaw < -85) ! 122: idealYaw = -85; ! 123: //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw); ! 124: break; ! 125: case 90: // +Y pitch: 0 to 90, -270 to -360 (or 0 to 90) ! 126: if(idealPitch < -180) ! 127: idealPitch += 360; ! 128: ! 129: if(idealPitch > 85) ! 130: idealPitch = 85; ! 131: else if(idealPitch < -85) ! 132: idealPitch = -85; ! 133: ! 134: // yaw: 0 to 180 ! 135: if(idealYaw > 270) ! 136: idealYaw -= 360; ! 137: if(idealYaw > 175) idealYaw = 175; ! 138: else if(idealYaw < 5) idealYaw = 5; ! 139: ! 140: break; ! 141: case 180: // -X pitch: 0 to 90, -270 to -360 (or 0 to 90) ! 142: if(idealPitch < -180) ! 143: idealPitch += 360; ! 144: ! 145: if(idealPitch > 85) ! 146: idealPitch = 85; ! 147: else if(idealPitch < -85) ! 148: idealPitch = -85; ! 149: ! 150: // yaw: 90 to 270 ! 151: if(idealYaw > 265) idealYaw = 265; ! 152: else if(idealYaw < 95) idealYaw = 95; ! 153: ! 154: break; ! 155: case 270: // -Y pitch: 0 to 90, -270 to -360 (or 0 to 90) ! 156: if(idealPitch < -180) ! 157: idealPitch += 360; ! 158: ! 159: if(idealPitch > 85) ! 160: idealPitch = 85; ! 161: else if(idealPitch < -85) ! 162: idealPitch = -85; ! 163: ! 164: // yaw: 180 to 360 ! 165: if(idealYaw < 90) ! 166: idealYaw += 360; ! 167: if(idealYaw > 355) idealYaw = 355; ! 168: else if(idealYaw < 185) idealYaw = 185; ! 169: break; ! 170: } ! 171: ! 172: // ! 173: // adjust pitch ! 174: // ! 175: current = self->s.angles[PITCH]; ! 176: speed = self->yaw_speed; ! 177: ! 178: if(idealPitch != current) ! 179: { ! 180: move = idealPitch - current; ! 181: ! 182: while(move >= 360) ! 183: move -= 360; ! 184: if (move >= 90) ! 185: { ! 186: move = move - 360; ! 187: } ! 188: ! 189: while(move <= -360) ! 190: move += 360; ! 191: if (move <= -90) ! 192: { ! 193: move = move + 360; ! 194: } ! 195: ! 196: if (move > 0) ! 197: { ! 198: if (move > speed) ! 199: move = speed; ! 200: } ! 201: else ! 202: { ! 203: if (move < -speed) ! 204: move = -speed; ! 205: } ! 206: ! 207: self->s.angles[PITCH] = anglemod (current + move); ! 208: } ! 209: ! 210: // ! 211: // adjust yaw ! 212: // ! 213: current = self->s.angles[YAW]; ! 214: speed = self->yaw_speed; ! 215: ! 216: if(idealYaw != current) ! 217: { ! 218: move = idealYaw - current; ! 219: ! 220: // while(move >= 360) ! 221: // move -= 360; ! 222: if (move >= 180) ! 223: { ! 224: move = move - 360; ! 225: } ! 226: ! 227: // while(move <= -360) ! 228: // move += 360; ! 229: if (move <= -180) ! 230: { ! 231: move = move + 360; ! 232: } ! 233: ! 234: if (move > 0) ! 235: { ! 236: if (move > speed) ! 237: move = speed; ! 238: } ! 239: else ! 240: { ! 241: if (move < -speed) ! 242: move = -speed; ! 243: } ! 244: ! 245: self->s.angles[YAW] = anglemod (current + move); ! 246: } ! 247: ! 248: } ! 249: ! 250: void turret_sight (edict_t *self, edict_t *other) ! 251: { ! 252: } ! 253: ! 254: void turret_search (edict_t *self) ! 255: { ! 256: } ! 257: ! 258: mframe_t turret_frames_stand [] = ! 259: { ! 260: ai_stand, 0, NULL, ! 261: ai_stand, 0, NULL ! 262: }; ! 263: mmove_t turret_move_stand = {FRAME_stand01, FRAME_stand02, turret_frames_stand, NULL}; ! 264: ! 265: void turret_stand (edict_t *self) ! 266: { ! 267: //gi.dprintf("turret_stand\n"); ! 268: self->monsterinfo.currentmove = &turret_move_stand; ! 269: } ! 270: ! 271: mframe_t turret_frames_ready_gun [] = ! 272: { ! 273: ai_stand, 0, NULL, ! 274: ai_stand, 0, NULL, ! 275: ai_stand, 0, NULL, ! 276: ! 277: ai_stand, 0, NULL, ! 278: ai_stand, 0, NULL, ! 279: ai_stand, 0, NULL, ! 280: ! 281: ai_stand, 0, NULL ! 282: }; ! 283: mmove_t turret_move_ready_gun = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run }; ! 284: ! 285: void turret_ready_gun (edict_t *self) ! 286: { ! 287: self->monsterinfo.currentmove = &turret_move_ready_gun; ! 288: } ! 289: ! 290: mframe_t turret_frames_seek [] = ! 291: { ! 292: ai_walk, 0, TurretAim, ! 293: ai_walk, 0, TurretAim ! 294: }; ! 295: mmove_t turret_move_seek = {FRAME_run01, FRAME_run02, turret_frames_seek, NULL}; ! 296: ! 297: void turret_walk (edict_t *self) ! 298: { ! 299: if(self->s.frame < FRAME_run01) ! 300: turret_ready_gun(self); ! 301: else ! 302: self->monsterinfo.currentmove = &turret_move_seek; ! 303: } ! 304: ! 305: ! 306: mframe_t turret_frames_run [] = ! 307: { ! 308: ai_run, 0, TurretAim, ! 309: ai_run, 0, TurretAim ! 310: }; ! 311: mmove_t turret_move_run = {FRAME_run01, FRAME_run02, turret_frames_run, turret_run}; ! 312: ! 313: void turret_run (edict_t *self) ! 314: { ! 315: if(self->s.frame < FRAME_run01) ! 316: turret_ready_gun(self); ! 317: else ! 318: self->monsterinfo.currentmove = &turret_move_run; ! 319: } ! 320: ! 321: // ********************** ! 322: // ATTACK ! 323: // ********************** ! 324: ! 325: #define TURRET_BULLET_DAMAGE 4 ! 326: #define TURRET_HEAT_DAMAGE 4 ! 327: ! 328: void TurretFire (edict_t *self) ! 329: { ! 330: vec3_t forward; ! 331: vec3_t start, end, dir; ! 332: float time, dist, chance; ! 333: trace_t trace; ! 334: int rocketSpeed; ! 335: ! 336: TurretAim(self); ! 337: ! 338: if(!self->enemy || !self->enemy->inuse) ! 339: return; ! 340: ! 341: VectorSubtract(self->enemy->s.origin, self->s.origin, dir); ! 342: VectorNormalize(dir); ! 343: AngleVectors(self->s.angles, forward, NULL, NULL); ! 344: chance = DotProduct(dir, forward); ! 345: if(chance < 0.98) ! 346: { ! 347: // gi.dprintf("off-angle\n"); ! 348: return; ! 349: } ! 350: ! 351: chance = random(); ! 352: ! 353: // rockets fire less often than the others do. ! 354: if (self->spawnflags & SPAWN_ROCKET) ! 355: { ! 356: chance = chance * 3; ! 357: ! 358: rocketSpeed = 550; ! 359: if (skill->value == 2) ! 360: { ! 361: rocketSpeed += 200 * random(); ! 362: } ! 363: else if (skill->value == 3) ! 364: { ! 365: rocketSpeed += 100 + (200 * random()); ! 366: } ! 367: } ! 368: else if (self->spawnflags & SPAWN_BLASTER) ! 369: { ! 370: chance = chance * 2; ! 371: } ! 372: ! 373: // up the fire chance 20% per skill level. ! 374: chance = chance - (0.2 * skill->value); ! 375: ! 376: if(/*chance < 0.5 && */visible(self, self->enemy)) ! 377: { ! 378: VectorCopy(self->s.origin, start); ! 379: VectorCopy(self->enemy->s.origin, end); ! 380: ! 381: // aim for the head. ! 382: if ((self->enemy) && (self->enemy->client)) ! 383: end[2]+=self->enemy->viewheight; ! 384: else ! 385: end[2]+=22; ! 386: ! 387: VectorSubtract(end, start, dir); ! 388: dist = VectorLength(dir); ! 389: ! 390: // check for predictive fire if distance less than 512 ! 391: if(!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512)) ! 392: { ! 393: chance = random(); ! 394: // ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80% ! 395: chance += (3 - skill->value) * 0.1; ! 396: if(chance < 0.8) ! 397: { ! 398: // lead the target.... ! 399: time = dist / 1000; ! 400: VectorMA(end, time, self->enemy->velocity, end); ! 401: VectorSubtract(end, start, dir); ! 402: } ! 403: } ! 404: ! 405: VectorNormalize(dir); ! 406: trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); ! 407: if(trace.ent == self->enemy || trace.ent == world) ! 408: { ! 409: if(self->spawnflags & SPAWN_BLASTER) ! 410: monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER); ! 411: else if(self->spawnflags & SPAWN_MACHINEGUN) ! 412: monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN); ! 413: else if(self->spawnflags & SPAWN_ROCKET) ! 414: { ! 415: if(dist * trace.fraction > 72) ! 416: monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); ! 417: } ! 418: } ! 419: } ! 420: } ! 421: ! 422: // PMM ! 423: void TurretFireBlind (edict_t *self) ! 424: { ! 425: vec3_t forward; ! 426: vec3_t start, end, dir; ! 427: float dist, chance; ! 428: int rocketSpeed; ! 429: ! 430: TurretAim(self); ! 431: ! 432: if(!self->enemy || !self->enemy->inuse) ! 433: return; ! 434: ! 435: VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir); ! 436: VectorNormalize(dir); ! 437: AngleVectors(self->s.angles, forward, NULL, NULL); ! 438: chance = DotProduct(dir, forward); ! 439: if(chance < 0.98) ! 440: { ! 441: // gi.dprintf("off-angle\n"); ! 442: return; ! 443: } ! 444: ! 445: if (self->spawnflags & SPAWN_ROCKET) ! 446: { ! 447: rocketSpeed = 550; ! 448: if (skill->value == 2) ! 449: { ! 450: rocketSpeed += 200 * random(); ! 451: } ! 452: else if (skill->value == 3) ! 453: { ! 454: rocketSpeed += 100 + (200 * random()); ! 455: } ! 456: } ! 457: ! 458: VectorCopy(self->s.origin, start); ! 459: VectorCopy(self->monsterinfo.blind_fire_target, end); ! 460: ! 461: if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) ! 462: end[2] += self->enemy->viewheight + 10; ! 463: else ! 464: end[2] += self->enemy->mins[2] - 10; ! 465: ! 466: VectorSubtract(end, start, dir); ! 467: dist = VectorLength(dir); ! 468: ! 469: VectorNormalize(dir); ! 470: ! 471: if(self->spawnflags & SPAWN_BLASTER) ! 472: monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER); ! 473: else if(self->spawnflags & SPAWN_ROCKET) ! 474: monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); ! 475: } ! 476: //pmm ! 477: ! 478: mframe_t turret_frames_fire [] = ! 479: { ! 480: ai_run, 0, TurretFire, ! 481: ai_run, 0, TurretAim, ! 482: ai_run, 0, TurretAim, ! 483: ai_run, 0, TurretAim ! 484: }; ! 485: mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run}; ! 486: ! 487: //PMM ! 488: ! 489: // the blind frames need to aim first ! 490: mframe_t turret_frames_fire_blind [] = ! 491: { ! 492: ai_run, 0, TurretAim, ! 493: ai_run, 0, TurretAim, ! 494: ai_run, 0, TurretAim, ! 495: ai_run, 0, TurretFireBlind ! 496: }; ! 497: mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run}; ! 498: //pmm ! 499: ! 500: void turret_attack(edict_t *self) ! 501: { ! 502: float r, chance; ! 503: ! 504: if(self->s.frame < FRAME_run01) ! 505: turret_ready_gun(self); ! 506: // PMM ! 507: else if (self->monsterinfo.attack_state != AS_BLIND) ! 508: { ! 509: self->monsterinfo.nextframe = FRAME_pow01; ! 510: self->monsterinfo.currentmove = &turret_move_fire; ! 511: } ! 512: else ! 513: { ! 514: // setup shot probabilities ! 515: if (self->monsterinfo.blind_fire_delay < 1.0) ! 516: chance = 1.0; ! 517: else if (self->monsterinfo.blind_fire_delay < 7.5) ! 518: chance = 0.4; ! 519: else ! 520: chance = 0.1; ! 521: ! 522: r = random(); ! 523: ! 524: if ((g_showlogic) && (g_showlogic->value)) ! 525: gi.dprintf ("chance = %2.2f, roll = %2.2f\n", chance, r); ! 526: ! 527: // minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5 ! 528: self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random()*4.0; ! 529: // don't shoot at the origin ! 530: if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) ! 531: return; ! 532: ! 533: // don't shoot if the dice say not to ! 534: if (r > chance) ! 535: { ! 536: if ((g_showlogic) && (g_showlogic->value)) ! 537: gi.dprintf ("blindfire - NO SHOT\n"); ! 538: return; ! 539: } ! 540: ! 541: self->monsterinfo.nextframe = FRAME_pow01; ! 542: self->monsterinfo.currentmove = &turret_move_fire_blind; ! 543: } ! 544: // pmm ! 545: } ! 546: ! 547: // ********************** ! 548: // PAIN ! 549: // ********************** ! 550: ! 551: void turret_pain (edict_t *self, edict_t *other, float kick, int damage) ! 552: { ! 553: return; ! 554: } ! 555: ! 556: // ********************** ! 557: // DEATH ! 558: // ********************** ! 559: ! 560: void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) ! 561: { ! 562: vec3_t forward; ! 563: vec3_t start; ! 564: edict_t *base; ! 565: ! 566: gi.WriteByte (svc_temp_entity); ! 567: gi.WriteByte (TE_PLAIN_EXPLOSION); ! 568: gi.WritePosition (self->s.origin); ! 569: gi.multicast (self->s.origin, MULTICAST_PHS); ! 570: ! 571: AngleVectors(self->s.angles, forward, NULL, NULL); ! 572: VectorMA(self->s.origin, 1, forward, start); ! 573: ! 574: ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start); ! 575: ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start); ! 576: ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start); ! 577: ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start); ! 578: ! 579: if(self->teamchain) ! 580: { ! 581: base = self->teamchain; ! 582: base->solid = SOLID_BBOX; ! 583: base->takedamage = DAMAGE_NO; ! 584: base->movetype = MOVETYPE_NONE; ! 585: gi.linkentity (base); ! 586: } ! 587: ! 588: if(self->target) ! 589: { ! 590: if(self->enemy && self->enemy->inuse) ! 591: G_UseTargets (self, self->enemy); ! 592: else ! 593: G_UseTargets (self, self); ! 594: } ! 595: ! 596: G_FreeEdict(self); ! 597: } ! 598: ! 599: // ********************** ! 600: // WALL SPAWN ! 601: // ********************** ! 602: ! 603: void turret_wall_spawn (edict_t *turret) ! 604: { ! 605: edict_t *ent; ! 606: int angle; ! 607: ! 608: ent = G_Spawn(); ! 609: VectorCopy(turret->s.origin, ent->s.origin); ! 610: VectorCopy(turret->s.angles, ent->s.angles); ! 611: ! 612: angle = ent->s.angles[1]; ! 613: if(ent->s.angles[0] == 90) ! 614: angle = -1; ! 615: else if(ent->s.angles[0] == 270) ! 616: angle = -2; ! 617: switch (angle) ! 618: { ! 619: case -1: ! 620: VectorSet(ent->mins, -16, -16, -8); ! 621: VectorSet(ent->maxs, 16, 16, 0); ! 622: break; ! 623: case -2: ! 624: VectorSet(ent->mins, -16, -16, 0); ! 625: VectorSet(ent->maxs, 16, 16, 8); ! 626: break; ! 627: case 0: ! 628: VectorSet(ent->mins, -8, -16, -16); ! 629: VectorSet(ent->maxs, 0, 16, 16); ! 630: break; ! 631: case 90: ! 632: VectorSet(ent->mins, -16, -8, -16); ! 633: VectorSet(ent->maxs, 16, 0, 16); ! 634: break; ! 635: case 180: ! 636: VectorSet(ent->mins, 0, -16, -16); ! 637: VectorSet(ent->maxs, 8, 16, 16); ! 638: break; ! 639: case 270: ! 640: VectorSet(ent->mins, -16, 0, -16); ! 641: VectorSet(ent->maxs, 16, 8, 16); ! 642: break; ! 643: ! 644: } ! 645: ! 646: ent->movetype = MOVETYPE_PUSH; ! 647: ent->solid = SOLID_NOT; ! 648: ! 649: ent->teammaster = turret; ! 650: turret->teammaster = turret; ! 651: turret->teamchain = ent; ! 652: ent->teamchain = NULL; ! 653: ent->flags |= FL_TEAMSLAVE; ! 654: ent->owner = turret; ! 655: ! 656: ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2"); ! 657: ! 658: gi.linkentity (ent); ! 659: } ! 660: ! 661: void turret_wake (edict_t *self) ! 662: { ! 663: // the wall section will call this when it stops moving. ! 664: // just return without doing anything. easiest way to have a null function. ! 665: if(self->flags & FL_TEAMSLAVE) ! 666: { ! 667: return; ! 668: } ! 669: ! 670: self->monsterinfo.stand = turret_stand; ! 671: self->monsterinfo.walk = turret_walk; ! 672: self->monsterinfo.run = turret_run; ! 673: self->monsterinfo.dodge = NULL; ! 674: self->monsterinfo.attack = turret_attack; ! 675: self->monsterinfo.melee = NULL; ! 676: self->monsterinfo.sight = turret_sight; ! 677: self->monsterinfo.search = turret_search; ! 678: self->monsterinfo.currentmove = &turret_move_stand; ! 679: self->takedamage = DAMAGE_AIM; ! 680: self->movetype = MOVETYPE_NONE; ! 681: // prevent counting twice ! 682: self->monsterinfo.aiflags |= AI_DO_NOT_COUNT; ! 683: ! 684: gi.linkentity (self); ! 685: ! 686: stationarymonster_start (self); ! 687: ! 688: if(self->spawnflags & SPAWN_MACHINEGUN) ! 689: { ! 690: self->s.skinnum = 1; ! 691: } ! 692: else if(self->spawnflags & SPAWN_ROCKET) ! 693: { ! 694: self->s.skinnum = 2; ! 695: } ! 696: ! 697: // but we do want the death to count ! 698: self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT; ! 699: } ! 700: ! 701: extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)); ! 702: ! 703: void turret_activate (edict_t *self, edict_t *other, edict_t *activator) ! 704: { ! 705: vec3_t endpos; ! 706: vec3_t forward; ! 707: edict_t *base; ! 708: ! 709: self->movetype = MOVETYPE_PUSH; ! 710: if(!self->speed) ! 711: self->speed = 15; ! 712: self->moveinfo.speed = self->speed; ! 713: self->moveinfo.accel = self->speed; ! 714: self->moveinfo.decel = self->speed; ! 715: ! 716: if(self->s.angles[0] == 270) ! 717: { ! 718: VectorSet (forward, 0,0,1); ! 719: } ! 720: else if(self->s.angles[0] == 90) ! 721: { ! 722: VectorSet (forward, 0,0,-1); ! 723: } ! 724: else if(self->s.angles[1] == 0) ! 725: { ! 726: VectorSet (forward, 1,0,0); ! 727: } ! 728: else if(self->s.angles[1] == 90) ! 729: { ! 730: VectorSet (forward, 0,1,0); ! 731: } ! 732: else if(self->s.angles[1] == 180) ! 733: { ! 734: VectorSet (forward, -1,0,0); ! 735: } ! 736: else if(self->s.angles[1] == 270) ! 737: { ! 738: VectorSet (forward, 0,-1,0); ! 739: } ! 740: ! 741: // start up the turret ! 742: VectorMA(self->s.origin, 32, forward, endpos); ! 743: Move_Calc(self, endpos, turret_wake); ! 744: ! 745: base = self->teamchain; ! 746: if(base) ! 747: { ! 748: base->movetype = MOVETYPE_PUSH; ! 749: base->speed = self->speed; ! 750: base->moveinfo.speed = base->speed; ! 751: base->moveinfo.accel = base->speed; ! 752: base->moveinfo.decel = base->speed; ! 753: ! 754: // start up the wall section ! 755: VectorMA(self->teamchain->s.origin, 32, forward, endpos); ! 756: Move_Calc(self->teamchain, endpos, turret_wake); ! 757: } ! 758: ! 759: gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0); ! 760: } ! 761: ! 762: // PMM ! 763: // checkattack .. ignore range, just attack if available ! 764: qboolean turret_checkattack (edict_t *self) ! 765: { ! 766: vec3_t spot1, spot2; ! 767: float chance, nexttime; ! 768: trace_t tr; ! 769: int enemy_range; ! 770: ! 771: if (self->enemy->health > 0) ! 772: { ! 773: // see if any entities are in the way of the shot ! 774: VectorCopy (self->s.origin, spot1); ! 775: spot1[2] += self->viewheight; ! 776: VectorCopy (self->enemy->s.origin, spot2); ! 777: spot2[2] += self->enemy->viewheight; ! 778: ! 779: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); ! 780: ! 781: // do we have a clear shot? ! 782: if (tr.ent != self->enemy) ! 783: { ! 784: // PGM - we want them to go ahead and shoot at info_notnulls if they can. ! 785: if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM ! 786: { ! 787: // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available ! 788: if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) ! 789: { ! 790: if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0)) ! 791: { ! 792: if (level.time < self->monsterinfo.attack_finished) ! 793: { ! 794: return false; ! 795: } ! 796: if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) ! 797: { ! 798: // wait for our time ! 799: return false; ! 800: } ! 801: else ! 802: { ! 803: // make sure we're not going to shoot something we don't want to shoot ! 804: tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER); ! 805: if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy))) ! 806: { ! 807: if ((g_showlogic) && (g_showlogic->value)) ! 808: gi.dprintf ("blindfire blocked\n"); ! 809: return false; ! 810: } ! 811: ! 812: self->monsterinfo.attack_state = AS_BLIND; ! 813: self->monsterinfo.attack_finished = level.time + 0.5 + 2*random(); ! 814: return true; ! 815: } ! 816: } ! 817: } ! 818: // pmm ! 819: return false; ! 820: } ! 821: } ! 822: } ! 823: ! 824: if (level.time < self->monsterinfo.attack_finished) ! 825: return false; ! 826: ! 827: enemy_range = range(self, self->enemy); ! 828: ! 829: if (enemy_range == RANGE_MELEE) ! 830: { ! 831: // don't always melee in easy mode ! 832: if (skill->value == 0 && (rand()&3) ) ! 833: return false; ! 834: self->monsterinfo.attack_state = AS_MISSILE; ! 835: return true; ! 836: } ! 837: ! 838: if (self->spawnflags & SPAWN_ROCKET) ! 839: { ! 840: chance = 0.10; ! 841: nexttime = (1.8 - (0.2 * skill->value)); ! 842: } ! 843: else if(self->spawnflags & SPAWN_BLASTER) ! 844: { ! 845: chance = 0.35; ! 846: nexttime = (1.2 - (0.2 * skill->value)); ! 847: } ! 848: else ! 849: { ! 850: chance = 0.50; ! 851: nexttime = (0.8 - (0.1 * skill->value)); ! 852: } ! 853: ! 854: if (skill->value == 0) ! 855: chance *= 0.5; ! 856: else if (skill->value > 1) ! 857: chance *= 2; ! 858: ! 859: // PGM - go ahead and shoot every time if it's a info_notnull ! 860: // PMM - added visibility check ! 861: if ( ((random () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT)) ! 862: { ! 863: self->monsterinfo.attack_state = AS_MISSILE; ! 864: // self->monsterinfo.attack_finished = level.time + 0.3 + 2*random(); ! 865: self->monsterinfo.attack_finished = level.time + nexttime; ! 866: return true; ! 867: } ! 868: ! 869: self->monsterinfo.attack_state = AS_STRAIGHT; ! 870: ! 871: return false; ! 872: } ! 873: ! 874: ! 875: // ********************** ! 876: // SPAWN ! 877: // ********************** ! 878: ! 879: /*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit ! 880: ! 881: The automated defense turret that mounts on walls. ! 882: Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam. ! 883: Default weapon is blaster. ! 884: When activated, wall units move 32 units in the direction they're facing. ! 885: */ ! 886: void SP_monster_turret (edict_t *self) ! 887: { ! 888: int angle; ! 889: ! 890: if (deathmatch->value) ! 891: { ! 892: G_FreeEdict (self); ! 893: return; ! 894: } ! 895: ! 896: // VERSIONING ! 897: if (g_showlogic && g_showlogic->value) ! 898: gi.dprintf ("%s\n", ROGUE_VERSION_STRING); ! 899: ! 900: self->plat2flags = ROGUE_VERSION_ID; ! 901: // versions ! 902: ! 903: // pre-caches ! 904: gi.soundindex ("world/dr_short.wav"); ! 905: gi.modelindex ("models/objects/debris1/tris.md2"); ! 906: ! 907: self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2"); ! 908: ! 909: VectorSet (self->mins, -12, -12, -12); ! 910: VectorSet (self->maxs, 12, 12, 12); ! 911: self->movetype = MOVETYPE_NONE; ! 912: self->solid = SOLID_BBOX; ! 913: ! 914: self->health = 240; ! 915: self->gib_health = -100; ! 916: self->mass = 250; ! 917: self->yaw_speed = 45; ! 918: ! 919: self->flags |= FL_MECHANICAL; ! 920: ! 921: self->pain = turret_pain; ! 922: self->die = turret_die; ! 923: ! 924: // map designer didn't specify weapon type. set it now. ! 925: if(!(self->spawnflags & SPAWN_WEAPONCHOICE)) ! 926: { ! 927: self->spawnflags |= SPAWN_BLASTER; ! 928: // self->spawnflags |= SPAWN_MACHINEGUN; ! 929: // self->spawnflags |= SPAWN_ROCKET; ! 930: // self->spawnflags |= SPAWN_HEATBEAM; ! 931: } ! 932: ! 933: if(self->spawnflags & SPAWN_HEATBEAM) ! 934: { ! 935: self->spawnflags &= ~SPAWN_HEATBEAM; ! 936: self->spawnflags |= SPAWN_BLASTER; ! 937: } ! 938: ! 939: if(!(self->spawnflags & SPAWN_WALL_UNIT)) ! 940: { ! 941: self->monsterinfo.stand = turret_stand; ! 942: self->monsterinfo.walk = turret_walk; ! 943: self->monsterinfo.run = turret_run; ! 944: self->monsterinfo.dodge = NULL; ! 945: self->monsterinfo.attack = turret_attack; ! 946: self->monsterinfo.melee = NULL; ! 947: self->monsterinfo.sight = turret_sight; ! 948: self->monsterinfo.search = turret_search; ! 949: self->monsterinfo.currentmove = &turret_move_stand; ! 950: } ! 951: ! 952: // PMM ! 953: self->monsterinfo.checkattack = turret_checkattack; ! 954: ! 955: self->monsterinfo.aiflags |= AI_MANUAL_STEERING; ! 956: self->monsterinfo.scale = MODEL_SCALE; ! 957: self->gravity = 0; ! 958: ! 959: VectorCopy(self->s.angles, self->offset); ! 960: angle=(int)self->s.angles[1]; ! 961: switch(angle) ! 962: { ! 963: case -1: // up ! 964: self->s.angles[0] = 270; ! 965: self->s.angles[1] = 0; ! 966: self->s.origin[2] += 2; ! 967: break; ! 968: case -2: // down ! 969: self->s.angles[0] = 90; ! 970: self->s.angles[1] = 0; ! 971: self->s.origin[2] -= 2; ! 972: break; ! 973: case 0: ! 974: self->s.origin[0] += 2; ! 975: break; ! 976: case 90: ! 977: self->s.origin[1] += 2; ! 978: break; ! 979: case 180: ! 980: self->s.origin[0] -= 2; ! 981: break; ! 982: case 270: ! 983: self->s.origin[1] -= 2; ! 984: break; ! 985: default: ! 986: break; ! 987: } ! 988: ! 989: gi.linkentity (self); ! 990: ! 991: ! 992: if(self->spawnflags & SPAWN_WALL_UNIT) ! 993: { ! 994: if(!self->targetname) ! 995: { ! 996: gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin)); ! 997: G_FreeEdict(self); ! 998: return; ! 999: } ! 1000: ! 1001: self->takedamage = DAMAGE_NO; ! 1002: self->use = turret_activate; ! 1003: turret_wall_spawn(self); ! 1004: if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))) ! 1005: level.total_monsters++; ! 1006: ! 1007: } ! 1008: else ! 1009: { ! 1010: stationarymonster_start (self); ! 1011: } ! 1012: ! 1013: if(self->spawnflags & SPAWN_MACHINEGUN) ! 1014: { ! 1015: gi.soundindex ("infantry/infatck1.wav"); ! 1016: self->s.skinnum = 1; ! 1017: } ! 1018: else if(self->spawnflags & SPAWN_ROCKET) ! 1019: { ! 1020: gi.soundindex ("weapons/rockfly.wav"); ! 1021: gi.modelindex ("models/objects/rocket/tris.md2"); ! 1022: gi.soundindex ("chick/chkatck2.wav"); ! 1023: self->s.skinnum = 2; ! 1024: } ! 1025: else ! 1026: { ! 1027: if (!(self->spawnflags & SPAWN_BLASTER)) ! 1028: { ! 1029: if ((g_showlogic) && (g_showlogic->value)) ! 1030: gi.dprintf ("Unknown spawn flags for turret. Defaulting to blaster.\n"); ! 1031: self->spawnflags |= SPAWN_BLASTER; ! 1032: } ! 1033: gi.modelindex ("models/objects/laser/tris.md2"); ! 1034: gi.soundindex ("misc/lasfly.wav"); ! 1035: gi.soundindex ("soldier/solatck2.wav"); ! 1036: } ! 1037: ! 1038: // PMM - turrets don't get mad at monsters, and visa versa ! 1039: self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; ! 1040: // PMM - blindfire ! 1041: if(self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER)) ! 1042: self->monsterinfo.blindfire = true; ! 1043: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.