vkdoom_m/src/g_raven/a_minotaur.cpp
Randy Heit e4af82ae96 - Enough with this "momentum" garbage. What Doom calls "momentum" is really
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)
2009-06-30 20:57:51 +00:00

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 ();
}
}