From f17f6c30c2faa4eb464ea407332f38e189780094 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 29 Nov 2016 17:17:10 +0100 Subject: [PATCH] - scriptified the Heresiarch. --- src/CMakeLists.txt | 1 - src/g_hexen/a_heresiarch.cpp | 584 --------------------- src/g_hexen/a_hexenmisc.cpp | 1 - src/scripting/thingdef_data.cpp | 10 +- src/scripting/zscript/zcc_compile.cpp | 19 + wadsrc/static/zscript/base.txt | 1 + wadsrc/static/zscript/hexen/heresiarch.txt | 441 +++++++++++++++- 7 files changed, 461 insertions(+), 596 deletions(-) delete mode 100644 src/g_hexen/a_heresiarch.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d0474794..c7228edd8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/g_hexen/a_heresiarch.cpp b/src/g_hexen/a_heresiarch.cpp deleted file mode 100644 index 38d916094..000000000 --- a/src/g_hexen/a_heresiarch.cpp +++ /dev/null @@ -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(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 (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(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; - } -} - diff --git a/src/g_hexen/a_hexenmisc.cpp b/src/g_hexen/a_hexenmisc.cpp index f871e82d9..1f4aa18eb 100644 --- a/src/g_hexen/a_hexenmisc.cpp +++ b/src/g_hexen/a_hexenmisc.cpp @@ -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" diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 65a979edd..b34157b36 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -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; -} \ No newline at end of file +} + diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 4eab47c1b..ae666a2f7 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -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(c->Type()); + PFunction *virtsym = nullptr; + if (cls != nullptr && cls->ParentClass != nullptr) virtsym = dyn_cast(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) diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 08125952d..b5ffe3a06 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -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(); } diff --git a/wadsrc/static/zscript/hexen/heresiarch.txt b/wadsrc/static/zscript/hexen/heresiarch.txt index cf0b16372..9cfdde19d 100644 --- a/wadsrc/static/zscript/hexen/heresiarch.txt +++ b/wadsrc/static/zscript/hexen/heresiarch.txt @@ -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 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 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 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;