mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-01-18 15:11:46 +00:00
- scriptified the Heresiarch.
This commit is contained in:
parent
f5b3429274
commit
f17f6c30c2
7 changed files with 461 additions and 596 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue