g_ai.c (25020B)
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 */ 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 }