- scriptified the Heresiarch.

This commit is contained in:
Christoph Oelckers 2016-11-29 17:17:10 +01:00
parent f5b3429274
commit f17f6c30c2
7 changed files with 461 additions and 596 deletions

View file

@ -859,7 +859,6 @@ set( NOT_COMPILED_SOURCE_FILES
${OTHER_SYSTEM_SOURCES}
sc_man_scanner.h
sc_man_scanner.re
g_hexen/a_heresiarch.cpp
g_hexen/a_spike.cpp
g_shared/sbarinfo_commands.cpp
xlat/xlat_parser.y

View file

@ -1,584 +0,0 @@
/*
#include "actor.h"
#include "info.h"
#include "p_local.h"
#include "s_sound.h"
#include "a_action.h"
#include "m_random.h"
#include "a_hexenglobal.h"
#include "i_system.h"
#include "vm.h"
#include "g_level.h"
*/
#define SORCBALL_INITIAL_SPEED 7
#define SORCBALL_TERMINAL_SPEED 25
#define SORCBALL_SPEED_ROTATIONS 5
#define SORC_DEFENSE_TIME 255
#define SORC_DEFENSE_HEIGHT 45
#define BOUNCE_TIME_UNIT (35/2)
#define SORCFX4_RAPIDFIRE_TIME (6*3) // 3 seconds
#define SORCFX4_SPREAD_ANGLE 20
#define SORC_DECELERATE 0
#define SORC_ACCELERATE 1
#define SORC_STOPPING 2
#define SORC_FIRESPELL 3
#define SORC_STOPPED 4
#define SORC_NORMAL 5
#define SORC_FIRING_SPELL 6
#define BALL1_ANGLEOFFSET 0.
#define BALL2_ANGLEOFFSET 120.
#define BALL3_ANGLEOFFSET 240.
void A_SlowBalls (AActor *actor);
void A_StopBalls (AActor *actor);
void A_AccelBalls (AActor *actor);
void A_DecelBalls (AActor *actor);
void A_SorcOffense2 (AActor *actor);
void A_DoBounceCheck (AActor *actor, const char *sound);
static FRandom pr_heresiarch ("Heresiarch");
// The Heresiarch him/itself ------------------------------------------------
class AHeresiarch : public AActor
{
DECLARE_CLASS (AHeresiarch, AActor)
public:
PClassActor *StopBall;
DAngle BallAngle;
void Serialize(FSerializer &arc);
void Die (AActor *source, AActor *inflictor, int dmgflags);
};
IMPLEMENT_CLASS(AHeresiarch, false, false)
void AHeresiarch::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("stopball", StopBall)
("ballangle", BallAngle);
}
void AHeresiarch::Die (AActor *source, AActor *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)
{
P_StartScript (this, NULL, script, level.MapName, NULL, 0, 0);
}
}
// Base class for the balls flying around the Heresiarch's head -------------
class ASorcBall : public AActor
{
DECLARE_CLASS (ASorcBall, AActor)
public:
virtual void DoFireSpell ();
virtual void SorcUpdateBallAngle ();
virtual void CastSorcererSpell ();
DAngle AngleOffset;
DAngle OldAngle;
void Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("angleoffset", AngleOffset)
("oldangle", OldAngle);
}
bool SpecialBlastHandling (AActor *source, double strength)
{ // don't blast sorcerer balls
return false;
}
};
IMPLEMENT_CLASS(ASorcBall, false, false)
// First ball (purple) - fires projectiles ----------------------------------
class ASorcBall1 : public ASorcBall
{
DECLARE_CLASS (ASorcBall1, ASorcBall)
public:
void BeginPlay ()
{
Super::BeginPlay ();
AngleOffset = BALL1_ANGLEOFFSET;
}
virtual void DoFireSpell ();
virtual void SorcUpdateBallAngle ();
virtual void CastSorcererSpell ();
};
IMPLEMENT_CLASS(ASorcBall1, false, false)
// Second ball (blue) - generates the shield --------------------------------
class ASorcBall2 : public ASorcBall
{
DECLARE_CLASS (ASorcBall2, ASorcBall)
public:
void BeginPlay ()
{
Super::BeginPlay ();
AngleOffset = BALL2_ANGLEOFFSET;
}
virtual void CastSorcererSpell ();
};
IMPLEMENT_CLASS(ASorcBall2, false, false)
// Third ball (green) - summons Bishops -------------------------------------
class ASorcBall3 : public ASorcBall
{
DECLARE_CLASS (ASorcBall3, ASorcBall)
public:
void BeginPlay ()
{
Super::BeginPlay ();
AngleOffset = BALL3_ANGLEOFFSET;
}
virtual void CastSorcererSpell ();
};
IMPLEMENT_CLASS(ASorcBall3, false, false)
// Sorcerer spell 1 (The burning, bouncing head thing) ----------------------
//============================================================================
//
// SorcBall::DoFireSpell
//
//============================================================================
void ASorcBall::DoFireSpell ()
{
CastSorcererSpell ();
target->args[3] = SORC_STOPPED;
}
//============================================================================
//
// SorcBall1::DoFireSpell
//
//============================================================================
void ASorcBall1::DoFireSpell ()
{
if (pr_heresiarch() < 200)
{
S_Sound (target, CHAN_VOICE, "SorcererSpellCast", 1, ATTN_NONE);
special2 = SORCFX4_RAPIDFIRE_TIME;
args[4] = 128;
target->args[3] = SORC_FIRING_SPELL;
}
else
{
Super::DoFireSpell ();
}
}
DEFINE_FIELD(AHeresiarch, BallAngle);
//============================================================================
//
// A_SorcBallOrbit
//
// - actor is ball
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SorcBallOrbit)
{
PARAM_SELF_PROLOGUE(ASorcBall);
// [RH] If no parent, then die instead of crashing
if (self->target == NULL)
{
self->SetState (self->FindState(NAME_Pain));
return 0;
}
int mode = self->target->args[3];
AHeresiarch *parent = barrier_cast<AHeresiarch *>(self->target);
double dist = parent->radius - (self->radius*2);
#if 0
// This cannot happen anymore because this is defined locally in SorcBall
if (!self->IsKindOf (RUNTIME_CLASS(ASorcBall)))
{
I_Error ("Corrupted sorcerer:\nTried to use a %s", self->GetClass()->TypeName.GetChars());
}
#endif
if (self->target->health <= 0)
{
self->SetState (self->FindState(NAME_Pain));
return 0;
}
DAngle prevangle = self->OldAngle;
DAngle baseangle = parent->BallAngle;
DAngle angle = baseangle + self->AngleOffset;
self->Angles.Yaw = angle;
switch (mode)
{
case SORC_NORMAL: // Balls rotating normally
self->SorcUpdateBallAngle ();
break;
case SORC_DECELERATE: // Balls decelerating
A_DecelBalls(self);
self->SorcUpdateBallAngle ();
break;
case SORC_ACCELERATE: // Balls accelerating
A_AccelBalls(self);
self->SorcUpdateBallAngle ();
break;
case SORC_STOPPING: // Balls stopping
if ((parent->StopBall == self->GetClass()) &&
(parent->args[1] > SORCBALL_SPEED_ROTATIONS) &&
absangle(angle, parent->Angles.Yaw) < 42.1875)
{
// Can stop now
self->target->args[3] = SORC_FIRESPELL;
self->target->args[4] = 0;
// Set angle so self angle == sorcerer angle
parent->BallAngle = parent->Angles.Yaw - self->AngleOffset;
}
else
{
self->SorcUpdateBallAngle ();
}
break;
case SORC_FIRESPELL: // Casting spell
if (parent->StopBall == self->GetClass())
{
// Put sorcerer into special throw spell anim
if (parent->health > 0)
parent->SetState (parent->FindState("Attack1"));
self->DoFireSpell ();
}
break;
case SORC_FIRING_SPELL:
if (parent->StopBall == self->GetClass())
{
if (self->special2-- <= 0)
{
// Done rapid firing
parent->args[3] = SORC_STOPPED;
// Back to orbit balls
if (parent->health > 0)
parent->SetState (parent->FindState("Attack2"));
}
else
{
// Do rapid fire spell
A_SorcOffense2(self);
}
}
break;
case SORC_STOPPED: // Balls stopped
default:
break;
}
if ( angle.BAMs() < prevangle.BAMs() && (parent->args[4]==SORCBALL_TERMINAL_SPEED))
{
parent->args[1]++; // Bump rotation counter
// Completed full rotation - make woosh sound
S_Sound (self, CHAN_BODY, "SorcererBallWoosh", 1, ATTN_NORM);
}
self->OldAngle = angle; // Set previous angle
DVector3 pos = parent->Vec3Angle(dist, angle, -parent->Floorclip + parent->Height);
self->SetOrigin (pos, true);
self->floorz = parent->floorz;
self->ceilingz = parent->ceilingz;
return 0;
}
//============================================================================
//
// A_StopBalls
//
// Instant stop when rotation gets to ball in special2
// self is sorcerer
//
//============================================================================
void A_StopBalls(AActor *scary)
{
AHeresiarch *self = static_cast<AHeresiarch *> (scary);
int chance = pr_heresiarch();
self->args[3] = SORC_STOPPING; // stopping mode
self->args[1] = 0; // Reset rotation counter
if ((self->args[0] <= 0) && (chance < 200))
{
self->StopBall = RUNTIME_CLASS(ASorcBall2); // Blue
}
else if((self->health < (self->SpawnHealth() >> 1)) &&
(chance < 200))
{
self->StopBall = RUNTIME_CLASS(ASorcBall3); // Green
}
else
{
self->StopBall = RUNTIME_CLASS(ASorcBall1); // Yellow
}
}
//============================================================================
//
// A_AccelBalls
//
// Increase ball orbit speed - actor is ball
//
//============================================================================
void A_AccelBalls(AActor *self)
{
AActor *sorc = self->target;
if (sorc->args[4] < sorc->args[2])
{
sorc->args[4]++;
}
else
{
sorc->args[3] = SORC_NORMAL;
if (sorc->args[4] >= SORCBALL_TERMINAL_SPEED)
{
// Reached terminal velocity - stop balls
A_StopBalls(sorc);
}
}
}
//============================================================================
//
// A_DecelBalls
//
// Decrease ball orbit speed - actor is ball
//
//============================================================================
void A_DecelBalls(AActor *self)
{
AActor *sorc = self->target;
if (sorc->args[4] > sorc->args[2])
{
sorc->args[4]--;
}
else
{
sorc->args[3] = SORC_NORMAL;
}
}
//============================================================================
//
// ASorcBall1::SorcUpdateBallAngle
//
// Update angle if first ball
//============================================================================
void ASorcBall1::SorcUpdateBallAngle ()
{
barrier_cast<AHeresiarch*>(target)->BallAngle += target->args[4];
}
//============================================================================
//
// ASorcBall::SorcUpdateBallAngle
//
//============================================================================
void ASorcBall::SorcUpdateBallAngle ()
{
}
//============================================================================
//
// ASorcBall::CastSorcererSpell
//
// Make noise and change the parent sorcerer's animation
//
//============================================================================
void ASorcBall::CastSorcererSpell ()
{
S_Sound (target, CHAN_VOICE, "SorcererSpellCast", 1, ATTN_NONE);
// Put sorcerer into throw spell animation
if (target->health > 0)
target->SetState (target->FindState("Attack2"));
}
//============================================================================
//
// ASorcBall2::CastSorcererSpell
//
// Defensive
//
//============================================================================
void ASorcBall2::CastSorcererSpell ()
{
Super::CastSorcererSpell ();
AActor *parent = target;
AActor *mo;
mo = Spawn("SorcFX2", PosPlusZ(parent->Floorclip + SORC_DEFENSE_HEIGHT), ALLOW_REPLACE);
parent->flags2 |= MF2_REFLECTIVE|MF2_INVULNERABLE;
parent->args[0] = SORC_DEFENSE_TIME;
if (mo) mo->target = parent;
}
//============================================================================
//
// ASorcBall3::CastSorcererSpell
//
// Reinforcements
//
//============================================================================
void ASorcBall3::CastSorcererSpell ()
{
Super::CastSorcererSpell ();
AActor *mo;
DAngle ang1, ang2;
AActor *parent = target;
ang1 = Angles.Yaw.Degrees - 45;
ang2 = Angles.Yaw.Degrees + 45;
PClassActor *cls = PClass::FindActor("SorcFX3");
if (health < (SpawnHealth()/3))
{ // Spawn 2 at a time
mo = P_SpawnMissileAngle(parent, cls, ang1, 4.);
if (mo) mo->target = parent;
mo = P_SpawnMissileAngle(parent, cls, ang2, 4.);
if (mo) mo->target = parent;
}
else
{
if (pr_heresiarch() < 128)
ang1 = ang2;
mo = P_SpawnMissileAngle(parent, cls, ang1, 4.);
if (mo) mo->target = parent;
}
}
/*
void A_SpawnReinforcements(AActor *actor)
{
AActor *parent = self->target;
AActor *mo;
DAngle ang;
ang = P_Random();
mo = P_SpawnMissileAngle(actor, MT_SORCFX3, ang, 5.);
if (mo) mo->target = parent;
}
*/
//============================================================================
//
// SorcBall1::CastSorcererSpell
//
// Offensive
//
//============================================================================
void ASorcBall1::CastSorcererSpell ()
{
Super::CastSorcererSpell ();
AActor *mo;
DAngle ang1, ang2;
AActor *parent = target;
ang1 = Angles.Yaw.Degrees + 70;
ang2 = Angles.Yaw.Degrees - 70;
PClassActor *cls = PClass::FindActor("SorcFX1");
mo = P_SpawnMissileAngle (parent, cls, ang1, 0);
if (mo)
{
mo->target = parent;
mo->tracer = parent->target;
mo->args[4] = BOUNCE_TIME_UNIT;
mo->args[3] = 15; // Bounce time in seconds
}
mo = P_SpawnMissileAngle (parent, cls, ang2, 0);
if (mo)
{
mo->target = parent;
mo->tracer = parent->target;
mo->args[4] = BOUNCE_TIME_UNIT;
mo->args[3] = 15; // Bounce time in seconds
}
}
//============================================================================
//
// A_SorcOffense2
//
// Actor is ball
//
//============================================================================
void A_SorcOffense2(AActor *self)
{
DAngle ang1;
AActor *mo;
double delta;
int index;
AActor *parent = self->target;
AActor *dest = parent->target;
double dist;
// [RH] If no enemy, then don't try to shoot.
if (dest == NULL)
{
return;
}
index = self->args[4];
self->args[4] = (self->args[4] + 15) & 255;
delta = DAngle(index * (360 / 256.f)).Sin() * SORCFX4_SPREAD_ANGLE;
ang1 = self->Angles.Yaw + delta;
mo = P_SpawnMissileAngle(parent, PClass::FindActor("SorcFX4"), ang1, 0);
if (mo)
{
mo->special2 = 35*5/2; // 5 seconds
dist = mo->DistanceBySpeed(dest, mo->Speed);
mo->Vel.Z = (dest->Z() - mo->Z()) / dist;
}
}

View file

@ -24,5 +24,4 @@
#include "a_pickups.h"
// Include all the Hexen stuff here to reduce compile time
#include "a_heresiarch.cpp"
#include "a_spike.cpp"

View file

@ -842,6 +842,13 @@ DEFINE_ACTION_FUNCTION(DObject, GameType)
ACTION_RETURN_INT(gameinfo.gametype);
}
DEFINE_ACTION_FUNCTION(DObject, BAM)
{
PARAM_PROLOGUE;
PARAM_FLOAT(ang);
ACTION_RETURN_INT(DAngle(ang).BAMs());
}
DEFINE_ACTION_FUNCTION(FStringTable, Localize)
{
PARAM_PROLOGUE;
@ -856,4 +863,5 @@ DEFINE_ACTION_FUNCTION(FString, Replace)
PARAM_STRING(s2);
self->Substitute(*s1, *s2);
return 0;
}
}

View file

@ -2285,6 +2285,25 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
sym->AddVariant(NewPrototype(rets, args), argflags, argnames, afd == nullptr ? nullptr : *(afd->VMPointer), varflags, useflags);
c->Type()->Symbols.ReplaceSymbol(sym);
auto cls = dyn_cast<PClass>(c->Type());
PFunction *virtsym = nullptr;
if (cls != nullptr && cls->ParentClass != nullptr) virtsym = dyn_cast<PFunction>(cls->ParentClass->Symbols.FindSymbol(FName(f->Name), true));
unsigned vindex = ~0u;
if (virtsym != nullptr) vindex = virtsym->Variants[0].Implementation->VirtualIndex;
if (vindex != ~0u || (varflags & VARF_Virtual))
{
// Todo: Check if the declaration is legal.
// First step: compare prototypes - if they do not match the virtual base method does not apply.
// Second step: Check flags. Possible cases:
// 1. Base method is final: Error.
// 2. This method is override: Base virtual method must exist
// 3. This method is virtual but not override: Base may not have a virtual method with the same prototype.
}
if (!(f->Flags & ZCC_Native))
{
if (f->Body == nullptr)

View file

@ -10,6 +10,7 @@ class Object native
native static int GameType();
native static void S_Sound (Sound sound_id, int channel, float volume = 1, float attenuation = ATTN_NORM);
native static void C_MidPrint(string fontname, string textlabel, bool bold = false); // always uses the stringtable.
native static uint BAM(double angle);
/*virtual*/ native void Destroy();
}

View file

@ -18,7 +18,7 @@
// special2 Countdown of rapid fire (FX4)
//============================================================================
class Heresiarch native
class Heresiarch : Actor
{
const SORCBALL_INITIAL_SPEED = 7;
@ -45,7 +45,8 @@ class Heresiarch native
const BALL2_ANGLEOFFSET = 120.;
const BALL3_ANGLEOFFSET = 240.;
native double BallAngle;
double BallAngle;
class<SorcBall> StopBall;
Default
{
@ -110,7 +111,50 @@ class Heresiarch native
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
@ -213,7 +257,7 @@ class Heresiarch native
// Base class for the balls flying around the Heresiarch's head -------------
class SorcBall native
class SorcBall : Actor
{
Default
{
@ -231,8 +275,239 @@ class SorcBall native
DeathSound "SorcererBigBallExplode";
}
native void A_SorcBallOrbit();
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;
@ -286,7 +561,7 @@ class SorcBall native
// First ball (purple) - fires projectiles ----------------------------------
class SorcBall1 : SorcBall native
class SorcBall1 : SorcBall
{
States
{
@ -303,12 +578,90 @@ class SorcBall1 : SorcBall native
SBS4 FGH 6;
Stop;
}
override void BeginPlay ()
{
Super.BeginPlay ();
AngleOffset = Heresiarch.BALL1_ANGLEOFFSET;
A_Log("Ball1 begins " .. 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 native
class SorcBall2 : SorcBall
{
States
{
@ -325,11 +678,40 @@ class SorcBall2 : SorcBall native
SBS3 FGH 6;
Stop;
}
override void BeginPlay ()
{
Super.BeginPlay ();
AngleOffset = Heresiarch.BALL2_ANGLEOFFSET;
A_Log("Ball2 begins " .. 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 native
class SorcBall3 : SorcBall
{
States
{
@ -346,6 +728,47 @@ class SorcBall3 : SorcBall native
SBS3 FGH 6;
Stop;
}
override void BeginPlay ()
{
Super.BeginPlay ();
AngleOffset = Heresiarch.BALL3_ANGLEOFFSET;
A_Log("Ball3 begins " .. 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;
}
}
}
@ -489,7 +912,7 @@ class SorcFX2 : Actor
Actor parent = target;
// [RH] If no parent, then disappear
if (parent == NULL)
if (parent == null)
{
Destroy();
return;
@ -616,7 +1039,7 @@ class SorcFX3 : Actor
mo.ClearCounters();
mo.Destroy ();
}
else if (target != NULL)
else if (target != null)
{ // [RH] Make the new bishops inherit the Heriarch's target
mo.CopyFriendliness (target, true);
mo.master = target;