1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: Actor.DoomAttack.vc 4325 2010-07-15 23:11:16Z firebrand_kh $ 11//** 12//** Copyright (C) 1999-2006 Jānis Legzdiņš 13//** 14//** This program is free software; you can redistribute it and/or 15//** modify it under the terms of the GNU General Public License 16//** as published by the Free Software Foundation; either version 2 17//** of the License, or (at your option) any later version. 18//** 19//** This program is distributed in the hope that it will be useful, 20//** but WITHOUT ANY WARRANTY; without even the implied warranty of 21//** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22//** GNU General Public License for more details. 23//** 24//************************************************************************** 25 26//************************************************************************** 27// Original Doom/Strife monster attacks 28//************************************************************************** 29 30//========================================================================== 31// 32// A_PosAttack 33// 34// Zombieman attack. 35// 36//========================================================================== 37 38final void A_PosAttack() 39{ 40 int damage; 41 TVec dir; 42 43 if (!Target) 44 { 45 return; 46 } 47 48 A_FaceTarget(); 49 50 AimLineAttack(dir, Angles, MISSILERANGE); 51 VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0); 52 53 PlaySound('grunt/attack', CHAN_WEAPON); 54 55 damage = ((P_Random() % 5) + 1) * 3; 56 57 LineAttack(dir, MISSILERANGE, damage, BulletPuff); 58} 59 60//========================================================================== 61// 62// A_SPosAttack 63// 64// For DeHackEd compatibility only. 65// 66//========================================================================== 67 68final void A_SPosAttack() 69{ 70 if (!Target) 71 { 72 return; 73 } 74 75 PlaySound('shotguy/attack', CHAN_WEAPON); 76 DoSPosAttack(); 77} 78 79//========================================================================== 80// 81// A_SPosAttackUseAtkSound 82// 83// Shotgun guy, Spider mastermind attack. 84// 85//========================================================================== 86 87final void A_SPosAttackUseAtkSound() 88{ 89 if (!Target) 90 { 91 return; 92 } 93 94 PlaySound(AttackSound, CHAN_WEAPON); 95 DoSPosAttack(); 96} 97 98//========================================================================== 99// 100// DoSPosAttack 101// 102//========================================================================== 103 104final void DoSPosAttack() 105{ 106 int i; 107 int damage; 108 TVec aimDir; 109 TVec dir; 110 111 A_FaceTarget(); 112 AimLineAttack(aimDir, Angles, MISSILERANGE); 113 for (i = 0; i < 3; i++) 114 { 115 dir = aimDir; 116 VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0); 117 damage = ((P_Random() % 5) + 1) * 3; 118 LineAttack(dir, MISSILERANGE, damage, BulletPuff); 119 } 120} 121 122//========================================================================== 123// 124// A_CPosAttack 125// 126// Heavy weapon dude attack. 127// 128//========================================================================== 129 130final void A_CPosAttack() 131{ 132 int damage; 133 TVec dir; 134 135 if (!Target) 136 { 137 return; 138 } 139 140 // Andy Baker's stealth monsters 141 if (bStealth) 142 { 143 VisDir = 1; 144 } 145 146 //FIXME 147 PlaySound(AttackSound, CHAN_WEAPON); 148 A_FaceTarget(); 149 AimLineAttack(dir, Angles, MISSILERANGE); 150 VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0); 151 damage = ((P_Random() % 5) + 1) * 3; 152 LineAttack(dir, MISSILERANGE, damage, BulletPuff); 153} 154 155//========================================================================== 156// 157// A_CPosRefire 158// 159// Heavy weapon dude refire. 160// 161//========================================================================== 162 163final void A_CPosRefire() 164{ 165 // keep firing unless target got out of sight 166 A_FaceTarget(); 167 168 if (P_Random() < 40) 169 { 170 return; 171 } 172 173 if (!Target || Target.Health <= 0 || !CanSee(Target)) 174 { 175 SetState(SeeState); 176 } 177} 178 179//========================================================================== 180// 181// A_SpidRefire 182// 183// Spider mastermind refire. 184// 185//========================================================================== 186 187final void A_SpidRefire() 188{ 189 // keep firing unless target got out of sight 190 A_FaceTarget(); 191 192 if (P_Random() < 10) 193 return; 194 195 if (!Target || Target.Health <= 0 || !CanSee(Target)) 196 { 197 SetState(SeeState); 198 } 199} 200 201//========================================================================== 202// 203// A_TroopAttack 204// 205// Imp attack. 206// 207//========================================================================== 208 209final void A_TroopAttack() 210{ 211 int damage; 212 213 if (!Target) 214 { 215 return; 216 } 217 218 A_FaceTarget(); 219 if (CheckMeleeRange()) 220 { 221 PlaySound('imp/melee', CHAN_WEAPON); 222 damage = (P_Random() % 8 + 1) * 3; 223 Target.Damage(self, self, damage); 224 return; 225 } 226 227 // launch a missile 228 SpawnMissile(Target, DoomImpBall); 229} 230 231//========================================================================== 232// 233// A_SargAttack 234// 235// Demon, Spectre attack. 236// 237//========================================================================== 238 239final void A_SargAttack() 240{ 241 int damage; 242 243 if (!Target) 244 { 245 return; 246 } 247 248 A_FaceTarget(); 249 if (CheckMeleeRange()) 250 { 251 damage = ((P_Random() % 10) + 1) * 4; 252 Target.Damage(self, self, damage); 253 } 254} 255 256//========================================================================== 257// 258// A_HeadAttack 259// 260// Cacodemon attack. 261// 262//========================================================================== 263 264final void A_HeadAttack() 265{ 266 int damage; 267 268 if (!Target) 269 { 270 return; 271 } 272 273 A_FaceTarget(); 274 if (CheckMeleeRange()) 275 { 276 damage = (P_Random() % 6 + 1) * 10; 277 Target.Damage(self, self, damage); 278 return; 279 } 280 281 // launch a missile 282 SpawnMissile(Target, CacodemonBall); 283} 284 285//========================================================================== 286// 287// A_BruisAttack 288// 289// Hell knight, Baron of hell attack. 290// 291//========================================================================== 292 293final void A_BruisAttack() 294{ 295 int damage; 296 297 if (!Target) 298 { 299 return; 300 } 301 302 if (CheckMeleeRange()) 303 { 304 PlaySound('baron/melee', CHAN_WEAPON); 305 damage = (P_Random() % 8 + 1) * 10; 306 Target.Damage(self, self, damage); 307 return; 308 } 309 310 // launch a missile 311 SpawnMissile(Target, BaronBall); 312} 313 314//========================================================================== 315// 316// A_SkullAttack 317// 318// Lost soul attack. Fly at the player like a missile. 319// 320//========================================================================== 321 322const float SKULLSPEED = 700.0; 323 324final void A_SkullAttack() 325{ 326 if (!Target) 327 { 328 return; 329 } 330 331 bSkullFly = true; 332 333 PlaySound(AttackSound, CHAN_VOICE); 334 A_FaceTarget(); 335 Velocity = Normalise(Target.GetCentre() - Origin) * SKULLSPEED; 336} 337 338//========================================================================== 339// 340// A_SkullAttack 341// 342//========================================================================== 343 344final void decorate_A_SkullAttack(optional float SkullSpeed) 345{ 346 if (!specified_SkullSpeed) 347 { 348 SkullSpeed = 20.0; 349 } 350 if (!Target) 351 { 352 return; 353 } 354 355 bSkullFly = true; 356 357 PlaySound(AttackSound, CHAN_VOICE); 358 A_FaceTarget(); 359 Velocity = Normalise(Target.GetCentre() - Origin) * SkullSpeed * 35.0; 360} 361 362//========================================================================== 363// 364// A_BspiAttack 365// 366// Arachnotron attack. 367// 368//========================================================================== 369 370final void A_BspiAttack() 371{ 372 if (!Target) 373 { 374 return; 375 } 376 377 A_FaceTarget(); 378 379 // launch a missile 380 SpawnMissile(Target, ArachnotronPlasma); 381} 382 383//========================================================================== 384// 385// A_CyberAttack 386// 387// Cyberdemon attack. 388// 389//========================================================================== 390 391final void A_CyberAttack() 392{ 393 if (!Target) 394 { 395 return; 396 } 397 398 A_FaceTarget(); 399 SpawnMissile(Target, Rocket); 400} 401 402//========================================================================== 403// 404// A_PainAttack 405// 406// Pain elemental attack. Spawn a lost soul and launch it at the target. 407// 408//========================================================================== 409 410final void A_PainAttack() 411{ 412 decorate_A_PainAttack(LostSoul); 413} 414 415//========================================================================== 416// 417// decorate_A_PainAttack 418// 419// Pain elemental attack. Spawn a lost soul and launch it at the target. 420// 421//========================================================================== 422 423final void decorate_A_PainAttack(optional class<Actor> SpawnType) 424{ 425 if (!Target) 426 { 427 return; 428 } 429 430 A_FaceTarget(); 431 PainShootSkull(SpawnType, Angles.yaw); 432} 433 434//========================================================================== 435// 436// A_DualPainAttack 437// 438//========================================================================== 439 440final void A_DualPainAttack(optional class<Actor> SpawnType) 441{ 442 if (!Target) 443 { 444 return; 445 } 446 447 A_FaceTarget(); 448 PainShootSkull(SpawnType, Angles.yaw + 45.0); 449 PainShootSkull(SpawnType, Angles.yaw - 45.0); 450} 451 452//========================================================================== 453// 454// A_PainDie 455// 456// Pain elemental death. 457// 458//========================================================================== 459 460final void A_PainDie() 461{ 462 decorate_A_PainDie(LostSoul); 463} 464 465//========================================================================== 466// 467// decorate_A_PainDie 468// 469// Pain elemental death. 470// 471//========================================================================== 472 473final void decorate_A_PainDie(optional class<Actor> SpawnType) 474{ 475 A_Fall(); 476 PainShootSkull(SpawnType, Angles.yaw + 90.0); 477 PainShootSkull(SpawnType, Angles.yaw + 180.0); 478 PainShootSkull(SpawnType, Angles.yaw + 270.0); 479} 480 481//========================================================================== 482// 483// A_SkelFist 484// 485// Ravenant close attack. 486// 487//========================================================================== 488 489final void A_SkelFist() 490{ 491 int damage; 492 493 if (!Target) 494 { 495 return; 496 } 497 498 A_FaceTarget(); 499 500 if (CheckMeleeRange()) 501 { 502 damage = ((P_Random() % 10) + 1) * 6; 503 PlaySound('skeleton/melee', CHAN_WEAPON); 504 Target.Damage(self, self, damage); 505 } 506} 507 508//========================================================================== 509// 510// A_SkelMissile 511// 512// Ravenant far attack. 513// 514//========================================================================== 515 516final void A_SkelMissile() 517{ 518 EntityEx A; 519 520 if (!Target) 521 { 522 return; 523 } 524 525 A_FaceTarget(); 526 Origin.z += 16.0; // so missile spawns higher 527 A = SpawnMissile(Target, RevenantTracer); 528 Origin.z -= 16.0; // back to normal 529 530 if (A) 531 { 532 A.UnlinkFromWorld(); 533 A.Origin = A.Origin + A.Velocity * 0.03; 534 A.LinkToWorld(); 535 A.Tracer = Target; 536 A.bSeekerMissile = true; 537 } 538} 539 540//========================================================================== 541// 542// A_FatAttack1 543// 544// Mancubus attack, firing three missiles (bruisers) in three different 545// directions? Doesn't look like it. 546// 547//========================================================================== 548 549final void A_FatAttack1() 550{ 551 decorate_A_FatAttack1(FatShot); 552} 553 554//========================================================================== 555// 556// decorate_A_FatAttack1 557// 558// Mancubus attack, firing three missiles (bruisers) in three different 559// directions? Doesn't look like it. 560// 561//========================================================================== 562 563const float FatsoSpreadAngle = 90.0 / 8.0; 564 565final void decorate_A_FatAttack1(optional class<EntityEx> SpawnType) 566{ 567 EntityEx A; 568 569 if (!SpawnType) 570 { 571 SpawnType = FatShot; 572 } 573 574 if (!Target) 575 { 576 return; 577 } 578 579 A_FaceTarget(); 580 // Change direction to ... 581 Angles.yaw = AngleMod360(Angles.yaw + FatsoSpreadAngle); 582 SpawnMissile(Target, SpawnType); 583 584 A = SpawnMissile(Target, SpawnType); 585 if (A) 586 { 587 A.Angles.yaw = AngleMod360(A.Angles.yaw + FatsoSpreadAngle); 588 VectorRotateAroundZ(&A.Velocity, FatsoSpreadAngle); 589 } 590} 591 592//========================================================================== 593// 594// A_FatAttack2 595// 596// Mancubus attack, second direction. 597// 598//========================================================================== 599 600final void A_FatAttack2() 601{ 602 decorate_A_FatAttack2(FatShot); 603} 604 605//========================================================================== 606// 607// decorate_A_FatAttack2 608// 609// Mancubus attack, second direction. 610// 611//========================================================================== 612 613final void decorate_A_FatAttack2(optional class<EntityEx> SpawnType) 614{ 615 EntityEx A; 616 617 if (!SpawnType) 618 { 619 SpawnType = FatShot; 620 } 621 622 if (!Target) 623 { 624 return; 625 } 626 627 A_FaceTarget(); 628 // Now here choose opposite deviation. 629 Angles.yaw = AngleMod360(Angles.yaw - FatsoSpreadAngle); 630 SpawnMissile(Target, SpawnType); 631 632 A = SpawnMissile(Target, SpawnType); 633 if (A) 634 { 635 A.Angles.yaw = AngleMod360(A.Angles.yaw - FatsoSpreadAngle * 2.0); 636 VectorRotateAroundZ(&A.Velocity, -FatsoSpreadAngle * 2.0); 637 } 638} 639 640//========================================================================== 641// 642// A_FatAttack3 643// 644// Mancubus attack, third direction. 645// 646//========================================================================== 647 648final void A_FatAttack3() 649{ 650 decorate_A_FatAttack3(FatShot); 651} 652 653//========================================================================== 654// 655// decorate_A_FatAttack3 656// 657// Mancubus attack, third direction. 658// 659//========================================================================== 660 661final void decorate_A_FatAttack3(optional class<EntityEx> SpawnType) 662{ 663 EntityEx A; 664 665 if (!SpawnType) 666 { 667 SpawnType = FatShot; 668 } 669 670 if (!Target) 671 { 672 return; 673 } 674 675 A_FaceTarget(); 676 677 A = SpawnMissile(Target, SpawnType); 678 if (A) 679 { 680 A.Angles.yaw = AngleMod360(A.Angles.yaw - FatsoSpreadAngle / 2.0); 681 VectorRotateAroundZ(&A.Velocity, -FatsoSpreadAngle / 2.0); 682 } 683 684 A = SpawnMissile(Target, SpawnType); 685 if (A) 686 { 687 A.Angles.yaw = AngleMod360(A.Angles.yaw + FatsoSpreadAngle / 2.0); 688 VectorRotateAroundZ(&A.Velocity, FatsoSpreadAngle / 2.0); 689 } 690} 691 692//========================================================================== 693// 694// A_VileTarget 695// 696// Spawn the hellfire. 697// 698//========================================================================== 699 700final void A_VileTarget() 701{ 702 EntityEx fog; 703 704 if (!Target) 705 { 706 return; 707 } 708 709 A_FaceTarget(); 710 711 fog = Spawn(ArchvileFire, Target.Origin); 712 713 Tracer = fog; 714 fog.Target = self; 715 fog.Tracer = Target; 716 fog.SetState(fog.IdleState); 717} 718 719//========================================================================== 720// 721// A_VileAttack 722// 723// Arch-vile attack. 724// 725//========================================================================== 726 727final void A_VileAttack() 728{ 729 EntityEx fire; 730 731 if (!Target) 732 { 733 return; 734 } 735 736 A_FaceTarget(); 737 738 if (!CanSee(Target)) 739 { 740 return; 741 } 742 743 PlaySound('vile/stop', CHAN_WEAPON); 744 Target.Damage(self, self, 20); 745 746 fire = Tracer; 747 748 if (!fire) 749 { 750 return; 751 } 752 753 // move the fire between the vile and the player 754 fire.UnlinkFromWorld(); 755 fire.Origin.x = Target.Origin.x - 24.0 * cos(Angles.yaw); 756 fire.Origin.y = Target.Origin.y - 24.0 * sin(Angles.yaw); 757 fire.LinkToWorld(); 758 fire.RadiusAttack(self, 70, 70.0, true); 759 // change velocity Z component before doing RadiusAttack 760 Target.Velocity.z = (1000.0 / Target.Mass) * 35.0; 761} 762 763//========================================================================== 764// 765// A_BrainSpit 766// 767// Spawn a cube. 768// 769//========================================================================== 770 771final void A_BrainSpit() 772{ 773 BrainState BState; 774 EntityEx targ; 775 EntityEx A; 776 float Frac; 777 778 // Find brain state object, create it if not found. 779 BState = none; 780 foreach AllThinkers(BrainState, BState) 781 { 782 break; 783 } 784 if (!BState) 785 { 786 BState = Spawn(BrainState); 787 BState.FindTargets(); 788 } 789 790 if (!BState.Targets.Num) 791 { 792 // No brain targets placed on a map. 793 return; 794 } 795 796 BState.bEasy = !BState.bEasy; 797 if (Level.World.bSkillEasyBossBrain && !BState.bEasy) 798 { 799 return; 800 } 801 802 // shoot a cube at current target 803 targ = BState.Targets[BState.TargetOn]; 804 BState.TargetOn = (BState.TargetOn + 1) % BState.Targets.Num; 805 806 // spawn brain missile 807 A = SpawnMissile(targ, SpawnShot); 808 if (A) 809 { 810 A.Target = targ; 811 A.Master = self; 812 // Prevent division by 0 813 if (!A.Velocity.x && !A.Velocity.y) 814 { 815 Frac = 0.0; 816 } 817 else if (fabs(A.Velocity.x) > fabs(A.Velocity.y)) 818 { 819 Frac = (targ.Origin.x - Origin.x) / A.Velocity.x; 820 } 821 else 822 { 823 Frac = (targ.Origin.y - Origin.y) / A.Velocity.y; 824 } 825 A.ReactionCount = ftoi(Frac / GetStateDuration(A.State)); 826 } 827 828 PlaySound('brain/spit', CHAN_WEAPON, 1.0, ATTN_NONE); 829} 830 831//========================================================================== 832// 833// SpawnFly 834// 835// Cube flying, spawn monster, when finished. 836// 837//========================================================================== 838 839final void SpawnFly(class<EntityEx> SpawnType, name SpawnSound) 840{ 841 EntityEx newmobj; 842 EntityEx fog; 843 int r; 844 class<EntityEx> type; 845 846 if (--ReactionCount) 847 return; // still flying 848 849 // First spawn teleport fog. 850 fog = Spawn(SpawnType, Target.Origin, vector(0.0, 0.0, 0.0)); 851 fog.PlaySound(SpawnSound, CHAN_VOICE); 852 853 // Randomly select monster to spawn. 854 r = P_Random(); 855 856 // Probability distribution (kind of :), 857 // decreasing likelihood. 858 if (r < 50) 859 type = DoomImp; 860 else if (r < 90) 861 type = Demon; 862 else if (r < 120) 863 type = Spectre; 864 else if (r < 130) 865 type = PainElemental; 866 else if (r < 160) 867 type = Cacodemon; 868 else if (r < 162) 869 type = Archvile; 870 else if (r < 172) 871 type = Revenant; 872 else if (r < 192) 873 type = Arachnotron; 874 else if (r < 222) 875 type = Fatso; 876 else if (r < 246) 877 type = HellKnight; 878 else 879 type = BaronOfHell; 880 881 newmobj = Spawn(type, Target.Origin, vector(0.0, 0.0, 0.0)); 882 // Make new monster hate the same thing eye hates. Note that this 883 // differs from ZDoom which copies friendliness from target spot. 884 if (Master) 885 { 886 newmobj.CopyFriendliness(Master, false); 887 } 888 if (newmobj.SeeState && newmobj.LookForPlayers(true)) 889 { 890 newmobj.SetState(newmobj.SeeState); 891 } 892 893 // telefrag anything in this spot 894 if (!newmobj.IsDestroyed()) 895 { 896 newmobj.bTelestomp = true; 897 newmobj.TeleportMove(newmobj.Origin); 898 } 899 900 // remove self (i.e., cube). 901 Destroy(); 902} 903 904//========================================================================== 905// 906// A_SpawnFly 907// 908//========================================================================== 909 910final void A_SpawnFly() 911{ 912 SpawnFly(SpawnFire, 'brain/spawn'); 913} 914 915//========================================================================== 916// 917// decorate_A_SpawnFly 918// 919//========================================================================== 920 921final void decorate_A_SpawnFly(optional class<EntityEx> SpawnType) 922{ 923 name SpawnSound; 924 if (SpawnType) 925 { 926 SpawnSound = SpawnType.default.SightSound; 927 } 928 else 929 { 930 SpawnType = SpawnFire; 931 SpawnSound = 'brain/spawn'; 932 } 933 SpawnFly(SpawnType, SpawnSound); 934} 935 936//========================================================================== 937// 938// A_SpawnSound 939// 940// Travelling cube sound. 941// 942//========================================================================== 943 944final void A_SpawnSound() 945{ 946 PlaySound('brain/cube', CHAN_VOICE); 947 SpawnFly(SpawnFire, 'brain/spawn'); 948} 949 950//========================================================================== 951// 952// A_BrainScream 953// 954// Brain death sound, make explosions. 955// 956//========================================================================== 957 958final void A_BrainScream() 959{ 960 TVec org; 961 962 for (org.x = Origin.x - 196.0; org.x < Origin.x + 320.0; org.x += 8.0) 963 { 964 org.y = Origin.y - 320.0; 965 org.z = 1.0 / 512.0 + Random() * 512.0; 966 BrainishExplosion(org); 967 } 968 969 PlaySound('brain/death', CHAN_VOICE, 1.0, ATTN_NONE); 970} 971 972//========================================================================== 973// 974// A_BrainExplode 975// 976// Brain explosions. 977// 978//========================================================================== 979 980final void A_BrainExplode() 981{ 982 TVec org; 983 984 org.x = Origin.x + (Random() - Random()) * 8.0; 985 org.y = Origin.y; 986 org.z = 1.0 / 512.0 + Random() * 512.0; 987 BrainishExplosion(org); 988} 989 990//========================================================================== 991// 992// BrainishExplosion 993// 994//========================================================================== 995 996final void BrainishExplosion(TVec org) 997{ 998 EntityEx A = Spawn(Rocket, org,,, false); 999 A.DeathSound = 'misc/brainexplode'; 1000 // Disables collision detection which is not wanted here 1001 A.MissileDamage = 0; 1002 A.Velocity.z = Random() * 2.0 * 35.0; 1003 1004 A.SetState(FindClassState(BossBrain, 'BrainExplode')); 1005 1006 A.StateTime -= Random() * 0.2; 1007 if (A.StateTime < 0.1) 1008 { 1009 A.StateTime = 0.1; 1010 } 1011} 1012 1013//========================================================================== 1014// 1015// A_Mushroom 1016// 1017//========================================================================== 1018 1019final void A_Mushroom() 1020{ 1021 decorate_A_Mushroom(); 1022} 1023 1024//========================================================================== 1025// 1026// decorate_A_Mushroom 1027// 1028// killough 9/98: a mushroom explosion effect, sorta :) 1029// Original idea: Linguica 1030// 1031//========================================================================== 1032 1033final void decorate_A_Mushroom(optional class<Actor> SpawnType, 1034 optional int Amount, optional int Flags, optional float vrange, 1035 optional float hrange) 1036{ 1037 int i; 1038 int j; 1039 1040 if (!SpawnType) 1041 { 1042 SpawnType = FatShot; 1043 } 1044 if (!Amount) 1045 { 1046 Amount = GetMissileDamage(0, 1); 1047 } 1048 1049 // First make normal explosion 1050 RadiusAttack(self, 128, 128.0, !(Flags & 2), DamageType); 1051 1052 // Now launch mushroom cloud 1053 // We need something to aim at. 1054 Actor target = Spawn(MapSpot,,,, false); 1055 target.Height = Height; 1056 for (i = -Amount; i <= Amount; i += 8) 1057 { 1058 for (j = -Amount; j <= Amount; j += 8) 1059 { 1060 // Aim in many directions from source 1061 target.UnlinkFromWorld(); 1062 target.Origin.x = Origin.x + itof(i); 1063 target.Origin.y = Origin.y + itof(j); 1064 target.LinkToWorld(); 1065 // Aim up fairly high 1066 target.Origin.z = Origin.z + Length(vector(itof(i), itof(j), 1067 0.0)) * (vrange ? vrange : 4.0); 1068 EntityEx mo; 1069 // Launch fireball 1070 if (Flags & 1) 1071 { 1072 TVec org = vector(Origin.x, Origin.y, Origin.z + 32.0); 1073 mo = SpawnMissileXYZ(org, target, SpawnType); 1074 } 1075 else 1076 { 1077 mo = SpawnMissile(target, SpawnType); 1078 } 1079 if (mo) 1080 { 1081 // Slow it down a bit 1082 mo.Velocity = mo.Velocity * (hrange ? hrange : 0.5); 1083 // Make debris fall under gravity 1084 mo.bNoGravity = false; 1085 } 1086 } 1087 } 1088 target.Destroy(); 1089} 1090 1091//=========================================================================== 1092// 1093// A_M_Saw 1094// 1095//=========================================================================== 1096 1097final void A_M_Saw() 1098{ 1099 float angle; 1100 int damage; 1101 TAVec aimAng; 1102 TVec dir; 1103 EntityEx AimTarget; 1104 1105 if (!Target) 1106 { 1107 return; 1108 } 1109 1110 A_FaceTarget(); 1111 if (CheckMeleeRange()) 1112 { 1113 damage = 2 * (P_Random() % 10 + 1); 1114 aimAng = Angles; 1115 aimAng.yaw = AngleMod360(aimAng.yaw + (Random() - 1116 Random()) * 45.0 / 8.0); 1117 1118 // use meleerange + 1 se the puff doesn't skip the flash 1119 AimTarget = AimLineAttack(dir, aimAng, MELEERANGE + 0.00001); 1120 LineAttack(dir, MELEERANGE + 0.00001, damage, BulletPuff); 1121 1122 if (!AimTarget) 1123 { 1124 PlaySound('weapons/sawfull', CHAN_WEAPON); 1125 return; 1126 } 1127 PlaySound('weapons/sawhit', CHAN_WEAPON); 1128 1129 // turn to face target 1130 angle = atan2(AimTarget.Origin.y - Origin.y, 1131 AimTarget.Origin.x - Origin.x); 1132 if (AngleMod360(angle - Angles.yaw) > 180.0) 1133 { 1134 if (AngleMod360(angle - Angles.yaw) < -90.0 / 20.0) 1135 Angles.yaw = angle + 90.0 / 21.0; 1136 else 1137 Angles.yaw = Angles.yaw - 90.0 / 20.0; 1138 } 1139 else 1140 { 1141 if (AngleMod360(angle - Angles.yaw) > 90.0 / 20.0) 1142 Angles.yaw = angle - 90.0 / 21.0; 1143 else 1144 Angles.yaw += 90.0 / 20.0; 1145 } 1146 Angles.yaw = AngleMod360(Angles.yaw); 1147 } 1148 else 1149 { 1150 PlaySound('weapons/sawfull', CHAN_WEAPON); 1151 } 1152} 1153 1154//========================================================================== 1155// 1156// A_SentinelRefire 1157// 1158//========================================================================== 1159 1160final void A_SentinelRefire() 1161{ 1162 // keep firing unless target got out of sight 1163 A_FaceTarget(); 1164 1165 if (P_Random() < 30) 1166 { 1167 return; 1168 } 1169 1170 if (!Target || Target.Health <= 0 || !CanSee(Target) || P_Random() < 40) 1171 { 1172 SetState(SeeState); 1173 } 1174} 1175