mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-10 14:51:40 +00:00
- scriptified the Minotaur.
Interesting tidbit: The damage calculation in P_MinotaurSlam had been incorrect for the Heretic version since the friendly Hexen Dark Servant was added, but nobody ever noticed in 14 years...
This commit is contained in:
parent
6e1c6c4b33
commit
7385cd70c0
22 changed files with 834 additions and 789 deletions
|
@ -825,7 +825,6 @@ file( GLOB HEADER_FILES
|
|||
${EXTRA_HEADER_DIRS}
|
||||
fragglescript/*.h
|
||||
g_hexen/*.h
|
||||
g_raven/*.h
|
||||
g_shared/*.h
|
||||
g_strife/*.h
|
||||
intermission/*.h
|
||||
|
@ -878,7 +877,6 @@ set( NOT_COMPILED_SOURCE_FILES
|
|||
g_hexen/a_magestaff.cpp
|
||||
g_hexen/a_serpent.cpp
|
||||
g_hexen/a_spike.cpp
|
||||
g_hexen/a_summon.cpp
|
||||
g_hexen/a_teleportother.cpp
|
||||
g_strife/a_acolyte.cpp
|
||||
g_strife/a_alienspectres.cpp
|
||||
|
@ -1147,7 +1145,6 @@ set (PCH_SOURCES
|
|||
wi_stuff.cpp
|
||||
zstrformat.cpp
|
||||
g_hexen/a_hexenmisc.cpp
|
||||
g_raven/a_minotaur.cpp
|
||||
g_strife/a_strifestuff.cpp
|
||||
g_strife/strife_sbar.cpp
|
||||
g_shared/a_action.cpp
|
||||
|
@ -1300,7 +1297,6 @@ endif()
|
|||
target_link_libraries( zdoom ${ZDOOM_LIBS} gdtoa dumb lzma )
|
||||
include_directories( .
|
||||
g_hexen
|
||||
g_raven
|
||||
g_strife
|
||||
g_shared
|
||||
oplsynth
|
||||
|
@ -1432,7 +1428,6 @@ source_group("External\\RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_D
|
|||
source_group("Externak\\SFMT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sfmt/.+")
|
||||
source_group("FraggleScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/fragglescript/.+")
|
||||
source_group("Games\\Hexen Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_hexen/.+")
|
||||
source_group("Games\\Raven Shared" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_raven/.+")
|
||||
source_group("Games\\Strife Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_strife/.+")
|
||||
source_group("Intermission" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/intermission/.+")
|
||||
source_group("Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/menu/.+")
|
||||
|
|
|
@ -628,6 +628,7 @@ public:
|
|||
|
||||
// Called when actor dies
|
||||
virtual void Die (AActor *source, AActor *inflictor, int dmgflags = 0);
|
||||
void CallDie(AActor *source, AActor *inflictor, int dmgflags = 0);
|
||||
|
||||
// Perform some special damage action. Returns the amount of damage to do.
|
||||
// Returning -1 signals the damage routine to exit immediately
|
||||
|
@ -660,6 +661,7 @@ public:
|
|||
// Actor had MF_SKULLFLY set and rammed into something
|
||||
// Returns false to stop moving and true to keep moving
|
||||
virtual bool Slam (AActor *victim);
|
||||
bool CallSlam(AActor *victim);
|
||||
|
||||
// Called by PIT_CheckThing() and needed for some Hexen things.
|
||||
// Returns -1 for normal behavior, 0 to return false, and 1 to return true.
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "p_lnspec.h"
|
||||
#include "p_terrain.h"
|
||||
#include "m_bbox.h"
|
||||
#include "ravenshared.h"
|
||||
#include "v_palette.h"
|
||||
#include "g_game.h"
|
||||
#include "p_blockmap.h"
|
||||
|
@ -47,5 +46,4 @@
|
|||
#include "a_magestaff.cpp"
|
||||
#include "a_serpent.cpp"
|
||||
#include "a_spike.cpp"
|
||||
#include "a_summon.cpp"
|
||||
#include "a_teleportother.cpp"
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
#include "info.h"
|
||||
#include "a_pickups.h"
|
||||
#include "a_artifacts.h"
|
||||
#include "gstrings.h"
|
||||
#include "p_local.h"
|
||||
#include "s_sound.h"
|
||||
#include "ravenshared.h"
|
||||
#include "vm.h"
|
||||
#include "g_level.h"
|
||||
*/
|
||||
|
||||
void A_Summon (AActor *);
|
||||
|
||||
// Dark Servant Artifact ----------------------------------------------------
|
||||
|
||||
class AArtiDarkServant : public AInventory
|
||||
{
|
||||
DECLARE_CLASS (AArtiDarkServant, AInventory)
|
||||
public:
|
||||
bool Use (bool pickup);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS(AArtiDarkServant, false, false)
|
||||
|
||||
//============================================================================
|
||||
//
|
||||
// Activate the summoning artifact
|
||||
//
|
||||
//============================================================================
|
||||
|
||||
bool AArtiDarkServant::Use (bool pickup)
|
||||
{
|
||||
AActor *mo = P_SpawnPlayerMissile (Owner, PClass::FindActor("SummoningDoll"));
|
||||
if (mo)
|
||||
{
|
||||
mo->target = Owner;
|
||||
mo->tracer = Owner;
|
||||
mo->Vel.Z = 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
//
|
||||
// A_Summon
|
||||
//
|
||||
//============================================================================
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_Summon)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
AMinotaurFriend *mo;
|
||||
|
||||
mo = Spawn<AMinotaurFriend>(self->Pos(), ALLOW_REPLACE);
|
||||
if (mo)
|
||||
{
|
||||
if (P_TestMobjLocation(mo) == false || !self->tracer)
|
||||
{ // Didn't fit - change back to artifact
|
||||
mo->Destroy();
|
||||
AActor *arti = Spawn<AArtiDarkServant>(self->Pos(), ALLOW_REPLACE);
|
||||
if (arti) arti->flags |= MF_DROPPED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mo->StartTime = level.maptime;
|
||||
if (self->tracer->flags & MF_CORPSE)
|
||||
{ // Master dead
|
||||
mo->tracer = NULL; // No master
|
||||
}
|
||||
else
|
||||
{
|
||||
mo->tracer = self->tracer; // Pointer to master
|
||||
AInventory *power = Spawn<APowerMinotaur>();
|
||||
power->CallTryPickup(self->tracer);
|
||||
mo->SetFriendPlayer(self->tracer->player);
|
||||
}
|
||||
|
||||
// Make smoke puff
|
||||
Spawn("MinotaurSmoke", self->Pos(), ALLOW_REPLACE);
|
||||
S_Sound(self, CHAN_VOICE, mo->ActiveSound, 1, ATTN_NORM);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1,623 +0,0 @@
|
|||
#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 "vm.h"
|
||||
#include "g_level.h"
|
||||
#include "doomstat.h"
|
||||
#include "a_pickups.h"
|
||||
#include "d_player.h"
|
||||
#include "serializer.h"
|
||||
#include "vm.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, false, false)
|
||||
|
||||
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, false, false)
|
||||
|
||||
void AMinotaurFriend::BeginPlay ()
|
||||
{
|
||||
Super::BeginPlay ();
|
||||
StartTime = -1;
|
||||
}
|
||||
|
||||
void AMinotaurFriend::Serialize(FSerializer &arc)
|
||||
{
|
||||
Super::Serialize (arc);
|
||||
arc("starttime", 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::FindActor("PowerMinotaur"));
|
||||
if (power != NULL)
|
||||
{
|
||||
power->Destroy ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action functions for the minotaur ----------------------------------------
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk1
|
||||
//
|
||||
// Melee attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurDeath)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
if (Wads.CheckNumForName ("MNTRF1", ns_sprites) < 0 &&
|
||||
Wads.CheckNumForName ("MNTRF0", ns_sprites) < 0)
|
||||
self->SetState(self->FindState ("FadeOut"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk1)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
player_t *player;
|
||||
|
||||
if (!self->target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurDecide
|
||||
//
|
||||
// Choose a missile attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#define MNTR_CHARGE_SPEED (13.)
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurDecide)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
||||
AActor *target;
|
||||
double dist;
|
||||
|
||||
target = self->target;
|
||||
if (!target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!friendly)
|
||||
{
|
||||
S_Sound (self, CHAN_WEAPON, "minotaur/sight", 1, ATTN_NORM);
|
||||
}
|
||||
dist = self->Distance2D(target);
|
||||
if (target->Top() > self->Z()
|
||||
&& target->Top() < self->Top()
|
||||
&& dist < (friendly ? 16*64. : 8*64.)
|
||||
&& dist > 1*64.
|
||||
&& 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);
|
||||
self->VelFromAngle(MNTR_CHARGE_SPEED);
|
||||
self->special1 = TICRATE/2; // Charge duration
|
||||
}
|
||||
else if (target->Z() == target->floorz
|
||||
&& dist < 9*64.
|
||||
&& 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
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurCharge
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurCharge)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
AActor *puff;
|
||||
|
||||
if (self->target == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (self->special1 > 0)
|
||||
{
|
||||
PClassActor *type;
|
||||
|
||||
if (gameinfo.gametype == GAME_Heretic)
|
||||
{
|
||||
type = PClass::FindActor("PhoenixPuff");
|
||||
}
|
||||
else
|
||||
{
|
||||
type = PClass::FindActor("PunchPuff");
|
||||
}
|
||||
puff = Spawn (type, self->Pos(), ALLOW_REPLACE);
|
||||
puff->Vel.Z = 2;
|
||||
self->special1--;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->flags &= ~MF_SKULLFLY;
|
||||
self->flags2 &= ~MF2_INVULNERABLE;
|
||||
self->SetState (self->SeeState);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk2
|
||||
//
|
||||
// Swing attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk2)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
AActor *mo;
|
||||
DAngle angle;
|
||||
double vz;
|
||||
double z;
|
||||
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
||||
|
||||
if (self->target == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
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 0;
|
||||
}
|
||||
z = self->Z() + 40;
|
||||
PClassActor *fx = PClass::FindActor("MinotaurFX1");
|
||||
if (fx)
|
||||
{
|
||||
mo = P_SpawnMissileZ (self, z, self->target, fx);
|
||||
if (mo != NULL)
|
||||
{
|
||||
// S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
||||
vz = mo->Vel.Z;
|
||||
angle = mo->Angles.Yaw;
|
||||
P_SpawnMissileAngleZ (self, z, fx, angle-(45./8), vz);
|
||||
P_SpawnMissileAngleZ (self, z, fx, angle+(45./8), vz);
|
||||
P_SpawnMissileAngleZ (self, z, fx, angle-(45./16), vz);
|
||||
P_SpawnMissileAngleZ (self, z, fx, angle+(45./16), vz);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk3
|
||||
//
|
||||
// Floor fire attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk3)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
AActor *mo;
|
||||
player_t *player;
|
||||
bool friendly = !!(self->flags5 & MF5_SUMMONEDMONSTER);
|
||||
|
||||
if (!self->target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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::FindActor("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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MntrFloorFire
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MntrFloorFire)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
AActor *mo;
|
||||
|
||||
self->SetZ(self->floorz);
|
||||
double x = pr_fire.Random2() / 64.;
|
||||
double y = pr_fire.Random2() / 64.;
|
||||
|
||||
mo = Spawn("MinotaurFX3", self->Vec2OffsetZ(x, y, self->floorz), ALLOW_REPLACE);
|
||||
mo->target = self->target;
|
||||
mo->Vel.X = MinVel; // Force block checking
|
||||
P_CheckMissileSpawn (mo, self->radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// FUNC P_MinotaurSlam
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void P_MinotaurSlam (AActor *source, AActor *target)
|
||||
{
|
||||
DAngle angle;
|
||||
double thrust;
|
||||
int damage;
|
||||
|
||||
angle = source->AngleTo(target);
|
||||
thrust = 16 + pr_minotaurslam() / 64.;
|
||||
target->VelFromAngle(thrust, 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)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
// 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 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurLook
|
||||
//
|
||||
// Look for enemy of player
|
||||
//----------------------------------------------------------------------------
|
||||
#define MINOTAUR_LOOK_DIST (16*54.)
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurLook)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
if (!self->IsKindOf(RUNTIME_CLASS(AMinotaurFriend)))
|
||||
{
|
||||
CALL_ACTION(A_Look, self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AActor *mo = NULL;
|
||||
player_t *player;
|
||||
double 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 = self->Distance2D(mo);
|
||||
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 = self->Distance2D(mo);
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, A_MinotaurChase)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
if (!self->IsKindOf(RUNTIME_CLASS(AMinotaurFriend)))
|
||||
{
|
||||
A_Chase (stack, self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 0;
|
||||
}
|
||||
|
||||
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->SetIdle();
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 0;
|
||||
}
|
||||
|
||||
// Missile attack
|
||||
if (self1->MissileState && P_CheckMissileRange(self1))
|
||||
{
|
||||
self1->SetState (self1->MissileState);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// chase towards target
|
||||
if (!P_Move (self1))
|
||||
{
|
||||
P_NewChaseDir (self1);
|
||||
FaceMovementDirection (self1);
|
||||
}
|
||||
|
||||
// Active sound
|
||||
if (pr_minotaurchase() < 6)
|
||||
{
|
||||
self1->PlayActiveSound ();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#ifndef __RAVENSHARED_H__
|
||||
#define __RAVENSHARED_H__
|
||||
|
||||
class AActor;
|
||||
|
||||
class AMinotaur : public AActor
|
||||
{
|
||||
DECLARE_CLASS (AMinotaur, AActor)
|
||||
public:
|
||||
int DoSpecialDamage (AActor *target, int damage, FName damagetype);
|
||||
|
||||
public:
|
||||
bool Slam (AActor *);
|
||||
void Tick ();
|
||||
};
|
||||
|
||||
class AMinotaurFriend : public AMinotaur
|
||||
{
|
||||
DECLARE_CLASS (AMinotaurFriend, AMinotaur)
|
||||
public:
|
||||
int StartTime;
|
||||
|
||||
void Die (AActor *source, AActor *inflictor, int dmgflags);
|
||||
void BeginPlay ();
|
||||
|
||||
void Serialize(FSerializer &arc);
|
||||
};
|
||||
|
||||
#endif //__RAVENSHARED_H__
|
|
@ -704,7 +704,7 @@ void AMorphedMonster::Die (AActor *source, AActor *inflictor, int dmgflags)
|
|||
if (UnmorphedMe != NULL && (UnmorphedMe->flags & MF_UNMORPHED))
|
||||
{
|
||||
UnmorphedMe->health = health;
|
||||
UnmorphedMe->Die (source, inflictor, dmgflags);
|
||||
UnmorphedMe->CallDie (source, inflictor, dmgflags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_AlienSpectreDeath)
|
|||
if (oracle->health > 0)
|
||||
{
|
||||
oracle->health = 0;
|
||||
oracle->Die (self, self);
|
||||
oracle->CallDie (self, self);
|
||||
}
|
||||
}
|
||||
player->GiveInventoryType (QuestItemClasses[22]);
|
||||
|
|
|
@ -119,7 +119,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_GetHurt)
|
|||
}
|
||||
if (self->health <= 0)
|
||||
{
|
||||
self->Die (self->target, self->target);
|
||||
self->CallDie (self->target, self->target);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
#include "d_net.h"
|
||||
#include "d_netinf.h"
|
||||
#include "a_morph.h"
|
||||
#include "virtual.h"
|
||||
|
||||
static FRandom pr_obituary ("Obituary");
|
||||
static FRandom pr_botrespawn ("BotRespawn");
|
||||
|
@ -342,7 +343,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags)
|
|||
realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed)l
|
||||
}
|
||||
}
|
||||
realthis->Die(source, inflictor, dmgflags);
|
||||
realthis->CallDie(source, inflictor, dmgflags);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -761,7 +762,26 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags)
|
|||
}
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, Die)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_OBJECT(source, AActor);
|
||||
PARAM_OBJECT(inflictor, AActor);
|
||||
PARAM_INT(dmgflags);
|
||||
self->Die(source, inflictor, dmgflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AActor::CallDie(AActor *source, AActor *inflictor, int dmgflags)
|
||||
{
|
||||
IFVIRTUAL(AActor, Die)
|
||||
{
|
||||
VMValue params[4] = { (DObject*)this, source, inflictor, dmgflags };
|
||||
VMFrameStack stack;
|
||||
stack.Call(func, params, 4, nullptr, 0, nullptr);
|
||||
}
|
||||
else return Die(source, inflictor, dmgflags);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1443,7 +1463,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage,
|
|||
source = source->tracer;
|
||||
}
|
||||
}
|
||||
target->Die (source, inflictor, flags);
|
||||
target->CallDie (source, inflictor, flags);
|
||||
return damage;
|
||||
}
|
||||
}
|
||||
|
@ -1794,7 +1814,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage,
|
|||
target->DamageType = player->poisontype;
|
||||
}
|
||||
}
|
||||
target->Die(source, source);
|
||||
target->CallDie(source, source);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1245,7 +1245,7 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch
|
|||
// Check for skulls slamming into things
|
||||
if (tm.thing->flags & MF_SKULLFLY)
|
||||
{
|
||||
bool res = tm.thing->Slam(tm.thing->BlockingMobj);
|
||||
bool res = tm.thing->CallSlam(tm.thing->BlockingMobj);
|
||||
tm.thing->BlockingMobj = NULL;
|
||||
return res;
|
||||
}
|
||||
|
@ -3278,7 +3278,7 @@ bool FSlide::BounceWall(AActor *mo)
|
|||
if (mo->flags & MF_MISSILE)
|
||||
P_ExplodeMissile(mo, line, NULL);
|
||||
else
|
||||
mo->Die(NULL, NULL);
|
||||
mo->CallDie(NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4611,6 +4611,18 @@ void P_TraceBleed(int damage, AActor *target, DAngle angle, DAngle pitch)
|
|||
P_TraceBleed(damage, target->PosPlusZ(target->Height/2), target, angle, pitch);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, TraceBleedAngle)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_INT(damage);
|
||||
PARAM_FLOAT(angle);
|
||||
PARAM_FLOAT(pitch);
|
||||
|
||||
P_TraceBleed(damage, self, angle, pitch);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
|
|
@ -1690,6 +1690,14 @@ AActor *P_RoughMonsterSearch (AActor *mo, int distance, bool onlyseekable)
|
|||
return P_BlockmapSearch (mo, distance, RoughBlockCheck, (void *)onlyseekable);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, RoughMonsterSearch)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_INT(distance);
|
||||
PARAM_BOOL_DEF(onlyseekable);
|
||||
ACTION_RETURN_OBJECT(P_RoughMonsterSearch(self, distance, onlyseekable));
|
||||
}
|
||||
|
||||
AActor *P_BlockmapSearch (AActor *mo, int distance, AActor *(*check)(AActor*, int, void *), void *params)
|
||||
{
|
||||
int blockX;
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
#include "p_acs.h"
|
||||
#include "cmdlib.h"
|
||||
#include "decallib.h"
|
||||
#include "ravenshared.h"
|
||||
#include "a_action.h"
|
||||
#include "a_keys.h"
|
||||
#include "p_conversation.h"
|
||||
|
@ -1787,7 +1786,7 @@ bool AActor::FloorBounceMissile (secplane_t &plane)
|
|||
if (flags & MF_MISSILE)
|
||||
P_ExplodeMissile(this, NULL, NULL);
|
||||
else
|
||||
Die(NULL, NULL);
|
||||
CallDie(NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
if (!(BounceFlags & BOUNCE_CanBounceWater))
|
||||
|
@ -1814,7 +1813,7 @@ bool AActor::FloorBounceMissile (secplane_t &plane)
|
|||
if (flags & MF_MISSILE)
|
||||
P_ExplodeMissile(this, NULL, NULL);
|
||||
else
|
||||
Die(NULL, NULL);
|
||||
CallDie(NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3313,6 +3312,31 @@ bool AActor::Slam (AActor *thing)
|
|||
return false; // stop moving
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, Slam)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_OBJECT(thing, AActor);
|
||||
ACTION_RETURN_BOOL(self->Slam(thing));
|
||||
}
|
||||
|
||||
bool AActor::CallSlam(AActor *thing)
|
||||
{
|
||||
IFVIRTUAL(AActor, Slam)
|
||||
{
|
||||
VMValue params[2] = { (DObject*)this, thing };
|
||||
VMReturn ret;
|
||||
VMFrameStack stack;
|
||||
int retval;
|
||||
ret.IntAt(&retval);
|
||||
stack.Call(func, params, 2, &ret, 1, nullptr);
|
||||
return !!retval;
|
||||
|
||||
}
|
||||
else return Slam(thing);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool AActor::SpecialBlastHandling (AActor *source, double strength)
|
||||
{
|
||||
return true;
|
||||
|
@ -5495,7 +5519,7 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position)
|
|||
else
|
||||
mobj->health = -mthing->health;
|
||||
if (mthing->health == 0)
|
||||
mobj->Die(NULL, NULL);
|
||||
mobj->CallDie(NULL, NULL);
|
||||
else if (mthing->health != 1)
|
||||
mobj->StartHealth = mobj->health;
|
||||
|
||||
|
@ -6902,6 +6926,14 @@ void AActor::SetIdle(bool nofunction)
|
|||
SetState(idle, nofunction);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, SetIdle)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_BOOL_DEF(nofunction);
|
||||
self->SetIdle(nofunction);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AActor::SpawnHealth() const
|
||||
{
|
||||
int defhealth = StartHealth ? StartHealth : GetDefault()->health;
|
||||
|
@ -7351,6 +7383,13 @@ DEFINE_ACTION_FUNCTION(AActor, PlayerNumber)
|
|||
ACTION_RETURN_INT(self->player ? int(self->player - players) : 0);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(AActor, SetFriendPlayer)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_POINTER(player, player_t);
|
||||
self->SetFriendPlayer(player);
|
||||
return 0;
|
||||
}
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// DropItem handling
|
||||
|
|
|
@ -1446,7 +1446,7 @@ void APlayerPawn::Die (AActor *source, AActor *inflictor, int dmgflags)
|
|||
|
||||
if (player != NULL && player->mo != this)
|
||||
{ // Make the real player die, too
|
||||
player->mo->Die (source, inflictor, dmgflags);
|
||||
player->mo->CallDie (source, inflictor, dmgflags);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -185,6 +185,16 @@ FxLocalVariableDeclaration *FCompileContext::FindLocalVariable(FName name)
|
|||
}
|
||||
}
|
||||
|
||||
static PStruct *FindStructType(FName name)
|
||||
{
|
||||
PStruct *ccls = PClass::FindClass(name);
|
||||
if (ccls == nullptr)
|
||||
{
|
||||
ccls = dyn_cast<PStruct>(TypeTable.FindType(RUNTIME_CLASS(PStruct), 0, (intptr_t)name, nullptr));
|
||||
if (ccls == nullptr) ccls = dyn_cast<PStruct>(TypeTable.FindType(RUNTIME_CLASS(PNativeStruct), 0, (intptr_t)name, nullptr));
|
||||
}
|
||||
return ccls;
|
||||
}
|
||||
//==========================================================================
|
||||
//
|
||||
// ExpEmit
|
||||
|
@ -244,7 +254,7 @@ static PSymbol *FindBuiltinFunction(FName funcname, VMNativeFunction::NativeCall
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
static bool AreCompatiblePointerTypes(PType *dest, PType *source)
|
||||
static bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare = false)
|
||||
{
|
||||
if (dest->IsKindOf(RUNTIME_CLASS(PPointer)) && source->IsKindOf(RUNTIME_CLASS(PPointer)))
|
||||
{
|
||||
|
@ -252,12 +262,13 @@ static bool AreCompatiblePointerTypes(PType *dest, PType *source)
|
|||
auto fromtype = static_cast<PPointer *>(source);
|
||||
auto totype = static_cast<PPointer *>(dest);
|
||||
if (fromtype == nullptr) return true;
|
||||
if (totype->IsConst && !fromtype->IsConst) return false;
|
||||
if (!forcompare && totype->IsConst && !fromtype->IsConst) return false;
|
||||
if (fromtype == totype) return true;
|
||||
if (fromtype->PointedType->IsKindOf(RUNTIME_CLASS(PClass)) && totype->PointedType->IsKindOf(RUNTIME_CLASS(PClass)))
|
||||
{
|
||||
auto fromcls = static_cast<PClass *>(fromtype->PointedType);
|
||||
auto tocls = static_cast<PClass *>(totype->PointedType);
|
||||
if (forcompare && tocls->IsDescendantOf(fromcls)) return true;
|
||||
return (fromcls->IsDescendantOf(tocls));
|
||||
}
|
||||
}
|
||||
|
@ -2211,6 +2222,14 @@ FxExpression *FxAssign::Resolve(FCompileContext &ctx)
|
|||
}
|
||||
// Both types are the same so this is ok.
|
||||
}
|
||||
else if (Right->ValueType->IsA(RUNTIME_CLASS(PNativeStruct)) && Base->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && static_cast<PPointer*>(Base->ValueType)->PointedType == Right->ValueType)
|
||||
{
|
||||
// allow conversion of native structs to pointers of the same type. This is necessary to assign elements from global arrays like players, sectors, etc. to local pointers.
|
||||
// For all other types this is not needed. Structs are not assignable and classes can only exist as references.
|
||||
bool writable;
|
||||
Right->RequestAddress(ctx, &writable);
|
||||
Right->ValueType = Base->ValueType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// pass it to FxTypeCast for complete handling.
|
||||
|
@ -3267,7 +3286,7 @@ FxExpression *FxCompareEq::Resolve(FCompileContext& ctx)
|
|||
else if (left->ValueType->GetRegType() == REGT_POINTER && right->ValueType->GetRegType() == REGT_POINTER)
|
||||
{
|
||||
if (left->ValueType != right->ValueType && right->ValueType != TypeNullPtr && left->ValueType != TypeNullPtr &&
|
||||
!AreCompatiblePointerTypes(left->ValueType, right->ValueType))
|
||||
!AreCompatiblePointerTypes(left->ValueType, right->ValueType, true))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
@ -5691,11 +5710,47 @@ FxMemberIdentifier::~FxMemberIdentifier()
|
|||
|
||||
FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx)
|
||||
{
|
||||
PStruct *ccls = nullptr;
|
||||
CHECKRESOLVED();
|
||||
|
||||
if (Object->ExprType == EFX_Identifier)
|
||||
{
|
||||
// If the left side is a class name for a static member function call it needs to be resolved manually
|
||||
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
|
||||
ccls = FindStructType(static_cast<FxIdentifier *>(Object)->Identifier);
|
||||
if (ccls != nullptr) static_cast<FxIdentifier *>(Object)->noglobal = true;
|
||||
}
|
||||
|
||||
SAFE_RESOLVE(Object, ctx);
|
||||
|
||||
// allow accessing the color chanels by mapping the type to a matching struct which defines them.
|
||||
// check for class or struct constants if the left side is a type name.
|
||||
if (Object->ValueType == TypeError)
|
||||
{
|
||||
if (ccls != nullptr)
|
||||
{
|
||||
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
|
||||
{
|
||||
PSymbol *sym;
|
||||
if ((sym = ccls->Symbols.FindSymbol(Identifier, true)) != nullptr)
|
||||
{
|
||||
if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConst)))
|
||||
{
|
||||
ScriptPosition.Message(MSG_DEBUGLOG, "Resolving name '%s.%s' as constant\n", ccls->TypeName.GetChars(), Identifier.GetChars());
|
||||
delete this;
|
||||
return FxConstant::MakeConstant(sym, ScriptPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "Unable to access '%s.%s' in a static context\n", ccls->TypeName.GetChars(), Identifier.GetChars());
|
||||
delete this;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allow accessing the color channels by mapping the type to a matching struct which defines them.
|
||||
if (Object->ValueType == TypeColor)
|
||||
Object->ValueType = TypeColorStruct;
|
||||
|
||||
|
@ -6635,12 +6690,12 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
|
|||
|
||||
if (!start.Konst)
|
||||
{
|
||||
ExpEmit indexwork = indexv.Fixed ? ExpEmit(build, indexv.RegType) : indexv;
|
||||
int shiftbits = 0;
|
||||
while (1u << shiftbits < arraytype->ElementSize)
|
||||
{
|
||||
shiftbits++;
|
||||
}
|
||||
ExpEmit indexwork = indexv.Fixed && arraytype->ElementSize > 1 ? ExpEmit(build, indexv.RegType) : indexv;
|
||||
if (1u << shiftbits == arraytype->ElementSize)
|
||||
{
|
||||
if (shiftbits > 0)
|
||||
|
@ -7079,7 +7134,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
|
|||
bool staticonly = false;
|
||||
bool novirtual = false;
|
||||
|
||||
PClass *ccls = nullptr;
|
||||
PStruct *ccls = nullptr;
|
||||
|
||||
for (auto a : ArgList)
|
||||
{
|
||||
|
@ -7093,10 +7148,10 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
|
|||
|
||||
if (Self->ExprType == EFX_Identifier)
|
||||
{
|
||||
ccls = PClass::FindClass(static_cast<FxIdentifier *>(Self)->Identifier);
|
||||
// If the left side is a class name for a static member function call it needs to be resolved manually
|
||||
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
|
||||
if (ccls != nullptr)static_cast<FxIdentifier *>(Self)->noglobal = true;
|
||||
ccls = FindStructType(static_cast<FxIdentifier *>(Self)->Identifier);
|
||||
if (ccls != nullptr) static_cast<FxIdentifier *>(Self)->noglobal = true;
|
||||
}
|
||||
|
||||
SAFE_RESOLVE(Self, ctx);
|
||||
|
@ -7105,17 +7160,13 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
|
|||
{
|
||||
if (ccls != nullptr)
|
||||
{
|
||||
if (ccls->bExported)
|
||||
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
|
||||
{
|
||||
cls = ccls;
|
||||
staticonly = true;
|
||||
goto isresolved;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo: static struct members need to work as well.
|
||||
}
|
||||
}
|
||||
|
||||
if (Self->ExprType == EFX_Super)
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "d_player.h"
|
||||
#include "p_effect.h"
|
||||
#include "autosegs.h"
|
||||
#include "gi.h"
|
||||
|
||||
static TArray<FPropertyInfo*> properties;
|
||||
static TArray<AFuncDesc> AFTable;
|
||||
|
@ -721,7 +722,11 @@ void InitThingdef()
|
|||
pstruct->Size = sizeof(player_t);
|
||||
pstruct->Align = alignof(player_t);
|
||||
PArray *parray = NewArray(pstruct, MAXPLAYERS);
|
||||
PField *playerf = new PField("players", pstruct, VARF_Native | VARF_Static, (intptr_t)&players);
|
||||
PField *playerf = new PField("players", parray, VARF_Native | VARF_Static, (intptr_t)&players);
|
||||
GlobalSymbols.AddSymbol(playerf);
|
||||
|
||||
parray = NewArray(TypeBool, MAXPLAYERS);
|
||||
playerf = new PField("playeringame", parray, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&playeringame);
|
||||
GlobalSymbols.AddSymbol(playerf);
|
||||
|
||||
|
||||
|
@ -803,3 +808,9 @@ void InitThingdef()
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(DObject, GameType)
|
||||
{
|
||||
PARAM_PROLOGUE;
|
||||
ACTION_RETURN_INT(gameinfo.gametype);
|
||||
}
|
||||
|
|
|
@ -501,6 +501,15 @@ int FWadCollection::CheckNumForName (const char *name, int space, int wadnum, bo
|
|||
return i != NULL_INDEX ? i : -1;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(_Wads, CheckNumForName)
|
||||
{
|
||||
PARAM_PROLOGUE;
|
||||
PARAM_STRING(name);
|
||||
PARAM_INT(ns);
|
||||
PARAM_INT_DEF(wadnum);
|
||||
PARAM_BOOL_DEF(exact);
|
||||
ACTION_RETURN_INT(Wads.CheckNumForName(name, ns, wadnum, exact));
|
||||
}
|
||||
//==========================================================================
|
||||
//
|
||||
// W_GetNumForName
|
||||
|
|
|
@ -262,6 +262,8 @@ class Actor : Thinker native
|
|||
virtual native void Activate(Actor activator);
|
||||
virtual native void Deactivate(Actor activator);
|
||||
virtual native int DoSpecialDamage (Actor target, int damage, Name damagetype);
|
||||
virtual native void Die(Actor source, Actor inflictor, int dmgflags);
|
||||
virtual native bool Slam(Actor victim);
|
||||
virtual native bool UseInventory(Inventory item);
|
||||
|
||||
|
||||
|
@ -276,6 +278,7 @@ class Actor : Thinker native
|
|||
native bool HitFloor();
|
||||
native bool isTeammate(Actor other);
|
||||
native int PlayerNumber();
|
||||
native void SetFriendPlayer(PlayerInfo player);
|
||||
|
||||
native void RestoreDamage();
|
||||
native int SpawnHealth();
|
||||
|
@ -296,6 +299,7 @@ class Actor : Thinker native
|
|||
native Actor SpawnMissileAngleZSpeed (double z, class<Actor> type, double angle, double vz, double speed, Actor owner = null, bool checkspawn = true);
|
||||
native Actor, Actor SpawnPlayerMissile(class<Actor> type, double angle = 0, double x = 0, double y = 0, double z = 0, out FTranslatedLineTarget pLineTarget = null, bool nofreeaim = false, bool noautoaim = false, int aimflags = 0);
|
||||
native void SpawnTeleportFog(Vector3 pos, bool beforeTele, bool setTarget);
|
||||
native Actor RoughMonsterSearch(int distance, bool onlyseekable = false);
|
||||
|
||||
void A_Light(int extralight) { if (player) player.extralight = clamp(extralight, -20, 20); }
|
||||
void A_Light0() { if (player) player.extralight = 0; }
|
||||
|
@ -308,6 +312,9 @@ class Actor : Thinker native
|
|||
|
||||
native bool Teleport(Vector3 pos, double angle, int flags);
|
||||
native void TraceBleed(int damage, Actor missile);
|
||||
native void TraceBleedAngle(int damage, double angle, double pitch);
|
||||
|
||||
native void SetIdle(bool nofunction = false);
|
||||
native bool CheckMeleeRange();
|
||||
native int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags = 0, double angle = 0);
|
||||
native double AimLineAttack(double angle, double distance, out FTranslatedLineTarget pLineTarget = null, double vrange = 0., int flags = 0, Actor target = null, Actor friender = null);
|
||||
|
|
|
@ -7,6 +7,7 @@ class Object native
|
|||
native static double G_SkillPropertyFloat(int p);
|
||||
native static vector3, int G_PickDeathmatchStart();
|
||||
native static vector3, int G_PickPlayerStart(int pnum, int flags = 0);
|
||||
native static int GameType();
|
||||
|
||||
/*virtual*/ native void Destroy();
|
||||
}
|
||||
|
@ -133,3 +134,33 @@ struct Sector native
|
|||
native double, Sector, F3DFloor NextHighestCeilingAt(double x, double y, double bottomz, double topz, int flags = 0);
|
||||
}
|
||||
|
||||
struct Wads
|
||||
{
|
||||
enum WadNamespace
|
||||
{
|
||||
ns_hidden = -1,
|
||||
|
||||
ns_global = 0,
|
||||
ns_sprites,
|
||||
ns_flats,
|
||||
ns_colormaps,
|
||||
ns_acslibrary,
|
||||
ns_newtextures,
|
||||
ns_bloodraw,
|
||||
ns_bloodsfx,
|
||||
ns_bloodmisc,
|
||||
ns_strifevoices,
|
||||
ns_hires,
|
||||
ns_voxels,
|
||||
|
||||
ns_specialzipdirectory,
|
||||
ns_sounds,
|
||||
ns_patches,
|
||||
ns_graphics,
|
||||
ns_music,
|
||||
|
||||
ns_firstskin,
|
||||
}
|
||||
|
||||
native static int CheckNumForName(string name, int ns, int wadnum = -1, bool exact = false);
|
||||
}
|
|
@ -993,3 +993,17 @@ enum ETeleport
|
|||
TELF_ROTATEBOOM = 32,
|
||||
TELF_ROTATEBOOMINVERSE = 64,
|
||||
};
|
||||
|
||||
enum EGameType
|
||||
{
|
||||
GAME_Any = 0,
|
||||
GAME_Doom = 1,
|
||||
GAME_Heretic = 2,
|
||||
GAME_Hexen = 4,
|
||||
GAME_Strife = 8,
|
||||
GAME_Chex = 16, //Chex is basically Doom, but we need to have a different set of actors.
|
||||
|
||||
GAME_Raven = GAME_Heretic|GAME_Hexen,
|
||||
GAME_DoomChex = GAME_Doom|GAME_Chex,
|
||||
GAME_DoomStrifeChex = GAME_Doom|GAME_Strife|GAME_Chex
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
// Dark Servant Artifact ----------------------------------------------------
|
||||
|
||||
class ArtiDarkServant : Inventory native
|
||||
class ArtiDarkServant : Inventory
|
||||
{
|
||||
Default
|
||||
{
|
||||
|
@ -23,6 +23,26 @@ class ArtiDarkServant : Inventory native
|
|||
SUMN A 350;
|
||||
Loop;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
//
|
||||
// Activate the summoning artifact
|
||||
//
|
||||
//============================================================================
|
||||
|
||||
override bool Use (bool pickup)
|
||||
{
|
||||
Actor mo = Owner.SpawnPlayerMissile ("SummoningDoll");
|
||||
if (mo)
|
||||
{
|
||||
mo.target = Owner;
|
||||
mo.tracer = Owner;
|
||||
mo.Vel.Z = 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Summoning Doll -----------------------------------------------------------
|
||||
|
@ -36,8 +56,6 @@ class SummoningDoll : Actor
|
|||
+NOTELEPORT
|
||||
}
|
||||
|
||||
native void A_Summon();
|
||||
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
|
@ -48,6 +66,49 @@ class SummoningDoll : Actor
|
|||
SUMN A 4 A_Summon;
|
||||
Stop;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
//
|
||||
// A_Summon
|
||||
//
|
||||
//============================================================================
|
||||
|
||||
void A_Summon()
|
||||
{
|
||||
Actor mo = Spawn("MinotaurFriend", pos, ALLOW_REPLACE);
|
||||
if (mo)
|
||||
{
|
||||
if (mo.TestMobjLocation() == false || !tracer)
|
||||
{ // Didn't fit - change back to artifact
|
||||
mo.Destroy();
|
||||
Actor arti = Spawn("ArtiDarkServant", Pos, ALLOW_REPLACE);
|
||||
if (arti) arti.bDropped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Careful! The Minotaur might have been replaced
|
||||
// so only set the time if we got a genuine one.
|
||||
MinotaurFriend m = MinotaurFriend(mo);
|
||||
if (m) m.StartTime = level.maptime;
|
||||
|
||||
if (tracer.bCorpse)
|
||||
{ // Master dead
|
||||
mo.tracer = null; // No master
|
||||
}
|
||||
else
|
||||
{
|
||||
mo.tracer = tracer; // Pointer to master
|
||||
Inventory power = Inventory(Spawn("PowerMinotaur"));
|
||||
power.CallTryPickup(tracer);
|
||||
mo.SetFriendPlayer(tracer.player);
|
||||
}
|
||||
|
||||
// Make smoke puff
|
||||
Spawn("MinotaurSmoke", Pos, ALLOW_REPLACE);
|
||||
A_PlaySound(mo.ActiveSound, CHAN_VOICE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Minotaur Smoke -----------------------------------------------------------
|
||||
|
@ -69,3 +130,4 @@ class MinotaurSmoke : Actor
|
|||
Stop;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
class Minotaur : Actor native
|
||||
class Minotaur : Actor
|
||||
{
|
||||
const MAULATORTICS = 25 * TICRATE;
|
||||
const MNTR_CHARGE_SPEED =13.;
|
||||
const MINOTAUR_LOOK_DIST = 16*54.;
|
||||
|
||||
Default
|
||||
{
|
||||
Health 3000;
|
||||
|
@ -26,16 +30,6 @@ class Minotaur : Actor native
|
|||
DropItem "PhoenixRodAmmo", 84, 10;
|
||||
}
|
||||
|
||||
native void A_MinotaurDecide();
|
||||
native void A_MinotaurAtk1();
|
||||
native void A_MinotaurAtk2();
|
||||
native void A_MinotaurAtk3();
|
||||
native void A_MinotaurCharge();
|
||||
native void A_MinotaurLook();
|
||||
native void A_MinotaurRoam();
|
||||
native void A_MinotaurChase();
|
||||
native void A_MinotaurDeath();
|
||||
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
|
@ -101,10 +95,478 @@ class Minotaur : Actor native
|
|||
MNTR E 10 A_BossDeath;
|
||||
Stop;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// FUNC P_MinotaurSlam
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void MinotaurSlam (Actor target)
|
||||
{
|
||||
double ang = AngleTo(target);
|
||||
double thrust = 16 + random[MinotaurSlam]() / 64.;
|
||||
target.VelFromAngle(ang, thrust);
|
||||
int damage = random[MinotaurSlam](1, 8) * (bSummonedMonster? 4 : 6);
|
||||
int newdam = target.DamageMobj (null, null, damage, 'Melee');
|
||||
target.TraceBleedAngle (newdam > 0 ? newdam : damage, ang, 0.);
|
||||
if (target.player)
|
||||
{
|
||||
target.reactiontime = random[MinotaurSlam](14, 21);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
override void Tick ()
|
||||
{
|
||||
Super.Tick ();
|
||||
|
||||
// The unfriendly Minotaur (Heretic's) is invulnerable while charging
|
||||
if (!bSummonedMonster)
|
||||
{
|
||||
bInvulnerable = bSkullFly;
|
||||
}
|
||||
}
|
||||
|
||||
override bool Slam (Actor thing)
|
||||
{
|
||||
// Slamming minotaurs shouldn't move non-creatures
|
||||
if (!thing.bIsMonster && !thing.player)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Super.Slam (thing);
|
||||
}
|
||||
|
||||
override int DoSpecialDamage (Actor target, int damage, Name damagetype)
|
||||
{
|
||||
damage = Super.DoSpecialDamage (target, damage, damagetype);
|
||||
if (damage != -1 && bSkullFly)
|
||||
{ // Slam only when in charge mode
|
||||
MinotaurSlam (target);
|
||||
return -1;
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk1
|
||||
//
|
||||
// Melee attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurAtk1()
|
||||
{
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
A_PlaySound ("minotaur/melee", CHAN_WEAPON);
|
||||
if (CheckMeleeRange())
|
||||
{
|
||||
int damage = random[MinotaurAtk1](1, 8) * 4;
|
||||
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
||||
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
||||
PlayerInfo player = target.player;
|
||||
if (player != null && player.mo == target)
|
||||
{ // Squish the player
|
||||
player.deltaviewheight = -16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurDecide
|
||||
//
|
||||
// Choose a missile attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurDecide()
|
||||
{
|
||||
bool friendly = bSummonedMonster;
|
||||
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!friendly)
|
||||
{
|
||||
A_PlaySound ("minotaur/sight", CHAN_WEAPON);
|
||||
}
|
||||
double dist = Distance2D(target);
|
||||
if (target.pos.z + target.height > pos.z
|
||||
&& target.pos.z + target.height < pos.z + height
|
||||
&& dist < (friendly ? 16*64. : 8*64.)
|
||||
&& dist > 1*64.
|
||||
&& random[MinotaurDecide]() < 150)
|
||||
{ // Charge attack
|
||||
// Don't call the state function right away
|
||||
SetStateLabel("Charge", true);
|
||||
bSkullFly = true;
|
||||
if (!friendly)
|
||||
{ // Heretic's Minotaur is invulnerable during charge attack
|
||||
bInvulnerable = true;
|
||||
}
|
||||
A_FaceTarget ();
|
||||
VelFromAngle(MNTR_CHARGE_SPEED);
|
||||
special1 = TICRATE/2; // Charge duration
|
||||
}
|
||||
else if (target.pos.z == target.floorz
|
||||
&& dist < 9*64.
|
||||
&& random[MinotaurDecide]() < (friendly ? 100 : 220))
|
||||
{ // Floor fire attack
|
||||
SetStateLabel("Hammer");
|
||||
special2 = 0;
|
||||
}
|
||||
else
|
||||
{ // Swing attack
|
||||
A_FaceTarget ();
|
||||
// Don't need to call P_SetMobjState because the current state
|
||||
// falls through to the swing attack
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurCharge
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurCharge()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (special1 > 0)
|
||||
{
|
||||
Class<Actor> type;
|
||||
|
||||
//if (gameinfo.gametype == GAME_Heretic)
|
||||
if (gametype() == GAME_Heretic)
|
||||
{
|
||||
type = "PhoenixPuff";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "PunchPuff";
|
||||
}
|
||||
Actor puff = Spawn (type, Pos, ALLOW_REPLACE);
|
||||
puff.Vel.Z = 2;
|
||||
special1--;
|
||||
}
|
||||
else
|
||||
{
|
||||
bSkullFly = false;
|
||||
bInvulnerable = false;
|
||||
SetState (SeeState);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk2
|
||||
//
|
||||
// Swing attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurAtk2()
|
||||
{
|
||||
bool friendly = bSummonedMonster;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
A_PlaySound ("minotaur/attack2", CHAN_WEAPON);
|
||||
if (CheckMeleeRange())
|
||||
{
|
||||
int damage = random[MinotaurAtk2](1, 8) * (friendly ? 3 : 5);
|
||||
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
||||
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
||||
return;
|
||||
}
|
||||
double z = pos.z + 40;
|
||||
Class<Actor> fx = "MinotaurFX1";
|
||||
Actor mo = SpawnMissileZ (z, target, fx);
|
||||
if (mo != null)
|
||||
{
|
||||
// S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
||||
double vz = mo.Vel.Z;
|
||||
double ang = mo.angle;
|
||||
SpawnMissileAngleZ (z, fx, ang-(45./8), vz);
|
||||
SpawnMissileAngleZ (z, fx, ang+(45./8), vz);
|
||||
SpawnMissileAngleZ (z, fx, ang-(45./16), vz);
|
||||
SpawnMissileAngleZ (z, fx, ang+(45./16), vz);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurAtk3
|
||||
//
|
||||
// Floor fire attack.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurAtk3()
|
||||
{
|
||||
bool friendly = bSummonedMonster;
|
||||
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
A_PlaySound ("minotaur/attack3", CHAN_VOICE);
|
||||
if (CheckMeleeRange())
|
||||
{
|
||||
int damage = random[MinotaurAtk3](1, 8) * (friendly ? 3 : 5);
|
||||
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
||||
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
||||
PlayerInfo player = target.player;
|
||||
if (player != null && player.mo == target)
|
||||
{ // Squish the player
|
||||
player.deltaviewheight = -16;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Floorclip > 0 && compat_minotaur)
|
||||
{
|
||||
// only play the sound.
|
||||
A_PlaySound ("minotaur/fx2hit", CHAN_WEAPON);
|
||||
}
|
||||
else
|
||||
{
|
||||
Actor mo = SpawnMissile (target, "MinotaurFX2");
|
||||
if (mo != null)
|
||||
{
|
||||
mo.A_PlaySound ("minotaur/attack1", CHAN_WEAPON);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (random[MinotaurAtk3]() < 192 && special2 == 0)
|
||||
{
|
||||
SetStateLabel ("HammerLoop");
|
||||
special2 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurDeath
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurDeath()
|
||||
{
|
||||
if (Wads.CheckNumForName ("MNTRF1", Wads.ns_sprites) < 0 &&
|
||||
Wads.CheckNumForName ("MNTRF0", Wads.ns_sprites) < 0)
|
||||
SetStateLabel("FadeOut");
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// A_MinotaurRoam
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurRoam()
|
||||
{
|
||||
// In case pain caused him to skip his fade in.
|
||||
A_SetRenderStyle(1, STYLE_Normal);
|
||||
|
||||
MinotaurFriend mf = MinotaurFriend(self);
|
||||
if (mf)
|
||||
{
|
||||
if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
|
||||
{
|
||||
DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (random[MinotaurRoam]() < 30)
|
||||
A_MinotaurLook(); // adjust to closest target
|
||||
|
||||
if (random[MinotaurRoam]() < 6)
|
||||
{
|
||||
//Choose new direction
|
||||
movedir = random[MinotaurRoam]() % 8;
|
||||
FaceMovementDirection ();
|
||||
}
|
||||
if (!MonsterMove())
|
||||
{
|
||||
// Turn
|
||||
if (random[MinotaurRoam]() & 1)
|
||||
movedir = (movedir + 1) % 8;
|
||||
else
|
||||
movedir = (movedir + 7) % 8;
|
||||
FaceMovementDirection ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurLook
|
||||
//
|
||||
// Look for enemy of player
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurLook()
|
||||
{
|
||||
if (!(self is "MinotaurFriend"))
|
||||
{
|
||||
A_Look();
|
||||
return;
|
||||
}
|
||||
|
||||
Actor mo = null;
|
||||
PlayerInfo player;
|
||||
double dist;
|
||||
Actor master = tracer;
|
||||
|
||||
target = null;
|
||||
if (deathmatch) // Quick search for players
|
||||
{
|
||||
for (int 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 = Distance2D(mo);
|
||||
if (dist > MINOTAUR_LOOK_DIST) continue;
|
||||
target = mo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) // Near player monster search
|
||||
{
|
||||
if (master && (master.health > 0) && (master.player))
|
||||
mo = master.RoughMonsterSearch(20);
|
||||
else
|
||||
mo = RoughMonsterSearch(20);
|
||||
target = mo;
|
||||
}
|
||||
|
||||
if (!target) // Normal monster search
|
||||
{
|
||||
ThinkerIterator it = ThinkerIterator.Create("Actor");
|
||||
|
||||
while ((mo = Actor(it.Next())) != null)
|
||||
{
|
||||
if (!mo.bIsMonster) continue;
|
||||
if (mo.health <= 0) continue;
|
||||
if (!mo.bShootable) continue;
|
||||
dist = Distance2D(mo);
|
||||
if (dist > MINOTAUR_LOOK_DIST) continue;
|
||||
if (mo == master || mo == self) continue;
|
||||
if (mo.bSummonedMonster && mo.tracer == master) continue;
|
||||
target = mo;
|
||||
break; // Found actor to attack
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
{
|
||||
SetState (SeeState, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStateLabel ("Roam", true);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MinotaurChase
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MinotaurChase()
|
||||
{
|
||||
MinotaurFriend mf = MinotaurFriend(self);
|
||||
if (!mf)
|
||||
{
|
||||
A_Chase();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// In case pain caused him to skip his fade in.
|
||||
A_SetRenderStyle(1, STYLE_Normal);
|
||||
|
||||
if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
|
||||
{
|
||||
DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
|
||||
return;
|
||||
}
|
||||
|
||||
if (random[MinotaurChase]() < 30)
|
||||
A_MinotaurLook(); // adjust to closest target
|
||||
|
||||
if (!target || (target.health <= 0) || !target.bShootable)
|
||||
{ // look for a new target
|
||||
SetIdle();
|
||||
return;
|
||||
}
|
||||
|
||||
FaceMovementDirection ();
|
||||
reactiontime = 0;
|
||||
|
||||
// Melee attack
|
||||
if (MeleeState && CheckMeleeRange ())
|
||||
{
|
||||
if (AttackSound)
|
||||
{
|
||||
A_PlaySound (AttackSound, CHAN_WEAPON);
|
||||
}
|
||||
SetState (MeleeState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Missile attack
|
||||
if (MissileState && CheckMissileRange())
|
||||
{
|
||||
SetState (MissileState);
|
||||
return;
|
||||
}
|
||||
|
||||
// chase towards target
|
||||
if (!MonsterMove ())
|
||||
{
|
||||
NewChaseDir ();
|
||||
FaceMovementDirection ();
|
||||
}
|
||||
|
||||
// Active sound
|
||||
if (random[MinotaurChase]() < 6)
|
||||
{
|
||||
PlayActiveSound ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MinotaurFriend : Minotaur native
|
||||
class MinotaurFriend : Minotaur
|
||||
{
|
||||
int StartTime;
|
||||
|
||||
Default
|
||||
{
|
||||
Health 2500;
|
||||
|
@ -132,6 +594,51 @@ class MinotaurFriend : Minotaur native
|
|||
Death:
|
||||
Goto FadeOut;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
override void BeginPlay ()
|
||||
{
|
||||
Super.BeginPlay ();
|
||||
StartTime = -1;
|
||||
}
|
||||
|
||||
override void Die (Actor source, Actor inflictor, int dmgflags)
|
||||
{
|
||||
Super.Die (source, inflictor, dmgflags);
|
||||
|
||||
if (tracer && tracer.health > 0 && tracer.player)
|
||||
{
|
||||
// Search thinker list for minotaur
|
||||
ThinkerIterator it = ThinkerIterator.Create("MinotaurFriend");
|
||||
MinotaurFriend mo;
|
||||
|
||||
while ((mo = MinotaurFriend(it.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.bCorpse) continue;
|
||||
if (mo.StartTime >= 0 && (level.maptime - StartTime) >= MAULATORTICS) continue;
|
||||
if (mo.tracer != null && mo.tracer.player == tracer.player) break;
|
||||
}
|
||||
|
||||
if (mo == null)
|
||||
{
|
||||
Inventory power = tracer.FindInventory("PowerMinotaur");
|
||||
if (power != null)
|
||||
{
|
||||
power.Destroy ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Minotaur FX 1 ------------------------------------------------------------
|
||||
|
@ -179,8 +686,6 @@ class MinotaurFX2 : MinotaurFX1
|
|||
DeathSound "minotaur/fx2hit";
|
||||
}
|
||||
|
||||
native void A_MntrFloorFire();
|
||||
|
||||
states
|
||||
{
|
||||
Spawn:
|
||||
|
@ -191,6 +696,24 @@ class MinotaurFX2 : MinotaurFX1
|
|||
FX13 JKLM 4 Bright;
|
||||
Stop;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// PROC A_MntrFloorFire
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void A_MntrFloorFire()
|
||||
{
|
||||
SetZ(floorz);
|
||||
double x = Random2[MntrFloorFire]() / 64.;
|
||||
double y = Random2[MntrFloorFire]() / 64.;
|
||||
|
||||
Actor mo = Spawn("MinotaurFX3", Vec2OffsetZ(x, y, floorz), ALLOW_REPLACE);
|
||||
mo.target = target;
|
||||
mo.Vel.X = MinVel; // Force block checking
|
||||
mo.CheckMissileSpawn (radius);
|
||||
}
|
||||
}
|
||||
|
||||
// Minotaur FX 3 ------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue