//=========================================================================== // 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) //=========================================================================== #include "actor.h" #include "info.h" #include "p_local.h" #include "p_spec.h" #include "s_sound.h" #include "p_enemy.h" #include "a_action.h" #include "m_random.h" #include "i_system.h" const int KORAX_SPIRIT_LIFETIME = 5*TICRATE/5; // 5 seconds const int KORAX_COMMAND_HEIGHT = 120; const int KORAX_COMMAND_OFFSET = 27; const int KORAX_TID = 245; const int KORAX_FIRST_TELEPORT_TID = 248; const int KORAX_TELEPORT_TID = 249; const int KORAX_DELTAANGLE = 85*ANGLE_1; const int KORAX_ARM_EXTENSION_SHORT = 40; const int KORAX_ARM_EXTENSION_LONG = 55; const int KORAX_ARM1_HEIGHT = 108*FRACUNIT; const int KORAX_ARM2_HEIGHT = 82*FRACUNIT; const int KORAX_ARM3_HEIGHT = 54*FRACUNIT; const int KORAX_ARM4_HEIGHT = 104*FRACUNIT; const int KORAX_ARM5_HEIGHT = 86*FRACUNIT; const int KORAX_ARM6_HEIGHT = 53*FRACUNIT; const int KORAX_BOLT_HEIGHT = 48*FRACUNIT; const int KORAX_BOLT_LIFETIME = 3; static FRandom pr_koraxchase ("KoraxChase"); static FRandom pr_kspiritinit ("KSpiritInit"); static FRandom pr_koraxdecide ("KoraxDecide"); static FRandom pr_koraxmissile ("KoraxMissile"); static FRandom pr_koraxcommand ("KoraxCommand"); static FRandom pr_kspiritweave ("KSpiritWeave"); static FRandom pr_kspiritseek ("KSpiritSeek"); static FRandom pr_kspiritroam ("KSpiritRoam"); static FRandom pr_missile ("SKoraxMissile"); void A_KoraxChase (AActor *); void A_KoraxStep (AActor *); void A_KoraxStep2 (AActor *); void A_KoraxDecide (AActor *); void A_KoraxBonePop (AActor *); void A_KoraxMissile (AActor *); void A_KoraxCommand (AActor *); void A_KSpiritRoam (AActor *); void A_KBolt (AActor *); void A_KBoltRaise (AActor *); void KoraxFire (AActor *actor, const TypeInfo *type, int arm); void KSpiritInit (AActor *spirit, AActor *korax); AActor *P_SpawnKoraxMissile (fixed_t x, fixed_t y, fixed_t z, AActor *source, AActor *dest, const TypeInfo *type); extern void SpawnSpiritTail (AActor *spirit); // Korax -------------------------------------------------------------------- class AKorax : public AActor { DECLARE_ACTOR (AKorax, AActor) }; FState AKorax::States[] = { #define S_KORAX_LOOK1 0 S_NORMAL (KORX, 'A', 5, A_Look , &States[S_KORAX_LOOK1]), #define S_KORAX_CHASE2 (S_KORAX_LOOK1+1) S_NORMAL (KORX, 'A', 3, A_KoraxChase , &States[S_KORAX_CHASE2+1]), S_NORMAL (KORX, 'A', 3, A_KoraxChase , &States[S_KORAX_CHASE2+2]), S_NORMAL (KORX, 'A', 3, A_KoraxChase , &States[S_KORAX_CHASE2+3]), S_NORMAL (KORX, 'B', 3, A_KoraxStep , &States[S_KORAX_CHASE2+4]), S_NORMAL (KORX, 'B', 3, A_KoraxChase , &States[S_KORAX_CHASE2+5]), S_NORMAL (KORX, 'B', 3, A_KoraxChase , &States[S_KORAX_CHASE2+6]), S_NORMAL (KORX, 'B', 3, A_KoraxChase , &States[S_KORAX_CHASE2+7]), S_NORMAL (KORX, 'C', 3, A_KoraxStep2 , &States[S_KORAX_CHASE2+8]), S_NORMAL (KORX, 'C', 3, A_KoraxChase , &States[S_KORAX_CHASE2+9]), S_NORMAL (KORX, 'C', 3, A_KoraxChase , &States[S_KORAX_CHASE2+10]), S_NORMAL (KORX, 'C', 3, A_KoraxChase , &States[S_KORAX_CHASE2+11]), S_NORMAL (KORX, 'D', 3, A_KoraxStep , &States[S_KORAX_CHASE2+12]), S_NORMAL (KORX, 'D', 3, A_KoraxChase , &States[S_KORAX_CHASE2+13]), S_NORMAL (KORX, 'D', 3, A_KoraxChase , &States[S_KORAX_CHASE2+14]), S_NORMAL (KORX, 'D', 3, A_KoraxChase , &States[S_KORAX_CHASE2+15]), S_NORMAL (KORX, 'A', 3, A_KoraxStep2 , &States[S_KORAX_CHASE2]), #define S_KORAX_PAIN1 (S_KORAX_CHASE2+16) S_NORMAL (KORX, 'H', 5, A_Pain , &States[S_KORAX_PAIN1+1]), S_NORMAL (KORX, 'H', 5, NULL , &States[S_KORAX_CHASE2]), #define S_KORAX_ATTACK1 (S_KORAX_PAIN1+2) S_BRIGHT (KORX, 'E', 2, A_FaceTarget , &States[S_KORAX_ATTACK1+1]), S_BRIGHT (KORX, 'E', 5, A_KoraxDecide , &States[S_KORAX_ATTACK1+1]), #define S_KORAX_DEATH1 (S_KORAX_ATTACK1+2) S_NORMAL (KORX, 'I', 5, NULL , &States[S_KORAX_DEATH1+1]), S_NORMAL (KORX, 'J', 5, A_FaceTarget , &States[S_KORAX_DEATH1+2]), S_NORMAL (KORX, 'K', 5, A_Scream , &States[S_KORAX_DEATH1+3]), S_NORMAL (KORX, 'L', 5, NULL , &States[S_KORAX_DEATH1+4]), S_NORMAL (KORX, 'M', 5, NULL , &States[S_KORAX_DEATH1+5]), S_NORMAL (KORX, 'N', 5, NULL , &States[S_KORAX_DEATH1+6]), S_NORMAL (KORX, 'O', 5, NULL , &States[S_KORAX_DEATH1+7]), S_NORMAL (KORX, 'P', 5, NULL , &States[S_KORAX_DEATH1+8]), S_NORMAL (KORX, 'Q', 10, NULL , &States[S_KORAX_DEATH1+9]), S_NORMAL (KORX, 'R', 5, A_KoraxBonePop , &States[S_KORAX_DEATH1+10]), S_NORMAL (KORX, 'S', 5, A_NoBlocking , &States[S_KORAX_DEATH1+11]), S_NORMAL (KORX, 'T', 5, NULL , &States[S_KORAX_DEATH1+12]), S_NORMAL (KORX, 'U', 5, NULL , &States[S_KORAX_DEATH1+13]), S_NORMAL (KORX, 'V', -1, NULL , NULL), #define S_KORAX_MISSILE1 (S_KORAX_DEATH1+14) S_BRIGHT (KORX, 'E', 4, A_FaceTarget , &States[S_KORAX_MISSILE1+1]), S_BRIGHT (KORX, 'F', 8, A_KoraxMissile , &States[S_KORAX_MISSILE1+2]), S_BRIGHT (KORX, 'E', 8, NULL , &States[S_KORAX_CHASE2]), #define S_KORAX_COMMAND1 (S_KORAX_MISSILE1+3) S_BRIGHT (KORX, 'E', 5, A_FaceTarget , &States[S_KORAX_COMMAND1+1]), S_BRIGHT (KORX, 'W', 10, A_FaceTarget , &States[S_KORAX_COMMAND1+2]), S_BRIGHT (KORX, 'G', 15, A_KoraxCommand , &States[S_KORAX_COMMAND1+3]), S_BRIGHT (KORX, 'W', 10, NULL , &States[S_KORAX_COMMAND1+4]), S_BRIGHT (KORX, 'E', 5, NULL , &States[S_KORAX_CHASE2]), }; IMPLEMENT_ACTOR (AKorax, Hexen, 10200, 0) PROP_SpawnHealth (5000) PROP_PainChance (20) PROP_SpeedFixed (10) PROP_RadiusFixed (65) PROP_HeightFixed (115) PROP_Mass (2000) PROP_Damage (15) PROP_Flags (MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL) PROP_Flags2 (MF2_FLOORCLIP|MF2_BOSS|MF2_TELESTOMP|MF2_PUSHWALL|MF2_MCROSS) PROP_Flags3 (MF3_DONTMORPH) PROP_Flags4 (MF4_NOICEDEATH) PROP_SpawnState (S_KORAX_LOOK1) PROP_SeeState (S_KORAX_CHASE2) PROP_PainState (S_KORAX_PAIN1) PROP_MissileState (S_KORAX_ATTACK1) PROP_DeathState (S_KORAX_DEATH1) PROP_SeeSound ("KoraxSight") PROP_AttackSound ("KoraxAttack") PROP_PainSound ("KoraxPain") PROP_DeathSound ("KoraxDeath") PROP_ActiveSound ("KoraxActive") END_DEFAULTS // Korax Spirit ------------------------------------------------------------- class AKoraxSpirit : public AActor { DECLARE_ACTOR (AKoraxSpirit, AActor) }; FState AKoraxSpirit::States[] = { S_NORMAL (SPIR, 'A', 5, A_KSpiritRoam , &States[1]), S_NORMAL (SPIR, 'B', 5, A_KSpiritRoam , &States[0]), #define S_KSPIRIT_DEATH (2) S_NORMAL (SPIR, 'D', 5, NULL , &States[S_KSPIRIT_DEATH+1]), S_NORMAL (SPIR, 'E', 5, NULL , &States[S_KSPIRIT_DEATH+2]), S_NORMAL (SPIR, 'F', 5, NULL , &States[S_KSPIRIT_DEATH+3]), S_NORMAL (SPIR, 'G', 5, NULL , &States[S_KSPIRIT_DEATH+4]), S_NORMAL (SPIR, 'H', 5, NULL , &States[S_KSPIRIT_DEATH+5]), S_NORMAL (SPIR, 'I', 5, NULL , NULL), }; IMPLEMENT_ACTOR (AKoraxSpirit, Hexen, -1, 0) PROP_SpeedFixed (8) PROP_Flags (MF_NOBLOCKMAP|MF_NOGRAVITY|MF_DROPOFF|MF_NOCLIP|MF_MISSILE) PROP_Flags2 (MF2_NOTELEPORT) PROP_RenderStyle (STYLE_Translucent) PROP_Alpha (HX_ALTSHADOW) PROP_SpawnState (0) END_DEFAULTS // Korax Bolt --------------------------------------------------------------- class AKoraxBolt : public AActor { DECLARE_ACTOR (AKoraxBolt, AActor) }; FState AKoraxBolt::States[] = { #define S_KBOLT1 0 S_BRIGHT (MLFX, 'I', 2, NULL , &States[S_KBOLT1+1]), S_BRIGHT (MLFX, 'J', 2, A_KBoltRaise , &States[S_KBOLT1+2]), S_BRIGHT (MLFX, 'I', 2, A_KBolt , &States[S_KBOLT1+3]), S_BRIGHT (MLFX, 'J', 2, A_KBolt , &States[S_KBOLT1+4]), S_BRIGHT (MLFX, 'K', 2, A_KBolt , &States[S_KBOLT1+5]), S_BRIGHT (MLFX, 'L', 2, A_KBolt , &States[S_KBOLT1+6]), S_BRIGHT (MLFX, 'M', 2, A_KBolt , &States[S_KBOLT1+2]), }; IMPLEMENT_ACTOR (AKoraxBolt, Hexen, -1, 0) PROP_RadiusFixed (15) PROP_HeightFixed (35) PROP_Flags (MF_NOBLOCKMAP|MF_NOGRAVITY|MF_DROPOFF|MF_MISSILE) PROP_Flags2 (MF2_NOTELEPORT) PROP_RenderStyle (STYLE_Add) PROP_SpawnState (S_KBOLT1) END_DEFAULTS //============================================================================ //============================================================================ // // A_KoraxChase // //============================================================================ void A_KoraxChase (AActor *actor) { AActor *spot; if ((!actor->special2) && (actor->health <= (actor->GetDefault()->health/2))) { FActorIterator iterator (KORAX_FIRST_TELEPORT_TID); spot = iterator.Next (); if (spot != NULL) { P_Teleport (actor, spot->x, spot->y, ONFLOORZ, spot->angle, true, true, false); } P_StartScript (actor, NULL, 249, NULL, 0, 0, 0, 0, 0, false); actor->special2 = 1; // Don't run again return; } if (!actor->target) return; if (pr_koraxchase()<30) { actor->SetState (actor->MissileState); } else if (pr_koraxchase()<30) { S_Sound (actor, CHAN_VOICE, "KoraxActive", 1, ATTN_NONE); } // Teleport away if (actor->health < (actor->GetDefault()->health>>1)) { if (pr_koraxchase()<10) { FActorIterator iterator (KORAX_TELEPORT_TID); if (actor->tracer != NULL) { // Find the previous teleport destination do { spot = iterator.Next (); } while (spot != NULL && spot != actor->tracer); } // Go to the next teleport destination spot = iterator.Next (); actor->tracer = spot; if (spot) { P_Teleport (actor, spot->x, spot->y, ONFLOORZ, spot->angle, true, true, false); } } } } //============================================================================ // // A_KoraxStep // //============================================================================ void A_KoraxStep (AActor *actor) { A_Chase (actor); } //============================================================================ // // A_KoraxStep2 // //============================================================================ void A_KoraxStep2 (AActor *actor) { S_Sound (actor, CHAN_BODY, "KoraxStep", 1, ATTN_NONE); A_Chase (actor); } //============================================================================ // // A_KoraxBonePop // //============================================================================ void A_KoraxBonePop (AActor *actor) { AActor *mo; int i; // Spawn 6 spirits equalangularly for (i = 0; i < 6; ++i) { mo = P_SpawnMissileAngle (actor, RUNTIME_CLASS(AKoraxSpirit), ANGLE_60*i, 5*FRACUNIT); if (mo) KSpiritInit (mo, actor); } P_StartScript (actor, NULL, 255, NULL, 0, 0, 0, 0, false, false); // Death script } //============================================================================ // // KSpiritInit // //============================================================================ void KSpiritInit (AActor *spirit, AActor *korax) { spirit->health = KORAX_SPIRIT_LIFETIME; spirit->tracer = korax; // Swarm around korax spirit->special2 = 32+(pr_kspiritinit()&7); // Float bob index spirit->args[0] = 10; // initial turn value spirit->args[1] = 0; // initial look angle // Spawn a tail for spirit SpawnSpiritTail (spirit); } //============================================================================ // // A_KoraxDecide // //============================================================================ void A_KoraxDecide (AActor *actor) { if (pr_koraxdecide()<220) { actor->SetState (&AKorax::States[S_KORAX_MISSILE1]); } else { actor->SetState (&AKorax::States[S_KORAX_COMMAND1]); } } //============================================================================ // // A_KoraxMissile // //============================================================================ void A_KoraxMissile (AActor *actor) { static const struct { const char *type, *sound; } choices[6] = { { "WraithFX1", "WraithMissileFire" }, { "Demon1FX1", "DemonMissileFire" }, { "Demon2FX1", "DemonMissileFire" }, { "FireDemonMissile", "FireDemonAttack" }, { "CentaurFX", "CentaurLeaderAttack" }, { "SerpentFX", "CentaurLeaderAttack" } }; int type = pr_koraxmissile()%6; int i; const TypeInfo *info; S_Sound (actor, CHAN_VOICE, "KoraxAttack", 1, ATTN_NORM); info = TypeInfo::FindType (choices[type].type); if (info == NULL) { I_Error ("Unknown Korax missile: %s\n", choices[type].type); } // Fire all 6 missiles at once S_Sound (actor, CHAN_WEAPON, choices[type].sound, 1, ATTN_NONE); for (i = 0; i < 6; ++i) { KoraxFire (actor, info, i); } } //============================================================================ // // A_KoraxCommand // // Call action code scripts (250-254) // //============================================================================ void A_KoraxCommand (AActor *actor) { fixed_t x,y,z; angle_t ang; int numcommands; S_Sound (actor, CHAN_VOICE, "KoraxCommand", 1, ATTN_NORM); // Shoot stream of lightning to ceiling ang = (actor->angle - ANGLE_90) >> ANGLETOFINESHIFT; x = actor->x + KORAX_COMMAND_OFFSET * finecosine[ang]; y = actor->y + KORAX_COMMAND_OFFSET * finesine[ang]; z = actor->z + KORAX_COMMAND_HEIGHT*FRACUNIT; Spawn (x, y, z); if (actor->health <= (actor->GetDefault()->health >> 1)) { numcommands = 5; } else { numcommands = 4; } P_StartScript (actor, NULL, 250+(pr_koraxcommand()%numcommands), NULL, 0, 0, 0, 0, false, false); } //============================================================================ // // 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 (AActor *actor, const TypeInfo *type, int arm) { static const int extension[6] = { 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 fixed_t armheight[6] = { KORAX_ARM1_HEIGHT, KORAX_ARM2_HEIGHT, KORAX_ARM3_HEIGHT, KORAX_ARM4_HEIGHT, KORAX_ARM5_HEIGHT, KORAX_ARM6_HEIGHT }; angle_t ang; fixed_t x,y,z; ang = (actor->angle + (arm < 3 ? -KORAX_DELTAANGLE : KORAX_DELTAANGLE)) >> ANGLETOFINESHIFT; x = actor->x + extension[arm] * finecosine[ang]; y = actor->y + extension[arm] * finesine[ang]; z = actor->z - actor->floorclip + armheight[arm]; P_SpawnKoraxMissile (x, y, z, actor, actor->target, type); } //============================================================================ // // A_KSpiritWeave // //============================================================================ void A_KSpiritWeave (AActor *actor) { fixed_t newX, newY; int weaveXY, weaveZ; int angle; weaveXY = actor->special2>>16; weaveZ = actor->special2&0xFFFF; angle = (actor->angle+ANG90)>>ANGLETOFINESHIFT; newX = actor->x-FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]<<2); newY = actor->y-FixedMul(finesine[angle], FloatBobOffsets[weaveXY]<<2); weaveXY = (weaveXY+(pr_kspiritweave()%5))&63; newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]<<2); newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY]<<2); P_TryMove(actor, newX, newY, true); actor->z -= FloatBobOffsets[weaveZ]<<1; weaveZ = (weaveZ+(pr_kspiritweave()%5))&63; actor->z += FloatBobOffsets[weaveZ]<<1; actor->special2 = weaveZ+(weaveXY<<16); } //============================================================================ // // A_KSpiritSeeker // //============================================================================ void A_KSpiritSeeker (AActor *actor, angle_t thresh, angle_t turnMax) { int dir; int dist; angle_t delta; angle_t angle; AActor *target; fixed_t newZ; fixed_t deltaZ; target = actor->tracer; if (target == NULL) { return; } dir = P_FaceMobj (actor, target, &delta); if (delta > thresh) { delta >>= 1; if(delta > turnMax) { delta = turnMax; } } if(dir) { // Turn clockwise actor->angle += delta; } else { // Turn counter clockwise actor->angle -= delta; } angle = actor->angle>>ANGLETOFINESHIFT; actor->momx = FixedMul (actor->Speed, finecosine[angle]); actor->momy = FixedMul (actor->Speed, finesine[angle]); if (!(level.time&15) || actor->z > target->z+(target->GetDefault()->height) || actor->z+actor->height < target->z) { newZ = target->z+((pr_kspiritseek()*target->GetDefault()->height)>>8); deltaZ = newZ-actor->z; if (abs(deltaZ) > 15*FRACUNIT) { if(deltaZ > 0) { deltaZ = 15*FRACUNIT; } else { deltaZ = -15*FRACUNIT; } } dist = P_AproxDistance (target->x-actor->x, target->y-actor->y); dist = dist/actor->Speed; if (dist < 1) { dist = 1; } actor->momz = deltaZ/dist; } return; } //============================================================================ // // A_KSpiritRoam // //============================================================================ void A_KSpiritRoam (AActor *actor) { if (actor->health-- <= 0) { S_Sound (actor, CHAN_VOICE, "SpiritDie", 1, ATTN_NORM); actor->SetState (&AKoraxSpirit::States[S_KSPIRIT_DEATH]); } else { if (actor->tracer) { A_KSpiritSeeker (actor, actor->args[0]*ANGLE_1, actor->args[0]*ANGLE_1*2); } A_KSpiritWeave (actor); if (pr_kspiritroam()<50) { S_Sound (actor, CHAN_VOICE, "SpiritActive", 1, ATTN_NONE); } } } //============================================================================ // // A_KBolt // //============================================================================ void A_KBolt (AActor *actor) { // Countdown lifetime if (actor->special1-- <= 0) { actor->Destroy (); } } //============================================================================ // // A_KBoltRaise // //============================================================================ void A_KBoltRaise (AActor *actor) { AActor *mo; fixed_t z; // Spawn a child upward z = actor->z + KORAX_BOLT_HEIGHT; if ((z + KORAX_BOLT_HEIGHT) < actor->ceilingz) { mo = Spawn (actor->x, actor->y, z); if (mo) { mo->special1 = KORAX_BOLT_LIFETIME; } } else { // Maybe cap it off here } } //============================================================================ // // P_SpawnKoraxMissile // //============================================================================ AActor *P_SpawnKoraxMissile (fixed_t x, fixed_t y, fixed_t z, AActor *source, AActor *dest, const TypeInfo *type) { AActor *th; angle_t an; int dist; z -= source->floorclip; th = Spawn (type, x, y, z); if (th->SeeSound) { S_SoundID (th, CHAN_BODY, th->SeeSound, 1, ATTN_NORM); } th->target = source; // Originator an = R_PointToAngle2(x, y, dest->x, dest->y); if (dest->flags & MF_SHADOW) { // Invisible target an += pr_missile.Random2()<<21; } th->angle = an; an >>= ANGLETOFINESHIFT; th->momx = FixedMul (th->Speed, finecosine[an]); th->momy = FixedMul (th->Speed, finesine[an]); dist = P_AproxDistance (dest->x - x, dest->y - y); dist = dist/th->Speed; if (dist < 1) { dist = 1; } th->momz = (dest->z-z+(30*FRACUNIT))/dist; return (P_CheckMissileSpawn(th) ? th : NULL); }