|
|
1.1 ! root 1: // g_ai.c ! 2: ! 3: #include "g_local.h" ! 4: ! 5: qboolean FindTarget (edict_t *self); ! 6: extern cvar_t *maxclients; ! 7: ! 8: qboolean ai_checkattack (edict_t *self, float dist); ! 9: ! 10: qboolean enemy_vis; ! 11: qboolean enemy_infront; ! 12: int enemy_range; ! 13: float enemy_yaw; ! 14: ! 15: // ROGUE STUFF ! 16: #define SLIDING_TROOPS 1 ! 17: #define MAX_SIDESTEP 8.0 ! 18: // ! 19: ! 20: //============================================================================ ! 21: ! 22: ! 23: /* ! 24: ================= ! 25: AI_SetSightClient ! 26: ! 27: Called once each frame to set level.sight_client to the ! 28: player to be checked for in findtarget. ! 29: ! 30: If all clients are either dead or in notarget, sight_client ! 31: will be null. ! 32: ! 33: In coop games, sight_client will cycle between the clients. ! 34: ================= ! 35: */ ! 36: void AI_SetSightClient (void) ! 37: { ! 38: edict_t *ent; ! 39: int start, check; ! 40: ! 41: if (level.sight_client == NULL) ! 42: start = 1; ! 43: else ! 44: start = level.sight_client - g_edicts; ! 45: ! 46: check = start; ! 47: while (1) ! 48: { ! 49: check++; ! 50: if (check > game.maxclients) ! 51: check = 1; ! 52: ent = &g_edicts[check]; ! 53: if (ent->inuse ! 54: && ent->health > 0 ! 55: && !(ent->flags & (FL_NOTARGET|FL_DISGUISED)) ) ! 56: { ! 57: level.sight_client = ent; ! 58: return; // got one ! 59: } ! 60: if (check == start) ! 61: { ! 62: level.sight_client = NULL; ! 63: return; // nobody to see ! 64: } ! 65: } ! 66: } ! 67: ! 68: //============================================================================ ! 69: ! 70: /* ! 71: ============= ! 72: ai_move ! 73: ! 74: Move the specified distance at current facing. ! 75: This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward ! 76: ============== ! 77: */ ! 78: void ai_move (edict_t *self, float dist) ! 79: { ! 80: M_walkmove (self, self->s.angles[YAW], dist); ! 81: } ! 82: ! 83: ! 84: /* ! 85: ============= ! 86: ai_stand ! 87: ! 88: Used for standing around and looking for players ! 89: Distance is for slight position adjustments needed by the animations ! 90: ============== ! 91: */ ! 92: void ai_stand (edict_t *self, float dist) ! 93: { ! 94: vec3_t v; ! 95: ! 96: if (dist) ! 97: M_walkmove (self, self->s.angles[YAW], dist); ! 98: ! 99: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 100: { ! 101: if (self->enemy) ! 102: { ! 103: VectorSubtract (self->enemy->s.origin, self->s.origin, v); ! 104: self->ideal_yaw = vectoyaw(v); ! 105: if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) ! 106: { ! 107: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 108: self->monsterinfo.run (self); ! 109: } ! 110: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 111: M_ChangeYaw (self); ! 112: // PMM ! 113: if ((self->enemy) && (self->enemy->inuse) && (visible(self, self->enemy))) ! 114: { ! 115: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; ! 116: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 117: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 118: self->monsterinfo.trail_time = level.time; ! 119: self->monsterinfo.blind_fire_delay = 0; ! 120: } ! 121: // pmm ! 122: ai_checkattack (self, 0); ! 123: } ! 124: else ! 125: FindTarget (self); ! 126: return; ! 127: } ! 128: ! 129: if (FindTarget (self)) ! 130: return; ! 131: ! 132: if (level.time > self->monsterinfo.pausetime) ! 133: { ! 134: self->monsterinfo.walk (self); ! 135: return; ! 136: } ! 137: ! 138: if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) ! 139: { ! 140: if (self->monsterinfo.idle_time) ! 141: { ! 142: self->monsterinfo.idle (self); ! 143: self->monsterinfo.idle_time = level.time + 15 + random() * 15; ! 144: } ! 145: else ! 146: { ! 147: self->monsterinfo.idle_time = level.time + random() * 15; ! 148: } ! 149: } ! 150: } ! 151: ! 152: ! 153: /* ! 154: ============= ! 155: ai_walk ! 156: ! 157: The monster is walking it's beat ! 158: ============= ! 159: */ ! 160: void ai_walk (edict_t *self, float dist) ! 161: { ! 162: M_MoveToGoal (self, dist); ! 163: ! 164: // check for noticing a player ! 165: if (FindTarget (self)) ! 166: return; ! 167: ! 168: if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) ! 169: { ! 170: if (self->monsterinfo.idle_time) ! 171: { ! 172: self->monsterinfo.search (self); ! 173: self->monsterinfo.idle_time = level.time + 15 + random() * 15; ! 174: } ! 175: else ! 176: { ! 177: self->monsterinfo.idle_time = level.time + random() * 15; ! 178: } ! 179: } ! 180: } ! 181: ! 182: ! 183: /* ! 184: ============= ! 185: ai_charge ! 186: ! 187: Turns towards target and advances ! 188: Use this call with a distance of 0 to replace ai_face ! 189: ============== ! 190: */ ! 191: void ai_charge (edict_t *self, float dist) ! 192: { ! 193: vec3_t v; ! 194: // PMM ! 195: float ofs; ! 196: // PMM ! 197: ! 198: // PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but ! 199: // don't set the ideal_yaw ! 200: ! 201: // This is put in there so monsters won't move towards the origin after killing ! 202: // a tesla. This could be problematic, so keep an eye on it. ! 203: if(!self->enemy || !self->enemy->inuse) //PGM ! 204: return; //PGM ! 205: ! 206: // PMM - save blindfire target ! 207: if (visible(self, self->enemy)) ! 208: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 209: // pmm ! 210: ! 211: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 212: { ! 213: VectorSubtract (self->enemy->s.origin, self->s.origin, v); ! 214: self->ideal_yaw = vectoyaw(v); ! 215: // gi.dprintf ("enemy = %s\n", vtos (self->enemy->s.origin)); ! 216: // gi.dprintf ("enemy: ideal yaw is %f\n", self->ideal_yaw); ! 217: } ! 218: // if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 219: M_ChangeYaw (self); ! 220: // PMM ! 221: // if (dist) ! 222: // M_walkmove (self, self->s.angles[YAW], dist); ! 223: ! 224: if (dist) ! 225: { ! 226: if (self->monsterinfo.aiflags & AI_CHARGING) ! 227: { ! 228: M_MoveToGoal (self, dist); ! 229: return; ! 230: } ! 231: // circle strafe support ! 232: if (self->monsterinfo.attack_state == AS_SLIDING) ! 233: { ! 234: if (self->monsterinfo.lefty) ! 235: ofs = 90; ! 236: else ! 237: ofs = -90; ! 238: ! 239: if (M_walkmove (self, self->ideal_yaw + ofs, dist)) ! 240: return; ! 241: ! 242: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; ! 243: M_walkmove (self, self->ideal_yaw - ofs, dist); ! 244: } ! 245: else ! 246: M_walkmove (self, self->s.angles[YAW], dist); ! 247: } ! 248: // PMM ! 249: } ! 250: ! 251: ! 252: /* ! 253: ============= ! 254: ai_turn ! 255: ! 256: don't move, but turn towards ideal_yaw ! 257: Distance is for slight position adjustments needed by the animations ! 258: ============= ! 259: */ ! 260: void ai_turn (edict_t *self, float dist) ! 261: { ! 262: if (dist) ! 263: M_walkmove (self, self->s.angles[YAW], dist); ! 264: ! 265: if (FindTarget (self)) ! 266: return; ! 267: ! 268: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 269: M_ChangeYaw (self); ! 270: } ! 271: ! 272: ! 273: /* ! 274: ! 275: .enemy ! 276: Will be world if not currently angry at anyone. ! 277: ! 278: .movetarget ! 279: The next path spot to walk toward. If .enemy, ignore .movetarget. ! 280: When an enemy is killed, the monster will try to return to it's path. ! 281: ! 282: .hunt_time ! 283: Set to time + something when the player is in sight, but movement straight for ! 284: him is blocked. This causes the monster to use wall following code for ! 285: movement direction instead of sighting on the player. ! 286: ! 287: .ideal_yaw ! 288: A yaw angle of the intended direction, which will be turned towards at up ! 289: to 45 deg / state. If the enemy is in view and hunt_time is not active, ! 290: this will be the exact line towards the enemy. ! 291: ! 292: .pausetime ! 293: A monster will leave it's stand state and head towards it's .movetarget when ! 294: time > .pausetime. ! 295: ! 296: walkmove(angle, speed) primitive is all or nothing ! 297: */ ! 298: ! 299: /* ! 300: ============= ! 301: range ! 302: ! 303: returns the range catagorization of an entity reletive to self ! 304: 0 melee range, will become hostile even if back is turned ! 305: 1 visibility and infront, or visibility and show hostile ! 306: 2 infront and show hostile ! 307: 3 only triggered by damage ! 308: ============= ! 309: */ ! 310: int range (edict_t *self, edict_t *other) ! 311: { ! 312: vec3_t v; ! 313: float len; ! 314: ! 315: VectorSubtract (self->s.origin, other->s.origin, v); ! 316: len = VectorLength (v); ! 317: if (len < MELEE_DISTANCE) ! 318: return RANGE_MELEE; ! 319: if (len < 500) ! 320: return RANGE_NEAR; ! 321: if (len < 1000) ! 322: return RANGE_MID; ! 323: return RANGE_FAR; ! 324: } ! 325: ! 326: /* ! 327: ============= ! 328: visible ! 329: ! 330: returns 1 if the entity is visible to self, even if not infront () ! 331: ============= ! 332: */ ! 333: qboolean visible (edict_t *self, edict_t *other) ! 334: { ! 335: vec3_t spot1; ! 336: vec3_t spot2; ! 337: trace_t trace; ! 338: ! 339: VectorCopy (self->s.origin, spot1); ! 340: spot1[2] += self->viewheight; ! 341: VectorCopy (other->s.origin, spot2); ! 342: spot2[2] += other->viewheight; ! 343: trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); ! 344: ! 345: if (trace.fraction == 1.0 || trace.ent == other) // PGM ! 346: return true; ! 347: return false; ! 348: } ! 349: ! 350: ! 351: /* ! 352: ============= ! 353: infront ! 354: ! 355: returns 1 if the entity is in front (in sight) of self ! 356: ============= ! 357: */ ! 358: qboolean infront (edict_t *self, edict_t *other) ! 359: { ! 360: vec3_t vec; ! 361: float dot; ! 362: vec3_t forward; ! 363: ! 364: AngleVectors (self->s.angles, forward, NULL, NULL); ! 365: VectorSubtract (other->s.origin, self->s.origin, vec); ! 366: VectorNormalize (vec); ! 367: dot = DotProduct (vec, forward); ! 368: ! 369: if (dot > 0.3) ! 370: return true; ! 371: return false; ! 372: } ! 373: ! 374: ! 375: //============================================================================ ! 376: ! 377: void HuntTarget (edict_t *self) ! 378: { ! 379: vec3_t vec; ! 380: ! 381: self->goalentity = self->enemy; ! 382: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 383: self->monsterinfo.stand (self); ! 384: else ! 385: self->monsterinfo.run (self); ! 386: VectorSubtract (self->enemy->s.origin, self->s.origin, vec); ! 387: self->ideal_yaw = vectoyaw(vec); ! 388: // wait a while before first attack ! 389: if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) ! 390: AttackFinished (self, 1); ! 391: } ! 392: ! 393: void FoundTarget (edict_t *self) ! 394: { ! 395: // let other monsters see this monster for a while ! 396: if (self->enemy->client) ! 397: { ! 398: if(self->enemy->flags & FL_DISGUISED) ! 399: { ! 400: // level.disguise_violator = self->enemy; ! 401: // level.disguise_violation_framenum = level.framenum + 5; ! 402: self->enemy->flags &= ~FL_DISGUISED; ! 403: } ! 404: ! 405: level.sight_entity = self; ! 406: level.sight_entity_framenum = level.framenum; ! 407: level.sight_entity->light_level = 128; ! 408: } ! 409: ! 410: self->show_hostile = level.time + 1; // wake up other monsters ! 411: ! 412: VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); ! 413: self->monsterinfo.trail_time = level.time; ! 414: // PMM ! 415: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 416: self->monsterinfo.blind_fire_delay = 0; ! 417: // PMM ! 418: ! 419: if (!self->combattarget) ! 420: { ! 421: HuntTarget (self); ! 422: return; ! 423: } ! 424: ! 425: self->goalentity = self->movetarget = G_PickTarget(self->combattarget); ! 426: if (!self->movetarget) ! 427: { ! 428: self->goalentity = self->movetarget = self->enemy; ! 429: HuntTarget (self); ! 430: gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); ! 431: return; ! 432: } ! 433: ! 434: // clear out our combattarget, these are a one shot deal ! 435: self->combattarget = NULL; ! 436: self->monsterinfo.aiflags |= AI_COMBAT_POINT; ! 437: ! 438: // clear the targetname, that point is ours! ! 439: self->movetarget->targetname = NULL; ! 440: self->monsterinfo.pausetime = 0; ! 441: ! 442: // run for it ! 443: self->monsterinfo.run (self); ! 444: } ! 445: ! 446: ! 447: /* ! 448: =========== ! 449: FindTarget ! 450: ! 451: Self is currently not attacking anything, so try to find a target ! 452: ! 453: Returns TRUE if an enemy was sighted ! 454: ! 455: When a player fires a missile, the point of impact becomes a fakeplayer so ! 456: that monsters that see the impact will respond as if they had seen the ! 457: player. ! 458: ! 459: To avoid spending too much time, only a single client (or fakeclient) is ! 460: checked each frame. This means multi player games will have slightly ! 461: slower noticing monsters. ! 462: ============ ! 463: */ ! 464: qboolean FindTarget (edict_t *self) ! 465: { ! 466: edict_t *client; ! 467: qboolean heardit; ! 468: int r; ! 469: ! 470: if (self->monsterinfo.aiflags & AI_GOOD_GUY) ! 471: { ! 472: if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) ! 473: { ! 474: if (strcmp(self->goalentity->classname, "target_actor") == 0) ! 475: return false; ! 476: } ! 477: ! 478: //FIXME look for monsters? ! 479: return false; ! 480: } ! 481: ! 482: // if we're going to a combat point, just proceed ! 483: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 484: return false; ! 485: ! 486: // if the first spawnflag bit is set, the monster will only wake up on ! 487: // really seeing the player, not another monster getting angry or hearing ! 488: // something ! 489: ! 490: // revised behavior so they will wake up if they "see" a player make a noise ! 491: // but not weapon impact/explosion noises ! 492: ! 493: heardit = false; ! 494: if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) ! 495: { ! 496: client = level.sight_entity; ! 497: if (client->enemy == self->enemy) ! 498: { ! 499: return false; ! 500: } ! 501: } ! 502: else if (level.disguise_violation_framenum > level.framenum) ! 503: { ! 504: client = level.disguise_violator; ! 505: } ! 506: else if (level.sound_entity_framenum >= (level.framenum - 1)) ! 507: { ! 508: client = level.sound_entity; ! 509: heardit = true; ! 510: } ! 511: else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) ! 512: { ! 513: client = level.sound2_entity; ! 514: heardit = true; ! 515: } ! 516: else ! 517: { ! 518: client = level.sight_client; ! 519: if (!client) ! 520: return false; // no clients to get mad at ! 521: } ! 522: ! 523: // if the entity went away, forget it ! 524: if (!client->inuse) ! 525: return false; ! 526: ! 527: if (client == self->enemy) ! 528: return true; // JDC false; ! 529: ! 530: if (client->client) ! 531: { ! 532: if (client->flags & FL_NOTARGET) ! 533: return false; ! 534: } ! 535: else if (client->svflags & SVF_MONSTER) ! 536: { ! 537: if (!client->enemy) ! 538: return false; ! 539: if (client->enemy->flags & FL_NOTARGET) ! 540: return false; ! 541: } ! 542: else if (heardit) ! 543: { ! 544: // pgm - a little more paranoia won't hurt.... ! 545: if ((client->owner) && (client->owner->flags & FL_NOTARGET)) ! 546: return false; ! 547: } ! 548: else ! 549: return false; ! 550: ! 551: if (!heardit) ! 552: { ! 553: r = range (self, client); ! 554: ! 555: if (r == RANGE_FAR) ! 556: return false; ! 557: ! 558: // this is where we would check invisibility ! 559: ! 560: // is client in an spot too dark to be seen? ! 561: if (client->light_level <= 5) ! 562: return false; ! 563: ! 564: if (!visible (self, client)) ! 565: { ! 566: return false; ! 567: } ! 568: ! 569: if (r == RANGE_NEAR) ! 570: { ! 571: if (client->show_hostile < level.time && !infront (self, client)) ! 572: { ! 573: return false; ! 574: } ! 575: } ! 576: else if (r == RANGE_MID) ! 577: { ! 578: if (!infront (self, client)) ! 579: { ! 580: return false; ! 581: } ! 582: } ! 583: ! 584: self->enemy = client; ! 585: ! 586: if (strcmp(self->enemy->classname, "player_noise") != 0) ! 587: { ! 588: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; ! 589: ! 590: if (!self->enemy->client) ! 591: { ! 592: self->enemy = self->enemy->enemy; ! 593: if (!self->enemy->client) ! 594: { ! 595: self->enemy = NULL; ! 596: return false; ! 597: } ! 598: } ! 599: } ! 600: } ! 601: else // heardit ! 602: { ! 603: vec3_t temp; ! 604: ! 605: if (self->spawnflags & 1) ! 606: { ! 607: if (!visible (self, client)) ! 608: return false; ! 609: } ! 610: else ! 611: { ! 612: if (!gi.inPHS(self->s.origin, client->s.origin)) ! 613: return false; ! 614: } ! 615: ! 616: VectorSubtract (client->s.origin, self->s.origin, temp); ! 617: ! 618: if (VectorLength(temp) > 1000) // too far to hear ! 619: { ! 620: return false; ! 621: } ! 622: ! 623: // check area portals - if they are different and not connected then we can't hear it ! 624: if (client->areanum != self->areanum) ! 625: if (!gi.AreasConnected(self->areanum, client->areanum)) ! 626: return false; ! 627: ! 628: self->ideal_yaw = vectoyaw(temp); ! 629: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 630: M_ChangeYaw (self); ! 631: ! 632: // hunt the sound for a bit; hopefully find the real player ! 633: self->monsterinfo.aiflags |= AI_SOUND_TARGET; ! 634: self->enemy = client; ! 635: } ! 636: ! 637: // ! 638: // got one ! 639: // ! 640: FoundTarget (self); ! 641: ! 642: if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) ! 643: self->monsterinfo.sight (self, self->enemy); ! 644: ! 645: return true; ! 646: } ! 647: ! 648: ! 649: //============================================================================= ! 650: ! 651: /* ! 652: ============ ! 653: FacingIdeal ! 654: ! 655: ============ ! 656: */ ! 657: qboolean FacingIdeal(edict_t *self) ! 658: { ! 659: float delta; ! 660: ! 661: delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); ! 662: if (delta > 45 && delta < 315) ! 663: return false; ! 664: return true; ! 665: } ! 666: ! 667: ! 668: //============================================================================= ! 669: ! 670: qboolean M_CheckAttack (edict_t *self) ! 671: { ! 672: vec3_t spot1, spot2; ! 673: float chance; ! 674: trace_t tr; ! 675: ! 676: if (self->enemy->health > 0) ! 677: { ! 678: // see if any entities are in the way of the shot ! 679: VectorCopy (self->s.origin, spot1); ! 680: spot1[2] += self->viewheight; ! 681: VectorCopy (self->enemy->s.origin, spot2); ! 682: spot2[2] += self->enemy->viewheight; ! 683: ! 684: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); ! 685: ! 686: // do we have a clear shot? ! 687: if (tr.ent != self->enemy) ! 688: { ! 689: // PGM - we want them to go ahead and shoot at info_notnulls if they can. ! 690: if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM ! 691: { ! 692: // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available ! 693: if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) ! 694: { ! 695: if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 20.0)) ! 696: { ! 697: if (level.time < self->monsterinfo.attack_finished) ! 698: { ! 699: return false; ! 700: } ! 701: if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) ! 702: { ! 703: // wait for our time ! 704: return false; ! 705: } ! 706: else ! 707: { ! 708: // gi.WriteByte (svc_temp_entity); ! 709: // gi.WriteByte (TE_DEBUGTRAIL); ! 710: // gi.WritePosition (spot1); ! 711: // gi.WritePosition (self->monsterinfo.blind_fire_target); ! 712: // gi.multicast (self->s.origin, MULTICAST_ALL); ! 713: // make sure we're not going to shoot a monster ! 714: tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER); ! 715: if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy))) ! 716: { ! 717: if ((g_showlogic) && (g_showlogic->value)) ! 718: gi.dprintf ("blindfire blocked\n"); ! 719: return false; ! 720: } ! 721: ! 722: self->monsterinfo.attack_state = AS_BLIND; ! 723: return true; ! 724: } ! 725: } ! 726: } ! 727: // pmm ! 728: return false; ! 729: } ! 730: } ! 731: } ! 732: ! 733: // melee attack ! 734: if (enemy_range == RANGE_MELEE) ! 735: { ! 736: // don't always melee in easy mode ! 737: if (skill->value == 0 && (rand()&3) ) ! 738: { ! 739: // PMM - fix for melee only monsters & strafing ! 740: self->monsterinfo.attack_state = AS_STRAIGHT; ! 741: return false; ! 742: } ! 743: if (self->monsterinfo.melee) ! 744: self->monsterinfo.attack_state = AS_MELEE; ! 745: else ! 746: self->monsterinfo.attack_state = AS_MISSILE; ! 747: return true; ! 748: } ! 749: ! 750: // missile attack ! 751: if (!self->monsterinfo.attack) ! 752: { ! 753: // PMM - fix for melee only monsters & strafing ! 754: self->monsterinfo.attack_state = AS_STRAIGHT; ! 755: return false; ! 756: } ! 757: ! 758: if (level.time < self->monsterinfo.attack_finished) ! 759: return false; ! 760: ! 761: if (enemy_range == RANGE_FAR) ! 762: return false; ! 763: ! 764: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 765: { ! 766: chance = 0.4; ! 767: } ! 768: else if (enemy_range == RANGE_MELEE) ! 769: { ! 770: chance = 0.2; ! 771: } ! 772: else if (enemy_range == RANGE_NEAR) ! 773: { ! 774: chance = 0.1; ! 775: } ! 776: else if (enemy_range == RANGE_MID) ! 777: { ! 778: chance = 0.02; ! 779: } ! 780: else ! 781: { ! 782: return false; ! 783: } ! 784: ! 785: if (skill->value == 0) ! 786: chance *= 0.5; ! 787: else if (skill->value >= 2) ! 788: chance *= 2; ! 789: ! 790: // PGM - go ahead and shoot every time if it's a info_notnull ! 791: if ((random () < chance) || (self->enemy->solid == SOLID_NOT)) ! 792: { ! 793: self->monsterinfo.attack_state = AS_MISSILE; ! 794: self->monsterinfo.attack_finished = level.time + 2*random(); ! 795: return true; ! 796: } ! 797: ! 798: // PMM -daedalus should strafe more .. this can be done here or in a customized ! 799: // check_attack code for the hover. ! 800: if (self->flags & FL_FLY) ! 801: { ! 802: // originally, just 0.3 ! 803: float strafe_chance; ! 804: if (!(strcmp(self->classname, "monster_daedalus"))) ! 805: strafe_chance = 0.8; ! 806: else ! 807: strafe_chance = 0.6; ! 808: ! 809: if (random() < strafe_chance) ! 810: self->monsterinfo.attack_state = AS_SLIDING; ! 811: else ! 812: self->monsterinfo.attack_state = AS_STRAIGHT; ! 813: } ! 814: // do we want the monsters strafing? ! 815: #ifdef SLIDING_TROOPS ! 816: else ! 817: { ! 818: if (random() < 0.4) ! 819: self->monsterinfo.attack_state = AS_SLIDING; ! 820: else ! 821: self->monsterinfo.attack_state = AS_STRAIGHT; ! 822: } ! 823: #endif ! 824: //-PMM ! 825: ! 826: return false; ! 827: } ! 828: ! 829: ! 830: /* ! 831: ============= ! 832: ai_run_melee ! 833: ! 834: Turn and close until within an angle to launch a melee attack ! 835: ============= ! 836: */ ! 837: void ai_run_melee(edict_t *self) ! 838: { ! 839: self->ideal_yaw = enemy_yaw; ! 840: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 841: M_ChangeYaw (self); ! 842: ! 843: if (FacingIdeal(self)) ! 844: { ! 845: self->monsterinfo.melee (self); ! 846: self->monsterinfo.attack_state = AS_STRAIGHT; ! 847: } ! 848: } ! 849: ! 850: ! 851: /* ! 852: ============= ! 853: ai_run_missile ! 854: ! 855: Turn in place until within an angle to launch a missile attack ! 856: ============= ! 857: */ ! 858: void ai_run_missile(edict_t *self) ! 859: { ! 860: self->ideal_yaw = enemy_yaw; ! 861: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 862: M_ChangeYaw (self); ! 863: ! 864: if (FacingIdeal(self)) ! 865: { ! 866: self->monsterinfo.attack (self); ! 867: // if (self->monsterinfo.attack_state == AS_MISSILE) ! 868: if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND)) ! 869: self->monsterinfo.attack_state = AS_STRAIGHT; ! 870: // else if (self->monsterinfo.attack_state != AS_SLIDING) ! 871: // gi.dprintf ("ai_run_missile: Unexpected attack state %d !\n", self->monsterinfo.attack_state); ! 872: } ! 873: }; ! 874: ! 875: ! 876: /* ! 877: ============= ! 878: ai_run_slide ! 879: ! 880: Strafe sideways, but stay at aproximately the same range ! 881: ============= ! 882: */ ! 883: void ai_run_slide(edict_t *self, float distance) ! 884: { ! 885: float ofs; ! 886: float angle; ! 887: ! 888: self->ideal_yaw = enemy_yaw; ! 889: ! 890: // if (self->flags & FL_FLY) ! 891: // angle = 90; ! 892: // else ! 893: // angle = 45; ! 894: ! 895: angle = 90; ! 896: ! 897: if (self->monsterinfo.lefty) ! 898: ofs = angle; ! 899: else ! 900: ofs = -angle; ! 901: // ! 902: // if (!(self->flags & FL_FLY)) ! 903: // { ! 904: // // non fliers should actually turn towards the direction their trying to run ! 905: // self->ideal_yaw += ofs; ! 906: // } ! 907: // ! 908: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 909: M_ChangeYaw (self); ! 910: ! 911: /* ! 912: if (!(self->flags & FL_FLY)) ! 913: { ! 914: if (M_walkmove (self, self->ideal_yaw + ofs, distance)) ! 915: return; ! 916: } ! 917: else ! 918: { ! 919: if (M_walkmove (self, self->ideal_yaw, distance)) ! 920: return; ! 921: } ! 922: */ ! 923: // PMM - clamp maximum sideways move for non flyers to make them look less jerky ! 924: if (!self->flags & FL_FLY) ! 925: distance = min (distance, MAX_SIDESTEP); ! 926: if (M_walkmove (self, self->ideal_yaw + ofs, distance)) ! 927: return; ! 928: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; ! 929: if (M_walkmove (self, self->ideal_yaw - ofs, distance)) ! 930: return; ! 931: // PMM - the move failed, so signal the caller (ai_run) to try going straight ! 932: self->monsterinfo.attack_state = AS_STRAIGHT; ! 933: /* ! 934: if (!(self->flags & FL_FLY)) ! 935: { ! 936: M_walkmove (self, self->ideal_yaw + ofs, distance); ! 937: } ! 938: else ! 939: { ! 940: M_walkmove (self, self->ideal_yaw, distance); ! 941: }*/ ! 942: } ! 943: ! 944: ! 945: /* ! 946: ============= ! 947: ai_checkattack ! 948: ! 949: Decides if we're going to attack or do something else ! 950: used by ai_run and ai_stand ! 951: ============= ! 952: */ ! 953: qboolean ai_checkattack (edict_t *self, float dist) ! 954: { ! 955: vec3_t temp; ! 956: qboolean hesDeadJim; ! 957: // PMM ! 958: qboolean retval; ! 959: ! 960: // this causes monsters to run blindly to the combat point w/o firing ! 961: if (self->goalentity) ! 962: { ! 963: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 964: return false; ! 965: ! 966: if (self->monsterinfo.aiflags & AI_SOUND_TARGET) ! 967: { ! 968: if ((level.time - self->enemy->teleport_time) > 5.0) ! 969: { ! 970: if (self->goalentity == self->enemy) ! 971: if (self->movetarget) ! 972: self->goalentity = self->movetarget; ! 973: else ! 974: self->goalentity = NULL; ! 975: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; ! 976: if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) ! 977: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 978: } ! 979: else ! 980: { ! 981: self->show_hostile = level.time + 1; ! 982: return false; ! 983: } ! 984: } ! 985: } ! 986: ! 987: enemy_vis = false; ! 988: ! 989: // see if the enemy is dead ! 990: hesDeadJim = false; ! 991: if ((!self->enemy) || (!self->enemy->inuse)) ! 992: { ! 993: hesDeadJim = true; ! 994: } ! 995: else if (self->monsterinfo.aiflags & AI_MEDIC) ! 996: { ! 997: if (self->enemy->health > 0) ! 998: { ! 999: hesDeadJim = true; ! 1000: // self->monsterinfo.aiflags &= ~AI_MEDIC; ! 1001: } ! 1002: } ! 1003: else ! 1004: { ! 1005: if (self->monsterinfo.aiflags & AI_BRUTAL) ! 1006: { ! 1007: if (self->enemy->health <= -80) ! 1008: hesDeadJim = true; ! 1009: } ! 1010: else ! 1011: { ! 1012: if (self->enemy->health <= 0) ! 1013: hesDeadJim = true; ! 1014: } ! 1015: } ! 1016: ! 1017: if (hesDeadJim) ! 1018: { ! 1019: self->monsterinfo.aiflags &= ~AI_MEDIC; ! 1020: self->enemy = NULL; ! 1021: // FIXME: look all around for other targets ! 1022: if (self->oldenemy && self->oldenemy->health > 0) ! 1023: { ! 1024: self->enemy = self->oldenemy; ! 1025: self->oldenemy = NULL; ! 1026: HuntTarget (self); ! 1027: } ! 1028: //ROGUE - multiple teslas make monsters lose track of the player. ! 1029: else if(self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0) ! 1030: { ! 1031: if ((g_showlogic) && (g_showlogic->value)) ! 1032: gi.dprintf("resorting to last_player_enemy...\n"); ! 1033: self->enemy = self->monsterinfo.last_player_enemy; ! 1034: self->oldenemy = NULL; ! 1035: self->monsterinfo.last_player_enemy = NULL; ! 1036: HuntTarget (self); ! 1037: } ! 1038: //ROGUE ! 1039: else ! 1040: { ! 1041: if (self->movetarget) ! 1042: { ! 1043: self->goalentity = self->movetarget; ! 1044: self->monsterinfo.walk (self); ! 1045: } ! 1046: else ! 1047: { ! 1048: // we need the pausetime otherwise the stand code ! 1049: // will just revert to walking with no target and ! 1050: // the monsters will wonder around aimlessly trying ! 1051: // to hunt the world entity ! 1052: self->monsterinfo.pausetime = level.time + 100000000; ! 1053: self->monsterinfo.stand (self); ! 1054: } ! 1055: return true; ! 1056: } ! 1057: } ! 1058: ! 1059: self->show_hostile = level.time + 1; // wake up other monsters ! 1060: ! 1061: // check knowledge of enemy ! 1062: enemy_vis = visible(self, self->enemy); ! 1063: if (enemy_vis) ! 1064: { ! 1065: self->monsterinfo.search_time = level.time + 5; ! 1066: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 1067: // PMM ! 1068: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; ! 1069: self->monsterinfo.trail_time = level.time; ! 1070: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 1071: self->monsterinfo.blind_fire_delay = 0; ! 1072: // pmm ! 1073: } ! 1074: ! 1075: // look for other coop players here ! 1076: // if (coop && self->monsterinfo.search_time < level.time) ! 1077: // { ! 1078: // if (FindTarget (self)) ! 1079: // return true; ! 1080: // } ! 1081: ! 1082: enemy_infront = infront(self, self->enemy); ! 1083: enemy_range = range(self, self->enemy); ! 1084: VectorSubtract (self->enemy->s.origin, self->s.origin, temp); ! 1085: enemy_yaw = vectoyaw(temp); ! 1086: ! 1087: ! 1088: // JDC self->ideal_yaw = enemy_yaw; ! 1089: ! 1090: // PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis ! 1091: // stuff .. this allows for, among other things, circle strafing and attacking while in ai_run ! 1092: retval = self->monsterinfo.checkattack (self); ! 1093: if (retval) ! 1094: { ! 1095: // PMM ! 1096: if (self->monsterinfo.attack_state == AS_MISSILE) ! 1097: { ! 1098: ai_run_missile (self); ! 1099: return true; ! 1100: } ! 1101: if (self->monsterinfo.attack_state == AS_MELEE) ! 1102: { ! 1103: ai_run_melee (self); ! 1104: return true; ! 1105: } ! 1106: // PMM -- added so monsters can shoot blind ! 1107: if (self->monsterinfo.attack_state == AS_BLIND) ! 1108: { ! 1109: ai_run_missile (self); ! 1110: return true; ! 1111: } ! 1112: // pmm ! 1113: ! 1114: // if enemy is not currently visible, we will never attack ! 1115: if (!enemy_vis) ! 1116: return false; ! 1117: // PMM ! 1118: } ! 1119: return retval; ! 1120: // PMM ! 1121: // return self->monsterinfo.checkattack (self); ! 1122: } ! 1123: ! 1124: ! 1125: /* ! 1126: ============= ! 1127: ai_run ! 1128: ! 1129: The monster has an enemy it is trying to kill ! 1130: ============= ! 1131: */ ! 1132: void ai_run (edict_t *self, float dist) ! 1133: { ! 1134: vec3_t v; ! 1135: edict_t *tempgoal; ! 1136: edict_t *save; ! 1137: qboolean new; ! 1138: edict_t *marker; ! 1139: float d1, d2; ! 1140: trace_t tr; ! 1141: vec3_t v_forward, v_right; ! 1142: float left, center, right; ! 1143: vec3_t left_target, right_target; ! 1144: //PMM ! 1145: qboolean retval; ! 1146: qboolean alreadyMoved = false; ! 1147: ! 1148: // if we're going to a combat point, just proceed ! 1149: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 1150: { ! 1151: M_MoveToGoal (self, dist); ! 1152: return; ! 1153: } ! 1154: ! 1155: // PMM ! 1156: if (self->monsterinfo.aiflags & AI_DUCKED) ! 1157: { ! 1158: if ((g_showlogic) && (g_showlogic->value)) ! 1159: gi.dprintf ("%s - duck flag cleaned up!\n", self->classname); ! 1160: self->monsterinfo.aiflags &= ~AI_DUCKED; ! 1161: } ! 1162: if (self->maxs[2] != self->monsterinfo.base_height) ! 1163: { ! 1164: if ((g_showlogic) && (g_showlogic->value)) ! 1165: gi.dprintf ("%s - ducked height corrected!\n", self->classname); ! 1166: monster_duck_up (self); ! 1167: } ! 1168: if ((self->monsterinfo.aiflags & AI_MANUAL_STEERING) && (strcmp(self->classname, "monster_turret"))) ! 1169: { ! 1170: if ((g_showlogic) && (g_showlogic->value)) ! 1171: gi.dprintf ("%s - manual steering in ai_run!\n", self->classname); ! 1172: } ! 1173: // pmm ! 1174: ! 1175: //========== ! 1176: //PGM ! 1177: // if we're currently looking for a hint path ! 1178: if (self->monsterinfo.aiflags & AI_HINT_PATH) ! 1179: { ! 1180: // determine direction to our destination hintpath. ! 1181: // FIXME - is this needed EVERY time? I was having trouble with them ! 1182: // sometimes not going after it, and this fixed it. ! 1183: // VectorSubtract(self->movetarget->s.origin, self->s.origin, v); ! 1184: // vectoangles(v, v_forward); ! 1185: // self->ideal_yaw = v_forward[YAW]; ! 1186: // gi.dprintf("seeking hintpath. origin: %s %0.1f\n", vtos(v), self->ideal_yaw); ! 1187: M_MoveToGoal (self, dist); ! 1188: if(!self->inuse) ! 1189: return; // PGM - g_touchtrigger free problem ! 1190: // return; ! 1191: ! 1192: // if we've already seen the player, and can't see him now, return ! 1193: if(self->enemy && !visible(self, self->enemy)) ! 1194: return; ! 1195: ! 1196: // if not and can't find the player, return ! 1197: // if(!FindTarget(self)) ! 1198: // return; ! 1199: ! 1200: // if we see the player, stop following hintpaths. ! 1201: if(g_showlogic && g_showlogic->value) ! 1202: gi.dprintf("stopped following hint paths\n"); ! 1203: ! 1204: // disconnect from hintpaths and start looking normally for players. ! 1205: hintpath_stop (self); ! 1206: // pmm - no longer needed, since hintpath_stop does it ! 1207: // HuntTarget(self); ! 1208: return; ! 1209: } ! 1210: //PGM ! 1211: //========== ! 1212: ! 1213: if (self->monsterinfo.aiflags & AI_SOUND_TARGET) ! 1214: { ! 1215: VectorSubtract (self->s.origin, self->enemy->s.origin, v); ! 1216: if (VectorLength(v) < 64) ! 1217: { ! 1218: self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 1219: self->monsterinfo.stand (self); ! 1220: return; ! 1221: } ! 1222: ! 1223: M_MoveToGoal (self, dist); ! 1224: // PMM - prevent double moves for sound_targets ! 1225: alreadyMoved = true; ! 1226: // pmm ! 1227: if(!self->inuse) ! 1228: return; // PGM - g_touchtrigger free problem ! 1229: ! 1230: if (!FindTarget (self)) ! 1231: return; ! 1232: } ! 1233: ! 1234: // PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging ! 1235: ! 1236: // PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING ! 1237: ! 1238: retval = ai_checkattack (self, dist); ! 1239: ! 1240: if (self->monsterinfo.aiflags & AI_DODGING) ! 1241: self->monsterinfo.attack_state = AS_SLIDING; ! 1242: ! 1243: if (self->monsterinfo.attack_state == AS_SLIDING) ! 1244: { ! 1245: // PMM - protect against double moves ! 1246: if (!alreadyMoved) ! 1247: ai_run_slide (self, dist); ! 1248: // PMM ! 1249: // we're using attack_state as the return value out of ai_run_slide to indicate whether or not the ! 1250: // move succeeded. If the move succeeded, and we're still sliding, we're done in here (since we've ! 1251: // had our chance to shoot in ai_checkattack, and have moved). ! 1252: // if the move failed, our state is as_straight, and it will be taken care of below ! 1253: if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING)) ! 1254: return; ! 1255: } ! 1256: else if (self->monsterinfo.aiflags & AI_CHARGING) ! 1257: { ! 1258: self->ideal_yaw = enemy_yaw; ! 1259: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING)) ! 1260: M_ChangeYaw (self); ! 1261: } ! 1262: if (retval) ! 1263: { ! 1264: // PMM - is this useful? Monsters attacking usually call the ai_charge routine.. ! 1265: // the only monster this affects should be the soldier ! 1266: if ((dist != 0) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && (!(self->monsterinfo.aiflags & AI_STAND_GROUND))) ! 1267: { ! 1268: M_MoveToGoal (self, dist); ! 1269: } ! 1270: if ((self->enemy) && (self->enemy->inuse) && (enemy_vis)) ! 1271: { ! 1272: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; ! 1273: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 1274: self->monsterinfo.trail_time = level.time; ! 1275: //PMM ! 1276: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 1277: self->monsterinfo.blind_fire_delay = 0; ! 1278: //pmm ! 1279: } ! 1280: return; ! 1281: } ! 1282: //PMM ! 1283: // if (ai_checkattack (self, dist)) ! 1284: // return; ! 1285: ! 1286: // if (self->monsterinfo.attack_state == AS_SLIDING) ! 1287: // { ! 1288: // ai_run_slide (self, dist); ! 1289: // return; ! 1290: // } ! 1291: ! 1292: if (enemy_vis) ! 1293: { ! 1294: // if (self->monsterinfo.aiflags & AI_LOST_SIGHT) ! 1295: // gi.dprintf("regained sight\n"); ! 1296: // PMM - check for alreadyMoved ! 1297: if (!alreadyMoved) ! 1298: M_MoveToGoal (self, dist); ! 1299: if(!self->inuse) ! 1300: return; // PGM - g_touchtrigger free problem ! 1301: ! 1302: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; ! 1303: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 1304: self->monsterinfo.trail_time = level.time; ! 1305: // PMM ! 1306: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target); ! 1307: self->monsterinfo.blind_fire_delay = 0; ! 1308: // pmm ! 1309: return; ! 1310: } ! 1311: ! 1312: // coop will change to another enemy if visible ! 1313: if (coop->value) ! 1314: { // FIXME: insane guys get mad with this, which causes crashes! ! 1315: if (FindTarget (self)) ! 1316: return; ! 1317: } ! 1318: ! 1319: //======= ! 1320: //PGM ! 1321: // if we've been looking (unsuccessfully) for the player for 10 seconds ! 1322: // PMM - reduced to 5, makes them much nastier ! 1323: if((self->monsterinfo.trail_time + 5) <= level.time) ! 1324: { ! 1325: // and we haven't checked for valid hint paths in the last 10 seconds ! 1326: if((self->monsterinfo.last_hint_time + 10) <= level.time) ! 1327: { ! 1328: // check for hint_paths. ! 1329: self->monsterinfo.last_hint_time = level.time; ! 1330: if(monsterlost_checkhint(self)) ! 1331: return; ! 1332: } ! 1333: } ! 1334: //PGM ! 1335: //======= ! 1336: ! 1337: if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) ! 1338: { ! 1339: // PMM - double move protection ! 1340: if (!alreadyMoved) ! 1341: M_MoveToGoal (self, dist); ! 1342: self->monsterinfo.search_time = 0; ! 1343: // gi.dprintf("search timeout\n"); ! 1344: return; ! 1345: } ! 1346: ! 1347: save = self->goalentity; ! 1348: tempgoal = G_Spawn(); ! 1349: self->goalentity = tempgoal; ! 1350: ! 1351: new = false; ! 1352: ! 1353: if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) ! 1354: { ! 1355: // just lost sight of the player, decide where to go first ! 1356: // gi.dprintf("lost sight of player, last seen at %s\n", vtos(self->monsterinfo.last_sighting)); ! 1357: self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); ! 1358: self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); ! 1359: new = true; ! 1360: } ! 1361: ! 1362: if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) ! 1363: { ! 1364: // vec3_t debug_vec; ! 1365: ! 1366: self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; ! 1367: // VectorSubtract(self->monsterinfo.last_sighting, self->s.origin, debug_vec); ! 1368: // gi.dprintf("reached current goal: %s %s %f", vtos(self->s.origin), vtos(self->monsterinfo.last_sighting), VectorLength(debug_vec)); ! 1369: ! 1370: // give ourself more time since we got this far ! 1371: self->monsterinfo.search_time = level.time + 5; ! 1372: ! 1373: if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) ! 1374: { ! 1375: // gi.dprintf("was temp goal; retrying original\n"); ! 1376: self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; ! 1377: marker = NULL; ! 1378: VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); ! 1379: new = true; ! 1380: } ! 1381: else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) ! 1382: { ! 1383: self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; ! 1384: marker = PlayerTrail_PickFirst (self); ! 1385: } ! 1386: else ! 1387: { ! 1388: marker = PlayerTrail_PickNext (self); ! 1389: } ! 1390: ! 1391: if (marker) ! 1392: { ! 1393: VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); ! 1394: self->monsterinfo.trail_time = marker->timestamp; ! 1395: self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; ! 1396: // gi.dprintf("heading is %0.1f\n", self->ideal_yaw); ! 1397: ! 1398: // debug_drawline(self.origin, self.last_sighting, 52); ! 1399: new = true; ! 1400: } ! 1401: } ! 1402: ! 1403: VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); ! 1404: d1 = VectorLength(v); ! 1405: if (d1 <= dist) ! 1406: { ! 1407: self->monsterinfo.aiflags |= AI_PURSUE_NEXT; ! 1408: dist = d1; ! 1409: } ! 1410: ! 1411: VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); ! 1412: ! 1413: if (new) ! 1414: { ! 1415: // gi.dprintf("checking for course correction\n"); ! 1416: ! 1417: tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); ! 1418: if (tr.fraction < 1) ! 1419: { ! 1420: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1421: d1 = VectorLength(v); ! 1422: center = tr.fraction; ! 1423: d2 = d1 * ((center+1)/2); ! 1424: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1425: AngleVectors(self->s.angles, v_forward, v_right, NULL); ! 1426: ! 1427: VectorSet(v, d2, -16, 0); ! 1428: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); ! 1429: tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); ! 1430: left = tr.fraction; ! 1431: ! 1432: VectorSet(v, d2, 16, 0); ! 1433: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); ! 1434: tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); ! 1435: right = tr.fraction; ! 1436: ! 1437: center = (d1*center)/d2; ! 1438: if (left >= center && left > right) ! 1439: { ! 1440: if (left < 1) ! 1441: { ! 1442: VectorSet(v, d2 * left * 0.5, -16, 0); ! 1443: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); ! 1444: // gi.dprintf("incomplete path, go part way and adjust again\n"); ! 1445: } ! 1446: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); ! 1447: self->monsterinfo.aiflags |= AI_PURSUE_TEMP; ! 1448: VectorCopy (left_target, self->goalentity->s.origin); ! 1449: VectorCopy (left_target, self->monsterinfo.last_sighting); ! 1450: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1451: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1452: // gi.dprintf("adjusted left\n"); ! 1453: // debug_drawline(self.origin, self.last_sighting, 152); ! 1454: } ! 1455: else if (right >= center && right > left) ! 1456: { ! 1457: if (right < 1) ! 1458: { ! 1459: VectorSet(v, d2 * right * 0.5, 16, 0); ! 1460: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); ! 1461: // gi.dprintf("incomplete path, go part way and adjust again\n"); ! 1462: } ! 1463: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); ! 1464: self->monsterinfo.aiflags |= AI_PURSUE_TEMP; ! 1465: VectorCopy (right_target, self->goalentity->s.origin); ! 1466: VectorCopy (right_target, self->monsterinfo.last_sighting); ! 1467: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1468: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1469: // gi.dprintf("adjusted right\n"); ! 1470: // debug_drawline(self.origin, self.last_sighting, 152); ! 1471: } ! 1472: } ! 1473: // else gi.dprintf("course was fine\n"); ! 1474: } ! 1475: ! 1476: M_MoveToGoal (self, dist); ! 1477: if(!self->inuse) ! 1478: return; // PGM - g_touchtrigger free problem ! 1479: ! 1480: G_FreeEdict(tempgoal); ! 1481: ! 1482: if (self) ! 1483: self->goalentity = save; ! 1484: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.