velocity, and now it's known as such. The actor variables momx/momy/momz are now known as velx/vely/velz, and the ACS functions GetActorMomX/Y/Z are now known as GetActorVelX/Y/Z. For compatibility, momx/momy/momz will continue to work as aliases from DECORATE. The ACS functions, however, require you to use the new name, since they never saw an official release yet. SVN r1689 (trunk)
638 lines
16 KiB
C++
638 lines
16 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"
|
|
#include "thingdef/thingdef.h"
|
|
#include "g_level.h"
|
|
#include "doomstat.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 P_MinotaurSlam (AActor *source, AActor *target);
|
|
|
|
DECLARE_ACTION(A_MinotaurLook)
|
|
|
|
IMPLEMENT_CLASS(AMinotaur)
|
|
|
|
void AMinotaur::Tick ()
|
|
{
|
|
Super::Tick ();
|
|
|
|
// The unfriendly Minotaur (Heretic's) is invulnerable while charging
|
|
if (!(flags5 & MF5_SUMMONEDMONSTER))
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Minotaur Friend ----------------------------------------------------------
|
|
|
|
IMPLEMENT_CLASS(AMinotaurFriend)
|
|
|
|
void AMinotaurFriend::BeginPlay ()
|
|
{
|
|
Super::BeginPlay ();
|
|
StartTime = -1;
|
|
}
|
|
|
|
void AMinotaurFriend::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << StartTime;
|
|
}
|
|
|
|
bool AMinotaurFriend::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->flags5 & MF5_SUMMONEDMONSTER) && (link->tracer == tracer))
|
|
{
|
|
return false;
|
|
}
|
|
if (multiplayer && !deathmatch && link->player)
|
|
{
|
|
return false;
|
|
}
|
|
if (P_CheckSight (this, link))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AMinotaurFriend::Die (AActor *source, AActor *inflictor)
|
|
{
|
|
Super::Die (source, inflictor);
|
|
|
|
if (tracer && tracer->health > 0 && tracer->player)
|
|
{
|
|
// Search thinker list for minotaur
|
|
TThinkerIterator<AMinotaurFriend> iterator;
|
|
AMinotaurFriend *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 (PClass::FindClass("PowerMinotaur"));
|
|
if (power != NULL)
|
|
{
|
|
power->Destroy ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AMinotaurFriend::OkayToSwitchTarget (AActor *other)
|
|
{
|
|
if (other == tracer) return false; // Do not target the master
|
|
return Super::OkayToSwitchTarget (other);
|
|
}
|
|
|
|
// Action functions for the minotaur ----------------------------------------
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk1
|
|
//
|
|
// Melee attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurDeath)
|
|
{
|
|
if (Wads.CheckNumForName ("MNTRF1", ns_sprites) < 0 &&
|
|
Wads.CheckNumForName ("MNTRF0", ns_sprites) < 0)
|
|
self->SetState(self->FindState ("FadeOut"));
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk1)
|
|
{
|
|
player_t *player;
|
|
|
|
if (!self->target)
|
|
{
|
|
return;
|
|
}
|
|
S_Sound (self, CHAN_WEAPON, "minotaur/melee", 1, ATTN_NORM);
|
|
if (self->CheckMeleeRange())
|
|
{
|
|
int damage = pr_minotauratk1.HitDice (4);
|
|
P_DamageMobj (self->target, self, self, damage, NAME_Melee);
|
|
P_TraceBleed (damage, self->target, self);
|
|
if ((player = self->target->player) != NULL &&
|
|
player->mo == self->target)
|
|
{ // Squish the player
|
|
player->deltaviewheight = -16*FRACUNIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurDecide
|
|
//
|
|
// Choose a missile attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#define MNTR_CHARGE_SPEED (13*FRACUNIT)
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurDecide)
|
|
{
|
|
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
|
angle_t angle;
|
|
AActor *target;
|
|
int dist;
|
|
|
|
target = self->target;
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
if (!friendly)
|
|
{
|
|
S_Sound (self, CHAN_WEAPON, "minotaur/sight", 1, ATTN_NORM);
|
|
}
|
|
dist = P_AproxDistance (self->x-target->x, self->y-target->y);
|
|
if (target->z+target->height > self->z
|
|
&& target->z+target->height < self->z+self->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
|
|
self->SetStateNF (self->FindState ("Charge"));
|
|
self->flags |= MF_SKULLFLY;
|
|
if (!friendly)
|
|
{ // Heretic's Minotaur is invulnerable during charge attack
|
|
self->flags2 |= MF2_INVULNERABLE;
|
|
}
|
|
A_FaceTarget (self);
|
|
angle = self->angle>>ANGLETOFINESHIFT;
|
|
self->velx = FixedMul (MNTR_CHARGE_SPEED, finecosine[angle]);
|
|
self->vely = FixedMul (MNTR_CHARGE_SPEED, finesine[angle]);
|
|
self->special1 = TICRATE/2; // Charge duration
|
|
}
|
|
else if (target->z == target->floorz
|
|
&& dist < 9*64*FRACUNIT
|
|
&& pr_minotaurdecide() < (friendly ? 100 : 220))
|
|
{ // Floor fire attack
|
|
self->SetState (self->FindState ("Hammer"));
|
|
self->special2 = 0;
|
|
}
|
|
else
|
|
{ // Swing attack
|
|
A_FaceTarget (self);
|
|
// Don't need to call P_SetMobjState because the current state
|
|
// falls through to the swing attack
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurCharge
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurCharge)
|
|
{
|
|
AActor *puff;
|
|
|
|
if (!self->target) return;
|
|
|
|
if (self->special1 > 0)
|
|
{
|
|
const PClass *type;
|
|
|
|
if (gameinfo.gametype == GAME_Heretic)
|
|
{
|
|
type = PClass::FindClass ("PhoenixPuff");
|
|
}
|
|
else
|
|
{
|
|
type = PClass::FindClass ("PunchPuff");
|
|
}
|
|
puff = Spawn (type, self->x, self->y, self->z, ALLOW_REPLACE);
|
|
puff->velz = 2*FRACUNIT;
|
|
self->special1--;
|
|
}
|
|
else
|
|
{
|
|
self->flags &= ~MF_SKULLFLY;
|
|
self->flags2 &= ~MF2_INVULNERABLE;
|
|
self->SetState (self->SeeState);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk2
|
|
//
|
|
// Swing attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk2)
|
|
{
|
|
AActor *mo;
|
|
angle_t angle;
|
|
fixed_t velz;
|
|
fixed_t z;
|
|
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
|
|
|
if (!self->target)
|
|
{
|
|
return;
|
|
}
|
|
S_Sound (self, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
|
if (self->CheckMeleeRange())
|
|
{
|
|
int damage;
|
|
damage = pr_atk.HitDice (friendly ? 3 : 5);
|
|
P_DamageMobj (self->target, self, self, damage, NAME_Melee);
|
|
P_TraceBleed (damage, self->target, self);
|
|
return;
|
|
}
|
|
z = self->z + 40*FRACUNIT;
|
|
const PClass *fx = PClass::FindClass("MinotaurFX1");
|
|
if (fx)
|
|
{
|
|
mo = P_SpawnMissileZ (self, z, self->target, fx);
|
|
if (mo != NULL)
|
|
{
|
|
// S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
|
velz = mo->velz;
|
|
angle = mo->angle;
|
|
P_SpawnMissileAngleZ (self, z, fx, angle-(ANG45/8), velz);
|
|
P_SpawnMissileAngleZ (self, z, fx, angle+(ANG45/8), velz);
|
|
P_SpawnMissileAngleZ (self, z, fx, angle-(ANG45/16), velz);
|
|
P_SpawnMissileAngleZ (self, z, fx, angle+(ANG45/16), velz);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk3
|
|
//
|
|
// Floor fire attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk3)
|
|
{
|
|
AActor *mo;
|
|
player_t *player;
|
|
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
|
|
|
if (!self->target)
|
|
{
|
|
return;
|
|
}
|
|
S_Sound (self, CHAN_VOICE, "minotaur/attack3", 1, ATTN_NORM);
|
|
if (self->CheckMeleeRange())
|
|
{
|
|
int damage;
|
|
|
|
damage = pr_minotauratk3.HitDice (friendly ? 3 : 5);
|
|
P_DamageMobj (self->target, self, self, damage, NAME_Melee);
|
|
P_TraceBleed (damage, self->target, self);
|
|
if ((player = self->target->player) != NULL &&
|
|
player->mo == self->target)
|
|
{ // Squish the player
|
|
player->deltaviewheight = -16*FRACUNIT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (self->floorclip > 0 && (i_compatflags & COMPATF_MINOTAUR))
|
|
{
|
|
// only play the sound.
|
|
S_Sound (self, CHAN_WEAPON, "minotaur/fx2hit", 1, ATTN_NORM);
|
|
}
|
|
else
|
|
{
|
|
mo = P_SpawnMissile (self, self->target, PClass::FindClass("MinotaurFX2"));
|
|
if (mo != NULL)
|
|
{
|
|
S_Sound (mo, CHAN_WEAPON, "minotaur/attack1", 1, ATTN_NORM);
|
|
}
|
|
}
|
|
}
|
|
if (pr_minotauratk3() < 192 && self->special2 == 0)
|
|
{
|
|
self->SetState (self->FindState ("HammerLoop"));
|
|
self->special2 = 1;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MntrFloorFire
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MntrFloorFire)
|
|
{
|
|
AActor *mo;
|
|
fixed_t x, y;
|
|
|
|
self->z = self->floorz;
|
|
x = self->x + (pr_fire.Random2 () << 10);
|
|
y = self->y + (pr_fire.Random2 () << 10);
|
|
mo = Spawn("MinotaurFX3", x, y, self->floorz, ALLOW_REPLACE);
|
|
mo->target = self->target;
|
|
mo->velx = 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->velx += FixedMul (thrust, finecosine[angle]);
|
|
target->vely += FixedMul (thrust, finesine[angle]);
|
|
damage = pr_minotaurslam.HitDice (static_cast<AMinotaur *>(source) ? 4 : 6);
|
|
P_DamageMobj (target, NULL, NULL, damage, NAME_Melee);
|
|
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
|
|
//----------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// A_MinotaurRoam
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurRoam)
|
|
{
|
|
AMinotaurFriend *self1 = static_cast<AMinotaurFriend *> (self);
|
|
|
|
// In case pain caused him to skip his fade in.
|
|
self1->RenderStyle = STYLE_Normal;
|
|
|
|
if (self1->StartTime >= 0 && (level.maptime - self1->StartTime) >= MAULATORTICS)
|
|
{
|
|
P_DamageMobj (self1, NULL, NULL, 1000000, NAME_None);
|
|
return;
|
|
}
|
|
|
|
if (pr_minotaurroam() < 30)
|
|
CALL_ACTION(A_MinotaurLook, self1); // adjust to closest target
|
|
|
|
if (pr_minotaurroam() < 6)
|
|
{
|
|
//Choose new direction
|
|
self1->movedir = pr_minotaurroam() % 8;
|
|
FaceMovementDirection (self1);
|
|
}
|
|
if (!P_Move(self1))
|
|
{
|
|
// Turn
|
|
if (pr_minotaurroam() & 1)
|
|
self1->movedir = (++self1->movedir)%8;
|
|
else
|
|
self1->movedir = (self1->movedir+7)%8;
|
|
FaceMovementDirection (self1);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurLook
|
|
//
|
|
// Look for enemy of player
|
|
//----------------------------------------------------------------------------
|
|
#define MINOTAUR_LOOK_DIST (16*54*FRACUNIT)
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurLook)
|
|
{
|
|
if (!self->IsKindOf(RUNTIME_CLASS(AMinotaurFriend)))
|
|
{
|
|
CALL_ACTION(A_Look, self);
|
|
return;
|
|
}
|
|
|
|
AActor *mo = NULL;
|
|
player_t *player;
|
|
fixed_t dist;
|
|
int i;
|
|
AActor *master = self->tracer;
|
|
|
|
self->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(self->x - mo->x, self->y - mo->y);
|
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
|
self->target = mo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!self->target) // Near player monster search
|
|
{
|
|
if (master && (master->health>0) && (master->player))
|
|
mo = P_RoughMonsterSearch(master, 20);
|
|
else
|
|
mo = P_RoughMonsterSearch(self, 20);
|
|
self->target = mo;
|
|
}
|
|
|
|
if (!self->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 (self->x - mo->x, self->y - mo->y);
|
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
|
if ((mo == master) || (mo == self)) continue;
|
|
if ((mo->flags5 & MF5_SUMMONEDMONSTER) && (mo->tracer == master)) continue;
|
|
self->target = mo;
|
|
break; // Found actor to attack
|
|
}
|
|
}
|
|
|
|
if (self->target)
|
|
{
|
|
self->SetStateNF (self->SeeState);
|
|
}
|
|
else
|
|
{
|
|
self->SetStateNF (self->FindState ("Roam"));
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurChase)
|
|
{
|
|
if (!self->IsKindOf(RUNTIME_CLASS(AMinotaurFriend)))
|
|
{
|
|
A_Chase (self);
|
|
return;
|
|
}
|
|
|
|
AMinotaurFriend *self1 = static_cast<AMinotaurFriend *> (self);
|
|
|
|
// In case pain caused him to skip his fade in.
|
|
self1->RenderStyle = STYLE_Normal;
|
|
|
|
if (self1->StartTime >= 0 && (level.maptime - self1->StartTime) >= MAULATORTICS)
|
|
{
|
|
P_DamageMobj (self1, NULL, NULL, 1000000, NAME_None);
|
|
return;
|
|
}
|
|
|
|
if (pr_minotaurchase() < 30)
|
|
CALL_ACTION(A_MinotaurLook, self1); // adjust to closest target
|
|
|
|
if (!self1->target || (self1->target->health <= 0) ||
|
|
!(self1->target->flags&MF_SHOOTABLE))
|
|
{ // look for a new target
|
|
self1->SetState (self1->FindState ("Spawn"));
|
|
return;
|
|
}
|
|
|
|
FaceMovementDirection (self1);
|
|
self1->reactiontime = 0;
|
|
|
|
// Melee attack
|
|
if (self1->MeleeState && self1->CheckMeleeRange ())
|
|
{
|
|
if (self1->AttackSound)
|
|
{
|
|
S_Sound (self1, CHAN_WEAPON, self1->AttackSound, 1, ATTN_NORM);
|
|
}
|
|
self1->SetState (self1->MeleeState);
|
|
return;
|
|
}
|
|
|
|
// Missile attack
|
|
if (self1->MissileState && P_CheckMissileRange(self1))
|
|
{
|
|
self1->SetState (self1->MissileState);
|
|
return;
|
|
}
|
|
|
|
// chase towards target
|
|
if (!P_Move (self1))
|
|
{
|
|
P_NewChaseDir (self1);
|
|
FaceMovementDirection (self1);
|
|
}
|
|
|
|
// Active sound
|
|
if (pr_minotaurchase() < 6)
|
|
{
|
|
self1->PlayActiveSound ();
|
|
}
|
|
}
|
|
|