qzdoom/src/g_heretic/a_ironlich.cpp

433 lines
12 KiB
C++
Raw Normal View History

#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"
static FRandom pr_foo ("WhirlwindDamage");
static FRandom pr_atk ("LichAttack");
static FRandom pr_seek ("WhirlwindSeek");
void A_LichAttack (AActor *);
void A_LichIceImpact (AActor *);
void A_LichFireGrow (AActor *);
void A_WhirlwindSeek (AActor *);
// Ironlich -----------------------------------------------------------------
class AIronlich : public AActor
{
DECLARE_ACTOR (AIronlich, AActor)
public:
void NoBlockingSet ();
const char *GetObituary ();
const char *GetHitObituary ();
};
FState AIronlich::States[] =
{
#define S_HEAD_LOOK 0
S_NORMAL (LICH, 'A', 10, A_Look , &States[S_HEAD_LOOK]),
#define S_HEAD_FLOAT (S_HEAD_LOOK+1)
S_NORMAL (LICH, 'A', 4, A_Chase , &States[S_HEAD_FLOAT]),
#define S_HEAD_ATK (S_HEAD_FLOAT+1)
S_NORMAL (LICH, 'A', 5, A_FaceTarget , &States[S_HEAD_ATK+1]),
S_NORMAL (LICH, 'B', 20, A_LichAttack , &States[S_HEAD_FLOAT]),
#define S_HEAD_PAIN (S_HEAD_ATK+2)
S_NORMAL (LICH, 'A', 4, NULL , &States[S_HEAD_PAIN+1]),
S_NORMAL (LICH, 'A', 4, A_Pain , &States[S_HEAD_FLOAT]),
#define S_HEAD_DIE (S_HEAD_PAIN+2)
S_NORMAL (LICH, 'C', 7, NULL , &States[S_HEAD_DIE+1]),
S_NORMAL (LICH, 'D', 7, A_Scream , &States[S_HEAD_DIE+2]),
S_NORMAL (LICH, 'E', 7, NULL , &States[S_HEAD_DIE+3]),
S_NORMAL (LICH, 'F', 7, NULL , &States[S_HEAD_DIE+4]),
S_NORMAL (LICH, 'G', 7, A_NoBlocking , &States[S_HEAD_DIE+5]),
S_NORMAL (LICH, 'H', 7, NULL , &States[S_HEAD_DIE+6]),
S_NORMAL (LICH, 'I', -1, A_BossDeath , NULL)
};
IMPLEMENT_ACTOR (AIronlich, Heretic, 6, 20)
PROP_SpawnHealth (700)
PROP_RadiusFixed (40)
PROP_HeightFixed (72)
PROP_Mass (325)
PROP_SpeedFixed (6)
PROP_PainChance (32)
PROP_Flags (MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL|MF_NOBLOOD)
PROP_Flags2 (MF2_PASSMOBJ|MF2_PUSHWALL)
PROP_Flags3 (MF3_DONTMORPH|MF3_DONTSQUASH)
PROP_Flags4 (MF4_BOSSDEATH)
PROP_SpawnState (S_HEAD_LOOK)
PROP_SeeState (S_HEAD_FLOAT)
PROP_PainState (S_HEAD_PAIN)
PROP_MissileState (S_HEAD_ATK)
PROP_DeathState (S_HEAD_DIE)
PROP_SeeSound ("ironlich/sight")
PROP_AttackSound ("ironlich/attack")
PROP_PainSound ("ironlich/pain")
PROP_DeathSound ("ironlich/death")
PROP_ActiveSound ("ironlich/active")
END_DEFAULTS
void AIronlich::NoBlockingSet ()
{
P_DropItem (this, "BlasterAmmo", 10, 84);
P_DropItem (this, "ArtiEgg", 0, 51);
}
const char *AIronlich::GetObituary ()
{
return GStrings("OB_IRONLICH");
}
const char *AIronlich::GetHitObituary ()
{
return GStrings("OB_IRONLICHHIT");
}
// Head FX 1 ----------------------------------------------------------------
class AHeadFX1 : public AActor
{
DECLARE_ACTOR (AHeadFX1, AActor)
};
FState AHeadFX1::States[] =
{
#define S_HEADFX1 0
S_NORMAL (FX05, 'A', 6, NULL , &States[S_HEADFX1+1]),
S_NORMAL (FX05, 'B', 6, NULL , &States[S_HEADFX1+2]),
S_NORMAL (FX05, 'C', 6, NULL , &States[S_HEADFX1+0]),
#define S_HEADFXI1 (S_HEADFX1+3)
S_NORMAL (FX05, 'D', 5, A_LichIceImpact , &States[S_HEADFXI1+1]),
S_NORMAL (FX05, 'E', 5, NULL , &States[S_HEADFXI1+2]),
S_NORMAL (FX05, 'F', 5, NULL , &States[S_HEADFXI1+3]),
S_NORMAL (FX05, 'G', 5, NULL , NULL)
};
IMPLEMENT_ACTOR (AHeadFX1, Heretic, -1, 164)
PROP_RadiusFixed (12)
PROP_HeightFixed (6)
PROP_SpeedFixed (13)
PROP_Damage (1)
PROP_Flags (MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY)
PROP_Flags2 (MF2_NOTELEPORT|MF2_THRUGHOST)
PROP_SpawnState (S_HEADFX1)
PROP_DeathState (S_HEADFXI1)
END_DEFAULTS
AT_SPEED_SET (HeadFX1, speed)
{
SimpleSpeedSetter (AHeadFX1, 13*FRACUNIT, 20*FRACUNIT, speed);
}
// Head FX 2 ----------------------------------------------------------------
class AHeadFX2 : public AActor
{
DECLARE_ACTOR (AHeadFX2, AActor)
};
FState AHeadFX2::States[] =
{
#define S_HEADFX2 0
S_NORMAL (FX05, 'H', 6, NULL , &States[S_HEADFX2+1]),
S_NORMAL (FX05, 'I', 6, NULL , &States[S_HEADFX2+2]),
S_NORMAL (FX05, 'J', 6, NULL , &States[S_HEADFX2+0]),
#define S_HEADFXI2 (S_HEADFX2+3)
S_NORMAL (FX05, 'D', 5, NULL , &States[S_HEADFXI2+1]),
S_NORMAL (FX05, 'E', 5, NULL , &States[S_HEADFXI2+2]),
S_NORMAL (FX05, 'F', 5, NULL , &States[S_HEADFXI2+3]),
S_NORMAL (FX05, 'G', 5, NULL , NULL)
};
IMPLEMENT_ACTOR (AHeadFX2, Heretic, -1, 0)
PROP_RadiusFixed (12)
PROP_HeightFixed (6)
PROP_SpeedFixed (8)
PROP_Damage (3)
PROP_Flags (MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY)
PROP_Flags2 (MF2_NOTELEPORT)
PROP_SpawnState (S_HEADFX2)
PROP_DeathState (S_HEADFXI2)
END_DEFAULTS
// Head FX 3 ----------------------------------------------------------------
class AHeadFX3 : public AActor
{
DECLARE_ACTOR (AHeadFX3, AActor)
};
FState AHeadFX3::States[] =
{
#define S_HEADFX3 0
S_NORMAL (FX06, 'A', 4, A_LichFireGrow , &States[S_HEADFX3+1]),
S_NORMAL (FX06, 'B', 4, A_LichFireGrow , &States[S_HEADFX3+2]),
S_NORMAL (FX06, 'C', 4, A_LichFireGrow , &States[S_HEADFX3+0]),
S_NORMAL (FX06, 'A', 5, NULL , &States[S_HEADFX3+4]),
S_NORMAL (FX06, 'B', 5, NULL , &States[S_HEADFX3+5]),
S_NORMAL (FX06, 'C', 5, NULL , &States[S_HEADFX3+3]),
#define S_HEADFXI3 (S_HEADFX3+6)
S_NORMAL (FX06, 'D', 5, NULL , &States[S_HEADFXI3+1]),
S_NORMAL (FX06, 'E', 5, NULL , &States[S_HEADFXI3+2]),
S_NORMAL (FX06, 'F', 5, NULL , &States[S_HEADFXI3+3]),
S_NORMAL (FX06, 'G', 5, NULL , NULL)
};
IMPLEMENT_ACTOR (AHeadFX3, Heretic, -1, 0)
PROP_RadiusFixed (14)
PROP_HeightFixed (12)
PROP_SpeedFixed (10)
PROP_Damage (5)
PROP_Flags (MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY)
PROP_Flags2 (MF2_WINDTHRUST|MF2_NOTELEPORT)
PROP_SpawnState (S_HEADFX3)
PROP_DeathState (S_HEADFXI3)
END_DEFAULTS
AT_SPEED_SET (HeadFX3, speed)
{
SimpleSpeedSetter (AHeadFX3, 10*FRACUNIT, 18*FRACUNIT, speed);
}
// Whirlwind ----------------------------------------------------------------
class AWhirlwind : public AActor
{
DECLARE_ACTOR (AWhirlwind, AActor)
public:
int DoSpecialDamage (AActor *target, int damage);
};
FState AWhirlwind::States[] =
{
#define S_HEADFX4 0
S_NORMAL (FX07, 'D', 3, NULL , &States[S_HEADFX4+1]),
S_NORMAL (FX07, 'E', 3, NULL , &States[S_HEADFX4+2]),
S_NORMAL (FX07, 'F', 3, NULL , &States[S_HEADFX4+3]),
S_NORMAL (FX07, 'G', 3, NULL , &States[S_HEADFX4+4]),
S_NORMAL (FX07, 'A', 3, A_WhirlwindSeek , &States[S_HEADFX4+5]),
S_NORMAL (FX07, 'B', 3, A_WhirlwindSeek , &States[S_HEADFX4+6]),
S_NORMAL (FX07, 'C', 3, A_WhirlwindSeek , &States[S_HEADFX4+4]),
#define S_HEADFXI4 (S_HEADFX4+7)
S_NORMAL (FX07, 'G', 4, NULL , &States[S_HEADFXI4+1]),
S_NORMAL (FX07, 'F', 4, NULL , &States[S_HEADFXI4+2]),
S_NORMAL (FX07, 'E', 4, NULL , &States[S_HEADFXI4+3]),
S_NORMAL (FX07, 'D', 4, NULL , NULL)
};
IMPLEMENT_ACTOR (AWhirlwind, Heretic, -1, 165)
PROP_RadiusFixed (16)
PROP_HeightFixed (74)
PROP_SpeedFixed (10)
PROP_Damage (1)
PROP_Flags (MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY)
PROP_Flags2 (MF2_NOTELEPORT|MF2_SEEKERMISSILE)
PROP_Flags3 (MF3_EXPLOCOUNT)
PROP_RenderStyle (STYLE_Translucent)
PROP_Alpha (HR_SHADOW)
PROP_SpawnState (S_HEADFX4)
PROP_DeathState (S_HEADFXI4)
END_DEFAULTS
int AWhirlwind::DoSpecialDamage (AActor *target, int damage)
{
int randVal;
target->angle += pr_foo.Random2() << 20;
target->momx += pr_foo.Random2() << 10;
target->momy += pr_foo.Random2() << 10;
if ((level.time & 16) && !(target->flags2 & MF2_BOSS))
{
randVal = pr_foo();
if (randVal > 160)
{
randVal = 160;
}
target->momz += randVal << 11;
if (target->momz > 12*FRACUNIT)
{
target->momz = 12*FRACUNIT;
}
}
if (!(level.time & 7))
{
P_DamageMobj (target, NULL, this->target, 3, MOD_HIT);
}
return -1;
}
//----------------------------------------------------------------------------
//
// PROC A_LichAttack
//
//----------------------------------------------------------------------------
void A_LichAttack (AActor *actor)
{
int i;
AActor *fire;
AActor *baseFire;
AActor *mo;
AActor *target;
int randAttack;
static const int atkResolve1[] = { 50, 150 };
static const int atkResolve2[] = { 150, 200 };
int dist;
// Ice ball (close 20% : far 60%)
// Fire column (close 40% : far 20%)
// Whirlwind (close 40% : far 20%)
// Distance threshold = 8 cells
target = actor->target;
if (target == NULL)
{
return;
}
A_FaceTarget (actor);
if (actor->CheckMeleeRange ())
{
int damage = pr_atk.HitDice (6);
P_DamageMobj (target, actor, actor, damage, MOD_HIT);
P_TraceBleed (damage, target, actor);
return;
}
dist = P_AproxDistance (actor->x-target->x, actor->y-target->y)
> 8*64*FRACUNIT;
randAttack = pr_atk ();
if (randAttack < atkResolve1[dist])
{ // Ice ball
P_SpawnMissile (actor, target, RUNTIME_CLASS(AHeadFX1));
S_Sound (actor, CHAN_BODY, "ironlich/attack2", 1, ATTN_NORM);
}
else if (randAttack < atkResolve2[dist])
{ // Fire column
baseFire = P_SpawnMissile (actor, target, RUNTIME_CLASS(AHeadFX3));
if (baseFire != NULL)
{
baseFire->SetState (&AHeadFX3::States[S_HEADFX3+3]); // Don't grow
for (i = 0; i < 5; i++)
{
fire = Spawn<AHeadFX3> (baseFire->x, baseFire->y,
baseFire->z);
if (i == 0)
{
S_Sound (actor, CHAN_BODY, "ironlich/attack1", 1, ATTN_NORM);
}
fire->target = baseFire->target;
fire->angle = baseFire->angle;
fire->momx = baseFire->momx;
fire->momy = baseFire->momy;
fire->momz = baseFire->momz;
fire->damage = 0;
fire->health = (i+1) * 2;
P_CheckMissileSpawn (fire);
}
}
}
else
{ // Whirlwind
mo = P_SpawnMissile (actor, target, RUNTIME_CLASS(AWhirlwind));
if (mo != NULL)
{
mo->z -= 32*FRACUNIT;
mo->tracer = target;
mo->special1 = 60;
mo->special2 = 50; // Timer for active sound
mo->health = 20*TICRATE; // Duration
S_Sound (actor, CHAN_BODY, "ironlich/attack3", 1, ATTN_NORM);
}
}
}
//----------------------------------------------------------------------------
//
// PROC A_WhirlwindSeek
//
//----------------------------------------------------------------------------
void A_WhirlwindSeek (AActor *actor)
{
actor->health -= 3;
if (actor->health < 0)
{
actor->momx = actor->momy = actor->momz = 0;
actor->SetState (actor->DeathState);
actor->flags &= ~MF_MISSILE;
return;
}
if ((actor->special2 -= 3) < 0)
{
actor->special2 = 58 + (pr_seek() & 31);
S_Sound (actor, CHAN_BODY, "ironlich/attack3", 1, ATTN_NORM);
}
if (actor->tracer && actor->tracer->flags&MF_SHADOW)
{
return;
}
P_SeekerMissile (actor, ANGLE_1*10, ANGLE_1*30);
}
//----------------------------------------------------------------------------
//
// PROC A_LichIceImpact
//
//----------------------------------------------------------------------------
void A_LichIceImpact (AActor *ice)
{
int i;
angle_t angle;
AActor *shard;
for (i = 0; i < 8; i++)
{
shard = Spawn<AHeadFX2> (ice->x, ice->y, ice->z);
angle = i*ANG45;
shard->target = ice->target;
shard->angle = angle;
angle >>= ANGLETOFINESHIFT;
shard->momx = FixedMul (shard->Speed, finecosine[angle]);
shard->momy = FixedMul (shard->Speed, finesine[angle]);
shard->momz = -FRACUNIT*6/10;
P_CheckMissileSpawn (shard);
}
}
//----------------------------------------------------------------------------
//
// PROC A_LichFireGrow
//
//----------------------------------------------------------------------------
void A_LichFireGrow (AActor *fire)
{
fire->health--;
fire->z += 9*FRACUNIT;
if (fire->health == 0)
{
fire->damage = fire->GetDefault()->damage;
fire->SetState (&AHeadFX3::States[S_HEADFX3+3]);
}
}