1void() movetarget_f; 2void() t_movetarget; 3void() knight_walk1; 4void() knight_bow6; 5void() knight_bow1; 6void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage; 7/* 8 9.enemy 10Will be world if not currently angry at anyone. 11 12.movetarget 13The next path spot to walk toward. If .enemy, ignore .movetarget. 14When an enemy is killed, the monster will try to return to it's path. 15 16.huntt_ime 17Set to time + something when the player is in sight, but movement straight for 18him is blocked. This causes the monster to use wall following code for 19movement direction instead of sighting on the player. 20 21.ideal_yaw 22A yaw angle of the intended direction, which will be turned towards at up 23to 45 deg / state. If the enemy is in view and hunt_time is not active, 24this will be the exact line towards the enemy. 25 26.pausetime 27A monster will leave it's stand state and head towards it's .movetarget when 28time > .pausetime. 29 30walkmove(angle, speed) primitive is all or nothing 31*/ 32 33 34// 35// globals 36// 37float current_yaw; 38 39// 40// when a monster becomes angry at a player, that monster will be used 41// as the sight target the next frame so that monsters near that one 42// will wake up even if they wouldn't have noticed the player 43// 44entity sight_entity; 45float sight_entity_time; 46 47float(float v) anglemod = 48{ 49 while (v >= 360) 50 v = v - 360; 51 while (v < 0) 52 v = v + 360; 53 return v; 54}; 55 56/* 57============================================================================== 58 59MOVETARGET CODE 60 61The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target. 62 63targetname 64must be present. The name of this movetarget. 65 66target 67the next spot to move to. If not present, stop here for good. 68 69pausetime 70The number of seconds to spend standing or bowing for path_stand or path_bow 71 72============================================================================== 73*/ 74 75 76void() movetarget_f = 77{ 78 if (!self.targetname) 79 objerror ("monster_movetarget: no targetname"); 80 81 self.solid = SOLID_TRIGGER; 82 self.touch = t_movetarget; 83 setsize (self, '-8 -8 -8', '8 8 8'); 84 85}; 86 87/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) 88Monsters will continue walking towards the next target corner. 89*/ 90void() path_corner = 91{ 92 movetarget_f (); 93}; 94 95 96/* 97============= 98t_movetarget 99 100Something has bumped into a movetarget. If it is a monster 101moving towards it, change the next destination and continue. 102============== 103*/ 104void() t_movetarget = 105{ 106local entity temp; 107 108 if (other.movetarget != self) 109 return; 110 111 if (other.enemy) 112 return; // fighting, not following a path 113 114 temp = self; 115 self = other; 116 other = temp; 117 118 if (self.classname == "monster_ogre") 119 sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound 120 121//dprint ("t_movetarget\n"); 122 self.goalentity = self.movetarget = find (world, targetname, other.target); 123 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); 124 if (!self.movetarget) 125 { 126 self.pausetime = time + 999999; 127 self.th_stand (); 128 return; 129 } 130}; 131 132 133 134//============================================================================ 135 136/* 137============= 138range 139 140returns the range catagorization of an entity reletive to self 1410 melee range, will become hostile even if back is turned 1421 visibility and infront, or visibility and show hostile 1432 infront and show hostile 1443 only triggered by damage 145============= 146*/ 147float(entity targ) range = 148{ 149local vector spot1, spot2; 150local float r; 151 spot1 = self.origin + self.view_ofs; 152 spot2 = targ.origin + targ.view_ofs; 153 154 r = vlen (spot1 - spot2); 155 if (r < 120) 156 return RANGE_MELEE; 157 if (r < 500) 158 return RANGE_NEAR; 159 if (r < 1000) 160 return RANGE_MID; 161 return RANGE_FAR; 162}; 163 164/* 165============= 166visible 167 168returns 1 if the entity is visible to self, even if not infront () 169============= 170*/ 171float (entity targ) visible = 172{ 173 local vector spot1, spot2; 174 175 spot1 = self.origin + self.view_ofs; 176 spot2 = targ.origin + targ.view_ofs; 177 traceline (spot1, spot2, TRUE, self); // see through other monsters 178 179 if (trace_inopen && trace_inwater) 180 return FALSE; // sight line crossed contents 181 182 if (trace_fraction == 1) 183 return TRUE; 184 return FALSE; 185}; 186 187 188/* 189============= 190infront 191 192returns 1 if the entity is in front (in sight) of self 193============= 194*/ 195float(entity targ) infront = 196{ 197 local vector vec; 198 local float dot; 199 200 makevectors (self.angles); 201 vec = normalize (targ.origin - self.origin); 202 dot = vec * v_forward; 203 204 if ( dot > 0.3) 205 { 206 return TRUE; 207 } 208 return FALSE; 209}; 210 211 212//============================================================================ 213 214/* 215=========== 216ChangeYaw 217 218Turns towards self.ideal_yaw at self.yaw_speed 219Sets the global variable current_yaw 220Called every 0.1 sec by monsters 221============ 222*/ 223/* 224 225void() ChangeYaw = 226{ 227 local float ideal, move; 228 229//current_yaw = self.ideal_yaw; 230// mod down the current angle 231 current_yaw = anglemod( self.angles_y ); 232 ideal = self.ideal_yaw; 233 234 if (current_yaw == ideal) 235 return; 236 237 move = ideal - current_yaw; 238 if (ideal > current_yaw) 239 { 240 if (move > 180) 241 move = move - 360; 242 } 243 else 244 { 245 if (move < -180) 246 move = move + 360; 247 } 248 249 if (move > 0) 250 { 251 if (move > self.yaw_speed) 252 move = self.yaw_speed; 253 } 254 else 255 { 256 if (move < 0-self.yaw_speed ) 257 move = 0-self.yaw_speed; 258 } 259 260 current_yaw = anglemod (current_yaw + move); 261 262 self.angles_y = current_yaw; 263}; 264 265*/ 266 267 268//============================================================================ 269 270void() HuntTarget = 271{ 272 self.goalentity = self.enemy; 273 self.think = self.th_run; 274 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); 275 self.nextthink = time + 0.1; 276 SUB_AttackFinished (1); // wait a while before first attack 277}; 278 279void() SightSound = 280{ 281local float rsnd; 282 283 if (self.classname == "monster_ogre") 284 sound (self, CHAN_VOICE, "ogre/ogwake.wav", 1, ATTN_NORM); 285 else if (self.classname == "monster_knight") 286 sound (self, CHAN_VOICE, "knight/ksight.wav", 1, ATTN_NORM); 287 else if (self.classname == "monster_shambler") 288 sound (self, CHAN_VOICE, "shambler/ssight.wav", 1, ATTN_NORM); 289 else if (self.classname == "monster_demon1") 290 sound (self, CHAN_VOICE, "demon/sight2.wav", 1, ATTN_NORM); 291 else if (self.classname == "monster_wizard") 292 sound (self, CHAN_VOICE, "wizard/wsight.wav", 1, ATTN_NORM); 293 else if (self.classname == "monster_zombie") 294 sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_NORM); 295 else if (self.classname == "monster_dog") 296 sound (self, CHAN_VOICE, "dog/dsight.wav", 1, ATTN_NORM); 297 else if (self.classname == "monster_hell_knight") 298 sound (self, CHAN_VOICE, "hknight/sight1.wav", 1, ATTN_NORM); 299 else if (self.classname == "monster_tarbaby") 300 sound (self, CHAN_VOICE, "blob/sight1.wav", 1, ATTN_NORM); 301 else if (self.classname == "monster_vomit") 302 sound (self, CHAN_VOICE, "vomitus/v_sight1.wav", 1, ATTN_NORM); 303 else if (self.classname == "monster_enforcer") 304 { 305 rsnd = rint(random() * 3); 306 if (rsnd == 1) 307 sound (self, CHAN_VOICE, "enforcer/sight1.wav", 1, ATTN_NORM); 308 else if (rsnd == 2) 309 sound (self, CHAN_VOICE, "enforcer/sight2.wav", 1, ATTN_NORM); 310 else if (rsnd == 0) 311 sound (self, CHAN_VOICE, "enforcer/sight3.wav", 1, ATTN_NORM); 312 else 313 sound (self, CHAN_VOICE, "enforcer/sight4.wav", 1, ATTN_NORM); 314 } 315 else if (self.classname == "monster_army") 316 sound (self, CHAN_VOICE, "soldier/sight1.wav", 1, ATTN_NORM); 317 else if (self.classname == "monster_shalrath") 318 sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM); 319}; 320 321void() FoundTarget = 322{ 323 if (self.enemy.classname == "player") 324 { // let other monsters see this monster for a while 325 sight_entity = self; 326 sight_entity_time = time; 327 } 328 329 self.show_hostile = time + 1; // wake up other monsters 330 331 SightSound (); 332 HuntTarget (); 333}; 334 335/* 336=========== 337FindTarget 338 339Self is currently not attacking anything, so try to find a target 340 341Returns TRUE if an enemy was sighted 342 343When a player fires a missile, the point of impact becomes a fakeplayer so 344that monsters that see the impact will respond as if they had seen the 345player. 346 347To avoid spending too much time, only a single client (or fakeclient) is 348checked each frame. This means multi player games will have slightly 349slower noticing monsters. 350============ 351*/ 352float() FindTarget = 353{ 354 local entity client; 355 local float r; 356 357// if the first spawnflag bit is set, the monster will only wake up on 358// really seeing the player, not another monster getting angry 359 360// spawnflags & 3 is a big hack, because zombie crucified used the first 361// spawn flag prior to the ambush flag, and I forgot about it, so the second 362// spawn flag works as well 363 if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) ) 364 { 365 client = sight_entity; 366 if (client.enemy == self.enemy) 367 return; 368 } 369 else 370 { 371 client = checkclient (); 372 if (!client) 373 return FALSE; // current check entity isn't in PVS 374 } 375 376 if (client == self.enemy) 377 return FALSE; 378 379 if (client.flags & FL_NOTARGET) 380 return FALSE; 381 if (client.items & IT_INVISIBILITY) 382 return FALSE; 383 384 r = range (client); 385 if (r == RANGE_FAR) 386 return FALSE; 387 388 if (!visible (client)) 389 return FALSE; 390 391 if (r == RANGE_NEAR) 392 { 393 if (client.show_hostile < time && !infront (client)) 394 return FALSE; 395 } 396 else if (r == RANGE_MID) 397 { 398 if ( /* client.show_hostile < time || */ !infront (client)) 399 return FALSE; 400 } 401 402// 403// got one 404// 405 self.enemy = client; 406 if (self.enemy.classname != "player") 407 { 408 self.enemy = self.enemy.enemy; 409 if (self.enemy.classname != "player") 410 { 411 self.enemy = world; 412 return FALSE; 413 } 414 } 415 416 FoundTarget (); 417 418 return TRUE; 419}; 420 421 422//============================================================================= 423 424void(float dist) ai_forward = 425{ 426 walkmove (self.angles_y, dist); 427}; 428 429void(float dist) ai_back = 430{ 431 walkmove ( (self.angles_y+180), dist); 432}; 433 434 435/* 436============= 437ai_pain 438 439stagger back a bit 440============= 441*/ 442void(float dist) ai_pain = 443{ 444 ai_back (dist); 445/* 446 local float away; 447 448 away = anglemod (vectoyaw (self.origin - self.enemy.origin) 449 + 180*(random()- 0.5) ); 450 451 walkmove (away, dist); 452*/ 453}; 454 455/* 456============= 457ai_painforward 458 459stagger back a bit 460============= 461*/ 462void(float dist) ai_painforward = 463{ 464 walkmove (self.ideal_yaw, dist); 465}; 466 467/* 468============= 469ai_walk 470 471The monster is walking it's beat 472============= 473*/ 474void(float dist) ai_walk = 475{ 476 local vector mtemp; 477 478 movedist = dist; 479 480 if (self.classname == "monster_dragon") 481 { 482 movetogoal (dist); 483 return; 484 } 485 // check for noticing a player 486 if (FindTarget ()) 487 return; 488 489 movetogoal (dist); 490}; 491 492 493/* 494============= 495ai_stand 496 497The monster is staying in one place for a while, with slight angle turns 498============= 499*/ 500void() ai_stand = 501{ 502 if (FindTarget ()) 503 return; 504 505 if (time > self.pausetime) 506 { 507 self.th_walk (); 508 return; 509 } 510 511// change angle slightly 512 513}; 514 515/* 516============= 517ai_turn 518 519don't move, but turn towards ideal_yaw 520============= 521*/ 522void() ai_turn = 523{ 524 if (FindTarget ()) 525 return; 526 527 ChangeYaw (); 528}; 529 530//============================================================================= 531 532/* 533============= 534ChooseTurn 535============= 536*/ 537void(vector dest3) ChooseTurn = 538{ 539 local vector dir, newdir; 540 541 dir = self.origin - dest3; 542 543 newdir_x = trace_plane_normal_y; 544 newdir_y = 0 - trace_plane_normal_x; 545 newdir_z = 0; 546 547 if (dir * newdir > 0) 548 { 549 dir_x = 0 - trace_plane_normal_y; 550 dir_y = trace_plane_normal_x; 551 } 552 else 553 { 554 dir_x = trace_plane_normal_y; 555 dir_y = 0 - trace_plane_normal_x; 556 } 557 558 dir_z = 0; 559 self.ideal_yaw = vectoyaw(dir); 560}; 561 562/* 563============ 564FacingIdeal 565 566============ 567*/ 568float() FacingIdeal = 569{ 570 local float delta; 571 572 delta = anglemod(self.angles_y - self.ideal_yaw); 573 if (delta > 45 && delta < 315) 574 return FALSE; 575 return TRUE; 576}; 577 578 579//============================================================================= 580 581float() WizardCheckAttack; 582float() DogCheckAttack; 583 584float() CheckAnyAttack = 585{ 586 if (!enemy_vis) 587 return; 588 if (self.classname == "monster_army") 589 return SoldierCheckAttack (); 590 if (self.classname == "monster_ogre") 591 return OgreCheckAttack (); 592 if (self.classname == "monster_shambler") 593 return ShamCheckAttack (); 594 if (self.classname == "monster_demon1") 595 return DemonCheckAttack (); 596 if (self.classname == "monster_dog") 597 return DogCheckAttack (); 598 if (self.classname == "monster_wizard") 599 return WizardCheckAttack (); 600 return CheckAttack (); 601}; 602 603 604/* 605============= 606ai_run_melee 607 608Turn and close until within an angle to launch a melee attack 609============= 610*/ 611void() ai_run_melee = 612{ 613 self.ideal_yaw = enemy_yaw; 614 ChangeYaw (); 615 616 if (FacingIdeal()) 617 { 618 self.th_melee (); 619 self.attack_state = AS_STRAIGHT; 620 } 621}; 622 623 624/* 625============= 626ai_run_missile 627 628Turn in place until within an angle to launch a missile attack 629============= 630*/ 631void() ai_run_missile = 632{ 633 self.ideal_yaw = enemy_yaw; 634 ChangeYaw (); 635 if (FacingIdeal()) 636 { 637 self.th_missile (); 638 self.attack_state = AS_STRAIGHT; 639 } 640}; 641 642 643/* 644============= 645ai_run_slide 646 647Strafe sideways, but stay at aproximately the same range 648============= 649*/ 650void() ai_run_slide = 651{ 652 local float ofs; 653 654 self.ideal_yaw = enemy_yaw; 655 ChangeYaw (); 656 if (self.lefty) 657 ofs = 90; 658 else 659 ofs = -90; 660 661 if (walkmove (self.ideal_yaw + ofs, movedist)) 662 return; 663 664 self.lefty = 1 - self.lefty; 665 666 walkmove (self.ideal_yaw - ofs, movedist); 667}; 668 669 670/* 671============= 672ai_run 673 674The monster has an enemy it is trying to kill 675============= 676*/ 677void(float dist) ai_run = 678{ 679 local vector delta; 680 local float axis; 681 local float direct, ang_rint, ang_floor, ang_ceil; 682 683 movedist = dist; 684// see if the enemy is dead 685 if (self.enemy.health <= 0) 686 { 687 self.enemy = world; 688 // FIXME: look all around for other targets 689 if (self.oldenemy.health > 0) 690 { 691 self.enemy = self.oldenemy; 692 HuntTarget (); 693 } 694 else 695 { 696 if (self.movetarget) 697 self.th_walk (); 698 else 699 self.th_stand (); 700 return; 701 } 702 } 703 704 self.show_hostile = time + 1; // wake up other monsters 705 706// check knowledge of enemy 707 enemy_vis = visible(self.enemy); 708 if (enemy_vis) 709 self.search_time = time + 5; 710 711// look for other coop players 712 if (coop && self.search_time < time) 713 { 714 if (FindTarget ()) 715 return; 716 } 717 718 enemy_infront = infront(self.enemy); 719 enemy_range = range(self.enemy); 720 enemy_yaw = vectoyaw(self.enemy.origin - self.origin); 721 722 if (self.attack_state == AS_MISSILE) 723 { 724//dprint ("ai_run_missile\n"); 725 ai_run_missile (); 726 return; 727 } 728 if (self.attack_state == AS_MELEE) 729 { 730//dprint ("ai_run_melee\n"); 731 ai_run_melee (); 732 return; 733 } 734 735 if (CheckAnyAttack ()) 736 return; // beginning an attack 737 738 if (self.attack_state == AS_SLIDING) 739 { 740 ai_run_slide (); 741 return; 742 } 743 744// head straight in 745 movetogoal (dist); // done in C code... 746}; 747 748