qzdoom-gpl/src/g_raven/a_minotaur.cpp
Randy Heit 549712e719 - P_DamageMobj() now returns the amount of damage actually done so that the bleed functions
can perform based on the amount of damage actually taken after all modifications are done to
  it. However, if the damage is canceled away, blood will still spawn for the original damage
  amount rather than the modified amount.

SVN r4012 (trunk)
2013-01-02 04:39:59 +00:00

599 lines
15 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"
#include "farchive.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, FName damagetype)
{
damage = Super::DoSpecialDamage (target, damage, damagetype);
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;
}
void AMinotaurFriend::Die (AActor *source, AActor *inflictor, int dmgflags)
{
Super::Die (source, inflictor, dmgflags);
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);
int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee);
P_TraceBleed (newdam > 0 ? newdam : 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->SetState (self->FindState ("Charge"), true);
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);
int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee);
P_TraceBleed (newdam > 0 ? newdam : 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);
int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee);
P_TraceBleed (newdam > 0 ? newdam : 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);
int newdam = P_DamageMobj (target, NULL, NULL, damage, NAME_Melee);
P_TraceBleed (newdam > 0 ? newdam : 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)
{
// In case pain caused him to skip his fade in.
self->RenderStyle = STYLE_Normal;
if (self->IsKindOf(RUNTIME_CLASS(AMinotaurFriend)))
{
AMinotaurFriend *self1 = static_cast<AMinotaurFriend *> (self);
if (self1->StartTime >= 0 && (level.maptime - self1->StartTime) >= MAULATORTICS)
{
P_DamageMobj (self1, NULL, NULL, TELEFRAG_DAMAGE, NAME_None);
return;
}
}
if (pr_minotaurroam() < 30)
CALL_ACTION(A_MinotaurLook, self); // adjust to closest target
if (pr_minotaurroam() < 6)
{
//Choose new direction
self->movedir = pr_minotaurroam() % 8;
FaceMovementDirection (self);
}
if (!P_Move(self))
{
// Turn
if (pr_minotaurroam() & 1)
self->movedir = (self->movedir + 1) % 8;
else
self->movedir = (self->movedir + 7) % 8;
FaceMovementDirection (self);
}
}
//----------------------------------------------------------------------------
//
// 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->SetState (self->SeeState, true);
}
else
{
self->SetState (self->FindState ("Roam"), true);
}
}
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, TELEFRAG_DAMAGE, 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 ();
}
}