//=========================================================================== // Korax Variables // tracer last teleport destination // special2 set if "below half" script not yet run // // Korax Scripts (reserved) // 249 Tell scripts that we are below half health // 250-254 Control scripts (254 is only used when less than half health) // 255 Death script // // Korax TIDs (reserved) // 245 Reserved for Korax himself // 248 Initial teleport destination // 249 Teleport destination // 250-254 For use in respective control scripts // 255 For use in death script (spawn spots) //=========================================================================== class Korax : Actor { const KORAX_ARM_EXTENSION_SHORT = 40; const KORAX_ARM_EXTENSION_LONG = 55; const KORAX_ARM1_HEIGHT = 108; const KORAX_ARM2_HEIGHT = 82; const KORAX_ARM3_HEIGHT = 54; const KORAX_ARM4_HEIGHT = 104; const KORAX_ARM5_HEIGHT = 86; const KORAX_ARM6_HEIGHT = 53; const KORAX_FIRST_TELEPORT_TID = 248; const KORAX_TELEPORT_TID = 249; const KORAX_DELTAANGLE = 85; const KORAX_COMMAND_HEIGHT = 120; const KORAX_COMMAND_OFFSET = 27; const KORAX_SPIRIT_LIFETIME = 5*TICRATE/5; // 5 seconds Default { Health 5000; Painchance 20; Speed 10; Radius 65; Height 115; Mass 2000; Damage 15; Monster; +BOSS +FLOORCLIP +TELESTOMP +DONTMORPH +NOTARGET +NOICEDEATH SeeSound "KoraxSight"; AttackSound "KoraxAttack"; PainSound "KoraxPain"; DeathSound "KoraxDeath"; ActiveSound "KoraxActive"; Obituary "$OB_KORAX"; } States { Spawn: KORX A 5 A_Look; Loop; See: KORX AAA 3 A_KoraxChase; KORX B 3 A_Chase; KORX BBB 3 A_KoraxChase; KORX C 3 A_KoraxStep; KORX CCC 3 A_KoraxChase; KORX D 3 A_Chase; KORX DDD 3 A_KoraxChase; KORX A 3 A_KoraxStep; Loop; Pain: KORX H 5 A_Pain; KORX H 5; Goto See; Missile: KORX E 2 Bright A_FaceTarget; KORX E 5 Bright A_KoraxDecide; Wait; Death: KORX I 5; KORX J 5 A_FaceTarget; KORX K 5 A_Scream; KORX LMNOP 5; KORX Q 10; KORX R 5 A_KoraxBonePop; KORX S 5 A_NoBlocking; KORX TU 5; KORX V -1; Stop; Attack: KORX E 4 Bright A_FaceTarget; KORX F 8 Bright A_KoraxMissile; KORX E 8 Bright; Goto See; Command: KORX E 5 Bright A_FaceTarget; KORX W 10 Bright A_FaceTarget; KORX G 15 Bright A_KoraxCommand; KORX W 10 Bright; KORX E 5 Bright; Goto See; } void A_KoraxStep() { A_PlaySound("KoraxStep"); A_Chase(); } //============================================================================ // // A_KoraxChase // //============================================================================ void A_KoraxChase() { if ((!special2) && (health <= (SpawnHealth()/2))) { ActorIterator it = ActorIterator.Create(KORAX_FIRST_TELEPORT_TID); Actor spot = it.Next (); if (spot != null) { Teleport ((spot.pos.xy, ONFLOORZ), spot.angle, TELF_SOURCEFOG | TELF_DESTFOG); } ACS_Execute(249, 0); special2 = 1; // Don't run again return; } if (target == null) { return; } if (random[KoraxChase]() < 30) { SetState (MissileState); } else if (random[KoraxChase]() < 30) { A_PlaySound("KoraxActive", CHAN_VOICE, 1, false, ATTN_NONE); } // Teleport away if (health < (SpawnHealth() >> 1)) { if (random[KoraxChase]() < 10) { ActorIterator it = ActorIterator.Create(KORAX_TELEPORT_TID); Actor spot; if (tracer != null) { // Find the previous teleport destination do { spot = it.Next (); } while (spot != null && spot != tracer); } // Go to the next teleport destination spot = it.Next (); tracer = spot; if (spot) { Teleport ((spot.pos.xy, ONFLOORZ), spot.angle, TELF_SOURCEFOG | TELF_DESTFOG); } } } } //============================================================================ // // A_KoraxDecide // //============================================================================ void A_KoraxDecide() { if (random[KoraxDecide]() < 220) { SetStateLabel ("Attack"); } else { SetStateLabel ("Command"); } } //============================================================================ // // A_KoraxBonePop // //============================================================================ void A_KoraxBonePop() { // Spawn 6 spirits equalangularly for (int i = 0; i < 6; ++i) { Actor mo = SpawnMissileAngle ("KoraxSpirit", 60.*i, 5.); if (mo) { KSpiritInit (mo); } } ACS_Execute(255, 0); } //============================================================================ // // KSpiritInit // //============================================================================ private void KSpiritInit (Actor spirit) { spirit.health = KORAX_SPIRIT_LIFETIME; spirit.tracer = self; // Swarm around korax spirit.WeaveIndexZ = 32 + (random[Kspiritnit]() & 7); // Float bob index spirit.args[0] = 10; // initial turn value spirit.args[1] = 0; // initial look angle // Spawn a tail for spirit HolyTail.SpawnSpiritTail (spirit); } //============================================================================ // // A_KoraxMissile // //============================================================================ void A_KoraxMissile() { static const class choices[] = { "WraithFX1", "Demon1FX1", "Demon2FX1", "FireDemonMissile", "CentaurFX", "SerpentFX" }; static const sound sounds[] = { "WraithMissileFire", "DemonMissileFire", "DemonMissileFire", "FireDemonAttack", "CentaurLeaderAttack", "SerpentLeaderAttack" }; int type = random[KoraxMissile]() % 6; A_PlaySound("KoraxAttack", CHAN_VOICE); // Fire all 6 missiles at once A_PlaySound(sounds[type], CHAN_WEAPON, 1, false, ATTN_NONE); class info = choices[type]; for (int i = 0; i < 6; ++i) { KoraxFire(info, i); } } //============================================================================ // // KoraxFire // // Arm projectiles // arm positions numbered: // 1 top left // 2 middle left // 3 lower left // 4 top right // 5 middle right // 6 lower right // //============================================================================ void KoraxFire (Class type, int arm) { static const int extension[] = { KORAX_ARM_EXTENSION_SHORT, KORAX_ARM_EXTENSION_LONG, KORAX_ARM_EXTENSION_LONG, KORAX_ARM_EXTENSION_SHORT, KORAX_ARM_EXTENSION_LONG, KORAX_ARM_EXTENSION_LONG }; static const int armheight[] = { KORAX_ARM1_HEIGHT, KORAX_ARM2_HEIGHT, KORAX_ARM3_HEIGHT, KORAX_ARM4_HEIGHT, KORAX_ARM5_HEIGHT, KORAX_ARM6_HEIGHT }; double ang = angle + (arm < 3 ? -KORAX_DELTAANGLE : KORAX_DELTAANGLE); Vector3 pos = Vec3Angle(extension[arm], ang, armheight[arm] - Floorclip); SpawnKoraxMissile (pos, target, type); } //============================================================================ // // P_SpawnKoraxMissile // //============================================================================ private void SpawnKoraxMissile (Vector3 pos, Actor dest, Class type) { Actor th = Spawn (type, pos, ALLOW_REPLACE); th.target = self; // Originator double an = th.AngleTo(dest); if (dest.bShadow) { // Invisible target an += Random2[KoraxMissile]() * (45/256.); } th.angle = an; th.VelFromAngle(); double dist = dest.DistanceBySpeed(th, th.Speed); th.Vel.Z = (dest.pos.z - pos.Z + 30) / dist; th.CheckMissileSpawn(radius); } //============================================================================ // // A_KoraxCommand // // Call action code scripts (250-254) // //============================================================================ void A_KoraxCommand() { int numcommands; A_PlaySound("KoraxCommand", CHAN_VOICE); // Shoot stream of lightning to ceiling double ang = angle - 90; Vector3 pos = Vec3Angle(KORAX_COMMAND_OFFSET, ang, KORAX_COMMAND_HEIGHT); Spawn("KoraxBolt", pos, ALLOW_REPLACE); if (health <= (SpawnHealth() >> 1)) { numcommands = 5; } else { numcommands = 4; } ACS_Execute(250 + (random[KoraxCommand]()%numcommands), 0); } } class KoraxSpirit : Actor { Default { Speed 8; Projectile; +NOCLIP -ACTIVATEPCROSS -ACTIVATEIMPACT RenderStyle "Translucent"; Alpha 0.4; } States { Spawn: SPIR AB 5 A_KSpiritRoam; Loop; Death: SPIR DEFGHI 5; Stop; } //============================================================================ // // A_KSpiritSeeker // //============================================================================ private void KSpiritSeeker (double thresh, double turnMax) { Actor target = tracer; if (target == null) { return; } double dir = deltaangle(angle, AngleTo(target)); double delta = abs(dir); if (delta > thresh) { delta /= 2; if(delta > turnMax) { delta = turnMax; } } if(dir > 0) { // Turn clockwise angle += delta; } else { // Turn counter clockwise angle -= delta; } VelFromAngle(); if (!(level.time&15) || pos.z > target.pos.z + target.Default.Height || pos.z + height < target.pos.z) { double newZ = target.pos.z + random[KoraxRoam]() * target.Default.Height / 256; double deltaZ = newZ - pos.z; if (abs(deltaZ) > 15) { if(deltaZ > 0) { deltaZ = 15; } else { deltaZ = -15; } } Vel.Z = deltaZ + DistanceBySpeed(target, Speed); } } //============================================================================ // // A_KSpiritRoam // //============================================================================ void A_KSpiritRoam() { if (health-- <= 0) { A_PlaySound("SpiritDie", CHAN_VOICE); SetStateLabel ("Death"); } else { if (tracer) { KSpiritSeeker(args[0], args[0] * 2.); } int xyspeed = (random[KoraxRoam]() % 5); int zspeed = (random[KoraxRoam]() % 5); A_Weave(xyspeed, zspeed, 4., 2.); if (random[KoraxRoam]() < 50) { A_PlaySound("SpiritActive", CHAN_VOICE, 1, false, ATTN_NONE); } } } } class KoraxBolt : Actor { const KORAX_BOLT_HEIGHT = 48.; const KORAX_BOLT_LIFETIME = 3; Default { Radius 15; Height 35; Projectile; -ACTIVATEPCROSS -ACTIVATEIMPACT RenderStyle "Add"; } States { Spawn: MLFX I 2 Bright; MLFX J 2 Bright A_KBoltRaise; MLFX IJKLM 2 Bright A_KBolt; Stop; } //============================================================================ // // A_KBolt // //============================================================================ void A_KBolt() { // Countdown lifetime if (special1-- <= 0) { Destroy (); } } //============================================================================ // // A_KBoltRaise // //============================================================================ void A_KBoltRaise() { // Spawn a child upward double z = pos.z + KORAX_BOLT_HEIGHT; if ((z + KORAX_BOLT_HEIGHT) < ceilingz) { Actor mo = Spawn("KoraxBolt", (pos.xy, z), ALLOW_REPLACE); if (mo) { mo.special1 = KORAX_BOLT_LIFETIME; } } } }