|
|
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: //============================================================================ ! 16: ! 17: ! 18: /* ! 19: ================= ! 20: AI_SetSightClient ! 21: ! 22: Called once each frame to set level.sight_client to the ! 23: player to be checked for in findtarget. ! 24: ! 25: If all clients are either dead or in notarget, sight_client ! 26: will be null. ! 27: ! 28: In coop games, sight_client will cycle between the clients. ! 29: ================= ! 30: */ ! 31: void AI_SetSightClient (void) ! 32: { ! 33: edict_t *ent; ! 34: int start, check; ! 35: ! 36: if (level.sight_client == NULL) ! 37: start = 1; ! 38: else ! 39: start = level.sight_client - g_edicts; ! 40: ! 41: check = start; ! 42: while (1) ! 43: { ! 44: check++; ! 45: if (check > game.maxclients) ! 46: check = 1; ! 47: ent = &g_edicts[check]; ! 48: if (ent->inuse ! 49: && ent->health > 0 ! 50: && !(ent->flags & FL_NOTARGET) ) ! 51: { ! 52: level.sight_client = ent; ! 53: return; // got one ! 54: } ! 55: if (check == start) ! 56: { ! 57: level.sight_client = NULL; ! 58: return; // nobody to see ! 59: } ! 60: } ! 61: } ! 62: ! 63: //============================================================================ ! 64: ! 65: /* ! 66: ============= ! 67: ai_move ! 68: ! 69: Move the specified distance at current facing. ! 70: This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward ! 71: ============== ! 72: */ ! 73: void ai_move (edict_t *self, float dist) ! 74: { ! 75: M_walkmove (self, self->s.angles[YAW], dist); ! 76: } ! 77: ! 78: ! 79: /* ! 80: ============= ! 81: ai_stand ! 82: ! 83: Used for standing around and looking for players ! 84: Distance is for slight position adjustments needed by the animations ! 85: ============== ! 86: */ ! 87: void ai_stand (edict_t *self, float dist) ! 88: { ! 89: vec3_t v; ! 90: ! 91: if (dist) ! 92: M_walkmove (self, self->s.angles[YAW], dist); ! 93: ! 94: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 95: { ! 96: if (self->enemy) ! 97: { ! 98: VectorSubtract (self->enemy->s.origin, self->s.origin, v); ! 99: self->ideal_yaw = vectoyaw(v); ! 100: if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) ! 101: { ! 102: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 103: self->monsterinfo.run (self); ! 104: } ! 105: M_ChangeYaw (self); ! 106: ai_checkattack (self, 0); ! 107: } ! 108: else ! 109: FindTarget (self); ! 110: return; ! 111: } ! 112: ! 113: if (FindTarget (self)) ! 114: return; ! 115: ! 116: if (level.time > self->monsterinfo.pausetime) ! 117: { ! 118: self->monsterinfo.walk (self); ! 119: return; ! 120: } ! 121: ! 122: if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) ! 123: { ! 124: if (self->monsterinfo.idle_time) ! 125: { ! 126: self->monsterinfo.idle (self); ! 127: self->monsterinfo.idle_time = level.time + 15 + random() * 15; ! 128: } ! 129: else ! 130: { ! 131: self->monsterinfo.idle_time = level.time + random() * 15; ! 132: } ! 133: } ! 134: } ! 135: ! 136: ! 137: /* ! 138: ============= ! 139: ai_walk ! 140: ! 141: The monster is walking it's beat ! 142: ============= ! 143: */ ! 144: void ai_walk (edict_t *self, float dist) ! 145: { ! 146: M_MoveToGoal (self, dist); ! 147: ! 148: // check for noticing a player ! 149: if (FindTarget (self)) ! 150: return; ! 151: ! 152: if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) ! 153: { ! 154: if (self->monsterinfo.idle_time) ! 155: { ! 156: self->monsterinfo.search (self); ! 157: self->monsterinfo.idle_time = level.time + 15 + random() * 15; ! 158: } ! 159: else ! 160: { ! 161: self->monsterinfo.idle_time = level.time + random() * 15; ! 162: } ! 163: } ! 164: } ! 165: ! 166: ! 167: /* ! 168: ============= ! 169: ai_charge ! 170: ! 171: Turns towards target and advances ! 172: Use this call with a distnace of 0 to replace ai_face ! 173: ============== ! 174: */ ! 175: void ai_charge (edict_t *self, float dist) ! 176: { ! 177: vec3_t v; ! 178: ! 179: VectorSubtract (self->enemy->s.origin, self->s.origin, v); ! 180: self->ideal_yaw = vectoyaw(v); ! 181: M_ChangeYaw (self); ! 182: ! 183: if (dist) ! 184: M_walkmove (self, self->s.angles[YAW], dist); ! 185: } ! 186: ! 187: ! 188: /* ! 189: ============= ! 190: ai_turn ! 191: ! 192: don't move, but turn towards ideal_yaw ! 193: Distance is for slight position adjustments needed by the animations ! 194: ============= ! 195: */ ! 196: void ai_turn (edict_t *self, float dist) ! 197: { ! 198: if (dist) ! 199: M_walkmove (self, self->s.angles[YAW], dist); ! 200: ! 201: if (FindTarget (self)) ! 202: return; ! 203: ! 204: M_ChangeYaw (self); ! 205: } ! 206: ! 207: ! 208: /* ! 209: ! 210: .enemy ! 211: Will be world if not currently angry at anyone. ! 212: ! 213: .movetarget ! 214: The next path spot to walk toward. If .enemy, ignore .movetarget. ! 215: When an enemy is killed, the monster will try to return to it's path. ! 216: ! 217: .hunt_time ! 218: Set to time + something when the player is in sight, but movement straight for ! 219: him is blocked. This causes the monster to use wall following code for ! 220: movement direction instead of sighting on the player. ! 221: ! 222: .ideal_yaw ! 223: A yaw angle of the intended direction, which will be turned towards at up ! 224: to 45 deg / state. If the enemy is in view and hunt_time is not active, ! 225: this will be the exact line towards the enemy. ! 226: ! 227: .pausetime ! 228: A monster will leave it's stand state and head towards it's .movetarget when ! 229: time > .pausetime. ! 230: ! 231: walkmove(angle, speed) primitive is all or nothing ! 232: */ ! 233: ! 234: /* ! 235: ============= ! 236: range ! 237: ! 238: returns the range catagorization of an entity reletive to self ! 239: 0 melee range, will become hostile even if back is turned ! 240: 1 visibility and infront, or visibility and show hostile ! 241: 2 infront and show hostile ! 242: 3 only triggered by damage ! 243: ============= ! 244: */ ! 245: int range (edict_t *self, edict_t *other) ! 246: { ! 247: vec3_t v; ! 248: float len; ! 249: ! 250: VectorSubtract (self->s.origin, other->s.origin, v); ! 251: len = VectorLength (v); ! 252: if (len < MELEE_DISTANCE) ! 253: return RANGE_MELEE; ! 254: if (len < 500) ! 255: return RANGE_NEAR; ! 256: if (len < 1000) ! 257: return RANGE_MID; ! 258: return RANGE_FAR; ! 259: } ! 260: ! 261: /* ! 262: ============= ! 263: visible ! 264: ! 265: returns 1 if the entity is visible to self, even if not infront () ! 266: ============= ! 267: */ ! 268: qboolean visible (edict_t *self, edict_t *other) ! 269: { ! 270: vec3_t spot1; ! 271: vec3_t spot2; ! 272: trace_t trace; ! 273: ! 274: VectorCopy (self->s.origin, spot1); ! 275: spot1[2] += self->viewheight; ! 276: VectorCopy (other->s.origin, spot2); ! 277: spot2[2] += other->viewheight; ! 278: trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); ! 279: ! 280: if (trace.fraction == 1.0) ! 281: return true; ! 282: return false; ! 283: } ! 284: ! 285: ! 286: /* ! 287: ============= ! 288: infront ! 289: ! 290: returns 1 if the entity is in front (in sight) of self ! 291: ============= ! 292: */ ! 293: qboolean infront (edict_t *self, edict_t *other) ! 294: { ! 295: vec3_t vec; ! 296: float dot; ! 297: vec3_t forward; ! 298: ! 299: AngleVectors (self->s.angles, forward, NULL, NULL); ! 300: VectorSubtract (other->s.origin, self->s.origin, vec); ! 301: VectorNormalize (vec); ! 302: dot = DotProduct (vec, forward); ! 303: ! 304: if (dot > 0.3) ! 305: return true; ! 306: return false; ! 307: } ! 308: ! 309: ! 310: //============================================================================ ! 311: ! 312: void HuntTarget (edict_t *self) ! 313: { ! 314: vec3_t vec; ! 315: ! 316: self->goalentity = self->enemy; ! 317: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 318: self->monsterinfo.stand (self); ! 319: else ! 320: self->monsterinfo.run (self); ! 321: VectorSubtract (self->enemy->s.origin, self->s.origin, vec); ! 322: self->ideal_yaw = vectoyaw(vec); ! 323: // wait a while before first attack ! 324: if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) ! 325: AttackFinished (self, 1); ! 326: } ! 327: ! 328: void FoundTarget (edict_t *self) ! 329: { ! 330: // let other monsters see this monster for a while ! 331: if (self->enemy->client) ! 332: { ! 333: level.sight_entity = self; ! 334: level.sight_entity_framenum = level.framenum; ! 335: level.sight_entity->light_level = 128; ! 336: } ! 337: ! 338: self->show_hostile = level.time + 1; // wake up other monsters ! 339: ! 340: VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); ! 341: self->monsterinfo.trail_time = level.time; ! 342: ! 343: if (!self->combattarget) ! 344: { ! 345: HuntTarget (self); ! 346: return; ! 347: } ! 348: ! 349: self->goalentity = self->movetarget = G_PickTarget(self->combattarget); ! 350: if (!self->movetarget) ! 351: { ! 352: self->goalentity = self->movetarget = self->enemy; ! 353: HuntTarget (self); ! 354: gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); ! 355: return; ! 356: } ! 357: ! 358: // clear out our combattarget, these are a one shot deal ! 359: self->combattarget = NULL; ! 360: self->monsterinfo.aiflags |= AI_COMBAT_POINT; ! 361: ! 362: // clear the targetname, that point is ours! ! 363: self->movetarget->targetname = NULL; ! 364: self->monsterinfo.pausetime = 0; ! 365: ! 366: // run for it ! 367: self->monsterinfo.run (self); ! 368: } ! 369: ! 370: ! 371: /* ! 372: =========== ! 373: FindTarget ! 374: ! 375: Self is currently not attacking anything, so try to find a target ! 376: ! 377: Returns TRUE if an enemy was sighted ! 378: ! 379: When a player fires a missile, the point of impact becomes a fakeplayer so ! 380: that monsters that see the impact will respond as if they had seen the ! 381: player. ! 382: ! 383: To avoid spending too much time, only a single client (or fakeclient) is ! 384: checked each frame. This means multi player games will have slightly ! 385: slower noticing monsters. ! 386: ============ ! 387: */ ! 388: qboolean FindTarget (edict_t *self) ! 389: { ! 390: edict_t *client; ! 391: qboolean heardit; ! 392: int r; ! 393: ! 394: if (self->monsterinfo.aiflags & AI_GOOD_GUY) ! 395: { ! 396: if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) ! 397: { ! 398: if (strcmp(self->goalentity->classname, "target_actor") == 0) ! 399: return false; ! 400: } ! 401: ! 402: //FIXME look for monsters? ! 403: return false; ! 404: } ! 405: ! 406: // if we're going to a combat point, just proceed ! 407: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 408: return false; ! 409: ! 410: // if the first spawnflag bit is set, the monster will only wake up on ! 411: // really seeing the player, not another monster getting angry or hearing ! 412: // something ! 413: ! 414: // revised behavior so they will wake up if they "see" a player make a noise ! 415: // but not weapon impact/explosion noises ! 416: ! 417: heardit = false; ! 418: if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) ! 419: { ! 420: client = level.sight_entity; ! 421: if (client->enemy == self->enemy) ! 422: { ! 423: return false; ! 424: } ! 425: } ! 426: else if (level.sound_entity_framenum >= (level.framenum - 1)) ! 427: { ! 428: client = level.sound_entity; ! 429: heardit = true; ! 430: } ! 431: else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) ! 432: { ! 433: client = level.sound2_entity; ! 434: heardit = true; ! 435: } ! 436: else ! 437: { ! 438: client = level.sight_client; ! 439: if (!client) ! 440: return false; // no clients to get mad at ! 441: } ! 442: ! 443: // if the entity went away, forget it ! 444: if (!client->inuse) ! 445: return false; ! 446: ! 447: if (client == self->enemy) ! 448: return true; // JDC false; ! 449: ! 450: if (client->client) ! 451: { ! 452: if (client->flags & FL_NOTARGET) ! 453: return false; ! 454: } ! 455: else if (client->svflags & SVF_MONSTER) ! 456: { ! 457: if (!client->enemy) ! 458: return false; ! 459: if (client->enemy->flags & FL_NOTARGET) ! 460: return false; ! 461: } ! 462: else if (heardit) ! 463: { ! 464: if (client->owner->flags & FL_NOTARGET) ! 465: return false; ! 466: } ! 467: else ! 468: return false; ! 469: ! 470: if (!heardit) ! 471: { ! 472: r = range (self, client); ! 473: ! 474: if (r == RANGE_FAR) ! 475: return false; ! 476: ! 477: // this is where we would check invisibility ! 478: ! 479: // is client in an spot too dark to be seen? ! 480: if (client->light_level <= 5) ! 481: return false; ! 482: ! 483: if (!visible (self, client)) ! 484: { ! 485: return false; ! 486: } ! 487: ! 488: if (r == RANGE_NEAR) ! 489: { ! 490: if (client->show_hostile < level.time && !infront (self, client)) ! 491: { ! 492: return false; ! 493: } ! 494: } ! 495: else if (r == RANGE_MID) ! 496: { ! 497: if (!infront (self, client)) ! 498: { ! 499: return false; ! 500: } ! 501: } ! 502: ! 503: self->enemy = client; ! 504: ! 505: if (strcmp(self->enemy->classname, "player_noise") != 0) ! 506: { ! 507: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; ! 508: ! 509: if (!self->enemy->client) ! 510: { ! 511: self->enemy = self->enemy->enemy; ! 512: if (!self->enemy->client) ! 513: { ! 514: self->enemy = NULL; ! 515: return false; ! 516: } ! 517: } ! 518: } ! 519: } ! 520: else // heardit ! 521: { ! 522: vec3_t temp; ! 523: ! 524: if (self->spawnflags & 1) ! 525: { ! 526: if (!visible (self, client)) ! 527: return false; ! 528: } ! 529: else ! 530: { ! 531: if (!gi.inPHS(self->s.origin, client->s.origin)) ! 532: return false; ! 533: } ! 534: ! 535: VectorSubtract (client->s.origin, self->s.origin, temp); ! 536: ! 537: if (VectorLength(temp) > 1000) // too far to hear ! 538: { ! 539: return false; ! 540: } ! 541: ! 542: // check area portals - if they are different and not connected then we can't hear it ! 543: if (client->areanum != self->areanum) ! 544: if (!gi.AreasConnected(self->areanum, client->areanum)) ! 545: return false; ! 546: ! 547: self->ideal_yaw = vectoyaw(temp); ! 548: M_ChangeYaw (self); ! 549: ! 550: // hunt the sound for a bit; hopefully find the real player ! 551: self->monsterinfo.aiflags |= AI_SOUND_TARGET; ! 552: self->enemy = client; ! 553: } ! 554: ! 555: // ! 556: // got one ! 557: // ! 558: FoundTarget (self); ! 559: ! 560: if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) ! 561: self->monsterinfo.sight (self, self->enemy); ! 562: ! 563: return true; ! 564: } ! 565: ! 566: ! 567: //============================================================================= ! 568: ! 569: /* ! 570: ============ ! 571: FacingIdeal ! 572: ! 573: ============ ! 574: */ ! 575: qboolean FacingIdeal(edict_t *self) ! 576: { ! 577: float delta; ! 578: ! 579: delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); ! 580: if (delta > 45 && delta < 315) ! 581: return false; ! 582: return true; ! 583: } ! 584: ! 585: ! 586: //============================================================================= ! 587: ! 588: qboolean M_CheckAttack (edict_t *self) ! 589: { ! 590: vec3_t spot1, spot2; ! 591: float chance; ! 592: trace_t tr; ! 593: ! 594: if (self->enemy->health > 0) ! 595: { ! 596: // see if any entities are in the way of the shot ! 597: VectorCopy (self->s.origin, spot1); ! 598: spot1[2] += self->viewheight; ! 599: VectorCopy (self->enemy->s.origin, spot2); ! 600: spot2[2] += self->enemy->viewheight; ! 601: ! 602: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); ! 603: ! 604: // do we have a clear shot? ! 605: if (tr.ent != self->enemy) ! 606: return false; ! 607: } ! 608: ! 609: // melee attack ! 610: if (enemy_range == RANGE_MELEE) ! 611: { ! 612: // don't always melee in easy mode ! 613: if (skill->value == 0 && (rand()&3) ) ! 614: return false; ! 615: if (self->monsterinfo.melee) ! 616: self->monsterinfo.attack_state = AS_MELEE; ! 617: else ! 618: self->monsterinfo.attack_state = AS_MISSILE; ! 619: return true; ! 620: } ! 621: ! 622: // missile attack ! 623: if (!self->monsterinfo.attack) ! 624: return false; ! 625: ! 626: if (level.time < self->monsterinfo.attack_finished) ! 627: return false; ! 628: ! 629: if (enemy_range == RANGE_FAR) ! 630: return false; ! 631: ! 632: if (self->monsterinfo.aiflags & AI_STAND_GROUND) ! 633: { ! 634: chance = 0.4; ! 635: } ! 636: else if (enemy_range == RANGE_MELEE) ! 637: { ! 638: chance = 0.2; ! 639: } ! 640: else if (enemy_range == RANGE_NEAR) ! 641: { ! 642: chance = 0.1; ! 643: } ! 644: else if (enemy_range == RANGE_MID) ! 645: { ! 646: chance = 0.02; ! 647: } ! 648: else ! 649: { ! 650: return false; ! 651: } ! 652: ! 653: if (skill->value == 0) ! 654: chance *= 0.5; ! 655: else if (skill->value >= 2) ! 656: chance *= 2; ! 657: ! 658: if (random () < chance) ! 659: { ! 660: self->monsterinfo.attack_state = AS_MISSILE; ! 661: self->monsterinfo.attack_finished = level.time + 2*random(); ! 662: return true; ! 663: } ! 664: ! 665: if (self->flags & FL_FLY) ! 666: { ! 667: if (random() < 0.3) ! 668: self->monsterinfo.attack_state = AS_SLIDING; ! 669: else ! 670: self->monsterinfo.attack_state = AS_STRAIGHT; ! 671: } ! 672: ! 673: return false; ! 674: } ! 675: ! 676: ! 677: /* ! 678: ============= ! 679: ai_run_melee ! 680: ! 681: Turn and close until within an angle to launch a melee attack ! 682: ============= ! 683: */ ! 684: void ai_run_melee(edict_t *self) ! 685: { ! 686: self->ideal_yaw = enemy_yaw; ! 687: M_ChangeYaw (self); ! 688: ! 689: if (FacingIdeal(self)) ! 690: { ! 691: self->monsterinfo.melee (self); ! 692: self->monsterinfo.attack_state = AS_STRAIGHT; ! 693: } ! 694: } ! 695: ! 696: ! 697: /* ! 698: ============= ! 699: ai_run_missile ! 700: ! 701: Turn in place until within an angle to launch a missile attack ! 702: ============= ! 703: */ ! 704: void ai_run_missile(edict_t *self) ! 705: { ! 706: self->ideal_yaw = enemy_yaw; ! 707: M_ChangeYaw (self); ! 708: ! 709: if (FacingIdeal(self)) ! 710: { ! 711: self->monsterinfo.attack (self); ! 712: self->monsterinfo.attack_state = AS_STRAIGHT; ! 713: } ! 714: }; ! 715: ! 716: ! 717: /* ! 718: ============= ! 719: ai_run_slide ! 720: ! 721: Strafe sideways, but stay at aproximately the same range ! 722: ============= ! 723: */ ! 724: void ai_run_slide(edict_t *self, float distance) ! 725: { ! 726: float ofs; ! 727: ! 728: self->ideal_yaw = enemy_yaw; ! 729: M_ChangeYaw (self); ! 730: ! 731: if (self->monsterinfo.lefty) ! 732: ofs = 90; ! 733: else ! 734: ofs = -90; ! 735: ! 736: if (M_walkmove (self, self->ideal_yaw + ofs, distance)) ! 737: return; ! 738: ! 739: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; ! 740: M_walkmove (self, self->ideal_yaw - ofs, distance); ! 741: } ! 742: ! 743: ! 744: /* ! 745: ============= ! 746: ai_checkattack ! 747: ! 748: Decides if we're going to attack or do something else ! 749: used by ai_run and ai_stand ! 750: ============= ! 751: */ ! 752: qboolean ai_checkattack (edict_t *self, float dist) ! 753: { ! 754: vec3_t temp; ! 755: qboolean hesDeadJim; ! 756: ! 757: // this causes monsters to run blindly to the combat point w/o firing ! 758: if (self->goalentity) ! 759: { ! 760: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 761: return false; ! 762: ! 763: if (self->monsterinfo.aiflags & AI_SOUND_TARGET) ! 764: { ! 765: if ((level.time - self->enemy->teleport_time) > 5.0) ! 766: { ! 767: if (self->goalentity == self->enemy) ! 768: if (self->movetarget) ! 769: self->goalentity = self->movetarget; ! 770: else ! 771: self->goalentity = NULL; ! 772: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; ! 773: if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) ! 774: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 775: } ! 776: else ! 777: { ! 778: self->show_hostile = level.time + 1; ! 779: return false; ! 780: } ! 781: } ! 782: } ! 783: ! 784: enemy_vis = false; ! 785: ! 786: // see if the enemy is dead ! 787: hesDeadJim = false; ! 788: if ((!self->enemy) || (!self->enemy->inuse)) ! 789: { ! 790: hesDeadJim = true; ! 791: } ! 792: else if (self->monsterinfo.aiflags & AI_MEDIC) ! 793: { ! 794: if (self->enemy->health > 0) ! 795: { ! 796: hesDeadJim = true; ! 797: self->monsterinfo.aiflags &= ~AI_MEDIC; ! 798: } ! 799: } ! 800: else ! 801: { ! 802: if (self->monsterinfo.aiflags & AI_BRUTAL) ! 803: { ! 804: if (self->enemy->health <= -80) ! 805: hesDeadJim = true; ! 806: } ! 807: else ! 808: { ! 809: if (self->enemy->health <= 0) ! 810: hesDeadJim = true; ! 811: } ! 812: } ! 813: ! 814: if (hesDeadJim) ! 815: { ! 816: self->enemy = NULL; ! 817: // FIXME: look all around for other targets ! 818: if (self->oldenemy && self->oldenemy->health > 0) ! 819: { ! 820: self->enemy = self->oldenemy; ! 821: self->oldenemy = NULL; ! 822: HuntTarget (self); ! 823: } ! 824: else ! 825: { ! 826: if (self->movetarget) ! 827: { ! 828: self->goalentity = self->movetarget; ! 829: self->monsterinfo.walk (self); ! 830: } ! 831: else ! 832: { ! 833: // we need the pausetime otherwise the stand code ! 834: // will just revert to walking with no target and ! 835: // the monsters will wonder around aimlessly trying ! 836: // to hunt the world entity ! 837: self->monsterinfo.pausetime = level.time + 100000000; ! 838: self->monsterinfo.stand (self); ! 839: } ! 840: return true; ! 841: } ! 842: } ! 843: ! 844: self->show_hostile = level.time + 1; // wake up other monsters ! 845: ! 846: // check knowledge of enemy ! 847: enemy_vis = visible(self, self->enemy); ! 848: if (enemy_vis) ! 849: { ! 850: self->monsterinfo.search_time = level.time + 5; ! 851: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 852: } ! 853: ! 854: // look for other coop players here ! 855: // if (coop && self->monsterinfo.search_time < level.time) ! 856: // { ! 857: // if (FindTarget (self)) ! 858: // return true; ! 859: // } ! 860: ! 861: enemy_infront = infront(self, self->enemy); ! 862: enemy_range = range(self, self->enemy); ! 863: VectorSubtract (self->enemy->s.origin, self->s.origin, temp); ! 864: enemy_yaw = vectoyaw(temp); ! 865: ! 866: ! 867: // JDC self->ideal_yaw = enemy_yaw; ! 868: ! 869: if (self->monsterinfo.attack_state == AS_MISSILE) ! 870: { ! 871: ai_run_missile (self); ! 872: return true; ! 873: } ! 874: if (self->monsterinfo.attack_state == AS_MELEE) ! 875: { ! 876: ai_run_melee (self); ! 877: return true; ! 878: } ! 879: ! 880: // if enemy is not currently visible, we will never attack ! 881: if (!enemy_vis) ! 882: return false; ! 883: ! 884: return self->monsterinfo.checkattack (self); ! 885: } ! 886: ! 887: ! 888: /* ! 889: ============= ! 890: ai_run ! 891: ! 892: The monster has an enemy it is trying to kill ! 893: ============= ! 894: */ ! 895: void ai_run (edict_t *self, float dist) ! 896: { ! 897: vec3_t v; ! 898: edict_t *tempgoal; ! 899: edict_t *save; ! 900: qboolean new; ! 901: edict_t *marker; ! 902: float d1, d2; ! 903: trace_t tr; ! 904: vec3_t v_forward, v_right; ! 905: float left, center, right; ! 906: vec3_t left_target, right_target; ! 907: ! 908: // if we're going to a combat point, just proceed ! 909: if (self->monsterinfo.aiflags & AI_COMBAT_POINT) ! 910: { ! 911: M_MoveToGoal (self, dist); ! 912: return; ! 913: } ! 914: ! 915: if (self->monsterinfo.aiflags & AI_SOUND_TARGET) ! 916: { ! 917: VectorSubtract (self->s.origin, self->enemy->s.origin, v); ! 918: if (VectorLength(v) < 64) ! 919: { ! 920: self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); ! 921: self->monsterinfo.stand (self); ! 922: return; ! 923: } ! 924: ! 925: M_MoveToGoal (self, dist); ! 926: ! 927: if (!FindTarget (self)) ! 928: return; ! 929: } ! 930: ! 931: if (ai_checkattack (self, dist)) ! 932: return; ! 933: ! 934: if (self->monsterinfo.attack_state == AS_SLIDING) ! 935: { ! 936: ai_run_slide (self, dist); ! 937: return; ! 938: } ! 939: ! 940: if (enemy_vis) ! 941: { ! 942: // if (self.aiflags & AI_LOST_SIGHT) ! 943: // dprint("regained sight\n"); ! 944: M_MoveToGoal (self, dist); ! 945: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; ! 946: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); ! 947: self->monsterinfo.trail_time = level.time; ! 948: return; ! 949: } ! 950: ! 951: // coop will change to another enemy if visible ! 952: if (coop->value) ! 953: { // FIXME: insane guys get mad with this, which causes crashes! ! 954: if (FindTarget (self)) ! 955: return; ! 956: } ! 957: ! 958: if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) ! 959: { ! 960: M_MoveToGoal (self, dist); ! 961: self->monsterinfo.search_time = 0; ! 962: // dprint("search timeout\n"); ! 963: return; ! 964: } ! 965: ! 966: save = self->goalentity; ! 967: tempgoal = G_Spawn(); ! 968: self->goalentity = tempgoal; ! 969: ! 970: new = false; ! 971: ! 972: if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) ! 973: { ! 974: // just lost sight of the player, decide where to go first ! 975: // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); ! 976: self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); ! 977: self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); ! 978: new = true; ! 979: } ! 980: ! 981: if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) ! 982: { ! 983: self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; ! 984: // dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); ! 985: ! 986: // give ourself more time since we got this far ! 987: self->monsterinfo.search_time = level.time + 5; ! 988: ! 989: if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) ! 990: { ! 991: // dprint("was temp goal; retrying original\n"); ! 992: self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; ! 993: marker = NULL; ! 994: VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); ! 995: new = true; ! 996: } ! 997: else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) ! 998: { ! 999: self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; ! 1000: marker = PlayerTrail_PickFirst (self); ! 1001: } ! 1002: else ! 1003: { ! 1004: marker = PlayerTrail_PickNext (self); ! 1005: } ! 1006: ! 1007: if (marker) ! 1008: { ! 1009: VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); ! 1010: self->monsterinfo.trail_time = marker->timestamp; ! 1011: self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; ! 1012: // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); ! 1013: ! 1014: // debug_drawline(self.origin, self.last_sighting, 52); ! 1015: new = true; ! 1016: } ! 1017: } ! 1018: ! 1019: VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); ! 1020: d1 = VectorLength(v); ! 1021: if (d1 <= dist) ! 1022: { ! 1023: self->monsterinfo.aiflags |= AI_PURSUE_NEXT; ! 1024: dist = d1; ! 1025: } ! 1026: ! 1027: VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); ! 1028: ! 1029: if (new) ! 1030: { ! 1031: // gi.dprintf("checking for course correction\n"); ! 1032: ! 1033: tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); ! 1034: if (tr.fraction < 1) ! 1035: { ! 1036: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1037: d1 = VectorLength(v); ! 1038: center = tr.fraction; ! 1039: d2 = d1 * ((center+1)/2); ! 1040: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1041: AngleVectors(self->s.angles, v_forward, v_right, NULL); ! 1042: ! 1043: VectorSet(v, d2, -16, 0); ! 1044: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); ! 1045: tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); ! 1046: left = tr.fraction; ! 1047: ! 1048: VectorSet(v, d2, 16, 0); ! 1049: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); ! 1050: tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); ! 1051: right = tr.fraction; ! 1052: ! 1053: center = (d1*center)/d2; ! 1054: if (left >= center && left > right) ! 1055: { ! 1056: if (left < 1) ! 1057: { ! 1058: VectorSet(v, d2 * left * 0.5, -16, 0); ! 1059: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); ! 1060: // gi.dprintf("incomplete path, go part way and adjust again\n"); ! 1061: } ! 1062: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); ! 1063: self->monsterinfo.aiflags |= AI_PURSUE_TEMP; ! 1064: VectorCopy (left_target, self->goalentity->s.origin); ! 1065: VectorCopy (left_target, self->monsterinfo.last_sighting); ! 1066: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1067: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1068: // gi.dprintf("adjusted left\n"); ! 1069: // debug_drawline(self.origin, self.last_sighting, 152); ! 1070: } ! 1071: else if (right >= center && right > left) ! 1072: { ! 1073: if (right < 1) ! 1074: { ! 1075: VectorSet(v, d2 * right * 0.5, 16, 0); ! 1076: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); ! 1077: // gi.dprintf("incomplete path, go part way and adjust again\n"); ! 1078: } ! 1079: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); ! 1080: self->monsterinfo.aiflags |= AI_PURSUE_TEMP; ! 1081: VectorCopy (right_target, self->goalentity->s.origin); ! 1082: VectorCopy (right_target, self->monsterinfo.last_sighting); ! 1083: VectorSubtract (self->goalentity->s.origin, self->s.origin, v); ! 1084: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); ! 1085: // gi.dprintf("adjusted right\n"); ! 1086: // debug_drawline(self.origin, self.last_sighting, 152); ! 1087: } ! 1088: } ! 1089: // else gi.dprintf("course was fine\n"); ! 1090: } ! 1091: ! 1092: M_MoveToGoal (self, dist); ! 1093: ! 1094: G_FreeEdict(tempgoal); ! 1095: ! 1096: if (self) ! 1097: self->goalentity = save; ! 1098: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.