qzdoom-gpl/src/g_hexen/a_korax.cpp
2016-04-14 00:21:38 +02:00

499 lines
12 KiB
C++

//===========================================================================
// 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 "a_action.h"
#include "m_random.h"
#include "i_system.h"
#include "thingdef/thingdef.h"
#include "g_level.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;
const int KORAX_ARM_EXTENSION_SHORT = 40;
const int KORAX_ARM_EXTENSION_LONG = 55;
const int KORAX_ARM1_HEIGHT = 108;
const int KORAX_ARM2_HEIGHT = 82;
const int KORAX_ARM3_HEIGHT = 54;
const int KORAX_ARM4_HEIGHT = 104;
const int KORAX_ARM5_HEIGHT = 86;
const int KORAX_ARM6_HEIGHT = 53;
const double KORAX_BOLT_HEIGHT = 48.;
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_kmissile ("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, PClassActor *type, int arm);
void KSpiritInit (AActor *spirit, AActor *korax);
AActor *P_SpawnKoraxMissile (const DVector3 &pos, AActor *source, AActor *dest, PClassActor *type);
extern void SpawnSpiritTail (AActor *spirit);
//============================================================================
//
// A_KoraxChase
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KoraxChase)
{
PARAM_ACTION_PROLOGUE;
AActor *spot;
if ((!self->special2) && (self->health <= (self->SpawnHealth()/2)))
{
FActorIterator iterator (KORAX_FIRST_TELEPORT_TID);
spot = iterator.Next ();
if (spot != NULL)
{
P_Teleport (self, spot->PosAtZ(ONFLOORZ), spot->Angles.Yaw, TELF_SOURCEFOG | TELF_DESTFOG);
}
P_StartScript (self, NULL, 249, NULL, NULL, 0, 0);
self->special2 = 1; // Don't run again
return 0;
}
if (self->target == NULL)
{
return 0;
}
if (pr_koraxchase()<30)
{
self->SetState (self->MissileState);
}
else if (pr_koraxchase()<30)
{
S_Sound (self, CHAN_VOICE, "KoraxActive", 1, ATTN_NONE);
}
// Teleport away
if (self->health < (self->SpawnHealth()>>1))
{
if (pr_koraxchase()<10)
{
FActorIterator iterator (KORAX_TELEPORT_TID);
if (self->tracer != NULL)
{ // Find the previous teleport destination
do
{
spot = iterator.Next ();
} while (spot != NULL && spot != self->tracer);
}
// Go to the next teleport destination
spot = iterator.Next ();
self->tracer = spot;
if (spot)
{
P_Teleport (self, spot->PosAtZ(ONFLOORZ), spot->Angles.Yaw, TELF_SOURCEFOG | TELF_DESTFOG);
}
}
}
return 0;
}
//============================================================================
//
// A_KoraxBonePop
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KoraxBonePop)
{
PARAM_ACTION_PROLOGUE;
AActor *mo;
int i;
// Spawn 6 spirits equalangularly
for (i = 0; i < 6; ++i)
{
mo = P_SpawnMissileAngle (self, PClass::FindActor("KoraxSpirit"), DAngle(60.*i), 5.);
if (mo)
{
KSpiritInit (mo, self);
}
}
P_StartScript (self, NULL, 255, NULL, NULL, 0, 0); // Death script
return 0;
}
//============================================================================
//
// KSpiritInit
//
//============================================================================
void KSpiritInit (AActor *spirit, AActor *korax)
{
spirit->health = KORAX_SPIRIT_LIFETIME;
spirit->tracer = korax; // Swarm around korax
spirit->WeaveIndexZ = 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
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KoraxDecide)
{
PARAM_ACTION_PROLOGUE;
if (pr_koraxdecide()<220)
{
self->SetState (self->FindState("Attack"));
}
else
{
self->SetState (self->FindState("Command"));
}
return 0;
}
//============================================================================
//
// A_KoraxMissile
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KoraxMissile)
{
PARAM_ACTION_PROLOGUE;
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;
PClassActor *info;
S_Sound(self, CHAN_VOICE, "KoraxAttack", 1, ATTN_NORM);
info = PClass::FindActor(choices[type].type);
if (info == NULL)
{
I_Error("Unknown Korax missile: %s\n", choices[type].type);
}
// Fire all 6 missiles at once
S_Sound(self, CHAN_WEAPON, choices[type].sound, 1, ATTN_NONE);
for (i = 0; i < 6; ++i)
{
KoraxFire(self, info, i);
}
return 0;
}
//============================================================================
//
// A_KoraxCommand
//
// Call action code scripts (250-254)
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KoraxCommand)
{
PARAM_ACTION_PROLOGUE;
DAngle ang;
int numcommands;
S_Sound (self, CHAN_VOICE, "KoraxCommand", 1, ATTN_NORM);
// Shoot stream of lightning to ceiling
ang = self->Angles.Yaw - 90;
DVector3 pos = self->Vec3Angle(KORAX_COMMAND_OFFSET, ang, KORAX_COMMAND_HEIGHT);
Spawn("KoraxBolt", pos, ALLOW_REPLACE);
if (self->health <= (self->SpawnHealth() >> 1))
{
numcommands = 5;
}
else
{
numcommands = 4;
}
P_StartScript (self, NULL, 250+(pr_koraxcommand()%numcommands), NULL, NULL, 0, 0);
return 0;
}
//============================================================================
//
// 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, PClassActor *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 int armheight[6] =
{
KORAX_ARM1_HEIGHT,
KORAX_ARM2_HEIGHT,
KORAX_ARM3_HEIGHT,
KORAX_ARM4_HEIGHT,
KORAX_ARM5_HEIGHT,
KORAX_ARM6_HEIGHT
};
DAngle ang = actor->Angles.Yaw + (arm < 3 ? -KORAX_DELTAANGLE : KORAX_DELTAANGLE);
DVector3 pos = actor->Vec3Angle(extension[arm], ang, armheight[arm] - actor->Floorclip);
P_SpawnKoraxMissile (pos, actor, actor->target, type);
}
//============================================================================
//
// A_KSpiritSeeker
//
//============================================================================
static void A_KSpiritSeeker (AActor *actor, DAngle thresh, DAngle turnMax)
{
int dir;
DAngle delta;
AActor *target;
double newZ;
double deltaZ;
target = actor->tracer;
if (target == NULL)
{
return;
}
dir = P_FaceMobj (actor, target, &delta);
if (delta > thresh)
{
delta /= 2;
if(delta > turnMax)
{
delta = turnMax;
}
}
if(dir)
{ // Turn clockwise
actor->Angles.Yaw += delta;
}
else
{ // Turn counter clockwise
actor->Angles.Yaw -= delta;
}
actor->VelFromAngle();
if (!(level.time&15)
|| actor->Z() > target->Z() + target->GetDefault()->Height
|| actor->Top() < target->Z())
{
newZ = target->Z() + pr_kspiritseek() * target->GetDefault()->Height / 256;
deltaZ = newZ-actor->Z();
if (fabs(deltaZ) > 15)
{
if(deltaZ > 0)
{
deltaZ = 15;
}
else
{
deltaZ = -15;
}
}
actor->Vel.Z = deltaZ + actor->DistanceBySpeed(target, actor->Speed);
}
return;
}
//============================================================================
//
// A_KSpiritRoam
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KSpiritRoam)
{
PARAM_ACTION_PROLOGUE;
if (self->health-- <= 0)
{
S_Sound (self, CHAN_VOICE, "SpiritDie", 1, ATTN_NORM);
self->SetState (self->FindState("Death"));
}
else
{
if (self->tracer)
{
A_KSpiritSeeker(self, (double)self->args[0], self->args[0] * 2.);
}
int xyspeed = (pr_kspiritweave() % 5);
int zspeed = (pr_kspiritweave() % 5);
A_Weave(self, xyspeed, zspeed, 4., 2.);
if (pr_kspiritroam()<50)
{
S_Sound (self, CHAN_VOICE, "SpiritActive", 1, ATTN_NONE);
}
}
return 0;
}
//============================================================================
//
// A_KBolt
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KBolt)
{
PARAM_ACTION_PROLOGUE;
// Countdown lifetime
if (self->special1-- <= 0)
{
self->Destroy ();
}
return 0;
}
//============================================================================
//
// A_KBoltRaise
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KBoltRaise)
{
PARAM_ACTION_PROLOGUE;
AActor *mo;
// Spawn a child upward
double z = self->Z() + KORAX_BOLT_HEIGHT;
if ((z + KORAX_BOLT_HEIGHT) < self->ceilingz)
{
mo = Spawn("KoraxBolt", self->PosAtZ(z), ALLOW_REPLACE);
if (mo)
{
mo->special1 = KORAX_BOLT_LIFETIME;
}
}
else
{
// Maybe cap it off here
}
return 0;
}
//============================================================================
//
// P_SpawnKoraxMissile
//
//============================================================================
AActor *P_SpawnKoraxMissile (const DVector3 &pos, AActor *source, AActor *dest, PClassActor *type)
{
AActor *th;
DAngle an;
double dist;
th = Spawn (type, pos, ALLOW_REPLACE);
th->target = source; // Originator
an = th->AngleTo(dest);
if (dest->flags & MF_SHADOW)
{ // Invisible target
an += pr_kmissile.Random2() * (45/256.);
}
th->Angles.Yaw = an;
th->VelFromAngle();
dist = dest->DistanceBySpeed(th, th->Speed);
th->Vel.Z = (dest->Z() - pos.Z + 30) / dist;
return (P_CheckMissileSpawn(th, source->radius) ? th : NULL);
}