vkdoom_m/src/g_hexen/a_clericholy.cpp
Christoph Oelckers a2f4cd7cda - fixed: All functions that are callable from weapon states and not members of Actor need to be declared 'action'.
With the stricter type checks of the self pointer that were now implemented these all produced errors.
2016-11-13 14:20:30 +01:00

516 lines
11 KiB
C++

/*
#include "actor.h"
#include "info.h"
#include "p_local.h"
#include "m_random.h"
#include "s_sound.h"
#include "a_hexenglobal.h"
#include "gstrings.h"
#include "a_weaponpiece.h"
#include "vm.h"
#include "g_level.h"
#include "doomstat.h"
*/
#define BLAST_FULLSTRENGTH 255
static FRandom pr_holyatk2 ("CHolyAtk2");
static FRandom pr_holyseeker ("CHolySeeker");
static FRandom pr_holyweave ("CHolyWeave");
static FRandom pr_holyseek ("CHolySeek");
static FRandom pr_checkscream ("CCheckScream");
static FRandom pr_spiritslam ("CHolySlam");
static FRandom pr_wraithvergedrop ("WraithvergeDrop");
void SpawnSpiritTail (AActor *spirit);
//==========================================================================
// Cleric's Wraithverge (Holy Symbol?) --------------------------------------
class ACWeapWraithverge : public AClericWeapon
{
DECLARE_CLASS (ACWeapWraithverge, AClericWeapon)
public:
void Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("cholycount", CHolyCount);
}
PalEntry GetBlend ()
{
if (paletteflash & PF_HEXENWEAPONS)
{
if (CHolyCount == 3)
return PalEntry(128, 70, 70, 70);
else if (CHolyCount == 2)
return PalEntry(128, 100, 100, 100);
else if (CHolyCount == 1)
return PalEntry(128, 130, 130, 130);
else
return PalEntry(0, 0, 0, 0);
}
else
{
return PalEntry (CHolyCount * 128 / 3, 131, 131, 131);
}
}
BYTE CHolyCount;
};
IMPLEMENT_CLASS(ACWeapWraithverge, false, false, false, false)
// Holy Spirit --------------------------------------------------------------
IMPLEMENT_CLASS(AHolySpirit, false, false, false, false)
bool AHolySpirit::Slam(AActor *thing)
{
if (thing->flags&MF_SHOOTABLE && thing != target)
{
if (multiplayer && !deathmatch && thing->player && target->player)
{ // don't attack other co-op players
return true;
}
if (thing->flags2&MF2_REFLECTIVE
&& (thing->player || thing->flags2&MF2_BOSS))
{
tracer = target;
target = thing;
return true;
}
if (thing->flags3&MF3_ISMONSTER || thing->player)
{
tracer = thing;
}
if (pr_spiritslam() < 96)
{
int dam = 12;
if (thing->player || thing->flags2&MF2_BOSS)
{
dam = 3;
// ghost burns out faster when attacking players/bosses
health -= 6;
}
P_DamageMobj(thing, this, target, dam, NAME_Melee);
if (pr_spiritslam() < 128)
{
Spawn("HolyPuff", Pos(), ALLOW_REPLACE);
S_Sound(this, CHAN_WEAPON, "SpiritAttack", 1, ATTN_NORM);
if (thing->flags3&MF3_ISMONSTER && pr_spiritslam() < 128)
{
thing->Howl();
}
}
}
if (thing->health <= 0)
{
tracer = NULL;
}
}
return true;
}
bool AHolySpirit::SpecialBlastHandling (AActor *source, double strength)
{
if (tracer == source)
{
tracer = target;
target = source;
GC::WriteBarrier(this, source);
}
return true;
}
//============================================================================
//
// A_CHolyAttack2
//
// Spawns the spirits
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyAttack2)
{
PARAM_SELF_PROLOGUE(AActor);
int j;
AActor *mo;
for (j = 0; j < 4; j++)
{
mo = Spawn<AHolySpirit> (self->Pos(), ALLOW_REPLACE);
if (!mo)
{
continue;
}
switch (j)
{ // float bob index
case 0:
mo->WeaveIndexZ = pr_holyatk2() & 7; // upper-left
break;
case 1:
mo->WeaveIndexZ = 32 + (pr_holyatk2() & 7); // upper-right
break;
case 2:
mo->WeaveIndexXY = 32 + (pr_holyatk2() & 7); // lower-left
break;
case 3:
mo->WeaveIndexXY = 32 + (pr_holyatk2() & 7);
mo->WeaveIndexZ = 32 + (pr_holyatk2() & 7);
break;
}
mo->SetZ(self->Z());
mo->Angles.Yaw = self->Angles.Yaw + 67.5 - 45.*j;
mo->Thrust();
mo->target = self->target;
mo->args[0] = 10; // initial turn value
mo->args[1] = 0; // initial look angle
if (deathmatch)
{ // Ghosts last slightly less longer in DeathMatch
mo->health = 85;
}
if (self->tracer)
{
mo->tracer = self->tracer;
mo->flags |= MF_NOCLIP|MF_SKULLFLY;
mo->flags &= ~MF_MISSILE;
}
SpawnSpiritTail (mo);
}
return 0;
}
//============================================================================
//
// SpawnSpiritTail
//
//============================================================================
void SpawnSpiritTail (AActor *spirit)
{
AActor *tail, *next;
int i;
tail = Spawn ("HolyTail", spirit->Pos(), ALLOW_REPLACE);
tail->target = spirit; // parent
for (i = 1; i < 3; i++)
{
next = Spawn ("HolyTailTrail", spirit->Pos(), ALLOW_REPLACE);
tail->tracer = next;
tail = next;
}
tail->tracer = NULL; // last tail bit
}
//============================================================================
//
// A_CHolyAttack
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyAttack)
{
PARAM_ACTION_PROLOGUE(AActor);
player_t *player;
FTranslatedLineTarget t;
if (NULL == (player = self->player))
{
return 0;
}
ACWeapWraithverge *weapon = static_cast<ACWeapWraithverge *> (self->player->ReadyWeapon);
if (weapon != NULL)
{
if (!weapon->DepleteAmmo (weapon->bAltFire))
return 0;
}
AActor *missile = P_SpawnPlayerMissile (self, 0,0,0, PClass::FindActor("HolyMissile"), self->Angles.Yaw, &t);
if (missile != NULL && !t.unlinked)
{
missile->tracer = t.linetarget;
}
weapon->CHolyCount = 3;
S_Sound (self, CHAN_WEAPON, "HolySymbolFire", 1, ATTN_NORM);
return 0;
}
//============================================================================
//
// A_CHolyPalette
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyPalette)
{
PARAM_ACTION_PROLOGUE(AActor);
if (self->player != NULL)
{
ACWeapWraithverge *weapon = static_cast<ACWeapWraithverge *> (self->player->ReadyWeapon);
if (weapon != NULL && weapon->CHolyCount != 0)
{
weapon->CHolyCount--;
}
}
return 0;
}
//============================================================================
//
// CHolyTailFollow
//
//============================================================================
static void CHolyTailFollow(AActor *actor, double dist)
{
AActor *child;
DAngle an;
double oldDistance, newDistance;
while (actor)
{
child = actor->tracer;
if (child)
{
an = actor->AngleTo(child);
oldDistance = child->Distance2D(actor);
if (P_TryMove(child, actor->Pos().XY() + an.ToVector(dist), true))
{
newDistance = child->Distance2D(actor) - 1;
if (oldDistance < 1)
{
if (child->Z() < actor->Z())
{
child->SetZ(actor->Z() - dist);
}
else
{
child->SetZ(actor->Z() + dist);
}
}
else
{
child->SetZ(actor->Z() + (newDistance * (child->Z() - actor->Z()) / oldDistance));
}
}
}
actor = child;
dist -= 1;
}
}
//============================================================================
//
// CHolyTailRemove
//
//============================================================================
static void CHolyTailRemove (AActor *actor)
{
AActor *next;
while (actor)
{
next = actor->tracer;
actor->Destroy ();
actor = next;
}
}
//============================================================================
//
// A_CHolyTail
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyTail)
{
PARAM_SELF_PROLOGUE(AActor);
AActor *parent;
parent = self->target;
if (parent == NULL || parent->health <= 0) // better check for health than current state - it's safer!
{ // Ghost removed, so remove all tail parts
CHolyTailRemove (self);
return 0;
}
else
{
if (P_TryMove(self, parent->Vec2Angle(14., parent->Angles.Yaw, true), true))
{
self->SetZ(parent->Z() - 5.);
}
CHolyTailFollow(self, 10);
}
return 0;
}
//============================================================================
//
// CHolyFindTarget
//
//============================================================================
static void CHolyFindTarget (AActor *actor)
{
AActor *target;
if ( (target = P_RoughMonsterSearch (actor, 6, true)) )
{
actor->tracer = target;
actor->flags |= MF_NOCLIP|MF_SKULLFLY;
actor->flags &= ~MF_MISSILE;
}
}
//============================================================================
//
// CHolySeekerMissile
//
// Similar to P_SeekerMissile, but seeks to a random Z on the target
//============================================================================
static void CHolySeekerMissile (AActor *actor, DAngle thresh, DAngle turnMax)
{
int dir;
DAngle delta;
AActor *target;
double newZ;
double deltaZ;
target = actor->tracer;
if (target == NULL)
{
return;
}
if (!(target->flags&MF_SHOOTABLE)
|| (!(target->flags3&MF3_ISMONSTER) && !target->player))
{ // Target died/target isn't a player or creature
actor->tracer = NULL;
actor->flags &= ~(MF_NOCLIP | MF_SKULLFLY);
actor->flags |= MF_MISSILE;
CHolyFindTarget(actor);
return;
}
dir = P_FaceMobj (actor, target, &delta);
if (delta > thresh)
{
delta /= 2;
if (delta > turnMax)
{
delta = turnMax;
}
}
if (dir)
{ // Turn clockwise
actor->Angles.Yaw += delta;
}
else
{ // Turn counter clockwise
actor->Angles.Yaw -= delta;
}
actor->VelFromAngle();
if (!(level.time&15)
|| actor->Z() > target->Top()
|| actor->Top() < target->Z())
{
newZ = target->Z() + ((pr_holyseeker()*target->Height) / 256.);
deltaZ = newZ - actor->Z();
if (fabs(deltaZ) > 15)
{
if (deltaZ > 0)
{
deltaZ = 15;
}
else
{
deltaZ = -15;
}
}
actor->Vel.Z = deltaZ / actor->DistanceBySpeed(target, actor->Speed);
}
return;
}
//============================================================================
//
// A_CHolySeek
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolySeek)
{
PARAM_SELF_PROLOGUE(AActor);
self->health--;
if (self->health <= 0)
{
self->Vel.X /= 4;
self->Vel.Y /= 4;
self->Vel.Z = 0;
self->SetState (self->FindState(NAME_Death));
self->tics -= pr_holyseek()&3;
return 0;
}
if (self->tracer)
{
CHolySeekerMissile (self, (double)self->args[0], self->args[0]*2.);
if (!((level.time+7)&15))
{
self->args[0] = 5+(pr_holyseek()/20);
}
}
int xyspeed = (pr_holyweave() % 5);
int zspeed = (pr_holyweave() % 5);
A_Weave(self, xyspeed, zspeed, 4., 2.);
return 0;
}
//============================================================================
//
// A_CHolyCheckScream
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyCheckScream)
{
PARAM_SELF_PROLOGUE(AActor);
CALL_ACTION(A_CHolySeek, self);
if (pr_checkscream() < 20)
{
S_Sound (self, CHAN_VOICE, "SpiritActive", 1, ATTN_NORM);
}
if (!self->tracer)
{
CHolyFindTarget(self);
}
return 0;
}
//============================================================================
//
// A_ClericAttack
// (for the ClericBoss)
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ClericAttack)
{
PARAM_SELF_PROLOGUE(AActor);
if (!self->target) return 0;
AActor * missile = P_SpawnMissileZ (self, self->Z() + 40., self->target, PClass::FindActor ("HolyMissile"));
if (missile != NULL) missile->tracer = NULL; // No initial target
S_Sound (self, CHAN_WEAPON, "HolySymbolFire", 1, ATTN_NORM);
return 0;
}