mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-23 20:43:15 +00:00
1136 lines
23 KiB
Text
1136 lines
23 KiB
Text
|
|
// The Heresiarch him/itself ------------------------------------------------
|
|
|
|
//============================================================================
|
|
//
|
|
// Sorcerer stuff
|
|
//
|
|
// Sorcerer Variables
|
|
// specialf1 Angle of ball 1 (all others relative to that)
|
|
// StopBall which ball to stop at in stop mode (MT_???)
|
|
// args[0] Defense time
|
|
// args[1] Number of full rotations since stopping mode
|
|
// args[2] Target orbit speed for acceleration/deceleration
|
|
// args[3] Movement mode (see SORC_ macros)
|
|
// args[4] Current ball orbit speed
|
|
// Sorcerer Ball Variables
|
|
// specialf1 Previous angle of ball (for woosh)
|
|
// special2 Countdown of rapid fire (FX4)
|
|
//============================================================================
|
|
|
|
class Heresiarch : Actor
|
|
{
|
|
|
|
const SORCBALL_INITIAL_SPEED = 7;
|
|
const SORCBALL_TERMINAL_SPEED = 25;
|
|
const SORCBALL_SPEED_ROTATIONS = 5;
|
|
const SORC_DEFENSE_TIME = 255;
|
|
const SORC_DEFENSE_HEIGHT = 45;
|
|
const BOUNCE_TIME_UNIT = (35/2);
|
|
const SORCFX4_RAPIDFIRE_TIME = (6*3); // 3 seconds
|
|
const SORCFX4_SPREAD_ANGLE = 20;
|
|
|
|
enum ESorc
|
|
{
|
|
SORC_DECELERATE,
|
|
SORC_ACCELERATE,
|
|
SORC_STOPPING,
|
|
SORC_FIRESPELL,
|
|
SORC_STOPPED,
|
|
SORC_NORMAL,
|
|
SORC_FIRING_SPELL
|
|
}
|
|
|
|
const BALL1_ANGLEOFFSET = 0.;
|
|
const BALL2_ANGLEOFFSET = 120.;
|
|
const BALL3_ANGLEOFFSET = 240.;
|
|
|
|
double BallAngle;
|
|
class<SorcBall> StopBall;
|
|
|
|
Default
|
|
{
|
|
Health 5000;
|
|
Painchance 10;
|
|
Speed 16;
|
|
Radius 40;
|
|
Height 110;
|
|
Mass 500;
|
|
Damage 9;
|
|
Monster;
|
|
+FLOORCLIP
|
|
+BOSS
|
|
+DONTMORPH
|
|
+NOTARGET
|
|
+NOICEDEATH
|
|
+DEFLECT
|
|
+NOBLOOD
|
|
SeeSound "SorcererSight";
|
|
PainSound "SorcererPain";
|
|
DeathSound "SorcererDeathScream";
|
|
ActiveSound "SorcererActive";
|
|
Obituary "$OB_HERESIARCH";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SORC A 3;
|
|
SORC A 2 A_SorcSpinBalls;
|
|
Idle:
|
|
SORC A 10 A_Look;
|
|
Wait;
|
|
See:
|
|
SORC ABCD 5 A_Chase;
|
|
Loop;
|
|
Pain:
|
|
SORC G 8;
|
|
SORC G 8 A_Pain;
|
|
Goto See;
|
|
Missile:
|
|
SORC F 6 Bright A_FaceTarget;
|
|
SORC F 6 Bright A_SpeedBalls;
|
|
SORC F 6 Bright A_FaceTarget;
|
|
Wait;
|
|
Attack1:
|
|
SORC E 6 Bright;
|
|
SORC E 6 Bright A_SpawnFizzle;
|
|
SORC E 5 Bright A_FaceTarget;
|
|
Goto Attack1+1;
|
|
Attack2:
|
|
SORC E 2 Bright;
|
|
SORC E 2 Bright A_SorcBossAttack;
|
|
Goto See;
|
|
Death:
|
|
SORC H 5 Bright;
|
|
SORC I 5 Bright A_FaceTarget;
|
|
SORC J 5 Bright A_Scream;
|
|
SORC KLMNOPQRST 5 Bright;
|
|
SORC U 5 Bright A_NoBlocking;
|
|
SORC VWXY 5 Bright;
|
|
SORC Z -1 Bright;
|
|
Stop;
|
|
}
|
|
|
|
void Die (Actor source, Actor inflictor, int dmgflags)
|
|
{
|
|
// The heresiarch just executes a script instead of a special upon death
|
|
int script = special;
|
|
special = 0;
|
|
|
|
Super.Die (source, inflictor, dmgflags);
|
|
|
|
if (script != 0)
|
|
{
|
|
ACS_Execute(script, 0);
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_StopBalls
|
|
//
|
|
// Instant stop when rotation gets to ball in special2
|
|
// self is sorcerer
|
|
//
|
|
//============================================================================
|
|
|
|
void A_StopBalls()
|
|
{
|
|
int chance = random[Heresiarch]();
|
|
args[3] = SORC_STOPPING; // stopping mode
|
|
args[1] = 0; // Reset rotation counter
|
|
|
|
if ((args[0] <= 0) && (chance < 200))
|
|
{
|
|
StopBall = "SorcBall2"; // Blue
|
|
}
|
|
else if((health < (SpawnHealth() >> 1)) && (chance < 200))
|
|
{
|
|
StopBall = "SorcBall3"; // Green
|
|
}
|
|
else
|
|
{
|
|
StopBall = "SorcBall1"; // Yellow
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcSpinBalls
|
|
//
|
|
// Spawn spinning balls above head - actor is sorcerer
|
|
//============================================================================
|
|
|
|
void A_SorcSpinBalls()
|
|
{
|
|
A_SlowBalls();
|
|
args[0] = 0; // Currently no defense
|
|
args[3] = SORC_NORMAL;
|
|
args[4] = SORCBALL_INITIAL_SPEED; // Initial orbit speed
|
|
BallAngle = 1.;
|
|
|
|
Vector3 ballpos = (pos.xy, -Floorclip + Height);
|
|
|
|
Actor mo = Spawn("SorcBall1", pos, NO_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = self;
|
|
mo.special2 = SORCFX4_RAPIDFIRE_TIME;
|
|
}
|
|
mo = Spawn("SorcBall2", pos, NO_REPLACE);
|
|
if (mo) mo.target = self;
|
|
mo = Spawn("SorcBall3", pos, NO_REPLACE);
|
|
if (mo) mo.target = self;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SpeedBalls
|
|
//
|
|
// Set balls to speed mode - self is sorcerer
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SpeedBalls()
|
|
{
|
|
args[3] = SORC_ACCELERATE; // speed mode
|
|
args[2] = SORCBALL_TERMINAL_SPEED; // target speed
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SlowBalls
|
|
//
|
|
// Set balls to slow mode - actor is sorcerer
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SlowBalls()
|
|
{
|
|
args[3] = SORC_DECELERATE; // slow mode
|
|
args[2] = SORCBALL_INITIAL_SPEED; // target speed
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcBossAttack
|
|
//
|
|
// Resume ball spinning
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcBossAttack()
|
|
{
|
|
args[3] = SORC_ACCELERATE;
|
|
args[2] = SORCBALL_INITIAL_SPEED;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SpawnFizzle
|
|
//
|
|
// spell cast magic fizzle
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SpawnFizzle()
|
|
{
|
|
Vector3 pos = Vec3Angle(5., Angle, -Floorclip + Height / 2. );
|
|
for (int ix=0; ix<5; ix++)
|
|
{
|
|
Actor mo = Spawn("SorcSpark1", pos, ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
double rangle = Angle + (random[Heresiarch]() % 5) * (4096 / 360.);
|
|
mo.Vel.X = (random[Heresiarch]() % speed) * cos(rangle);
|
|
mo.Vel.Y = (random[Heresiarch]() % speed) * sin(rangle);
|
|
mo.Vel.Z = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Base class for the balls flying around the Heresiarch's head -------------
|
|
|
|
class SorcBall : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 10;
|
|
Radius 5;
|
|
Height 5;
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
+FULLVOLDEATH
|
|
+CANBOUNCEWATER
|
|
+NOWALLBOUNCESND
|
|
BounceType "HexenCompat";
|
|
SeeSound "SorcererBallBounce";
|
|
DeathSound "SorcererBigBallExplode";
|
|
}
|
|
|
|
double OldAngle, AngleOffset;
|
|
|
|
//============================================================================
|
|
//
|
|
// SorcBall::DoFireSpell
|
|
//
|
|
//============================================================================
|
|
|
|
virtual void DoFireSpell ()
|
|
{
|
|
CastSorcererSpell ();
|
|
target.args[3] = Heresiarch.SORC_STOPPED;
|
|
}
|
|
|
|
|
|
virtual void SorcUpdateBallAngle ()
|
|
{
|
|
}
|
|
|
|
override bool SpecialBlastHandling (Actor source, double strength)
|
|
{ // don't blast sorcerer balls
|
|
return false;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// ASorcBall::CastSorcererSpell
|
|
//
|
|
// Make noise and change the parent sorcerer's animation
|
|
//
|
|
//============================================================================
|
|
|
|
virtual void CastSorcererSpell ()
|
|
{
|
|
target.A_PlaySound ("SorcererSpellCast", CHAN_VOICE);
|
|
|
|
// Put sorcerer into throw spell animation
|
|
if (target.health > 0)
|
|
target.SetStateLabel ("Attack2");
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcBallOrbit
|
|
//
|
|
// - actor is ball
|
|
//============================================================================
|
|
|
|
void A_SorcBallOrbit()
|
|
{
|
|
// [RH] If no parent, then die instead of crashing
|
|
if (target == null || target.health <= 0)
|
|
{
|
|
SetStateLabel ("Pain");
|
|
return;
|
|
}
|
|
|
|
int mode = target.args[3];
|
|
Heresiarch parent = Heresiarch(target);
|
|
double dist = parent.radius - (radius*2);
|
|
|
|
double prevangle = OldAngle;
|
|
double baseangle = parent.BallAngle;
|
|
double curangle = baseangle + AngleOffset;
|
|
|
|
angle = curangle;
|
|
|
|
switch (mode)
|
|
{
|
|
case Heresiarch.SORC_NORMAL: // Balls rotating normally
|
|
SorcUpdateBallAngle ();
|
|
break;
|
|
|
|
case Heresiarch.SORC_DECELERATE: // Balls decelerating
|
|
A_DecelBalls();
|
|
SorcUpdateBallAngle ();
|
|
break;
|
|
|
|
case Heresiarch.SORC_ACCELERATE: // Balls accelerating
|
|
A_AccelBalls();
|
|
SorcUpdateBallAngle ();
|
|
break;
|
|
|
|
case Heresiarch.SORC_STOPPING: // Balls stopping
|
|
if ((parent.StopBall == GetClass()) &&
|
|
(parent.args[1] > Heresiarch.SORCBALL_SPEED_ROTATIONS) &&
|
|
absangle(curangle, parent.angle) < 42.1875)
|
|
{
|
|
// Can stop now
|
|
target.args[3] = Heresiarch.SORC_FIRESPELL;
|
|
target.args[4] = 0;
|
|
// Set angle so self angle == sorcerer angle
|
|
parent.BallAngle = parent.angle - AngleOffset;
|
|
}
|
|
else
|
|
{
|
|
SorcUpdateBallAngle ();
|
|
}
|
|
break;
|
|
|
|
case Heresiarch.SORC_FIRESPELL: // Casting spell
|
|
if (parent.StopBall == GetClass())
|
|
{
|
|
// Put sorcerer into special throw spell anim
|
|
if (parent.health > 0)
|
|
parent.SetStateLabel("Attack1");
|
|
|
|
DoFireSpell ();
|
|
}
|
|
break;
|
|
|
|
case Heresiarch.SORC_FIRING_SPELL:
|
|
if (parent.StopBall == GetClass())
|
|
{
|
|
if (special2-- <= 0)
|
|
{
|
|
// Done rapid firing
|
|
parent.args[3] = Heresiarch.SORC_STOPPED;
|
|
// Back to orbit balls
|
|
if (parent.health > 0)
|
|
parent.SetStateLabel("Attack2");
|
|
}
|
|
else
|
|
{
|
|
// Do rapid fire spell
|
|
A_SorcOffense2();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// The comparison here depends on binary angle semantics and cannot be done in floating point.
|
|
// It also requires very exact conversion that must be done natively.
|
|
if (BAM(curangle) < BAM(prevangle) && (parent.args[4] == Heresiarch.SORCBALL_TERMINAL_SPEED))
|
|
{
|
|
parent.args[1]++; // Bump rotation counter
|
|
// Completed full rotation - make woosh sound
|
|
A_PlaySound ("SorcererBallWoosh", CHAN_BODY);
|
|
}
|
|
OldAngle = curangle; // Set previous angle
|
|
|
|
Vector3 pos = parent.Vec3Angle(dist, curangle, -parent.Floorclip + parent.Height);
|
|
SetOrigin (pos, true);
|
|
floorz = parent.floorz;
|
|
ceilingz = parent.ceilingz;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcOffense2
|
|
//
|
|
// Actor is ball
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcOffense2()
|
|
{
|
|
Actor parent = target;
|
|
Actor dest = parent.target;
|
|
|
|
// [RH] If no enemy, then don't try to shoot.
|
|
if (dest == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int index = args[4];
|
|
args[4] = (args[4] + 15) & 255;
|
|
double delta = sin(index * (360 / 256.f)) * Heresiarch.SORCFX4_SPREAD_ANGLE;
|
|
|
|
double ang1 = Angle + delta;
|
|
Actor mo = parent.SpawnMissileAngle("SorcFX4", ang1, 0);
|
|
if (mo)
|
|
{
|
|
mo.special2 = 35*5/2; // 5 seconds
|
|
double dist = mo.DistanceBySpeed(dest, mo.Speed);
|
|
mo.Vel.Z = (dest.pos.z - mo.pos.z) / dist;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_AccelBalls
|
|
//
|
|
// Increase ball orbit speed - actor is ball
|
|
//
|
|
//============================================================================
|
|
|
|
void A_AccelBalls()
|
|
{
|
|
Heresiarch sorc = Heresiarch(target);
|
|
|
|
if (sorc.args[4] < sorc.args[2])
|
|
{
|
|
sorc.args[4]++;
|
|
}
|
|
else
|
|
{
|
|
sorc.args[3] = Heresiarch.SORC_NORMAL;
|
|
if (sorc.args[4] >= Heresiarch.SORCBALL_TERMINAL_SPEED)
|
|
{
|
|
// Reached terminal velocity - stop balls
|
|
sorc.A_StopBalls();
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_DecelBalls
|
|
//
|
|
// Decrease ball orbit speed - actor is ball
|
|
//
|
|
//============================================================================
|
|
|
|
void A_DecelBalls()
|
|
{
|
|
Actor sorc = target;
|
|
|
|
if (sorc.args[4] > sorc.args[2])
|
|
{
|
|
sorc.args[4]--;
|
|
}
|
|
else
|
|
{
|
|
sorc.args[3] = Heresiarch.SORC_NORMAL;
|
|
}
|
|
}
|
|
|
|
|
|
void A_SorcBallExplode()
|
|
{
|
|
bNoBounceSound = true;
|
|
A_Explode(255, 255);
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcBallPop
|
|
//
|
|
// Ball death - bounce away in a random direction
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcBallPop()
|
|
{
|
|
A_PlaySound ("SorcererBallPop", CHAN_BODY, 1, false, ATTN_NONE);
|
|
bNoGravity = false;
|
|
Gravity = 1. / 8;
|
|
|
|
Vel.X = ((random[Heresiarch]()%10)-5);
|
|
Vel.Y = ((random[Heresiarch]()%10)-5);
|
|
Vel.Z = (2+(random[Heresiarch]()%3));
|
|
args[4] = Heresiarch.BOUNCE_TIME_UNIT; // Bounce time unit
|
|
args[3] = 5; // Bounce time in seconds
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_BounceCheck
|
|
//
|
|
//============================================================================
|
|
|
|
void A_BounceCheck ()
|
|
{
|
|
if (args[4]-- <= 0)
|
|
{
|
|
if (args[3]-- <= 0)
|
|
{
|
|
SetStateLabel("Death");
|
|
A_PlaySound ("SorcererBigBallExplode", CHAN_BODY, 1, false, ATTN_NONE);
|
|
}
|
|
else
|
|
{
|
|
args[4] = Heresiarch.BOUNCE_TIME_UNIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// First ball (purple) - fires projectiles ----------------------------------
|
|
|
|
class SorcBall1 : SorcBall
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
SBMP ABCDEFGHIJKLMNOP 2 A_SorcBallOrbit;
|
|
Loop;
|
|
Pain:
|
|
SBMP A 5 A_SorcBallPop;
|
|
SBMP B 2 A_BounceCheck;
|
|
Wait;
|
|
Death:
|
|
SBS4 D 5 A_SorcBallExplode;
|
|
SBS4 E 5;
|
|
SBS4 FGH 6;
|
|
Stop;
|
|
}
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Super.BeginPlay ();
|
|
AngleOffset = Heresiarch.BALL1_ANGLEOFFSET;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// SorcBall1::CastSorcererSpell
|
|
//
|
|
// Offensive
|
|
//
|
|
//============================================================================
|
|
|
|
override void CastSorcererSpell ()
|
|
{
|
|
Super.CastSorcererSpell ();
|
|
|
|
Actor parent = target;
|
|
|
|
double ang1 = Angle + 70;
|
|
double ang2 = Angle - 70;
|
|
Class<Actor> cls = "SorcFX1";
|
|
Actor mo = parent.SpawnMissileAngle (cls, ang1, 0);
|
|
if (mo)
|
|
{
|
|
mo.target = parent;
|
|
mo.tracer = parent.target;
|
|
mo.args[4] = Heresiarch.BOUNCE_TIME_UNIT;
|
|
mo.args[3] = 15; // Bounce time in seconds
|
|
}
|
|
mo = parent.SpawnMissileAngle (cls, ang2, 0);
|
|
if (mo)
|
|
{
|
|
mo.target = parent;
|
|
mo.tracer = parent.target;
|
|
mo.args[4] = Heresiarch.BOUNCE_TIME_UNIT;
|
|
mo.args[3] = 15; // Bounce time in seconds
|
|
}
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// ASorcBall1::SorcUpdateBallAngle
|
|
//
|
|
// Update angle if first ball
|
|
//============================================================================
|
|
|
|
override void SorcUpdateBallAngle ()
|
|
{
|
|
(Heresiarch(target)).BallAngle += target.args[4];
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// SorcBall1::DoFireSpell
|
|
//
|
|
//============================================================================
|
|
|
|
override void DoFireSpell ()
|
|
{
|
|
if (random[Heresiarch]() < 200)
|
|
{
|
|
target.A_PlaySound ("SorcererSpellCast", CHAN_VOICE, 1, false, ATTN_NONE);
|
|
special2 = Heresiarch.SORCFX4_RAPIDFIRE_TIME;
|
|
args[4] = 128;
|
|
target.args[3] = Heresiarch.SORC_FIRING_SPELL;
|
|
}
|
|
else
|
|
{
|
|
Super.DoFireSpell ();
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Second ball (blue) - generates the shield --------------------------------
|
|
|
|
class SorcBall2 : SorcBall
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
SBMB ABCDEFGHIJKLMNOP 2 A_SorcBallOrbit;
|
|
Loop;
|
|
Pain:
|
|
SBMB A 5 A_SorcBallPop;
|
|
SBMB B 2 A_BounceCheck;
|
|
Wait;
|
|
Death:
|
|
SBS3 D 5 A_SorcBallExplode;
|
|
SBS3 E 5;
|
|
SBS3 FGH 6;
|
|
Stop;
|
|
}
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Super.BeginPlay ();
|
|
AngleOffset = Heresiarch.BALL2_ANGLEOFFSET;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// ASorcBall2::CastSorcererSpell
|
|
//
|
|
// Defensive
|
|
//
|
|
//============================================================================
|
|
|
|
override void CastSorcererSpell ()
|
|
{
|
|
Super.CastSorcererSpell ();
|
|
|
|
Actor parent = target;
|
|
Actor mo = Spawn("SorcFX2", Pos + (0, 0, parent.Floorclip + Heresiarch.SORC_DEFENSE_HEIGHT), ALLOW_REPLACE);
|
|
bReflective = true;
|
|
bInvulnerable = true;
|
|
parent.args[0] = Heresiarch.SORC_DEFENSE_TIME;
|
|
if (mo) mo.target = parent;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Third ball (green) - summons Bishops -------------------------------------
|
|
|
|
class SorcBall3 : SorcBall
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
SBMG ABCDEFGHIJKLMNOP 2 A_SorcBallOrbit;
|
|
Loop;
|
|
Pain:
|
|
SBMG A 5 A_SorcBallPop;
|
|
SBMG B 2 A_BounceCheck;
|
|
Wait;
|
|
Death:
|
|
SBS3 D 5 A_SorcBallExplode;
|
|
SBS3 E 5;
|
|
SBS3 FGH 6;
|
|
Stop;
|
|
}
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Super.BeginPlay ();
|
|
AngleOffset = Heresiarch.BALL3_ANGLEOFFSET;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// ASorcBall3::CastSorcererSpell
|
|
//
|
|
// Reinforcements
|
|
//
|
|
//============================================================================
|
|
|
|
override void CastSorcererSpell ()
|
|
{
|
|
Actor mo;
|
|
Super.CastSorcererSpell ();
|
|
Actor parent = target;
|
|
|
|
double ang1 = Angle - 45;
|
|
double ang2 = Angle + 45;
|
|
Class<Actor> cls = "SorcFX3";
|
|
if (health < (SpawnHealth()/3))
|
|
{ // Spawn 2 at a time
|
|
mo = parent.SpawnMissileAngle(cls, ang1, 4.);
|
|
if (mo) mo.target = parent;
|
|
mo = parent.SpawnMissileAngle(cls, ang2, 4.);
|
|
if (mo) mo.target = parent;
|
|
}
|
|
else
|
|
{
|
|
if (random[Heresiarch]() < 128) ang1 = ang2;
|
|
mo = parent.SpawnMissileAngle(cls, ang1, 4.);
|
|
if (mo) mo.target = parent;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Sorcerer spell 1 (The burning, bouncing head thing) ----------------------
|
|
|
|
class SorcFX1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 7;
|
|
Radius 5;
|
|
Height 5;
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
-NOGRAVITY
|
|
+FULLVOLDEATH
|
|
+CANBOUNCEWATER
|
|
+NOWALLBOUNCESND
|
|
BounceFactor 1.0;
|
|
BounceType "HexenCompat";
|
|
SeeSound "SorcererBallBounce";
|
|
DeathSound "SorcererHeadScream";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SBS1 A 2 Bright;
|
|
SBS1 BCD 3 Bright A_SorcFX1Seek;
|
|
Loop;
|
|
Death:
|
|
FHFX S 2 Bright A_Explode(30, 128);
|
|
FHFX SS 6 Bright;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcFX1Seek
|
|
//
|
|
// Yellow spell - offense
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcFX1Seek()
|
|
{
|
|
if (args[4]-- <= 0)
|
|
{
|
|
if (args[3]-- <= 0)
|
|
{
|
|
SetStateLabel("Death");
|
|
A_PlaySound ("SorcererHeadScream", CHAN_BODY, 1, false, ATTN_NONE);
|
|
}
|
|
else
|
|
{
|
|
args[4] = Heresiarch.BOUNCE_TIME_UNIT;
|
|
}
|
|
}
|
|
A_SeekerMissile(2, 6);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Sorcerer spell 2 (The visible part of the shield) ------------------------
|
|
|
|
class SorcFX2 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 15;
|
|
Radius 5;
|
|
Height 5;
|
|
+NOBLOCKMAP
|
|
+NOGRAVITY
|
|
+NOTELEPORT
|
|
}
|
|
|
|
states
|
|
{
|
|
Spawn:
|
|
SBS2 A 3 Bright A_SorcFX2Split;
|
|
Loop;
|
|
Orbit:
|
|
SBS2 A 2 Bright;
|
|
SBS2 BCDEFGHIJKLMNOPA 2 Bright A_SorcFX2Orbit;
|
|
Goto Orbit+1;
|
|
Death:
|
|
SBS2 A 10;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcFX2Split
|
|
//
|
|
// Blue spell - defense
|
|
//
|
|
//============================================================================
|
|
//
|
|
// FX2 Variables
|
|
// specialf1 current angle
|
|
// special2
|
|
// args[0] 0 = CW, 1 = CCW
|
|
// args[1]
|
|
//============================================================================
|
|
|
|
// Split ball in two
|
|
void A_SorcFX2Split()
|
|
{
|
|
Actor mo = Spawn(GetClass(), Pos, NO_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = target;
|
|
mo.args[0] = 0; // CW
|
|
mo.specialf1 = Angle; // Set angle
|
|
mo.SetStateLabel("Orbit");
|
|
}
|
|
mo = Spawn(GetClass(), Pos, NO_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = target;
|
|
mo.args[0] = 1; // CCW
|
|
mo.specialf1 = Angle; // Set angle
|
|
mo.SetStateLabel("Orbit");
|
|
}
|
|
Destroy ();
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcFX2Orbit
|
|
//
|
|
// Orbit FX2 about sorcerer
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcFX2Orbit ()
|
|
{
|
|
Actor parent = target;
|
|
|
|
// [RH] If no parent, then disappear
|
|
if (parent == null)
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
|
|
double dist = parent.radius;
|
|
|
|
if ((parent.health <= 0) || // Sorcerer is dead
|
|
(!parent.args[0])) // Time expired
|
|
{
|
|
SetStateLabel("Death");
|
|
parent.args[0] = 0;
|
|
parent.bReflective = false;
|
|
parent.bInvulnerable = false;
|
|
}
|
|
|
|
if (args[0] && (parent.args[0]-- <= 0)) // Time expired
|
|
{
|
|
SetStateLabel("Death");
|
|
parent.args[0] = 0;
|
|
parent.bReflective = false;
|
|
}
|
|
|
|
Vector3 posi;
|
|
// Move to new position based on angle
|
|
if (args[0]) // Counter clock-wise
|
|
{
|
|
specialf1 += 10;
|
|
angle = specialf1;
|
|
posi = parent.Vec3Angle(dist, angle, parent.Floorclip + Heresiarch.SORC_DEFENSE_HEIGHT);
|
|
posi.Z += 15 * cos(angle);
|
|
// Spawn trailer
|
|
Spawn("SorcFX2T1", pos, ALLOW_REPLACE);
|
|
}
|
|
else // Clock wise
|
|
{
|
|
specialf1 -= 10;
|
|
angle = specialf1;
|
|
posi = parent.Vec3Angle(dist, angle, parent.Floorclip + Heresiarch.SORC_DEFENSE_HEIGHT);
|
|
posi.Z += 20 * sin(angle);
|
|
// Spawn trailer
|
|
Spawn("SorcFX2T1", pos, ALLOW_REPLACE);
|
|
}
|
|
|
|
SetOrigin (posi, true);
|
|
floorz = parent.floorz;
|
|
ceilingz = parent.ceilingz;
|
|
}
|
|
}
|
|
|
|
// The translucent trail behind SorcFX2 -------------------------------------
|
|
|
|
class SorcFX2T1 : SorcFX2
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Translucent";
|
|
Alpha 0.4;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
Goto Death;
|
|
}
|
|
}
|
|
|
|
|
|
// Sorcerer spell 3 (The Bishop spawner) ------------------------------------
|
|
|
|
class SorcFX3 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 15;
|
|
Radius 22;
|
|
Height 65;
|
|
+NOBLOCKMAP
|
|
+MISSILE
|
|
+NOTELEPORT
|
|
SeeSound "SorcererBishopSpawn";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SBS3 ABC 2 Bright;
|
|
Loop;
|
|
Death:
|
|
SBS3 A 4 Bright;
|
|
BISH P 4 A_SorcererBishopEntry;
|
|
BISH ON 4;
|
|
BISH MLKJIH 3;
|
|
BISH G 3 A_SpawnBishop;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcererBishopEntry
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcererBishopEntry()
|
|
{
|
|
Spawn("SorcFX3Explosion", Pos, ALLOW_REPLACE);
|
|
A_PlaySound (SeeSound, CHAN_VOICE);
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SpawnBishop
|
|
//
|
|
// Green spell - spawn bishops
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SpawnBishop()
|
|
{
|
|
Actor mo = Spawn("Bishop", Pos, ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
if (!mo.TestMobjLocation())
|
|
{
|
|
mo.ClearCounters();
|
|
mo.Destroy ();
|
|
}
|
|
else if (target != null)
|
|
{ // [RH] Make the new bishops inherit the Heriarch's target
|
|
mo.CopyFriendliness (target, true);
|
|
mo.master = target;
|
|
}
|
|
}
|
|
Destroy ();
|
|
}
|
|
}
|
|
|
|
|
|
// The Bishop spawner's explosion animation ---------------------------------
|
|
|
|
class SorcFX3Explosion : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP
|
|
+NOGRAVITY
|
|
+NOTELEPORT
|
|
RenderStyle "Translucent";
|
|
Alpha 0.4;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SBS3 DEFGH 3;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
|
|
// Sorcerer spell 4 (The purple projectile) ---------------------------------
|
|
|
|
class SorcFX4 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 12;
|
|
Radius 10;
|
|
Height 10;
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
DeathSound "SorcererBallExplode";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SBS4 ABC 2 Bright A_SorcFX4Check;
|
|
Loop;
|
|
Death:
|
|
SBS4 D 2 Bright;
|
|
SBS4 E 2 Bright A_Explode(20, 128);
|
|
SBS4 FGH 2 Bright;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SorcFX4Check
|
|
//
|
|
// FX4 - rapid fire balls
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SorcFX4Check()
|
|
{
|
|
if (special2-- <= 0)
|
|
{
|
|
SetStateLabel ("Death");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Spark that appears when shooting SorcFX4 ---------------------------------
|
|
|
|
class SorcSpark1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 5;
|
|
Height 5;
|
|
Gravity 0.125;
|
|
+NOBLOCKMAP
|
|
+DROPOFF
|
|
+NOTELEPORT
|
|
RenderStyle "Add";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SBFX ABCDEFG 4 Bright;
|
|
Stop;
|
|
}
|
|
}
|