qzdoom/wadsrc/static/zscript/hexen/korax.txt
Christoph Oelckers 3d61d2c1f4 - reviewd script code for spawn calls that did not check their results.
Nothing should ever assume that spawning an actor is unconditionally successful. There can always be some edge cases where this is not the case.
2016-12-31 15:40:51 +01:00

534 lines
11 KiB
Text

//===========================================================================
// 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<Actor> 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<Actor> 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<Actor> 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<Actor> type)
{
Actor th = Spawn (type, pos, ALLOW_REPLACE);
if (th != null)
{
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;
}
}
}
}