diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9499cb64f..cd44a6761 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -860,7 +860,6 @@ set( NOT_COMPILED_SOURCE_FILES g_heretic/a_dsparil.cpp g_heretic/a_hereticartifacts.cpp g_heretic/a_hereticweaps.cpp - g_heretic/a_ironlich.cpp g_hexen/a_blastradius.cpp g_hexen/a_boostarmor.cpp g_hexen/a_clericflame.cpp diff --git a/src/g_heretic/a_hereticmisc.cpp b/src/g_heretic/a_hereticmisc.cpp index 5aa048977..18093b752 100644 --- a/src/g_heretic/a_hereticmisc.cpp +++ b/src/g_heretic/a_hereticmisc.cpp @@ -22,5 +22,3 @@ #include "a_dsparil.cpp" #include "a_hereticartifacts.cpp" #include "a_hereticweaps.cpp" -#include "a_ironlich.cpp" - diff --git a/src/g_heretic/a_ironlich.cpp b/src/g_heretic/a_ironlich.cpp deleted file mode 100644 index 8068c251b..000000000 --- a/src/g_heretic/a_ironlich.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* -#include "actor.h" -#include "info.h" -#include "m_random.h" -#include "s_sound.h" -#include "p_local.h" -#include "p_enemy.h" -#include "a_action.h" -#include "gstrings.h" -#include "vm.h" -#include "g_level.h" -*/ - -static FRandom pr_foo ("WhirlwindDamage"); -static FRandom pr_atk ("LichAttack"); -static FRandom pr_seek ("WhirlwindSeek"); - -class AWhirlwind : public AActor -{ - DECLARE_CLASS (AWhirlwind, AActor) -public: - int DoSpecialDamage (AActor *target, int damage, FName damagetype); -}; - -IMPLEMENT_CLASS(AWhirlwind, false, false, false, false) - -int AWhirlwind::DoSpecialDamage (AActor *target, int damage, FName damagetype) -{ - int randVal; - - if (!(target->flags7 & MF7_DONTTHRUST)) - { - target->Angles.Yaw += pr_foo.Random2() * (360 / 4096.); - target->Vel.X += pr_foo.Random2() / 64.; - target->Vel.Y += pr_foo.Random2() / 64.; - } - - if ((level.time & 16) && !(target->flags2 & MF2_BOSS) && !(target->flags7 & MF7_DONTTHRUST)) - { - randVal = pr_foo(); - if (randVal > 160) - { - randVal = 160; - } - target->Vel.Z += randVal / 32.; - if (target->Vel.Z > 12) - { - target->Vel.Z = 12; - } - } - if (!(level.time & 7)) - { - P_DamageMobj (target, NULL, this->target, 3, NAME_Melee); - } - return -1; -} - -//---------------------------------------------------------------------------- -// -// PROC A_LichAttack -// -//---------------------------------------------------------------------------- - -DEFINE_ACTION_FUNCTION(AActor, A_LichAttack) -{ - PARAM_SELF_PROLOGUE(AActor); - - int i; - AActor *fire; - AActor *baseFire; - AActor *mo; - AActor *target; - int randAttack; - static const int atkResolve1[] = { 50, 150 }; - static const int atkResolve2[] = { 150, 200 }; - - // Ice ball (close 20% : far 60%) - // Fire column (close 40% : far 20%) - // Whirlwind (close 40% : far 20%) - // Distance threshold = 8 cells - - target = self->target; - if (target == NULL) - { - return 0; - } - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - int damage = pr_atk.HitDice (6); - int newdam = P_DamageMobj (target, self, self, damage, NAME_Melee); - P_TraceBleed (newdam > 0 ? newdam : damage, target, self); - return 0; - } - int dist = self->Distance2D(target) > 8 * 64; - randAttack = pr_atk (); - if (randAttack < atkResolve1[dist]) - { // Ice ball - P_SpawnMissile (self, target, PClass::FindActor("HeadFX1")); - S_Sound (self, CHAN_BODY, "ironlich/attack2", 1, ATTN_NORM); - } - else if (randAttack < atkResolve2[dist]) - { // Fire column - baseFire = P_SpawnMissile (self, target, PClass::FindActor("HeadFX3")); - if (baseFire != NULL) - { - baseFire->SetState (baseFire->FindState("NoGrow")); - for (i = 0; i < 5; i++) - { - fire = Spawn("HeadFX3", baseFire->Pos(), ALLOW_REPLACE); - if (i == 0) - { - S_Sound (self, CHAN_BODY, "ironlich/attack1", 1, ATTN_NORM); - } - fire->target = baseFire->target; - fire->Angles.Yaw = baseFire->Angles.Yaw; - fire->Vel = baseFire->Vel; - fire->SetDamage(0); - fire->health = (i+1) * 2; - P_CheckMissileSpawn (fire, self->radius); - } - } - } - else - { // Whirlwind - mo = P_SpawnMissile (self, target, RUNTIME_CLASS(AWhirlwind)); - if (mo != NULL) - { - mo->AddZ(-32); - mo->tracer = target; - mo->health = 20*TICRATE; // Duration - S_Sound (self, CHAN_BODY, "ironlich/attack3", 1, ATTN_NORM); - } - } - return 0; -} - -//---------------------------------------------------------------------------- -// -// PROC A_WhirlwindSeek -// -//---------------------------------------------------------------------------- - -DEFINE_ACTION_FUNCTION(AActor, A_WhirlwindSeek) -{ - PARAM_SELF_PROLOGUE(AActor); - - self->health -= 3; - if (self->health < 0) - { - self->Vel.Zero(); - self->SetState(self->FindState(NAME_Death)); - self->flags &= ~MF_MISSILE; - return 0; - } - if ((self->threshold -= 3) < 0) - { - self->threshold = 58 + (pr_seek() & 31); - S_Sound(self, CHAN_BODY, "ironlich/attack3", 1, ATTN_NORM); - } - if (self->tracer && self->tracer->flags&MF_SHADOW) - { - return 0; - } - P_SeekerMissile(self, 10, 30); - return 0; -} - -//---------------------------------------------------------------------------- -// -// PROC A_LichIceImpact -// -//---------------------------------------------------------------------------- - -DEFINE_ACTION_FUNCTION(AActor, A_LichIceImpact) -{ - PARAM_SELF_PROLOGUE(AActor); - - unsigned int i; - AActor *shard; - - for (i = 0; i < 8; i++) - { - shard = Spawn("HeadFX2", self->Pos(), ALLOW_REPLACE); - shard->target = self->target; - shard->Angles.Yaw = i*45.; - shard->VelFromAngle(); - shard->Vel.Z = -.6; - P_CheckMissileSpawn (shard, self->radius); - } - return 0; -} - -//---------------------------------------------------------------------------- -// -// PROC A_LichFireGrow -// -//---------------------------------------------------------------------------- - -DEFINE_ACTION_FUNCTION(AActor, A_LichFireGrow) -{ - PARAM_SELF_PROLOGUE(AActor); - - self->health--; - self->AddZ(9.); - if (self->health == 0) - { - self->RestoreDamage(); - self->SetState (self->FindState("NoGrow")); - } - return 0; -} - diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 075d34c4e..a79fc0f16 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -6668,6 +6668,17 @@ int AActor::DoSpecialDamage (AActor *target, int damage, FName damagetype) } } +DEFINE_ACTION_FUNCTION(AActor, DoSpecialDamage) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(target, AActor); + PARAM_INT(damage); + PARAM_NAME(damagetype); + self->VMSuperCall(); + ACTION_RETURN_INT(self->DoSpecialDamage(target, damage, damagetype)); +} + + int AActor::TakeSpecialDamage (AActor *inflictor, AActor *source, int damage, FName damagetype) { FState *death; @@ -7180,6 +7191,13 @@ DEFINE_ACTION_FUNCTION(AActor, Vec3Offset) ACTION_RETURN_VEC3(self->Vec3Offset(x, y, z, absolute)); } +DEFINE_ACTION_FUNCTION(AActor, RestoreDamage) +{ + PARAM_SELF_PROLOGUE(AActor); + self->RestoreDamage(); + return 0; +} + //---------------------------------------------------------------------------- // // DropItem handling diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index ac9a8825c..3437c848c 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2326,6 +2326,11 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (varflags & VARF_Virtual) { + if (sym->Variants[0].Implementation == nullptr) + { + Error(f, "Virtual function %s.%s not present.", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + return; + } if (varflags & VARF_Final) { sym->Variants[0].Implementation->Final = true; diff --git a/src/virtual.h b/src/virtual.h index f3167407d..4e0b3f01b 100644 --- a/src/virtual.h +++ b/src/virtual.h @@ -38,6 +38,7 @@ VMEXPORTED_NATIVES_START VMEXPORTED_NATIVES_FUNC(BeginPlay) VMEXPORTED_NATIVES_FUNC(Activate) VMEXPORTED_NATIVES_FUNC(Deactivate) + VMEXPORTED_NATIVES_FUNC(DoSpecialDamage) VMEXPORTED_NATIVES_END @@ -192,6 +193,26 @@ public: stack.Call(VFUNC, params, 2, nullptr, 0, nullptr); } } + int DoSpecialDamage(AActor *target, int damage, FName damagetype) + { + if (this->ObjectFlags & OF_SuperCall) + { + this->ObjectFlags &= ~OF_SuperCall; + return ExportedNatives::Get()->template DoSpecialDamage(this, target, damage, damagetype); + } + else + { + VINDEX(AActor, DoSpecialDamage); + // Without the type cast this picks the 'void *' assignment... + VMValue params[4] = { (DObject*)this, (DObject*)target, damage, damagetype.GetIndex() }; + VMReturn ret; + VMFrameStack stack; + int retval; + ret.IntAt(&retval); + stack.Call(VFUNC, params, 4, &ret, 1, nullptr); + return retval; + } + } }; @@ -223,6 +244,7 @@ VMEXPORT_NATIVES_START(AActor, DThinker) VMEXPORT_NATIVES_FUNC(BeginPlay) VMEXPORT_NATIVES_FUNC(Activate) VMEXPORT_NATIVES_FUNC(Deactivate) + VMEXPORT_NATIVES_FUNC(DoSpecialDamage) VMEXPORT_NATIVES_END(AActor) /* diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 1aa02e5af..e7179bd47 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -237,6 +237,7 @@ class Actor : Thinker native virtual native void BeginPlay(); virtual native void Activate(Actor activator); virtual native void Deactivate(Actor activator); + virtual native int DoSpecialDamage (Actor target, int damage, Name damagetype); native void AdjustPlayerAngle(FTranslatedLineTarget t); native static readonly GetDefaultByType(class cls); @@ -247,6 +248,7 @@ class Actor : Thinker native native void ClearCounters(); native bool GiveBody (int num, int max=0); + native void RestoreDamage(); native void SetDamage(int dmg); native double Distance2D(Actor other); native void SetOrigin(vector3 newpos, bool moving); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 15a8c9a3b..11d565ae9 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -12,6 +12,8 @@ class Object native class Thinker : Object native { + const TICRATE = 35; + virtual native void Tick(); virtual native void PostBeginPlay(); } diff --git a/wadsrc/static/zscript/heretic/ironlich.txt b/wadsrc/static/zscript/heretic/ironlich.txt index e6ba05e05..8a4dd4c05 100644 --- a/wadsrc/static/zscript/heretic/ironlich.txt +++ b/wadsrc/static/zscript/heretic/ironlich.txt @@ -27,7 +27,6 @@ class Ironlich : Actor DropItem "ArtiEgg", 51, 0; } - native void A_LichAttack (); States { @@ -54,6 +53,77 @@ class Ironlich : Actor LICH I -1 A_BossDeath; Stop; } + + //---------------------------------------------------------------------------- + // + // PROC A_LichAttack + // + //---------------------------------------------------------------------------- + + void A_LichAttack () + { + static const int atkResolve1[] = { 50, 150 }; + static const int atkResolve2[] = { 150, 200 }; + + // Ice ball (close 20% : far 60%) + // Fire column (close 40% : far 20%) + // Whirlwind (close 40% : far 20%) + // Distance threshold = 8 cells + + if (target == null) + { + return; + } + A_FaceTarget (); + if (CheckMeleeRange ()) + { + int damage = random[LichAttack](1, 8) * 6; + int newdam = target.DamageMobj (self, self, damage, 'Melee'); + target.TraceBleed (newdam > 0 ? newdam : damage, self); + return; + } + int dist = Distance2D(target) > 8 * 64; + int randAttack = random[LichAttack](); + if (randAttack < atkResolve1[dist]) + { // Ice ball + SpawnMissile (target, "HeadFX1"); + A_PlaySound ("ironlich/attack2", CHAN_BODY); + } + else if (randAttack < atkResolve2[dist]) + { // Fire column + Actor baseFire = SpawnMissile (target, "HeadFX3"); + if (baseFire != null) + { + baseFire.SetStateLabel("NoGrow"); + for (int i = 0; i < 5; i++) + { + Actor fire = Spawn("HeadFX3", baseFire.Pos, ALLOW_REPLACE); + if (i == 0) + { + A_PlaySound ("ironlich/attack1", CHAN_BODY); + } + fire.target = baseFire.target; + fire.angle = baseFire.angle; + fire.Vel = baseFire.Vel; + fire.SetDamage(0); + fire.health = (i+1) * 2; + fire.CheckMissileSpawn (radius); + } + } + } + else + { // Whirlwind + Actor mo = SpawnMissile (target, "Whirlwind"); + if (mo != null) + { + mo.AddZ(-32); + mo.tracer = target; + mo.health = 20*TICRATE; // Duration + A_PlaySound ("ironlich/attack3", CHAN_BODY); + } + } + } + } // Head FX 1 ---------------------------------------------------------------- @@ -74,7 +144,6 @@ class HeadFX1 : Actor RenderStyle "Add"; } - native void A_LichIceImpact(); States { @@ -86,6 +155,25 @@ class HeadFX1 : Actor FX05 EFG 5 BRIGHT; Stop; } + + //---------------------------------------------------------------------------- + // + // PROC A_LichIceImpact + // + //---------------------------------------------------------------------------- + + void A_LichIceImpact() + { + for (int i = 0; i < 8; i++) + { + Actor shard = Spawn("HeadFX2", Pos, ALLOW_REPLACE); + shard.target = target; + shard.angle = i*45.; + shard.VelFromAngle(); + shard.Vel.Z = -.6; + shard.CheckMissileSpawn (radius); + } + } } // Head FX 2 ---------------------------------------------------------------- @@ -135,8 +223,6 @@ class HeadFX3 : Actor RenderStyle "Add"; } - native void A_LichFireGrow (); - States { Spawn: @@ -149,12 +235,29 @@ class HeadFX3 : Actor FX06 DEFG 5 BRIGHT; Stop; } + + //---------------------------------------------------------------------------- + // + // PROC A_LichFireGrow + // + //---------------------------------------------------------------------------- + + void A_LichFireGrow () + { + health--; + AddZ(9.); + if (health == 0) + { + RestoreDamage(); + SetStateLabel("NoGrow"); + } + } } // Whirlwind ---------------------------------------------------------------- -class Whirlwind : Actor native +class Whirlwind : Actor { Default { @@ -174,8 +277,6 @@ class Whirlwind : Actor native Alpha 0.4; } - native void A_WhirlwindSeek(); - States { Spawn: @@ -186,6 +287,64 @@ class Whirlwind : Actor native FX07 GFED 4; Stop; } + + override int DoSpecialDamage (Actor target, int damage, Name damagetype) + { + int randVal; + + if (!target.bDontThrust) + { + target.angle += Random2[WhirlwindDamage]() * (360 / 4096.); + target.Vel.X += Random2[WhirlwindDamage]() / 64.; + target.Vel.Y += Random2[WhirlwindDamage]() / 64.; + } + + if ((level.time & 16) && !target.bBoss && !target.bDontThrust) + { + randVal = min(160, random[WhirlwindSeek]()); + target.Vel.Z += randVal / 32.; + if (target.Vel.Z > 12) + { + target.Vel.Z = 12; + } + } + if (!(level.time & 7)) + { + target.DamageMobj (null, target, 3, 'Melee'); + } + return -1; + } + + //---------------------------------------------------------------------------- + // + // PROC A_WhirlwindSeek + // + //---------------------------------------------------------------------------- + + void A_WhirlwindSeek() + { + + health -= 3; + if (health < 0) + { + Vel = (0,0,0); + SetStateLabel("Death"); + bMissile = false; + return; + } + if ((threshold -= 3) < 0) + { + threshold = 58 + (random[WhirlwindSeek]() & 31); + A_PlaySound("ironlich/attack3", CHAN_BODY); + } + if (tracer && tracer.bShadow) + { + return; + } + A_SeekerMissile(10, 30); + } + } +