mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-12-15 14:41:20 +00:00
975 lines
27 KiB
C++
975 lines
27 KiB
C++
|
#include "actor.h"
|
||
|
#include "info.h"
|
||
|
#include "m_random.h"
|
||
|
#include "s_sound.h"
|
||
|
#include "p_local.h"
|
||
|
#include "p_enemy.h"
|
||
|
#include "ravenshared.h"
|
||
|
#include "a_action.h"
|
||
|
#include "gi.h"
|
||
|
#include "w_wad.h"
|
||
|
|
||
|
#define MAULATORTICS (25*35)
|
||
|
|
||
|
static FRandom pr_minotauratk1 ("MinotaurAtk1");
|
||
|
static FRandom pr_minotaurdecide ("MinotaurDecide");
|
||
|
static FRandom pr_atk ("MinotaurAtk2");
|
||
|
static FRandom pr_minotauratk3 ("MinotaurAtk3");
|
||
|
static FRandom pr_fire ("MntrFloorFire");
|
||
|
static FRandom pr_minotaurslam ("MinotaurSlam");
|
||
|
static FRandom pr_minotaurroam ("MinotaurRoam");
|
||
|
static FRandom pr_minotaurchase ("MinotaurChase");
|
||
|
|
||
|
void A_MinotaurDecide (AActor *);
|
||
|
void A_MinotaurAtk1 (AActor *);
|
||
|
void A_MinotaurAtk2 (AActor *);
|
||
|
void A_MinotaurAtk3 (AActor *);
|
||
|
void A_MinotaurCharge (AActor *);
|
||
|
void A_MntrFloorFire (AActor *);
|
||
|
void A_MinotaurFade0 (AActor *);
|
||
|
void A_MinotaurFade1 (AActor *);
|
||
|
void A_MinotaurFade2 (AActor *);
|
||
|
void A_MinotaurLook (AActor *);
|
||
|
void A_MinotaurRoam (AActor *);
|
||
|
void A_SmokePuffExit (AActor *);
|
||
|
void A_MinotaurChase (AActor *);
|
||
|
|
||
|
void P_MinotaurSlam (AActor *source, AActor *target);
|
||
|
|
||
|
// Class definitions --------------------------------------------------------
|
||
|
|
||
|
FState AMinotaur::States[] =
|
||
|
{
|
||
|
#define S_MNTR_LOOK 0
|
||
|
S_NORMAL (MNTR, 'A', 10, A_MinotaurLook , &States[S_MNTR_LOOK+1]),
|
||
|
S_NORMAL (MNTR, 'B', 10, A_MinotaurLook , &States[S_MNTR_LOOK+0]),
|
||
|
|
||
|
#define S_MNTR_ROAM (S_MNTR_LOOK+2)
|
||
|
S_NORMAL (MNTR, 'A', 5, A_MinotaurRoam , &States[S_MNTR_ROAM+1]),
|
||
|
S_NORMAL (MNTR, 'B', 5, A_MinotaurRoam , &States[S_MNTR_ROAM+2]),
|
||
|
S_NORMAL (MNTR, 'C', 5, A_MinotaurRoam , &States[S_MNTR_ROAM+3]),
|
||
|
S_NORMAL (MNTR, 'D', 5, A_MinotaurRoam , &States[S_MNTR_ROAM]),
|
||
|
|
||
|
#define S_MNTR_WALK (S_MNTR_ROAM+4)
|
||
|
S_NORMAL (MNTR, 'A', 5, A_MinotaurChase , &States[S_MNTR_WALK+1]),
|
||
|
S_NORMAL (MNTR, 'B', 5, A_MinotaurChase , &States[S_MNTR_WALK+2]),
|
||
|
S_NORMAL (MNTR, 'C', 5, A_MinotaurChase , &States[S_MNTR_WALK+3]),
|
||
|
S_NORMAL (MNTR, 'D', 5, A_MinotaurChase , &States[S_MNTR_WALK+0]),
|
||
|
|
||
|
#define S_MNTR_ATK1 (S_MNTR_WALK+4)
|
||
|
S_NORMAL (MNTR, 'V', 10, A_FaceTarget , &States[S_MNTR_ATK1+1]),
|
||
|
S_NORMAL (MNTR, 'W', 7, A_FaceTarget , &States[S_MNTR_ATK1+2]),
|
||
|
S_NORMAL (MNTR, 'X', 12, A_MinotaurAtk1 , &States[S_MNTR_WALK+0]),
|
||
|
|
||
|
#define S_MNTR_ATK2 (S_MNTR_ATK1+3)
|
||
|
S_NORMAL (MNTR, 'V', 10, A_MinotaurDecide , &States[S_MNTR_ATK2+1]),
|
||
|
S_NORMAL (MNTR, 'Y', 4, A_FaceTarget , &States[S_MNTR_ATK2+2]),
|
||
|
S_NORMAL (MNTR, 'Z', 9, A_MinotaurAtk2 , &States[S_MNTR_WALK+0]),
|
||
|
|
||
|
#define S_MNTR_ATK3 (S_MNTR_ATK2+3)
|
||
|
S_NORMAL (MNTR, 'V', 10, A_FaceTarget , &States[S_MNTR_ATK3+1]),
|
||
|
S_NORMAL (MNTR, 'W', 7, A_FaceTarget , &States[S_MNTR_ATK3+2]),
|
||
|
S_NORMAL (MNTR, 'X', 12, A_MinotaurAtk3 , &States[S_MNTR_WALK+0]),
|
||
|
S_NORMAL (MNTR, 'X', 12, NULL , &States[S_MNTR_ATK3+0]),
|
||
|
|
||
|
#define S_MNTR_ATK4 (S_MNTR_ATK3+4)
|
||
|
S_NORMAL (MNTR, 'U', 2, A_MinotaurCharge , &States[S_MNTR_ATK4+0]),
|
||
|
|
||
|
#define S_MNTR_PAIN (S_MNTR_ATK4+1)
|
||
|
S_NORMAL (MNTR, 'E', 3, NULL , &States[S_MNTR_PAIN+1]),
|
||
|
S_NORMAL (MNTR, 'E', 6, A_Pain , &States[S_MNTR_WALK+0]),
|
||
|
|
||
|
#define S_MNTR_DIE (S_MNTR_PAIN+2)
|
||
|
S_NORMAL (MNTR, 'F', 6, NULL , &States[S_MNTR_DIE+1]),
|
||
|
S_NORMAL (MNTR, 'G', 5, NULL , &States[S_MNTR_DIE+2]),
|
||
|
S_NORMAL (MNTR, 'H', 6, A_Scream , &States[S_MNTR_DIE+3]),
|
||
|
S_NORMAL (MNTR, 'I', 5, NULL , &States[S_MNTR_DIE+4]),
|
||
|
S_NORMAL (MNTR, 'J', 6, NULL , &States[S_MNTR_DIE+5]),
|
||
|
S_NORMAL (MNTR, 'K', 5, NULL , &States[S_MNTR_DIE+6]),
|
||
|
S_NORMAL (MNTR, 'L', 6, NULL , &States[S_MNTR_DIE+7]),
|
||
|
S_NORMAL (MNTR, 'M', 5, A_NoBlocking , &States[S_MNTR_DIE+8]),
|
||
|
S_NORMAL (MNTR, 'N', 6, NULL , &States[S_MNTR_DIE+9]),
|
||
|
S_NORMAL (MNTR, 'O', 5, NULL , &States[S_MNTR_DIE+10]),
|
||
|
S_NORMAL (MNTR, 'P', 6, NULL , &States[S_MNTR_DIE+11]),
|
||
|
S_NORMAL (MNTR, 'Q', 5, NULL , &States[S_MNTR_DIE+12]),
|
||
|
S_NORMAL (MNTR, 'R', 6, NULL , &States[S_MNTR_DIE+13]),
|
||
|
S_NORMAL (MNTR, 'S', 5, NULL , &States[S_MNTR_DIE+14]),
|
||
|
S_NORMAL (MNTR, 'T', -1, A_BossDeath , NULL),
|
||
|
|
||
|
#define S_MNTR_FADEIN (S_MNTR_DIE+15)
|
||
|
S_NORMAL (MNTR, 'A', 15, NULL , &States[S_MNTR_FADEIN+1]),
|
||
|
S_NORMAL (MNTR, 'A', 15, A_MinotaurFade1 , &States[S_MNTR_FADEIN+2]),
|
||
|
S_NORMAL (MNTR, 'A', 3, A_MinotaurFade2 , &States[S_MNTR_LOOK]),
|
||
|
|
||
|
#define S_MNTR_FADEOUT (S_MNTR_FADEIN+3)
|
||
|
S_NORMAL (MNTR, 'E', 6, NULL , &States[S_MNTR_FADEOUT+1]),
|
||
|
S_NORMAL (MNTR, 'E', 2, A_Scream , &States[S_MNTR_FADEOUT+2]),
|
||
|
S_NORMAL (MNTR, 'E', 5, A_SmokePuffExit , &States[S_MNTR_FADEOUT+3]),
|
||
|
S_NORMAL (MNTR, 'E', 5, NULL , &States[S_MNTR_FADEOUT+4]),
|
||
|
S_NORMAL (MNTR, 'E', 5, A_NoBlocking , &States[S_MNTR_FADEOUT+5]),
|
||
|
S_NORMAL (MNTR, 'E', 5, NULL , &States[S_MNTR_FADEOUT+6]),
|
||
|
S_NORMAL (MNTR, 'E', 5, A_MinotaurFade1 , &States[S_MNTR_FADEOUT+7]),
|
||
|
S_NORMAL (MNTR, 'E', 5, A_MinotaurFade0 , &States[S_MNTR_FADEOUT+8]),
|
||
|
S_NORMAL (MNTR, 'E', 10, NULL , NULL),
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_ACTOR (AMinotaur, Heretic, 9, 0)
|
||
|
PROP_SpawnHealth (3000)
|
||
|
PROP_RadiusFixed (28)
|
||
|
PROP_HeightFixed (100)
|
||
|
PROP_Mass (800)
|
||
|
PROP_SpeedFixed (16)
|
||
|
PROP_Damage (7)
|
||
|
PROP_PainChance (25)
|
||
|
PROP_Flags (MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL|MF_DROPOFF)
|
||
|
PROP_Flags2 (MF2_FLOORCLIP|MF2_PASSMOBJ|MF2_BOSS|MF2_PUSHWALL)
|
||
|
PROP_Flags3 (MF3_NORADIUSDMG|MF3_DONTMORPH|MF3_NOTARGET)
|
||
|
|
||
|
PROP_SpawnState (S_MNTR_LOOK)
|
||
|
PROP_SeeState (S_MNTR_WALK)
|
||
|
PROP_PainState (S_MNTR_PAIN)
|
||
|
PROP_MeleeState (S_MNTR_ATK1)
|
||
|
PROP_MissileState (S_MNTR_ATK2)
|
||
|
PROP_DeathState (S_MNTR_DIE)
|
||
|
|
||
|
PROP_SeeSound ("minotaur/sight")
|
||
|
PROP_AttackSound ("minotaur/attack1")
|
||
|
PROP_PainSound ("minotaur/pain")
|
||
|
PROP_DeathSound ("minotaur/death")
|
||
|
PROP_ActiveSound ("minotaur/active")
|
||
|
END_DEFAULTS
|
||
|
|
||
|
void AMinotaur::BeginPlay ()
|
||
|
{
|
||
|
Super::BeginPlay ();
|
||
|
StartTime = -1;
|
||
|
}
|
||
|
|
||
|
void AMinotaur::Serialize (FArchive &arc)
|
||
|
{
|
||
|
Super::Serialize (arc);
|
||
|
arc << StartTime;
|
||
|
}
|
||
|
|
||
|
void AMinotaur::Tick ()
|
||
|
{
|
||
|
Super::Tick ();
|
||
|
|
||
|
// The unfriendly Minotaur (Heretic's) is invulnerable while charging
|
||
|
if (!(flags & MF_FRIENDLY))
|
||
|
{
|
||
|
// Get MF_SKULLFLY bit and shift it so it matches MF2_INVULNERABLE
|
||
|
DWORD flying = (flags & MF_SKULLFLY) << 3;
|
||
|
if ((flags2 & MF2_INVULNERABLE) != flying)
|
||
|
{
|
||
|
flags2 ^= MF2_INVULNERABLE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AMinotaur::NoBlockingSet ()
|
||
|
{
|
||
|
if (!(flags & MF_FRIENDLY))
|
||
|
{
|
||
|
P_DropItem (this, "ArtiSuperHealth", 0, 51);
|
||
|
P_DropItem (this, "PhoenixRodAmmo", 10, 84);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool AMinotaur::Slam (AActor *thing)
|
||
|
{
|
||
|
// Slamming minotaurs shouldn't move non-creatures
|
||
|
if (!(thing->flags3&MF3_ISMONSTER) && !thing->player)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return Super::Slam (thing);
|
||
|
}
|
||
|
|
||
|
int AMinotaur::DoSpecialDamage (AActor *target, int damage)
|
||
|
{
|
||
|
damage = Super::DoSpecialDamage (target, damage);
|
||
|
if ((damage != -1) && (flags & MF_SKULLFLY))
|
||
|
{ // Slam only when in charge mode
|
||
|
P_MinotaurSlam (this, target);
|
||
|
return -1;
|
||
|
}
|
||
|
return damage;
|
||
|
}
|
||
|
|
||
|
bool AMinotaur::IsOkayToAttack (AActor *link)
|
||
|
{
|
||
|
if ((link->flags3&MF3_ISMONSTER) && (link != tracer))
|
||
|
{
|
||
|
if (!((link->flags ^ flags) & MF_FRIENDLY))
|
||
|
{ // Don't attack friends
|
||
|
if (link->flags & MF_FRIENDLY)
|
||
|
{
|
||
|
if (!deathmatch || link->FriendPlayer == 0 || FriendPlayer == 0 ||
|
||
|
link->FriendPlayer != FriendPlayer)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if (!(link->flags&MF_SHOOTABLE))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (link->flags2&MF2_DORMANT)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if ((link->IsKindOf (RUNTIME_CLASS(AMinotaur))) &&
|
||
|
(link->tracer == tracer))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (multiplayer && !deathmatch && link->player)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (P_CheckSight (this, link))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void AMinotaur::Die (AActor *source, AActor *inflictor)
|
||
|
{
|
||
|
Super::Die (source, inflictor);
|
||
|
|
||
|
if (tracer && tracer->health > 0 && tracer->player)
|
||
|
{
|
||
|
// Search thinker list for minotaur
|
||
|
TThinkerIterator<AMinotaur> iterator;
|
||
|
AMinotaur *mo;
|
||
|
|
||
|
while ((mo = iterator.Next()) != NULL)
|
||
|
{
|
||
|
if (mo->health <= 0) continue;
|
||
|
// [RH] Minotaurs can't be morphed, so this isn't needed
|
||
|
//if (!(mo->flags&MF_COUNTKILL)) continue; // for morphed minotaurs
|
||
|
if (mo->flags&MF_CORPSE) continue;
|
||
|
if (mo->StartTime >= 0 && (level.maptime - StartTime) >= MAULATORTICS) continue;
|
||
|
if (mo->tracer != NULL && mo->tracer->player == tracer->player) break;
|
||
|
}
|
||
|
|
||
|
if (mo == NULL)
|
||
|
{
|
||
|
AInventory *power = tracer->FindInventory (RUNTIME_CLASS(APowerMinotaur));
|
||
|
if (power != NULL)
|
||
|
{
|
||
|
power->Destroy ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool AMinotaur::OkayToSwitchTarget (AActor *other)
|
||
|
{ // Minotaurs with masters never change their target until
|
||
|
// they've killed their current one.
|
||
|
if (tracer != NULL && tracer->health > 0)
|
||
|
return false;
|
||
|
if (other == tracer)
|
||
|
return false; // Do not target the master
|
||
|
return Super::OkayToSwitchTarget (other);
|
||
|
}
|
||
|
|
||
|
// Minotaur Friend ----------------------------------------------------------
|
||
|
|
||
|
IMPLEMENT_STATELESS_ACTOR (AMinotaurFriend, Hexen, -1, 0)
|
||
|
PROP_SpawnState (S_MNTR_FADEIN)
|
||
|
PROP_DeathState (S_MNTR_FADEOUT)
|
||
|
PROP_RenderStyle (STYLE_Translucent)
|
||
|
PROP_Alpha (OPAQUE/3)
|
||
|
|
||
|
PROP_SpawnHealth (2500)
|
||
|
PROP_FlagsClear (MF_DROPOFF|MF_COUNTKILL)
|
||
|
PROP_FlagsSet (MF_FRIENDLY)
|
||
|
PROP_Flags2Clear (MF2_BOSS)
|
||
|
PROP_Flags2Set (MF2_TELESTOMP)
|
||
|
PROP_Flags3Set (MF3_STAYMORPHED)
|
||
|
PROP_Flags3Clear (MF3_DONTMORPH)
|
||
|
END_DEFAULTS
|
||
|
|
||
|
AT_GAME_SET (Minotaur)
|
||
|
{
|
||
|
// Modify the Minotaur to accomodate the difference in Hexen's graphics.
|
||
|
// (Hexen's has fewer frames than Heretic's.) Sprites have not been loaded
|
||
|
// yet, so check the wad for their presence.
|
||
|
if (Wads.CheckNumForName ("MNTRZ1", ns_sprites) < 0 &&
|
||
|
Wads.CheckNumForName ("MNTRZ0", ns_sprites) < 0)
|
||
|
{
|
||
|
AMinotaur::States[S_MNTR_ATK1+0].SetFrame ('G');
|
||
|
AMinotaur::States[S_MNTR_ATK1+1].SetFrame ('H');
|
||
|
AMinotaur::States[S_MNTR_ATK1+2].SetFrame ('I');
|
||
|
AMinotaur::States[S_MNTR_ATK2+0].SetFrame ('G');
|
||
|
AMinotaur::States[S_MNTR_ATK2+1].SetFrame ('J');
|
||
|
AMinotaur::States[S_MNTR_ATK2+2].SetFrame ('K');
|
||
|
AMinotaur::States[S_MNTR_ATK3+0].SetFrame ('G');
|
||
|
AMinotaur::States[S_MNTR_ATK3+1].SetFrame ('H');
|
||
|
AMinotaur::States[S_MNTR_ATK3+2].SetFrame ('I');
|
||
|
AMinotaur::States[S_MNTR_ATK3+3].SetFrame ('I');
|
||
|
AMinotaur::States[S_MNTR_ATK4+0].SetFrame ('F');
|
||
|
|
||
|
GetDefault<AMinotaur>()->DeathState = &AMinotaur::States[S_MNTR_FADEOUT];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AMinotaurFriend::NoBlockingSet ()
|
||
|
{
|
||
|
// Do not drop anything
|
||
|
}
|
||
|
|
||
|
// Minotaur FX 1 ------------------------------------------------------------
|
||
|
|
||
|
class AMinotaurFX1 : public AActor
|
||
|
{
|
||
|
DECLARE_ACTOR (AMinotaurFX1, AActor)
|
||
|
};
|
||
|
|
||
|
FState AMinotaurFX1::States[] =
|
||
|
{
|
||
|
#define S_MNTRFX1 0
|
||
|
S_BRIGHT (FX12, 'A', 6, NULL , &States[S_MNTRFX1+1]),
|
||
|
S_BRIGHT (FX12, 'B', 6, NULL , &States[S_MNTRFX1+0]),
|
||
|
|
||
|
#define S_MNTRFXI1 (S_MNTRFX1+2)
|
||
|
S_BRIGHT (FX12, 'C', 5, NULL , &States[S_MNTRFXI1+1]),
|
||
|
S_BRIGHT (FX12, 'D', 5, NULL , &States[S_MNTRFXI1+2]),
|
||
|
S_BRIGHT (FX12, 'E', 5, NULL , &States[S_MNTRFXI1+3]),
|
||
|
S_BRIGHT (FX12, 'F', 5, NULL , &States[S_MNTRFXI1+4]),
|
||
|
S_BRIGHT (FX12, 'G', 5, NULL , &States[S_MNTRFXI1+5]),
|
||
|
S_BRIGHT (FX12, 'H', 5, NULL , NULL)
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_ACTOR (AMinotaurFX1, Raven, -1, 0)
|
||
|
PROP_RadiusFixed (10)
|
||
|
PROP_HeightFixed (6)
|
||
|
PROP_SpeedFixed (20)
|
||
|
PROP_Damage (3)
|
||
|
PROP_DamageType (MOD_FIRE)
|
||
|
PROP_Flags (MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY)
|
||
|
PROP_Flags2 (MF2_NOTELEPORT)
|
||
|
PROP_RenderStyle (STYLE_Add)
|
||
|
|
||
|
PROP_SpawnState (S_MNTRFX1)
|
||
|
PROP_DeathState (S_MNTRFXI1)
|
||
|
END_DEFAULTS
|
||
|
|
||
|
AT_SPEED_SET (MinotaurFX1, speed)
|
||
|
{
|
||
|
SimpleSpeedSetter (AMinotaurFX1, 20*FRACUNIT, 26*FRACUNIT, speed);
|
||
|
}
|
||
|
|
||
|
// Minotaur FX 2 ------------------------------------------------------------
|
||
|
|
||
|
class AMinotaurFX2 : public AMinotaurFX1
|
||
|
{
|
||
|
DECLARE_ACTOR (AMinotaurFX2, AMinotaurFX1)
|
||
|
public:
|
||
|
void GetExplodeParms (int &damage, int &distance, bool &hurtSource)
|
||
|
{
|
||
|
damage = 24;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
FState AMinotaurFX2::States[] =
|
||
|
{
|
||
|
#define S_MNTRFX2 0
|
||
|
S_NORMAL (FX13, 'A', 2, A_MntrFloorFire , &States[S_MNTRFX2+0]),
|
||
|
|
||
|
#define S_MNTRFXI2 (S_MNTRFX2+1)
|
||
|
S_BRIGHT (FX13, 'I', 4, A_Explode , &States[S_MNTRFXI2+1]),
|
||
|
S_BRIGHT (FX13, 'J', 4, NULL , &States[S_MNTRFXI2+2]),
|
||
|
S_BRIGHT (FX13, 'K', 4, NULL , &States[S_MNTRFXI2+3]),
|
||
|
S_BRIGHT (FX13, 'L', 4, NULL , &States[S_MNTRFXI2+4]),
|
||
|
S_BRIGHT (FX13, 'M', 4, NULL , NULL)
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_ACTOR (AMinotaurFX2, Raven, -1, 0)
|
||
|
PROP_RadiusFixed (5)
|
||
|
PROP_HeightFixed (12)
|
||
|
PROP_SpeedFixed (14)
|
||
|
PROP_Damage (4)
|
||
|
PROP_Flags3 (MF3_FLOORHUGGER)
|
||
|
|
||
|
PROP_SpawnState (S_MNTRFX2)
|
||
|
PROP_DeathState (S_MNTRFXI2)
|
||
|
|
||
|
PROP_DeathSound ("minotaur/fx2hit")
|
||
|
END_DEFAULTS
|
||
|
|
||
|
AT_SPEED_SET (MinotaurFX2, speed)
|
||
|
{
|
||
|
SimpleSpeedSetter (AMinotaurFX2, 14*FRACUNIT, 20*FRACUNIT, speed);
|
||
|
}
|
||
|
|
||
|
// Minotaur FX 3 ------------------------------------------------------------
|
||
|
|
||
|
class AMinotaurFX3 : public AMinotaurFX2
|
||
|
{
|
||
|
DECLARE_ACTOR (AMinotaurFX3, AMinotaurFX2)
|
||
|
public:
|
||
|
void GetExplodeParms (int &damage, int &distance, bool &hurtSource)
|
||
|
{
|
||
|
damage = 128;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
FState AMinotaurFX3::States[] =
|
||
|
{
|
||
|
#define S_MNTRFX3 0
|
||
|
S_BRIGHT (FX13, 'D', 4, NULL , &States[S_MNTRFX3+1]),
|
||
|
S_BRIGHT (FX13, 'C', 4, NULL , &States[S_MNTRFX3+2]),
|
||
|
S_BRIGHT (FX13, 'B', 5, NULL , &States[S_MNTRFX3+3]),
|
||
|
S_BRIGHT (FX13, 'C', 5, NULL , &States[S_MNTRFX3+4]),
|
||
|
S_BRIGHT (FX13, 'D', 5, NULL , &States[S_MNTRFX3+5]),
|
||
|
S_BRIGHT (FX13, 'E', 5, NULL , &States[S_MNTRFX3+6]),
|
||
|
S_BRIGHT (FX13, 'F', 4, NULL , &States[S_MNTRFX3+7]),
|
||
|
S_BRIGHT (FX13, 'G', 4, NULL , &States[S_MNTRFX3+8]),
|
||
|
S_BRIGHT (FX13, 'H', 4, NULL , NULL)
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_ACTOR (AMinotaurFX3, Raven, -1, 0)
|
||
|
PROP_RadiusFixed (8)
|
||
|
PROP_HeightFixed (16)
|
||
|
PROP_SpeedFixed (0)
|
||
|
|
||
|
PROP_SpawnState (S_MNTRFX3)
|
||
|
|
||
|
PROP_DeathSound ("minotaur/fx3hit")
|
||
|
END_DEFAULTS
|
||
|
|
||
|
// Minotaur Smoke Exit ------------------------------------------------------
|
||
|
|
||
|
class AMinotaurSmokeExit : public AActor
|
||
|
{
|
||
|
DECLARE_ACTOR (AMinotaurSmokeExit, AActor)
|
||
|
};
|
||
|
|
||
|
FState AMinotaurSmokeExit::States[] =
|
||
|
{
|
||
|
S_NORMAL (MNSM, 'A', 3, NULL , &States[1]),
|
||
|
S_NORMAL (MNSM, 'B', 3, NULL , &States[2]),
|
||
|
S_NORMAL (MNSM, 'C', 3, NULL , &States[3]),
|
||
|
S_NORMAL (MNSM, 'D', 3, NULL , &States[4]),
|
||
|
S_NORMAL (MNSM, 'E', 3, NULL , &States[5]),
|
||
|
S_NORMAL (MNSM, 'F', 3, NULL , &States[6]),
|
||
|
S_NORMAL (MNSM, 'G', 3, NULL , &States[7]),
|
||
|
S_NORMAL (MNSM, 'H', 3, NULL , &States[8]),
|
||
|
S_NORMAL (MNSM, 'I', 3, NULL , &States[9]),
|
||
|
S_NORMAL (MNSM, 'J', 3, NULL , &States[10]),
|
||
|
S_NORMAL (MNSM, 'I', 3, NULL , &States[11]),
|
||
|
S_NORMAL (MNSM, 'H', 3, NULL , &States[12]),
|
||
|
S_NORMAL (MNSM, 'G', 3, NULL , &States[13]),
|
||
|
S_NORMAL (MNSM, 'F', 3, NULL , &States[14]),
|
||
|
S_NORMAL (MNSM, 'E', 3, NULL , &States[15]),
|
||
|
S_NORMAL (MNSM, 'D', 3, NULL , &States[16]),
|
||
|
S_NORMAL (MNSM, 'C', 3, NULL , &States[17]),
|
||
|
S_NORMAL (MNSM, 'B', 3, NULL , &States[18]),
|
||
|
S_NORMAL (MNSM, 'A', 3, NULL , NULL),
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_ACTOR (AMinotaurSmokeExit, Hexen, -1, 0)
|
||
|
PROP_Flags (MF_NOBLOCKMAP|MF_NOGRAVITY)
|
||
|
PROP_Flags2 (MF2_NOTELEPORT)
|
||
|
PROP_RenderStyle (STYLE_Translucent)
|
||
|
PROP_Alpha (HX_SHADOW)
|
||
|
|
||
|
PROP_SpawnState (0)
|
||
|
END_DEFAULTS
|
||
|
|
||
|
// Action functions for the minotaur ----------------------------------------
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurAtk1
|
||
|
//
|
||
|
// Melee attack.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurAtk1 (AActor *actor)
|
||
|
{
|
||
|
player_t *player;
|
||
|
|
||
|
if (!actor->target)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
S_Sound (actor, CHAN_WEAPON, "minotaur/melee", 1, ATTN_NORM);
|
||
|
if (actor->CheckMeleeRange())
|
||
|
{
|
||
|
int damage = pr_minotauratk1.HitDice (4);
|
||
|
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
|
||
|
P_TraceBleed (damage, actor->target, actor);
|
||
|
if ((player = actor->target->player) != NULL &&
|
||
|
player->mo == actor->target)
|
||
|
{ // Squish the player
|
||
|
player->deltaviewheight = -16*FRACUNIT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurDecide
|
||
|
//
|
||
|
// Choose a missile attack.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#define MNTR_CHARGE_SPEED (13*FRACUNIT)
|
||
|
|
||
|
void A_MinotaurDecide (AActor *actor)
|
||
|
{
|
||
|
bool friendly = !!(actor->flags & MF_FRIENDLY);
|
||
|
angle_t angle;
|
||
|
AActor *target;
|
||
|
int dist;
|
||
|
|
||
|
target = actor->target;
|
||
|
if (!target)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (!friendly)
|
||
|
{
|
||
|
S_Sound (actor, CHAN_WEAPON, "minotaur/sight", 1, ATTN_NORM);
|
||
|
}
|
||
|
dist = P_AproxDistance (actor->x-target->x, actor->y-target->y);
|
||
|
if (target->z+target->height > actor->z
|
||
|
&& target->z+target->height < actor->z+actor->height
|
||
|
&& dist < (friendly ? 16*64*FRACUNIT : 8*64*FRACUNIT)
|
||
|
&& dist > 1*64*FRACUNIT
|
||
|
&& pr_minotaurdecide() < 150)
|
||
|
{ // Charge attack
|
||
|
// Don't call the state function right away
|
||
|
actor->SetStateNF (&AMinotaur::States[S_MNTR_ATK4]);
|
||
|
actor->flags |= MF_SKULLFLY;
|
||
|
if (!friendly)
|
||
|
{ // Heretic's Minotaur is invulnerable during charge attack
|
||
|
actor->flags2 |= MF2_INVULNERABLE;
|
||
|
}
|
||
|
A_FaceTarget (actor);
|
||
|
angle = actor->angle>>ANGLETOFINESHIFT;
|
||
|
actor->momx = FixedMul (MNTR_CHARGE_SPEED, finecosine[angle]);
|
||
|
actor->momy = FixedMul (MNTR_CHARGE_SPEED, finesine[angle]);
|
||
|
actor->special1 = TICRATE/2; // Charge duration
|
||
|
}
|
||
|
else if (target->z == target->floorz
|
||
|
&& dist < 9*64*FRACUNIT
|
||
|
&& pr_minotaurdecide() < (friendly ? 100 : 220))
|
||
|
{ // Floor fire attack
|
||
|
actor->SetState (&AMinotaur::States[S_MNTR_ATK3]);
|
||
|
actor->special2 = 0;
|
||
|
}
|
||
|
else
|
||
|
{ // Swing attack
|
||
|
A_FaceTarget (actor);
|
||
|
// Don't need to call P_SetMobjState because the current state
|
||
|
// falls through to the swing attack
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurCharge
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurCharge (AActor *actor)
|
||
|
{
|
||
|
AActor *puff;
|
||
|
|
||
|
if (!actor->target) return;
|
||
|
|
||
|
if (actor->special1 > 0)
|
||
|
{
|
||
|
const TypeInfo *type;
|
||
|
|
||
|
if (gameinfo.gametype == GAME_Heretic)
|
||
|
{
|
||
|
type = TypeInfo::FindType ("PhoenixPuff");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
type = TypeInfo::FindType ("PunchPuff");
|
||
|
}
|
||
|
puff = Spawn (type, actor->x, actor->y, actor->z);
|
||
|
puff->momz = 2*FRACUNIT;
|
||
|
actor->special1--;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
actor->flags &= ~MF_SKULLFLY;
|
||
|
actor->flags2 &= ~MF2_INVULNERABLE;
|
||
|
actor->SetState (actor->SeeState);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurAtk2
|
||
|
//
|
||
|
// Swing attack.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurAtk2 (AActor *actor)
|
||
|
{
|
||
|
AActor *mo;
|
||
|
angle_t angle;
|
||
|
fixed_t momz;
|
||
|
fixed_t z;
|
||
|
|
||
|
if (!actor->target)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
S_Sound (actor, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
||
|
if (actor->CheckMeleeRange())
|
||
|
{
|
||
|
int damage;
|
||
|
damage = pr_atk.HitDice ((actor->flags & MF_FRIENDLY) ? 3 : 5);
|
||
|
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
|
||
|
P_TraceBleed (damage, actor->target, actor);
|
||
|
return;
|
||
|
}
|
||
|
z = actor->z + 40*FRACUNIT;
|
||
|
mo = P_SpawnMissileZ (actor, z, actor->target, RUNTIME_CLASS(AMinotaurFX1));
|
||
|
if (mo != NULL)
|
||
|
{
|
||
|
// S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
||
|
momz = mo->momz;
|
||
|
angle = mo->angle;
|
||
|
P_SpawnMissileAngleZ (actor, z, RUNTIME_CLASS(AMinotaurFX1), angle-(ANG45/8), momz);
|
||
|
P_SpawnMissileAngleZ (actor, z, RUNTIME_CLASS(AMinotaurFX1), angle+(ANG45/8), momz);
|
||
|
P_SpawnMissileAngleZ (actor, z, RUNTIME_CLASS(AMinotaurFX1), angle-(ANG45/16), momz);
|
||
|
P_SpawnMissileAngleZ (actor, z, RUNTIME_CLASS(AMinotaurFX1), angle+(ANG45/16), momz);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurAtk3
|
||
|
//
|
||
|
// Floor fire attack.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurAtk3 (AActor *actor)
|
||
|
{
|
||
|
AActor *mo;
|
||
|
player_t *player;
|
||
|
|
||
|
if (!actor->target)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
S_Sound (actor, CHAN_VOICE, "minotaur/attack3", 1, ATTN_NORM);
|
||
|
if (actor->CheckMeleeRange())
|
||
|
{
|
||
|
int damage;
|
||
|
|
||
|
damage = pr_minotauratk3.HitDice ((actor->flags & MF_FRIENDLY) ? 3 : 5);
|
||
|
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
|
||
|
P_TraceBleed (damage, actor->target, actor);
|
||
|
if ((player = actor->target->player) != NULL &&
|
||
|
player->mo == actor->target)
|
||
|
{ // Squish the player
|
||
|
player->deltaviewheight = -16*FRACUNIT;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mo = P_SpawnMissileZ (actor, ONFLOORZ, actor->target, RUNTIME_CLASS(AMinotaurFX2));
|
||
|
if (mo != NULL)
|
||
|
{
|
||
|
S_Sound (mo, CHAN_WEAPON, "minotaur/attack1", 1, ATTN_NORM);
|
||
|
}
|
||
|
}
|
||
|
if (pr_minotauratk3() < 192 && actor->special2 == 0)
|
||
|
{
|
||
|
actor->SetState (&AMinotaur::States[S_MNTR_ATK3+3]);
|
||
|
actor->special2 = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MntrFloorFire
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MntrFloorFire (AActor *actor)
|
||
|
{
|
||
|
AActor *mo;
|
||
|
fixed_t x, y;
|
||
|
|
||
|
actor->z = actor->floorz;
|
||
|
x = actor->x + (pr_fire.Random2 () << 10);
|
||
|
y = actor->y + (pr_fire.Random2 () << 10);
|
||
|
mo = Spawn<AMinotaurFX3> (x, y, ONFLOORZ);
|
||
|
mo->target = actor->target;
|
||
|
mo->momx = 1; // Force block checking
|
||
|
P_CheckMissileSpawn (mo);
|
||
|
}
|
||
|
|
||
|
//---------------------------------------------------------------------------
|
||
|
//
|
||
|
// FUNC P_MinotaurSlam
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
void P_MinotaurSlam (AActor *source, AActor *target)
|
||
|
{
|
||
|
angle_t angle;
|
||
|
fixed_t thrust;
|
||
|
int damage;
|
||
|
|
||
|
angle = R_PointToAngle2 (source->x, source->y, target->x, target->y);
|
||
|
angle >>= ANGLETOFINESHIFT;
|
||
|
thrust = 16*FRACUNIT+(pr_minotaurslam()<<10);
|
||
|
target->momx += FixedMul (thrust, finecosine[angle]);
|
||
|
target->momy += FixedMul (thrust, finesine[angle]);
|
||
|
damage = pr_minotaurslam.HitDice (static_cast<AMinotaur *>(source) ? 4 : 6);
|
||
|
P_DamageMobj (target, NULL, NULL, damage, MOD_HIT);
|
||
|
P_TraceBleed (damage, target, angle, 0);
|
||
|
if (target->player)
|
||
|
{
|
||
|
target->reactiontime = 14+(pr_minotaurslam()&7);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Minotaur variables
|
||
|
//
|
||
|
// special1 charge duration countdown
|
||
|
// special2 internal to minotaur AI
|
||
|
// StartTime minotaur start time
|
||
|
// tracer pointer to player that spawned it
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurFade0 (AActor *actor)
|
||
|
{
|
||
|
actor->RenderStyle = STYLE_Translucent;
|
||
|
actor->alpha = OPAQUE/3;
|
||
|
}
|
||
|
|
||
|
void A_MinotaurFade1 (AActor *actor)
|
||
|
{
|
||
|
// Second level of transparency
|
||
|
actor->RenderStyle = STYLE_Translucent;
|
||
|
actor->alpha = OPAQUE*2/3;
|
||
|
}
|
||
|
|
||
|
void A_MinotaurFade2 (AActor *actor)
|
||
|
{
|
||
|
// Make fully visible
|
||
|
actor->RenderStyle = STYLE_Normal;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// A_MinotaurRoam
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void A_MinotaurRoam (AActor *actor)
|
||
|
{
|
||
|
AMinotaur *self = static_cast<AMinotaur *> (actor);
|
||
|
|
||
|
// In case pain caused him to skip his fade in.
|
||
|
actor->RenderStyle = STYLE_Normal;
|
||
|
|
||
|
if (self->StartTime >= 0 && (level.maptime - self->StartTime) >= MAULATORTICS)
|
||
|
{
|
||
|
P_DamageMobj (actor, NULL, NULL, 1000000, MOD_UNKNOWN);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (pr_minotaurroam() < 30)
|
||
|
A_MinotaurLook (actor); // adjust to closest target
|
||
|
|
||
|
if (pr_minotaurroam() < 6)
|
||
|
{
|
||
|
//Choose new direction
|
||
|
actor->movedir = pr_minotaurroam() % 8;
|
||
|
FaceMovementDirection (actor);
|
||
|
}
|
||
|
if (!P_Move(actor))
|
||
|
{
|
||
|
// Turn
|
||
|
if (pr_minotaurroam() & 1)
|
||
|
actor->movedir = (++actor->movedir)%8;
|
||
|
else
|
||
|
actor->movedir = (actor->movedir+7)%8;
|
||
|
FaceMovementDirection (actor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// PROC A_MinotaurLook
|
||
|
//
|
||
|
// Look for enemy of player
|
||
|
//----------------------------------------------------------------------------
|
||
|
#define MINOTAUR_LOOK_DIST (16*54*FRACUNIT)
|
||
|
|
||
|
void A_MinotaurLook (AActor *actor)
|
||
|
{
|
||
|
AMinotaur *self = static_cast<AMinotaur *> (actor);
|
||
|
|
||
|
if (!(self->flags & MF_FRIENDLY))
|
||
|
{
|
||
|
A_Look (actor);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
AActor *mo = NULL;
|
||
|
player_t *player;
|
||
|
fixed_t dist;
|
||
|
int i;
|
||
|
AActor *master = actor->tracer;
|
||
|
|
||
|
actor->target = NULL;
|
||
|
if (deathmatch) // Quick search for players
|
||
|
{
|
||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||
|
{
|
||
|
if (!playeringame[i]) continue;
|
||
|
player = &players[i];
|
||
|
mo = player->mo;
|
||
|
if (mo == master) continue;
|
||
|
if (mo->health <= 0) continue;
|
||
|
dist = P_AproxDistance(actor->x - mo->x, actor->y - mo->y);
|
||
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
||
|
actor->target = mo;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!actor->target) // Near player monster search
|
||
|
{
|
||
|
if (master && (master->health>0) && (master->player))
|
||
|
mo = P_RoughMonsterSearch(master, 20);
|
||
|
else
|
||
|
mo = P_RoughMonsterSearch(actor, 20);
|
||
|
actor->target = mo;
|
||
|
}
|
||
|
|
||
|
if (!actor->target) // Normal monster search
|
||
|
{
|
||
|
FActorIterator iterator (0);
|
||
|
|
||
|
while ((mo = iterator.Next()) != NULL)
|
||
|
{
|
||
|
if (!(mo->flags3&MF3_ISMONSTER)) continue;
|
||
|
if (mo->health <= 0) continue;
|
||
|
if (!(mo->flags&MF_SHOOTABLE)) continue;
|
||
|
dist = P_AproxDistance (actor->x - mo->x, actor->y - mo->y);
|
||
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
||
|
if ((mo == master) || (mo == actor)) continue;
|
||
|
if ((mo->IsKindOf (RUNTIME_CLASS(AMinotaur))) &&
|
||
|
(mo->tracer == master)) continue;
|
||
|
actor->target = mo;
|
||
|
break; // Found actor to attack
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (actor->target)
|
||
|
{
|
||
|
actor->SetStateNF (&AMinotaur::States[S_MNTR_WALK]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
actor->SetStateNF (&AMinotaur::States[S_MNTR_ROAM]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void A_MinotaurChase (AActor *actor)
|
||
|
{
|
||
|
AMinotaur *self = static_cast<AMinotaur *> (actor);
|
||
|
|
||
|
// In case pain caused him to skip his fade in.
|
||
|
actor->RenderStyle = STYLE_Normal;
|
||
|
|
||
|
if (self->StartTime >= 0 && (level.maptime - self->StartTime) >= MAULATORTICS)
|
||
|
{
|
||
|
P_DamageMobj (actor, NULL, NULL, 1000000, MOD_UNKNOWN);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!(self->flags & MF_FRIENDLY))
|
||
|
{
|
||
|
A_Chase (actor);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (pr_minotaurchase() < 30)
|
||
|
A_MinotaurLook (actor); // adjust to closest target
|
||
|
|
||
|
if (!actor->target || (actor->target->health <= 0) ||
|
||
|
!(actor->target->flags&MF_SHOOTABLE))
|
||
|
{ // look for a new target
|
||
|
actor->SetState (&AMinotaur::States[S_MNTR_LOOK]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FaceMovementDirection (actor);
|
||
|
actor->reactiontime = 0;
|
||
|
|
||
|
// Melee attack
|
||
|
if (actor->MeleeState && actor->CheckMeleeRange ())
|
||
|
{
|
||
|
if (actor->AttackSound)
|
||
|
{
|
||
|
S_SoundID (actor, CHAN_WEAPON, actor->AttackSound, 1, ATTN_NORM);
|
||
|
}
|
||
|
actor->SetState (actor->MeleeState);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Missile attack
|
||
|
if (actor->MissileState && P_CheckMissileRange(actor))
|
||
|
{
|
||
|
actor->SetState (actor->MissileState);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// chase towards target
|
||
|
if (!P_Move (actor))
|
||
|
{
|
||
|
P_NewChaseDir (actor);
|
||
|
}
|
||
|
|
||
|
// Active sound
|
||
|
if (pr_minotaurchase() < 6)
|
||
|
{
|
||
|
actor->PlayActiveSound ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
void A_SmokePuffEntry(mobj_t *actor)
|
||
|
{
|
||
|
P_SpawnMobj(actor->x, actor->y, actor->z, MT_MNTRSMOKE);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
void A_SmokePuffExit (AActor *actor)
|
||
|
{
|
||
|
Spawn<AMinotaurSmokeExit> (actor->x, actor->y, actor->z);
|
||
|
}
|