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