1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: Actor.Hexen.vc 4319 2010-07-03 18:24:22Z 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 26const float FIREDEMON_ATTACK_RANGE = 512.0; 27 28const int SORCBALL_INITIAL_SPEED = 7; 29const int SORCBALL_TERMINAL_SPEED = 25; 30const int SORCBALL_SPEED_ROTATIONS = 5; 31const int SORCFX4_RAPIDFIRE_TIME = (6*3); // 3 seconds 32const float SORCFX4_SPREAD_ANGLE = 20.0; 33const float SORC_DEFENSE_HEIGHT = 45.0; 34const int SORC_DEFENSE_TIME = 255; 35const int BOUNCE_TIME_UNIT = (35/2); 36 37enum 38{ 39 SORC_DECELERATE, 40 SORC_ACCELERATE, 41 SORC_STOPPING, 42 SORC_FIRESPELL, 43 SORC_STOPPED, 44 SORC_NORMAL, 45 SORC_FIRING_SPELL 46}; 47 48const int KORAX_FIRST_TELEPORT_TID = 248; 49const int KORAX_TELEPORT_TID = 249; 50 51const float KORAX_MISSILE_DELTA_ANGLE = 85.0; 52const float KORAX_ARM_EXTENSION_SHORT = 40.0; 53const float KORAX_ARM_EXTENSION_LONG = 55.0; 54 55const float KORAX_ARM1_HEIGHT = 108.0; 56const float KORAX_ARM2_HEIGHT = 82.0; 57const float KORAX_ARM3_HEIGHT = 54.0; 58const float KORAX_ARM4_HEIGHT = 104.0; 59const float KORAX_ARM5_HEIGHT = 86.0; 60const float KORAX_ARM6_HEIGHT = 53.0; 61 62const float KORAX_COMMAND_HEIGHT = 120.0; 63const float KORAX_COMMAND_OFFSET = 27.0; 64 65const float KORAX_BOLT_HEIGHT = 48.0; 66const int KORAX_BOLT_LIFETIME = 3; 67 68//========================================================================== 69// 70// A_SmBounce 71// 72//========================================================================== 73 74final void A_SmBounce() 75{ 76 // give some more momentum (x,y,&z) 77 Origin.z = FloorZ + 1.0; 78 Velocity.z = (2.0 + Random() * 4.0) * 35.0; 79 Velocity.x = Random() * 3.0 * 35.0; 80 Velocity.y = Random() * 3.0 * 35.0; 81} 82 83//========================================================================== 84// 85// A_IceGuyMissileExplode 86// 87//========================================================================== 88 89final void A_IceGuyMissileExplode() 90{ 91 EntityEx mo; 92 int i; 93 94 for (i = 0; i < 8; i++) 95 { 96 mo = SpawnMissileAngle(IceGuyFX2, itof(i) * 45.0, -0.3 * 35.0, 3.0); 97 if (mo) 98 { 99 mo.Target = Target; 100 } 101 } 102} 103 104//========================================================================== 105// 106// A_SerpentHeadCheck 107// 108//========================================================================== 109 110final void A_SerpentHeadCheck() 111{ 112 if (Origin.z <= FloorZ) 113 { 114 if (GetFloorType()->bLiquid) 115 { 116 HitFloorType(); 117 SetState(none); 118 } 119 else 120 { 121 SetState(FindState('Death')); 122 } 123 } 124} 125 126//========================================================================== 127// 128// A_FloatGib 129// 130//========================================================================== 131 132final void A_FloatGib() 133{ 134 FloorClip -= 1.0; 135} 136 137//========================================================================== 138// 139// A_SinkGib 140// 141//========================================================================== 142 143final void A_SinkGib() 144{ 145 FloorClip += 1.0; 146} 147 148//========================================================================== 149// 150// A_DelayGib 151// 152//========================================================================== 153 154final void A_DelayGib() 155{ 156 StateTime -= Random() * 2.0; 157} 158 159//========================================================================== 160// 161// A_WraithFX2 162// 163// Spawns sparkle tail of missile. 164// 165//========================================================================== 166 167final void A_WraithFX2() 168{ 169 Actor A; 170 float angle; 171 int i; 172 173 for (i = 0; i < 2; i++) 174 { 175 A = Spawn(WraithFX2, Origin); 176 if (A) 177 { 178 if (Random() < 0.5) 179 { 180 angle = AngleMod360(Angles.yaw + Random() * 90.0); 181 } 182 else 183 { 184 angle = AngleMod360(Angles.yaw - Random() * 90.0); 185 } 186 A.Velocity.z = 0.0; 187 A.Velocity.x = (Random() / 2.0 + 1.0) * cos(angle) * 35.0; 188 A.Velocity.y = (Random() / 2.0 + 1.0) * sin(angle) * 35.0; 189 A.Target = self; 190 A.FloorClip = 10.0; 191 } 192 } 193} 194 195//============================================================================ 196// 197// A_DragonFX2 198// 199//============================================================================ 200 201final void A_DragonFX2() 202{ 203 Actor A; 204 int i; 205 float delay; 206 207 delay = 0.5 + Random(); 208 for (i = 1 + (P_Random() & 3); i; i--) 209 { 210 A = Spawn(DragonExplosion, Origin + vector((Random() - 0.5) * 64.0, 211 (Random() - 0.5) * 64.0, (Random() - 0.5) * 16.0)); 212 if (A) 213 { 214 A.StateTime = delay + Random() * 0.2 * itof(i); 215 A.Target = Target; 216 } 217 } 218} 219 220//============================================================================ 221// 222// A_SorcSpinBalls 223// 224// Spawn spinning balls above head - actor is sorcerer. 225// 226//============================================================================ 227 228final void A_SorcSpinBalls() 229{ 230 Actor ball; 231 TVec org; 232 233 // To prevent spawning balls more than once. 234 IdleState = GetStatePlus(IdleState, 2); 235 236 A_SlowBalls(); 237 Args[0] = 0; // Currently no defense 238 Args[3] = SORC_NORMAL; 239 Args[4] = SORCBALL_INITIAL_SPEED; // Initial orbit speed 240 Special1f = 1.0; 241 org = Origin; 242 org.z = Origin.z - FloorClip + Height; 243 244 ball = Spawn(SorcBall1, org,,, false); 245 if (ball) 246 { 247 ball.Target = self; 248 ball.Special2 = SORCFX4_RAPIDFIRE_TIME; 249 } 250 ball = Spawn(SorcBall2, org,,, false); 251 if (ball) 252 ball.Target = self; 253 ball = Spawn(SorcBall3, org,,, false); 254 if (ball) 255 ball.Target = self; 256} 257 258//========================================================================== 259// 260// A_SorcFX1Seek 261// 262// Yellow spell - offense 263// 264//========================================================================== 265 266final void A_SorcFX1Seek() 267{ 268 if (Args[4]-- <= 0) 269 { 270 if (Args[3]-- <= 0) 271 { 272 SetState(FindState('Death')); 273 PlaySound('SorcererHeadScream', CHAN_VOICE, 1.0, ATTN_NONE); 274 } 275 else 276 { 277 Args[4] = BOUNCE_TIME_UNIT; 278 } 279 } 280 281 SeekerMissile(2.0, 6.0); 282} 283 284//========================================================================== 285// Blue spell - defense 286//========================================================================== 287// 288// FX2 Variables 289// Special1f current angle 290// Args[0] 0 = CW, 1 = CCW 291//========================================================================== 292 293//========================================================================== 294// 295// A_SorcFX2Split 296// 297// Split ball in two 298// 299//========================================================================== 300 301final void A_SorcFX2Split() 302{ 303 Actor A; 304 305 A = Spawn(SorcFX2, Origin,,, false); 306 if (A) 307 { 308 A.Target = Target; 309 A.Args[0] = 0; // CW 310 A.Special1f = Angles.yaw; // Set angle 311 A.SetState(A.FindState('Orbit')); 312 } 313 A = Spawn(SorcFX2, Origin,,, false); 314 if (A) 315 { 316 A.Target = Target; 317 A.Args[0] = 1; // CCW 318 A.Special1f = Angles.yaw; // Set angle 319 A.SetState(A.FindState('Orbit')); 320 } 321 SetState(none); 322} 323 324//========================================================================== 325// 326// A_SorcFX2Orbit 327// 328// Orbit FX2 about sorcerer 329// 330//========================================================================== 331 332final void A_SorcFX2Orbit() 333{ 334 float angle; 335 float x, y, z; 336 float dist = Target.Radius; 337 338 if ((Target.Health <= 0) || // Sorcerer is dead 339 (!Target.Args[0])) // Time expired 340 { 341 SetState(FindState('Death')); 342 Target.Args[0] = 0; 343 Actor(Target).bReflective = false; 344 Actor(Target).bInvulnerable = false; 345 } 346 347 if (Args[0] && (Target.Args[0]-- <= 0)) // Time expired 348 { 349 SetState(FindState('Death')); 350 Target.Args[0] = 0; 351 Actor(Target).bReflective = false; 352 } 353 354 // Move to new position based on angle 355 if (Args[0]) // Counter clock-wise 356 { 357 Special1f = AngleMod360(Special1f + 10.0); 358 angle = Special1f; 359 x = Target.Origin.x + dist * cos(angle); 360 y = Target.Origin.y + dist * sin(angle); 361 z = Target.Origin.z - Target.FloorClip + SORC_DEFENSE_HEIGHT; 362 z += 15.0 * cos(angle); 363 // Spawn trailer 364 Spawn(SorcFX2T1, vector(x, y, z)); 365 } 366 else // Clock wise 367 { 368 Special1f = AngleMod360(Special1f - 10.0); 369 angle = Special1f; 370 x = Target.Origin.x + dist * cos(angle); 371 y = Target.Origin.y + dist * sin(angle); 372 z = Target.Origin.z - Target.FloorClip + SORC_DEFENSE_HEIGHT; 373 z += 20.0 * sin(angle); 374 // Spawn trailer 375 Spawn(SorcFX2T1, vector(x, y, z)); 376 } 377 378 Origin.x = x; 379 Origin.y = y; 380 Origin.z = z; 381} 382 383//========================================================================== 384// 385// A_SorcFX4Check 386// 387// FX4 - rapid fire balls 388// 389//========================================================================== 390 391final void A_SorcFX4Check() 392{ 393 if (Special2-- <= 0) 394 { 395 SetState(FindState('Death')); 396 } 397} 398 399//========================================================================== 400// 401// A_KBoltRaise 402// 403//========================================================================== 404 405final void A_KBoltRaise() 406{ 407 float z; 408 409 // Spawn a child upward 410 z = Origin.z + KORAX_BOLT_HEIGHT; 411 412 if (z + KORAX_BOLT_HEIGHT < CeilingZ) 413 { 414 Actor A = Spawn(KoraxBolt, vector(Origin.x, Origin.y, z)); 415 A.Special1 = KORAX_BOLT_LIFETIME; 416 } 417 else 418 { 419 // Maybe cap it off here 420 } 421} 422 423//========================================================================== 424// 425// A_KBolt 426// 427//========================================================================== 428 429final void A_KBolt() 430{ 431 // Countdown lifetime 432 if (Special1-- <= 0) 433 { 434 SetState(none); 435 } 436} 437 438//========================================================================== 439// 440// A_KSpiritSeeker 441// 442//========================================================================== 443 444final void A_KSpiritSeeker(float thresh, float turnMax) 445{ 446 int dir; 447 float dist; 448 float delta; 449 float angle; 450 float newZ; 451 float deltaZ; 452 453 if (Tracer == none) 454 { 455 return; 456 } 457 dir = FaceActor(Actor(Tracer), delta); 458 if (delta > thresh) 459 { 460 delta /= 2.0; 461 if (delta > turnMax) 462 { 463 delta = turnMax; 464 } 465 } 466 if (dir) 467 { 468 // Turn clockwise 469 Angles.yaw = AngleMod360(Angles.yaw + delta); 470 } 471 else 472 { 473 // Turn counter clockwise 474 Angles.yaw = AngleMod360(Angles.yaw - delta); 475 } 476 angle = Angles.yaw; 477 Velocity.x = Speed * cos(angle); 478 Velocity.y = Speed * sin(angle); 479 480 if (!(XLevel.TicTime & 15) || 481 Origin.z > Tracer.Origin.z + Tracer.Height || 482 Origin.z + Height < Tracer.Origin.z) 483 { 484 newZ = Tracer.Origin.z + Random() * Tracer.Height; 485 deltaZ = newZ - Origin.z; 486 if (fabs(deltaZ) > 15.0) 487 { 488 if (deltaZ > 0.0) 489 { 490 deltaZ = 15.0; 491 } 492 else 493 { 494 deltaZ = -15.0; 495 } 496 } 497 dist = DistTo2(Tracer); 498 dist = dist / Speed; 499 if (dist < 1.0) 500 { 501 dist = 1.0; 502 } 503 Velocity.z = deltaZ / dist; 504 } 505 return; 506} 507 508//========================================================================== 509// 510// A_KSpiritWeave 511// 512//========================================================================== 513 514final void A_KSpiritWeave() 515{ 516 float newX, newY; 517 float angle; 518 519 angle = AngleMod360(Angles.yaw + 90.0); 520 int WeaveXY = Special2 >> 16; 521 int WeaveZ = Special2 & 0xffff; 522 newX = Origin.x - cos(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0; 523 newY = Origin.y - sin(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0; 524 WeaveXY = (WeaveXY + (P_Random() % 5)) & 63; 525 newX += cos(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0; 526 newY += sin(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0; 527 TryMove(vector(newX, newY, Origin.z), false); 528 Origin.z -= Level.Game.FloatBobOffsets[WeaveZ] * 2.0; 529 WeaveZ = (WeaveZ + (P_Random() % 5)) & 63; 530 Origin.z += Level.Game.FloatBobOffsets[WeaveZ] * 2.0; 531 Special2 = (WeaveXY << 16) | (WeaveZ & 0xffff); 532} 533 534//========================================================================== 535// 536// A_KSpiritRoam 537// 538//========================================================================== 539 540final void A_KSpiritRoam() 541{ 542 if (Special1-- <= 0) 543 { 544 PlaySound('SpiritDie', CHAN_VOICE); 545 SetState(FindState('Death')); 546 } 547 else 548 { 549 if (Tracer) 550 { 551 A_KSpiritSeeker(10.0, 20.0); 552 } 553 A_KSpiritWeave(); 554 if (Random() < 0.1953125) 555 { 556 PlaySound('SpiritActive', CHAN_VOICE, 1.0, ATTN_NONE); 557 } 558 } 559} 560 561//========================================================================== 562// 563// A_CFlameRotate 564// 565//========================================================================== 566 567final void A_CFlameRotate() 568{ 569 float an; 570 571 an = AngleMod360(Angles.yaw + 90.0); 572 Velocity.x = Special1f + 2.0 * 35.0 * cos(an); 573 Velocity.y = Special2f + 2.0 * 35.0 * sin(an); 574 Angles.yaw = AngleMod360(Angles.yaw + 90.0 / 15.0); 575} 576 577//============================================================================ 578// 579// CHolyTailFollow 580// 581//============================================================================ 582 583final void CHolyTailFollow(float dist) 584{ 585 EntityEx child; 586 float an; 587 float oldDistance, newDistance; 588 589 child = Tracer; 590 if (child) 591 { 592 an = atan2(child.Origin.y - Origin.y, 593 Origin.x - child.Origin.x); 594 oldDistance = DistTo(child); 595 if (child.TryMove(vector(Origin.x + dist * cos(an), 596 Origin.y + dist * sin(an), child.Origin.z), false)) 597 { 598 newDistance = DistTo2(child) - 1.0; 599 if (oldDistance < 1.0) 600 { 601 if (child.Origin.z < Origin.z) 602 { 603 child.Origin.z = Origin.z - dist; 604 } 605 else 606 { 607 child.Origin.z = Origin.z + dist; 608 } 609 } 610 else 611 { 612 child.Origin.z = Origin.z + newDistance / oldDistance * 613 (child.Origin.z - Origin.z); 614 } 615 } 616 Actor(child).CHolyTailFollow(dist - 1.0); 617 } 618} 619 620//============================================================================ 621// 622// CHolyTailRemove 623// 624//============================================================================ 625 626final void CHolyTailRemove() 627{ 628 if (Tracer) 629 { 630 Actor(Tracer).CHolyTailRemove(); 631 } 632 Destroy(); 633} 634 635//============================================================================ 636// 637// A_CHolyTail 638// 639//============================================================================ 640 641final void A_CHolyTail() 642{ 643 EntityEx parent; 644 645 parent = Target; 646 647 if (parent) 648 { 649 if (!parent.bMissile) 650 { 651 // Ghost removed, so remove all tail parts 652 CHolyTailRemove(); 653 return; 654 } 655 else if (TryMove(vector( 656 parent.Origin.x - 14.0 * cos(parent.Angles.yaw), 657 parent.Origin.y - 14.0 * sin(parent.Angles.yaw), Origin.z), false)) 658 { 659 Origin.z = parent.Origin.z - 5.0; 660 } 661 CHolyTailFollow(10.0); 662 } 663} 664 665//=========================================================================== 666// 667// Bat Spawner Variables 668// Special1 frequency counter 669// Args[0] frequency of spawn (1=fastest, 10=slowest) 670// Args[1] spread angle (0..255) 671// Args[2] 672// Args[3] duration of bats (in octics) 673// Args[4] turn amount per move (in degrees) 674// 675// Bat Variables 676// Special2 - lifetime counter 677// Args[4] - turn amount per move (in degrees) 678// 679//=========================================================================== 680 681//=========================================================================== 682// 683// A_BatSpawnInit 684// 685//=========================================================================== 686 687final void A_BatSpawnInit() 688{ 689 Special1 = 0; // Frequency count 690} 691 692//=========================================================================== 693// 694// A_BatSpawn 695// 696//=========================================================================== 697 698final void A_BatSpawn() 699{ 700 EntityEx mo; 701 float delta; 702 float angle; 703 704 // Countdown until next spawn 705 if (Special1-- > 0) 706 { 707 return; 708 } 709 Special1 = Args[0]; // Reset frequency count 710 711 delta = itof(Args[1]); 712 if (delta == 0.0) 713 delta = 1.0; 714 angle = AngleMod360(Angles.yaw + 715 delta * (Random() - 0.5) * 360.0 / 256.0); 716 mo = SpawnMissileAngle(Bat, angle, 0.0); 717 if (mo) 718 { 719 mo.Args[0] = P_Random() & 63; // floatbob index 720 mo.Args[4] = Args[4]; // turn degrees 721 mo.Special2 = Args[3] << 3; // Set lifetime 722 mo.Target = self; 723 } 724} 725 726//=========================================================================== 727// 728// A_BatMove 729// 730//=========================================================================== 731 732final void A_BatMove() 733{ 734 float newangle; 735 float newSpeed; 736 737 if (Special2 < 0) 738 { 739 SetState(FindState('Death')); 740 } 741 Special2 -= 2; // Called every 2 tics 742 743 if (Random() < 0.5) 744 { 745 newangle = AngleMod360(Angles.yaw + itof(Args[4])); 746 } 747 else 748 { 749 newangle = AngleMod360(Angles.yaw - itof(Args[4])); 750 } 751 752 // Adjust momentum vector to new direction 753 newSpeed = Speed * Random() * 4.0; 754 Velocity.x = newSpeed * cos(newangle); 755 Velocity.y = newSpeed * sin(newangle); 756 757 if (Random() < 0.05859375) 758 PlaySound('BatScream', CHAN_VOICE); 759 760 // Handle Z movement 761 Origin.z = Target.Origin.z + 2.0 * Level.Game.FloatBobOffsets[Args[0]]; 762 Args[0] = (Args[0] + 3) & 63; 763} 764 765//========================================================================== 766// 767// A_TimeBomb 768// 769//========================================================================== 770 771final void A_TimeBomb() 772{ 773 // Time Bombs 774 Origin.z += 32.0; 775 RenderStyle = STYLE_Normal; 776 Alpha = 1.0; 777 A_Explode(); 778} 779 780//=========================================================================== 781// 782// A_CheckThrowBomb 783// 784//=========================================================================== 785 786final void A_CheckThrowBomb() 787{ 788 if (!--Health) 789 { 790 SetState(FindState('Death')); 791 } 792} 793 794//=========================================================================== 795// 796// A_CheckThrowBomb2 797// 798//=========================================================================== 799 800final void A_CheckThrowBomb2() 801{ 802 if (fabs(Velocity.x) < 1.5 * 35.0 && 803 fabs(Velocity.y) < 1.5 * 35.0 && 804 Velocity.z < 2.0 * 35.0) 805 { 806 SetState(GetStatePlus(IdleState, 6, true)); 807 Origin.z = FloorZ; 808 Velocity.z = 0.0; 809 BounceType = BOUNCE_None; 810 bMissile = false; 811 } 812 A_CheckThrowBomb(); 813} 814 815//========================================================================== 816// 817// Fog Variables: 818// 819// Special1 Counter for spawn frequency 820// Special2 Index into floatbob table 821// args[0] Speed (0..10) of fog 822// args[1] Angle of spread (0..128) 823// args[2] Frequency of spawn (1..10) 824// args[3] Lifetime countdown 825// args[4] Boolean: fog moving? 826// 827//========================================================================== 828 829//========================================================================== 830// 831// A_FogSpawn 832// 833//========================================================================== 834 835final void A_FogSpawn() 836{ 837 Actor A; 838 float delta; 839 840 if (Special1-- > 0) 841 { 842 return; 843 } 844 845 Special1 = Args[2]; // Reset frequency count 846 847 switch (P_Random() % 3) 848 { 849 case 0: 850 A = Spawn(FogPatchSmall, Origin); 851 break; 852 case 1: 853 A = Spawn(FogPatchMedium, Origin); 854 break; 855 case 2: 856 A = Spawn(FogPatchLarge, Origin); 857 break; 858 } 859 860 if (A) 861 { 862 delta = itof(Args[1]); 863 if (delta == 0.0) 864 delta = 1.0; 865 A.Angles.yaw = AngleMod360(Angles.yaw + 866 ((Random() * delta) - (delta * 0.5)) * 360.0 / 256.0); 867 A.Target = self; 868 if (Args[0] < 1) 869 Args[0] = 1; 870 A.Args[0] = (P_Random() % (Args[0])) + 1; // P_Random speed 871 A.Args[3] = Args[3]; // Set lifetime 872 A.Args[4] = 1; // Set to moving 873 A.Special2 = P_Random() & 63; 874 } 875} 876 877//========================================================================== 878// 879// A_FogMove 880// 881//========================================================================== 882 883final void A_FogMove() 884{ 885 float speed; 886 float angle; 887 int weaveindex; 888 889 if (!Args[4]) 890 return; 891 892 if (Args[3]-- <= 0) 893 { 894 SetState(FindState('Death')); 895 return; 896 } 897 898 if ((Args[3] % 4) == 0) 899 { 900 Origin.z += Level.Game.FloatBobOffsets[Special2] / 2.0; 901 Special2 = (Special2 + 1) & 63; 902 } 903 904 speed = itof(Args[0]); 905 angle = Angles.yaw; 906 Velocity.x = speed * cos(angle) * 35.0; 907 Velocity.y = speed * sin(angle) * 35.0; 908} 909 910//========================================================================== 911// 912// A_PotteryChooseBit 913// 914//========================================================================== 915 916final void A_PotteryChooseBit() 917{ 918 Special1 = 1 + 2 * (P_Random() % 5); 919 SetState(GetStatePlus(FindState('Death'), Special1, true)); 920 StateTime = 8.0 + Random() * 16.0; 921} 922 923//========================================================================== 924// 925// A_PotteryCheck 926// 927//========================================================================== 928 929final void A_PotteryCheck() 930{ 931 int i; 932 Actor pmo; 933 934 for (i = 0; i < MAXPLAYERS; i++) 935 { 936 if (!Level.Game.Players[i] || !Level.Game.Players[i].bSpawned) 937 { 938 continue; 939 } 940 pmo = Actor(Level.Game.Players[i].MO); 941 if (CanSee(pmo) && (fabs(AngleMod180(atan2(Origin.y - pmo.Origin.y, 942 Origin.x - pmo.Origin.x) - pmo.Angles.yaw)) <= 45.0)) 943 { 944 // Previous state (pottery bit waiting state) 945 SetState(GetStatePlus(FindState('Death'), Special1, true)); 946 return; 947 } 948 } 949} 950 951//============================================================================ 952// 953// A_CorpseExplode 954// 955//============================================================================ 956 957final void A_CorpseExplode() 958{ 959 Actor A; 960 int i; 961 962 for (i = (P_Random() & 3) + 3; i; i--) 963 { 964 A = Spawn(CorpseBit, Origin); 965 A.SetState(GetStatePlus(A.IdleState, P_Random() % 3, true)); 966 if (A) 967 { 968 A.Velocity.x = (Random() - Random()) * 4.0 * 35.0; 969 A.Velocity.y = (Random() - Random()) * 4.0 * 35.0; 970 A.Velocity.z = (Random() * 8.0 + 5.0) * (3.0 / 4.0) * 35.0; 971 } 972 } 973 // Spawn a skull 974 A = Spawn(CorpseBit, Origin); 975 A.SetState(GetStatePlus(A.IdleState, 3, true)); 976 if (A) 977 { 978 A.Velocity.x = (Random() - Random()) * 4.0 * 35.0; 979 A.Velocity.y = (Random() - Random()) * 4.0 * 35.0; 980 A.Velocity.z = (Random() * 8.0 + 5.0) * (3.0 / 4.0) * 35.0; 981 A.PlaySound('FireDemonDeath', CHAN_VOICE); 982 } 983 Destroy(); 984} 985 986//========================================================================== 987// 988// A_LeafSpawn 989// 990//========================================================================== 991 992final void A_LeafSpawn() 993{ 994 Actor A; 995 int i; 996 997 for (i = (P_Random() & 3) + 1; i; i--) 998 { 999 A = Spawn(P_Random() & 1 ? class<Actor>(Leaf2) : class<Actor>(Leaf1), 1000 Origin + vector((Random() - Random()) * 64.0, 1001 (Random() - Random()) * 64.0, Random() * 64.0)); 1002 if (A) 1003 { 1004 A.Thrust(Angles.yaw, Random() * 2.0 + 3.0); 1005 A.Target = self; 1006 } 1007 } 1008} 1009 1010//========================================================================== 1011// 1012// A_LeafThrust 1013// 1014//========================================================================== 1015 1016final void A_LeafThrust() 1017{ 1018 if (Random() > 0.375) 1019 { 1020 return; 1021 } 1022 Velocity.z += (Random() * 2.0 + 1.0) * 35.0; 1023} 1024 1025//========================================================================== 1026// 1027// A_LeafCheck 1028// 1029//========================================================================== 1030 1031final void A_LeafCheck() 1032{ 1033 Special1++; 1034 if (Special1 >= 20) 1035 { 1036 SetState(none); 1037 return; 1038 } 1039 if (Random() > 0.25) 1040 { 1041 if (!Velocity.x && !Velocity.y) 1042 { 1043 Thrust(Target.Angles.yaw, Random() * 2.0 + 1.0); 1044 } 1045 return; 1046 } 1047 SetState(GetStatePlus(IdleState, 7)); 1048 Velocity.z = (Random() * 2.0 + 1.0) * 35.0; 1049 Thrust(Target.Angles.yaw, Random() * 2.0 + 2.0); 1050 bMissile = true; 1051} 1052 1053//=========================================================================== 1054// 1055// A_SoAExplode 1056// 1057// Suit of Armor Explode 1058// 1059//=========================================================================== 1060 1061final void A_SoAExplode() 1062{ 1063 EntityEx A; 1064 int i; 1065 1066 for (i = 0; i < 10; i++) 1067 { 1068 A = Spawn(ZArmorChunk, Origin + vector((Random() - 0.5) * 16.0, 1069 (Random() - 0.5) * 16.0, Random() * Height)); 1070 A.SetState(GetStatePlus(A.IdleState, i, true)); 1071 if (A) 1072 { 1073 A.Velocity.x = (Random() - Random()) * 4.0 * 35.0; 1074 A.Velocity.y = (Random() - Random()) * 4.0 * 35.0; 1075 A.Velocity.z = (Random() * 8.0 + 5.0) * 35.0; 1076 } 1077 } 1078 if (Args[0]) 1079 { 1080 // Spawn an item. Don't spawn monsters it they are disabled. 1081 class<EntityEx> Cls = class<EntityEx>(FindClassFromScriptId(Args[0], 1082 LineSpecialGameInfo(Level.Game).GameFilterFlag)); 1083 if (Cls && (!Level.Game.nomonsters || !Cls.default.bMonster)) 1084 { 1085 Spawn(Cls, Origin, vector(0.0, 0.0, 0.0)); 1086 } 1087 } 1088 A.PlaySound('SuitofArmorBreak', CHAN_VOICE); 1089 Destroy(); 1090} 1091 1092// 1093// Fire Demon AI 1094// 1095 1096// Special1 Index into floatbob 1097// Special2 Whether strafing or not 1098 1099//============================================================================ 1100// 1101// A_FiredSpawnRock 1102// 1103//============================================================================ 1104 1105final void A_FiredSpawnRock() 1106{ 1107 Actor mo; 1108 TVec rockOrg; 1109 class<Actor> rtype; 1110 1111 switch (P_Random() % 5) 1112 { 1113 case 0: 1114 rtype = FireDemonRock1; 1115 break; 1116 case 1: 1117 rtype = FireDemonRock2; 1118 break; 1119 case 2: 1120 rtype = FireDemonRock3; 1121 break; 1122 case 3: 1123 rtype = FireDemonRock4; 1124 break; 1125 case 4: 1126 rtype = FireDemonRock5; 1127 break; 1128 } 1129 1130 rockOrg.x = Origin.x + (Random() - 0.5) * 16.0; 1131 rockOrg.y = Origin.y + (Random() - 0.5) * 16.0; 1132 rockOrg.z = Origin.z + Random() * 8.0; 1133 mo = Spawn(rtype, rockOrg); 1134 if (mo) 1135 { 1136 mo.Target = self; 1137 mo.Velocity.x = (Random() - 0.5) * 4.0 * 35.0; 1138 mo.Velocity.y = (Random() - 0.5) * 4.0 * 35.0; 1139 mo.Velocity.z = Random() * 4.0 * 35.0; 1140 mo.Special1 = 2; // Number bounces 1141 } 1142 1143 // Initialise fire demon 1144 Special2 = 0; 1145 bJustAttacked = false; 1146} 1147 1148//============================================================================ 1149// 1150// A_FiredRocks 1151// 1152//============================================================================ 1153 1154final void A_FiredRocks() 1155{ 1156 A_FiredSpawnRock(); 1157 A_FiredSpawnRock(); 1158 A_FiredSpawnRock(); 1159 A_FiredSpawnRock(); 1160 A_FiredSpawnRock(); 1161} 1162 1163//============================================================================ 1164// 1165// A_FiredChase 1166// 1167//============================================================================ 1168 1169final void A_FiredChase() 1170{ 1171 float ang; 1172 float dist; 1173 1174 if (ReactionCount) 1175 ReactionCount--; 1176 if (Threshold) 1177 Threshold--; 1178 1179 // Float up and down 1180 Origin.z += Level.Game.FloatBobOffsets[Special1]; 1181 Special1 = (Special1 + 2) & 63; 1182 1183 1184 // Insure it stays above certain height 1185 if (Origin.z < FloorZ + 64.0) 1186 { 1187 Origin.z += 2.0; 1188 } 1189 1190 if (!Target || !Target.bShootable) 1191 { 1192 // Invalid enemy 1193 LookForPlayers(true); 1194 return; 1195 } 1196 1197 // Strafe 1198 if (Special2 > 0) 1199 { 1200 Special2--; 1201 } 1202 else 1203 { 1204 Special2 = 0; 1205 Velocity.x = 0.0; 1206 Velocity.y = 0.0; 1207 dist = DistTo(Target); 1208 if (dist < FIREDEMON_ATTACK_RANGE) 1209 { 1210 if (P_Random() < 30) 1211 { 1212 ang = atan2(Target.Origin.y - Origin.y, 1213 Target.Origin.x - Origin.x); 1214 if (P_Random() < 128) 1215 ang = AngleMod360(ang + 90.0); 1216 else 1217 ang = AngleMod360(ang - 90.0); 1218 Velocity.x = 8.0 * cos(ang) * 35.0; 1219 Velocity.y = 8.0 * sin(ang) * 35.0; 1220 Special2 = 3; // strafe time 1221 } 1222 } 1223 } 1224 1225 FaceMovementDirection(); 1226 1227 // Normal movement 1228 if (!Special2) 1229 { 1230 if (--MoveCount < 0 || !StepMove()) 1231 { 1232 NewChaseDir(); 1233 } 1234 } 1235 1236 // Do missile attack 1237 if (!bJustAttacked) 1238 { 1239 if (CheckMissileRange() && (P_Random() < 20)) 1240 { 1241 SetState(MissileState); 1242 bJustAttacked = true; 1243 return; 1244 } 1245 } 1246 else 1247 { 1248 bJustAttacked = false; 1249 } 1250 1251 // make active sound 1252 if (ActiveSound && P_Random() < 3) 1253 { 1254 PlaySound(ActiveSound, CHAN_VOICE); 1255 } 1256} 1257 1258//============================================================================ 1259// 1260// A_FiredAttack 1261// 1262//============================================================================ 1263 1264final void A_FiredAttack() 1265{ 1266 EntityEx mo; 1267 1268 mo = SpawnMissile(Target, FireDemonMissile); 1269 if (mo) 1270 { 1271 PlaySound('FireDemonAttack', CHAN_WEAPON); 1272 } 1273} 1274 1275//============================================================================ 1276// 1277// A_FiredSplotch 1278// 1279//============================================================================ 1280 1281final void A_FiredSplotch() 1282{ 1283 Actor A; 1284 1285 A = Spawn(FireDemonSplotch1, Origin); 1286 if (A) 1287 { 1288 A.Velocity.x = (Random() - 0.5) * 8.0 * 35.0; 1289 A.Velocity.y = (Random() - 0.5) * 8.0 * 35.0; 1290 A.Velocity.z = (3.0 + Random() * 4.0) * 35.0; 1291 } 1292 A = Spawn(FireDemonSplotch2, Origin); 1293 if (A) 1294 { 1295 A.Velocity.x = (Random() - 0.5) * 8.0 * 35.0; 1296 A.Velocity.y = (Random() - 0.5) * 8.0 * 35.0; 1297 A.Velocity.z = (3.0 + Random() * 4.0) * 35.0; 1298 } 1299} 1300 1301//========================================================================== 1302// 1303// A_IceGuyLook 1304// 1305//========================================================================== 1306 1307final void A_IceGuyLook() 1308{ 1309 float dist; 1310 float an; 1311 class<Actor> tp; 1312 1313 A_Look(); 1314 if (Random() < 0.25) 1315 { 1316 dist = Radius * (Random() * 2.0 - 1.0); 1317 an = AngleMod360(Angles.yaw + 90.0); 1318 1319 if (Random() < 0.5) 1320 tp = IceGuyWisp2; 1321 else 1322 tp = IceGuyWisp1; 1323 Spawn(tp, Origin + vector(dist * cos(an), dist * sin(an), 60.0)); 1324 } 1325} 1326 1327//========================================================================== 1328// 1329// A_IceGuyChase 1330// 1331//========================================================================== 1332 1333final void A_IceGuyChase() 1334{ 1335 float dist; 1336 float an; 1337 Actor A; 1338 class<Actor> tp; 1339 1340 A_Chase(); 1341 if (Random() < 0.5) 1342 { 1343 dist = Radius * (Random() * 2.0 - 1.0); 1344 an = AngleMod360(Angles.yaw + 90.0); 1345 1346 if (Random() < 0.5) 1347 tp = IceGuyWisp2; 1348 else 1349 tp = IceGuyWisp1; 1350 A = Spawn(tp, Origin + vector(dist * cos(an), dist * sin(an), 60.0)); 1351 if (A) 1352 { 1353 A.Velocity = Velocity; 1354 A.Target = self; 1355 } 1356 } 1357} 1358 1359//========================================================================== 1360// 1361// A_IceGuyAttack 1362// 1363//========================================================================== 1364 1365final void A_IceGuyAttack() 1366{ 1367 float an; 1368 1369 if (!Target) 1370 { 1371 return; 1372 } 1373 an = AngleMod360(Angles.yaw + 90.0); 1374 SpawnMissileXYZ(Origin + vector(Radius / 2.0 * cos(an), 1375 Radius / 2.0 * sin(an), 40.0), Target, IceGuyFX); 1376 1377 an = AngleMod360(Angles.yaw - 90.0); 1378 SpawnMissileXYZ(Origin + vector(Radius / 2.0 * cos(an), 1379 Radius / 2.0 * sin(an), 40.0), Target, IceGuyFX); 1380 1381 PlaySound(AttackSound, CHAN_WEAPON); 1382} 1383 1384// Special1 Number of blurs/missiles to spawn 1385// Special2 Index into floatbob 1386 1387//========================================================================== 1388// 1389// A_BishopChase 1390// 1391//========================================================================== 1392 1393final void A_BishopChase() 1394{ 1395 Origin.z -= Level.Game.FloatBobOffsets[Special2] / 2.0; 1396 Special2 = (Special2 + 4) & 63; 1397 Origin.z += Level.Game.FloatBobOffsets[Special2] / 2.0; 1398} 1399 1400//========================================================================== 1401// 1402// A_BishopDecide 1403// 1404//========================================================================== 1405 1406final void A_BishopDecide() 1407{ 1408 if (P_Random() < 220) 1409 { 1410 return; 1411 } 1412 else 1413 { 1414 SetState(FindState('Blur')); 1415 } 1416} 1417 1418//========================================================================== 1419// 1420// A_BishopDoBlur 1421// 1422//========================================================================== 1423 1424final void A_BishopDoBlur() 1425{ 1426 Special1 = (P_Random() & 3) + 3; // P_Random number of blurs 1427 if (P_Random() < 120) 1428 { 1429 Thrust(AngleMod360(Angles.yaw + 90.0), 11.0); 1430 } 1431 else if (P_Random() > 125) 1432 { 1433 Thrust(AngleMod360(Angles.yaw - 90.0), 11.0); 1434 } 1435 else 1436 { 1437 // Thrust forward 1438 Thrust(Angles.yaw, 11.0); 1439 } 1440 PlaySound('BishopBlur', CHAN_VOICE); 1441} 1442 1443//========================================================================== 1444// 1445// A_BishopSpawnBlur 1446// 1447//========================================================================== 1448 1449final void A_BishopSpawnBlur() 1450{ 1451 Actor blur; 1452 1453 if (!--Special1) 1454 { 1455 Velocity.x = 0.0; 1456 Velocity.y = 0.0; 1457 if (P_Random() > 96) 1458 { 1459 SetState(SeeState); 1460 } 1461 else 1462 { 1463 SetState(MissileState); 1464 } 1465 } 1466 blur = Spawn(BishopBlur, Origin); 1467 if (blur) 1468 { 1469 blur.Angles = Angles; 1470 } 1471} 1472 1473//========================================================================== 1474// 1475// A_BishopAttack 1476// 1477//========================================================================== 1478 1479final void A_BishopAttack() 1480{ 1481 if (!Target) 1482 { 1483 return; 1484 } 1485 PlaySound(AttackSound, CHAN_WEAPON); 1486 if (CheckMeleeRange()) 1487 { 1488 Target.Damage(self, self, HITDICE(4)); 1489 return; 1490 } 1491 Special1 = (P_Random() & 3) + 5; 1492} 1493 1494//========================================================================== 1495// 1496// A_BishopAttack2 1497// 1498// Spawns one of a string of bishop missiles 1499// 1500//========================================================================== 1501 1502final void A_BishopAttack2() 1503{ 1504 EntityEx mo; 1505 1506 if (!Target || !Special1) 1507 { 1508 Special1 = 0; 1509 SetState(SeeState); 1510 return; 1511 } 1512 mo = SpawnMissile(Target, BishopFX); 1513 if (mo) 1514 { 1515 mo.Tracer = Target; 1516 mo.Special2 = 16; 1517 } 1518 Special1--; 1519} 1520 1521//========================================================================== 1522// 1523// A_BishopPainBlur 1524// 1525//========================================================================== 1526 1527final void A_BishopPainBlur() 1528{ 1529 Actor blur; 1530 1531 if (P_Random() < 64) 1532 { 1533 SetState(FindState('Blur')); 1534 return; 1535 } 1536 blur = Spawn(BishopPainBlur, 1537 Origin + vector((Random() - Random()) * 16.0, 1538 (Random() - Random()) * 16.0, (Random() - Random()) * 8.0)); 1539 if (blur) 1540 { 1541 blur.Angles = Angles; 1542 } 1543} 1544 1545//========================================================================== 1546// 1547// A_SerpentHumpDecide 1548// 1549// Decided whether to hump up or to missile attack 1550// 1551//========================================================================== 1552 1553final void A_SerpentHumpDecide() 1554{ 1555 if (MissileState) 1556 { 1557 if (P_Random() > 30) 1558 { 1559 return; 1560 } 1561 if (P_Random() < 40) 1562 { 1563 // Missile attack 1564 SetState(MeleeState); 1565 return; 1566 } 1567 if (!CheckMeleeRange()) 1568 { 1569 // The hump shouldn't occur when within melee range 1570 if (P_Random() < 128) 1571 { 1572 SetState(MeleeState); 1573 } 1574 else 1575 { 1576 SetState(FindState('Hump')); 1577 PlaySound('SerpentActive', CHAN_VOICE); 1578 } 1579 } 1580 } 1581 else 1582 { 1583 if (P_Random() > 3) 1584 { 1585 return; 1586 } 1587 if (!CheckMeleeRange()) 1588 { 1589 // The hump shouldn't occur when within melee range 1590 SetState(FindState('Hump')); 1591 PlaySound('SerpentActive', CHAN_VOICE); 1592 } 1593 } 1594} 1595 1596//========================================================================== 1597// 1598// A_SerpentUnHide 1599// 1600//========================================================================== 1601 1602final void A_SerpentUnHide() 1603{ 1604 bInvisible = false; 1605 FloorClip = 24.0; 1606} 1607 1608//========================================================================== 1609// 1610// A_SerpentRaiseHump 1611// 1612// Raises the hump above the surface by raising the FloorClip level 1613// 1614//========================================================================== 1615 1616final void A_SerpentRaiseHump() 1617{ 1618 FloorClip -= 4.0; 1619} 1620 1621//========================================================================== 1622// 1623// A_SerpentLowerHump 1624// 1625//========================================================================== 1626 1627final void A_SerpentLowerHump() 1628{ 1629 FloorClip += 4.0; 1630} 1631 1632//========================================================================== 1633// 1634// A_SerpentHide 1635// 1636//========================================================================== 1637 1638final void A_SerpentHide() 1639{ 1640 bInvisible = true; 1641 FloorClip = 0.0; 1642} 1643 1644//========================================================================== 1645// 1646// A_SerpentCheckForAttack 1647// 1648//========================================================================== 1649 1650final void A_SerpentCheckForAttack() 1651{ 1652 if (!Target) 1653 { 1654 return; 1655 } 1656 if (MissileState && !CheckMeleeRange()) 1657 { 1658 SetState(FindState('Attack')); 1659 return; 1660 } 1661 if (CheckMeleeRange2()) 1662 { 1663 SetState(FindState('Walk')); 1664 } 1665 else if (CheckMeleeRange()) 1666 { 1667 if (P_Random() < 32) 1668 { 1669 SetState(FindState('Walk')); 1670 } 1671 else 1672 { 1673 SetState(FindState('Attack')); 1674 } 1675 } 1676} 1677 1678//========================================================================== 1679// 1680// A_SerpentChooseAttack 1681// 1682//========================================================================== 1683 1684final void A_SerpentChooseAttack() 1685{ 1686 if (MissileState) 1687 { 1688 if (!Target || CheckMeleeRange()) 1689 { 1690 return; 1691 } 1692 SetState(MissileState); 1693 } 1694} 1695 1696//========================================================================== 1697// 1698// A_SerpentMeleeAttack 1699// 1700//========================================================================== 1701 1702final void A_SerpentMeleeAttack() 1703{ 1704 if (!Target) 1705 { 1706 return; 1707 } 1708 if (CheckMeleeRange()) 1709 { 1710 Target.Damage(self, self, HITDICE(5)); 1711 PlaySound('SerpentMeleeHit', CHAN_WEAPON); 1712 } 1713 if (P_Random() < 96) 1714 { 1715 A_SerpentCheckForAttack(); 1716 } 1717} 1718 1719//========================================================================== 1720// 1721// A_SerpentSpawnGibs 1722// 1723//========================================================================== 1724 1725final void A_SerpentSpawnGibs() 1726{ 1727 Actor A; 1728 1729 A = Spawn(SerpentGib1, vector(Origin.x + (Random() - 0.5) * 16.0, 1730 Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0)); 1731 if (A) 1732 { 1733 A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0; 1734 A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0; 1735 A.FloorClip = 6.0; 1736 A.Translation = Translation; 1737 } 1738 A = Spawn(SerpentGib2, vector(Origin.x + (Random() - 0.5) * 16.0, 1739 Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0)); 1740 if (A) 1741 { 1742 A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0; 1743 A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0; 1744 A.FloorClip = 6.0; 1745 A.Translation = Translation; 1746 } 1747 A = Spawn(SerpentGib3, vector(Origin.x + (Random() - 0.5) * 16.0, 1748 Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0)); 1749 if (A) 1750 { 1751 A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0; 1752 A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0; 1753 A.FloorClip = 6.0; 1754 A.Translation = Translation; 1755 } 1756} 1757 1758// 1759// Wraith Variables 1760// 1761 1762// Special1 Internal index into floatbob 1763 1764//========================================================================== 1765// 1766// A_WraithInit 1767// 1768//========================================================================== 1769 1770final void A_WraithInit() 1771{ 1772 Origin.z += 48.0; 1773 Special1 = 0; // index into floatbob 1774} 1775 1776//========================================================================== 1777// 1778// A_WraithFX3 1779// 1780// Spawn an FX3 around the actor during attacks. 1781// 1782//========================================================================== 1783 1784final void A_WraithFX3() 1785{ 1786 Actor A; 1787 int numdropped = P_Random() % 15; 1788 int i; 1789 1790 for (i = 0; i < numdropped; i++) 1791 { 1792 A = Spawn(WraithFX3, Origin); 1793 if (A) 1794 { 1795 A.Origin.x += (Random() - 0.5) * 8.0; 1796 A.Origin.y += (Random() - 0.5) * 8.0; 1797 A.Origin.z += Random() * 4.0; 1798 A.Target = self; 1799 } 1800 } 1801} 1802 1803//========================================================================== 1804// 1805// A_WraithFX4 1806// 1807// Spawn an FX4 during movement. 1808// 1809//========================================================================== 1810 1811final void A_WraithFX4() 1812{ 1813 Actor mo; 1814 int chance = P_Random(); 1815 int spawn4, spawn5; 1816 1817 if (chance < 10) 1818 { 1819 spawn4 = true; 1820 spawn5 = false; 1821 } 1822 else if (chance < 20) 1823 { 1824 spawn4 = false; 1825 spawn5 = true; 1826 } 1827 else if (chance < 25) 1828 { 1829 spawn4 = true; 1830 spawn5 = true; 1831 } 1832 else 1833 { 1834 spawn4 = false; 1835 spawn5 = false; 1836 } 1837 1838 if (spawn4) 1839 { 1840 mo = Spawn(WraithFX4, Origin); 1841 if (mo) 1842 { 1843 mo.Origin.x += (Random() - 0.5) * 16.0; 1844 mo.Origin.y += (Random() - 0.5) * 16.0; 1845 mo.Origin.z += Random() * 4.0; 1846 mo.Target = self; 1847 } 1848 } 1849 if (spawn5) 1850 { 1851 mo = Spawn(WraithFX5, Origin); 1852 if (mo) 1853 { 1854 mo.Origin.x += (Random() - 0.5) * 8.0; 1855 mo.Origin.y += (Random() - 0.5) * 8.0; 1856 mo.Origin.z += Random() * 4.0; 1857 mo.Target = self; 1858 } 1859 } 1860} 1861 1862//========================================================================== 1863// 1864// A_WraithChase 1865// 1866//========================================================================== 1867 1868final void A_WraithChase() 1869{ 1870 Origin.z += Level.Game.FloatBobOffsets[Special1]; 1871 Special1 = (Special1 + 2) & 63; 1872// if (actor->FloorClip > 0) 1873// { 1874// SetState(S_WRAITH_RAISE2); 1875// return; 1876// } 1877 A_Chase(); 1878 A_WraithFX4(); 1879} 1880 1881//========================================================================== 1882// 1883// A_WraithMelee 1884// 1885//========================================================================== 1886 1887final void A_WraithMelee() 1888{ 1889 int amount; 1890 1891 // Steal health from enemy and give to player 1892 if (CheckMeleeRange() && (P_Random() < 220)) 1893 { 1894 amount = HITDICE(2); 1895 Target.Damage(self, self, amount); 1896 Health += amount; 1897 } 1898} 1899 1900//========================================================================== 1901// 1902// A_WraithRaiseInit 1903// 1904//========================================================================== 1905 1906final void A_WraithRaiseInit() 1907{ 1908 bInvisible = false; 1909 bDontBlast = false; 1910 bNonShootable = false; 1911 bShootable = true; 1912 bSolid = true; 1913 FloorClip = Height; 1914} 1915 1916//============================================================================ 1917// 1918// RaiseFromFloor 1919// 1920// Raise incrementally from the floor 1921// 1922//============================================================================ 1923 1924bool RaiseFromFloor() 1925{ 1926 bool done; 1927 1928 done = true; 1929 // Raise a mobj from the ground 1930 if (FloorClip > 0.0) 1931 { 1932 FloorClip -= 2.0; 1933 if (FloorClip <= 0.0) 1934 { 1935 FloorClip = 0.0; 1936 done = true; 1937 } 1938 else 1939 { 1940 done = false; 1941 } 1942 } 1943 return done; // Reached target height 1944} 1945 1946//========================================================================== 1947// 1948// A_WraithRaise 1949// 1950//========================================================================== 1951 1952final void A_WraithRaise() 1953{ 1954 if (RaiseFromFloor()) 1955 { 1956 // Reached it's target height 1957 bNoMorph = false; 1958 SetState(FindState('Chase')); 1959 PainChance = Wraith.default.PainChance; 1960 } 1961 1962 SpawnDirt(Radius); 1963} 1964 1965//========================================================================== 1966// 1967// A_DragonInitFlight 1968// 1969//========================================================================== 1970 1971final void A_DragonInitFlight() 1972{ 1973 Tracer = none; 1974 do 1975 { 1976 // find the first tid identical to the dragon's tid 1977 Tracer = Actor(Level.FindMobjFromTID(TID, Tracer)); 1978 if (!Tracer) 1979 { 1980 SetState(IdleState); 1981 return; 1982 } 1983 } 1984 while (Tracer == self); 1985 SetTID(0); 1986} 1987 1988//========================================================================== 1989// 1990// DragonSeek 1991// 1992//========================================================================== 1993 1994final void DragonSeek(float thresh, float turnMax) 1995{ 1996 int dir; 1997 float dist; 1998 float delta; 1999 EntityEx tempTarget; 2000 int i; 2001 int bestArg; 2002 float bestAngle; 2003 float angleToSpot, angleToTarget; 2004 Actor A; 2005 EntityEx oldTarget; 2006 2007 tempTarget = Tracer; 2008 if (!tempTarget) 2009 { 2010 return; 2011 } 2012 dir = FaceActor(Actor(tempTarget), delta); 2013 if (delta > thresh) 2014 { 2015 delta /= 2.0; 2016 if (delta > turnMax) 2017 { 2018 delta = turnMax; 2019 } 2020 } 2021 if (dir) 2022 { 2023 // Turn clockwise 2024 Angles.yaw = AngleMod360(Angles.yaw + delta); 2025 } 2026 else 2027 { 2028 // Turn counter clockwise 2029 Angles.yaw = AngleMod360(Angles.yaw - delta); 2030 } 2031 Velocity.x = Speed * cos(Angles.yaw); 2032 Velocity.y = Speed * sin(Angles.yaw); 2033 if (Origin.z + Height < tempTarget.Origin.z || 2034 tempTarget.Origin.z + tempTarget.Height < Origin.z) 2035 { 2036 dist = DistTo2(tempTarget); 2037 dist = dist / Speed; 2038 if (dist < 1.0) 2039 { 2040 dist = 1.0; 2041 } 2042 Velocity.z = (tempTarget.Origin.z - Origin.z) / dist; 2043 } 2044 else 2045 { 2046 dist = DistTo2(tempTarget); 2047 dist = dist / Speed; 2048 } 2049 if (tempTarget.bShootable && P_Random() < 64) 2050 { 2051 // attack the destination mobj if it's attackable 2052 if (fabs(AngleMod180(Angles.yaw - 2053 atan2(tempTarget.Origin.y - Origin.y, 2054 tempTarget.Origin.x - Origin.x))) < 45.0 / 2.0) 2055 { 2056 oldTarget = Target; 2057 Target = tempTarget; 2058 if (CheckMeleeRange()) 2059 { 2060 Target.Damage(self, self, HITDICE(10)); 2061 PlaySound('DragonAttack', CHAN_WEAPON); 2062 } 2063 else if (P_Random() < 128 && CheckMissileRange()) 2064 { 2065 SpawnMissile(tempTarget, DragonFireball); 2066 PlaySound('DragonAttack', CHAN_WEAPON); 2067 } 2068 Target = oldTarget; 2069 } 2070 } 2071 if (dist * 35.0 < 4.0) 2072 { 2073 // Hit the target thing 2074 if (Target && P_Random() < 200) 2075 { 2076 bestArg = -1; 2077 bestAngle = 360.0; 2078 angleToTarget = atan2(Target.Origin.y - Origin.y, 2079 Target.Origin.x - Origin.x); 2080 for (i = 0; i < 5; i++) 2081 { 2082 if (!tempTarget.Args[i]) 2083 { 2084 continue; 2085 } 2086 A = Actor(Level.FindMobjFromTID(tempTarget.Args[i], none)); 2087 angleToSpot = atan2(A.Origin.y - Origin.y, 2088 A.Origin.x - Origin.x); 2089 delta = fabs(AngleMod180(angleToSpot - angleToTarget)); 2090 if (delta < bestAngle) 2091 { 2092 bestAngle = delta; 2093 bestArg = i; 2094 } 2095 } 2096 if (bestArg != -1) 2097 { 2098 Tracer = Actor(Level.FindMobjFromTID(tempTarget.Args[bestArg], 2099 none)); 2100 } 2101 } 2102 else 2103 { 2104 do 2105 { 2106 i = (P_Random() >> 2) % 5; 2107 } 2108 while (!tempTarget.Args[i]); 2109 Tracer = Actor(Level.FindMobjFromTID(tempTarget.Args[i], none)); 2110 } 2111 } 2112} 2113 2114//========================================================================== 2115// 2116// A_DragonFlight 2117// 2118//========================================================================== 2119 2120final void A_DragonFlight() 2121{ 2122 float angle; 2123 2124 DragonSeek(4.0, 8.0); 2125 if (Target) 2126 { 2127 if (!Target.bShootable) 2128 { 2129 // enemy died 2130 Target = none; 2131 return; 2132 } 2133 angle = atan2(Target.Origin.y - Origin.y, Target.Origin.x - Origin.x); 2134 if (fabs(AngleMod180(Angles.yaw - angle)) < 45.0 / 2.0 2135 && CheckMeleeRange()) 2136 { 2137 Target.Damage(self, self, HITDICE(8)); 2138 PlaySound('DragonAttack', CHAN_WEAPON); 2139 } 2140 else if (fabs(AngleMod180(Angles.yaw - angle)) <= 20.0) 2141 { 2142 SetState(MissileState); 2143 PlaySound('DragonAttack', CHAN_WEAPON); 2144 } 2145 } 2146 else 2147 { 2148 LookForPlayers(true); 2149 } 2150} 2151 2152//========================================================================== 2153// 2154// A_DragonFlap 2155// 2156//========================================================================== 2157 2158final void A_DragonFlap() 2159{ 2160 A_DragonFlight(); 2161 if (P_Random() < 240) 2162 { 2163 PlaySound('DragonWingflap', CHAN_BODY); 2164 } 2165 else 2166 { 2167 PlaySound(ActiveSound, CHAN_VOICE); 2168 } 2169} 2170 2171//========================================================================== 2172// 2173// A_DragonPain 2174// 2175//========================================================================== 2176 2177final void A_DragonPain() 2178{ 2179 A_Pain(); 2180 if (!Tracer) 2181 { 2182 // no destination spot yet 2183 SetState(SeeState); 2184 } 2185} 2186 2187// 2188// Korax 2189// 2190// Korax Scripts (reserved) 2191// 249 Tell scripts that we are below half health 2192// 250-254 Control scripts 2193// 255 Death script 2194// 2195// Korax TIDs (reserved) 2196// 245 Reserved for Korax himself 2197// 248 Initial teleport destination 2198// 249 Teleport destination 2199// 250-254 For use in respective control scripts 2200// 255 For use in death script (spawn spots) 2201// 2202// Arm projectiles 2203// arm positions numbered: 2204// 1 top left 2205// 2 middle left 2206// 3 lower left 2207// 4 top right 2208// 5 middle right 2209// 6 lower right 2210// 2211// Korax Variables 2212// 2213// Special1 last teleport destination 2214// Special2 set if "below half" script not yet run 2215// 2216 2217//========================================================================== 2218// 2219// A_KoraxChase 2220// 2221//========================================================================== 2222 2223final void A_KoraxChase() 2224{ 2225 Actor spot; 2226 2227 if (!Special2 && (Health <= (default.Health / 2))) 2228 { 2229 spot = Actor(Level.FindMobjFromTID(KORAX_FIRST_TELEPORT_TID, none)); 2230 if (spot) 2231 { 2232 Teleport(spot.Origin, spot.Angles.yaw, true, true, false); 2233 } 2234 2235 XLevel.StartACS(249, 0, 0, 0, 0, self, NULL, 0, false, false); 2236 Special2 = true; // Don't run again 2237 2238 return; 2239 } 2240 2241 if (!Target) 2242 return; 2243 if (Random() < 0.1171875) 2244 { 2245 SetState(MissileState); 2246 } 2247 else if (Random() < 0.1171875) 2248 { 2249 PlaySound('KoraxActive', CHAN_VOICE, 1.0, ATTN_NONE); 2250 } 2251 2252 // Teleport away 2253 if (Health < (default.Health >> 1)) 2254 { 2255 if (Random() < 0.0390625) 2256 { 2257 spot = Actor(Level.FindMobjFromTID(KORAX_TELEPORT_TID, Tracer)); 2258 Tracer = spot; 2259 if (spot) 2260 { 2261 Teleport(spot.Origin, spot.Angles.yaw, true, true, false); 2262 } 2263 } 2264 } 2265} 2266 2267//========================================================================== 2268// 2269// A_KoraxDecide 2270// 2271//========================================================================== 2272 2273final void A_KoraxDecide() 2274{ 2275 if (Random() < 0.859375) 2276 { 2277 SetState(FindState('Attack')); 2278 } 2279 else 2280 { 2281 SetState(FindState('Command')); 2282 } 2283} 2284 2285//========================================================================== 2286// 2287// SpawnKoraxMissile 2288// 2289//========================================================================== 2290 2291final Actor SpawnKoraxMissile(TVec org, EntityEx dest, class<Actor> type) 2292{ 2293 TVec dir; 2294 Actor th; 2295 2296 org.z -= FloorClip; 2297 th = Spawn(type, org); 2298 if (th.SightSound) 2299 { 2300 th.PlaySound(th.SightSound, CHAN_VOICE); 2301 } 2302 th.Target = self; // Originator 2303 dir = dest.Origin - org; 2304 if (dest.bShadow) 2305 { 2306 // Invisible target 2307 VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0); 2308 } 2309 dir = Normalise(dir); 2310 th.Velocity = dir * th.Speed; 2311 VectorAngles(&dir, &th.Angles); 2312 return th.CheckMissileSpawn() ? th : none; 2313} 2314 2315//========================================================================== 2316// Arm 1 projectile 2317//========================================================================== 2318 2319final void KoraxFire1(class<Actor> type) 2320{ 2321 float ang; 2322 float x, y, z; 2323 2324 ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE); 2325 x = Origin.x + KORAX_ARM_EXTENSION_SHORT * cos(ang); 2326 y = Origin.y + KORAX_ARM_EXTENSION_SHORT * sin(ang); 2327 z = Origin.z - FloorClip + KORAX_ARM1_HEIGHT; 2328 SpawnKoraxMissile(vector(x, y, z), Target, type); 2329} 2330 2331//============================================================================ 2332// Arm 2 projectile 2333//============================================================================ 2334 2335final void KoraxFire2(class<Actor> type) 2336{ 2337 float ang; 2338 float x, y, z; 2339 2340 ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE); 2341 x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang); 2342 y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang); 2343 z = Origin.z - FloorClip + KORAX_ARM2_HEIGHT; 2344 SpawnKoraxMissile(vector(x, y, z), Target, type); 2345} 2346 2347//============================================================================ 2348// Arm 3 projectile 2349//============================================================================ 2350 2351final void KoraxFire3(class<Actor> type) 2352{ 2353 float ang; 2354 float x, y, z; 2355 2356 ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE); 2357 x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang); 2358 y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang); 2359 z = Origin.z - FloorClip + KORAX_ARM3_HEIGHT; 2360 SpawnKoraxMissile(vector(x, y, z), Target, type); 2361} 2362 2363//============================================================================ 2364// Arm 4 projectile 2365//============================================================================ 2366 2367final void KoraxFire4(class<Actor> type) 2368{ 2369 float ang; 2370 float x, y, z; 2371 2372 ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE); 2373 x = Origin.x + KORAX_ARM_EXTENSION_SHORT * cos(ang); 2374 y = Origin.y + KORAX_ARM_EXTENSION_SHORT * sin(ang); 2375 z = Origin.z - FloorClip + KORAX_ARM4_HEIGHT; 2376 SpawnKoraxMissile(vector(x, y, z), Target, type); 2377} 2378 2379//============================================================================ 2380// Arm 5 projectile 2381//============================================================================ 2382 2383final void KoraxFire5(class<Actor> type) 2384{ 2385 float ang; 2386 float x, y, z; 2387 2388 ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE); 2389 x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang); 2390 y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang); 2391 z = Origin.z - FloorClip + KORAX_ARM5_HEIGHT; 2392 SpawnKoraxMissile(vector(x, y, z), Target, type); 2393} 2394 2395//============================================================================ 2396// Arm 6 projectile 2397//============================================================================ 2398 2399final void KoraxFire6(class<Actor> type) 2400{ 2401 float ang; 2402 float x, y, z; 2403 2404 ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE); 2405 x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang); 2406 y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang); 2407 z = Origin.z - FloorClip + KORAX_ARM6_HEIGHT; 2408 SpawnKoraxMissile(vector(x, y, z), Target, type); 2409} 2410 2411//============================================================================ 2412// 2413// A_KoraxMissile 2414// 2415//============================================================================ 2416 2417final void A_KoraxMissile() 2418{ 2419 class<Actor> mtype; 2420 name sound; 2421 2422 PlaySound('KoraxAttack', CHAN_VOICE); 2423 2424 switch (P_Random() % 6) 2425 { 2426 case 0: 2427 mtype = WraithFX1; 2428 sound = 'WraithMissileFire'; 2429 break; 2430 case 1: 2431 mtype = Demon1FX1; 2432 sound = 'DemonMissileFire'; 2433 break; 2434 case 2: 2435 mtype = Demon2FX1; 2436 sound = 'DemonMissileFire'; 2437 break; 2438 case 3: 2439 mtype = FireDemonMissile; 2440 sound = 'FireDemonAttack'; 2441 break; 2442 case 4: 2443 mtype = CentaurFX; 2444 sound = 'CentaurLeaderAttack'; 2445 break; 2446 case 5: 2447 mtype = SerpentFX; 2448 sound = 'CentaurLeaderAttack'; 2449 break; 2450 } 2451 2452 // Fire all 6 missiles at once 2453 PlaySound(sound, CHAN_VOICE, 1.0, ATTN_NONE); 2454 KoraxFire1(mtype); 2455 KoraxFire2(mtype); 2456 KoraxFire3(mtype); 2457 KoraxFire4(mtype); 2458 KoraxFire5(mtype); 2459 KoraxFire6(mtype); 2460} 2461 2462//============================================================================ 2463// Call action code scripts (250-254) 2464//============================================================================ 2465 2466final void A_KoraxCommand() 2467{ 2468 float x, y, z; 2469 float ang; 2470 int numcommands; 2471 2472 PlaySound('KoraxCommand', CHAN_VOICE); 2473 2474 // Shoot stream of lightning to ceiling 2475 ang = AngleMod360(Angles.yaw - 90.0); 2476 x = Origin.x + KORAX_COMMAND_OFFSET * cos(ang); 2477 y = Origin.y + KORAX_COMMAND_OFFSET * sin(ang); 2478 z = Origin.z + KORAX_COMMAND_HEIGHT; 2479 Spawn(KoraxBolt, vector(x, y, z)); 2480 2481 if (Health <= (default.Health >> 1)) 2482 { 2483 numcommands = 5; 2484 } 2485 else 2486 { 2487 numcommands = 4; 2488 } 2489 2490 switch (P_Random() % numcommands) 2491 { 2492 case 0: 2493 XLevel.StartACS(250, 0, 0, 0, 0, self, NULL, 0, false, false); 2494 break; 2495 case 1: 2496 XLevel.StartACS(251, 0, 0, 0, 0, self, NULL, 0, false, false); 2497 break; 2498 case 2: 2499 XLevel.StartACS(252, 0, 0, 0, 0, self, NULL, 0, false, false); 2500 break; 2501 case 3: 2502 XLevel.StartACS(253, 0, 0, 0, 0, self, NULL, 0, false, false); 2503 break; 2504 case 4: 2505 XLevel.StartACS(254, 0, 0, 0, 0, self, NULL, 0, false, false); 2506 break; 2507 } 2508} 2509 2510//============================================================================ 2511// 2512// A_KoraxBonePop 2513// 2514//============================================================================ 2515 2516final void A_KoraxBonePop() 2517{ 2518 EntityEx mo; 2519 2520 // Spawn 6 spirits equalangularly 2521 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 0.0, 5.0 * 35.0); 2522 if (mo) 2523 { 2524 KSpiritInit(mo); 2525 } 2526 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 1.0, 5.0 * 35.0); 2527 if (mo) 2528 { 2529 KSpiritInit(mo); 2530 } 2531 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 2.0, 5.0 * 35.0); 2532 if (mo) 2533 { 2534 KSpiritInit(mo); 2535 } 2536 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 3.0, 5.0 * 35.0); 2537 if (mo) 2538 { 2539 KSpiritInit(mo); 2540 } 2541 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 4.0, 5.0 * 35.0); 2542 if (mo) 2543 { 2544 KSpiritInit(mo); 2545 } 2546 mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 5.0, 5.0 * 35.0); 2547 if (mo) 2548 { 2549 KSpiritInit(mo); 2550 } 2551 2552 XLevel.StartACS(255, 0, 0, 0, 0, self, NULL, 0, false, false); // Death script 2553} 2554 2555//========================================================================== 2556// 2557// KSpiritInit 2558// 2559//========================================================================== 2560 2561final void KSpiritInit(EntityEx Spirit) 2562{ 2563 int i; 2564 EntityEx tail; 2565 EntityEx next; 2566 2567 Spirit.Tracer = self; // Swarm around korax 2568 Spirit.Special1 = 5 * 35 / 5; // 5 seconds 2569 Spirit.Special2 = 32 + (P_Random() & 7); // Float bob index 2570 2571 // Spawn a tail for spirit 2572 tail = Spawn(HolyTail, Spirit.Origin); 2573 tail.Target = Spirit; // parent 2574 for (i = 1; i < 3; i++) 2575 { 2576 next = Spawn(HolyTailTrail, Spirit.Origin); 2577 tail.Tracer = next; 2578 tail = next; 2579 } 2580} 2581 2582//============================================================================ 2583// 2584// A_CHolyAttack2 2585// 2586// Spawns the spirits 2587// 2588//============================================================================ 2589 2590final void A_CHolyAttack2() 2591{ 2592 int j; 2593 int i; 2594 Actor A; 2595 Actor tail; 2596 Actor next; 2597 2598 for (j = 0; j < 4; j++) 2599 { 2600 A = Spawn(HolySpirit, Origin); 2601 if (!A) 2602 { 2603 continue; 2604 } 2605 switch (j) 2606 { // float bob index 2607 case 0: 2608 HolySpirit(A).WeaveZ = P_Random() & 7; // upper-left 2609 break; 2610 case 1: 2611 HolySpirit(A).WeaveZ = 32 + (P_Random() & 7); // upper-right 2612 break; 2613 case 2: 2614 HolySpirit(A).WeaveXY = 32 + (P_Random() & 7); // lower-left 2615 break; 2616 case 3: 2617 HolySpirit(A).WeaveXY = 32 + (P_Random() & 7); 2618 HolySpirit(A).WeaveZ = 32 + (P_Random() & 7); 2619 break; 2620 } 2621 A.Origin.z = Origin.z; 2622 A.Angles.yaw = AngleMod360(Angles.yaw + 2623 (45.0 + 45.0 / 2.0) - 45.0 * itof(j)); 2624 A.Thrust(A.Angles.yaw, A.Speed * Level.Game.frametime); 2625 A.Target = Target; 2626 A.Args[0] = 10; // initial turn value 2627 A.Args[1] = 0; // initial look angle 2628 if (Level.Game.deathmatch) 2629 { 2630 // Ghosts last slightly less longer in DeathMatch 2631 A.Health = 85; 2632 } 2633 if (Tracer) 2634 { 2635 A.Tracer = Tracer; 2636 // Don't colide with world but colide with things, i.e explode 2637 A.bColideWithWorld = false; 2638 A.bSkullFly = true; 2639 A.bMissile = false; 2640 } 2641 tail = Spawn(HolyTail, A.Origin); 2642 tail.Target = A; // parent 2643 for (i = 1; i < 3; i++) 2644 { 2645 next = Spawn(HolyTailTrail, A.Origin); 2646 tail.Tracer = next; 2647 tail = next; 2648 } 2649 } 2650} 2651 2652//=========================================================================== 2653// 2654// A_PoisonBagInit 2655// 2656//=========================================================================== 2657 2658final void A_PoisonBagInit() 2659{ 2660 Actor A; 2661 2662 A = Spawn(PoisonCloud, Origin + vector(0.0, 0.0, 28.0)); 2663 if (A) 2664 { 2665 PoisonCloud(A).InitCloud(Actor(Target)); 2666 } 2667} 2668 2669//=========================================================================== 2670// 2671// A_PoisonShroom 2672// 2673//=========================================================================== 2674 2675final void A_PoisonShroom() 2676{ 2677 StateTime = 4.0 + Random() * 16.0; 2678} 2679 2680//========================================================================== 2681// 2682// A_FighterAttack 2683// 2684//========================================================================== 2685 2686final void A_FighterAttack() 2687{ 2688 if (!Target) 2689 return; 2690 2691 SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw + 45.0 / 4.0), 0.0); 2692 SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw + 45.0 / 8.0), 0.0); 2693 SpawnMissileAngle(FSwordMissile, Angles.yaw, 0.0); 2694 SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw - 45.0 / 8.0), 0.0); 2695 SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw - 45.0 / 4.0), 0.0); 2696 PlaySound('FighterSwordFire', CHAN_WEAPON); 2697} 2698 2699//========================================================================== 2700// 2701// A_ClericAttack 2702// 2703// Spawns the spirits 2704// 2705//========================================================================== 2706 2707final void A_ClericAttack() 2708{ 2709 EntityEx A; 2710 2711 if (!Target) 2712 return; 2713 A = SpawnMissile(Target, HolyMissile, 40.0); 2714 if (A) 2715 { 2716 A.Tracer = Target; 2717 } 2718 PlaySound('HolySymbolFire', CHAN_WEAPON); 2719} 2720 2721//========================================================================== 2722// 2723// MStaffSpawn2 2724// 2725//========================================================================== 2726 2727void MStaffSpawn2(float angle) 2728{ 2729 EntityEx mo; 2730 2731 mo = SpawnMissileAngle(MageStaffFX2, angle, 0.0, 40.0); 2732 if (mo) 2733 { 2734 mo.Target = self; 2735 MageStaffFX2(mo).FindEnemy(); 2736 } 2737} 2738 2739//========================================================================== 2740// 2741// A_MageAttack 2742// 2743//========================================================================== 2744 2745final void A_MageAttack() 2746{ 2747 if (!Target) 2748 return; 2749 2750 MStaffSpawn2(Angles.yaw); 2751 MStaffSpawn2(AngleMod360(Angles.yaw - 5.0)); 2752 MStaffSpawn2(AngleMod360(Angles.yaw + 5.0)); 2753 PlaySound('MageStaffFire', CHAN_WEAPON); 2754} 2755 2756//============================================================================ 2757// 2758// A_SorcererBishopEntry 2759// 2760//============================================================================ 2761 2762final void A_SorcererBishopEntry() 2763{ 2764 Spawn(SorcFX3Explosion, Origin); 2765 PlaySound(SightSound, CHAN_VOICE); 2766} 2767 2768//============================================================================ 2769// 2770// A_SpawnBishop 2771// 2772// Green spell - spawn bishops 2773// 2774//============================================================================ 2775 2776final void A_SpawnBishop() 2777{ 2778 Actor A; 2779 2780 A = Spawn(Bishop, Origin); 2781 if (A) 2782 { 2783 if (!A.TestLocation()) 2784 { 2785 A.SetState(none); 2786 Level.TotalKills--; 2787 } 2788 else if (Target) 2789 { 2790 A.CopyFriendliness(Target, true); 2791 A.Master = Target; 2792 } 2793 } 2794 SetState(none); 2795} 2796 2797//============================================================================ 2798// 2799// Set balls to slow mode - actor is sorcerer 2800// 2801//============================================================================ 2802 2803final void A_SlowBalls() 2804{ 2805 Args[3] = SORC_DECELERATE; // slow mode 2806 Args[2] = SORCBALL_INITIAL_SPEED; // target speed 2807} 2808 2809//============================================================================ 2810// 2811// Set balls to speed mode - actor is sorcerer 2812// 2813//============================================================================ 2814 2815final void A_SpeedBalls() 2816{ 2817 Args[3] = SORC_ACCELERATE; // speed mode 2818 Args[2] = SORCBALL_TERMINAL_SPEED; // target speed 2819} 2820 2821//============================================================================ 2822// 2823// A_SorcBossAttack 2824// 2825// Resume ball spinning 2826// 2827//============================================================================ 2828 2829final void A_SorcBossAttack() 2830{ 2831 Args[3] = SORC_ACCELERATE; 2832 Args[2] = SORCBALL_INITIAL_SPEED; 2833} 2834 2835//============================================================================ 2836// 2837// A_StopBalls 2838// 2839// Instant stop when rotation gets to ball in _Special2 2840// actor is sorcerer 2841// 2842//============================================================================ 2843 2844final void A_StopBalls() 2845{ 2846 int chance = P_Random(); 2847 2848 Args[3] = SORC_STOPPING; // stopping mode 2849 Args[1] = 0; // Reset rotation counter 2850 2851 if ((Args[0] <= 0) && (chance < 200)) 2852 { 2853 SpecialCID = SorcBall2; // Blue 2854 } 2855 else if ((Health < (default.Health >> 1)) && (chance < 200)) 2856 { 2857 SpecialCID = SorcBall3; // Green 2858 } 2859 else 2860 { 2861 SpecialCID = SorcBall1; // Yellow 2862 } 2863} 2864 2865//============================================================================ 2866// 2867// A_SpawnFizzle 2868// 2869// Spell cast magic fizzle 2870// 2871//============================================================================ 2872 2873final void A_SpawnFizzle() 2874{ 2875 TVec org; 2876 float dist = 5.0; 2877 float angle = Angles.yaw; 2878 float rangle; 2879 Actor A; 2880 int ix; 2881 2882 org.x = Origin.x + dist * cos(angle); 2883 org.y = Origin.y + dist * sin(angle); 2884 org.z = Origin.z - FloorClip + Height / 2.0; 2885 for (ix = 0; ix < 5; ix++) 2886 { 2887 A = Spawn(SorcSpark1, org); 2888 if (A) 2889 { 2890 rangle = angle + Random() * 5.0 * 90.0 / 1024.0; 2891 A.Velocity.x = Random() * Speed * cos(rangle); 2892 A.Velocity.y = Random() * Speed * sin(rangle); 2893 A.Velocity.z = 2.0 * 35.0; 2894 } 2895 } 2896} 2897 2898//========================================================================== 2899// 2900// A_Summon 2901// 2902// Summon Minotaur 2903// 2904//========================================================================== 2905 2906final void A_Summon() 2907{ 2908 EntityEx A; 2909 2910 A = Spawn(MinotaurFriend, Origin,,, true); 2911 if (A) 2912 { 2913 if (!A.TestLocation() || !Tracer) 2914 { 2915 // Didn't fit - change back to artifact 2916 A.SetState(none); 2917 A = Spawn(ArtiDarkServant, Origin,,, true); 2918 if (A) 2919 A.bDropped = true; 2920 return; 2921 } 2922 2923 MinotaurFriend(A).StartTime = XLevel.Time; 2924 if (!Tracer.bCorpse) 2925 { 2926 // Master isn't dead 2927 A.Tracer = Tracer; // Pointer to master 2928 Powerup Power = Spawn(PowerMinotaur,,,, false); 2929 Power.bAlwaysPickup = true; 2930 if (!Power.TryPickup(Target)) 2931 { 2932 Power.Destroy(); 2933 } 2934 if (Tracer.bIsPlayer) 2935 { 2936 A.FriendPlayer = Tracer.Player.GetPlayerNum() + 1; 2937 } 2938 } 2939 2940 // Make smoke puff 2941 Spawn(MinotaurSmoke, Origin,,, true); 2942 PlaySound(A.ActiveSound, CHAN_VOICE); 2943 } 2944} 2945