mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-07 21:41:42 +00:00
31c749058b
so now it is available in all games. - Replaced the call to A_FlameSnd in the HereticPlayer's burn death sequence with A_FireScream and defined *burndeath for Heretic. - Added Grubber's custom player class support. SVN r250 (trunk)
584 lines
14 KiB
C++
584 lines
14 KiB
C++
#include "actor.h"
|
|
#include "thingdef.h"
|
|
#include "p_conversation.h"
|
|
#include "p_lnspec.h"
|
|
#include "a_action.h"
|
|
#include "m_random.h"
|
|
#include "s_sound.h"
|
|
#include "d_player.h"
|
|
#include "p_local.h"
|
|
#include "p_terrain.h"
|
|
#include "p_enemy.h"
|
|
#include "statnums.h"
|
|
#include "templates.h"
|
|
|
|
static FRandom pr_freezedeath ("FreezeDeath");
|
|
static FRandom pr_icesettics ("IceSetTics");
|
|
static FRandom pr_freeze ("FreezeDeathChunks");
|
|
|
|
/***************************** IceChunk ************************************/
|
|
|
|
class AIceChunk : public AActor
|
|
{
|
|
DECLARE_ACTOR (AIceChunk, AActor)
|
|
};
|
|
|
|
FState AIceChunk::States[] =
|
|
{
|
|
S_NORMAL (ICEC, 'A', 10, NULL , &States[1]),
|
|
S_NORMAL (ICEC, 'B', 10, A_IceSetTics , &States[2]),
|
|
S_NORMAL (ICEC, 'C', 10, A_IceSetTics , &States[3]),
|
|
S_NORMAL (ICEC, 'D', 10, A_IceSetTics , NULL),
|
|
};
|
|
|
|
IMPLEMENT_ACTOR (AIceChunk, Any, -1, 0)
|
|
PROP_RadiusFixed (3)
|
|
PROP_HeightFixed (4)
|
|
PROP_Mass(5)
|
|
PROP_Flags (MF_DROPOFF)
|
|
PROP_Flags2 (MF2_LOGRAV|MF2_CANNOTPUSH|MF2_FLOORCLIP|MF2_NOTELEPORT)
|
|
|
|
PROP_SpawnState (0)
|
|
END_DEFAULTS
|
|
|
|
/***************************************************************************/
|
|
|
|
// A chunk of ice that is also a player -------------------------------------
|
|
|
|
class AIceChunkHead : public APlayerChunk
|
|
{
|
|
DECLARE_ACTOR (AIceChunkHead, APlayerChunk)
|
|
};
|
|
|
|
FState AIceChunkHead::States[] =
|
|
{
|
|
S_NORMAL (PLAY, 'A', 0, NULL , &States[1]),
|
|
S_NORMAL (ICEC, 'A', 10, A_IceCheckHeadDone , &States[1])
|
|
};
|
|
|
|
IMPLEMENT_ACTOR (AIceChunkHead, Any, -1, 0)
|
|
PROP_RadiusFixed (3)
|
|
PROP_HeightFixed (4)
|
|
PROP_Mass(5)
|
|
PROP_DamageType (MOD_ICE)
|
|
PROP_Flags (MF_DROPOFF)
|
|
PROP_Flags2 (MF2_LOGRAV|MF2_CANNOTPUSH)
|
|
|
|
PROP_SpawnState (0)
|
|
END_DEFAULTS
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_NoBlocking
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_NoBlocking (AActor *actor)
|
|
{
|
|
// [RH] Andy Baker's stealth monsters
|
|
if (actor->flags & MF_STEALTH)
|
|
{
|
|
actor->alpha = OPAQUE;
|
|
actor->visdir = 0;
|
|
}
|
|
|
|
actor->flags &= ~MF_SOLID;
|
|
|
|
// If the actor has a conversation that sets an item to drop, drop that.
|
|
if (actor->Conversation != NULL && actor->Conversation->DropType != NULL)
|
|
{
|
|
P_DropItem (actor, actor->Conversation->DropType, -1, 256);
|
|
actor->Conversation = NULL;
|
|
return;
|
|
}
|
|
|
|
actor->Conversation = NULL;
|
|
|
|
// If the actor has attached metadata for items to drop, drop those.
|
|
// Otherwise, call NoBlockingSet() and let it decide what to drop.
|
|
if (!actor->IsKindOf (RUNTIME_CLASS (APlayerPawn))) // [GRB]
|
|
{
|
|
FDropItem *di = GetDropItems(actor);
|
|
|
|
while (di != NULL)
|
|
{
|
|
if (di->Name != NAME_None)
|
|
{
|
|
const PClass *ti = PClass::FindClass(di->Name);
|
|
if (ti) P_DropItem (actor, ti, di->amount, di->probability);
|
|
}
|
|
di = di->Next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actor->NoBlockingSet ();
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_SetFloorClip
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_SetFloorClip (AActor *actor)
|
|
{
|
|
actor->flags2 |= MF2_FLOORCLIP;
|
|
actor->AdjustFloorClip ();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnSetFloorClip
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_UnSetFloorClip (AActor *actor)
|
|
{
|
|
actor->flags2 &= ~MF2_FLOORCLIP;
|
|
actor->floorclip = 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_HideThing
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_HideThing (AActor *actor)
|
|
{
|
|
actor->renderflags |= RF_INVISIBLE;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnHideThing
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_UnHideThing (AActor *actor)
|
|
{
|
|
actor->renderflags &= ~RF_INVISIBLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_FreezeDeath
|
|
//
|
|
//============================================================================
|
|
|
|
void A_FreezeDeath (AActor *actor)
|
|
{
|
|
int t = pr_freezedeath();
|
|
actor->tics = 75+t+pr_freezedeath();
|
|
actor->flags |= MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD|MF_ICECORPSE;
|
|
actor->flags2 |= MF2_PUSHABLE|MF2_TELESTOMP|MF2_PASSMOBJ|MF2_SLIDE;
|
|
actor->flags3 |= MF3_CRASHED;
|
|
actor->height = actor->GetDefault()->height;
|
|
S_Sound (actor, CHAN_BODY, "misc/freeze", 1, ATTN_NORM);
|
|
|
|
if (actor->player)
|
|
{
|
|
actor->player->damagecount = 0;
|
|
actor->player->poisoncount = 0;
|
|
actor->player->bonuscount = 0;
|
|
}
|
|
else if (actor->flags3&MF3_ISMONSTER && actor->special)
|
|
{ // Initiate monster death actions
|
|
LineSpecials [actor->special] (NULL, actor, false, actor->args[0],
|
|
actor->args[1], actor->args[2], actor->args[3], actor->args[4]);
|
|
actor->special = 0;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_IceSetTics
|
|
//
|
|
//============================================================================
|
|
|
|
void A_IceSetTics (AActor *actor)
|
|
{
|
|
int floor;
|
|
|
|
actor->tics = 70+(pr_icesettics()&63);
|
|
floor = P_GetThingFloorType (actor);
|
|
if (Terrains[floor].DamageMOD == MOD_FIRE)
|
|
{
|
|
actor->tics >>= 2;
|
|
}
|
|
else if (Terrains[floor].DamageMOD == MOD_ICE)
|
|
{
|
|
actor->tics <<= 1;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_IceCheckHeadDone
|
|
//
|
|
//============================================================================
|
|
|
|
void A_IceCheckHeadDone (AActor *actor)
|
|
{
|
|
if (actor->player == NULL)
|
|
{
|
|
actor->Destroy ();
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_FreezeDeathChunks
|
|
//
|
|
//============================================================================
|
|
|
|
void A_FreezeDeathChunks (AActor *actor)
|
|
{
|
|
|
|
int i;
|
|
int numChunks;
|
|
AActor *mo;
|
|
|
|
if (actor->momx || actor->momy || actor->momz)
|
|
{
|
|
actor->tics = 3*TICRATE;
|
|
return;
|
|
}
|
|
S_Sound (actor, CHAN_BODY, "misc/icebreak", 1, ATTN_NORM);
|
|
|
|
// [RH] In Hexen, this creates a random number of shards (range [24,56])
|
|
// with no relation to the size of the actor shattering. I think it should
|
|
// base the number of shards on the size of the dead thing, so bigger
|
|
// things break up into more shards than smaller things.
|
|
// An actor with radius 20 and height 64 creates ~40 chunks.
|
|
numChunks = MAX<int> (4, (actor->radius>>FRACBITS)*(actor->height>>FRACBITS)/32);
|
|
i = (pr_freeze.Random2()) % (numChunks/4);
|
|
for (i = MAX (24, numChunks + i); i >= 0; i--)
|
|
{
|
|
mo = Spawn<AIceChunk> (
|
|
actor->x + (((pr_freeze()-128)*actor->radius)>>7),
|
|
actor->y + (((pr_freeze()-128)*actor->radius)>>7),
|
|
actor->z + (pr_freeze()*actor->height/255));
|
|
mo->SetState (mo->SpawnState + (pr_freeze()%3));
|
|
if (mo)
|
|
{
|
|
mo->momz = FixedDiv(mo->z-actor->z, actor->height)<<2;
|
|
mo->momx = pr_freeze.Random2 () << (FRACBITS-7);
|
|
mo->momy = pr_freeze.Random2 () << (FRACBITS-7);
|
|
A_IceSetTics (mo); // set a random tic wait
|
|
mo->RenderStyle = actor->RenderStyle;
|
|
mo->alpha = actor->alpha;
|
|
}
|
|
}
|
|
if (actor->player)
|
|
{ // attach the player's view to a chunk of ice
|
|
AIceChunkHead *head = Spawn<AIceChunkHead> (actor->x, actor->y, actor->z + actor->player->mo->ViewHeight);
|
|
head->momz = FixedDiv(head->z-actor->z, actor->height)<<2;
|
|
head->momx = pr_freeze.Random2 () << (FRACBITS-7);
|
|
head->momy = pr_freeze.Random2 () << (FRACBITS-7);
|
|
head->player = actor->player;
|
|
actor->player = NULL;
|
|
head->health = actor->health;
|
|
head->angle = actor->angle;
|
|
head->player->mo = head;
|
|
head->pitch = 0;
|
|
head->RenderStyle = actor->RenderStyle;
|
|
head->alpha = actor->alpha;
|
|
if (head->player->camera == actor)
|
|
{
|
|
head->player->camera = head;
|
|
}
|
|
}
|
|
|
|
// [RH] Do some stuff to make this more useful outside Hexen
|
|
if (actor->flags4 & MF4_BOSSDEATH)
|
|
{
|
|
A_BossDeath (actor);
|
|
}
|
|
A_NoBlocking (actor);
|
|
|
|
actor->Destroy ();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CorpseQueue Routines (used by Hexen)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Corpse queue for monsters - this should be saved out
|
|
#define CORPSEQUEUESIZE 64
|
|
|
|
class DCorpsePointer : public DThinker
|
|
{
|
|
DECLARE_CLASS (DCorpsePointer, DThinker)
|
|
HAS_OBJECT_POINTERS
|
|
public:
|
|
DCorpsePointer (AActor *ptr);
|
|
void Destroy ();
|
|
void Serialize (FArchive &arc);
|
|
AActor *Corpse;
|
|
DWORD Count; // Only the first corpse pointer's count is valid.
|
|
private:
|
|
DCorpsePointer () {}
|
|
};
|
|
|
|
IMPLEMENT_POINTY_CLASS(DCorpsePointer)
|
|
DECLARE_POINTER(Corpse)
|
|
END_POINTERS
|
|
|
|
|
|
DCorpsePointer::DCorpsePointer (AActor *ptr)
|
|
: DThinker (STAT_CORPSEPOINTER), Corpse (ptr)
|
|
{
|
|
Count = 0;
|
|
|
|
// Thinkers are added to the end of their respective lists, so
|
|
// the first thinker in the list is the oldest one.
|
|
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
|
|
DCorpsePointer *first = iterator.Next ();
|
|
|
|
if (first != this)
|
|
{
|
|
if (first->Count >= CORPSEQUEUESIZE)
|
|
{
|
|
DCorpsePointer *next = iterator.Next ();
|
|
next->Count = first->Count;
|
|
first->Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
++first->Count;
|
|
}
|
|
|
|
void DCorpsePointer::Destroy ()
|
|
{
|
|
// Store the count of corpses in the first thinker in the list
|
|
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
|
|
DCorpsePointer *first = iterator.Next ();
|
|
|
|
int prevCount = first->Count;
|
|
|
|
if (first == this)
|
|
{
|
|
first = iterator.Next ();
|
|
}
|
|
|
|
if (first != NULL)
|
|
{
|
|
first->Count = prevCount - 1;
|
|
}
|
|
|
|
if (Corpse != NULL)
|
|
{
|
|
Corpse->Destroy ();
|
|
}
|
|
Super::Destroy ();
|
|
}
|
|
|
|
void DCorpsePointer::Serialize (FArchive &arc)
|
|
{
|
|
arc << Corpse << Count;
|
|
}
|
|
|
|
|
|
// throw another corpse on the queue
|
|
void A_QueueCorpse (AActor *actor)
|
|
{
|
|
new DCorpsePointer (actor);
|
|
}
|
|
|
|
// Remove an actor from the queue (for resurrection)
|
|
void A_DeQueueCorpse (AActor *actor)
|
|
{
|
|
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
|
|
DCorpsePointer *corpsePtr;
|
|
|
|
while ((corpsePtr = iterator.Next()) != NULL)
|
|
{
|
|
if (corpsePtr->Corpse == actor)
|
|
{
|
|
corpsePtr->Corpse = NULL;
|
|
corpsePtr->Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SetInvulnerable (AActor *actor)
|
|
{
|
|
actor->flags2 |= MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
void A_UnSetInvulnerable (AActor *actor)
|
|
{
|
|
actor->flags2 &= ~MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetReflective
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SetReflective (AActor *actor)
|
|
{
|
|
actor->flags2 |= MF2_REFLECTIVE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetReflective
|
|
//
|
|
//============================================================================
|
|
|
|
void A_UnSetReflective (AActor *actor)
|
|
{
|
|
actor->flags2 &= ~MF2_REFLECTIVE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetReflectiveInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
void A_SetReflectiveInvulnerable (AActor *actor)
|
|
{
|
|
actor->flags2 |= MF2_REFLECTIVE|MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetReflectiveInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
void A_UnSetReflectiveInvulnerable (AActor *actor)
|
|
{
|
|
actor->flags2 &= ~(MF2_REFLECTIVE|MF2_INVULNERABLE);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_SetShootable
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_SetShootable (AActor *actor)
|
|
{
|
|
actor->flags2 &= ~MF2_NONSHOOTABLE;
|
|
actor->flags |= MF_SHOOTABLE;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnSetShootable
|
|
//
|
|
//==========================================================================
|
|
|
|
void A_UnSetShootable (AActor *actor)
|
|
{
|
|
actor->flags2 |= MF2_NONSHOOTABLE;
|
|
actor->flags &= ~MF_SHOOTABLE;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_NoGravity
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_NoGravity (AActor *actor)
|
|
{
|
|
actor->flags |= MF_NOGRAVITY;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_Gravity
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_Gravity (AActor *actor)
|
|
{
|
|
actor->flags &= ~MF_NOGRAVITY;
|
|
actor->flags2 &= ~MF2_LOGRAV;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_LowGravity
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_LowGravity (AActor *actor)
|
|
{
|
|
actor->flags &= ~MF_NOGRAVITY;
|
|
actor->flags2 |= MF2_LOGRAV;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// FaceMovementDirection
|
|
//
|
|
//===========================================================================
|
|
|
|
void FaceMovementDirection (AActor *actor)
|
|
{
|
|
switch (actor->movedir)
|
|
{
|
|
case DI_EAST:
|
|
actor->angle = 0<<24;
|
|
break;
|
|
case DI_NORTHEAST:
|
|
actor->angle = 32<<24;
|
|
break;
|
|
case DI_NORTH:
|
|
actor->angle = 64<<24;
|
|
break;
|
|
case DI_NORTHWEST:
|
|
actor->angle = 96<<24;
|
|
break;
|
|
case DI_WEST:
|
|
actor->angle = 128<<24;
|
|
break;
|
|
case DI_SOUTHWEST:
|
|
actor->angle = 160<<24;
|
|
break;
|
|
case DI_SOUTH:
|
|
actor->angle = 192<<24;
|
|
break;
|
|
case DI_SOUTHEAST:
|
|
actor->angle = 224<<24;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_CheckBurnGone
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_CheckBurnGone (AActor *actor)
|
|
{
|
|
if (actor->player == NULL)
|
|
{
|
|
actor->Destroy ();
|
|
}
|
|
}
|